1 | //===-- lib/runtime/extrema.cpp ---------------------------------*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | // Implements MAXLOC, MINLOC, MAXVAL, & MINVAL for all required operand types |
10 | // and shapes and (for MAXLOC & MINLOC) result integer kinds. Also implements |
11 | // NORM2 using common infrastructure. |
12 | |
13 | #include "flang-rt/runtime/reduction-templates.h" |
14 | #include "flang/Common/float128.h" |
15 | #include "flang/Runtime/character.h" |
16 | #include "flang/Runtime/reduction.h" |
17 | #include <algorithm> |
18 | #include <cfloat> |
19 | #include <cinttypes> |
20 | #include <cmath> |
21 | #include <type_traits> |
22 | |
23 | namespace Fortran::runtime { |
24 | |
25 | // MAXLOC & MINLOC |
26 | |
27 | template <typename T, bool IS_MAX, bool BACK> struct NumericCompare { |
28 | using Type = T; |
29 | explicit RT_API_ATTRS NumericCompare(std::size_t /*elemLen; ignored*/) {} |
30 | RT_API_ATTRS bool operator()(const T &value, const T &previous) const { |
31 | if (std::is_floating_point_v<T> && previous != previous) { |
32 | return BACK || value == value; // replace NaN |
33 | } else if (value == previous) { |
34 | return BACK; |
35 | } else if constexpr (IS_MAX) { |
36 | return value > previous; |
37 | } else { |
38 | return value < previous; |
39 | } |
40 | } |
41 | }; |
42 | |
43 | template <typename T, bool IS_MAX, bool BACK> class CharacterCompare { |
44 | public: |
45 | using Type = T; |
46 | explicit RT_API_ATTRS CharacterCompare(std::size_t elemLen) |
47 | : chars_{elemLen / sizeof(T)} {} |
48 | RT_API_ATTRS bool operator()(const T &value, const T &previous) const { |
49 | int cmp{CharacterScalarCompare<T>(&value, &previous, chars_, chars_)}; |
50 | if (cmp == 0) { |
51 | return BACK; |
52 | } else if constexpr (IS_MAX) { |
53 | return cmp > 0; |
54 | } else { |
55 | return cmp < 0; |
56 | } |
57 | } |
58 | |
59 | private: |
60 | std::size_t chars_; |
61 | }; |
62 | |
63 | template <typename COMPARE> class ExtremumLocAccumulator { |
64 | public: |
65 | using Type = typename COMPARE::Type; |
66 | RT_API_ATTRS ExtremumLocAccumulator(const Descriptor &array) |
67 | : array_{array}, argRank_{array.rank()}, compare_{array.ElementBytes()} { |
68 | Reinitialize(); |
69 | } |
70 | RT_API_ATTRS void Reinitialize() { |
71 | // per standard: result indices are all zero if no data |
72 | for (int j{0}; j < argRank_; ++j) { |
73 | extremumLoc_[j] = 0; |
74 | } |
75 | previous_ = nullptr; |
76 | } |
77 | RT_API_ATTRS int argRank() const { return argRank_; } |
78 | template <typename A> |
79 | RT_API_ATTRS void GetResult(A *p, int zeroBasedDim = -1) { |
80 | if (zeroBasedDim >= 0) { |
81 | *p = extremumLoc_[zeroBasedDim]; |
82 | } else { |
83 | for (int j{0}; j < argRank_; ++j) { |
84 | p[j] = extremumLoc_[j]; |
85 | } |
86 | } |
87 | } |
88 | template <typename IGNORED> |
89 | RT_API_ATTRS bool AccumulateAt(const SubscriptValue at[]) { |
90 | const auto &value{*array_.Element<Type>(at)}; |
91 | if (!previous_ || compare_(value, *previous_)) { |
92 | previous_ = &value; |
93 | for (int j{0}; j < argRank_; ++j) { |
94 | extremumLoc_[j] = at[j] - array_.GetDimension(j).LowerBound() + 1; |
95 | } |
96 | } |
97 | return true; |
98 | } |
99 | |
100 | private: |
101 | const Descriptor &array_; |
102 | int argRank_; |
103 | SubscriptValue extremumLoc_[maxRank]; |
104 | const Type *previous_{nullptr}; |
105 | COMPARE compare_; |
106 | }; |
107 | |
108 | template <typename ACCUMULATOR, typename CPPTYPE> |
109 | static RT_API_ATTRS void LocationHelper(const char *intrinsic, |
110 | Descriptor &result, const Descriptor &x, int kind, const Descriptor *mask, |
111 | Terminator &terminator) { |
112 | ACCUMULATOR accumulator{x}; |
113 | DoTotalReduction<CPPTYPE>(x, 0, mask, accumulator, intrinsic, terminator); |
114 | ApplyIntegerKind<LocationResultHelper<ACCUMULATOR>::template Functor, void>( |
115 | kind, terminator, accumulator, result); |
116 | } |
117 | |
118 | template <TypeCategory CAT, int KIND, bool IS_MAX, |
119 | template <typename, bool, bool> class COMPARE> |
120 | inline RT_API_ATTRS void DoMaxOrMinLoc(const char *intrinsic, |
121 | Descriptor &result, const Descriptor &x, int kind, const char *source, |
122 | int line, const Descriptor *mask, bool back) { |
123 | using CppType = CppTypeFor<CAT, KIND>; |
124 | Terminator terminator{source, line}; |
125 | if (back) { |
126 | LocationHelper<ExtremumLocAccumulator<COMPARE<CppType, IS_MAX, true>>, |
127 | CppType>(intrinsic, result, x, kind, mask, terminator); |
128 | } else { |
129 | LocationHelper<ExtremumLocAccumulator<COMPARE<CppType, IS_MAX, false>>, |
130 | CppType>(intrinsic, result, x, kind, mask, terminator); |
131 | } |
132 | } |
133 | |
134 | template <bool IS_MAX> struct CharacterMaxOrMinLocHelper { |
135 | template <int KIND> struct Functor { |
136 | RT_API_ATTRS void operator()(const char *intrinsic, Descriptor &result, |
137 | const Descriptor &x, int kind, const char *source, int line, |
138 | const Descriptor *mask, bool back) const { |
139 | DoMaxOrMinLoc<TypeCategory::Character, KIND, IS_MAX, CharacterCompare>( |
140 | intrinsic, result, x, kind, source, line, mask, back); |
141 | } |
142 | }; |
143 | }; |
144 | |
145 | template <bool IS_MAX> |
146 | inline RT_API_ATTRS void CharacterMaxOrMinLoc(const char *intrinsic, |
147 | Descriptor &result, const Descriptor &x, int kind, const char *source, |
148 | int line, const Descriptor *mask, bool back) { |
149 | int rank{x.rank()}; |
150 | SubscriptValue extent[1]{rank}; |
151 | result.Establish(TypeCategory::Integer, kind, nullptr, 1, extent, |
152 | CFI_attribute_allocatable); |
153 | result.GetDimension(0).SetBounds(1, extent[0]); |
154 | Terminator terminator{source, line}; |
155 | if (int stat{result.Allocate(kNoAsyncObject)}) { |
156 | terminator.Crash( |
157 | "%s: could not allocate memory for result; STAT=%d" , intrinsic, stat); |
158 | } |
159 | CheckIntegerKind(terminator, kind, intrinsic); |
160 | auto catKind{x.type().GetCategoryAndKind()}; |
161 | RUNTIME_CHECK(terminator, catKind.has_value()); |
162 | switch (catKind->first) { |
163 | case TypeCategory::Character: |
164 | ApplyCharacterKind<CharacterMaxOrMinLocHelper<IS_MAX>::template Functor, |
165 | void>(catKind->second, terminator, intrinsic, result, x, kind, source, |
166 | line, mask, back); |
167 | break; |
168 | default: |
169 | terminator.Crash( |
170 | "%s: bad data type code (%d) for array" , intrinsic, x.type().raw()); |
171 | } |
172 | } |
173 | |
174 | template <TypeCategory CAT, int KIND, bool IS_MAXVAL> |
175 | inline RT_API_ATTRS void TotalNumericMaxOrMinLoc(const char *intrinsic, |
176 | Descriptor &result, const Descriptor &x, int kind, const char *source, |
177 | int line, const Descriptor *mask, bool back) { |
178 | int rank{x.rank()}; |
179 | SubscriptValue extent[1]{rank}; |
180 | result.Establish(TypeCategory::Integer, kind, nullptr, 1, extent, |
181 | CFI_attribute_allocatable); |
182 | result.GetDimension(0).SetBounds(1, extent[0]); |
183 | Terminator terminator{source, line}; |
184 | if (int stat{result.Allocate(kNoAsyncObject)}) { |
185 | terminator.Crash( |
186 | "%s: could not allocate memory for result; STAT=%d" , intrinsic, stat); |
187 | } |
188 | CheckIntegerKind(terminator, kind, intrinsic); |
189 | RUNTIME_CHECK(terminator, TypeCode(CAT, KIND) == x.type()); |
190 | DoMaxOrMinLoc<CAT, KIND, IS_MAXVAL, NumericCompare>( |
191 | intrinsic, result, x, kind, source, line, mask, back); |
192 | } |
193 | |
194 | extern "C" { |
195 | RT_EXT_API_GROUP_BEGIN |
196 | |
197 | void RTDEF(MaxlocCharacter)(Descriptor &result, const Descriptor &x, int kind, |
198 | const char *source, int line, const Descriptor *mask, bool back) { |
199 | CharacterMaxOrMinLoc<true>( |
200 | "MAXLOC" , result, x, kind, source, line, mask, back); |
201 | } |
202 | void RTDEF(MaxlocInteger1)(Descriptor &result, const Descriptor &x, int kind, |
203 | const char *source, int line, const Descriptor *mask, bool back) { |
204 | TotalNumericMaxOrMinLoc<TypeCategory::Integer, 1, true>( |
205 | "MAXLOC" , result, x, kind, source, line, mask, back); |
206 | } |
207 | void RTDEF(MaxlocInteger2)(Descriptor &result, const Descriptor &x, int kind, |
208 | const char *source, int line, const Descriptor *mask, bool back) { |
209 | TotalNumericMaxOrMinLoc<TypeCategory::Integer, 2, true>( |
210 | "MAXLOC" , result, x, kind, source, line, mask, back); |
211 | } |
212 | void RTDEF(MaxlocInteger4)(Descriptor &result, const Descriptor &x, int kind, |
213 | const char *source, int line, const Descriptor *mask, bool back) { |
214 | TotalNumericMaxOrMinLoc<TypeCategory::Integer, 4, true>( |
215 | "MAXLOC" , result, x, kind, source, line, mask, back); |
216 | } |
217 | void RTDEF(MaxlocInteger8)(Descriptor &result, const Descriptor &x, int kind, |
218 | const char *source, int line, const Descriptor *mask, bool back) { |
219 | TotalNumericMaxOrMinLoc<TypeCategory::Integer, 8, true>( |
220 | "MAXLOC" , result, x, kind, source, line, mask, back); |
221 | } |
222 | #ifdef __SIZEOF_INT128__ |
223 | void RTDEF(MaxlocInteger16)(Descriptor &result, const Descriptor &x, int kind, |
224 | const char *source, int line, const Descriptor *mask, bool back) { |
225 | TotalNumericMaxOrMinLoc<TypeCategory::Integer, 16, true>( |
226 | "MAXLOC" , result, x, kind, source, line, mask, back); |
227 | } |
228 | #endif |
229 | void RTDEF(MaxlocUnsigned1)(Descriptor &result, const Descriptor &x, int kind, |
230 | const char *source, int line, const Descriptor *mask, bool back) { |
231 | TotalNumericMaxOrMinLoc<TypeCategory::Unsigned, 1, true>( |
232 | "MAXLOC" , result, x, kind, source, line, mask, back); |
233 | } |
234 | void RTDEF(MaxlocUnsigned2)(Descriptor &result, const Descriptor &x, int kind, |
235 | const char *source, int line, const Descriptor *mask, bool back) { |
236 | TotalNumericMaxOrMinLoc<TypeCategory::Unsigned, 2, true>( |
237 | "MAXLOC" , result, x, kind, source, line, mask, back); |
238 | } |
239 | void RTDEF(MaxlocUnsigned4)(Descriptor &result, const Descriptor &x, int kind, |
240 | const char *source, int line, const Descriptor *mask, bool back) { |
241 | TotalNumericMaxOrMinLoc<TypeCategory::Unsigned, 4, true>( |
242 | "MAXLOC" , result, x, kind, source, line, mask, back); |
243 | } |
244 | void RTDEF(MaxlocUnsigned8)(Descriptor &result, const Descriptor &x, int kind, |
245 | const char *source, int line, const Descriptor *mask, bool back) { |
246 | TotalNumericMaxOrMinLoc<TypeCategory::Unsigned, 8, true>( |
247 | "MAXLOC" , result, x, kind, source, line, mask, back); |
248 | } |
249 | #ifdef __SIZEOF_INT128__ |
250 | void RTDEF(MaxlocUnsigned16)(Descriptor &result, const Descriptor &x, int kind, |
251 | const char *source, int line, const Descriptor *mask, bool back) { |
252 | TotalNumericMaxOrMinLoc<TypeCategory::Unsigned, 16, true>( |
253 | "MAXLOC" , result, x, kind, source, line, mask, back); |
254 | } |
255 | #endif |
256 | void RTDEF(MaxlocReal4)(Descriptor &result, const Descriptor &x, int kind, |
257 | const char *source, int line, const Descriptor *mask, bool back) { |
258 | TotalNumericMaxOrMinLoc<TypeCategory::Real, 4, true>( |
259 | "MAXLOC" , result, x, kind, source, line, mask, back); |
260 | } |
261 | void RTDEF(MaxlocReal8)(Descriptor &result, const Descriptor &x, int kind, |
262 | const char *source, int line, const Descriptor *mask, bool back) { |
263 | TotalNumericMaxOrMinLoc<TypeCategory::Real, 8, true>( |
264 | "MAXLOC" , result, x, kind, source, line, mask, back); |
265 | } |
266 | #if HAS_FLOAT80 |
267 | void RTDEF(MaxlocReal10)(Descriptor &result, const Descriptor &x, int kind, |
268 | const char *source, int line, const Descriptor *mask, bool back) { |
269 | TotalNumericMaxOrMinLoc<TypeCategory::Real, 10, true>( |
270 | "MAXLOC" , result, x, kind, source, line, mask, back); |
271 | } |
272 | #endif |
273 | #if HAS_LDBL128 || HAS_FLOAT128 |
274 | void RTDEF(MaxlocReal16)(Descriptor &result, const Descriptor &x, int kind, |
275 | const char *source, int line, const Descriptor *mask, bool back) { |
276 | TotalNumericMaxOrMinLoc<TypeCategory::Real, 16, true>( |
277 | "MAXLOC" , result, x, kind, source, line, mask, back); |
278 | } |
279 | #endif |
280 | void RTDEF(MinlocCharacter)(Descriptor &result, const Descriptor &x, int kind, |
281 | const char *source, int line, const Descriptor *mask, bool back) { |
282 | CharacterMaxOrMinLoc<false>( |
283 | "MINLOC" , result, x, kind, source, line, mask, back); |
284 | } |
285 | void RTDEF(MinlocInteger1)(Descriptor &result, const Descriptor &x, int kind, |
286 | const char *source, int line, const Descriptor *mask, bool back) { |
287 | TotalNumericMaxOrMinLoc<TypeCategory::Integer, 1, false>( |
288 | "MINLOC" , result, x, kind, source, line, mask, back); |
289 | } |
290 | void RTDEF(MinlocInteger2)(Descriptor &result, const Descriptor &x, int kind, |
291 | const char *source, int line, const Descriptor *mask, bool back) { |
292 | TotalNumericMaxOrMinLoc<TypeCategory::Integer, 2, false>( |
293 | "MINLOC" , result, x, kind, source, line, mask, back); |
294 | } |
295 | void RTDEF(MinlocInteger4)(Descriptor &result, const Descriptor &x, int kind, |
296 | const char *source, int line, const Descriptor *mask, bool back) { |
297 | TotalNumericMaxOrMinLoc<TypeCategory::Integer, 4, false>( |
298 | "MINLOC" , result, x, kind, source, line, mask, back); |
299 | } |
300 | void RTDEF(MinlocInteger8)(Descriptor &result, const Descriptor &x, int kind, |
301 | const char *source, int line, const Descriptor *mask, bool back) { |
302 | TotalNumericMaxOrMinLoc<TypeCategory::Integer, 8, false>( |
303 | "MINLOC" , result, x, kind, source, line, mask, back); |
304 | } |
305 | #ifdef __SIZEOF_INT128__ |
306 | void RTDEF(MinlocInteger16)(Descriptor &result, const Descriptor &x, int kind, |
307 | const char *source, int line, const Descriptor *mask, bool back) { |
308 | TotalNumericMaxOrMinLoc<TypeCategory::Integer, 16, false>( |
309 | "MINLOC" , result, x, kind, source, line, mask, back); |
310 | } |
311 | #endif |
312 | void RTDEF(MinlocUnsigned1)(Descriptor &result, const Descriptor &x, int kind, |
313 | const char *source, int line, const Descriptor *mask, bool back) { |
314 | TotalNumericMaxOrMinLoc<TypeCategory::Unsigned, 1, false>( |
315 | "MINLOC" , result, x, kind, source, line, mask, back); |
316 | } |
317 | void RTDEF(MinlocUnsigned2)(Descriptor &result, const Descriptor &x, int kind, |
318 | const char *source, int line, const Descriptor *mask, bool back) { |
319 | TotalNumericMaxOrMinLoc<TypeCategory::Unsigned, 2, false>( |
320 | "MINLOC" , result, x, kind, source, line, mask, back); |
321 | } |
322 | void RTDEF(MinlocUnsigned4)(Descriptor &result, const Descriptor &x, int kind, |
323 | const char *source, int line, const Descriptor *mask, bool back) { |
324 | TotalNumericMaxOrMinLoc<TypeCategory::Unsigned, 4, false>( |
325 | "MINLOC" , result, x, kind, source, line, mask, back); |
326 | } |
327 | void RTDEF(MinlocUnsigned8)(Descriptor &result, const Descriptor &x, int kind, |
328 | const char *source, int line, const Descriptor *mask, bool back) { |
329 | TotalNumericMaxOrMinLoc<TypeCategory::Unsigned, 8, false>( |
330 | "MINLOC" , result, x, kind, source, line, mask, back); |
331 | } |
332 | #ifdef __SIZEOF_INT128__ |
333 | void RTDEF(MinlocUnsigned16)(Descriptor &result, const Descriptor &x, int kind, |
334 | const char *source, int line, const Descriptor *mask, bool back) { |
335 | TotalNumericMaxOrMinLoc<TypeCategory::Unsigned, 16, false>( |
336 | "MINLOC" , result, x, kind, source, line, mask, back); |
337 | } |
338 | #endif |
339 | void RTDEF(MinlocReal4)(Descriptor &result, const Descriptor &x, int kind, |
340 | const char *source, int line, const Descriptor *mask, bool back) { |
341 | TotalNumericMaxOrMinLoc<TypeCategory::Real, 4, false>( |
342 | "MINLOC" , result, x, kind, source, line, mask, back); |
343 | } |
344 | void RTDEF(MinlocReal8)(Descriptor &result, const Descriptor &x, int kind, |
345 | const char *source, int line, const Descriptor *mask, bool back) { |
346 | TotalNumericMaxOrMinLoc<TypeCategory::Real, 8, false>( |
347 | "MINLOC" , result, x, kind, source, line, mask, back); |
348 | } |
349 | #if HAS_FLOAT80 |
350 | void RTDEF(MinlocReal10)(Descriptor &result, const Descriptor &x, int kind, |
351 | const char *source, int line, const Descriptor *mask, bool back) { |
352 | TotalNumericMaxOrMinLoc<TypeCategory::Real, 10, false>( |
353 | "MINLOC" , result, x, kind, source, line, mask, back); |
354 | } |
355 | #endif |
356 | #if HAS_LDBL128 || HAS_FLOAT128 |
357 | void RTDEF(MinlocReal16)(Descriptor &result, const Descriptor &x, int kind, |
358 | const char *source, int line, const Descriptor *mask, bool back) { |
359 | TotalNumericMaxOrMinLoc<TypeCategory::Real, 16, false>( |
360 | "MINLOC" , result, x, kind, source, line, mask, back); |
361 | } |
362 | #endif |
363 | |
364 | RT_EXT_API_GROUP_END |
365 | } // extern "C" |
366 | |
367 | // MAXLOC/MINLOC with DIM= |
368 | |
369 | template <TypeCategory CAT, int KIND, bool IS_MAX, |
370 | template <typename, bool, bool> class COMPARE, bool BACK> |
371 | static RT_API_ATTRS void DoPartialMaxOrMinLocDirection(const char *intrinsic, |
372 | Descriptor &result, const Descriptor &x, int kind, int dim, |
373 | const Descriptor *mask, Terminator &terminator) { |
374 | using CppType = CppTypeFor<CAT, KIND>; |
375 | using Accumulator = ExtremumLocAccumulator<COMPARE<CppType, IS_MAX, BACK>>; |
376 | Accumulator accumulator{x}; |
377 | ApplyIntegerKind<PartialLocationHelper<Accumulator>::template Functor, void>( |
378 | kind, terminator, result, x, dim, mask, terminator, intrinsic, |
379 | accumulator); |
380 | } |
381 | |
382 | template <TypeCategory CAT, int KIND, bool IS_MAX, |
383 | template <typename, bool, bool> class COMPARE> |
384 | inline RT_API_ATTRS void DoPartialMaxOrMinLoc(const char *intrinsic, |
385 | Descriptor &result, const Descriptor &x, int kind, int dim, |
386 | const Descriptor *mask, bool back, Terminator &terminator) { |
387 | if (back) { |
388 | DoPartialMaxOrMinLocDirection<CAT, KIND, IS_MAX, COMPARE, true>( |
389 | intrinsic, result, x, kind, dim, mask, terminator); |
390 | } else { |
391 | DoPartialMaxOrMinLocDirection<CAT, KIND, IS_MAX, COMPARE, false>( |
392 | intrinsic, result, x, kind, dim, mask, terminator); |
393 | } |
394 | } |
395 | |
396 | template <TypeCategory CAT, bool IS_MAX, |
397 | template <typename, bool, bool> class COMPARE> |
398 | struct DoPartialMaxOrMinLocHelper { |
399 | template <int KIND> struct Functor { |
400 | RT_API_ATTRS void operator()(const char *intrinsic, Descriptor &result, |
401 | const Descriptor &x, int kind, int dim, const Descriptor *mask, |
402 | bool back, Terminator &terminator) const { |
403 | DoPartialMaxOrMinLoc<CAT, KIND, IS_MAX, COMPARE>( |
404 | intrinsic, result, x, kind, dim, mask, back, terminator); |
405 | } |
406 | }; |
407 | }; |
408 | |
409 | template <bool IS_MAX> |
410 | inline RT_API_ATTRS void TypedPartialMaxOrMinLoc(const char *intrinsic, |
411 | Descriptor &result, const Descriptor &x, int kind, int dim, |
412 | const char *source, int line, const Descriptor *mask, bool back) { |
413 | Terminator terminator{source, line}; |
414 | CheckIntegerKind(terminator, kind, intrinsic); |
415 | auto catKind{x.type().GetCategoryAndKind()}; |
416 | RUNTIME_CHECK(terminator, catKind.has_value()); |
417 | const Descriptor *maskToUse{mask}; |
418 | SubscriptValue maskAt[maxRank]; // contents unused |
419 | if (mask && mask->rank() == 0) { |
420 | if (IsLogicalElementTrue(*mask, maskAt)) { |
421 | // A scalar MASK that's .TRUE. In this case, just get rid of the MASK. |
422 | maskToUse = nullptr; |
423 | } else { |
424 | // For scalar MASK arguments that are .FALSE., return all zeroes |
425 | |
426 | // Element size of the destination descriptor is the size |
427 | // of {TypeCategory::Integer, kind}. |
428 | CreatePartialReductionResult(result, x, |
429 | Descriptor::BytesFor(TypeCategory::Integer, kind), dim, terminator, |
430 | intrinsic, TypeCode{TypeCategory::Integer, kind}); |
431 | std::memset( |
432 | result.OffsetElement(), 0, result.Elements() * result.ElementBytes()); |
433 | return; |
434 | } |
435 | } |
436 | switch (catKind->first) { |
437 | case TypeCategory::Integer: |
438 | ApplyIntegerKind<DoPartialMaxOrMinLocHelper<TypeCategory::Integer, IS_MAX, |
439 | NumericCompare>::template Functor, |
440 | void>(catKind->second, terminator, intrinsic, result, x, kind, dim, |
441 | maskToUse, back, terminator); |
442 | break; |
443 | case TypeCategory::Unsigned: |
444 | ApplyIntegerKind<DoPartialMaxOrMinLocHelper<TypeCategory::Unsigned, IS_MAX, |
445 | NumericCompare>::template Functor, |
446 | void>(catKind->second, terminator, intrinsic, result, x, kind, dim, |
447 | maskToUse, back, terminator); |
448 | break; |
449 | case TypeCategory::Real: |
450 | ApplyFloatingPointKind<DoPartialMaxOrMinLocHelper<TypeCategory::Real, |
451 | IS_MAX, NumericCompare>::template Functor, |
452 | void>(catKind->second, terminator, intrinsic, result, x, kind, dim, |
453 | maskToUse, back, terminator); |
454 | break; |
455 | case TypeCategory::Character: |
456 | ApplyCharacterKind<DoPartialMaxOrMinLocHelper<TypeCategory::Character, |
457 | IS_MAX, CharacterCompare>::template Functor, |
458 | void>(catKind->second, terminator, intrinsic, result, x, kind, dim, |
459 | maskToUse, back, terminator); |
460 | break; |
461 | default: |
462 | terminator.Crash( |
463 | "%s: bad data type code (%d) for array" , intrinsic, x.type().raw()); |
464 | } |
465 | } |
466 | |
467 | extern "C" { |
468 | RT_EXT_API_GROUP_BEGIN |
469 | |
470 | void RTDEF(MaxlocDim)(Descriptor &result, const Descriptor &x, int kind, |
471 | int dim, const char *source, int line, const Descriptor *mask, bool back) { |
472 | TypedPartialMaxOrMinLoc<true>( |
473 | "MAXLOC" , result, x, kind, dim, source, line, mask, back); |
474 | } |
475 | void RTDEF(MinlocDim)(Descriptor &result, const Descriptor &x, int kind, |
476 | int dim, const char *source, int line, const Descriptor *mask, bool back) { |
477 | TypedPartialMaxOrMinLoc<false>( |
478 | "MINLOC" , result, x, kind, dim, source, line, mask, back); |
479 | } |
480 | |
481 | RT_EXT_API_GROUP_END |
482 | } // extern "C" |
483 | |
484 | // MAXVAL and MINVAL |
485 | |
486 | template <TypeCategory CAT, int KIND, bool IS_MAXVAL> |
487 | class NumericExtremumAccumulator { |
488 | public: |
489 | using Type = CppTypeFor<CAT, KIND>; |
490 | explicit RT_API_ATTRS NumericExtremumAccumulator(const Descriptor &array) |
491 | : array_{array} {} |
492 | RT_API_ATTRS void Reinitialize() { |
493 | any_ = false; |
494 | extremum_ = MaxOrMinIdentity<CAT, KIND, IS_MAXVAL>::Value(); |
495 | } |
496 | template <typename A> |
497 | RT_API_ATTRS void GetResult(A *p, int /*zeroBasedDim*/ = -1) const { |
498 | *p = extremum_; |
499 | } |
500 | RT_API_ATTRS bool Accumulate(Type x) { |
501 | if (!any_) { |
502 | extremum_ = x; |
503 | any_ = true; |
504 | } else if (CAT == TypeCategory::Real && extremum_ != extremum_) { |
505 | extremum_ = x; // replace NaN |
506 | } else if constexpr (IS_MAXVAL) { |
507 | if (x > extremum_) { |
508 | extremum_ = x; |
509 | } |
510 | } else if (x < extremum_) { |
511 | extremum_ = x; |
512 | } |
513 | return true; |
514 | } |
515 | template <typename A> |
516 | RT_API_ATTRS bool AccumulateAt(const SubscriptValue at[]) { |
517 | return Accumulate(*array_.Element<A>(at)); |
518 | } |
519 | |
520 | private: |
521 | const Descriptor &array_; |
522 | bool any_{false}; |
523 | Type extremum_{MaxOrMinIdentity<CAT, KIND, IS_MAXVAL>::Value()}; |
524 | }; |
525 | |
526 | template <TypeCategory CAT, int KIND, bool IS_MAXVAL> |
527 | inline RT_API_ATTRS CppTypeFor<CAT, KIND> TotalNumericMaxOrMin( |
528 | const Descriptor &x, const char *source, int line, int dim, |
529 | const Descriptor *mask, const char *intrinsic) { |
530 | return GetTotalReduction<CAT, KIND>(x, source, line, dim, mask, |
531 | NumericExtremumAccumulator<CAT, KIND, IS_MAXVAL>{x}, intrinsic); |
532 | } |
533 | |
534 | template <TypeCategory CAT, bool IS_MAXVAL> struct MaxOrMinHelper { |
535 | template <int KIND> struct Functor { |
536 | RT_API_ATTRS void operator()(Descriptor &result, const Descriptor &x, |
537 | int dim, const Descriptor *mask, const char *intrinsic, |
538 | Terminator &terminator) const { |
539 | DoMaxMinNorm2<CAT, KIND, |
540 | NumericExtremumAccumulator<CAT, KIND, IS_MAXVAL>>( |
541 | result, x, dim, mask, intrinsic, terminator); |
542 | } |
543 | }; |
544 | }; |
545 | |
546 | template <bool IS_MAXVAL> |
547 | inline RT_API_ATTRS void NumericMaxOrMin(Descriptor &result, |
548 | const Descriptor &x, int dim, const char *source, int line, |
549 | const Descriptor *mask, const char *intrinsic) { |
550 | Terminator terminator{source, line}; |
551 | auto type{x.type().GetCategoryAndKind()}; |
552 | RUNTIME_CHECK(terminator, type); |
553 | switch (type->first) { |
554 | case TypeCategory::Integer: |
555 | ApplyIntegerKind< |
556 | MaxOrMinHelper<TypeCategory::Integer, IS_MAXVAL>::template Functor, |
557 | void>( |
558 | type->second, terminator, result, x, dim, mask, intrinsic, terminator); |
559 | break; |
560 | case TypeCategory::Unsigned: |
561 | ApplyIntegerKind< |
562 | MaxOrMinHelper<TypeCategory::Unsigned, IS_MAXVAL>::template Functor, |
563 | void>( |
564 | type->second, terminator, result, x, dim, mask, intrinsic, terminator); |
565 | break; |
566 | case TypeCategory::Real: |
567 | ApplyFloatingPointKind< |
568 | MaxOrMinHelper<TypeCategory::Real, IS_MAXVAL>::template Functor, void>( |
569 | type->second, terminator, result, x, dim, mask, intrinsic, terminator); |
570 | break; |
571 | default: |
572 | terminator.Crash("%s: bad type code %d" , intrinsic, x.type().raw()); |
573 | } |
574 | } |
575 | |
576 | template <int KIND, bool IS_MAXVAL> class CharacterExtremumAccumulator { |
577 | public: |
578 | using Type = CppTypeFor<TypeCategory::Character, KIND>; |
579 | explicit RT_API_ATTRS CharacterExtremumAccumulator(const Descriptor &array) |
580 | : array_{array}, charLen_{array_.ElementBytes() / KIND} {} |
581 | RT_API_ATTRS void Reinitialize() { extremum_ = nullptr; } |
582 | template <typename A> |
583 | RT_API_ATTRS void GetResult(A *p, int /*zeroBasedDim*/ = -1) const { |
584 | static_assert(std::is_same_v<A, Type>); |
585 | std::size_t byteSize{array_.ElementBytes()}; |
586 | if (extremum_) { |
587 | std::memcpy(p, extremum_, byteSize); |
588 | } else { |
589 | // Empty array; fill with character 0 for MAXVAL. |
590 | // For MINVAL, set all of the bits. |
591 | std::memset(p, IS_MAXVAL ? 0 : 255, byteSize); |
592 | } |
593 | } |
594 | RT_API_ATTRS bool Accumulate(const Type *x) { |
595 | if (!extremum_) { |
596 | extremum_ = x; |
597 | } else { |
598 | int cmp{CharacterScalarCompare(x, extremum_, charLen_, charLen_)}; |
599 | if (IS_MAXVAL == (cmp > 0)) { |
600 | extremum_ = x; |
601 | } |
602 | } |
603 | return true; |
604 | } |
605 | template <typename A> |
606 | RT_API_ATTRS bool AccumulateAt(const SubscriptValue at[]) { |
607 | return Accumulate(array_.Element<A>(at)); |
608 | } |
609 | |
610 | private: |
611 | const Descriptor &array_; |
612 | std::size_t charLen_; |
613 | const Type *extremum_{nullptr}; |
614 | }; |
615 | |
616 | template <bool IS_MAXVAL> struct CharacterMaxOrMinHelper { |
617 | template <int KIND> struct Functor { |
618 | RT_API_ATTRS void operator()(Descriptor &result, const Descriptor &x, |
619 | int dim, const Descriptor *mask, const char *intrinsic, |
620 | Terminator &terminator) const { |
621 | DoMaxMinNorm2<TypeCategory::Character, KIND, |
622 | CharacterExtremumAccumulator<KIND, IS_MAXVAL>>( |
623 | result, x, dim, mask, intrinsic, terminator); |
624 | } |
625 | }; |
626 | }; |
627 | |
628 | template <bool IS_MAXVAL> |
629 | inline RT_API_ATTRS void CharacterMaxOrMin(Descriptor &result, |
630 | const Descriptor &x, int dim, const char *source, int line, |
631 | const Descriptor *mask, const char *intrinsic) { |
632 | Terminator terminator{source, line}; |
633 | auto type{x.type().GetCategoryAndKind()}; |
634 | RUNTIME_CHECK(terminator, type && type->first == TypeCategory::Character); |
635 | ApplyCharacterKind<CharacterMaxOrMinHelper<IS_MAXVAL>::template Functor, |
636 | void>( |
637 | type->second, terminator, result, x, dim, mask, intrinsic, terminator); |
638 | } |
639 | |
640 | extern "C" { |
641 | RT_EXT_API_GROUP_BEGIN |
642 | |
643 | CppTypeFor<TypeCategory::Integer, 1> RTDEF(MaxvalInteger1)(const Descriptor &x, |
644 | const char *source, int line, int dim, const Descriptor *mask) { |
645 | return TotalNumericMaxOrMin<TypeCategory::Integer, 1, true>( |
646 | x, source, line, dim, mask, "MAXVAL" ); |
647 | } |
648 | CppTypeFor<TypeCategory::Integer, 2> RTDEF(MaxvalInteger2)(const Descriptor &x, |
649 | const char *source, int line, int dim, const Descriptor *mask) { |
650 | return TotalNumericMaxOrMin<TypeCategory::Integer, 2, true>( |
651 | x, source, line, dim, mask, "MAXVAL" ); |
652 | } |
653 | CppTypeFor<TypeCategory::Integer, 4> RTDEF(MaxvalInteger4)(const Descriptor &x, |
654 | const char *source, int line, int dim, const Descriptor *mask) { |
655 | return TotalNumericMaxOrMin<TypeCategory::Integer, 4, true>( |
656 | x, source, line, dim, mask, "MAXVAL" ); |
657 | } |
658 | CppTypeFor<TypeCategory::Integer, 8> RTDEF(MaxvalInteger8)(const Descriptor &x, |
659 | const char *source, int line, int dim, const Descriptor *mask) { |
660 | return TotalNumericMaxOrMin<TypeCategory::Integer, 8, true>( |
661 | x, source, line, dim, mask, "MAXVAL" ); |
662 | } |
663 | #ifdef __SIZEOF_INT128__ |
664 | CppTypeFor<TypeCategory::Integer, 16> RTDEF(MaxvalInteger16)( |
665 | const Descriptor &x, const char *source, int line, int dim, |
666 | const Descriptor *mask) { |
667 | return TotalNumericMaxOrMin<TypeCategory::Integer, 16, true>( |
668 | x, source, line, dim, mask, "MAXVAL" ); |
669 | } |
670 | #endif |
671 | |
672 | CppTypeFor<TypeCategory::Unsigned, 1> RTDEF(MaxvalUnsigned1)( |
673 | const Descriptor &x, const char *source, int line, int dim, |
674 | const Descriptor *mask) { |
675 | return TotalNumericMaxOrMin<TypeCategory::Unsigned, 1, true>( |
676 | x, source, line, dim, mask, "MAXVAL" ); |
677 | } |
678 | CppTypeFor<TypeCategory::Unsigned, 2> RTDEF(MaxvalUnsigned2)( |
679 | const Descriptor &x, const char *source, int line, int dim, |
680 | const Descriptor *mask) { |
681 | return TotalNumericMaxOrMin<TypeCategory::Unsigned, 2, true>( |
682 | x, source, line, dim, mask, "MAXVAL" ); |
683 | } |
684 | CppTypeFor<TypeCategory::Unsigned, 4> RTDEF(MaxvalUnsigned4)( |
685 | const Descriptor &x, const char *source, int line, int dim, |
686 | const Descriptor *mask) { |
687 | return TotalNumericMaxOrMin<TypeCategory::Unsigned, 4, true>( |
688 | x, source, line, dim, mask, "MAXVAL" ); |
689 | } |
690 | CppTypeFor<TypeCategory::Unsigned, 8> RTDEF(MaxvalUnsigned8)( |
691 | const Descriptor &x, const char *source, int line, int dim, |
692 | const Descriptor *mask) { |
693 | return TotalNumericMaxOrMin<TypeCategory::Unsigned, 8, true>( |
694 | x, source, line, dim, mask, "MAXVAL" ); |
695 | } |
696 | #ifdef __SIZEOF_INT128__ |
697 | CppTypeFor<TypeCategory::Unsigned, 16> RTDEF(MaxvalUnsigned16)( |
698 | const Descriptor &x, const char *source, int line, int dim, |
699 | const Descriptor *mask) { |
700 | return TotalNumericMaxOrMin<TypeCategory::Unsigned, 16, true>( |
701 | x, source, line, dim, mask, "MAXVAL" ); |
702 | } |
703 | #endif |
704 | |
705 | // TODO: REAL(2 & 3) |
706 | CppTypeFor<TypeCategory::Real, 4> RTDEF(MaxvalReal4)(const Descriptor &x, |
707 | const char *source, int line, int dim, const Descriptor *mask) { |
708 | return TotalNumericMaxOrMin<TypeCategory::Real, 4, true>( |
709 | x, source, line, dim, mask, "MAXVAL" ); |
710 | } |
711 | CppTypeFor<TypeCategory::Real, 8> RTDEF(MaxvalReal8)(const Descriptor &x, |
712 | const char *source, int line, int dim, const Descriptor *mask) { |
713 | return TotalNumericMaxOrMin<TypeCategory::Real, 8, true>( |
714 | x, source, line, dim, mask, "MAXVAL" ); |
715 | } |
716 | #if HAS_FLOAT80 |
717 | CppTypeFor<TypeCategory::Real, 10> RTDEF(MaxvalReal10)(const Descriptor &x, |
718 | const char *source, int line, int dim, const Descriptor *mask) { |
719 | return TotalNumericMaxOrMin<TypeCategory::Real, 10, true>( |
720 | x, source, line, dim, mask, "MAXVAL" ); |
721 | } |
722 | #endif |
723 | #if HAS_LDBL128 || HAS_FLOAT128 |
724 | CppTypeFor<TypeCategory::Real, 16> RTDEF(MaxvalReal16)(const Descriptor &x, |
725 | const char *source, int line, int dim, const Descriptor *mask) { |
726 | return TotalNumericMaxOrMin<TypeCategory::Real, 16, true>( |
727 | x, source, line, dim, mask, "MAXVAL" ); |
728 | } |
729 | #endif |
730 | |
731 | void RTDEF(MaxvalCharacter)(Descriptor &result, const Descriptor &x, |
732 | const char *source, int line, const Descriptor *mask) { |
733 | CharacterMaxOrMin<true>(result, x, 0, source, line, mask, "MAXVAL" ); |
734 | } |
735 | |
736 | CppTypeFor<TypeCategory::Integer, 1> RTDEF(MinvalInteger1)(const Descriptor &x, |
737 | const char *source, int line, int dim, const Descriptor *mask) { |
738 | return TotalNumericMaxOrMin<TypeCategory::Integer, 1, false>( |
739 | x, source, line, dim, mask, "MINVAL" ); |
740 | } |
741 | CppTypeFor<TypeCategory::Integer, 2> RTDEF(MinvalInteger2)(const Descriptor &x, |
742 | const char *source, int line, int dim, const Descriptor *mask) { |
743 | return TotalNumericMaxOrMin<TypeCategory::Integer, 2, false>( |
744 | x, source, line, dim, mask, "MINVAL" ); |
745 | } |
746 | CppTypeFor<TypeCategory::Integer, 4> RTDEF(MinvalInteger4)(const Descriptor &x, |
747 | const char *source, int line, int dim, const Descriptor *mask) { |
748 | return TotalNumericMaxOrMin<TypeCategory::Integer, 4, false>( |
749 | x, source, line, dim, mask, "MINVAL" ); |
750 | } |
751 | CppTypeFor<TypeCategory::Integer, 8> RTDEF(MinvalInteger8)(const Descriptor &x, |
752 | const char *source, int line, int dim, const Descriptor *mask) { |
753 | return TotalNumericMaxOrMin<TypeCategory::Integer, 8, false>( |
754 | x, source, line, dim, mask, "MINVAL" ); |
755 | } |
756 | #ifdef __SIZEOF_INT128__ |
757 | CppTypeFor<TypeCategory::Integer, 16> RTDEF(MinvalInteger16)( |
758 | const Descriptor &x, const char *source, int line, int dim, |
759 | const Descriptor *mask) { |
760 | return TotalNumericMaxOrMin<TypeCategory::Integer, 16, false>( |
761 | x, source, line, dim, mask, "MINVAL" ); |
762 | } |
763 | #endif |
764 | |
765 | CppTypeFor<TypeCategory::Unsigned, 1> RTDEF(MinvalUnsigned1)( |
766 | const Descriptor &x, const char *source, int line, int dim, |
767 | const Descriptor *mask) { |
768 | return TotalNumericMaxOrMin<TypeCategory::Unsigned, 1, false>( |
769 | x, source, line, dim, mask, "MINVAL" ); |
770 | } |
771 | CppTypeFor<TypeCategory::Unsigned, 2> RTDEF(MinvalUnsigned2)( |
772 | const Descriptor &x, const char *source, int line, int dim, |
773 | const Descriptor *mask) { |
774 | return TotalNumericMaxOrMin<TypeCategory::Unsigned, 2, false>( |
775 | x, source, line, dim, mask, "MINVAL" ); |
776 | } |
777 | CppTypeFor<TypeCategory::Unsigned, 4> RTDEF(MinvalUnsigned4)( |
778 | const Descriptor &x, const char *source, int line, int dim, |
779 | const Descriptor *mask) { |
780 | return TotalNumericMaxOrMin<TypeCategory::Unsigned, 4, false>( |
781 | x, source, line, dim, mask, "MINVAL" ); |
782 | } |
783 | CppTypeFor<TypeCategory::Unsigned, 8> RTDEF(MinvalUnsigned8)( |
784 | const Descriptor &x, const char *source, int line, int dim, |
785 | const Descriptor *mask) { |
786 | return TotalNumericMaxOrMin<TypeCategory::Unsigned, 8, false>( |
787 | x, source, line, dim, mask, "MINVAL" ); |
788 | } |
789 | #ifdef __SIZEOF_INT128__ |
790 | CppTypeFor<TypeCategory::Unsigned, 16> RTDEF(MinvalUnsigned16)( |
791 | const Descriptor &x, const char *source, int line, int dim, |
792 | const Descriptor *mask) { |
793 | return TotalNumericMaxOrMin<TypeCategory::Unsigned, 16, false>( |
794 | x, source, line, dim, mask, "MINVAL" ); |
795 | } |
796 | #endif |
797 | |
798 | // TODO: REAL(2 & 3) |
799 | CppTypeFor<TypeCategory::Real, 4> RTDEF(MinvalReal4)(const Descriptor &x, |
800 | const char *source, int line, int dim, const Descriptor *mask) { |
801 | return TotalNumericMaxOrMin<TypeCategory::Real, 4, false>( |
802 | x, source, line, dim, mask, "MINVAL" ); |
803 | } |
804 | CppTypeFor<TypeCategory::Real, 8> RTDEF(MinvalReal8)(const Descriptor &x, |
805 | const char *source, int line, int dim, const Descriptor *mask) { |
806 | return TotalNumericMaxOrMin<TypeCategory::Real, 8, false>( |
807 | x, source, line, dim, mask, "MINVAL" ); |
808 | } |
809 | #if HAS_FLOAT80 |
810 | CppTypeFor<TypeCategory::Real, 10> RTDEF(MinvalReal10)(const Descriptor &x, |
811 | const char *source, int line, int dim, const Descriptor *mask) { |
812 | return TotalNumericMaxOrMin<TypeCategory::Real, 10, false>( |
813 | x, source, line, dim, mask, "MINVAL" ); |
814 | } |
815 | #endif |
816 | #if HAS_LDBL128 || HAS_FLOAT128 |
817 | CppTypeFor<TypeCategory::Real, 16> RTDEF(MinvalReal16)(const Descriptor &x, |
818 | const char *source, int line, int dim, const Descriptor *mask) { |
819 | return TotalNumericMaxOrMin<TypeCategory::Real, 16, false>( |
820 | x, source, line, dim, mask, "MINVAL" ); |
821 | } |
822 | #endif |
823 | |
824 | void RTDEF(MinvalCharacter)(Descriptor &result, const Descriptor &x, |
825 | const char *source, int line, const Descriptor *mask) { |
826 | CharacterMaxOrMin<false>(result, x, 0, source, line, mask, "MINVAL" ); |
827 | } |
828 | |
829 | void RTDEF(MaxvalDim)(Descriptor &result, const Descriptor &x, int dim, |
830 | const char *source, int line, const Descriptor *mask) { |
831 | if (x.type().IsCharacter()) { |
832 | CharacterMaxOrMin<true>(result, x, dim, source, line, mask, "MAXVAL" ); |
833 | } else { |
834 | NumericMaxOrMin<true>(result, x, dim, source, line, mask, "MAXVAL" ); |
835 | } |
836 | } |
837 | void RTDEF(MinvalDim)(Descriptor &result, const Descriptor &x, int dim, |
838 | const char *source, int line, const Descriptor *mask) { |
839 | if (x.type().IsCharacter()) { |
840 | CharacterMaxOrMin<false>(result, x, dim, source, line, mask, "MINVAL" ); |
841 | } else { |
842 | NumericMaxOrMin<false>(result, x, dim, source, line, mask, "MINVAL" ); |
843 | } |
844 | } |
845 | |
846 | RT_EXT_API_GROUP_END |
847 | } // extern "C" |
848 | |
849 | // NORM2 |
850 | |
851 | extern "C" { |
852 | RT_EXT_API_GROUP_BEGIN |
853 | |
854 | // TODO: REAL(2 & 3) |
855 | CppTypeFor<TypeCategory::Real, 4> RTDEF(Norm2_4)( |
856 | const Descriptor &x, const char *source, int line, int dim) { |
857 | return GetTotalReduction<TypeCategory::Real, 4>( |
858 | x, source, line, dim, nullptr, Norm2Accumulator<4>{x}, "NORM2" ); |
859 | } |
860 | CppTypeFor<TypeCategory::Real, 8> RTDEF(Norm2_8)( |
861 | const Descriptor &x, const char *source, int line, int dim) { |
862 | return GetTotalReduction<TypeCategory::Real, 8>( |
863 | x, source, line, dim, nullptr, Norm2Accumulator<8>{x}, "NORM2" ); |
864 | } |
865 | #if HAS_FLOAT80 |
866 | CppTypeFor<TypeCategory::Real, 10> RTDEF(Norm2_10)( |
867 | const Descriptor &x, const char *source, int line, int dim) { |
868 | return GetTotalReduction<TypeCategory::Real, 10>( |
869 | x, source, line, dim, nullptr, Norm2Accumulator<10>{x}, "NORM2" ); |
870 | } |
871 | #endif |
872 | |
873 | void RTDEF(Norm2Dim)(Descriptor &result, const Descriptor &x, int dim, |
874 | const char *source, int line) { |
875 | Terminator terminator{source, line}; |
876 | auto type{x.type().GetCategoryAndKind()}; |
877 | RUNTIME_CHECK(terminator, type); |
878 | if (type->first == TypeCategory::Real) { |
879 | ApplyFloatingPointKind<Norm2Helper, void, true>( |
880 | type->second, terminator, result, x, dim, nullptr, terminator); |
881 | } else { |
882 | terminator.Crash("NORM2: bad type code %d" , x.type().raw()); |
883 | } |
884 | } |
885 | |
886 | RT_EXT_API_GROUP_END |
887 | } // extern "C" |
888 | } // namespace Fortran::runtime |
889 | |