diff --git a/flang/include/flang/Evaluate/call.h b/flang/include/flang/Evaluate/call.h index e74e82d86f87a..7cf509c14a1f1 100644 --- a/flang/include/flang/Evaluate/call.h +++ b/flang/include/flang/Evaluate/call.h @@ -218,6 +218,22 @@ class ProcedureRef { int Rank() const; bool IsElemental() const { return proc_.IsElemental(); } bool hasAlternateReturns() const { return hasAlternateReturns_; } + + Expr *UnwrapArgExpr(int n) { + if (static_cast(n) < arguments_.size() && arguments_[n]) { + return arguments_[n]->UnwrapExpr(); + } else { + return nullptr; + } + } + const Expr *UnwrapArgExpr(int n) const { + if (static_cast(n) < arguments_.size() && arguments_[n]) { + return arguments_[n]->UnwrapExpr(); + } else { + return nullptr; + } + } + bool operator==(const ProcedureRef &) const; llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const; diff --git a/flang/include/flang/Evaluate/integer.h b/flang/include/flang/Evaluate/integer.h index 35b42239ca751..6a129bf62c19d 100644 --- a/flang/include/flang/Evaluate/integer.h +++ b/flang/include/flang/Evaluate/integer.h @@ -358,6 +358,7 @@ class Integer { static constexpr int DIGITS{bits - 1}; // don't count the sign bit static constexpr Integer HUGE() { return MASKR(bits - 1); } + static constexpr Integer Least() { return MASKL(1); } static constexpr int RANGE{// in the sense of SELECTED_INT_KIND // This magic value is LOG10(2.)*1E12. static_cast(((bits - 1) * 301029995664) / 1000000000000)}; diff --git a/flang/include/flang/Evaluate/shape.h b/flang/include/flang/Evaluate/shape.h index 5df3eeff73815..4f5a06ccd3712 100644 --- a/flang/include/flang/Evaluate/shape.h +++ b/flang/include/flang/Evaluate/shape.h @@ -48,6 +48,8 @@ Constant AsConstantShape(const ConstantSubscripts &); ConstantSubscripts AsConstantExtents(const Constant &); std::optional AsConstantExtents( FoldingContext &, const Shape &); +Shape AsShape(const ConstantSubscripts &); +std::optional AsShape(const std::optional &); inline int GetRank(const Shape &s) { return static_cast(s.size()); } @@ -89,6 +91,7 @@ MaybeExtentExpr CountTrips( // Computes SIZE() == PRODUCT(shape) MaybeExtentExpr GetSize(Shape &&); +ConstantSubscript GetSize(const ConstantSubscripts &); // Utility predicate: does an expression reference any implied DO index? bool ContainsAnyImpliedDoIndex(const ExtentExpr &); diff --git a/flang/include/flang/Evaluate/tools.h b/flang/include/flang/Evaluate/tools.h index 6adfe043a26c2..257fb054a3acb 100644 --- a/flang/include/flang/Evaluate/tools.h +++ b/flang/include/flang/Evaluate/tools.h @@ -644,6 +644,16 @@ std::optional> Negation( std::optional> Relate(parser::ContextualMessages &, RelationalOperator, Expr &&, Expr &&); +// Create a relational operation between two identically-typed operands +// and wrap it up in an Expr. +template +Expr PackageRelation( + RelationalOperator opr, Expr &&x, Expr &&y) { + static_assert(IsSpecificIntrinsicType); + return Expr{ + Relational{Relational{opr, std::move(x), std::move(y)}}}; +} + template Expr> LogicalNegation( Expr> &&x) { diff --git a/flang/lib/Evaluate/fold-character.cpp b/flang/lib/Evaluate/fold-character.cpp index 1eac58213f208..a73ed52672bb1 100644 --- a/flang/lib/Evaluate/fold-character.cpp +++ b/flang/lib/Evaluate/fold-character.cpp @@ -7,14 +7,49 @@ //===----------------------------------------------------------------------===// #include "fold-implementation.h" +#include "fold-reduction.h" namespace Fortran::evaluate { +static std::optional GetConstantLength( + FoldingContext &context, Expr &&expr) { + expr = Fold(context, std::move(expr)); + if (auto *chExpr{UnwrapExpr>(expr)}) { + if (auto len{chExpr->LEN()}) { + return ToInt64(*len); + } + } + return std::nullopt; +} + +template +static std::optional GetConstantLength( + FoldingContext &context, FunctionRef &funcRef, int zeroBasedArg) { + if (auto *expr{funcRef.UnwrapArgExpr(zeroBasedArg)}) { + return GetConstantLength(context, std::move(*expr)); + } else { + return std::nullopt; + } +} + +template +static std::optional> Identity( + Scalar str, std::optional len) { + if (len) { + return CharacterUtils::REPEAT( + str, std::max(*len, 0)); + } else { + return std::nullopt; + } +} + template Expr> FoldIntrinsicFunction( FoldingContext &context, FunctionRef> &&funcRef) { using T = Type; + using StringType = Scalar; // std::string or larger + using SingleCharType = typename StringType::value_type; // char &c. auto *intrinsic{std::get_if(&funcRef.proc().u)}; CHECK(intrinsic); std::string name{intrinsic->name}; @@ -32,10 +67,24 @@ Expr> FoldIntrinsicFunction( context, std::move(funcRef), CharacterUtils::ADJUSTR); } else if (name == "max") { return FoldMINorMAX(context, std::move(funcRef), Ordering::Greater); + } else if (name == "maxval") { + SingleCharType least{0}; + if (auto identity{Identity( + StringType{least}, GetConstantLength(context, funcRef, 0))}) { + return FoldMaxvalMinval( + context, std::move(funcRef), RelationalOperator::GT, *identity); + } } else if (name == "merge") { return FoldMerge(context, std::move(funcRef)); } else if (name == "min") { return FoldMINorMAX(context, std::move(funcRef), Ordering::Less); + } else if (name == "minval") { + auto most{std::numeric_limits::max()}; + if (auto identity{Identity( + StringType{most}, GetConstantLength(context, funcRef, 0))}) { + return FoldMaxvalMinval( + context, std::move(funcRef), RelationalOperator::LT, *identity); + } } else if (name == "new_line") { return Expr{Constant{CharacterUtils::NEW_LINE()}}; } else if (name == "repeat") { // not elemental @@ -52,7 +101,7 @@ Expr> FoldIntrinsicFunction( CharacterUtils::TRIM(std::get>(*scalar))}}; } } - // TODO: cshift, eoshift, maxval, minval, pack, reduce, + // TODO: cshift, eoshift, maxloc, minloc, pack, reduce, // spread, transfer, transpose, unpack return Expr{std::move(funcRef)}; } diff --git a/flang/lib/Evaluate/fold-implementation.h b/flang/lib/Evaluate/fold-implementation.h index 4dadebd47d056..aeb95536d6ad0 100644 --- a/flang/lib/Evaluate/fold-implementation.h +++ b/flang/lib/Evaluate/fold-implementation.h @@ -600,6 +600,9 @@ template Expr Folder::Reshape(FunctionRef &&funcRef) { template Expr FoldMINorMAX( FoldingContext &context, FunctionRef &&funcRef, Ordering order) { + static_assert(T::category == TypeCategory::Integer || + T::category == TypeCategory::Real || + T::category == TypeCategory::Character); std::vector *> constantArgs; // Call Folding on all arguments, even if some are not constant, // to make operand promotion explicit. @@ -608,8 +611,9 @@ Expr FoldMINorMAX( constantArgs.push_back(cst); } } - if (constantArgs.size() != funcRef.arguments().size()) + if (constantArgs.size() != funcRef.arguments().size()) { return Expr(std::move(funcRef)); + } CHECK(constantArgs.size() > 0); Expr result{std::move(*constantArgs[0])}; for (std::size_t i{1}; i < constantArgs.size(); ++i) { diff --git a/flang/lib/Evaluate/fold-integer.cpp b/flang/lib/Evaluate/fold-integer.cpp index fbbbbf40fa627..19b9f9293e519 100644 --- a/flang/lib/Evaluate/fold-integer.cpp +++ b/flang/lib/Evaluate/fold-integer.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "fold-implementation.h" +#include "fold-reduction.h" #include "flang/Evaluate/check-expression.h" namespace Fortran::evaluate { @@ -474,6 +475,9 @@ Expr> FoldIntrinsicFunction( }, sx->u); } + } else if (name == "maxval") { + return FoldMaxvalMinval(context, std::move(funcRef), + RelationalOperator::GT, T::Scalar::Least()); } else if (name == "merge") { return FoldMerge(context, std::move(funcRef)); } else if (name == "merge_bits") { @@ -492,6 +496,9 @@ Expr> FoldIntrinsicFunction( return FoldMINorMAX(context, std::move(funcRef), Ordering::Less); } else if (name == "min0" || name == "min1") { return RewriteSpecificMINorMAX(context, std::move(funcRef)); + } else if (name == "minval") { + return FoldMaxvalMinval( + context, std::move(funcRef), RelationalOperator::LT, T::Scalar::HUGE()); } else if (name == "mod") { return FoldElementalIntrinsic(context, std::move(funcRef), ScalarFuncWithContext( @@ -650,8 +657,7 @@ Expr> FoldIntrinsicFunction( // TODO: // cshift, dot_product, eoshift, // findloc, iall, iany, iparity, ibits, image_status, ishftc, - // matmul, maxloc, maxval, - // minloc, minval, not, pack, product, reduce, + // matmul, maxloc, minloc, not, pack, product, reduce, // sign, spread, sum, transfer, transpose, unpack return Expr{std::move(funcRef)}; } diff --git a/flang/lib/Evaluate/fold-real.cpp b/flang/lib/Evaluate/fold-real.cpp index d1c75e46178eb..88222689e4f3f 100644 --- a/flang/lib/Evaluate/fold-real.cpp +++ b/flang/lib/Evaluate/fold-real.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "fold-implementation.h" +#include "fold-reduction.h" namespace Fortran::evaluate { @@ -109,10 +110,16 @@ Expr> FoldIntrinsicFunction( return Expr{Scalar::HUGE()}; } else if (name == "max") { return FoldMINorMAX(context, std::move(funcRef), Ordering::Greater); + } else if (name == "maxval") { + return FoldMaxvalMinval(context, std::move(funcRef), + RelationalOperator::GT, T::Scalar::HUGE().Negate()); } else if (name == "merge") { return FoldMerge(context, std::move(funcRef)); } else if (name == "min") { return FoldMINorMAX(context, std::move(funcRef), Ordering::Less); + } else if (name == "minval") { + return FoldMaxvalMinval( + context, std::move(funcRef), RelationalOperator::LT, T::Scalar::HUGE()); } else if (name == "real") { if (auto *expr{args[0].value().UnwrapExpr()}) { return ToReal(context, std::move(*expr)); @@ -124,7 +131,7 @@ Expr> FoldIntrinsicFunction( return Expr{Scalar::TINY()}; } // TODO: cshift, dim, dot_product, eoshift, fraction, matmul, - // maxval, minval, modulo, nearest, norm2, pack, product, + // maxloc, minloc, modulo, nearest, norm2, pack, product, // reduce, rrspacing, scale, set_exponent, spacing, spread, // sum, transfer, transpose, unpack, bessel_jn (transformational) and // bessel_yn (transformational) diff --git a/flang/lib/Evaluate/fold-reduction.h b/flang/lib/Evaluate/fold-reduction.h new file mode 100644 index 0000000000000..1c7473a1500a1 --- /dev/null +++ b/flang/lib/Evaluate/fold-reduction.h @@ -0,0 +1,138 @@ +//===-- 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: ALL, ANY, COUNT, DOT_PRODUCT, FINDLOC, IALL, IANY, IPARITY, +// NORM2, MAXLOC, MINLOC, PARITY, PRODUCT, SUM + +#ifndef FORTRAN_EVALUATE_FOLD_REDUCTION_H_ +#define FORTRAN_EVALUATE_FOLD_REDUCTION_H_ + +#include "fold-implementation.h" + +namespace Fortran::evaluate { + +// MAXVAL & MINVAL +template +Expr FoldMaxvalMinval(FoldingContext &context, FunctionRef &&ref, + RelationalOperator opr, Scalar identity) { + static_assert(T::category == TypeCategory::Integer || + T::category == TypeCategory::Real || + T::category == TypeCategory::Character); + using Element = typename Constant::Element; + auto &arg{ref.arguments()}; + CHECK(arg.size() <= 3); + if (arg.empty()) { + return Expr{std::move(ref)}; + } + Constant *array{Folder{context}.Folding(arg[0])}; + if (!array || array->Rank() < 1) { + return Expr{std::move(ref)}; + } + std::optional dim; + if (arg.size() >= 2 && arg[1]) { + if (auto *dimConst{Folder{context}.Folding(arg[1])}) { + if (auto dimScalar{dimConst->GetScalarValue()}) { + dim.emplace(dimScalar->ToInt64()); + if (*dim < 1 || *dim > array->Rank()) { + context.messages().Say( + "DIM=%jd is not valid for an array of rank %d"_err_en_US, + static_cast(*dim), array->Rank()); + dim.reset(); + } + } + } + if (!dim) { + return Expr{std::move(ref)}; + } + } + Constant *mask{}; + if (arg.size() >= 3 && arg[2]) { + mask = Folder{context}.Folding(arg[2]); + if (!mask) { + return Expr{std::move(ref)}; + } + if (!CheckConformance(context.messages(), AsShape(array->shape()), + AsShape(mask->shape()), + CheckConformanceFlags::RightScalarExpandable, "ARRAY=", "MASK=") + .value_or(false)) { + return Expr{std::move(ref)}; + } + } + // Do it + ConstantSubscripts at{array->lbounds()}, maskAt; + bool maskAllFalse{false}; + if (mask) { + if (auto scalar{mask->GetScalarValue()}) { + if (scalar->IsTrue()) { + mask = nullptr; // all .TRUE. + } else { + maskAllFalse = true; + } + } else { + maskAt = mask->lbounds(); + } + } + std::vector result; + ConstantSubscripts resultShape; // empty -> scalar + // Internal function to accumulate into result.back(). + auto Accumulate{[&]() { + if (!maskAllFalse && (maskAt.empty() || mask->At(maskAt).IsTrue())) { + Expr test{ + PackageRelation(opr, Expr{Constant{array->At(at)}}, + Expr{Constant{result.back()}})}; + auto folded{GetScalarConstantValue( + test.Rewrite(context, std::move(test)))}; + CHECK(folded.has_value()); + if (folded->IsTrue()) { + result.back() = array->At(at); + } + } + }}; + 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}; + ConstantSubscript *maskDimAt{maskAt.empty() ? nullptr : &maskAt[*dim - 1]}; + ConstantSubscript maskLbound{maskDimAt ? *maskDimAt : 0}; + for (auto n{GetSize(resultShape)}; n-- > 0; + IncrementSubscripts(at, array->shape())) { + dimAt = dimLbound; + if (maskDimAt) { + *maskDimAt = maskLbound; + } + result.push_back(identity); + for (ConstantSubscript j{0}; j < dimExtent; + ++j, ++dimAt, maskDimAt && ++*maskDimAt) { + Accumulate(); + } + if (maskDimAt) { + IncrementSubscripts(maskAt, mask->shape()); + } + } + } else { // no DIM=, result is scalar + result.push_back(identity); + for (auto n{array->size()}; n-- > 0; + IncrementSubscripts(at, array->shape())) { + Accumulate(); + if (!maskAt.empty()) { + IncrementSubscripts(maskAt, mask->shape()); + } + } + } + if constexpr (T::category == TypeCategory::Character) { + return Expr{Constant{static_cast(identity.size()), + std::move(result), std::move(resultShape)}}; + } else { + return Expr{Constant{std::move(result), std::move(resultShape)}}; + } +} + +} // namespace Fortran::evaluate +#endif // FORTRAN_EVALUATE_FOLD_REDUCTION_H_ diff --git a/flang/lib/Evaluate/shape.cpp b/flang/lib/Evaluate/shape.cpp index fc7dd6dfea72c..a1b48135235fa 100644 --- a/flang/lib/Evaluate/shape.cpp +++ b/flang/lib/Evaluate/shape.cpp @@ -132,6 +132,22 @@ std::optional AsConstantExtents( } } +Shape AsShape(const ConstantSubscripts &shape) { + Shape result; + for (const auto &extent : shape) { + result.emplace_back(ExtentExpr{extent}); + } + return result; +} + +std::optional AsShape(const std::optional &shape) { + if (shape) { + return AsShape(*shape); + } else { + return std::nullopt; + } +} + Shape Fold(FoldingContext &context, Shape &&shape) { for (auto &dim : shape) { dim = Fold(context, std::move(dim)); @@ -190,6 +206,14 @@ MaybeExtentExpr GetSize(Shape &&shape) { return extent; } +ConstantSubscript GetSize(const ConstantSubscripts &shape) { + ConstantSubscript size{1}; + for (auto dim : std::move(shape)) { + size *= dim; + } + return size; +} + bool ContainsAnyImpliedDoIndex(const ExtentExpr &expr) { struct MyVisitor : public AnyTraverse { using Base = AnyTraverse; diff --git a/flang/lib/Evaluate/tools.cpp b/flang/lib/Evaluate/tools.cpp index f233adede1f95..7b218933304e3 100644 --- a/flang/lib/Evaluate/tools.cpp +++ b/flang/lib/Evaluate/tools.cpp @@ -475,14 +475,6 @@ Expr LogicalNegation(Expr &&x) { std::move(x.u)); } -template -Expr PackageRelation( - RelationalOperator opr, Expr &&x, Expr &&y) { - static_assert(IsSpecificIntrinsicType); - return Expr{ - Relational{Relational{opr, std::move(x), std::move(y)}}}; -} - template Expr PromoteAndRelate( RelationalOperator opr, Expr> &&x, Expr> &&y) { diff --git a/flang/runtime/reduction.cpp b/flang/runtime/reduction.cpp index cf9515b7ad43a..73fcfa831c2e6 100644 --- a/flang/runtime/reduction.cpp +++ b/flang/runtime/reduction.cpp @@ -9,8 +9,11 @@ // Implements ALL, ANY, COUNT, IPARITY, & PARITY for all required operand // types and shapes. // -// DOT_PRODUCT, FINDLOC, SUM, and PRODUCT are in their own eponymous source -// files; NORM2, MAXLOC, MINLOC, MAXVAL, and MINVAL are in extrema.cpp. +// DOT_PRODUCT, FINDLOC, MATMUL, SUM, and PRODUCT are in their own eponymous +// source files. +// NORM2, MAXLOC, MINLOC, MAXVAL, and MINVAL are in extrema.cpp. +// +// TODO: IALL, IANY #include "reduction.h" #include "reduction-templates.h" diff --git a/flang/test/Evaluate/folding20.f90 b/flang/test/Evaluate/folding20.f90 new file mode 100644 index 0000000000000..f84e033175b6b --- /dev/null +++ b/flang/test/Evaluate/folding20.f90 @@ -0,0 +1,26 @@ +! RUN: %S/test_folding.sh %s %t %flang_fc1 +! Tests intrinsic MAXVAL/MINVAL function folding +module m + logical, parameter :: test_imaxidentity = maxval([integer::]) == -huge(0) - 1 + logical, parameter :: test_iminidentity = minval([integer::]) == huge(0) + integer, parameter :: intmatrix(*,*) = reshape([1, 2, 3, 4, 5, 6], [2, 3]) + logical, parameter :: test_imaxval = maxval(intmatrix) == 6 + logical, parameter :: test_iminval = minval(intmatrix) == 1 + logical, parameter :: odds(2,3) = mod(intmatrix, 2) == 1 + logical, parameter :: test_imaxval_masked = maxval(intmatrix,odds) == 5 + logical, parameter :: test_iminval_masked = minval(intmatrix,.not.odds) == 2 + logical, parameter :: test_rmaxidentity = maxval([real::]) == -huge(0.0) + logical, parameter :: test_rminidentity = minval([real::]) == huge(0.0) + logical, parameter :: test_rmaxval = maxval(real(intmatrix)) == 6.0 + logical, parameter :: test_rminval = minval(real(intmatrix)) == 1.0 + logical, parameter :: test_rmaxval_scalar_mask = maxval(real(intmatrix), .true.) == 6.0 + logical, parameter :: test_rminval_scalar_mask = minval(real(intmatrix), .false.) == huge(0.0) + character(*), parameter :: chmatrix(*,*) = reshape(['abc', 'def', 'ghi', 'jkl', 'mno', 'pqr'], [2, 3]) + logical, parameter :: test_cmaxlen = len(maxval([character*4::])) == 4 + logical, parameter :: test_cmaxidentity = maxval([character*4::]) == repeat(char(0), 4) + logical, parameter :: test_cminidentity = minval([character*4::]) == repeat(char(127), 4) + logical, parameter :: test_cmaxval = maxval(chmatrix) == 'pqr' + logical, parameter :: test_cminval = minval(chmatrix) == 'abc' + logical, parameter :: test_dim1 = all(maxval(intmatrix,dim=1) == [2, 4, 6]) + logical, parameter :: test_dim2 = all(minval(intmatrix,dim=2,mask=odds) == [1, huge(0)]) +end