//===-- 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)}; } // OUT_OF_RANGE(x,mold[,round]) references are entirely rewritten here into // expressions, which are then folded into constants when 'x' and 'round' // are constant. It is guaranteed that 'x' is evaluated at most once. // TODO: unsigned template Expr RealToIntBoundHelper(bool round, bool negate) { using RType = Type; using RealType = Scalar; using IntType = Scalar>; RealType result{}; // 0. common::RoundingMode roundingMode{round ? common::RoundingMode::TiesAwayFromZero : common::RoundingMode::ToZero}; // Add decreasing powers of two to the result to find the largest magnitude // value that can be converted to the integer type without overflow. RealType at{RealType::FromInteger(IntType{negate ? -1 : 1}).value}; bool decrement{true}; while (!at.template ToInteger(roundingMode) .flags.test(RealFlag::Overflow)) { auto tmp{at.SCALE(IntType{1})}; if (tmp.flags.test(RealFlag::Overflow)) { decrement = false; break; } at = tmp.value; } while (true) { if (decrement) { at = at.SCALE(IntType{-1}).value; } else { decrement = true; } auto tmp{at.Add(result)}; if (tmp.flags.test(RealFlag::Inexact)) { break; } else if (!tmp.value.template ToInteger(roundingMode) .flags.test(RealFlag::Overflow)) { result = tmp.value; } } return AsCategoryExpr(Constant{std::move(result)}); } static Expr RealToIntBound( int xRKind, int moldIKind, bool round, bool negate) { switch (xRKind) { #define ICASES(RK) \ switch (moldIKind) { \ case 1: \ return RealToIntBoundHelper(round, negate); \ break; \ case 2: \ return RealToIntBoundHelper(round, negate); \ break; \ case 4: \ return RealToIntBoundHelper(round, negate); \ break; \ case 8: \ return RealToIntBoundHelper(round, negate); \ break; \ case 16: \ return RealToIntBoundHelper(round, negate); \ break; \ } \ break case 2: ICASES(2); break; case 3: ICASES(3); break; case 4: ICASES(4); break; case 8: ICASES(8); break; case 10: ICASES(10); break; case 16: ICASES(16); break; } DIE("RealToIntBound: no case"); #undef ICASES } class RealToIntLimitHelper { public: using Result = std::optional>; using Types = RealTypes; RealToIntLimitHelper( FoldingContext &context, Expr &&hi, Expr &lo) : context_{context}, hi_{std::move(hi)}, lo_{lo} {} template Result Test() { if (UnwrapExpr>(hi_)) { bool promote{T::kind < 16}; Result constResult; if (auto hiV{GetScalarConstantValue(hi_)}) { auto loV{GetScalarConstantValue(lo_)}; CHECK(loV.has_value()); auto diff{hiV->Subtract(*loV, Rounding{common::RoundingMode::ToZero})}; promote = promote && (diff.flags.test(RealFlag::Overflow) || diff.flags.test(RealFlag::Inexact)); constResult = AsCategoryExpr(Constant{std::move(diff.value)}); } if (promote) { constexpr int nextKind{T::kind < 4 ? 4 : T::kind == 4 ? 8 : 16}; using T2 = Type; hi_ = Expr{Fold(context_, ConvertToType(std::move(hi_)))}; lo_ = Expr{Fold(context_, ConvertToType(std::move(lo_)))}; if (constResult) { // Use promoted constants on next iteration of SearchTypes return std::nullopt; } } if (constResult) { return constResult; } else { return AsCategoryExpr(std::move(hi_) - Expr{lo_}); } } else { return std::nullopt; } } private: FoldingContext &context_; Expr hi_; Expr &lo_; }; static std::optional> RealToIntLimit( FoldingContext &context, Expr &&hi, Expr &lo) { return common::SearchTypes(RealToIntLimitHelper{context, std::move(hi), lo}); } // RealToRealBounds() returns a pair (HUGE(x),REAL(HUGE(mold),KIND(x))) // when REAL(HUGE(x),KIND(mold)) overflows, and std::nullopt otherwise. template std::optional, Expr>> RealToRealBoundsHelper() { using RType = Type; using RealType = Scalar; using MoldRealType = Scalar>; if (!MoldRealType::Convert(RealType::HUGE()).flags.test(RealFlag::Overflow)) { return std::nullopt; } else { return std::make_pair(AsCategoryExpr(Constant{ RealType::Convert(MoldRealType::HUGE()).value}), AsCategoryExpr(Constant{RealType::HUGE()})); } } static std::optional, Expr>> RealToRealBounds(int xRKind, int moldRKind) { switch (xRKind) { #define RCASES(RK) \ switch (moldRKind) { \ case 2: \ return RealToRealBoundsHelper(); \ break; \ case 3: \ return RealToRealBoundsHelper(); \ break; \ case 4: \ return RealToRealBoundsHelper(); \ break; \ case 8: \ return RealToRealBoundsHelper(); \ break; \ case 10: \ return RealToRealBoundsHelper(); \ break; \ case 16: \ return RealToRealBoundsHelper(); \ break; \ } \ break case 2: RCASES(2); break; case 3: RCASES(3); break; case 4: RCASES(4); break; case 8: RCASES(8); break; case 10: RCASES(10); break; case 16: RCASES(16); break; } DIE("RealToRealBounds: no case"); #undef RCASES } template std::optional> IntToRealBoundHelper(bool negate) { using IType = Type; using IntType = Scalar; using RealType = Scalar>; IntType result{}; // 0 while (true) { std::optional next; for (int bit{0}; bit < IntType::bits; ++bit) { IntType power{IntType{}.IBSET(bit)}; if (power.IsNegative()) { if (!negate) { break; } } else if (negate) { power = power.Negate().value; } auto tmp{power.AddSigned(result)}; if (tmp.overflow || RealType::FromInteger(tmp.value).flags.test(RealFlag::Overflow)) { break; } next = tmp.value; } if (next) { CHECK(result.CompareSigned(*next) != Ordering::Equal); result = *next; } else { break; } } if (result.CompareSigned(IntType::HUGE()) == Ordering::Equal) { return std::nullopt; } else { return AsCategoryExpr(Constant{std::move(result)}); } } static std::optional> IntToRealBound( int xIKind, int moldRKind, bool negate) { switch (xIKind) { #define RCASES(IK) \ switch (moldRKind) { \ case 2: \ return IntToRealBoundHelper(negate); \ break; \ case 3: \ return IntToRealBoundHelper(negate); \ break; \ case 4: \ return IntToRealBoundHelper(negate); \ break; \ case 8: \ return IntToRealBoundHelper(negate); \ break; \ case 10: \ return IntToRealBoundHelper(negate); \ break; \ case 16: \ return IntToRealBoundHelper(negate); \ break; \ } \ break case 1: RCASES(1); break; case 2: RCASES(2); break; case 4: RCASES(4); break; case 8: RCASES(8); break; case 16: RCASES(16); break; } DIE("IntToRealBound: no case"); #undef RCASES } template std::optional> IntToIntBoundHelper() { if constexpr (X_IKIND <= MOLD_IKIND) { return std::nullopt; } else { using XIType = Type; using IntegerType = Scalar; using MoldIType = Type; using MoldIntegerType = Scalar; return AsCategoryExpr(Constant{ IntegerType::ConvertSigned(MoldIntegerType::HUGE()).value}); } } static std::optional> IntToIntBound( int xIKind, int moldIKind) { switch (xIKind) { #define ICASES(IK) \ switch (moldIKind) { \ case 1: \ return IntToIntBoundHelper(); \ break; \ case 2: \ return IntToIntBoundHelper(); \ break; \ case 4: \ return IntToIntBoundHelper(); \ break; \ case 8: \ return IntToIntBoundHelper(); \ break; \ case 16: \ return IntToIntBoundHelper(); \ break; \ } \ break case 1: ICASES(1); break; case 2: ICASES(2); break; case 4: ICASES(4); break; case 8: ICASES(8); break; case 16: ICASES(16); break; } DIE("IntToIntBound: no case"); #undef ICASES } // ApplyIntrinsic() constructs the typed expression representation // for a specific intrinsic function reference. // TODO: maybe move into tools.h? class IntrinsicCallHelper { public: explicit IntrinsicCallHelper(SpecificCall &&call) : call_{call} { CHECK(proc_.IsFunction()); typeAndShape_ = proc_.functionResult->GetTypeAndShape(); CHECK(typeAndShape_ != nullptr); } using Result = std::optional>; using Types = LengthlessIntrinsicTypes; template Result Test() { if (T::category == typeAndShape_->type().category() && T::kind == typeAndShape_->type().kind()) { return AsGenericExpr(FunctionRef{ ProcedureDesignator{std::move(call_.specificIntrinsic)}, std::move(call_.arguments)}); } else { return std::nullopt; } } private: SpecificCall call_; const characteristics::Procedure &proc_{ call_.specificIntrinsic.characteristics.value()}; const characteristics::TypeAndShape *typeAndShape_{nullptr}; }; static Expr ApplyIntrinsic( FoldingContext &context, const std::string &func, ActualArguments &&args) { auto found{ context.intrinsics().Probe(CallCharacteristics{func}, args, context)}; CHECK(found.has_value()); auto result{common::SearchTypes(IntrinsicCallHelper{std::move(*found)})}; CHECK(result.has_value()); return *result; } static Expr CompareUnsigned(FoldingContext &context, const char *intrin, Expr &&x, Expr &&y) { Expr result{ApplyIntrinsic(context, intrin, ActualArguments{ ActualArgument{std::move(x)}, ActualArgument{std::move(y)}})}; return DEREF(UnwrapExpr>(result)); } // Determines the right kind of INTEGER to hold the bits of a REAL type. static Expr IntTransferMold( const TargetCharacteristics &target, DynamicType realType, bool asVector) { CHECK(realType.category() == TypeCategory::Real); int rKind{realType.kind()}; int iKind{std::max(target.GetAlignment(TypeCategory::Real, rKind), target.GetByteSize(TypeCategory::Real, rKind))}; CHECK(target.CanSupportType(TypeCategory::Integer, iKind)); DynamicType iType{TypeCategory::Integer, iKind}; ConstantSubscripts shape; if (asVector) { shape = ConstantSubscripts{1}; } Constant value{ std::vector>{0}, std::move(shape)}; auto expr{ConvertToType(iType, AsGenericExpr(std::move(value)))}; CHECK(expr.has_value()); return std::move(*expr); } static Expr GetRealBits(FoldingContext &context, Expr &&x) { auto xType{x.GetType()}; CHECK(xType.has_value()); bool asVector{x.Rank() > 0}; return ApplyIntrinsic(context, "transfer", ActualArguments{ActualArgument{AsGenericExpr(std::move(x))}, ActualArgument{IntTransferMold( context.targetCharacteristics(), *xType, asVector)}}); } template static Expr> RewriteOutOfRange( FoldingContext &context, FunctionRef> &&funcRef) { using ResultType = Type; ActualArguments &args{funcRef.arguments()}; // Fold x= and round= unconditionally if (auto *x{UnwrapExpr>(args[0])}) { *args[0] = Fold(context, std::move(*x)); } if (args.size() >= 3) { if (auto *round{UnwrapExpr>(args[2])}) { *args[2] = Fold(context, std::move(*round)); } } if (auto *x{UnwrapExpr>(args[0])}) { x = UnwrapExpr>(args[0]); CHECK(x != nullptr); if (const auto *mold{UnwrapExpr>(args[1])}) { DynamicType xType{x->GetType().value()}; std::optional> result; bool alwaysFalse{false}; if (auto *iXExpr{UnwrapExpr>(*x)}) { int iXKind{iXExpr->GetType().value().kind()}; if (auto *iMoldExpr{UnwrapExpr>(*mold)}) { // INTEGER -> INTEGER int iMoldKind{iMoldExpr->GetType().value().kind()}; if (auto hi{IntToIntBound(iXKind, iMoldKind)}) { // 'hi' is INT(HUGE(mold), KIND(x)) // OUT_OF_RANGE(x,mold) = (x + (hi + 1)) .UGT. (2*hi + 1) auto one{DEREF(UnwrapExpr>(ConvertToType( xType, AsGenericExpr(Constant{1}))))}; auto lhs{std::move(*iXExpr) + (Expr{*hi} + Expr{one})}; auto two{DEREF(UnwrapExpr>(ConvertToType( xType, AsGenericExpr(Constant{2}))))}; auto rhs{std::move(two) * std::move(*hi) + std::move(one)}; result = CompareUnsigned(context, "bgt", Expr{std::move(lhs)}, Expr{std::move(rhs)}); } else { alwaysFalse = true; } } else if (auto *rMoldExpr{UnwrapExpr>(*mold)}) { // INTEGER -> REAL int rMoldKind{rMoldExpr->GetType().value().kind()}; if (auto hi{IntToRealBound(iXKind, rMoldKind, /*negate=*/false)}) { // OUT_OF_RANGE(x,mold) = (x - lo) .UGT. (hi - lo) auto lo{IntToRealBound(iXKind, rMoldKind, /*negate=*/true)}; CHECK(lo.has_value()); auto lhs{std::move(*iXExpr) - Expr{*lo}}; auto rhs{std::move(*hi) - std::move(*lo)}; result = CompareUnsigned(context, "bgt", Expr{std::move(lhs)}, Expr{std::move(rhs)}); } else { alwaysFalse = true; } } } else if (auto *rXExpr{UnwrapExpr>(*x)}) { int rXKind{rXExpr->GetType().value().kind()}; if (auto *iMoldExpr{UnwrapExpr>(*mold)}) { // REAL -> INTEGER int iMoldKind{iMoldExpr->GetType().value().kind()}; auto hi{RealToIntBound(rXKind, iMoldKind, false, false)}; auto lo{RealToIntBound(rXKind, iMoldKind, false, true)}; if (args.size() >= 3) { // Bounds depend on round= value if (auto *round{UnwrapExpr>(args[2])}) { if (const Symbol * whole{UnwrapWholeSymbolDataRef(*round)}; whole && semantics::IsOptional(whole->GetUltimate()) && context.languageFeatures().ShouldWarn( common::UsageWarning::OptionalMustBePresent)) { if (auto source{args[2]->sourceLocation()}) { context.messages().Say( common::UsageWarning::OptionalMustBePresent, *source, "ROUND= argument to OUT_OF_RANGE() is an optional dummy argument that must be present at execution"_warn_en_US); } } auto rlo{RealToIntBound(rXKind, iMoldKind, true, true)}; auto rhi{RealToIntBound(rXKind, iMoldKind, true, false)}; auto mlo{Fold(context, ApplyIntrinsic(context, "merge", ActualArguments{ ActualArgument{Expr{std::move(rlo)}}, ActualArgument{Expr{std::move(lo)}}, ActualArgument{Expr{*round}}}))}; auto mhi{Fold(context, ApplyIntrinsic(context, "merge", ActualArguments{ ActualArgument{Expr{std::move(rhi)}}, ActualArgument{Expr{std::move(hi)}}, ActualArgument{std::move(*round)}}))}; lo = std::move(DEREF(UnwrapExpr>(mlo))); hi = std::move(DEREF(UnwrapExpr>(mhi))); } } // OUT_OF_RANGE(x,mold[,round]) = // TRANSFER(x - lo, int) .UGT. TRANSFER(hi - lo, int) hi = Fold(context, std::move(hi)); lo = Fold(context, std::move(lo)); if (auto rhs{RealToIntLimit(context, std::move(hi), lo)}) { Expr lhs{std::move(*rXExpr) - std::move(lo)}; result = CompareUnsigned(context, "bgt", GetRealBits(context, std::move(lhs)), GetRealBits(context, std::move(*rhs))); } } else if (auto *rMoldExpr{UnwrapExpr>(*mold)}) { // REAL -> REAL // Only finite arguments with ABS(x) > HUGE(mold) are .TRUE. // OUT_OF_RANGE(x,mold) = // TRANSFER(ABS(x) - HUGE(mold), int) - 1 .ULT. // TRANSFER(HUGE(mold), int) // Note that OUT_OF_RANGE(+/-Inf or NaN,mold) = // TRANSFER(+Inf or Nan, int) - 1 .ULT. TRANSFER(HUGE(mold), int) int rMoldKind{rMoldExpr->GetType().value().kind()}; if (auto bounds{RealToRealBounds(rXKind, rMoldKind)}) { auto &[moldHuge, xHuge]{*bounds}; Expr abs{ApplyIntrinsic(context, "abs", ActualArguments{ ActualArgument{Expr{std::move(*rXExpr)}}})}; auto &absR{DEREF(UnwrapExpr>(abs))}; Expr diffBits{ GetRealBits(context, std::move(absR) - std::move(moldHuge))}; auto &diffBitsI{DEREF(UnwrapExpr>(diffBits))}; Expr decr{std::move(diffBitsI) - Expr{Expr{1}}}; result = CompareUnsigned(context, "blt", std::move(decr), GetRealBits(context, std::move(xHuge))); } else { alwaysFalse = true; } } } if (alwaysFalse) { // xType can never overflow moldType, so // OUT_OF_RANGE(x) = (x /= 0) .AND. .FALSE. // which has the same shape as x. Expr scalarFalse{ Constant{Scalar{false}}}; if (x->Rank() > 0) { if (auto nez{Relate(context.messages(), RelationalOperator::NE, std::move(*x), AsGenericExpr(Constant{0}))}) { result = Expr{LogicalOperation{ LogicalOperator::And, std::move(*nez), std::move(scalarFalse)}}; } } else { result = std::move(scalarFalse); } } if (result) { auto restorer{context.messages().DiscardMessages()}; return Fold( context, AsExpr(ConvertToType(std::move(*result)))); } } } return AsExpr(std::move(funcRef)); } static std::optional GetRoundingMode( const std::optional &arg) { if (arg) { if (const auto *cst{UnwrapExpr>(*arg)}) { if (auto constr{cst->GetScalarValue()}) { if (StructureConstructorValues & values{constr->values()}; values.size() == 1) { const Expr &value{values.begin()->second.value()}; if (auto code{ToInt64(value)}) { return static_cast(*code); } } } } } return std::nullopt; } 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}; 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") { using SameInt = Type; 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 (const auto *ux{UnwrapExpr>(args[0])}) { return common::visit( [&](const auto &x) { using UT = 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)}; })); }, ux->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") { return RewriteOutOfRange(context, std::move(funcRef)); } 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") { return Expr{true}; } else if (name == "__builtin_ieee_support_denormal") { return Expr{context.targetCharacteristics().ieeeFeatures().test( IeeeFeature::Denormal)}; } else if (name == "__builtin_ieee_support_divide") { return Expr{context.targetCharacteristics().ieeeFeatures().test( IeeeFeature::Divide)}; } else if (name == "__builtin_ieee_support_flag") { return Expr{context.targetCharacteristics().ieeeFeatures().test( IeeeFeature::Flags)}; } else if (name == "__builtin_ieee_support_halting") { if (!context.targetCharacteristics() .haltingSupportIsUnknownAtCompileTime()) { return Expr{context.targetCharacteristics().ieeeFeatures().test( IeeeFeature::Halting)}; } } else if (name == "__builtin_ieee_support_inf") { return Expr{ context.targetCharacteristics().ieeeFeatures().test(IeeeFeature::Inf)}; } else if (name == "__builtin_ieee_support_io") { return Expr{ context.targetCharacteristics().ieeeFeatures().test(IeeeFeature::Io)}; } else if (name == "__builtin_ieee_support_nan") { return Expr{ context.targetCharacteristics().ieeeFeatures().test(IeeeFeature::NaN)}; } else if (name == "__builtin_ieee_support_rounding") { if (context.targetCharacteristics().ieeeFeatures().test( IeeeFeature::Rounding)) { if (auto mode{GetRoundingMode(args[0])}) { return Expr{mode != common::RoundingMode::TiesAwayFromZero}; } } } else if (name == "__builtin_ieee_support_sqrt") { return Expr{ context.targetCharacteristics().ieeeFeatures().test(IeeeFeature::Sqrt)}; } else if (name == "__builtin_ieee_support_standard") { return Expr{context.targetCharacteristics().ieeeFeatures().test( IeeeFeature::Standard)}; } else if (name == "__builtin_ieee_support_subnormal") { return Expr{context.targetCharacteristics().ieeeFeatures().test( IeeeFeature::Subnormal)}; } else if (name == "__builtin_ieee_support_underflow_control") { // Setting kind=0 checks subnormal flushing control across all type kinds. if (args[0]) { return Expr{ context.targetCharacteristics().hasSubnormalFlushingControl( args[0]->GetType().value().kind())}; } else { return Expr{ context.targetCharacteristics().hasSubnormalFlushingControl( /*any=*/false)}; } } 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::Unsigned) { result = Satisfies( relation.opr, folded->first.CompareUnsigned(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