xref: /llvm-project/flang/lib/Optimizer/Transforms/AddAliasTags.cpp (revision 392651a7ec250b82cecabbe9c765c3e5d5677d75)
1 //===- AddAliasTags.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 /// Adds TBAA alias tags to fir loads and stores, based on information from
12 /// fir::AliasAnalysis. More are added later in CodeGen - see fir::TBAABuilder
13 //===----------------------------------------------------------------------===//
14 
15 #include "flang/Optimizer/Analysis/AliasAnalysis.h"
16 #include "flang/Optimizer/Analysis/TBAAForest.h"
17 #include "flang/Optimizer/Dialect/FIRDialect.h"
18 #include "flang/Optimizer/Dialect/FirAliasTagOpInterface.h"
19 #include "flang/Optimizer/Transforms/Passes.h"
20 #include "mlir/IR/Dominance.h"
21 #include "mlir/Pass/Pass.h"
22 #include "llvm/ADT/DenseMap.h"
23 #include "llvm/ADT/StringRef.h"
24 #include "llvm/ADT/Twine.h"
25 #include "llvm/Support/CommandLine.h"
26 #include "llvm/Support/Debug.h"
27 #include "llvm/Support/raw_ostream.h"
28 #include <optional>
29 
30 namespace fir {
31 #define GEN_PASS_DEF_ADDALIASTAGS
32 #include "flang/Optimizer/Transforms/Passes.h.inc"
33 } // namespace fir
34 
35 #define DEBUG_TYPE "fir-add-alias-tags"
36 
37 static llvm::cl::opt<bool>
38     enableDummyArgs("dummy-arg-tbaa", llvm::cl::init(true), llvm::cl::Hidden,
39                     llvm::cl::desc("Add TBAA tags to dummy arguments"));
40 static llvm::cl::opt<bool>
41     enableGlobals("globals-tbaa", llvm::cl::init(true), llvm::cl::Hidden,
42                   llvm::cl::desc("Add TBAA tags to global variables"));
43 static llvm::cl::opt<bool>
44     enableDirect("direct-tbaa", llvm::cl::init(true), llvm::cl::Hidden,
45                  llvm::cl::desc("Add TBAA tags to direct variables"));
46 // This is **known unsafe** (misscompare in spec2017/wrf_r). It should
47 // not be enabled by default.
48 // The code is kept so that these may be tried with new benchmarks to see if
49 // this is worth fixing in the future.
50 static llvm::cl::opt<bool> enableLocalAllocs(
51     "local-alloc-tbaa", llvm::cl::init(false), llvm::cl::Hidden,
52     llvm::cl::desc("Add TBAA tags to local allocations. UNSAFE."));
53 
54 namespace {
55 
56 /// Shared state per-module
57 class PassState {
58 public:
59   PassState(mlir::DominanceInfo &domInfo) : domInfo(domInfo) {}
60   /// memoised call to fir::AliasAnalysis::getSource
61   inline const fir::AliasAnalysis::Source &getSource(mlir::Value value) {
62     if (!analysisCache.contains(value))
63       analysisCache.insert(
64           {value, analysis.getSource(value, /*getInstantiationPoint=*/true)});
65     return analysisCache[value];
66   }
67 
68   /// get the per-function TBAATree for this function
69   inline const fir::TBAATree &getFuncTree(mlir::func::FuncOp func) {
70     return forrest[func];
71   }
72   inline const fir::TBAATree &getFuncTreeWithScope(mlir::func::FuncOp func,
73                                                    fir::DummyScopeOp scope) {
74     auto &scopeMap = scopeNames.at(func);
75     return forrest.getFuncTreeWithScope(func, scopeMap.lookup(scope));
76   }
77 
78   void processFunctionScopes(mlir::func::FuncOp func);
79   fir::DummyScopeOp getDeclarationScope(fir::DeclareOp declareOp);
80 
81 private:
82   mlir::DominanceInfo &domInfo;
83   fir::AliasAnalysis analysis;
84   llvm::DenseMap<mlir::Value, fir::AliasAnalysis::Source> analysisCache;
85   fir::TBAAForrest forrest;
86   // Unique names for fir.dummy_scope operations within
87   // the given function.
88   llvm::DenseMap<mlir::func::FuncOp,
89                  llvm::DenseMap<fir::DummyScopeOp, std::string>>
90       scopeNames;
91   // A map providing a vector of fir.dummy_scope operations
92   // for the given function. The vectors are sorted according
93   // to the dominance information.
94   llvm::DenseMap<mlir::func::FuncOp, llvm::SmallVector<fir::DummyScopeOp, 16>>
95       sortedScopeOperations;
96 };
97 
98 // Process fir.dummy_scope operations in the given func:
99 // sort them according to the dominance information, and
100 // associate a unique (within the current function) scope name
101 // with each of them.
102 void PassState::processFunctionScopes(mlir::func::FuncOp func) {
103   if (scopeNames.contains(func))
104     return;
105 
106   auto &scopeMap = scopeNames[func];
107   auto &scopeOps = sortedScopeOperations[func];
108   func.walk([&](fir::DummyScopeOp op) { scopeOps.push_back(op); });
109   llvm::stable_sort(scopeOps, [&](const fir::DummyScopeOp &op1,
110                                   const fir::DummyScopeOp &op2) {
111     return domInfo.properlyDominates(&*op1, &*op2);
112   });
113   unsigned scopeId = 0;
114   for (auto scope : scopeOps) {
115     if (scopeId != 0) {
116       std::string name = (llvm::Twine("Scope ") + llvm::Twine(scopeId)).str();
117       LLVM_DEBUG(llvm::dbgs() << "Creating scope '" << name << "':\n"
118                               << scope << "\n");
119       scopeMap.insert({scope, std::move(name)});
120     }
121     ++scopeId;
122   }
123 }
124 
125 // For the given fir.declare returns the dominating fir.dummy_scope
126 // operation.
127 fir::DummyScopeOp PassState::getDeclarationScope(fir::DeclareOp declareOp) {
128   auto func = declareOp->getParentOfType<mlir::func::FuncOp>();
129   assert(func && "fir.declare does not have parent func.func");
130   auto &scopeOps = sortedScopeOperations.at(func);
131   for (auto II = scopeOps.rbegin(), IE = scopeOps.rend(); II != IE; ++II) {
132     if (domInfo.dominates(&**II, &*declareOp))
133       return *II;
134   }
135   return nullptr;
136 }
137 
138 class AddAliasTagsPass : public fir::impl::AddAliasTagsBase<AddAliasTagsPass> {
139 public:
140   void runOnOperation() override;
141 
142 private:
143   /// The real workhorse of the pass. This is a runOnOperation() which
144   /// operates on fir::FirAliasTagOpInterface, using some extra state
145   void runOnAliasInterface(fir::FirAliasTagOpInterface op, PassState &state);
146 };
147 
148 } // namespace
149 
150 static fir::DeclareOp getDeclareOp(mlir::Value arg) {
151   if (auto declare =
152           mlir::dyn_cast_or_null<fir::DeclareOp>(arg.getDefiningOp()))
153     return declare;
154   for (mlir::Operation *use : arg.getUsers())
155     if (fir::DeclareOp declare = mlir::dyn_cast<fir::DeclareOp>(use))
156       return declare;
157   return nullptr;
158 }
159 
160 /// Get the name of a function argument using the "fir.bindc_name" attribute,
161 /// or ""
162 static std::string getFuncArgName(mlir::Value arg) {
163   // first try getting the name from the fir.declare
164   if (fir::DeclareOp declare = getDeclareOp(arg))
165     return declare.getUniqName().str();
166 
167   // get from attribute on function argument
168   // always succeeds because arg is a function argument
169   mlir::BlockArgument blockArg = mlir::cast<mlir::BlockArgument>(arg);
170   assert(blockArg.getOwner() && blockArg.getOwner()->isEntryBlock() &&
171          "arg is a function argument");
172   mlir::FunctionOpInterface func = mlir::dyn_cast<mlir::FunctionOpInterface>(
173       blockArg.getOwner()->getParentOp());
174   assert(func && "This is not a function argument");
175   mlir::StringAttr attr = func.getArgAttrOfType<mlir::StringAttr>(
176       blockArg.getArgNumber(), "fir.bindc_name");
177   if (!attr)
178     return "";
179   return attr.str();
180 }
181 
182 void AddAliasTagsPass::runOnAliasInterface(fir::FirAliasTagOpInterface op,
183                                            PassState &state) {
184   mlir::func::FuncOp func = op->getParentOfType<mlir::func::FuncOp>();
185   if (!func)
186     return;
187 
188   llvm::SmallVector<mlir::Value> accessedOperands = op.getAccessedOperands();
189   assert(accessedOperands.size() == 1 &&
190          "load and store only access one address");
191   mlir::Value memref = accessedOperands.front();
192 
193   // skip boxes. These get an "any descriptor access" tag in TBAABuilder
194   // (CodeGen). I didn't see any speedup from giving each box a separate TBAA
195   // type.
196   if (mlir::isa<fir::BaseBoxType>(fir::unwrapRefType(memref.getType())))
197     return;
198   LLVM_DEBUG(llvm::dbgs() << "Analysing " << op << "\n");
199 
200   const fir::AliasAnalysis::Source &source = state.getSource(memref);
201   if (source.isTargetOrPointer()) {
202     LLVM_DEBUG(llvm::dbgs().indent(2) << "Skipping TARGET/POINTER\n");
203     // These will get an "any data access" tag in TBAABuilder (CodeGen): causing
204     // them to "MayAlias" with all non-box accesses
205     return;
206   }
207 
208   // Process the scopes, if not processed yet.
209   state.processFunctionScopes(func);
210 
211   fir::DummyScopeOp scopeOp;
212   if (auto declOp = source.origin.instantiationPoint) {
213     // If the source is a dummy argument within some fir.dummy_scope,
214     // then find the corresponding innermost scope to be used for finding
215     // the right TBAA tree.
216     auto declareOp = mlir::dyn_cast<fir::DeclareOp>(declOp);
217     assert(declareOp && "Instantiation point must be fir.declare");
218     if (auto dummyScope = declareOp.getDummyScope())
219       scopeOp = mlir::cast<fir::DummyScopeOp>(dummyScope.getDefiningOp());
220     if (!scopeOp)
221       scopeOp = state.getDeclarationScope(declareOp);
222   }
223 
224   mlir::LLVM::TBAATagAttr tag;
225   // TBAA for dummy arguments
226   if (enableDummyArgs &&
227       source.kind == fir::AliasAnalysis::SourceKind::Argument) {
228     LLVM_DEBUG(llvm::dbgs().indent(2)
229                << "Found reference to dummy argument at " << *op << "\n");
230     std::string name = getFuncArgName(llvm::cast<mlir::Value>(source.origin.u));
231     if (!name.empty())
232       tag = state.getFuncTreeWithScope(func, scopeOp)
233                 .dummyArgDataTree.getTag(name);
234     else
235       LLVM_DEBUG(llvm::dbgs().indent(2)
236                  << "WARN: couldn't find a name for dummy argument " << *op
237                  << "\n");
238 
239     // TBAA for global variables
240   } else if (enableGlobals &&
241              source.kind == fir::AliasAnalysis::SourceKind::Global &&
242              !source.isBoxData()) {
243     mlir::SymbolRefAttr glbl = llvm::cast<mlir::SymbolRefAttr>(source.origin.u);
244     const char *name = glbl.getRootReference().data();
245     LLVM_DEBUG(llvm::dbgs().indent(2) << "Found reference to global " << name
246                                       << " at " << *op << "\n");
247     tag = state.getFuncTreeWithScope(func, scopeOp).globalDataTree.getTag(name);
248 
249     // TBAA for SourceKind::Direct
250   } else if (enableDirect &&
251              source.kind == fir::AliasAnalysis::SourceKind::Global &&
252              source.isBoxData()) {
253     if (auto glbl = llvm::dyn_cast<mlir::SymbolRefAttr>(source.origin.u)) {
254       const char *name = glbl.getRootReference().data();
255       LLVM_DEBUG(llvm::dbgs().indent(2) << "Found reference to direct " << name
256                                         << " at " << *op << "\n");
257       tag =
258           state.getFuncTreeWithScope(func, scopeOp).directDataTree.getTag(name);
259     } else {
260       // SourceKind::Direct is likely to be extended to cases which are not a
261       // SymbolRefAttr in the future
262       LLVM_DEBUG(llvm::dbgs().indent(2) << "Can't get name for direct "
263                                         << source << " at " << *op << "\n");
264     }
265 
266     // TBAA for local allocations
267   } else if (enableLocalAllocs &&
268              source.kind == fir::AliasAnalysis::SourceKind::Allocate) {
269     std::optional<llvm::StringRef> name;
270     mlir::Operation *sourceOp =
271         llvm::cast<mlir::Value>(source.origin.u).getDefiningOp();
272     if (auto alloc = mlir::dyn_cast_or_null<fir::AllocaOp>(sourceOp))
273       name = alloc.getUniqName();
274     else if (auto alloc = mlir::dyn_cast_or_null<fir::AllocMemOp>(sourceOp))
275       name = alloc.getUniqName();
276     if (name) {
277       LLVM_DEBUG(llvm::dbgs().indent(2) << "Found reference to allocation "
278                                         << name << " at " << *op << "\n");
279       tag = state.getFuncTreeWithScope(func, scopeOp)
280                 .allocatedDataTree.getTag(*name);
281     } else {
282       LLVM_DEBUG(llvm::dbgs().indent(2)
283                  << "WARN: couldn't find a name for allocation " << *op
284                  << "\n");
285     }
286   } else {
287     if (source.kind != fir::AliasAnalysis::SourceKind::Argument &&
288         source.kind != fir::AliasAnalysis::SourceKind::Allocate &&
289         source.kind != fir::AliasAnalysis::SourceKind::Global)
290       LLVM_DEBUG(llvm::dbgs().indent(2)
291                  << "WARN: unsupported value: " << source << "\n");
292   }
293 
294   if (tag)
295     op.setTBAATags(mlir::ArrayAttr::get(&getContext(), tag));
296 }
297 
298 void AddAliasTagsPass::runOnOperation() {
299   LLVM_DEBUG(llvm::dbgs() << "=== Begin " DEBUG_TYPE " ===\n");
300 
301   // MLIR forbids storing state in a pass because different instances might be
302   // used in different threads.
303   // Instead this pass stores state per mlir::ModuleOp (which is what MLIR
304   // thinks the pass operates on), then the real work of the pass is done in
305   // runOnAliasInterface
306   auto &domInfo = getAnalysis<mlir::DominanceInfo>();
307   PassState state(domInfo);
308 
309   mlir::ModuleOp mod = getOperation();
310   mod.walk(
311       [&](fir::FirAliasTagOpInterface op) { runOnAliasInterface(op, state); });
312 
313   LLVM_DEBUG(llvm::dbgs() << "=== End " DEBUG_TYPE " ===\n");
314 }
315