Skip to content

[flang] allow intrinsic module procedures to be implemented in Fortran #97743

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 42 additions & 6 deletions flang/include/flang/Optimizer/Builder/IntrinsicCall.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,29 @@
namespace fir {

class StatementContext;
struct IntrinsicHandlerEntry;

// TODO: Error handling interface ?
// TODO: Implementation is incomplete. Many intrinsics to tbd.

/// Same as the other genIntrinsicCall version above, except that the result
/// deallocation, if required, is not added to a StatementContext. Instead, an
/// extra boolean result indicates if the result must be freed after use.
/// Lower an intrinsic call given the intrinsic \p name, its \p resultType (that
/// must be std::nullopt if and only if this is a subroutine call), and its
/// lowered arguments \p args. The returned pair contains the result value
/// (null mlir::Value for subroutine calls), and a boolean that indicates if
/// this result must be freed after use.
std::pair<fir::ExtendedValue, bool>
genIntrinsicCall(fir::FirOpBuilder &, mlir::Location, llvm::StringRef name,
std::optional<mlir::Type> resultType,
llvm::ArrayRef<fir::ExtendedValue> args,
Fortran::lower::AbstractConverter *converter = nullptr);

/// Same as the entry above except that instead of an intrinsic name it takes an
/// IntrinsicHandlerEntry obtained by a previous lookup for a handler to lower
/// this intrinsic (see lookupIntrinsicHandler).
std::pair<fir::ExtendedValue, bool>
genIntrinsicCall(fir::FirOpBuilder &, mlir::Location,
const IntrinsicHandlerEntry &,
std::optional<mlir::Type> resultType,
llvm::ArrayRef<fir::ExtendedValue> args,
Fortran::lower::AbstractConverter *converter = nullptr);

/// Enums used to templatize and share lowering of MIN and MAX.
enum class Extremum { Min, Max };

Expand Down Expand Up @@ -156,6 +166,11 @@ struct IntrinsicLibrary {
getRuntimeCallGenerator(llvm::StringRef name,
mlir::FunctionType soughtFuncType);

/// Helper to generate TODOs for module procedures that must be intercepted in
/// lowering and are not yet implemented.
template <const char *intrinsicName>
void genModuleProcTODO(llvm::ArrayRef<fir::ExtendedValue>);

void genAbort(llvm::ArrayRef<fir::ExtendedValue>);
/// Lowering for the ABS intrinsic. The ABS intrinsic expects one argument in
/// the llvm::ArrayRef. The ABS intrinsic is lowered into MLIR/FIR operation
Expand Down Expand Up @@ -676,6 +691,18 @@ static inline mlir::FunctionType genFuncType(mlir::MLIRContext *context,
return mlir::FunctionType::get(context, argTypes, {resType});
}

/// Entry into the tables describing how an intrinsic must be lowered.
struct IntrinsicHandlerEntry {
using RuntimeGeneratorRange =
std::pair<const MathOperation *, const MathOperation *>;
IntrinsicHandlerEntry(const IntrinsicHandler *handler) : entry{handler} {
assert(handler && "handler must not be nullptr");
};
IntrinsicHandlerEntry(RuntimeGeneratorRange rt) : entry{rt} {};
const IntrinsicArgumentLoweringRules *getArgumentLoweringRules() const;
std::variant<const IntrinsicHandler *, RuntimeGeneratorRange> entry;
};

//===----------------------------------------------------------------------===//
// Helper functions for argument handling.
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -728,6 +755,15 @@ mlir::Value genLibSplitComplexArgsCall(fir::FirOpBuilder &builder,
mlir::FunctionType libFuncType,
llvm::ArrayRef<mlir::Value> args);

/// Lookup for a handler or runtime call generator to lower intrinsic
/// \p intrinsicName.
std::optional<IntrinsicHandlerEntry>
lookupIntrinsicHandler(fir::FirOpBuilder &, llvm::StringRef intrinsicName,
std::optional<mlir::Type> resultType);

/// Generate a TODO error message for an as yet unimplemented intrinsic.
void crashOnMissingIntrinsic(mlir::Location loc, llvm::StringRef name);

/// Return argument lowering rules for an intrinsic.
/// Returns a nullptr if all the intrinsic arguments should be lowered by value.
const IntrinsicArgumentLoweringRules *
Expand Down
99 changes: 61 additions & 38 deletions flang/lib/Lower/ConvertCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1841,7 +1841,7 @@ static std::optional<hlfir::EntityWithAttributes> genCustomIntrinsicRefCore(
static std::optional<hlfir::EntityWithAttributes>
genIntrinsicRefCore(Fortran::lower::PreparedActualArguments &loweredActuals,
const Fortran::evaluate::SpecificIntrinsic *intrinsic,
const fir::IntrinsicArgumentLoweringRules *argLowering,
const fir::IntrinsicHandlerEntry &intrinsicEntry,
CallContext &callContext) {
auto &converter = callContext.converter;
if (intrinsic && Fortran::lower::intrinsicRequiresCustomOptionalHandling(
Expand All @@ -1856,6 +1856,8 @@ genIntrinsicRefCore(Fortran::lower::PreparedActualArguments &loweredActuals,
auto &stmtCtx = callContext.stmtCtx;
fir::FirOpBuilder &builder = callContext.getBuilder();
mlir::Location loc = callContext.loc;
const fir::IntrinsicArgumentLoweringRules *argLowering =
intrinsicEntry.getArgumentLoweringRules();
for (auto arg : llvm::enumerate(loweredActuals)) {
if (!arg.value()) {
operands.emplace_back(fir::getAbsentIntrinsicArgument());
Expand Down Expand Up @@ -1991,7 +1993,7 @@ genIntrinsicRefCore(Fortran::lower::PreparedActualArguments &loweredActuals,
const std::string intrinsicName = callContext.getProcedureName();
// Let the intrinsic library lower the intrinsic procedure call.
auto [resultExv, mustBeFreed] = genIntrinsicCall(
builder, loc, intrinsicName, scalarResultType, operands, &converter);
builder, loc, intrinsicEntry, scalarResultType, operands, &converter);
for (const hlfir::CleanupFunction &fn : cleanupFns)
fn();
if (!fir::getBase(resultExv))
Expand Down Expand Up @@ -2023,18 +2025,16 @@ genIntrinsicRefCore(Fortran::lower::PreparedActualArguments &loweredActuals,
static std::optional<hlfir::EntityWithAttributes> genHLFIRIntrinsicRefCore(
Fortran::lower::PreparedActualArguments &loweredActuals,
const Fortran::evaluate::SpecificIntrinsic *intrinsic,
const fir::IntrinsicArgumentLoweringRules *argLowering,
const fir::IntrinsicHandlerEntry &intrinsicEntry,
CallContext &callContext) {
if (!useHlfirIntrinsicOps)
return genIntrinsicRefCore(loweredActuals, intrinsic, argLowering,
callContext);

fir::FirOpBuilder &builder = callContext.getBuilder();
mlir::Location loc = callContext.loc;
const std::string intrinsicName = callContext.getProcedureName();

// transformational intrinsic ops always have a result type
if (callContext.resultType) {
// Try lowering transformational intrinsic ops to HLFIR ops if enabled
// (transformational always have a result type)
if (useHlfirIntrinsicOps && callContext.resultType) {
fir::FirOpBuilder &builder = callContext.getBuilder();
mlir::Location loc = callContext.loc;
const std::string intrinsicName = callContext.getProcedureName();
const fir::IntrinsicArgumentLoweringRules *argLowering =
intrinsicEntry.getArgumentLoweringRules();
std::optional<hlfir::EntityWithAttributes> res =
Fortran::lower::lowerHlfirIntrinsic(builder, loc, intrinsicName,
loweredActuals, argLowering,
Expand All @@ -2044,7 +2044,7 @@ static std::optional<hlfir::EntityWithAttributes> genHLFIRIntrinsicRefCore(
}

// fallback to calling the intrinsic via fir.call
return genIntrinsicRefCore(loweredActuals, intrinsic, argLowering,
return genIntrinsicRefCore(loweredActuals, intrinsic, intrinsicEntry,
callContext);
}

Expand Down Expand Up @@ -2303,13 +2303,13 @@ class ElementalIntrinsicCallBuilder
public:
ElementalIntrinsicCallBuilder(
const Fortran::evaluate::SpecificIntrinsic *intrinsic,
const fir::IntrinsicArgumentLoweringRules *argLowering, bool isFunction)
: intrinsic{intrinsic}, argLowering{argLowering}, isFunction{isFunction} {
}
const fir::IntrinsicHandlerEntry &intrinsicEntry, bool isFunction)
: intrinsic{intrinsic}, intrinsicEntry{intrinsicEntry},
isFunction{isFunction} {}
std::optional<hlfir::Entity>
genElementalKernel(Fortran::lower::PreparedActualArguments &loweredActuals,
CallContext &callContext) {
return genHLFIRIntrinsicRefCore(loweredActuals, intrinsic, argLowering,
return genHLFIRIntrinsicRefCore(loweredActuals, intrinsic, intrinsicEntry,
callContext);
}
// Elemental intrinsic functions cannot modify their arguments.
Expand Down Expand Up @@ -2363,7 +2363,7 @@ class ElementalIntrinsicCallBuilder

private:
const Fortran::evaluate::SpecificIntrinsic *intrinsic;
const fir::IntrinsicArgumentLoweringRules *argLowering;
fir::IntrinsicHandlerEntry intrinsicEntry;
const bool isFunction;
};
} // namespace
Expand Down Expand Up @@ -2436,11 +2436,16 @@ genCustomElementalIntrinsicRef(
callContext.procRef, *intrinsic, callContext.resultType,
prepareOptionalArg, prepareOtherArg, converter);

const fir::IntrinsicArgumentLoweringRules *argLowering =
fir::getIntrinsicArgumentLowering(callContext.getProcedureName());
std::optional<fir::IntrinsicHandlerEntry> intrinsicEntry =
fir::lookupIntrinsicHandler(callContext.getBuilder(),
callContext.getProcedureName(),
callContext.resultType);
assert(intrinsicEntry.has_value() &&
"intrinsic with custom handling for OPTIONAL arguments must have "
"lowering entries");
// All of the custom intrinsic elementals with custom handling are pure
// functions
return ElementalIntrinsicCallBuilder{intrinsic, argLowering,
return ElementalIntrinsicCallBuilder{intrinsic, *intrinsicEntry,
/*isFunction=*/true}
.genElementalCall(operands, /*isImpure=*/false, callContext);
}
Expand Down Expand Up @@ -2517,21 +2522,15 @@ genCustomIntrinsicRef(const Fortran::evaluate::SpecificIntrinsic *intrinsic,
/// lowered as if it were an intrinsic module procedure (like C_LOC which is a
/// procedure from intrinsic module iso_c_binding). Otherwise, \p intrinsic
/// must not be null.

static std::optional<hlfir::EntityWithAttributes>
genIntrinsicRef(const Fortran::evaluate::SpecificIntrinsic *intrinsic,
const fir::IntrinsicHandlerEntry &intrinsicEntry,
CallContext &callContext) {
mlir::Location loc = callContext.loc;
auto &converter = callContext.converter;
if (intrinsic && Fortran::lower::intrinsicRequiresCustomOptionalHandling(
callContext.procRef, *intrinsic, converter)) {
if (callContext.isElementalProcWithArrayArgs())
return genCustomElementalIntrinsicRef(intrinsic, callContext);
return genCustomIntrinsicRef(intrinsic, callContext);
}

Fortran::lower::PreparedActualArguments loweredActuals;
const fir::IntrinsicArgumentLoweringRules *argLowering =
fir::getIntrinsicArgumentLowering(callContext.getProcedureName());
intrinsicEntry.getArgumentLoweringRules();
for (const auto &arg : llvm::enumerate(callContext.procRef.arguments())) {

if (!arg.value()) {
Expand Down Expand Up @@ -2581,12 +2580,12 @@ genIntrinsicRef(const Fortran::evaluate::SpecificIntrinsic *intrinsic,
if (callContext.isElementalProcWithArrayArgs()) {
// All intrinsic elemental functions are pure.
const bool isFunction = callContext.resultType.has_value();
return ElementalIntrinsicCallBuilder{intrinsic, argLowering, isFunction}
return ElementalIntrinsicCallBuilder{intrinsic, intrinsicEntry, isFunction}
.genElementalCall(loweredActuals, /*isImpure=*/!isFunction,
callContext);
}
std::optional<hlfir::EntityWithAttributes> result = genHLFIRIntrinsicRefCore(
loweredActuals, intrinsic, argLowering, callContext);
loweredActuals, intrinsic, intrinsicEntry, callContext);
if (result && mlir::isa<hlfir::ExprType>(result->getType())) {
fir::FirOpBuilder *bldr = &callContext.getBuilder();
callContext.stmtCtx.attachCleanup(
Expand All @@ -2595,18 +2594,43 @@ genIntrinsicRef(const Fortran::evaluate::SpecificIntrinsic *intrinsic,
return result;
}

static std::optional<hlfir::EntityWithAttributes>
genIntrinsicRef(const Fortran::evaluate::SpecificIntrinsic *intrinsic,
CallContext &callContext) {
mlir::Location loc = callContext.loc;
auto &converter = callContext.converter;
if (intrinsic && Fortran::lower::intrinsicRequiresCustomOptionalHandling(
callContext.procRef, *intrinsic, converter)) {
if (callContext.isElementalProcWithArrayArgs())
return genCustomElementalIntrinsicRef(intrinsic, callContext);
return genCustomIntrinsicRef(intrinsic, callContext);
}
std::optional<fir::IntrinsicHandlerEntry> intrinsicEntry =
fir::lookupIntrinsicHandler(callContext.getBuilder(),
callContext.getProcedureName(),
callContext.resultType);
if (!intrinsicEntry)
fir::crashOnMissingIntrinsic(loc, callContext.getProcedureName());
return genIntrinsicRef(intrinsic, *intrinsicEntry, callContext);
}

/// Main entry point to lower procedure references, regardless of what they are.
static std::optional<hlfir::EntityWithAttributes>
genProcedureRef(CallContext &callContext) {
mlir::Location loc = callContext.loc;
fir::FirOpBuilder &builder = callContext.getBuilder();
if (auto *intrinsic = callContext.procRef.proc().GetSpecificIntrinsic())
return genIntrinsicRef(intrinsic, callContext);
// If it is an intrinsic module procedure reference - then treat as
// intrinsic unless it is bind(c) (since implementation is external from
// module).
// Intercept non BIND(C) module procedure reference that have lowering
// handlers defined for there name. Otherwise, lower them as user
// procedure calls and expect the implementation to be part of
// runtime libraries with the proper name mangling.
if (Fortran::lower::isIntrinsicModuleProcRef(callContext.procRef) &&
!callContext.isBindcCall())
return genIntrinsicRef(nullptr, callContext);
if (std::optional<fir::IntrinsicHandlerEntry> intrinsicEntry =
fir::lookupIntrinsicHandler(builder, callContext.getProcedureName(),
callContext.resultType))
return genIntrinsicRef(nullptr, *intrinsicEntry, callContext);

if (callContext.isStatementFunctionCall())
return genStmtFunctionRef(loc, callContext.converter, callContext.symMap,
Expand Down Expand Up @@ -2641,7 +2665,6 @@ genProcedureRef(CallContext &callContext) {
// TYPE(*) cannot be ALLOCATABLE/POINTER (C709) so there is no
// need to cover the case of passing an ALLOCATABLE/POINTER to an
// OPTIONAL.
fir::FirOpBuilder &builder = callContext.getBuilder();
isPresent =
builder.create<fir::IsPresentOp>(loc, builder.getI1Type(), actual)
.getResult();
Expand Down
Loading
Loading