Skip to content

Commit 727ecaf

Browse files
authored
[flang] allow intrinsic module procedures to be implemented in Fortran (#97743)
Currently, all procedures from intrinsic modules that are not BIND(C) are expected to be intercepted by the compiler in lowering and to have a handler in IntrinsicCall.cpp. As more "intrinsic" modules are being added (OpenMP, OpenACC, CUF, ...), this requirement is preventing seamless implementation of intrinsic modules in Fortran. Procedures from intrinsic modules are different from generic intrinsics defined in section 16 of the standard. They are declared in Fortran file seating in the intrinsic module directory and inside the compiler they look like regular user call except for the INTRINSIC attribute set on their module. So an easy implementation is just to have the implementation done in Fortran and linked to the runtime without any need for the compiler to necessarily understand and handle these calls in special ways. This patch splits the lookup and generation part of IntrinsicCall.cpp so that it can be allowed to only intercept calls to procedure from intrinsic module if they have a handler. Otherwise, the assumption is that they should be implemented in Fortran. Add explicit TODOs handler for the IEEE procedure that are known to not yet been implemented and won't be implemented via Fortran code so that this patch is an NFC for what is currently supported. This patch also prevents doing two lookups in the intrinsic table (There was one to get argument lowering rules, and another one to generate the code).
1 parent 12d6832 commit 727ecaf

File tree

3 files changed

+260
-104
lines changed

3 files changed

+260
-104
lines changed

flang/include/flang/Optimizer/Builder/IntrinsicCall.h

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,29 @@
2525
namespace fir {
2626

2727
class StatementContext;
28+
struct IntrinsicHandlerEntry;
2829

29-
// TODO: Error handling interface ?
30-
// TODO: Implementation is incomplete. Many intrinsics to tbd.
31-
32-
/// Same as the other genIntrinsicCall version above, except that the result
33-
/// deallocation, if required, is not added to a StatementContext. Instead, an
34-
/// extra boolean result indicates if the result must be freed after use.
30+
/// Lower an intrinsic call given the intrinsic \p name, its \p resultType (that
31+
/// must be std::nullopt if and only if this is a subroutine call), and its
32+
/// lowered arguments \p args. The returned pair contains the result value
33+
/// (null mlir::Value for subroutine calls), and a boolean that indicates if
34+
/// this result must be freed after use.
3535
std::pair<fir::ExtendedValue, bool>
3636
genIntrinsicCall(fir::FirOpBuilder &, mlir::Location, llvm::StringRef name,
3737
std::optional<mlir::Type> resultType,
3838
llvm::ArrayRef<fir::ExtendedValue> args,
3939
Fortran::lower::AbstractConverter *converter = nullptr);
4040

41+
/// Same as the entry above except that instead of an intrinsic name it takes an
42+
/// IntrinsicHandlerEntry obtained by a previous lookup for a handler to lower
43+
/// this intrinsic (see lookupIntrinsicHandler).
44+
std::pair<fir::ExtendedValue, bool>
45+
genIntrinsicCall(fir::FirOpBuilder &, mlir::Location,
46+
const IntrinsicHandlerEntry &,
47+
std::optional<mlir::Type> resultType,
48+
llvm::ArrayRef<fir::ExtendedValue> args,
49+
Fortran::lower::AbstractConverter *converter = nullptr);
50+
4151
/// Enums used to templatize and share lowering of MIN and MAX.
4252
enum class Extremum { Min, Max };
4353

@@ -156,6 +166,11 @@ struct IntrinsicLibrary {
156166
getRuntimeCallGenerator(llvm::StringRef name,
157167
mlir::FunctionType soughtFuncType);
158168

169+
/// Helper to generate TODOs for module procedures that must be intercepted in
170+
/// lowering and are not yet implemented.
171+
template <const char *intrinsicName>
172+
void genModuleProcTODO(llvm::ArrayRef<fir::ExtendedValue>);
173+
159174
void genAbort(llvm::ArrayRef<fir::ExtendedValue>);
160175
/// Lowering for the ABS intrinsic. The ABS intrinsic expects one argument in
161176
/// the llvm::ArrayRef. The ABS intrinsic is lowered into MLIR/FIR operation
@@ -676,6 +691,18 @@ static inline mlir::FunctionType genFuncType(mlir::MLIRContext *context,
676691
return mlir::FunctionType::get(context, argTypes, {resType});
677692
}
678693

694+
/// Entry into the tables describing how an intrinsic must be lowered.
695+
struct IntrinsicHandlerEntry {
696+
using RuntimeGeneratorRange =
697+
std::pair<const MathOperation *, const MathOperation *>;
698+
IntrinsicHandlerEntry(const IntrinsicHandler *handler) : entry{handler} {
699+
assert(handler && "handler must not be nullptr");
700+
};
701+
IntrinsicHandlerEntry(RuntimeGeneratorRange rt) : entry{rt} {};
702+
const IntrinsicArgumentLoweringRules *getArgumentLoweringRules() const;
703+
std::variant<const IntrinsicHandler *, RuntimeGeneratorRange> entry;
704+
};
705+
679706
//===----------------------------------------------------------------------===//
680707
// Helper functions for argument handling.
681708
//===----------------------------------------------------------------------===//
@@ -728,6 +755,15 @@ mlir::Value genLibSplitComplexArgsCall(fir::FirOpBuilder &builder,
728755
mlir::FunctionType libFuncType,
729756
llvm::ArrayRef<mlir::Value> args);
730757

758+
/// Lookup for a handler or runtime call generator to lower intrinsic
759+
/// \p intrinsicName.
760+
std::optional<IntrinsicHandlerEntry>
761+
lookupIntrinsicHandler(fir::FirOpBuilder &, llvm::StringRef intrinsicName,
762+
std::optional<mlir::Type> resultType);
763+
764+
/// Generate a TODO error message for an as yet unimplemented intrinsic.
765+
void crashOnMissingIntrinsic(mlir::Location loc, llvm::StringRef name);
766+
731767
/// Return argument lowering rules for an intrinsic.
732768
/// Returns a nullptr if all the intrinsic arguments should be lowered by value.
733769
const IntrinsicArgumentLoweringRules *

flang/lib/Lower/ConvertCall.cpp

Lines changed: 61 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1841,7 +1841,7 @@ static std::optional<hlfir::EntityWithAttributes> genCustomIntrinsicRefCore(
18411841
static std::optional<hlfir::EntityWithAttributes>
18421842
genIntrinsicRefCore(Fortran::lower::PreparedActualArguments &loweredActuals,
18431843
const Fortran::evaluate::SpecificIntrinsic *intrinsic,
1844-
const fir::IntrinsicArgumentLoweringRules *argLowering,
1844+
const fir::IntrinsicHandlerEntry &intrinsicEntry,
18451845
CallContext &callContext) {
18461846
auto &converter = callContext.converter;
18471847
if (intrinsic && Fortran::lower::intrinsicRequiresCustomOptionalHandling(
@@ -1856,6 +1856,8 @@ genIntrinsicRefCore(Fortran::lower::PreparedActualArguments &loweredActuals,
18561856
auto &stmtCtx = callContext.stmtCtx;
18571857
fir::FirOpBuilder &builder = callContext.getBuilder();
18581858
mlir::Location loc = callContext.loc;
1859+
const fir::IntrinsicArgumentLoweringRules *argLowering =
1860+
intrinsicEntry.getArgumentLoweringRules();
18591861
for (auto arg : llvm::enumerate(loweredActuals)) {
18601862
if (!arg.value()) {
18611863
operands.emplace_back(fir::getAbsentIntrinsicArgument());
@@ -1991,7 +1993,7 @@ genIntrinsicRefCore(Fortran::lower::PreparedActualArguments &loweredActuals,
19911993
const std::string intrinsicName = callContext.getProcedureName();
19921994
// Let the intrinsic library lower the intrinsic procedure call.
19931995
auto [resultExv, mustBeFreed] = genIntrinsicCall(
1994-
builder, loc, intrinsicName, scalarResultType, operands, &converter);
1996+
builder, loc, intrinsicEntry, scalarResultType, operands, &converter);
19951997
for (const hlfir::CleanupFunction &fn : cleanupFns)
19961998
fn();
19971999
if (!fir::getBase(resultExv))
@@ -2023,18 +2025,16 @@ genIntrinsicRefCore(Fortran::lower::PreparedActualArguments &loweredActuals,
20232025
static std::optional<hlfir::EntityWithAttributes> genHLFIRIntrinsicRefCore(
20242026
Fortran::lower::PreparedActualArguments &loweredActuals,
20252027
const Fortran::evaluate::SpecificIntrinsic *intrinsic,
2026-
const fir::IntrinsicArgumentLoweringRules *argLowering,
2028+
const fir::IntrinsicHandlerEntry &intrinsicEntry,
20272029
CallContext &callContext) {
2028-
if (!useHlfirIntrinsicOps)
2029-
return genIntrinsicRefCore(loweredActuals, intrinsic, argLowering,
2030-
callContext);
2031-
2032-
fir::FirOpBuilder &builder = callContext.getBuilder();
2033-
mlir::Location loc = callContext.loc;
2034-
const std::string intrinsicName = callContext.getProcedureName();
2035-
2036-
// transformational intrinsic ops always have a result type
2037-
if (callContext.resultType) {
2030+
// Try lowering transformational intrinsic ops to HLFIR ops if enabled
2031+
// (transformational always have a result type)
2032+
if (useHlfirIntrinsicOps && callContext.resultType) {
2033+
fir::FirOpBuilder &builder = callContext.getBuilder();
2034+
mlir::Location loc = callContext.loc;
2035+
const std::string intrinsicName = callContext.getProcedureName();
2036+
const fir::IntrinsicArgumentLoweringRules *argLowering =
2037+
intrinsicEntry.getArgumentLoweringRules();
20382038
std::optional<hlfir::EntityWithAttributes> res =
20392039
Fortran::lower::lowerHlfirIntrinsic(builder, loc, intrinsicName,
20402040
loweredActuals, argLowering,
@@ -2044,7 +2044,7 @@ static std::optional<hlfir::EntityWithAttributes> genHLFIRIntrinsicRefCore(
20442044
}
20452045

20462046
// fallback to calling the intrinsic via fir.call
2047-
return genIntrinsicRefCore(loweredActuals, intrinsic, argLowering,
2047+
return genIntrinsicRefCore(loweredActuals, intrinsic, intrinsicEntry,
20482048
callContext);
20492049
}
20502050

@@ -2303,13 +2303,13 @@ class ElementalIntrinsicCallBuilder
23032303
public:
23042304
ElementalIntrinsicCallBuilder(
23052305
const Fortran::evaluate::SpecificIntrinsic *intrinsic,
2306-
const fir::IntrinsicArgumentLoweringRules *argLowering, bool isFunction)
2307-
: intrinsic{intrinsic}, argLowering{argLowering}, isFunction{isFunction} {
2308-
}
2306+
const fir::IntrinsicHandlerEntry &intrinsicEntry, bool isFunction)
2307+
: intrinsic{intrinsic}, intrinsicEntry{intrinsicEntry},
2308+
isFunction{isFunction} {}
23092309
std::optional<hlfir::Entity>
23102310
genElementalKernel(Fortran::lower::PreparedActualArguments &loweredActuals,
23112311
CallContext &callContext) {
2312-
return genHLFIRIntrinsicRefCore(loweredActuals, intrinsic, argLowering,
2312+
return genHLFIRIntrinsicRefCore(loweredActuals, intrinsic, intrinsicEntry,
23132313
callContext);
23142314
}
23152315
// Elemental intrinsic functions cannot modify their arguments.
@@ -2363,7 +2363,7 @@ class ElementalIntrinsicCallBuilder
23632363

23642364
private:
23652365
const Fortran::evaluate::SpecificIntrinsic *intrinsic;
2366-
const fir::IntrinsicArgumentLoweringRules *argLowering;
2366+
fir::IntrinsicHandlerEntry intrinsicEntry;
23672367
const bool isFunction;
23682368
};
23692369
} // namespace
@@ -2436,11 +2436,16 @@ genCustomElementalIntrinsicRef(
24362436
callContext.procRef, *intrinsic, callContext.resultType,
24372437
prepareOptionalArg, prepareOtherArg, converter);
24382438

2439-
const fir::IntrinsicArgumentLoweringRules *argLowering =
2440-
fir::getIntrinsicArgumentLowering(callContext.getProcedureName());
2439+
std::optional<fir::IntrinsicHandlerEntry> intrinsicEntry =
2440+
fir::lookupIntrinsicHandler(callContext.getBuilder(),
2441+
callContext.getProcedureName(),
2442+
callContext.resultType);
2443+
assert(intrinsicEntry.has_value() &&
2444+
"intrinsic with custom handling for OPTIONAL arguments must have "
2445+
"lowering entries");
24412446
// All of the custom intrinsic elementals with custom handling are pure
24422447
// functions
2443-
return ElementalIntrinsicCallBuilder{intrinsic, argLowering,
2448+
return ElementalIntrinsicCallBuilder{intrinsic, *intrinsicEntry,
24442449
/*isFunction=*/true}
24452450
.genElementalCall(operands, /*isImpure=*/false, callContext);
24462451
}
@@ -2517,21 +2522,15 @@ genCustomIntrinsicRef(const Fortran::evaluate::SpecificIntrinsic *intrinsic,
25172522
/// lowered as if it were an intrinsic module procedure (like C_LOC which is a
25182523
/// procedure from intrinsic module iso_c_binding). Otherwise, \p intrinsic
25192524
/// must not be null.
2525+
25202526
static std::optional<hlfir::EntityWithAttributes>
25212527
genIntrinsicRef(const Fortran::evaluate::SpecificIntrinsic *intrinsic,
2528+
const fir::IntrinsicHandlerEntry &intrinsicEntry,
25222529
CallContext &callContext) {
25232530
mlir::Location loc = callContext.loc;
2524-
auto &converter = callContext.converter;
2525-
if (intrinsic && Fortran::lower::intrinsicRequiresCustomOptionalHandling(
2526-
callContext.procRef, *intrinsic, converter)) {
2527-
if (callContext.isElementalProcWithArrayArgs())
2528-
return genCustomElementalIntrinsicRef(intrinsic, callContext);
2529-
return genCustomIntrinsicRef(intrinsic, callContext);
2530-
}
2531-
25322531
Fortran::lower::PreparedActualArguments loweredActuals;
25332532
const fir::IntrinsicArgumentLoweringRules *argLowering =
2534-
fir::getIntrinsicArgumentLowering(callContext.getProcedureName());
2533+
intrinsicEntry.getArgumentLoweringRules();
25352534
for (const auto &arg : llvm::enumerate(callContext.procRef.arguments())) {
25362535

25372536
if (!arg.value()) {
@@ -2581,12 +2580,12 @@ genIntrinsicRef(const Fortran::evaluate::SpecificIntrinsic *intrinsic,
25812580
if (callContext.isElementalProcWithArrayArgs()) {
25822581
// All intrinsic elemental functions are pure.
25832582
const bool isFunction = callContext.resultType.has_value();
2584-
return ElementalIntrinsicCallBuilder{intrinsic, argLowering, isFunction}
2583+
return ElementalIntrinsicCallBuilder{intrinsic, intrinsicEntry, isFunction}
25852584
.genElementalCall(loweredActuals, /*isImpure=*/!isFunction,
25862585
callContext);
25872586
}
25882587
std::optional<hlfir::EntityWithAttributes> result = genHLFIRIntrinsicRefCore(
2589-
loweredActuals, intrinsic, argLowering, callContext);
2588+
loweredActuals, intrinsic, intrinsicEntry, callContext);
25902589
if (result && mlir::isa<hlfir::ExprType>(result->getType())) {
25912590
fir::FirOpBuilder *bldr = &callContext.getBuilder();
25922591
callContext.stmtCtx.attachCleanup(
@@ -2595,18 +2594,43 @@ genIntrinsicRef(const Fortran::evaluate::SpecificIntrinsic *intrinsic,
25952594
return result;
25962595
}
25972596

2597+
static std::optional<hlfir::EntityWithAttributes>
2598+
genIntrinsicRef(const Fortran::evaluate::SpecificIntrinsic *intrinsic,
2599+
CallContext &callContext) {
2600+
mlir::Location loc = callContext.loc;
2601+
auto &converter = callContext.converter;
2602+
if (intrinsic && Fortran::lower::intrinsicRequiresCustomOptionalHandling(
2603+
callContext.procRef, *intrinsic, converter)) {
2604+
if (callContext.isElementalProcWithArrayArgs())
2605+
return genCustomElementalIntrinsicRef(intrinsic, callContext);
2606+
return genCustomIntrinsicRef(intrinsic, callContext);
2607+
}
2608+
std::optional<fir::IntrinsicHandlerEntry> intrinsicEntry =
2609+
fir::lookupIntrinsicHandler(callContext.getBuilder(),
2610+
callContext.getProcedureName(),
2611+
callContext.resultType);
2612+
if (!intrinsicEntry)
2613+
fir::crashOnMissingIntrinsic(loc, callContext.getProcedureName());
2614+
return genIntrinsicRef(intrinsic, *intrinsicEntry, callContext);
2615+
}
2616+
25982617
/// Main entry point to lower procedure references, regardless of what they are.
25992618
static std::optional<hlfir::EntityWithAttributes>
26002619
genProcedureRef(CallContext &callContext) {
26012620
mlir::Location loc = callContext.loc;
2621+
fir::FirOpBuilder &builder = callContext.getBuilder();
26022622
if (auto *intrinsic = callContext.procRef.proc().GetSpecificIntrinsic())
26032623
return genIntrinsicRef(intrinsic, callContext);
2604-
// If it is an intrinsic module procedure reference - then treat as
2605-
// intrinsic unless it is bind(c) (since implementation is external from
2606-
// module).
2624+
// Intercept non BIND(C) module procedure reference that have lowering
2625+
// handlers defined for there name. Otherwise, lower them as user
2626+
// procedure calls and expect the implementation to be part of
2627+
// runtime libraries with the proper name mangling.
26072628
if (Fortran::lower::isIntrinsicModuleProcRef(callContext.procRef) &&
26082629
!callContext.isBindcCall())
2609-
return genIntrinsicRef(nullptr, callContext);
2630+
if (std::optional<fir::IntrinsicHandlerEntry> intrinsicEntry =
2631+
fir::lookupIntrinsicHandler(builder, callContext.getProcedureName(),
2632+
callContext.resultType))
2633+
return genIntrinsicRef(nullptr, *intrinsicEntry, callContext);
26102634

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

0 commit comments

Comments
 (0)