| //===- SetRuntimeCallAttributes.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 |
| // |
| //===----------------------------------------------------------------------===// |
| |
| //===----------------------------------------------------------------------===// |
| /// \file |
| /// SetRuntimeCallAttributesPass looks for fir.call operations |
| /// that are calling into Fortran runtime, and tries to set different |
| /// attributes on them to enable more optimizations in LLVM backend |
| /// (granted that they are preserved all the way to LLVM IR). |
| /// This pass is currently only attaching fir.call wide atttributes, |
| /// such as ones corresponding to llvm.memory, nosync, nocallbac, etc. |
| /// It is not designed to attach attributes to the arguments and the results |
| /// of a call. |
| //===----------------------------------------------------------------------===// |
| #include "flang/Common/static-multimap-view.h" |
| #include "flang/Optimizer/Builder/Runtime/RTBuilder.h" |
| #include "flang/Optimizer/Dialect/FIRDialect.h" |
| #include "flang/Optimizer/Dialect/FIROpsSupport.h" |
| #include "flang/Optimizer/Support/InternalNames.h" |
| #include "flang/Optimizer/Transforms/Passes.h" |
| #include "flang/Runtime/io-api.h" |
| #include "mlir/Dialect/LLVMIR/LLVMAttrs.h" |
| |
| namespace fir { |
| #define GEN_PASS_DEF_SETRUNTIMECALLATTRIBUTES |
| #include "flang/Optimizer/Transforms/Passes.h.inc" |
| } // namespace fir |
| |
| #define DEBUG_TYPE "set-runtime-call-attrs" |
| |
| using namespace Fortran::runtime; |
| using namespace Fortran::runtime::io; |
| |
| #define mkIOKey(X) FirmkKey(IONAME(X)) |
| #define mkRTKey(X) FirmkKey(RTNAME(X)) |
| |
| // Return LLVM dialect MemoryEffectsAttr for the given Fortran runtime call. |
| // This function is computing a generic value of this attribute |
| // by analyzing the arguments and their types. |
| // It tries to figure out if an "indirect" memory access is possible |
| // during this call. If it is not possible, then the memory effects |
| // are: |
| // * other = NoModRef |
| // * argMem = ModRef |
| // * inaccessibleMem = ModRef |
| // |
| // Otherwise, it returns an empty attribute meaning ModRef for all kinds |
| // of memory. |
| // |
| // The attribute deduction is conservative in a sense that it applies |
| // to most of the runtime calls, but it may still be incorrect for some |
| // runtime calls. |
| static mlir::LLVM::MemoryEffectsAttr getGenericMemoryAttr(fir::CallOp callOp) { |
| bool maybeIndirectAccess = false; |
| for (auto arg : callOp.getArgOperands()) { |
| mlir::Type argType = arg.getType(); |
| if (mlir::isa<fir::BaseBoxType>(argType)) { |
| // If it is a null/absent box, then this particular call |
| // cannot access memory indirectly through the box's |
| // base_addr. |
| auto def = arg.getDefiningOp(); |
| if (!mlir::isa_and_nonnull<fir::ZeroOp, fir::AbsentOp>(def)) { |
| maybeIndirectAccess = true; |
| break; |
| } |
| } |
| if (auto refType = mlir::dyn_cast<fir::ReferenceType>(argType)) { |
| if (!fir::isa_trivial(refType.getElementType())) { |
| maybeIndirectAccess = true; |
| break; |
| } |
| } |
| if (auto ptrType = mlir::dyn_cast<mlir::LLVM::LLVMPointerType>(argType)) { |
| maybeIndirectAccess = true; |
| break; |
| } |
| } |
| if (!maybeIndirectAccess) { |
| return mlir::LLVM::MemoryEffectsAttr::get( |
| callOp->getContext(), |
| {/*other=*/mlir::LLVM::ModRefInfo::NoModRef, |
| /*argMem=*/mlir::LLVM::ModRefInfo::ModRef, |
| /*inaccessibleMem=*/mlir::LLVM::ModRefInfo::ModRef, |
| /*errnoMem=*/mlir::LLVM::ModRefInfo::NoModRef, |
| /*targetMem0=*/mlir::LLVM::ModRefInfo::NoModRef, |
| /*targetMem1=*/mlir::LLVM::ModRefInfo::NoModRef}); |
| } |
| |
| return {}; |
| } |
| |
| namespace { |
| class SetRuntimeCallAttributesPass |
| : public fir::impl::SetRuntimeCallAttributesBase< |
| SetRuntimeCallAttributesPass> { |
| public: |
| void runOnOperation() override; |
| }; |
| |
| // A helper to match a type against a list of types. |
| template <typename T, typename... Ts> |
| constexpr bool IsAny = std::disjunction_v<std::is_same<T, Ts>...>; |
| } // end anonymous namespace |
| |
| // MemoryAttrDesc type provides get() method for computing |
| // mlir::LLVM::MemoryEffectsAttr for the given Fortran runtime call. |
| // If needed, add specializations for particular runtime calls. |
| namespace { |
| // Default implementation just uses getGenericMemoryAttr(). |
| // Note that it may be incorrect for some runtime calls. |
| template <typename KEY, typename Enable = void> |
| struct MemoryAttrDesc { |
| static mlir::LLVM::MemoryEffectsAttr get(fir::CallOp callOp) { |
| return getGenericMemoryAttr(callOp); |
| } |
| }; |
| } // end anonymous namespace |
| |
| // NosyncAttrDesc type provides get() method for computing |
| // LLVM nosync attribute for the given call. |
| namespace { |
| // Default implementation always returns LLVM nosync. |
| // This should be true for the majority of the Fortran runtime calls. |
| template <typename KEY, typename Enable = void> |
| struct NosyncAttrDesc { |
| static std::optional<mlir::NamedAttribute> get(fir::CallOp callOp) { |
| // TODO: replace llvm.nosync with an LLVM dialect callback. |
| return mlir::NamedAttribute("llvm.nosync", |
| mlir::UnitAttr::get(callOp->getContext())); |
| } |
| }; |
| } // end anonymous namespace |
| |
| // NocallbackAttrDesc type provides get() method for computing |
| // LLVM nocallback attribute for the given call. |
| namespace { |
| // Default implementation always returns LLVM nocallback. |
| // It must be specialized for Fortran runtime functions that may call |
| // user functions during their execution (e.g. defined IO, assignment). |
| template <typename KEY, typename Enable = void> |
| struct NocallbackAttrDesc { |
| static std::optional<mlir::NamedAttribute> get(fir::CallOp callOp) { |
| // TODO: replace llvm.nocallback with an LLVM dialect callback. |
| return mlir::NamedAttribute("llvm.nocallback", |
| mlir::UnitAttr::get(callOp->getContext())); |
| } |
| }; |
| |
| // Derived types IO may call back into a Fortran module. |
| // This specialization is conservative for Input/OutputDerivedType, |
| // and it might be improved by checking if the NonTbpDefinedIoTable |
| // pointer argument is null. |
| template <typename KEY> |
| struct NocallbackAttrDesc< |
| KEY, std::enable_if_t< |
| IsAny<KEY, mkIOKey(OutputDerivedType), mkIOKey(InputDerivedType), |
| mkIOKey(OutputNamelist), mkIOKey(InputNamelist)>>> { |
| static std::optional<mlir::NamedAttribute> get(fir::CallOp) { |
| return std::nullopt; |
| } |
| }; |
| } // end anonymous namespace |
| |
| namespace { |
| // RuntimeFunction provides different callbacks that compute values |
| // of fir.call attributes for a Fortran runtime function. |
| struct RuntimeFunction { |
| using MemoryAttrGeneratorTy = mlir::LLVM::MemoryEffectsAttr (*)(fir::CallOp); |
| using NamedAttrGeneratorTy = |
| std::optional<mlir::NamedAttribute> (*)(fir::CallOp); |
| using Key = std::string_view; |
| constexpr operator Key() const { return key; } |
| Key key; |
| MemoryAttrGeneratorTy memoryAttrGenerator; |
| NamedAttrGeneratorTy nosyncAttrGenerator; |
| NamedAttrGeneratorTy nocallbackAttrGenerator; |
| }; |
| |
| // Helper type to create a RuntimeFunction descriptor given |
| // the KEY and a function name. |
| template <typename KEY> |
| struct RuntimeFactory { |
| static constexpr RuntimeFunction create(const char name[]) { |
| // GCC 7 does not recognize this as a constant expression: |
| // ((const char *)RuntimeFunction<>::name) == nullptr |
| // This comparison comes from the basic_string_view(const char *) |
| // constructor. We have to use the other constructor |
| // that takes explicit length parameter. |
| return RuntimeFunction{ |
| std::string_view{name, std::char_traits<char>::length(name)}, |
| MemoryAttrDesc<KEY>::get, NosyncAttrDesc<KEY>::get, |
| NocallbackAttrDesc<KEY>::get}; |
| } |
| }; |
| } // end anonymous namespace |
| |
| #define KNOWN_IO_FUNC(X) RuntimeFactory<mkIOKey(X)>::create(mkIOKey(X)::name) |
| #define KNOWN_RUNTIME_FUNC(X) \ |
| RuntimeFactory<mkRTKey(X)>::create(mkRTKey(X)::name) |
| |
| // A table of RuntimeFunction descriptors for all recognized |
| // Fortran runtime functions. |
| static constexpr RuntimeFunction runtimeFuncsTable[] = { |
| #include "flang/Optimizer/Transforms/RuntimeFunctions.inc" |
| }; |
| |
| static constexpr Fortran::common::StaticMultimapView<RuntimeFunction> |
| runtimeFuncs(runtimeFuncsTable); |
| static_assert(runtimeFuncs.Verify() && "map must be sorted"); |
| |
| // Set attributes for the given Fortran runtime call. |
| // The symbolTable is used to cache the name lookups in the module. |
| static void setRuntimeCallAttributes(fir::CallOp callOp, |
| mlir::SymbolTableCollection &symbolTable) { |
| auto iface = mlir::cast<mlir::CallOpInterface>(callOp.getOperation()); |
| auto funcOp = mlir::dyn_cast_or_null<mlir::func::FuncOp>( |
| iface.resolveCallableInTable(&symbolTable)); |
| |
| if (!funcOp || !funcOp->hasAttrOfType<mlir::UnitAttr>( |
| fir::FIROpsDialect::getFirRuntimeAttrName())) |
| return; |
| |
| llvm::StringRef name = funcOp.getName(); |
| if (auto range = runtimeFuncs.equal_range(name); |
| range.first != range.second) { |
| // There should not be duplicate entries. |
| assert(range.first + 1 == range.second); |
| const RuntimeFunction &desc = *range.first; |
| LLVM_DEBUG(llvm::dbgs() |
| << "Identified runtime function call: " << desc.key << '\n'); |
| if (mlir::LLVM::MemoryEffectsAttr memoryAttr = |
| desc.memoryAttrGenerator(callOp)) |
| callOp->setAttr(fir::FIROpsDialect::getFirCallMemoryAttrName(), |
| memoryAttr); |
| if (auto attr = desc.nosyncAttrGenerator(callOp)) |
| callOp->setAttr(attr->getName(), attr->getValue()); |
| if (auto attr = desc.nocallbackAttrGenerator(callOp)) |
| callOp->setAttr(attr->getName(), attr->getValue()); |
| LLVM_DEBUG(llvm::dbgs() << "Operation with attrs: " << callOp << '\n'); |
| } |
| } |
| |
| void SetRuntimeCallAttributesPass::runOnOperation() { |
| mlir::func::FuncOp funcOp = getOperation(); |
| // Exit early for declarations to skip the debug output for them. |
| if (funcOp.isDeclaration()) |
| return; |
| LLVM_DEBUG(llvm::dbgs() << "=== Begin " DEBUG_TYPE " ===\n"); |
| LLVM_DEBUG(llvm::dbgs() << "Func-name:" << funcOp.getSymName() << "\n"); |
| |
| mlir::SymbolTableCollection symbolTable; |
| funcOp.walk([&](fir::CallOp callOp) { |
| setRuntimeCallAttributes(callOp, symbolTable); |
| }); |
| LLVM_DEBUG(llvm::dbgs() << "=== End " DEBUG_TYPE " ===\n"); |
| } |