//===-- runtime/extrema.cpp -----------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // Implements MAXLOC, MINLOC, MAXVAL, & MINVAL for all required operand types // and shapes and (for MAXLOC & MINLOC) result integer kinds. Also implements // NORM2 using common infrastructure. #include "character.h" #include "reduction-templates.h" #include "reduction.h" #include "flang/Common/long-double.h" #include #include #include #include namespace Fortran::runtime { // MAXLOC & MINLOC template struct NumericCompare { using Type = T; explicit NumericCompare(std::size_t /*elemLen; ignored*/) {} bool operator()(const T &value, const T &previous) const { if (value == previous) { return BACK; } else if constexpr (IS_MAX) { return value > previous; } else { return value < previous; } } }; template class CharacterCompare { public: using Type = T; explicit CharacterCompare(std::size_t elemLen) : chars_{elemLen / sizeof(T)} {} bool operator()(const T &value, const T &previous) const { int cmp{CharacterScalarCompare(&value, &previous, chars_, chars_)}; if (cmp == 0) { return BACK; } else if constexpr (IS_MAX) { return cmp > 0; } else { return cmp < 0; } } private: std::size_t chars_; }; template class ExtremumLocAccumulator { public: using Type = typename COMPARE::Type; ExtremumLocAccumulator(const Descriptor &array, std::size_t chars = 0) : array_{array}, argRank_{array.rank()}, compare_{array.ElementBytes()} { Reinitialize(); } void Reinitialize() { // per standard: result indices are all zero if no data for (int j{0}; j < argRank_; ++j) { extremumLoc_[j] = 0; } previous_ = nullptr; } int argRank() const { return argRank_; } template void GetResult(A *p, int zeroBasedDim = -1) { if (zeroBasedDim >= 0) { *p = extremumLoc_[zeroBasedDim] - array_.GetDimension(zeroBasedDim).LowerBound() + 1; } else { for (int j{0}; j < argRank_; ++j) { p[j] = extremumLoc_[j] - array_.GetDimension(j).LowerBound() + 1; } } } template bool AccumulateAt(const SubscriptValue at[]) { const auto &value{*array_.Element(at)}; if (!previous_ || compare_(value, *previous_)) { previous_ = &value; for (int j{0}; j < argRank_; ++j) { extremumLoc_[j] = at[j]; } } return true; } private: const Descriptor &array_; int argRank_; SubscriptValue extremumLoc_[maxRank]; const Type *previous_{nullptr}; COMPARE compare_; }; template static void LocationHelper(const char *intrinsic, Descriptor &result, const Descriptor &x, int kind, const Descriptor *mask, Terminator &terminator) { ACCUMULATOR accumulator{x}; DoTotalReduction(x, 0, mask, accumulator, intrinsic, terminator); ApplyIntegerKind::template Functor, void>( kind, terminator, accumulator, result); } template class COMPARE> inline void DoMaxOrMinLoc(const char *intrinsic, Descriptor &result, const Descriptor &x, int kind, const char *source, int line, const Descriptor *mask, bool back) { using CppType = CppTypeFor; Terminator terminator{source, line}; if (back) { LocationHelper>, CppType>(intrinsic, result, x, kind, mask, terminator); } else { LocationHelper>, CppType>(intrinsic, result, x, kind, mask, terminator); } } template struct TypedMaxOrMinLocHelper { template struct Functor { void operator()(const char *intrinsic, Descriptor &result, const Descriptor &x, int kind, const char *source, int line, const Descriptor *mask, bool back) const { DoMaxOrMinLoc( intrinsic, result, x, kind, source, line, mask, back); } }; }; template inline void TypedMaxOrMinLoc(const char *intrinsic, Descriptor &result, const Descriptor &x, int kind, const char *source, int line, const Descriptor *mask, bool back) { int rank{x.rank()}; SubscriptValue extent[1]{rank}; result.Establish(TypeCategory::Integer, kind, nullptr, 1, extent, CFI_attribute_allocatable); result.GetDimension(0).SetBounds(1, extent[0]); Terminator terminator{source, line}; if (int stat{result.Allocate()}) { terminator.Crash( "%s: could not allocate memory for result; STAT=%d", intrinsic, stat); } CheckIntegerKind(terminator, kind, intrinsic); auto catKind{x.type().GetCategoryAndKind()}; RUNTIME_CHECK(terminator, catKind.has_value()); switch (catKind->first) { case TypeCategory::Integer: ApplyIntegerKind< TypedMaxOrMinLocHelper::template Functor, void>(catKind->second, terminator, intrinsic, result, x, kind, source, line, mask, back); break; case TypeCategory::Real: ApplyFloatingPointKind< TypedMaxOrMinLocHelper::template Functor, void>(catKind->second, terminator, intrinsic, result, x, kind, source, line, mask, back); break; case TypeCategory::Character: ApplyCharacterKind::template Functor, void>(catKind->second, terminator, intrinsic, result, x, kind, source, line, mask, back); break; default: terminator.Crash( "%s: Bad data type code (%d) for array", intrinsic, x.type().raw()); } } extern "C" { void RTNAME(Maxloc)(Descriptor &result, const Descriptor &x, int kind, const char *source, int line, const Descriptor *mask, bool back) { TypedMaxOrMinLoc("MAXLOC", result, x, kind, source, line, mask, back); } void RTNAME(Minloc)(Descriptor &result, const Descriptor &x, int kind, const char *source, int line, const Descriptor *mask, bool back) { TypedMaxOrMinLoc("MINLOC", result, x, kind, source, line, mask, back); } } // extern "C" // MAXLOC/MINLOC with DIM= template class COMPARE, bool BACK> static void DoPartialMaxOrMinLocDirection(const char *intrinsic, Descriptor &result, const Descriptor &x, int kind, int dim, const Descriptor *mask, Terminator &terminator) { using CppType = CppTypeFor; using Accumulator = ExtremumLocAccumulator>; Accumulator accumulator{x}; ApplyIntegerKind::template Functor, void>( kind, terminator, result, x, dim, mask, terminator, intrinsic, accumulator); } template class COMPARE> inline void DoPartialMaxOrMinLoc(const char *intrinsic, Descriptor &result, const Descriptor &x, int kind, int dim, const Descriptor *mask, bool back, Terminator &terminator) { if (back) { DoPartialMaxOrMinLocDirection( intrinsic, result, x, kind, dim, mask, terminator); } else { DoPartialMaxOrMinLocDirection( intrinsic, result, x, kind, dim, mask, terminator); } } template class COMPARE> struct DoPartialMaxOrMinLocHelper { template struct Functor { void operator()(const char *intrinsic, Descriptor &result, const Descriptor &x, int kind, int dim, const Descriptor *mask, bool back, Terminator &terminator) const { DoPartialMaxOrMinLoc( intrinsic, result, x, kind, dim, mask, back, terminator); } }; }; template inline void TypedPartialMaxOrMinLoc(const char *intrinsic, Descriptor &result, const Descriptor &x, int kind, int dim, const char *source, int line, const Descriptor *mask, bool back) { Terminator terminator{source, line}; CheckIntegerKind(terminator, kind, intrinsic); auto catKind{x.type().GetCategoryAndKind()}; RUNTIME_CHECK(terminator, catKind.has_value()); switch (catKind->first) { case TypeCategory::Integer: ApplyIntegerKind::template Functor, void>(catKind->second, terminator, intrinsic, result, x, kind, dim, mask, back, terminator); break; case TypeCategory::Real: ApplyFloatingPointKind::template Functor, void>(catKind->second, terminator, intrinsic, result, x, kind, dim, mask, back, terminator); break; case TypeCategory::Character: ApplyCharacterKind::template Functor, void>(catKind->second, terminator, intrinsic, result, x, kind, dim, mask, back, terminator); break; default: terminator.Crash( "%s: Bad data type code (%d) for array", intrinsic, x.type().raw()); } } extern "C" { void RTNAME(MaxlocDim)(Descriptor &result, const Descriptor &x, int kind, int dim, const char *source, int line, const Descriptor *mask, bool back) { TypedPartialMaxOrMinLoc( "MAXLOC", result, x, kind, dim, source, line, mask, back); } void RTNAME(MinlocDim)(Descriptor &result, const Descriptor &x, int kind, int dim, const char *source, int line, const Descriptor *mask, bool back) { TypedPartialMaxOrMinLoc( "MINLOC", result, x, kind, dim, source, line, mask, back); } } // extern "C" // MAXVAL and MINVAL template struct MaxOrMinIdentity { using Type = CppTypeFor; static constexpr Type Value() { return IS_MAXVAL ? std::numeric_limits::lowest() : std::numeric_limits::max(); } }; // std::numeric_limits<> may not know int128_t template struct MaxOrMinIdentity { using Type = CppTypeFor; static constexpr Type Value() { return IS_MAXVAL ? Type{1} << 127 : ~Type{0} >> 1; } }; template class NumericExtremumAccumulator { public: using Type = CppTypeFor; explicit NumericExtremumAccumulator(const Descriptor &array) : array_{array} {} void Reinitialize() { extremum_ = MaxOrMinIdentity::Value(); } template void GetResult(A *p, int /*zeroBasedDim*/ = -1) const { *p = extremum_; } bool Accumulate(Type x) { if constexpr (IS_MAXVAL) { if (x > extremum_) { extremum_ = x; } } else if (x < extremum_) { extremum_ = x; } return true; } template bool AccumulateAt(const SubscriptValue at[]) { return Accumulate(*array_.Element(at)); } private: const Descriptor &array_; Type extremum_{MaxOrMinIdentity::Value()}; }; template inline CppTypeFor TotalNumericMaxOrMin(const Descriptor &x, const char *source, int line, int dim, const Descriptor *mask, const char *intrinsic) { return GetTotalReduction(x, source, line, dim, mask, NumericExtremumAccumulator{x}, intrinsic); } template static void DoMaxMinNorm2(Descriptor &result, const Descriptor &x, int dim, const Descriptor *mask, const char *intrinsic, Terminator &terminator) { using Type = CppTypeFor; ACCUMULATOR accumulator{x}; if (dim == 0 || x.rank() == 1) { // Total reduction result.Establish(x.type(), x.ElementBytes(), nullptr, 0, nullptr, CFI_attribute_allocatable); if (int stat{result.Allocate()}) { terminator.Crash( "%s: could not allocate memory for result; STAT=%d", intrinsic, stat); } DoTotalReduction(x, dim, mask, accumulator, intrinsic, terminator); accumulator.GetResult(result.OffsetElement()); } else { // Partial reduction PartialReduction( result, x, dim, mask, terminator, intrinsic, accumulator); } } template struct MaxOrMinHelper { template struct Functor { void operator()(Descriptor &result, const Descriptor &x, int dim, const Descriptor *mask, const char *intrinsic, Terminator &terminator) const { DoMaxMinNorm2>( result, x, dim, mask, intrinsic, terminator); } }; }; template inline void NumericMaxOrMin(Descriptor &result, const Descriptor &x, int dim, const char *source, int line, const Descriptor *mask, const char *intrinsic) { Terminator terminator{source, line}; auto type{x.type().GetCategoryAndKind()}; RUNTIME_CHECK(terminator, type); switch (type->first) { case TypeCategory::Integer: ApplyIntegerKind< MaxOrMinHelper::template Functor, void>( type->second, terminator, result, x, dim, mask, intrinsic, terminator); break; case TypeCategory::Real: ApplyFloatingPointKind< MaxOrMinHelper::template Functor, void>( type->second, terminator, result, x, dim, mask, intrinsic, terminator); break; default: terminator.Crash("%s: bad type code %d", intrinsic, x.type().raw()); } } template class CharacterExtremumAccumulator { public: using Type = CppTypeFor; explicit CharacterExtremumAccumulator(const Descriptor &array) : array_{array}, charLen_{array_.ElementBytes() / KIND} {} void Reinitialize() { extremum_ = nullptr; } template void GetResult(A *p, int /*zeroBasedDim*/ = -1) const { static_assert(std::is_same_v); if (extremum_) { std::memcpy(p, extremum_, charLen_); } else { // empty array: result is all zero-valued characters std::memset(p, 0, charLen_); } } bool Accumulate(const Type *x) { if (!extremum_) { extremum_ = x; } else { int cmp{CharacterScalarCompare(x, extremum_, charLen_, charLen_)}; if (IS_MAXVAL == (cmp > 0)) { extremum_ = x; } } return true; } template bool AccumulateAt(const SubscriptValue at[]) { return Accumulate(array_.Element(at)); } private: const Descriptor &array_; std::size_t charLen_; const Type *extremum_{nullptr}; }; template struct CharacterMaxOrMinHelper { template struct Functor { void operator()(Descriptor &result, const Descriptor &x, int dim, const Descriptor *mask, const char *intrinsic, Terminator &terminator) const { DoMaxMinNorm2>( result, x, dim, mask, intrinsic, terminator); } }; }; template inline void CharacterMaxOrMin(Descriptor &result, const Descriptor &x, int dim, const char *source, int line, const Descriptor *mask, const char *intrinsic) { Terminator terminator{source, line}; auto type{x.type().GetCategoryAndKind()}; RUNTIME_CHECK(terminator, type && type->first == TypeCategory::Character); ApplyCharacterKind::template Functor, void>( type->second, terminator, result, x, dim, mask, intrinsic, terminator); } extern "C" { CppTypeFor RTNAME(MaxvalInteger1)(const Descriptor &x, const char *source, int line, int dim, const Descriptor *mask) { return TotalNumericMaxOrMin( x, source, line, dim, mask, "MAXVAL"); } CppTypeFor RTNAME(MaxvalInteger2)(const Descriptor &x, const char *source, int line, int dim, const Descriptor *mask) { return TotalNumericMaxOrMin( x, source, line, dim, mask, "MAXVAL"); } CppTypeFor RTNAME(MaxvalInteger4)(const Descriptor &x, const char *source, int line, int dim, const Descriptor *mask) { return TotalNumericMaxOrMin( x, source, line, dim, mask, "MAXVAL"); } CppTypeFor RTNAME(MaxvalInteger8)(const Descriptor &x, const char *source, int line, int dim, const Descriptor *mask) { return TotalNumericMaxOrMin( x, source, line, dim, mask, "MAXVAL"); } #ifdef __SIZEOF_INT128__ CppTypeFor RTNAME(MaxvalInteger16)( const Descriptor &x, const char *source, int line, int dim, const Descriptor *mask) { return TotalNumericMaxOrMin( x, source, line, dim, mask, "MAXVAL"); } #endif // TODO: REAL(2 & 3) CppTypeFor RTNAME(MaxvalReal4)(const Descriptor &x, const char *source, int line, int dim, const Descriptor *mask) { return TotalNumericMaxOrMin( x, source, line, dim, mask, "MAXVAL"); } CppTypeFor RTNAME(MaxvalReal8)(const Descriptor &x, const char *source, int line, int dim, const Descriptor *mask) { return TotalNumericMaxOrMin( x, source, line, dim, mask, "MAXVAL"); } #if LONG_DOUBLE == 80 CppTypeFor RTNAME(MaxvalReal10)(const Descriptor &x, const char *source, int line, int dim, const Descriptor *mask) { return TotalNumericMaxOrMin( x, source, line, dim, mask, "MAXVAL"); } #elif LONG_DOUBLE == 128 CppTypeFor RTNAME(MaxvalReal16)(const Descriptor &x, const char *source, int line, int dim, const Descriptor *mask) { return TotalNumericMaxOrMin( x, source, line, dim, mask, "MAXVAL"); } #endif void RTNAME(MaxvalCharacter)(Descriptor &result, const Descriptor &x, const char *source, int line, const Descriptor *mask) { CharacterMaxOrMin(result, x, 0, source, line, mask, "MAXVAL"); } CppTypeFor RTNAME(MinvalInteger1)(const Descriptor &x, const char *source, int line, int dim, const Descriptor *mask) { return TotalNumericMaxOrMin( x, source, line, dim, mask, "MINVAL"); } CppTypeFor RTNAME(MinvalInteger2)(const Descriptor &x, const char *source, int line, int dim, const Descriptor *mask) { return TotalNumericMaxOrMin( x, source, line, dim, mask, "MINVAL"); } CppTypeFor RTNAME(MinvalInteger4)(const Descriptor &x, const char *source, int line, int dim, const Descriptor *mask) { return TotalNumericMaxOrMin( x, source, line, dim, mask, "MINVAL"); } CppTypeFor RTNAME(MinvalInteger8)(const Descriptor &x, const char *source, int line, int dim, const Descriptor *mask) { return TotalNumericMaxOrMin( x, source, line, dim, mask, "MINVAL"); } #ifdef __SIZEOF_INT128__ CppTypeFor RTNAME(MinvalInteger16)( const Descriptor &x, const char *source, int line, int dim, const Descriptor *mask) { return TotalNumericMaxOrMin( x, source, line, dim, mask, "MINVAL"); } #endif // TODO: REAL(2 & 3) CppTypeFor RTNAME(MinvalReal4)(const Descriptor &x, const char *source, int line, int dim, const Descriptor *mask) { return TotalNumericMaxOrMin( x, source, line, dim, mask, "MINVAL"); } CppTypeFor RTNAME(MinvalReal8)(const Descriptor &x, const char *source, int line, int dim, const Descriptor *mask) { return TotalNumericMaxOrMin( x, source, line, dim, mask, "MINVAL"); } #if LONG_DOUBLE == 80 CppTypeFor RTNAME(MinvalReal10)(const Descriptor &x, const char *source, int line, int dim, const Descriptor *mask) { return TotalNumericMaxOrMin( x, source, line, dim, mask, "MINVAL"); } #elif LONG_DOUBLE == 128 CppTypeFor RTNAME(MinvalReal16)(const Descriptor &x, const char *source, int line, int dim, const Descriptor *mask) { return TotalNumericMaxOrMin( x, source, line, dim, mask, "MINVAL"); } #endif void RTNAME(MinvalCharacter)(Descriptor &result, const Descriptor &x, const char *source, int line, const Descriptor *mask) { CharacterMaxOrMin(result, x, 0, source, line, mask, "MINVAL"); } void RTNAME(MaxvalDim)(Descriptor &result, const Descriptor &x, int dim, const char *source, int line, const Descriptor *mask) { if (x.type().IsCharacter()) { CharacterMaxOrMin(result, x, dim, source, line, mask, "MAXVAL"); } else { NumericMaxOrMin(result, x, dim, source, line, mask, "MAXVAL"); } } void RTNAME(MinvalDim)(Descriptor &result, const Descriptor &x, int dim, const char *source, int line, const Descriptor *mask) { if (x.type().IsCharacter()) { CharacterMaxOrMin(result, x, dim, source, line, mask, "MINVAL"); } else { NumericMaxOrMin(result, x, dim, source, line, mask, "MINVAL"); } } } // extern "C" // NORM2 template class Norm2Accumulator { public: using Type = CppTypeFor; // Use at least double precision for accumulators using AccumType = CppTypeFor; explicit Norm2Accumulator(const Descriptor &array) : array_{array} {} void Reinitialize() { max_ = sum_ = 0; } template void GetResult(A *p, int /*zeroBasedDim*/ = -1) const { // m * sqrt(1 + sum((others(:)/m)**2)) *p = static_cast(max_ * std::sqrt(1 + sum_)); } bool Accumulate(Type x) { auto absX{AccumType{std::abs(x)}}; if (!max_) { max_ = x; } else if (absX > max_) { auto t{max_ / absX}; // < 1.0 auto tsq{t * t}; sum_ *= tsq; // scale sum to reflect change to the max sum_ += tsq; // include a term for the previous max max_ = absX; } else { // absX <= max_ auto t{absX / max_}; sum_ += t * t; } return true; } template bool AccumulateAt(const SubscriptValue at[]) { return Accumulate(*array_.Element(at)); } private: const Descriptor &array_; AccumType max_{0}; // value (m) with largest magnitude AccumType sum_{0}; // sum((others(:)/m)**2) }; template struct Norm2Helper { void operator()(Descriptor &result, const Descriptor &x, int dim, const Descriptor *mask, Terminator &terminator) const { DoMaxMinNorm2>( result, x, dim, mask, "NORM2", terminator); } }; extern "C" { // TODO: REAL(2 & 3) CppTypeFor RTNAME(Norm2_4)(const Descriptor &x, const char *source, int line, int dim, const Descriptor *mask) { return GetTotalReduction( x, source, line, dim, mask, Norm2Accumulator<4>{x}, "NORM2"); } CppTypeFor RTNAME(Norm2_8)(const Descriptor &x, const char *source, int line, int dim, const Descriptor *mask) { return GetTotalReduction( x, source, line, dim, mask, Norm2Accumulator<8>{x}, "NORM2"); } #if LONG_DOUBLE == 80 CppTypeFor RTNAME(Norm2_10)(const Descriptor &x, const char *source, int line, int dim, const Descriptor *mask) { return GetTotalReduction( x, source, line, dim, mask, Norm2Accumulator<10>{x}, "NORM2"); } #elif LONG_DOUBLE == 128 CppTypeFor RTNAME(Norm2_16)(const Descriptor &x, const char *source, int line, int dim, const Descriptor *mask) { return GetTotalReduction( x, source, line, dim, mask, Norm2Accumulator<16>{x}, "NORM2"); } #endif void RTNAME(Norm2Dim)(Descriptor &result, const Descriptor &x, int dim, const char *source, int line, const Descriptor *mask) { Terminator terminator{source, line}; auto type{x.type().GetCategoryAndKind()}; RUNTIME_CHECK(terminator, type); if (type->first == TypeCategory::Real) { ApplyFloatingPointKind( type->second, terminator, result, x, dim, mask, terminator); } else { terminator.Crash("NORM2: bad type code %d", x.type().raw()); } } } // extern "C" } // namespace Fortran::runtime