| //===-- CustomIntrinsicCall.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 | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/ | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "flang/Lower/CustomIntrinsicCall.h" | |
| #include "flang/Evaluate/expression.h" | |
| #include "flang/Evaluate/tools.h" | |
| #include "flang/Lower/StatementContext.h" | |
| #include "flang/Optimizer/Builder/IntrinsicCall.h" | |
| #include "flang/Optimizer/Builder/Todo.h" | |
| #include "flang/Semantics/tools.h" | |
| #include "flang/Support/Fortran-features.h" | |
| #include <optional> | |
| /// Is this a call to MIN or MAX intrinsic with arguments that may be absent at | |
| /// runtime? This is a special case because MIN and MAX can have any number of | |
| /// arguments. | |
| static bool isMinOrMaxWithDynamicallyOptionalArg( | |
| llvm::StringRef name, const Fortran::evaluate::ProcedureRef &procRef) { | |
| if (name != "min" && name != "max") | |
| return false; | |
| const auto &args = procRef.arguments(); | |
| std::size_t argSize = args.size(); | |
| if (argSize <= 2) | |
| return false; | |
| for (std::size_t i = 2; i < argSize; ++i) { | |
| if (auto *expr = | |
| Fortran::evaluate::UnwrapExpr<Fortran::lower::SomeExpr>(args[i])) | |
| if (Fortran::evaluate::MayBePassedAsAbsentOptional(*expr)) | |
| return true; | |
| } | |
| return false; | |
| } | |
| /// Is this a call to ISHFTC intrinsic with a SIZE argument that may be absent | |
| /// at runtime? This is a special case because the SIZE value to be applied | |
| /// when absent is not zero. | |
| static bool isIshftcWithDynamicallyOptionalArg( | |
| llvm::StringRef name, const Fortran::evaluate::ProcedureRef &procRef) { | |
| if (name != "ishftc" || procRef.arguments().size() < 3) | |
| return false; | |
| auto *expr = Fortran::evaluate::UnwrapExpr<Fortran::lower::SomeExpr>( | |
| procRef.arguments()[2]); | |
| return expr && Fortran::evaluate::MayBePassedAsAbsentOptional(*expr); | |
| } | |
| /// Is this a call to ASSOCIATED where the TARGET is an OPTIONAL (but not a | |
| /// deallocated allocatable or disassociated pointer)? | |
| /// Subtle: contrary to other intrinsic optional arguments, disassociated | |
| /// POINTER and unallocated ALLOCATABLE actual argument are not considered | |
| /// absent here. This is because ASSOCIATED has special requirements for TARGET | |
| /// actual arguments that are POINTERs. There is no precise requirements for | |
| /// ALLOCATABLEs, but all existing Fortran compilers treat them similarly to | |
| /// POINTERs. That is: unallocated TARGETs cause ASSOCIATED to rerun false. The | |
| /// runtime deals with the disassociated/unallocated case. Simply ensures that | |
| /// TARGET that are OPTIONAL get conditionally emboxed here to convey the | |
| /// optional aspect to the runtime. | |
| static bool isAssociatedWithDynamicallyOptionalArg( | |
| llvm::StringRef name, const Fortran::evaluate::ProcedureRef &procRef) { | |
| if (name != "associated" || procRef.arguments().size() < 2) | |
| return false; | |
| auto *expr = Fortran::evaluate::UnwrapExpr<Fortran::lower::SomeExpr>( | |
| procRef.arguments()[1]); | |
| const Fortran::semantics::Symbol *sym{ | |
| expr ? Fortran::evaluate::UnwrapWholeSymbolOrComponentDataRef(expr) | |
| : nullptr}; | |
| return (sym && Fortran::semantics::IsOptional(*sym)); | |
| } | |
| bool Fortran::lower::intrinsicRequiresCustomOptionalHandling( | |
| const Fortran::evaluate::ProcedureRef &procRef, | |
| const Fortran::evaluate::SpecificIntrinsic &intrinsic, | |
| AbstractConverter &converter) { | |
| llvm::StringRef name = intrinsic.name; | |
| return isMinOrMaxWithDynamicallyOptionalArg(name, procRef) || | |
| isIshftcWithDynamicallyOptionalArg(name, procRef) || | |
| isAssociatedWithDynamicallyOptionalArg(name, procRef); | |
| } | |
| fir::IntrinsicLoweringOptions | |
| Fortran::lower::getIntrinsicLoweringOptions(AbstractConverter &converter) { | |
| fir::IntrinsicLoweringOptions options; | |
| options.coarrayEnabled = | |
| converter.getFoldingContext().languageFeatures().IsEnabled( | |
| Fortran::common::LanguageFeature::Coarray); | |
| options.noPPCNativeVecElemOrder = | |
| converter.getLoweringOptions().getNoPPCNativeVecElemOrder(); | |
| return options; | |
| } | |
| /// Generate the FIR+MLIR operations for the generic intrinsic \p name | |
| /// with arguments \p args and the expected result type \p resultType. | |
| /// Returned fir::ExtendedValue is the returned Fortran intrinsic value. | |
| fir::ExtendedValue | |
| Fortran::lower::genIntrinsicCall(fir::FirOpBuilder &builder, mlir::Location loc, | |
| llvm::StringRef name, | |
| std::optional<mlir::Type> resultType, | |
| llvm::ArrayRef<fir::ExtendedValue> args, | |
| Fortran::lower::StatementContext &stmtCtx, | |
| Fortran::lower::AbstractConverter *converter) { | |
| fir::IntrinsicLoweringOptions options; | |
| if (converter) | |
| options = getIntrinsicLoweringOptions(*converter); | |
| auto [result, mustBeFreed] = | |
| fir::genIntrinsicCall(builder, loc, name, resultType, args, options); | |
| if (mustBeFreed) { | |
| mlir::Value addr = fir::getBase(result); | |
| if (auto *box = result.getBoxOf<fir::BoxValue>()) | |
| addr = | |
| fir::BoxAddrOp::create(builder, loc, box->getMemTy(), box->getAddr()); | |
| fir::FirOpBuilder *bldr = &builder; | |
| stmtCtx.attachCleanup([=]() { fir::FreeMemOp::create(*bldr, loc, addr); }); | |
| } | |
| return result; | |
| } | |
| static void prepareMinOrMaxArguments( | |
| const Fortran::evaluate::ProcedureRef &procRef, | |
| const Fortran::evaluate::SpecificIntrinsic &intrinsic, | |
| std::optional<mlir::Type> retTy, | |
| const Fortran::lower::OperandPrepare &prepareOptionalArgument, | |
| const Fortran::lower::OperandPrepareAs &prepareOtherArgument, | |
| Fortran::lower::AbstractConverter &converter) { | |
| assert(retTy && "MIN and MAX must have a return type"); | |
| mlir::Type resultType = *retTy; | |
| mlir::Location loc = converter.getCurrentLocation(); | |
| if (fir::isa_char(fir::unwrapSequenceType(resultType))) | |
| TODO(loc, "CHARACTER MIN and MAX with dynamically optional arguments"); | |
| for (auto arg : llvm::enumerate(procRef.arguments())) { | |
| const auto *expr = | |
| Fortran::evaluate::UnwrapExpr<Fortran::lower::SomeExpr>(arg.value()); | |
| if (!expr) | |
| continue; | |
| if (arg.index() <= 1 || | |
| !Fortran::evaluate::MayBePassedAsAbsentOptional(*expr)) { | |
| // Non optional arguments. | |
| prepareOtherArgument(*expr, fir::LowerIntrinsicArgAs::Value); | |
| } else { | |
| // Dynamically optional arguments. | |
| // Subtle: even for scalar the if-then-else will be generated in the loop | |
| // nest because the then part will require the current extremum value that | |
| // may depend on previous array element argument and cannot be outlined. | |
| prepareOptionalArgument(*expr); | |
| } | |
| } | |
| } | |
| static fir::ExtendedValue | |
| lowerMinOrMax(fir::FirOpBuilder &builder, mlir::Location loc, | |
| llvm::StringRef name, std::optional<mlir::Type> retTy, | |
| const Fortran::lower::OperandPresent &isPresentCheck, | |
| const Fortran::lower::OperandGetter &getOperand, | |
| std::size_t numOperands, | |
| Fortran::lower::StatementContext &stmtCtx) { | |
| assert(numOperands >= 2 && !isPresentCheck(0) && !isPresentCheck(1) && | |
| "min/max must have at least two non-optional args"); | |
| assert(retTy && "MIN and MAX must have a return type"); | |
| mlir::Type resultType = *retTy; | |
| llvm::SmallVector<fir::ExtendedValue> args; | |
| const bool loadOperand = true; | |
| args.push_back(getOperand(0, loadOperand)); | |
| args.push_back(getOperand(1, loadOperand)); | |
| mlir::Value extremum = fir::getBase( | |
| genIntrinsicCall(builder, loc, name, resultType, args, stmtCtx)); | |
| for (std::size_t opIndex = 2; opIndex < numOperands; ++opIndex) { | |
| if (std::optional<mlir::Value> isPresentRuntimeCheck = | |
| isPresentCheck(opIndex)) { | |
| // Argument is dynamically optional. | |
| extremum = | |
| builder | |
| .genIfOp(loc, {resultType}, *isPresentRuntimeCheck, | |
| /*withElseRegion=*/true) | |
| .genThen([&]() { | |
| llvm::SmallVector<fir::ExtendedValue> args; | |
| args.emplace_back(extremum); | |
| args.emplace_back(getOperand(opIndex, loadOperand)); | |
| fir::ExtendedValue newExtremum = genIntrinsicCall( | |
| builder, loc, name, resultType, args, stmtCtx); | |
| fir::ResultOp::create(builder, loc, fir::getBase(newExtremum)); | |
| }) | |
| .genElse([&]() { fir::ResultOp::create(builder, loc, extremum); }) | |
| .getResults()[0]; | |
| } else { | |
| // Argument is know to be present at compile time. | |
| llvm::SmallVector<fir::ExtendedValue> args; | |
| args.emplace_back(extremum); | |
| args.emplace_back(getOperand(opIndex, loadOperand)); | |
| extremum = fir::getBase( | |
| genIntrinsicCall(builder, loc, name, resultType, args, stmtCtx)); | |
| } | |
| } | |
| return extremum; | |
| } | |
| static void prepareIshftcArguments( | |
| const Fortran::evaluate::ProcedureRef &procRef, | |
| const Fortran::evaluate::SpecificIntrinsic &intrinsic, | |
| std::optional<mlir::Type> retTy, | |
| const Fortran::lower::OperandPrepare &prepareOptionalArgument, | |
| const Fortran::lower::OperandPrepareAs &prepareOtherArgument, | |
| Fortran::lower::AbstractConverter &converter) { | |
| for (auto arg : llvm::enumerate(procRef.arguments())) { | |
| const auto *expr = | |
| Fortran::evaluate::UnwrapExpr<Fortran::lower::SomeExpr>(arg.value()); | |
| assert(expr && "expected all ISHFTC argument to be textually present here"); | |
| if (arg.index() == 2) { | |
| assert(Fortran::evaluate::MayBePassedAsAbsentOptional(*expr) && | |
| "expected ISHFTC SIZE arg to be dynamically optional"); | |
| prepareOptionalArgument(*expr); | |
| } else { | |
| // Non optional arguments. | |
| prepareOtherArgument(*expr, fir::LowerIntrinsicArgAs::Value); | |
| } | |
| } | |
| } | |
| static fir::ExtendedValue | |
| lowerIshftc(fir::FirOpBuilder &builder, mlir::Location loc, | |
| llvm::StringRef name, std::optional<mlir::Type> retTy, | |
| const Fortran::lower::OperandPresent &isPresentCheck, | |
| const Fortran::lower::OperandGetter &getOperand, | |
| std::size_t numOperands, | |
| Fortran::lower::StatementContext &stmtCtx) { | |
| assert(numOperands == 3 && !isPresentCheck(0) && !isPresentCheck(1) && | |
| isPresentCheck(2) && | |
| "only ISHFTC SIZE arg is expected to be dynamically optional here"); | |
| assert(retTy && "ISFHTC must have a return type"); | |
| mlir::Type resultType = *retTy; | |
| llvm::SmallVector<fir::ExtendedValue> args; | |
| const bool loadOperand = true; | |
| args.push_back(getOperand(0, loadOperand)); | |
| args.push_back(getOperand(1, loadOperand)); | |
| auto iPC = isPresentCheck(2); | |
| assert(iPC.has_value()); | |
| args.push_back( | |
| builder | |
| .genIfOp(loc, {resultType}, *iPC, | |
| /*withElseRegion=*/true) | |
| .genThen([&]() { | |
| fir::ExtendedValue sizeExv = getOperand(2, loadOperand); | |
| mlir::Value size = | |
| builder.createConvert(loc, resultType, fir::getBase(sizeExv)); | |
| fir::ResultOp::create(builder, loc, size); | |
| }) | |
| .genElse([&]() { | |
| mlir::Value bitSize = builder.createIntegerConstant( | |
| loc, resultType, | |
| mlir::cast<mlir::IntegerType>(resultType).getWidth()); | |
| fir::ResultOp::create(builder, loc, bitSize); | |
| }) | |
| .getResults()[0]); | |
| return genIntrinsicCall(builder, loc, name, resultType, args, stmtCtx); | |
| } | |
| static void prepareAssociatedArguments( | |
| const Fortran::evaluate::ProcedureRef &procRef, | |
| const Fortran::evaluate::SpecificIntrinsic &intrinsic, | |
| std::optional<mlir::Type> retTy, | |
| const Fortran::lower::OperandPrepare &prepareOptionalArgument, | |
| const Fortran::lower::OperandPrepareAs &prepareOtherArgument, | |
| Fortran::lower::AbstractConverter &converter) { | |
| const auto *pointer = procRef.UnwrapArgExpr(0); | |
| const auto *optionalTarget = procRef.UnwrapArgExpr(1); | |
| assert(pointer && optionalTarget && | |
| "expected call to associated with a target"); | |
| prepareOtherArgument(*pointer, fir::LowerIntrinsicArgAs::Inquired); | |
| prepareOptionalArgument(*optionalTarget); | |
| } | |
| static fir::ExtendedValue | |
| lowerAssociated(fir::FirOpBuilder &builder, mlir::Location loc, | |
| llvm::StringRef name, std::optional<mlir::Type> resultType, | |
| const Fortran::lower::OperandPresent &isPresentCheck, | |
| const Fortran::lower::OperandGetter &getOperand, | |
| std::size_t numOperands, | |
| Fortran::lower::StatementContext &stmtCtx) { | |
| assert(numOperands == 2 && "expect two arguments when TARGET is OPTIONAL"); | |
| llvm::SmallVector<fir::ExtendedValue> args; | |
| args.push_back(getOperand(0, /*loadOperand=*/false)); | |
| // Ensure a null descriptor is passed to the code lowering Associated if | |
| // TARGET is absent. | |
| fir::ExtendedValue targetExv = getOperand(1, /*loadOperand=*/false); | |
| mlir::Value targetBase = fir::getBase(targetExv); | |
| // subtle: isPresentCheck would test for an unallocated/disassociated target, | |
| // while the optionality of the target pointer/allocatable is what must be | |
| // checked here. | |
| mlir::Value isPresent = | |
| fir::IsPresentOp::create(builder, loc, builder.getI1Type(), targetBase); | |
| mlir::Type targetType = fir::unwrapRefType(targetBase.getType()); | |
| mlir::Type targetValueType = fir::unwrapPassByRefType(targetType); | |
| mlir::Type boxType = mlir::isa<fir::BaseBoxType>(targetType) | |
| ? targetType | |
| : fir::BoxType::get(targetValueType); | |
| fir::BoxValue targetBox = | |
| builder | |
| .genIfOp(loc, {boxType}, isPresent, | |
| /*withElseRegion=*/true) | |
| .genThen([&]() { | |
| mlir::Value box = builder.createBox(loc, targetExv); | |
| mlir::Value cast = builder.createConvert(loc, boxType, box); | |
| fir::ResultOp::create(builder, loc, cast); | |
| }) | |
| .genElse([&]() { | |
| mlir::Value absentBox = | |
| fir::AbsentOp::create(builder, loc, boxType); | |
| fir::ResultOp::create(builder, loc, absentBox); | |
| }) | |
| .getResults()[0]; | |
| args.emplace_back(std::move(targetBox)); | |
| return genIntrinsicCall(builder, loc, name, resultType, args, stmtCtx); | |
| } | |
| void Fortran::lower::prepareCustomIntrinsicArgument( | |
| const Fortran::evaluate::ProcedureRef &procRef, | |
| const Fortran::evaluate::SpecificIntrinsic &intrinsic, | |
| std::optional<mlir::Type> retTy, | |
| const OperandPrepare &prepareOptionalArgument, | |
| const OperandPrepareAs &prepareOtherArgument, | |
| AbstractConverter &converter) { | |
| llvm::StringRef name = intrinsic.name; | |
| if (name == "min" || name == "max") | |
| return prepareMinOrMaxArguments(procRef, intrinsic, retTy, | |
| prepareOptionalArgument, | |
| prepareOtherArgument, converter); | |
| if (name == "associated") | |
| return prepareAssociatedArguments(procRef, intrinsic, retTy, | |
| prepareOptionalArgument, | |
| prepareOtherArgument, converter); | |
| assert(name == "ishftc" && "unexpected custom intrinsic argument call"); | |
| return prepareIshftcArguments(procRef, intrinsic, retTy, | |
| prepareOptionalArgument, prepareOtherArgument, | |
| converter); | |
| } | |
| fir::ExtendedValue Fortran::lower::lowerCustomIntrinsic( | |
| fir::FirOpBuilder &builder, mlir::Location loc, llvm::StringRef name, | |
| std::optional<mlir::Type> retTy, const OperandPresent &isPresentCheck, | |
| const OperandGetter &getOperand, std::size_t numOperands, | |
| Fortran::lower::StatementContext &stmtCtx) { | |
| if (name == "min" || name == "max") | |
| return lowerMinOrMax(builder, loc, name, retTy, isPresentCheck, getOperand, | |
| numOperands, stmtCtx); | |
| if (name == "associated") | |
| return lowerAssociated(builder, loc, name, retTy, isPresentCheck, | |
| getOperand, numOperands, stmtCtx); | |
| assert(name == "ishftc" && "unexpected custom intrinsic call"); | |
| return lowerIshftc(builder, loc, name, retTy, isPresentCheck, getOperand, | |
| numOperands, stmtCtx); | |
| } |