//===- 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(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(def)) { maybeIndirectAccess = true; break; } } if (auto refType = mlir::dyn_cast(argType)) { if (!fir::isa_trivial(refType.getElementType())) { maybeIndirectAccess = true; break; } } if (auto ptrType = mlir::dyn_cast(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}); } 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 constexpr bool IsAny = std::disjunction_v...>; } // 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 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 struct NosyncAttrDesc { static std::optional 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 struct NocallbackAttrDesc { static std::optional 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 struct NocallbackAttrDesc< KEY, std::enable_if_t< IsAny>> { static std::optional 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 (*)(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 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::length(name)}, MemoryAttrDesc::get, NosyncAttrDesc::get, NocallbackAttrDesc::get}; } }; } // end anonymous namespace #define KNOWN_IO_FUNC(X) RuntimeFactory::create(mkIOKey(X)::name) #define KNOWN_RUNTIME_FUNC(X) \ RuntimeFactory::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 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(callOp.getOperation()); auto funcOp = mlir::dyn_cast_or_null( iface.resolveCallableInTable(&symbolTable)); if (!funcOp || !funcOp->hasAttrOfType( 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"); }