[flang-commits] [flang] [flang] allow intrinsic module procedures to be implemented in Fortran (PR #97743)
via flang-commits
flang-commits at lists.llvm.org
Thu Jul 4 08:27:47 PDT 2024
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-flang-fir-hlfir
Author: None (jeanPerier)
<details>
<summary>Changes</summary>
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).
---
Patch is 29.78 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/97743.diff
3 Files Affected:
- (modified) flang/include/flang/Optimizer/Builder/IntrinsicCall.h (+34)
- (modified) flang/lib/Lower/ConvertCall.cpp (+61-38)
- (modified) flang/lib/Optimizer/Builder/IntrinsicCall.cpp (+157-60)
``````````diff
diff --git a/flang/include/flang/Optimizer/Builder/IntrinsicCall.h b/flang/include/flang/Optimizer/Builder/IntrinsicCall.h
index ec1fb411ff0e2..87a91820fd246 100644
--- a/flang/include/flang/Optimizer/Builder/IntrinsicCall.h
+++ b/flang/include/flang/Optimizer/Builder/IntrinsicCall.h
@@ -25,6 +25,7 @@
namespace fir {
class StatementContext;
+struct IntrinsicHandlerEntry;
// TODO: Error handling interface ?
// TODO: Implementation is incomplete. Many intrinsics to tbd.
@@ -38,6 +39,13 @@ genIntrinsicCall(fir::FirOpBuilder &, mlir::Location, llvm::StringRef name,
llvm::ArrayRef<fir::ExtendedValue> args,
Fortran::lower::AbstractConverter *converter = nullptr);
+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 };
@@ -156,6 +164,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
@@ -676,6 +689,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.
//===----------------------------------------------------------------------===//
@@ -728,6 +753,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 *
diff --git a/flang/lib/Lower/ConvertCall.cpp b/flang/lib/Lower/ConvertCall.cpp
index 5e20f9eee4fc9..54e29a1d60689 100644
--- a/flang/lib/Lower/ConvertCall.cpp
+++ b/flang/lib/Lower/ConvertCall.cpp
@@ -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(
@@ -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());
@@ -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))
@@ -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,
@@ -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);
}
@@ -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.
@@ -2363,7 +2363,7 @@ class ElementalIntrinsicCallBuilder
private:
const Fortran::evaluate::SpecificIntrinsic *intrinsic;
- const fir::IntrinsicArgumentLoweringRules *argLowering;
+ fir::IntrinsicHandlerEntry intrinsicEntry;
const bool isFunction;
};
} // namespace
@@ -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);
}
@@ -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()) {
@@ -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(
@@ -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,
@@ -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();
diff --git a/flang/lib/Optimizer/Builder/IntrinsicCall.cpp b/flang/lib/Optimizer/Builder/IntrinsicCall.cpp
index a1cef7437fa2d..f4541bf30676a 100644
--- a/flang/lib/Optimizer/Builder/IntrinsicCall.cpp
+++ b/flang/lib/Optimizer/Builder/IntrinsicCall.cpp
@@ -95,6 +95,17 @@ static bool isStaticallyPresent(const fir::ExtendedValue &exv) {
return !isStaticallyAbsent(exv);
}
+/// IEEE module procedure names not yet implemented for genModuleProcTODO.
+static constexpr char ieee_int[] = "ieee_int";
+static constexpr char ieee_get_underflow_mode[] = "ieee_get_underflow_mode";
+static constexpr char ieee_next_after[] = "ieee_next_after";
+static constexpr char ieee_next_down[] = "ieee_next_down";
+static constexpr char ieee_next_up[] = "ieee_next_up";
+static constexpr char ieee_real[] = "ieee_real";
+static constexpr char ieee_rem[] = "ieee_rem";
+static constexpr char ieee_rint[] = "ieee_rint";
+static constexpr char ieee_set_underflow_mode[] = "ieee_set_underflow_mode";
+
using I = IntrinsicLibrary;
/// Flag to indicate that an intrinsic argument has to be handled as
@@ -321,6 +332,8 @@ static constexpr IntrinsicHandler handlers[]{
{"radix", asValue, handleDynamicOptional}}},
/*isElemental=*/false},
{"ieee_get_status", &I::genIeeeGetOrSetStatus</*isGet=*/true>},
+ {"ieee_get_underflow_mode", &I::genModuleProcTODO<ieee_get_underflow_mode>},
+ {"ieee_int", &I::genModuleProcTODO<ieee_int>},
{"ieee_is_finite", &I::genIeeeIsFinite},
{"ieee_is_nan", &I::genIeeeIsNan},
{"ieee_is_negative", &I::genIeeeIsNegative},
@@ -342,12 +355,18 @@ static constexpr IntrinsicHandler handlers[]{
&I::genIeeeMaxMin</*isMax=*/false, /*isNum=*/true, /*isMag=*/false>},
{"ieee_min_num_mag",
&I::genIeeeMaxMin</*isMax=*/false, /*isNum=*/true, /*isMag=*/true>},
+ {"ieee_next_after", &I::genModuleProcTODO<ieee_next_after>},
+ {"ieee_next_down", &I::genModuleProcTODO<ieee_next_down>},
+ {"ieee_next_up", &I::genModuleProcTODO<ieee_next_up>},
{"ieee_quiet_eq", &I::genIeeeQuietCompare<mlir::arith::CmpFPredicate::OEQ>},
{"ieee_quiet_ge", &I::genIeeeQuietCompare<mlir::arith::CmpFPredicate::OGE>},
{"ieee_quiet_gt", &I::genIeeeQuietCompare<mlir::arith::CmpFPredicate::OGT>},
{"ieee_quiet_le", &I::genIeeeQuietCompare<mlir::arith::CmpFPredicate::OLE>},
{"ieee_quiet_lt", &I::genIeeeQuietCompare<mlir::arith::CmpFPredicate::OLT>},
{"ieee_quiet_ne", &I::genIeeeQuietCompare<mlir::arith::CmpFPredicate::UNE>},
+ {"ieee_real", &I::genModuleProcTODO<ieee_real>},
+ {"ieee_rem", &I::genModuleProcTODO<ieee_rem>},
+ {"ieee_rint", &I::genModuleProcTODO<ieee_rint>},
{"ieee_round_eq", &I::genIeeeTypeCompare<mlir::arith::CmpIPredicate::eq>},
{"ieee_round_ne", &I::genIeeeTypeCompare<mlir::arith::CmpIPredicate::ne>},
{"ieee_set_flag", &I::genIeeeSetFlagOrHaltingMode</*isFlag=*/true>},
@@ -360,6 +379,7 @@ static constexpr IntrinsicHandler handlers[]{
{"radix", asValue, handleDynamicOptional}}},
/*isElemental=*/false},
{"ieee_set_status", &I::genIeeeGetOrSetStatus</*isGet=*/false>},
+ {"ieee_set_underflow_mode", &I::genModuleProcTODO<ieee_set_underflow_mode>},
{"ieee_signaling_eq",
&I::genIeeeSignalingCompare<mlir::arith::CmpFPredicate::OEQ>},
{"ieee_signaling_ge",
@@ -1493,17 +1513,11 @@ static_assert(mathOps.Verify() && "map must be sorted");
/// \p bestMatchDistance specifies the FunctionDistance between
/// the requested operation and the non-exact match.
static const MathOperation *
-searchMathOperation(fir::FirOpBuilder &builder, llvm::StringRef name,
+searchMathOperation(fir::FirOpBuilder &builder,
+ const IntrinsicHandlerEntry::RuntimeGeneratorRange &range,
mlir::FunctionType funcType,
const MathOperation **bestNearMatch,
FunctionDistance &bestMatchDistance) {
- auto range = mathOps.equal_range(name);
- auto mod = builder.getModule();
-
- // Search ppcMathOps only if targetting PowerPC arch
- if (fir::getTargetTriple(mod).isPPC() && range.first == range.second) {
- range = checkPPCMathOperationsRange(name);
- }
for (auto iter = range.first; iter != range.second && iter; ++iter) {
const auto &impl = *iter;
auto implType = impl.typeGenerator(builder.getContext(), builder);
@@ -1649,8 +1663,46 @@ llvm::StringRef genericName(llvm::StringRef specificName) {
return name.drop_back(name.size() - size);
}
+std::optional<IntrinsicHandlerEntry::RuntimeGeneratorRange>
+lookupRuntimeGenerator(llvm::StringRef name, bool isPPCTarget) {
+ if (auto range = mathOps.equal_range(name); range.first != range.second)
+ return std::make_optional<IntrinsicHandlerEntry::RuntimeGeneratorRange>(
+ range);
+ // Search ppcMathOps only if targetting PowerPC arch
+ if (isPPCTarget)
+ if (auto range = checkPPCMathOperationsRange(name);
+ range.first != range.second)
+ return std::make_optional<IntrinsicHandlerEntry::RuntimeGeneratorRange>(
+ range);
+ return std::nullopt;
+}
+
+std::optional<IntrinsicHandlerEntry>
+lookupIntrinsicHandler(fir::FirOpBuilder &builder,
+ llvm::StringRef intrinsicName,
+ std::optional<mlir::Type> resultType) {
+ llvm::StringRef name = genericName(intrinsicName);
+ if (const IntrinsicHandler *handler = findIntrinsicHandler(name))
+ return std::make_optional<IntrinsicHandlerEntry>(handler);
+ bool isPPCTarget = fir::getTargetTriple(builder.getModule()).isPPC();
+ // If targeting PowerPC, check PPC intrinsic handlers.
+ if (isPPCTarget)
+ if (const IntrinsicHandler *ppcHandler = findPPCIntrinsicHandler(name))
+ return std::make_optional<IntrinsicHan...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/97743
More information about the flang-commits
mailing list