//===-- lib/Evaluate/fold-reduction.h -------------------------------------===// // // 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 // //===----------------------------------------------------------------------===// // TODO: DOT_PRODUCT, NORM2, PARITY #ifndef FORTRAN_EVALUATE_FOLD_REDUCTION_H_ #define FORTRAN_EVALUATE_FOLD_REDUCTION_H_ #include "fold-implementation.h" namespace Fortran::evaluate { // Fold and validate a DIM= argument. Returns false on error. bool CheckReductionDIM(std::optional &dim, FoldingContext &, ActualArguments &, std::optional dimIndex, int rank); // Fold and validate a MASK= argument. Return null on error, absent MASK=, or // non-constant MASK=. Constant *GetReductionMASK( std::optional &maskArg, const ConstantSubscripts &shape, FoldingContext &); // Common preprocessing for reduction transformational intrinsic function // folding. If the intrinsic can have DIM= &/or MASK= arguments, extract // and check them. If a MASK= is present, apply it to the array data and // substitute identity values for elements corresponding to .FALSE. in // the mask. If the result is present, the intrinsic call can be folded. template static std::optional> ProcessReductionArgs(FoldingContext &context, ActualArguments &arg, std::optional &dim, const Scalar &identity, int arrayIndex, std::optional dimIndex = std::nullopt, std::optional maskIndex = std::nullopt) { if (arg.empty()) { return std::nullopt; } Constant *folded{Folder{context}.Folding(arg[arrayIndex])}; if (!folded || folded->Rank() < 1) { return std::nullopt; } if (!CheckReductionDIM(dim, context, arg, dimIndex, folded->Rank())) { return std::nullopt; } if (maskIndex && static_cast(*maskIndex) < arg.size() && arg[*maskIndex]) { if (const Constant *mask{ GetReductionMASK(arg[*maskIndex], folded->shape(), context)}) { // Apply the mask in place to the array std::size_t n{folded->size()}; std::vector::Element> elements; if (auto scalarMask{mask->GetScalarValue()}) { if (scalarMask->IsTrue()) { return Constant{*folded}; } else { // MASK=.FALSE. elements = std::vector::Element>(n, identity); } } else { // mask is an array; test its elements elements = std::vector::Element>(n, identity); ConstantSubscripts at{folded->lbounds()}; for (std::size_t j{0}; j < n; ++j, folded->IncrementSubscripts(at)) { if (mask->values()[j].IsTrue()) { elements[j] = folded->At(at); } } } if constexpr (T::category == TypeCategory::Character) { return Constant{static_cast(identity.size()), std::move(elements), ConstantSubscripts{folded->shape()}}; } else { return Constant{ std::move(elements), ConstantSubscripts{folded->shape()}}; } } else { return std::nullopt; } } else { return Constant{*folded}; } } // Generalized reduction to an array of one dimension fewer (w/ DIM=) // or to a scalar (w/o DIM=). template static Constant DoReduction(const Constant &array, std::optional &dim, const Scalar &identity, ACCUMULATOR &accumulator) { ConstantSubscripts at{array.lbounds()}; std::vector::Element> elements; ConstantSubscripts resultShape; // empty -> scalar if (dim) { // DIM= is present, so result is an array resultShape = array.shape(); resultShape.erase(resultShape.begin() + (*dim - 1)); ConstantSubscript dimExtent{array.shape().at(*dim - 1)}; ConstantSubscript &dimAt{at[*dim - 1]}; ConstantSubscript dimLbound{dimAt}; for (auto n{GetSize(resultShape)}; n-- > 0; IncrementSubscripts(at, array.shape())) { dimAt = dimLbound; elements.push_back(identity); for (ConstantSubscript j{0}; j < dimExtent; ++j, ++dimAt) { accumulator(elements.back(), at); } } } else { // no DIM=, result is scalar elements.push_back(identity); for (auto n{array.size()}; n-- > 0; IncrementSubscripts(at, array.shape())) { accumulator(elements.back(), at); } } if constexpr (T::category == TypeCategory::Character) { return {static_cast(identity.size()), std::move(elements), std::move(resultShape)}; } else { return {std::move(elements), std::move(resultShape)}; } } // MAXVAL & MINVAL template static Expr FoldMaxvalMinval(FoldingContext &context, FunctionRef &&ref, RelationalOperator opr, const Scalar &identity) { static_assert(T::category == TypeCategory::Integer || T::category == TypeCategory::Real || T::category == TypeCategory::Character); using Element = Scalar; std::optional dim; if (std::optional> array{ ProcessReductionArgs(context, ref.arguments(), dim, identity, /*ARRAY=*/0, /*DIM=*/1, /*MASK=*/2)}) { auto accumulator{[&](Element &element, const ConstantSubscripts &at) { Expr test{PackageRelation(opr, Expr{Constant{array->At(at)}}, Expr{Constant{element}})}; auto folded{GetScalarConstantValue( test.Rewrite(context, std::move(test)))}; CHECK(folded.has_value()); if (folded->IsTrue()) { element = array->At(at); } }}; return Expr{DoReduction(*array, dim, identity, accumulator)}; } return Expr{std::move(ref)}; } // PRODUCT template static Expr FoldProduct( FoldingContext &context, FunctionRef &&ref, Scalar identity) { static_assert(T::category == TypeCategory::Integer || T::category == TypeCategory::Real || T::category == TypeCategory::Complex); using Element = typename Constant::Element; std::optional dim; if (std::optional> array{ ProcessReductionArgs(context, ref.arguments(), dim, identity, /*ARRAY=*/0, /*DIM=*/1, /*MASK=*/2)}) { bool overflow{false}; auto accumulator{[&](Element &element, const ConstantSubscripts &at) { if constexpr (T::category == TypeCategory::Integer) { auto prod{element.MultiplySigned(array->At(at))}; overflow |= prod.SignedMultiplicationOverflowed(); element = prod.lower; } else { // Real & Complex auto prod{element.Multiply(array->At(at))}; overflow |= prod.flags.test(RealFlag::Overflow); element = prod.value; } }}; if (overflow) { context.messages().Say( "PRODUCT() of %s data overflowed"_warn_en_US, T::AsFortran()); } else { return Expr{DoReduction(*array, dim, identity, accumulator)}; } } return Expr{std::move(ref)}; } // SUM template static Expr FoldSum(FoldingContext &context, FunctionRef &&ref) { static_assert(T::category == TypeCategory::Integer || T::category == TypeCategory::Real || T::category == TypeCategory::Complex); using Element = typename Constant::Element; std::optional dim; Element identity{}, correction{}; if (std::optional> array{ ProcessReductionArgs(context, ref.arguments(), dim, identity, /*ARRAY=*/0, /*DIM=*/1, /*MASK=*/2)}) { bool overflow{false}; auto accumulator{[&](Element &element, const ConstantSubscripts &at) { if constexpr (T::category == TypeCategory::Integer) { auto sum{element.AddSigned(array->At(at))}; overflow |= sum.overflow; element = sum.value; } else { // Real & Complex: use Kahan summation auto next{array->At(at).Add(correction)}; overflow |= next.flags.test(RealFlag::Overflow); auto sum{element.Add(next.value)}; overflow |= sum.flags.test(RealFlag::Overflow); // correction = (sum - element) - next; algebraically zero correction = sum.value.Subtract(element).value.Subtract(next.value).value; element = sum.value; } }}; if (overflow) { context.messages().Say( "SUM() of %s data overflowed"_warn_en_US, T::AsFortran()); } else { return Expr{DoReduction(*array, dim, identity, accumulator)}; } } return Expr{std::move(ref)}; } } // namespace Fortran::evaluate #endif // FORTRAN_EVALUATE_FOLD_REDUCTION_H_