//===-- lib/Evaluate/fold-logical.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 // //===----------------------------------------------------------------------===// #include "fold-implementation.h" #include "fold-matmul.h" #include "fold-reduction.h" #include "flang/Evaluate/check-expression.h" #include "flang/Runtime/magic-numbers.h" namespace Fortran::evaluate { template static std::optional> ZeroExtend(const Constant &c) { std::vector> exts; for (const auto &v : c.values()) { exts.push_back(Scalar::ConvertUnsigned(v).value); } return AsGenericExpr( Constant(std::move(exts), ConstantSubscripts(c.shape()))); } // for ALL, ANY & PARITY template static Expr FoldAllAnyParity(FoldingContext &context, FunctionRef &&ref, Scalar (Scalar::*operation)(const Scalar &) const, Scalar identity) { static_assert(T::category == TypeCategory::Logical); std::optional dim; if (std::optional> arrayAndMask{ ProcessReductionArgs(context, ref.arguments(), dim, /*ARRAY(MASK)=*/0, /*DIM=*/1)}) { OperationAccumulator accumulator{arrayAndMask->array, operation}; return Expr{DoReduction( arrayAndMask->array, arrayAndMask->mask, dim, identity, accumulator)}; } return Expr{std::move(ref)}; } template Expr> FoldIntrinsicFunction( FoldingContext &context, FunctionRef> &&funcRef) { using T = Type; ActualArguments &args{funcRef.arguments()}; auto *intrinsic{std::get_if(&funcRef.proc().u)}; CHECK(intrinsic); std::string name{intrinsic->name}; using SameInt = Type; if (name == "all") { return FoldAllAnyParity( context, std::move(funcRef), &Scalar::AND, Scalar{true}); } else if (name == "any") { return FoldAllAnyParity( context, std::move(funcRef), &Scalar::OR, Scalar{false}); } else if (name == "associated") { bool gotConstant{true}; const Expr *firstArgExpr{args[0]->UnwrapExpr()}; if (!firstArgExpr || !IsNullPointer(*firstArgExpr)) { gotConstant = false; } else if (args[1]) { // There's a second argument const Expr *secondArgExpr{args[1]->UnwrapExpr()}; if (!secondArgExpr || !IsNullPointer(*secondArgExpr)) { gotConstant = false; } } return gotConstant ? Expr{false} : Expr{std::move(funcRef)}; } else if (name == "bge" || name == "bgt" || name == "ble" || name == "blt") { static_assert(std::is_same_v, BOZLiteralConstant>); // The arguments to these intrinsics can be of different types. In that // case, the shorter of the two would need to be zero-extended to match // the size of the other. If at least one of the operands is not a constant, // the zero-extending will be done during lowering. Otherwise, the folding // must be done here. std::optional> constArgs[2]; for (int i{0}; i <= 1; i++) { if (BOZLiteralConstant * x{UnwrapExpr(args[i])}) { constArgs[i] = AsGenericExpr(Constant{std::move(*x)}); } else if (auto *x{UnwrapExpr>(args[i])}) { common::visit( [&](const auto &ix) { using IntT = typename std::decay_t::Result; if (auto *c{UnwrapConstantValue(ix)}) { constArgs[i] = ZeroExtend(*c); } }, x->u); } } if (constArgs[0] && constArgs[1]) { auto fptr{&Scalar::BGE}; if (name == "bge") { // done in fptr declaration } else if (name == "bgt") { fptr = &Scalar::BGT; } else if (name == "ble") { fptr = &Scalar::BLE; } else if (name == "blt") { fptr = &Scalar::BLT; } else { common::die("missing case to fold intrinsic function %s", name.c_str()); } for (int i{0}; i <= 1; i++) { *args[i] = std::move(constArgs[i].value()); } return FoldElementalIntrinsic(context, std::move(funcRef), ScalarFunc( [&fptr]( const Scalar &i, const Scalar &j) { return Scalar{std::invoke(fptr, i, j)}; })); } else { return Expr{std::move(funcRef)}; } } else if (name == "btest") { if (const auto *ix{UnwrapExpr>(args[0])}) { return common::visit( [&](const auto &x) { using IT = ResultType; return FoldElementalIntrinsic(context, std::move(funcRef), ScalarFunc( [&](const Scalar &x, const Scalar &pos) { auto posVal{pos.ToInt64()}; if (posVal < 0 || posVal >= x.bits) { context.messages().Say( "POS=%jd out of range for BTEST"_err_en_US, static_cast(posVal)); } return Scalar{x.BTEST(posVal)}; })); }, ix->u); } } else if (name == "dot_product") { return FoldDotProduct(context, std::move(funcRef)); } else if (name == "extends_type_of") { // Type extension testing with EXTENDS_TYPE_OF() ignores any type // parameters. Returns a constant truth value when the result is known now. if (args[0] && args[1]) { auto t0{args[0]->GetType()}; auto t1{args[1]->GetType()}; if (t0 && t1) { if (auto result{t0->ExtendsTypeOf(*t1)}) { return Expr{*result}; } } } } else if (name == "isnan" || name == "__builtin_ieee_is_nan") { // Only replace the type of the function if we can do the fold if (args[0] && args[0]->UnwrapExpr() && IsActuallyConstant(*args[0]->UnwrapExpr())) { auto restorer{context.messages().DiscardMessages()}; using DefaultReal = Type; return FoldElementalIntrinsic(context, std::move(funcRef), ScalarFunc([](const Scalar &x) { return Scalar{x.IsNotANumber()}; })); } } else if (name == "__builtin_ieee_is_negative") { auto restorer{context.messages().DiscardMessages()}; using DefaultReal = Type; if (args[0] && args[0]->UnwrapExpr() && IsActuallyConstant(*args[0]->UnwrapExpr())) { return FoldElementalIntrinsic(context, std::move(funcRef), ScalarFunc([](const Scalar &x) { return Scalar{x.IsNegative()}; })); } } else if (name == "__builtin_ieee_is_normal") { auto restorer{context.messages().DiscardMessages()}; using DefaultReal = Type; if (args[0] && args[0]->UnwrapExpr() && IsActuallyConstant(*args[0]->UnwrapExpr())) { return FoldElementalIntrinsic(context, std::move(funcRef), ScalarFunc([](const Scalar &x) { return Scalar{x.IsNormal()}; })); } } else if (name == "is_contiguous") { if (args.at(0)) { if (auto *expr{args[0]->UnwrapExpr()}) { if (auto contiguous{IsContiguous(*expr, context)}) { return Expr{*contiguous}; } } else if (auto *assumedType{args[0]->GetAssumedTypeDummy()}) { if (auto contiguous{IsContiguous(*assumedType, context)}) { return Expr{*contiguous}; } } } } else if (name == "is_iostat_end") { if (args[0] && args[0]->UnwrapExpr() && IsActuallyConstant(*args[0]->UnwrapExpr())) { using Int64 = Type; return FoldElementalIntrinsic(context, std::move(funcRef), ScalarFunc([](const Scalar &x) { return Scalar{x.ToInt64() == FORTRAN_RUNTIME_IOSTAT_END}; })); } } else if (name == "is_iostat_eor") { if (args[0] && args[0]->UnwrapExpr() && IsActuallyConstant(*args[0]->UnwrapExpr())) { using Int64 = Type; return FoldElementalIntrinsic(context, std::move(funcRef), ScalarFunc([](const Scalar &x) { return Scalar{x.ToInt64() == FORTRAN_RUNTIME_IOSTAT_EOR}; })); } } else if (name == "lge" || name == "lgt" || name == "lle" || name == "llt") { // Rewrite LGE/LGT/LLE/LLT into ASCII character relations auto *cx0{UnwrapExpr>(args[0])}; auto *cx1{UnwrapExpr>(args[1])}; if (cx0 && cx1) { return Fold(context, ConvertToType( PackageRelation(name == "lge" ? RelationalOperator::GE : name == "lgt" ? RelationalOperator::GT : name == "lle" ? RelationalOperator::LE : RelationalOperator::LT, ConvertToType(std::move(*cx0)), ConvertToType(std::move(*cx1))))); } } else if (name == "logical") { if (auto *expr{UnwrapExpr>(args[0])}) { return Fold(context, ConvertToType(std::move(*expr))); } } else if (name == "matmul") { return FoldMatmul(context, std::move(funcRef)); } else if (name == "out_of_range") { if (Expr * cx{UnwrapExpr>(args[0])}) { auto restorer{context.messages().DiscardMessages()}; *args[0] = Fold(context, std::move(*cx)); if (Expr & folded{DEREF(args[0].value().UnwrapExpr())}; IsActuallyConstant(folded)) { std::optional> result; if (Expr * realMold{UnwrapExpr>(args[1])}) { if (const auto *xInt{UnwrapExpr>(folded)}) { result.emplace(); std::visit( [&](const auto &mold, const auto &x) { using RealType = typename std::decay_t::Result; static_assert(RealType::category == TypeCategory::Real); using Scalar = typename RealType::Scalar; using xType = typename std::decay_t::Result; const auto &xConst{DEREF(UnwrapExpr>(x))}; for (const auto &elt : xConst.values()) { result->emplace_back( Scalar::template FromInteger(elt).flags.test( RealFlag::Overflow)); } }, realMold->u, xInt->u); } else if (const auto *xReal{UnwrapExpr>(folded)}) { result.emplace(); std::visit( [&](const auto &mold, const auto &x) { using RealType = typename std::decay_t::Result; static_assert(RealType::category == TypeCategory::Real); using Scalar = typename RealType::Scalar; using xType = typename std::decay_t::Result; const auto &xConst{DEREF(UnwrapExpr>(x))}; for (const auto &elt : xConst.values()) { result->emplace_back(elt.IsFinite() && Scalar::template Convert(elt).flags.test( RealFlag::Overflow)); } }, realMold->u, xReal->u); } } else if (Expr * intMold{UnwrapExpr>(args[1])}) { if (const auto *xInt{UnwrapExpr>(folded)}) { result.emplace(); std::visit( [&](const auto &mold, const auto &x) { using IntType = typename std::decay_t::Result; static_assert(IntType::category == TypeCategory::Integer); using Scalar = typename IntType::Scalar; using xType = typename std::decay_t::Result; const auto &xConst{DEREF(UnwrapExpr>(x))}; for (const auto &elt : xConst.values()) { result->emplace_back( Scalar::template ConvertSigned(elt).overflow); } }, intMold->u, xInt->u); } else if (Expr * cRound{args.size() >= 3 ? UnwrapExpr>(args[2]) : nullptr}; !cRound || IsActuallyConstant(*args[2]->UnwrapExpr())) { if (const auto *xReal{UnwrapExpr>(folded)}) { common::RoundingMode roundingMode{common::RoundingMode::ToZero}; if (cRound && common::visit( [](const auto &x) { using xType = typename std::decay_t::Result; return GetScalarConstantValue(x) .value() .IsTrue(); }, cRound->u)) { // ROUND=.TRUE. - convert with NINT() roundingMode = common::RoundingMode::TiesAwayFromZero; } result.emplace(); std::visit( [&](const auto &mold, const auto &x) { using IntType = typename std::decay_t::Result; static_assert(IntType::category == TypeCategory::Integer); using Scalar = typename IntType::Scalar; using xType = typename std::decay_t::Result; const auto &xConst{DEREF(UnwrapExpr>(x))}; for (const auto &elt : xConst.values()) { // Note that OUT_OF_RANGE(Inf/NaN) is .TRUE. for the // real->integer case, but not for real->real. result->emplace_back(!elt.IsFinite() || elt.template ToInteger(roundingMode) .flags.test(RealFlag::Overflow)); } }, intMold->u, xReal->u); } } } if (result) { if (auto extents{GetConstantExtents(context, folded)}) { return Expr{ Constant{std::move(*result), std::move(*extents)}}; } } } } } else if (name == "parity") { return FoldAllAnyParity( context, std::move(funcRef), &Scalar::NEQV, Scalar{false}); } else if (name == "same_type_as") { // Type equality testing with SAME_TYPE_AS() ignores any type parameters. // Returns a constant truth value when the result is known now. if (args[0] && args[1]) { auto t0{args[0]->GetType()}; auto t1{args[1]->GetType()}; if (t0 && t1) { if (auto result{t0->SameTypeAs(*t1)}) { return Expr{*result}; } } } } else if (name == "__builtin_ieee_support_datatype" || name == "__builtin_ieee_support_denormal" || name == "__builtin_ieee_support_divide" || name == "__builtin_ieee_support_inf" || name == "__builtin_ieee_support_io" || name == "__builtin_ieee_support_nan" || name == "__builtin_ieee_support_sqrt" || name == "__builtin_ieee_support_standard" || name == "__builtin_ieee_support_subnormal" || name == "__builtin_ieee_support_underflow_control") { return Expr{true}; } return Expr{std::move(funcRef)}; } template Expr FoldOperation( FoldingContext &context, Relational &&relation) { if (auto array{ApplyElementwise(context, relation, std::function(Expr &&, Expr &&)>{ [=](Expr &&x, Expr &&y) { return Expr{Relational{ Relational{relation.opr, std::move(x), std::move(y)}}}; }})}) { return *array; } if (auto folded{OperandsAreConstants(relation)}) { bool result{}; if constexpr (T::category == TypeCategory::Integer) { result = Satisfies(relation.opr, folded->first.CompareSigned(folded->second)); } else if constexpr (T::category == TypeCategory::Real) { result = Satisfies(relation.opr, folded->first.Compare(folded->second)); } else if constexpr (T::category == TypeCategory::Complex) { result = (relation.opr == RelationalOperator::EQ) == folded->first.Equals(folded->second); } else if constexpr (T::category == TypeCategory::Character) { result = Satisfies(relation.opr, Compare(folded->first, folded->second)); } else { static_assert(T::category != TypeCategory::Logical); } return Expr{Constant{result}}; } return Expr{Relational{std::move(relation)}}; } Expr FoldOperation( FoldingContext &context, Relational &&relation) { return common::visit( [&](auto &&x) { return Expr{FoldOperation(context, std::move(x))}; }, std::move(relation.u)); } template Expr> FoldOperation( FoldingContext &context, Not &&x) { if (auto array{ApplyElementwise(context, x)}) { return *array; } using Ty = Type; auto &operand{x.left()}; if (auto value{GetScalarConstantValue(operand)}) { return Expr{Constant{!value->IsTrue()}}; } return Expr{x}; } template Expr> FoldOperation( FoldingContext &context, LogicalOperation &&operation) { using LOGICAL = Type; if (auto array{ApplyElementwise(context, operation, std::function(Expr &&, Expr &&)>{ [=](Expr &&x, Expr &&y) { return Expr{LogicalOperation{ operation.logicalOperator, std::move(x), std::move(y)}}; }})}) { return *array; } if (auto folded{OperandsAreConstants(operation)}) { bool xt{folded->first.IsTrue()}, yt{folded->second.IsTrue()}, result{}; switch (operation.logicalOperator) { case LogicalOperator::And: result = xt && yt; break; case LogicalOperator::Or: result = xt || yt; break; case LogicalOperator::Eqv: result = xt == yt; break; case LogicalOperator::Neqv: result = xt != yt; break; case LogicalOperator::Not: DIE("not a binary operator"); } return Expr{Constant{result}}; } return Expr{std::move(operation)}; } #ifdef _MSC_VER // disable bogus warning about missing definitions #pragma warning(disable : 4661) #endif FOR_EACH_LOGICAL_KIND(template class ExpressionBase, ) template class ExpressionBase; } // namespace Fortran::evaluate