//===-- lib/Evaluate/fold-integer.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" namespace Fortran::evaluate { template Expr> LBOUND(FoldingContext &context, FunctionRef> &&funcRef) { using T = Type; ActualArguments &args{funcRef.arguments()}; if (const auto *array{UnwrapExpr>(args[0])}) { if (int rank{array->Rank()}; rank > 0) { std::optional dim; if (funcRef.Rank() == 0) { // Optional DIM= argument is present: result is scalar. if (auto dim64{GetInt64Arg(args[1])}) { if (*dim64 < 1 || *dim64 > rank) { context.messages().Say("DIM=%jd dimension is out of range for " "rank-%d array"_en_US, *dim64, rank); return MakeInvalidIntrinsic(std::move(funcRef)); } else { dim = *dim64 - 1; // 1-based to 0-based } } else { // DIM= is present but not constant return Expr{std::move(funcRef)}; } } bool lowerBoundsAreOne{true}; if (auto named{ExtractNamedEntity(*array)}) { const Symbol &symbol{named->GetLastSymbol()}; if (symbol.Rank() == rank) { lowerBoundsAreOne = false; if (dim) { return Fold(context, ConvertToType(GetLowerBound(context, *named, *dim))); } else if (auto extents{ AsExtentArrayExpr(GetLowerBounds(context, *named))}) { return Fold(context, ConvertToType(Expr{std::move(*extents)})); } } else { lowerBoundsAreOne = symbol.Rank() == 0; // LBOUND(array%component) } } if (lowerBoundsAreOne) { if (dim) { return Expr{1}; } else { std::vector> ones(rank, Scalar{1}); return Expr{ Constant{std::move(ones), ConstantSubscripts{rank}}}; } } } } return Expr{std::move(funcRef)}; } template Expr> UBOUND(FoldingContext &context, FunctionRef> &&funcRef) { using T = Type; ActualArguments &args{funcRef.arguments()}; if (auto *array{UnwrapExpr>(args[0])}) { if (int rank{array->Rank()}; rank > 0) { std::optional dim; if (funcRef.Rank() == 0) { // Optional DIM= argument is present: result is scalar. if (auto dim64{GetInt64Arg(args[1])}) { if (*dim64 < 1 || *dim64 > rank) { context.messages().Say("DIM=%jd dimension is out of range for " "rank-%d array"_en_US, *dim64, rank); return MakeInvalidIntrinsic(std::move(funcRef)); } else { dim = *dim64 - 1; // 1-based to 0-based } } else { // DIM= is present but not constant return Expr{std::move(funcRef)}; } } bool takeBoundsFromShape{true}; if (auto named{ExtractNamedEntity(*array)}) { const Symbol &symbol{named->GetLastSymbol()}; if (symbol.Rank() == rank) { takeBoundsFromShape = false; if (dim) { if (semantics::IsAssumedSizeArray(symbol) && *dim == rank) { return Expr{-1}; } else if (auto ub{GetUpperBound(context, *named, *dim)}) { return Fold(context, ConvertToType(std::move(*ub))); } } else { Shape ubounds{GetUpperBounds(context, *named)}; if (semantics::IsAssumedSizeArray(symbol)) { CHECK(!ubounds.back()); ubounds.back() = ExtentExpr{-1}; } if (auto extents{AsExtentArrayExpr(ubounds)}) { return Fold(context, ConvertToType(Expr{std::move(*extents)})); } } } else { takeBoundsFromShape = symbol.Rank() == 0; // UBOUND(array%component) } } if (takeBoundsFromShape) { if (auto shape{GetShape(context, *array)}) { if (dim) { if (auto &dimSize{shape->at(*dim)}) { return Fold(context, ConvertToType(Expr{std::move(*dimSize)})); } } else if (auto shapeExpr{AsExtentArrayExpr(*shape)}) { return Fold(context, ConvertToType(std::move(*shapeExpr))); } } } } } return Expr{std::move(funcRef)}; } template Expr> FoldIntrinsicFunction( FoldingContext &context, FunctionRef> &&funcRef) { using T = Type; using Int4 = Type; ActualArguments &args{funcRef.arguments()}; auto *intrinsic{std::get_if(&funcRef.proc().u)}; CHECK(intrinsic); std::string name{intrinsic->name}; if (name == "abs") { return FoldElementalIntrinsic(context, std::move(funcRef), ScalarFunc([&context](const Scalar &i) -> Scalar { typename Scalar::ValueWithOverflow j{i.ABS()}; if (j.overflow) { context.messages().Say( "abs(integer(kind=%d)) folding overflowed"_en_US, KIND); } return j.value; })); } else if (name == "bit_size") { return Expr{Scalar::bits}; } else if (name == "ceiling" || name == "floor" || name == "nint") { if (const auto *cx{UnwrapExpr>(args[0])}) { // NINT rounds ties away from zero, not to even common::RoundingMode mode{name == "ceiling" ? common::RoundingMode::Up : name == "floor" ? common::RoundingMode::Down : common::RoundingMode::TiesAwayFromZero}; return std::visit( [&](const auto &kx) { using TR = ResultType; return FoldElementalIntrinsic(context, std::move(funcRef), ScalarFunc([&](const Scalar &x) { auto y{x.template ToInteger>(mode)}; if (y.flags.test(RealFlag::Overflow)) { context.messages().Say( "%s intrinsic folding overflow"_en_US, name); } return y.value; })); }, cx->u); } } else if (name == "count") { if (!args[1]) { // TODO: COUNT(x,DIM=d) if (const auto *constant{UnwrapConstantValue(args[0])}) { std::int64_t result{0}; for (const auto &element : constant->values()) { if (element.IsTrue()) { ++result; } } return Expr{result}; } } } else if (name == "digits") { if (const auto *cx{UnwrapExpr>(args[0])}) { return Expr{std::visit( [](const auto &kx) { return Scalar>::DIGITS; }, cx->u)}; } else if (const auto *cx{UnwrapExpr>(args[0])}) { return Expr{std::visit( [](const auto &kx) { return Scalar>::DIGITS; }, cx->u)}; } else if (const auto *cx{UnwrapExpr>(args[0])}) { return Expr{std::visit( [](const auto &kx) { return Scalar::Part>::DIGITS; }, cx->u)}; } } else if (name == "dim") { return FoldElementalIntrinsic( context, std::move(funcRef), &Scalar::DIM); } else if (name == "dshiftl" || name == "dshiftr") { const auto fptr{ name == "dshiftl" ? &Scalar::DSHIFTL : &Scalar::DSHIFTR}; // Third argument can be of any kind. However, it must be smaller or equal // than BIT_SIZE. It can be converted to Int4 to simplify. return FoldElementalIntrinsic(context, std::move(funcRef), ScalarFunc( [&fptr](const Scalar &i, const Scalar &j, const Scalar &shift) -> Scalar { return std::invoke(fptr, i, j, static_cast(shift.ToInt64())); })); } else if (name == "exponent") { if (auto *sx{UnwrapExpr>(args[0])}) { return std::visit( [&funcRef, &context](const auto &x) -> Expr { using TR = typename std::decay_t::Result; return FoldElementalIntrinsic(context, std::move(funcRef), &Scalar::template EXPONENT>); }, sx->u); } else { DIE("exponent argument must be real"); } } else if (name == "huge") { return Expr{Scalar::HUGE()}; } else if (name == "iachar" || name == "ichar") { auto *someChar{UnwrapExpr>(args[0])}; CHECK(someChar); if (auto len{ToInt64(someChar->LEN())}) { if (len.value() != 1) { // Do not die, this was not checked before context.messages().Say( "Character in intrinsic function %s must have length one"_en_US, name); } else { return std::visit( [&funcRef, &context](const auto &str) -> Expr { using Char = typename std::decay_t::Result; return FoldElementalIntrinsic(context, std::move(funcRef), ScalarFunc([](const Scalar &c) { return Scalar{CharacterUtils::ICHAR(c)}; })); }, someChar->u); } } } else if (name == "iand" || name == "ior" || name == "ieor") { auto fptr{&Scalar::IAND}; if (name == "iand") { // done in fptr declaration } else if (name == "ior") { fptr = &Scalar::IOR; } else if (name == "ieor") { fptr = &Scalar::IEOR; } else { common::die("missing case to fold intrinsic function %s", name.c_str()); } return FoldElementalIntrinsic( context, std::move(funcRef), ScalarFunc(fptr)); } else if (name == "ibclr" || name == "ibset" || name == "ishft" || name == "shifta" || name == "shiftr" || name == "shiftl") { // Second argument can be of any kind. However, it must be smaller or // equal than BIT_SIZE. It can be converted to Int4 to simplify. auto fptr{&Scalar::IBCLR}; if (name == "ibclr") { // done in fprt definition } else if (name == "ibset") { fptr = &Scalar::IBSET; } else if (name == "ishft") { fptr = &Scalar::ISHFT; } else if (name == "shifta") { fptr = &Scalar::SHIFTA; } else if (name == "shiftr") { fptr = &Scalar::SHIFTR; } else if (name == "shiftl") { fptr = &Scalar::SHIFTL; } else { common::die("missing case to fold intrinsic function %s", name.c_str()); } return FoldElementalIntrinsic(context, std::move(funcRef), ScalarFunc( [&fptr](const Scalar &i, const Scalar &pos) -> Scalar { return std::invoke(fptr, i, static_cast(pos.ToInt64())); })); } else if (name == "index" || name == "scan" || name == "verify") { if (auto *charExpr{UnwrapExpr>(args[0])}) { return std::visit( [&](const auto &kch) -> Expr { using TC = typename std::decay_t::Result; if (UnwrapExpr>(args[2])) { // BACK= return FoldElementalIntrinsic(context, std::move(funcRef), ScalarFunc{ [&name](const Scalar &str, const Scalar &other, const Scalar &back) -> Scalar { return name == "index" ? CharacterUtils::INDEX( str, other, back.IsTrue()) : name == "scan" ? CharacterUtils::SCAN( str, other, back.IsTrue()) : CharacterUtils::VERIFY( str, other, back.IsTrue()); }}); } else { return FoldElementalIntrinsic(context, std::move(funcRef), ScalarFunc{ [&name](const Scalar &str, const Scalar &other) -> Scalar { return name == "index" ? CharacterUtils::INDEX(str, other) : name == "scan" ? CharacterUtils::SCAN(str, other) : CharacterUtils::VERIFY(str, other); }}); } }, charExpr->u); } else { DIE("first argument must be CHARACTER"); } } else if (name == "int") { if (auto *expr{UnwrapExpr>(args[0])}) { return std::visit( [&](auto &&x) -> Expr { using From = std::decay_t; if constexpr (std::is_same_v || IsNumericCategoryExpr()) { return Fold(context, ConvertToType(std::move(x))); } DIE("int() argument type not valid"); }, std::move(expr->u)); } } else if (name == "int_ptr_kind") { return Expr{8}; } else if (name == "kind") { if constexpr (common::HasMember) { return Expr{args[0].value().GetType()->kind()}; } else { DIE("kind() result not integral"); } } else if (name == "lbound") { return LBOUND(context, std::move(funcRef)); } else if (name == "leadz" || name == "trailz" || name == "poppar" || name == "popcnt") { if (auto *sn{UnwrapExpr>(args[0])}) { return std::visit( [&funcRef, &context, &name](const auto &n) -> Expr { using TI = typename std::decay_t::Result; if (name == "poppar") { return FoldElementalIntrinsic(context, std::move(funcRef), ScalarFunc([](const Scalar &i) -> Scalar { return Scalar{i.POPPAR() ? 1 : 0}; })); } auto fptr{&Scalar::LEADZ}; if (name == "leadz") { // done in fptr definition } else if (name == "trailz") { fptr = &Scalar::TRAILZ; } else if (name == "popcnt") { fptr = &Scalar::POPCNT; } else { common::die( "missing case to fold intrinsic function %s", name.c_str()); } return FoldElementalIntrinsic(context, std::move(funcRef), ScalarFunc([&fptr](const Scalar &i) -> Scalar { return Scalar{std::invoke(fptr, i)}; })); }, sn->u); } else { DIE("leadz argument must be integer"); } } else if (name == "len") { if (auto *charExpr{UnwrapExpr>(args[0])}) { return std::visit( [&](auto &kx) { if (auto len{kx.LEN()}) { return Fold(context, ConvertToType(*std::move(len))); } else { return Expr{std::move(funcRef)}; } }, charExpr->u); } else { DIE("len() argument must be of character type"); } } else if (name == "len_trim") { if (auto *charExpr{UnwrapExpr>(args[0])}) { return std::visit( [&](const auto &kch) -> Expr { using TC = typename std::decay_t::Result; return FoldElementalIntrinsic(context, std::move(funcRef), ScalarFunc{[](const Scalar &str) -> Scalar { return CharacterUtils::LEN_TRIM(str); }}); }, charExpr->u); } else { DIE("len_trim() argument must be of character type"); } } else if (name == "maskl" || name == "maskr") { // Argument can be of any kind but value has to be smaller than BIT_SIZE. // It can be safely converted to Int4 to simplify. const auto fptr{name == "maskl" ? &Scalar::MASKL : &Scalar::MASKR}; return FoldElementalIntrinsic(context, std::move(funcRef), ScalarFunc([&fptr](const Scalar &places) -> Scalar { return fptr(static_cast(places.ToInt64())); })); } else if (name == "max") { return FoldMINorMAX(context, std::move(funcRef), Ordering::Greater); } else if (name == "max0" || name == "max1") { return RewriteSpecificMINorMAX(context, std::move(funcRef)); } else if (name == "maxexponent") { if (auto *sx{UnwrapExpr>(args[0])}) { return std::visit( [](const auto &x) { using TR = typename std::decay_t::Result; return Expr{Scalar::MAXEXPONENT}; }, sx->u); } } else if (name == "merge") { return FoldMerge(context, std::move(funcRef)); } else if (name == "merge_bits") { return FoldElementalIntrinsic( context, std::move(funcRef), &Scalar::MERGE_BITS); } else if (name == "minexponent") { if (auto *sx{UnwrapExpr>(args[0])}) { return std::visit( [](const auto &x) { using TR = typename std::decay_t::Result; return Expr{Scalar::MINEXPONENT}; }, sx->u); } } else if (name == "min") { return FoldMINorMAX(context, std::move(funcRef), Ordering::Less); } else if (name == "min0" || name == "min1") { return RewriteSpecificMINorMAX(context, std::move(funcRef)); } else if (name == "mod") { return FoldElementalIntrinsic(context, std::move(funcRef), ScalarFuncWithContext( [](FoldingContext &context, const Scalar &x, const Scalar &y) -> Scalar { auto quotRem{x.DivideSigned(y)}; if (quotRem.divisionByZero) { context.messages().Say("mod() by zero"_en_US); } else if (quotRem.overflow) { context.messages().Say("mod() folding overflowed"_en_US); } return quotRem.remainder; })); } else if (name == "modulo") { return FoldElementalIntrinsic(context, std::move(funcRef), ScalarFuncWithContext( [](FoldingContext &context, const Scalar &x, const Scalar &y) -> Scalar { auto result{x.MODULO(y)}; if (result.overflow) { context.messages().Say("modulo() folding overflowed"_en_US); } return result.value; })); } else if (name == "precision") { if (const auto *cx{UnwrapExpr>(args[0])}) { return Expr{std::visit( [](const auto &kx) { return Scalar>::PRECISION; }, cx->u)}; } else if (const auto *cx{UnwrapExpr>(args[0])}) { return Expr{std::visit( [](const auto &kx) { return Scalar::Part>::PRECISION; }, cx->u)}; } } else if (name == "radix") { return Expr{2}; } else if (name == "range") { if (const auto *cx{UnwrapExpr>(args[0])}) { return Expr{std::visit( [](const auto &kx) { return Scalar>::RANGE; }, cx->u)}; } else if (const auto *cx{UnwrapExpr>(args[0])}) { return Expr{std::visit( [](const auto &kx) { return Scalar>::RANGE; }, cx->u)}; } else if (const auto *cx{UnwrapExpr>(args[0])}) { return Expr{std::visit( [](const auto &kx) { return Scalar::Part>::RANGE; }, cx->u)}; } } else if (name == "rank") { if (const auto *array{UnwrapExpr>(args[0])}) { if (auto named{ExtractNamedEntity(*array)}) { const Symbol &symbol{named->GetLastSymbol()}; if (semantics::IsAssumedRankArray(symbol)) { // DescriptorInquiry can only be placed in expression of kind // DescriptorInquiry::Result::kind. return ConvertToType(Expr< Type>{ DescriptorInquiry{*named, DescriptorInquiry::Field::Rank}}); } } return Expr{args[0].value().Rank()}; } return Expr{args[0].value().Rank()}; } else if (name == "selected_char_kind") { if (const auto *chCon{UnwrapExpr>>(args[0])}) { if (std::optional value{chCon->GetScalarValue()}) { int defaultKind{ context.defaults().GetDefaultKind(TypeCategory::Character)}; return Expr{SelectedCharKind(*value, defaultKind)}; } } } else if (name == "selected_int_kind") { if (auto p{GetInt64Arg(args[0])}) { return Expr{SelectedIntKind(*p)}; } } else if (name == "selected_real_kind") { if (auto p{GetInt64ArgOr(args[0], 0)}) { if (auto r{GetInt64ArgOr(args[1], 0)}) { if (auto radix{GetInt64ArgOr(args[2], 2)}) { return Expr{SelectedRealKind(*p, *r, *radix)}; } } } } else if (name == "shape") { if (auto shape{GetShape(context, args[0])}) { if (auto shapeExpr{AsExtentArrayExpr(*shape)}) { return Fold(context, ConvertToType(std::move(*shapeExpr))); } } } else if (name == "sign") { return FoldElementalIntrinsic(context, std::move(funcRef), ScalarFunc( [&context](const Scalar &j, const Scalar &k) -> Scalar { typename Scalar::ValueWithOverflow result{j.SIGN(k)}; if (result.overflow) { context.messages().Say( "sign(integer(kind=%d)) folding overflowed"_en_US, KIND); } return result.value; })); } else if (name == "size") { if (auto shape{GetShape(context, args[0])}) { if (auto &dimArg{args[1]}) { // DIM= is present, get one extent if (auto dim{GetInt64Arg(args[1])}) { int rank{GetRank(*shape)}; if (*dim >= 1 && *dim <= rank) { if (auto &extent{shape->at(*dim - 1)}) { return Fold(context, ConvertToType(std::move(*extent))); } } else { context.messages().Say( "size(array,dim=%jd) dimension is out of range for rank-%d array"_en_US, *dim, rank); } } } else if (auto extents{common::AllElementsPresent(std::move(*shape))}) { // DIM= is absent; compute PRODUCT(SHAPE()) ExtentExpr product{1}; for (auto &&extent : std::move(*extents)) { product = std::move(product) * std::move(extent); } return Expr{ConvertToType(Fold(context, std::move(product)))}; } } } else if (name == "sizeof") { // in bytes; extension if (auto info{ characteristics::TypeAndShape::Characterize(args[0], context)}) { if (auto bytes{info->MeasureSizeInBytes(context)}) { return Expr{Fold(context, ConvertToType(std::move(*bytes)))}; } } } else if (name == "storage_size") { // in bits if (const auto *expr{UnwrapExpr>(args[0])}) { if (auto type{expr->GetType()}) { if (auto bytes{type->MeasureSizeInBytes(context, true)}) { return Expr{ Fold(context, Expr{8} * ConvertToType(std::move(*bytes)))}; } } } } else if (name == "ubound") { return UBOUND(context, std::move(funcRef)); } // TODO: // cshift, dot_product, eoshift, // findloc, iall, iany, iparity, ibits, image_status, ishftc, // matmul, maxloc, maxval, // minloc, minval, not, pack, product, reduce, // sign, spread, sum, transfer, transpose, unpack return Expr{std::move(funcRef)}; } // Substitute a bare type parameter reference with its value if it has one now Expr FoldOperation( FoldingContext &context, TypeParamInquiry &&inquiry) { if (!inquiry.base()) { // A "bare" type parameter: replace with its value, if that's now known. if (const auto *pdt{context.pdtInstance()}) { if (const semantics::Scope * scope{context.pdtInstance()->scope()}) { auto iter{scope->find(inquiry.parameter().name())}; if (iter != scope->end()) { const Symbol &symbol{*iter->second}; const auto *details{symbol.detailsIf()}; if (details && details->init() && (details->attr() == common::TypeParamAttr::Kind || IsConstantExpr(*details->init()))) { Expr expr{*details->init()}; return Fold(context, ConvertToType(std::move(expr))); } } } if (const auto *value{pdt->FindParameter(inquiry.parameter().name())}) { if (value->isExplicit()) { return Fold(context, AsExpr(ConvertToType( Expr{value->GetExplicit().value()}))); } } } } return AsExpr(std::move(inquiry)); } std::optional ToInt64(const Expr &expr) { return std::visit( [](const auto &kindExpr) { return ToInt64(kindExpr); }, expr.u); } std::optional ToInt64(const Expr &expr) { if (const auto *intExpr{UnwrapExpr>(expr)}) { return ToInt64(*intExpr); } else { return std::nullopt; } } FOR_EACH_INTEGER_KIND(template class ExpressionBase, ) template class ExpressionBase; } // namespace Fortran::evaluate