//===-- 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/fold.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 /// 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(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( 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( 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); } /// 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 resultType, llvm::ArrayRef args, Fortran::lower::StatementContext &stmtCtx, Fortran::lower::AbstractConverter *converter) { auto [result, mustBeFreed] = fir::genIntrinsicCall(builder, loc, name, resultType, args, converter); if (mustBeFreed) { mlir::Value addr = fir::getBase(result); if (auto *box = result.getBoxOf()) addr = builder.create(loc, box->getMemTy(), box->getAddr()); fir::FirOpBuilder *bldr = &builder; stmtCtx.attachCleanup([=]() { bldr->create(loc, addr); }); } return result; } static void prepareMinOrMaxArguments( const Fortran::evaluate::ProcedureRef &procRef, const Fortran::evaluate::SpecificIntrinsic &intrinsic, std::optional 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(resultType)) TODO(loc, "CHARACTER MIN and MAX with dynamically optional arguments"); for (auto arg : llvm::enumerate(procRef.arguments())) { const auto *expr = Fortran::evaluate::UnwrapExpr(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 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 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 isPresentRuntimeCheck = isPresentCheck(opIndex)) { // Argument is dynamically optional. extremum = builder .genIfOp(loc, {resultType}, *isPresentRuntimeCheck, /*withElseRegion=*/true) .genThen([&]() { llvm::SmallVector args; args.emplace_back(extremum); args.emplace_back(getOperand(opIndex, loadOperand)); fir::ExtendedValue newExtremum = genIntrinsicCall( builder, loc, name, resultType, args, stmtCtx); builder.create(loc, fir::getBase(newExtremum)); }) .genElse([&]() { builder.create(loc, extremum); }) .getResults()[0]; } else { // Argument is know to be present at compile time. llvm::SmallVector 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 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(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 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 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)); builder.create(loc, size); }) .genElse([&]() { mlir::Value bitSize = builder.createIntegerConstant( loc, resultType, resultType.cast().getWidth()); builder.create(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 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 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 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 = builder.create(loc, builder.getI1Type(), targetBase); mlir::Type targetType = fir::unwrapRefType(targetBase.getType()); mlir::Type targetValueType = fir::unwrapPassByRefType(targetType); mlir::Type boxType = targetType.isa() ? 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); builder.create(loc, cast); }) .genElse([&]() { mlir::Value absentBox = builder.create(loc, boxType); builder.create(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 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 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); }