Index: flang/lib/Evaluate/intrinsics-library.cpp =================================================================== --- flang/lib/Evaluate/intrinsics-library.cpp +++ flang/lib/Evaluate/intrinsics-library.cpp @@ -330,12 +330,14 @@ using F = FuncPointer; using FN = FuncPointer; static constexpr HostRuntimeFunction table[]{ - FolderFactory::Create("bessel_j0"), - FolderFactory::Create("bessel_j1"), - FolderFactory::Create("bessel_jn"), - FolderFactory::Create("bessel_y0"), - FolderFactory::Create("bessel_y1"), - FolderFactory::Create("bessel_yn"), +#if !_ARCH_PPC // double-double, not supported; TODO: use HostTypeExists instead + FolderFactory::Create("bessel_j0"), + FolderFactory::Create("bessel_j1"), + FolderFactory::Create("bessel_jn"), + FolderFactory::Create("bessel_y0"), + FolderFactory::Create("bessel_y1"), + FolderFactory::Create("bessel_yn"), +#endif // !_ARCH_PPC }; static constexpr HostRuntimeMap map{table}; static_assert(map.Verify(), "map must be sorted"); @@ -549,10 +551,244 @@ return type; } +/// Structure to register intrinsic argument checks that must be performed. +using ArgumentVerifierFunc = bool (*)( + const std::vector> &, FoldingContext &); +struct ArgumentVerifier { + using Key = std::string_view; + // Needed for implicit compare with keys. + constexpr operator Key() const { return key; } + Key key; // intrinsic name + ArgumentVerifierFunc verifier; +}; + +static constexpr int lastArg{-1}; +static constexpr int firstArg{0}; + +static const Expr &getArg( + int position, const std::vector> &args) { + if (position == lastArg) { + CHECK(!args.empty()); + return args.back(); + } + CHECK(position >= 0 && static_cast(position) < args.size()); + return args[position]; +} + +template +static bool IsInRange(const Expr &expr, int lb, int ub) { + if (auto scalar{GetScalarConstantValue(expr)}) { + auto lbValue{Scalar::FromInteger(value::Integer<8>{lb}).value}; + auto ubValue{Scalar::FromInteger(value::Integer<8>{ub}).value}; + return Satisfies(RelationalOperator::LE, lbValue.Compare(*scalar)) && + Satisfies(RelationalOperator::LE, scalar->Compare(ubValue)); + } + return true; +} + +/// Verify that the argument in an intrinsic call belongs to [lb, ub] if is +/// real. +template +static bool VerifyInRangeIfReal( + const std::vector> &args, FoldingContext &context) { + if (const auto *someReal = + std::get_if>(&getArg(firstArg, args).u)) { + const bool isInRange{common::visit( + [&](const auto &x) -> bool { return IsInRange(x, lb, ub); }, + someReal->u)}; + if (!isInRange) { + context.messages().Say( + "argument is out of range [%d., %d.]"_warn_en_US, lb, ub); + } + return isInRange; + } + return true; +} + +template +static bool VerifyStrictlyPositiveIfReal( + const std::vector> &args, FoldingContext &context) { + if (const auto *someReal = + std::get_if>(&getArg(argPosition, args).u)) { + const bool isStrictlyPositive{common::visit( + [&](const auto &x) -> bool { + using T = typename std::decay_t::Result; + auto scalar{GetScalarConstantValue(x)}; + return Satisfies( + RelationalOperator::LT, Scalar{}.Compare(*scalar)); + }, + someReal->u)}; + if (!isStrictlyPositive) { + context.messages().Say( + "argument '%s' must be strictly positive"_warn_en_US, argName); + } + return isStrictlyPositive; + } + return true; +} + +/// Verify that an intrinsic call argument is not zero if it is real. +template +static bool VerifyNotZeroIfReal( + const std::vector> &args, FoldingContext &context) { + if (const auto *someReal = + std::get_if>(&getArg(argPosition, args).u)) { + const bool isNotZero{common::visit( + [&](const auto &x) -> bool { + using T = typename std::decay_t::Result; + auto scalar{GetScalarConstantValue(x)}; + return !scalar || !scalar->IsZero(); + }, + someReal->u)}; + if (!isNotZero) { + context.messages().Say( + "argument '%s' must be different from zero"_warn_en_US, argName); + } + return isNotZero; + } + return true; +} + +/// Verify that the argument in an intrinsic call is not zero if is complex. +static bool VerifyNotZeroIfComplex( + const std::vector> &args, FoldingContext &context) { + if (const auto *someComplex = + std::get_if>(&getArg(firstArg, args).u)) { + const bool isNotZero{common::visit( + [&](const auto &z) -> bool { + using T = typename std::decay_t::Result; + auto scalar{GetScalarConstantValue(z)}; + return !scalar || !scalar->IsZero(); + }, + someComplex->u)}; + if (!isNotZero) { + context.messages().Say( + "complex argument must be different from zero"_warn_en_US); + } + return isNotZero; + } + return true; +} + +// Verify that the argument in an intrinsic call is not zero and not a negative +// integer. +static bool VerifyGammaLikeArgument( + const std::vector> &args, FoldingContext &context) { + if (const auto *someReal = + std::get_if>(&getArg(firstArg, args).u)) { + const bool isValid{common::visit( + [&](const auto &x) -> bool { + using T = typename std::decay_t::Result; + auto scalar{GetScalarConstantValue(x)}; + if (scalar) { + return !scalar->IsZero() && + !(scalar->IsNegative() && + scalar->ToWholeNumber().value == scalar); + } + return true; + }, + someReal->u)}; + if (!isValid) { + context.messages().Say( + "argument must not be a negative integer or zero"_warn_en_US); + } + return isValid; + } + return true; +} + +// Verify that two real arguments are not both zero. +static bool VerifyAtan2LikeArguments( + const std::vector> &args, FoldingContext &context) { + if (const auto *someReal = + std::get_if>(&getArg(firstArg, args).u)) { + const bool isValid{common::visit( + [&](const auto &typedExpr) -> bool { + using T = typename std::decay_t::Result; + auto x{GetScalarConstantValue(typedExpr)}; + auto y{GetScalarConstantValue(getArg(lastArg, args))}; + if (x && y) { + return !(x->IsZero() && y->IsZero()); + } + return true; + }, + someReal->u)}; + if (!isValid) { + context.messages().Say( + "'x' and 'y' arguments must not be both zero"_warn_en_US); + } + return isValid; + } + return true; +} + +template +static bool CombineVerifiers( + const std::vector> &args, FoldingContext &context) { + return (... & F(args, context)); +} + +/// Define argument names to be used error messages when the intrinsic have +/// several arguments. +static constexpr char xName[]{"x"}; +static constexpr char pName[]{"p"}; + +/// Register argument verifiers for all intrinsics folded with runtime. +static constexpr ArgumentVerifier intrinsicArgumentVerifiers[]{ + {"acos", VerifyInRangeIfReal<-1, 1>}, + {"asin", VerifyInRangeIfReal<-1, 1>}, + {"atan2", VerifyAtan2LikeArguments}, + {"bessel_y0", VerifyStrictlyPositiveIfReal}, + {"bessel_y1", VerifyStrictlyPositiveIfReal}, + {"bessel_yn", VerifyStrictlyPositiveIfReal}, + {"gamma", VerifyGammaLikeArgument}, + {"log", + CombineVerifiers, + VerifyNotZeroIfComplex>}, + {"log10", VerifyStrictlyPositiveIfReal}, + {"log_gamma", VerifyGammaLikeArgument}, + {"mod", VerifyNotZeroIfReal}, +}; + +const ArgumentVerifierFunc *findVerifier(const std::string &intrinsicName) { + static constexpr Fortran::common::StaticMultimapView + verifiers(intrinsicArgumentVerifiers); + static_assert(verifiers.Verify(), "map must be sorted"); + auto range{verifiers.equal_range(intrinsicName)}; + if (range.first != range.second) { + return &range.first->verifier; + } + return nullptr; +} + +/// Ensure argument verifiers, if any, are run before calling the runtime +/// wrapper to fold an intrinsic. +static std::optional AddArgumentVerifierIfAny( + const std::string &intrinsicName, HostRuntimeWrapper &&runtimeWrapper) { + if (const auto *verifier{findVerifier(intrinsicName)}) { + return [runtimeWrapper, verifier]( + FoldingContext &context, std::vector> &&args) { + const bool validArguments{(*verifier)(args, context)}; + if (!validArguments) { + // Silence fp signal warnings since a more detailed warning about + // invalid arguments was already emitted. + parser::Messages localBuffer; + parser::Messages *finalBuffer{context.messages().messages()}; + parser::ContextualMessages localMessages{ + context.messages().at(), finalBuffer ? &localBuffer : nullptr}; + FoldingContext localContext{context, localMessages}; + return runtimeWrapper(localContext, std::move(args)); + } + return runtimeWrapper(context, std::move(args)); + }; + } + return std::move(runtimeWrapper); +} + std::optional GetHostRuntimeWrapper(const std::string &name, DynamicType resultType, const std::vector &argTypes) { if (const auto *hostFunction{SearchHostRuntime(name, resultType, argTypes)}) { - return hostFunction->folder; + return AddArgumentVerifierIfAny(name, hostFunction->folder); } // If no exact match, search with "bigger" types and insert type // conversions around the folder. @@ -563,8 +799,8 @@ } if (const auto *hostFunction{ SearchHostRuntime(name, biggerResultType, biggerArgTypes)}) { - return [hostFunction, resultType]( - FoldingContext &context, std::vector> &&args) { + auto folder{[hostFunction, resultType](FoldingContext &context, + std::vector> &&args) { auto nArgs{args.size()}; for (size_t i{0}; i < nArgs; ++i) { args[i] = Fold(context, @@ -575,7 +811,8 @@ ConvertToType( resultType, hostFunction->folder(context, std::move(args))) .value()); - }; + }}; + return AddArgumentVerifierIfAny(name, std::move(folder)); } return std::nullopt; }