262 lines · cpp
1//===- SetRuntimeCallAttributes.cpp ---------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9//===----------------------------------------------------------------------===//10/// \file11/// SetRuntimeCallAttributesPass looks for fir.call operations12/// that are calling into Fortran runtime, and tries to set different13/// attributes on them to enable more optimizations in LLVM backend14/// (granted that they are preserved all the way to LLVM IR).15/// This pass is currently only attaching fir.call wide atttributes,16/// such as ones corresponding to llvm.memory, nosync, nocallbac, etc.17/// It is not designed to attach attributes to the arguments and the results18/// of a call.19//===----------------------------------------------------------------------===//20#include "flang/Common/static-multimap-view.h"21#include "flang/Optimizer/Builder/Runtime/RTBuilder.h"22#include "flang/Optimizer/Dialect/FIRDialect.h"23#include "flang/Optimizer/Dialect/FIROpsSupport.h"24#include "flang/Optimizer/Support/InternalNames.h"25#include "flang/Optimizer/Transforms/Passes.h"26#include "flang/Runtime/io-api.h"27#include "mlir/Dialect/LLVMIR/LLVMAttrs.h"28 29namespace fir {30#define GEN_PASS_DEF_SETRUNTIMECALLATTRIBUTES31#include "flang/Optimizer/Transforms/Passes.h.inc"32} // namespace fir33 34#define DEBUG_TYPE "set-runtime-call-attrs"35 36using namespace Fortran::runtime;37using namespace Fortran::runtime::io;38 39#define mkIOKey(X) FirmkKey(IONAME(X))40#define mkRTKey(X) FirmkKey(RTNAME(X))41 42// Return LLVM dialect MemoryEffectsAttr for the given Fortran runtime call.43// This function is computing a generic value of this attribute44// by analyzing the arguments and their types.45// It tries to figure out if an "indirect" memory access is possible46// during this call. If it is not possible, then the memory effects47// are:48// * other = NoModRef49// * argMem = ModRef50// * inaccessibleMem = ModRef51//52// Otherwise, it returns an empty attribute meaning ModRef for all kinds53// of memory.54//55// The attribute deduction is conservative in a sense that it applies56// to most of the runtime calls, but it may still be incorrect for some57// runtime calls.58static mlir::LLVM::MemoryEffectsAttr getGenericMemoryAttr(fir::CallOp callOp) {59 bool maybeIndirectAccess = false;60 for (auto arg : callOp.getArgOperands()) {61 mlir::Type argType = arg.getType();62 if (mlir::isa<fir::BaseBoxType>(argType)) {63 // If it is a null/absent box, then this particular call64 // cannot access memory indirectly through the box's65 // base_addr.66 auto def = arg.getDefiningOp();67 if (!mlir::isa_and_nonnull<fir::ZeroOp, fir::AbsentOp>(def)) {68 maybeIndirectAccess = true;69 break;70 }71 }72 if (auto refType = mlir::dyn_cast<fir::ReferenceType>(argType)) {73 if (!fir::isa_trivial(refType.getElementType())) {74 maybeIndirectAccess = true;75 break;76 }77 }78 if (auto ptrType = mlir::dyn_cast<mlir::LLVM::LLVMPointerType>(argType)) {79 maybeIndirectAccess = true;80 break;81 }82 }83 if (!maybeIndirectAccess) {84 return mlir::LLVM::MemoryEffectsAttr::get(85 callOp->getContext(),86 {/*other=*/mlir::LLVM::ModRefInfo::NoModRef,87 /*argMem=*/mlir::LLVM::ModRefInfo::ModRef,88 /*inaccessibleMem=*/mlir::LLVM::ModRefInfo::ModRef,89 /*errnoMem=*/mlir::LLVM::ModRefInfo::NoModRef,90 /*targetMem0=*/mlir::LLVM::ModRefInfo::NoModRef,91 /*targetMem1=*/mlir::LLVM::ModRefInfo::NoModRef});92 }93 94 return {};95}96 97namespace {98class SetRuntimeCallAttributesPass99 : public fir::impl::SetRuntimeCallAttributesBase<100 SetRuntimeCallAttributesPass> {101public:102 void runOnOperation() override;103};104 105// A helper to match a type against a list of types.106template <typename T, typename... Ts>107constexpr bool IsAny = std::disjunction_v<std::is_same<T, Ts>...>;108} // end anonymous namespace109 110// MemoryAttrDesc type provides get() method for computing111// mlir::LLVM::MemoryEffectsAttr for the given Fortran runtime call.112// If needed, add specializations for particular runtime calls.113namespace {114// Default implementation just uses getGenericMemoryAttr().115// Note that it may be incorrect for some runtime calls.116template <typename KEY, typename Enable = void>117struct MemoryAttrDesc {118 static mlir::LLVM::MemoryEffectsAttr get(fir::CallOp callOp) {119 return getGenericMemoryAttr(callOp);120 }121};122} // end anonymous namespace123 124// NosyncAttrDesc type provides get() method for computing125// LLVM nosync attribute for the given call.126namespace {127// Default implementation always returns LLVM nosync.128// This should be true for the majority of the Fortran runtime calls.129template <typename KEY, typename Enable = void>130struct NosyncAttrDesc {131 static std::optional<mlir::NamedAttribute> get(fir::CallOp callOp) {132 // TODO: replace llvm.nosync with an LLVM dialect callback.133 return mlir::NamedAttribute("llvm.nosync",134 mlir::UnitAttr::get(callOp->getContext()));135 }136};137} // end anonymous namespace138 139// NocallbackAttrDesc type provides get() method for computing140// LLVM nocallback attribute for the given call.141namespace {142// Default implementation always returns LLVM nocallback.143// It must be specialized for Fortran runtime functions that may call144// user functions during their execution (e.g. defined IO, assignment).145template <typename KEY, typename Enable = void>146struct NocallbackAttrDesc {147 static std::optional<mlir::NamedAttribute> get(fir::CallOp callOp) {148 // TODO: replace llvm.nocallback with an LLVM dialect callback.149 return mlir::NamedAttribute("llvm.nocallback",150 mlir::UnitAttr::get(callOp->getContext()));151 }152};153 154// Derived types IO may call back into a Fortran module.155// This specialization is conservative for Input/OutputDerivedType,156// and it might be improved by checking if the NonTbpDefinedIoTable157// pointer argument is null.158template <typename KEY>159struct NocallbackAttrDesc<160 KEY, std::enable_if_t<161 IsAny<KEY, mkIOKey(OutputDerivedType), mkIOKey(InputDerivedType),162 mkIOKey(OutputNamelist), mkIOKey(InputNamelist)>>> {163 static std::optional<mlir::NamedAttribute> get(fir::CallOp) {164 return std::nullopt;165 }166};167} // end anonymous namespace168 169namespace {170// RuntimeFunction provides different callbacks that compute values171// of fir.call attributes for a Fortran runtime function.172struct RuntimeFunction {173 using MemoryAttrGeneratorTy = mlir::LLVM::MemoryEffectsAttr (*)(fir::CallOp);174 using NamedAttrGeneratorTy =175 std::optional<mlir::NamedAttribute> (*)(fir::CallOp);176 using Key = std::string_view;177 constexpr operator Key() const { return key; }178 Key key;179 MemoryAttrGeneratorTy memoryAttrGenerator;180 NamedAttrGeneratorTy nosyncAttrGenerator;181 NamedAttrGeneratorTy nocallbackAttrGenerator;182};183 184// Helper type to create a RuntimeFunction descriptor given185// the KEY and a function name.186template <typename KEY>187struct RuntimeFactory {188 static constexpr RuntimeFunction create(const char name[]) {189 // GCC 7 does not recognize this as a constant expression:190 // ((const char *)RuntimeFunction<>::name) == nullptr191 // This comparison comes from the basic_string_view(const char *)192 // constructor. We have to use the other constructor193 // that takes explicit length parameter.194 return RuntimeFunction{195 std::string_view{name, std::char_traits<char>::length(name)},196 MemoryAttrDesc<KEY>::get, NosyncAttrDesc<KEY>::get,197 NocallbackAttrDesc<KEY>::get};198 }199};200} // end anonymous namespace201 202#define KNOWN_IO_FUNC(X) RuntimeFactory<mkIOKey(X)>::create(mkIOKey(X)::name)203#define KNOWN_RUNTIME_FUNC(X) \204 RuntimeFactory<mkRTKey(X)>::create(mkRTKey(X)::name)205 206// A table of RuntimeFunction descriptors for all recognized207// Fortran runtime functions.208static constexpr RuntimeFunction runtimeFuncsTable[] = {209#include "flang/Optimizer/Transforms/RuntimeFunctions.inc"210};211 212static constexpr Fortran::common::StaticMultimapView<RuntimeFunction>213 runtimeFuncs(runtimeFuncsTable);214static_assert(runtimeFuncs.Verify() && "map must be sorted");215 216// Set attributes for the given Fortran runtime call.217// The symbolTable is used to cache the name lookups in the module.218static void setRuntimeCallAttributes(fir::CallOp callOp,219 mlir::SymbolTableCollection &symbolTable) {220 auto iface = mlir::cast<mlir::CallOpInterface>(callOp.getOperation());221 auto funcOp = mlir::dyn_cast_or_null<mlir::func::FuncOp>(222 iface.resolveCallableInTable(&symbolTable));223 224 if (!funcOp || !funcOp->hasAttrOfType<mlir::UnitAttr>(225 fir::FIROpsDialect::getFirRuntimeAttrName()))226 return;227 228 llvm::StringRef name = funcOp.getName();229 if (auto range = runtimeFuncs.equal_range(name);230 range.first != range.second) {231 // There should not be duplicate entries.232 assert(range.first + 1 == range.second);233 const RuntimeFunction &desc = *range.first;234 LLVM_DEBUG(llvm::dbgs()235 << "Identified runtime function call: " << desc.key << '\n');236 if (mlir::LLVM::MemoryEffectsAttr memoryAttr =237 desc.memoryAttrGenerator(callOp))238 callOp->setAttr(fir::FIROpsDialect::getFirCallMemoryAttrName(),239 memoryAttr);240 if (auto attr = desc.nosyncAttrGenerator(callOp))241 callOp->setAttr(attr->getName(), attr->getValue());242 if (auto attr = desc.nocallbackAttrGenerator(callOp))243 callOp->setAttr(attr->getName(), attr->getValue());244 LLVM_DEBUG(llvm::dbgs() << "Operation with attrs: " << callOp << '\n');245 }246}247 248void SetRuntimeCallAttributesPass::runOnOperation() {249 mlir::func::FuncOp funcOp = getOperation();250 // Exit early for declarations to skip the debug output for them.251 if (funcOp.isDeclaration())252 return;253 LLVM_DEBUG(llvm::dbgs() << "=== Begin " DEBUG_TYPE " ===\n");254 LLVM_DEBUG(llvm::dbgs() << "Func-name:" << funcOp.getSymName() << "\n");255 256 mlir::SymbolTableCollection symbolTable;257 funcOp.walk([&](fir::CallOp callOp) {258 setRuntimeCallAttributes(callOp, symbolTable);259 });260 LLVM_DEBUG(llvm::dbgs() << "=== End " DEBUG_TYPE " ===\n");261}262