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-exception
6//
7//===----------------------------------------------------------------------===//
8
9//===----------------------------------------------------------------------===//
10/// \file
11/// SetRuntimeCallAttributesPass looks for fir.call operations
12/// that are calling into Fortran runtime, and tries to set different
13/// attributes on them to enable more optimizations in LLVM backend
14/// (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 results
18/// 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_SETRUNTIMECALLATTRIBUTES
31#include "flang/Optimizer/Transforms/Passes.h.inc"
32} // namespace fir
33
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 attribute
44// by analyzing the arguments and their types.
45// It tries to figure out if an "indirect" memory access is possible
46// during this call. If it is not possible, then the memory effects
47// are:
48// * other = NoModRef
49// * argMem = ModRef
50// * inaccessibleMem = ModRef
51//
52// Otherwise, it returns an empty attribute meaning ModRef for all kinds
53// of memory.
54//
55// The attribute deduction is conservative in a sense that it applies
56// to most of the runtime calls, but it may still be incorrect for some
57// 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 call
64 // cannot access memory indirectly through the box's
65 // 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 }
90
91 return {};
92}
93
94namespace {
95class SetRuntimeCallAttributesPass
96 : public fir::impl::SetRuntimeCallAttributesBase<
97 SetRuntimeCallAttributesPass> {
98public:
99 void runOnOperation() override;
100};
101
102// A helper to match a type against a list of types.
103template <typename T, typename... Ts>
104constexpr bool IsAny = std::disjunction_v<std::is_same<T, Ts>...>;
105} // end anonymous namespace
106
107// MemoryAttrDesc type provides get() method for computing
108// mlir::LLVM::MemoryEffectsAttr for the given Fortran runtime call.
109// If needed, add specializations for particular runtime calls.
110namespace {
111// Default implementation just uses getGenericMemoryAttr().
112// Note that it may be incorrect for some runtime calls.
113template <typename KEY, typename Enable = void>
114struct MemoryAttrDesc {
115 static mlir::LLVM::MemoryEffectsAttr get(fir::CallOp callOp) {
116 return getGenericMemoryAttr(callOp);
117 }
118};
119} // end anonymous namespace
120
121// NosyncAttrDesc type provides get() method for computing
122// LLVM nosync attribute for the given call.
123namespace {
124// Default implementation always returns LLVM nosync.
125// This should be true for the majority of the Fortran runtime calls.
126template <typename KEY, typename Enable = void>
127struct NosyncAttrDesc {
128 static std::optional<mlir::NamedAttribute> get(fir::CallOp callOp) {
129 // TODO: replace llvm.nosync with an LLVM dialect callback.
130 return mlir::NamedAttribute("llvm.nosync",
131 mlir::UnitAttr::get(callOp->getContext()));
132 }
133};
134} // end anonymous namespace
135
136// NocallbackAttrDesc type provides get() method for computing
137// LLVM nocallback attribute for the given call.
138namespace {
139// Default implementation always returns LLVM nocallback.
140// It must be specialized for Fortran runtime functions that may call
141// user functions during their execution (e.g. defined IO, assignment).
142template <typename KEY, typename Enable = void>
143struct NocallbackAttrDesc {
144 static std::optional<mlir::NamedAttribute> get(fir::CallOp callOp) {
145 // TODO: replace llvm.nocallback with an LLVM dialect callback.
146 return mlir::NamedAttribute("llvm.nocallback",
147 mlir::UnitAttr::get(callOp->getContext()));
148 }
149};
150
151// Derived types IO may call back into a Fortran module.
152// This specialization is conservative for Input/OutputDerivedType,
153// and it might be improved by checking if the NonTbpDefinedIoTable
154// pointer argument is null.
155template <typename KEY>
156struct NocallbackAttrDesc<
157 KEY, std::enable_if_t<
158 IsAny<KEY, mkIOKey(OutputDerivedType), mkIOKey(InputDerivedType),
159 mkIOKey(OutputNamelist), mkIOKey(InputNamelist)>>> {
160 static std::optional<mlir::NamedAttribute> get(fir::CallOp) {
161 return std::nullopt;
162 }
163};
164} // end anonymous namespace
165
166namespace {
167// RuntimeFunction provides different callbacks that compute values
168// of fir.call attributes for a Fortran runtime function.
169struct RuntimeFunction {
170 using MemoryAttrGeneratorTy = mlir::LLVM::MemoryEffectsAttr (*)(fir::CallOp);
171 using NamedAttrGeneratorTy =
172 std::optional<mlir::NamedAttribute> (*)(fir::CallOp);
173 using Key = std::string_view;
174 constexpr operator Key() const { return key; }
175 Key key;
176 MemoryAttrGeneratorTy memoryAttrGenerator;
177 NamedAttrGeneratorTy nosyncAttrGenerator;
178 NamedAttrGeneratorTy nocallbackAttrGenerator;
179};
180
181// Helper type to create a RuntimeFunction descriptor given
182// the KEY and a function name.
183template <typename KEY>
184struct RuntimeFactory {
185 static constexpr RuntimeFunction create(const char name[]) {
186 // GCC 7 does not recognize this as a constant expression:
187 // ((const char *)RuntimeFunction<>::name) == nullptr
188 // This comparison comes from the basic_string_view(const char *)
189 // constructor. We have to use the other constructor
190 // that takes explicit length parameter.
191 return RuntimeFunction{
192 std::string_view{name, std::char_traits<char>::length(name)},
193 MemoryAttrDesc<KEY>::get, NosyncAttrDesc<KEY>::get,
194 NocallbackAttrDesc<KEY>::get};
195 }
196};
197} // end anonymous namespace
198
199#define KNOWN_IO_FUNC(X) RuntimeFactory<mkIOKey(X)>::create(mkIOKey(X)::name)
200#define KNOWN_RUNTIME_FUNC(X) \
201 RuntimeFactory<mkRTKey(X)>::create(mkRTKey(X)::name)
202
203// A table of RuntimeFunction descriptors for all recognized
204// Fortran runtime functions.
205static constexpr RuntimeFunction runtimeFuncsTable[] = {
206#include "flang/Optimizer/Transforms/RuntimeFunctions.inc"
207};
208
209static constexpr Fortran::common::StaticMultimapView<RuntimeFunction>
210 runtimeFuncs(runtimeFuncsTable);
211static_assert(runtimeFuncs.Verify() && "map must be sorted");
212
213// Set attributes for the given Fortran runtime call.
214// The symbolTable is used to cache the name lookups in the module.
215static void setRuntimeCallAttributes(fir::CallOp callOp,
216 mlir::SymbolTableCollection &symbolTable) {
217 auto iface = mlir::cast<mlir::CallOpInterface>(callOp.getOperation());
218 auto funcOp = mlir::dyn_cast_or_null<mlir::func::FuncOp>(
219 iface.resolveCallableInTable(&symbolTable));
220
221 if (!funcOp || !funcOp->hasAttrOfType<mlir::UnitAttr>(
222 fir::FIROpsDialect::getFirRuntimeAttrName()))
223 return;
224
225 llvm::StringRef name = funcOp.getName();
226 if (auto range = runtimeFuncs.equal_range(name);
227 range.first != range.second) {
228 // There should not be duplicate entries.
229 assert(range.first + 1 == range.second);
230 const RuntimeFunction &desc = *range.first;
231 LLVM_DEBUG(llvm::dbgs()
232 << "Identified runtime function call: " << desc.key << '\n');
233 if (mlir::LLVM::MemoryEffectsAttr memoryAttr =
234 desc.memoryAttrGenerator(callOp))
235 callOp->setAttr(fir::FIROpsDialect::getFirCallMemoryAttrName(),
236 memoryAttr);
237 if (auto attr = desc.nosyncAttrGenerator(callOp))
238 callOp->setAttr(attr->getName(), attr->getValue());
239 if (auto attr = desc.nocallbackAttrGenerator(callOp))
240 callOp->setAttr(attr->getName(), attr->getValue());
241 LLVM_DEBUG(llvm::dbgs() << "Operation with attrs: " << callOp << '\n');
242 }
243}
244
245void SetRuntimeCallAttributesPass::runOnOperation() {
246 mlir::func::FuncOp funcOp = getOperation();
247 // Exit early for declarations to skip the debug output for them.
248 if (funcOp.isDeclaration())
249 return;
250 LLVM_DEBUG(llvm::dbgs() << "=== Begin " DEBUG_TYPE " ===\n");
251 LLVM_DEBUG(llvm::dbgs() << "Func-name:" << funcOp.getSymName() << "\n");
252
253 mlir::SymbolTableCollection symbolTable;
254 funcOp.walk([&](fir::CallOp callOp) {
255 setRuntimeCallAttributes(callOp, symbolTable);
256 });
257 LLVM_DEBUG(llvm::dbgs() << "=== End " DEBUG_TYPE " ===\n");
258}
259

source code of flang/lib/Optimizer/Transforms/SetRuntimeCallAttributes.cpp