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