//===-- 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 <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);
}
/// 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) {
auto [result, mustBeFreed] =
fir::genIntrinsicCall(builder, loc, name, resultType, args, converter);
if (mustBeFreed) {
mlir::Value addr = fir::getBase(result);
if (auto *box = result.getBoxOf<fir::BoxValue>())
addr =
builder.create<fir::BoxAddrOp>(loc, box->getMemTy(), box->getAddr());
fir::FirOpBuilder *bldr = &builder;
stmtCtx.attachCleanup([=]() { bldr->create<fir::FreeMemOp>(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(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);
builder.create<fir::ResultOp>(loc, fir::getBase(newExtremum));
})
.genElse([&]() { builder.create<fir::ResultOp>(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));
builder.create<fir::ResultOp>(loc, size);
})
.genElse([&]() {
mlir::Value bitSize = builder.createIntegerConstant(
loc, resultType,
mlir::cast<mlir::IntegerType>(resultType).getWidth());
builder.create<fir::ResultOp>(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 =
builder.create<fir::IsPresentOp>(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);
builder.create<fir::ResultOp>(loc, cast);
})
.genElse([&]() {
mlir::Value absentBox = builder.create<fir::AbsentOp>(loc, boxType);
builder.create<fir::ResultOp>(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);
}