1 //===- AffineScalarReplacement.cpp - Affine scalar replacement pass -------===// 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 // This file implements a pass to forward affine memref stores to loads, thereby 10 // potentially getting rid of intermediate memrefs entirely. It also removes 11 // redundant loads. 12 // TODO: In the future, similar techniques could be used to eliminate 13 // dead memref store's and perform more complex forwarding when support for 14 // SSA scalars live out of 'affine.for'/'affine.if' statements is available. 15 //===----------------------------------------------------------------------===// 16 17 #include "PassDetail.h" 18 #include "mlir/Analysis/AffineAnalysis.h" 19 #include "mlir/Analysis/Utils.h" 20 #include "mlir/Dialect/Affine/IR/AffineOps.h" 21 #include "mlir/Dialect/Affine/Passes.h" 22 #include "mlir/Dialect/MemRef/IR/MemRef.h" 23 #include "mlir/Dialect/StandardOps/IR/Ops.h" 24 #include "mlir/IR/Dominance.h" 25 #include "mlir/Support/LogicalResult.h" 26 #include "llvm/ADT/SmallPtrSet.h" 27 #include <algorithm> 28 29 #define DEBUG_TYPE "memref-dataflow-opt" 30 31 using namespace mlir; 32 33 namespace { 34 // The store to load forwarding and load CSE rely on three conditions: 35 // 36 // 1) store/load and load need to have mathematically equivalent affine access 37 // functions (checked after full composition of load/store operands); this 38 // implies that they access the same single memref element for all iterations of 39 // the common surrounding loop, 40 // 41 // 2) the store/load op should dominate the load op, 42 // 43 // 3) among all op's that satisfy both (1) and (2), for store to load 44 // forwarding, the one that does not dominate any store op that has a 45 // dependence into the load, is provably the last writer to the particular 46 // memref location being loaded at the load op, and its store value can be 47 // forwarded to the load; for load CSE, any op that does not dominate any store 48 // op that have a dependence into the load can be forwarded and the first one 49 // found is chosen. Note that the only dependences that are to be considered are 50 // those that are satisfied at the block* of the innermost common surrounding 51 // loop of the <store/load, load> being considered. 52 // 53 // (* A dependence being satisfied at a block: a dependence that is satisfied by 54 // virtue of the destination operation appearing textually / lexically after 55 // the source operation within the body of a 'affine.for' operation; thus, a 56 // dependence is always either satisfied by a loop or by a block). 57 // 58 // The above conditions are simple to check, sufficient, and powerful for most 59 // cases in practice - they are sufficient, but not necessary --- since they 60 // don't reason about loops that are guaranteed to execute at least once or 61 // multiple sources to forward from. 62 // 63 // TODO: more forwarding can be done when support for 64 // loop/conditional live-out SSA values is available. 65 // TODO: do general dead store elimination for memref's. This pass 66 // currently only eliminates the stores only if no other loads/uses (other 67 // than dealloc) remain. 68 // 69 struct AffineScalarReplacement 70 : public AffineScalarReplacementBase<AffineScalarReplacement> { 71 void runOnFunction() override; 72 73 LogicalResult forwardStoreToLoad(AffineReadOpInterface loadOp); 74 void loadCSE(AffineReadOpInterface loadOp); 75 76 // A list of memref's that are potentially dead / could be eliminated. 77 SmallPtrSet<Value, 4> memrefsToErase; 78 // Load ops whose results were replaced by those forwarded from stores 79 // dominating stores or loads.. 80 SmallVector<Operation *, 8> loadOpsToErase; 81 82 DominanceInfo *domInfo = nullptr; 83 }; 84 85 } // end anonymous namespace 86 87 /// Creates a pass to perform optimizations relying on memref dataflow such as 88 /// store to load forwarding, elimination of dead stores, and dead allocs. 89 std::unique_ptr<OperationPass<FuncOp>> 90 mlir::createAffineScalarReplacementPass() { 91 return std::make_unique<AffineScalarReplacement>(); 92 } 93 94 // Check if the store may be reaching the load. 95 static bool storeMayReachLoad(Operation *storeOp, Operation *loadOp, 96 unsigned minSurroundingLoops) { 97 MemRefAccess srcAccess(storeOp); 98 MemRefAccess destAccess(loadOp); 99 FlatAffineConstraints dependenceConstraints; 100 unsigned nsLoops = getNumCommonSurroundingLoops(*loadOp, *storeOp); 101 unsigned d; 102 // Dependences at loop depth <= minSurroundingLoops do NOT matter. 103 for (d = nsLoops + 1; d > minSurroundingLoops; d--) { 104 DependenceResult result = checkMemrefAccessDependence( 105 srcAccess, destAccess, d, &dependenceConstraints, 106 /*dependenceComponents=*/nullptr); 107 if (hasDependence(result)) 108 break; 109 } 110 if (d <= minSurroundingLoops) 111 return false; 112 113 return true; 114 } 115 116 // This is a straightforward implementation not optimized for speed. Optimize 117 // if needed. 118 LogicalResult 119 AffineScalarReplacement::forwardStoreToLoad(AffineReadOpInterface loadOp) { 120 // First pass over the use list to get the minimum number of surrounding 121 // loops common between the load op and the store op, with min taken across 122 // all store ops. 123 SmallVector<Operation *, 8> storeOps; 124 unsigned minSurroundingLoops = getNestingDepth(loadOp); 125 for (auto *user : loadOp.getMemRef().getUsers()) { 126 auto storeOp = dyn_cast<AffineWriteOpInterface>(user); 127 if (!storeOp) 128 continue; 129 unsigned nsLoops = getNumCommonSurroundingLoops(*loadOp, *storeOp); 130 minSurroundingLoops = std::min(nsLoops, minSurroundingLoops); 131 storeOps.push_back(storeOp); 132 } 133 134 // The list of store op candidates for forwarding that satisfy conditions 135 // (1) and (2) above - they will be filtered later when checking (3). 136 SmallVector<Operation *, 8> fwdingCandidates; 137 138 // Store ops that have a dependence into the load (even if they aren't 139 // forwarding candidates). Each forwarding candidate will be checked for a 140 // dominance on these. 'fwdingCandidates' are a subset of depSrcStores. 141 SmallVector<Operation *, 8> depSrcStores; 142 143 for (auto *storeOp : storeOps) { 144 if (!storeMayReachLoad(storeOp, loadOp, minSurroundingLoops)) 145 continue; 146 147 // Stores that *may* be reaching the load. 148 depSrcStores.push_back(storeOp); 149 150 // 1. Check if the store and the load have mathematically equivalent 151 // affine access functions; this implies that they statically refer to the 152 // same single memref element. As an example this filters out cases like: 153 // store %A[%i0 + 1] 154 // load %A[%i0] 155 // store %A[%M] 156 // load %A[%N] 157 // Use the AffineValueMap difference based memref access equality checking. 158 MemRefAccess srcAccess(storeOp); 159 MemRefAccess destAccess(loadOp); 160 if (srcAccess != destAccess) 161 continue; 162 163 // 2. The store has to dominate the load op to be candidate. 164 if (!domInfo->dominates(storeOp, loadOp)) 165 continue; 166 167 // We now have a candidate for forwarding. 168 fwdingCandidates.push_back(storeOp); 169 } 170 171 // 3. Of all the store ops that meet the above criteria, the store op 172 // that does not dominate any of the ops in 'depSrcStores' (if such exists) 173 // will not have any of those latter ops on its paths to `loadOp`. It would 174 // thus be the unique store providing the value to the load. This condition is 175 // however conservative for eg: 176 // 177 // for ... { 178 // store 179 // load 180 // store 181 // load 182 // } 183 // 184 Operation *lastWriteStoreOp = nullptr; 185 for (auto *storeOp : fwdingCandidates) { 186 if (llvm::all_of(depSrcStores, [&](Operation *depStore) { 187 return !domInfo->properlyDominates(storeOp, depStore); 188 })) { 189 lastWriteStoreOp = storeOp; 190 break; 191 } 192 } 193 if (!lastWriteStoreOp) 194 return failure(); 195 196 // Perform the actual store to load forwarding. 197 Value storeVal = 198 cast<AffineWriteOpInterface>(lastWriteStoreOp).getValueToStore(); 199 // Check if 2 values have the same shape. This is needed for affine vector 200 // loads and stores. 201 if (storeVal.getType() != loadOp.getValue().getType()) 202 return failure(); 203 loadOp.getValue().replaceAllUsesWith(storeVal); 204 // Record the memref for a later sweep to optimize away. 205 memrefsToErase.insert(loadOp.getMemRef()); 206 // Record this to erase later. 207 loadOpsToErase.push_back(loadOp); 208 return success(); 209 } 210 211 // The load to load forwarding / redundant load elimination is similar to the 212 // store to load forwarding. 213 // loadA will be be replaced with loadB if: 214 // 1) loadA and loadB have mathematically equivalent affine access functions. 215 // 2) loadB dominates loadA. 216 // 3) loadB does not dominate any of the store ops that have a dependence into 217 // loadA. 218 void AffineScalarReplacement::loadCSE(AffineReadOpInterface loadOp) { 219 // The list of load op candidates for forwarding that satisfy conditions 220 // (1) and (2) above - they will be filtered later when checking (3). 221 SmallVector<Operation *, 8> fwdingCandidates; 222 SmallVector<Operation *, 8> storeOps; 223 unsigned minSurroundingLoops = getNestingDepth(loadOp); 224 MemRefAccess memRefAccess(loadOp); 225 // First pass over the use list to get 1) the minimum number of surrounding 226 // loops common between the load op and an load op candidate, with min taken 227 // across all load op candidates; 2) load op candidates; 3) store ops. 228 // We take min across all load op candidates instead of all load ops to make 229 // sure later dependence check is performed at loop depths that do matter. 230 for (auto *user : loadOp.getMemRef().getUsers()) { 231 if (auto storeOp = dyn_cast<AffineWriteOpInterface>(user)) { 232 storeOps.push_back(storeOp); 233 } else if (auto aLoadOp = dyn_cast<AffineReadOpInterface>(user)) { 234 MemRefAccess otherMemRefAccess(aLoadOp); 235 // No need to consider Load ops that have been replaced in previous store 236 // to load forwarding or loadCSE. If loadA or storeA can be forwarded to 237 // loadB, then loadA or storeA can be forwarded to loadC iff loadB can be 238 // forwarded to loadC. 239 // If loadB is visited before loadC and replace with loadA, we do not put 240 // loadB in candidates list, only loadA. If loadC is visited before loadB, 241 // loadC may be replaced with loadB, which will be replaced with loadA 242 // later. 243 if (aLoadOp != loadOp && !llvm::is_contained(loadOpsToErase, aLoadOp) && 244 memRefAccess == otherMemRefAccess && 245 domInfo->dominates(aLoadOp, loadOp)) { 246 fwdingCandidates.push_back(aLoadOp); 247 unsigned nsLoops = getNumCommonSurroundingLoops(*loadOp, *aLoadOp); 248 minSurroundingLoops = std::min(nsLoops, minSurroundingLoops); 249 } 250 } 251 } 252 253 // No forwarding candidate. 254 if (fwdingCandidates.empty()) 255 return; 256 257 // Store ops that have a dependence into the load. 258 SmallVector<Operation *, 8> depSrcStores; 259 260 for (auto *storeOp : storeOps) { 261 if (!storeMayReachLoad(storeOp, loadOp, minSurroundingLoops)) 262 continue; 263 264 // Stores that *may* be reaching the load. 265 depSrcStores.push_back(storeOp); 266 } 267 268 // 3. Of all the load op's that meet the above criteria, return the first load 269 // found that does not dominate any op in 'depSrcStores' and has the same 270 // shape as the load to be replaced (if one exists). The shape check is needed 271 // for affine vector loads. 272 Operation *firstLoadOp = nullptr; 273 Value oldVal = loadOp.getValue(); 274 for (auto *loadOp : fwdingCandidates) { 275 if (llvm::all_of(depSrcStores, 276 [&](Operation *depStore) { 277 return !domInfo->properlyDominates(loadOp, depStore); 278 }) && 279 cast<AffineReadOpInterface>(loadOp).getValue().getType() == 280 oldVal.getType()) { 281 firstLoadOp = loadOp; 282 break; 283 } 284 } 285 if (!firstLoadOp) 286 return; 287 288 // Perform the actual load to load forwarding. 289 Value loadVal = cast<AffineReadOpInterface>(firstLoadOp).getValue(); 290 loadOp.getValue().replaceAllUsesWith(loadVal); 291 // Record this to erase later. 292 loadOpsToErase.push_back(loadOp); 293 } 294 295 void AffineScalarReplacement::runOnFunction() { 296 // Only supports single block functions at the moment. 297 FuncOp f = getFunction(); 298 if (!llvm::hasSingleElement(f)) { 299 markAllAnalysesPreserved(); 300 return; 301 } 302 303 domInfo = &getAnalysis<DominanceInfo>(); 304 305 loadOpsToErase.clear(); 306 memrefsToErase.clear(); 307 308 // Walk all load's and perform store to load forwarding and loadCSE. 309 f.walk([&](AffineReadOpInterface loadOp) { 310 // Do store to load forwarding first, if no success, try loadCSE. 311 if (failed(forwardStoreToLoad(loadOp))) 312 loadCSE(loadOp); 313 }); 314 315 // Erase all load op's whose results were replaced with store or load fwd'ed 316 // ones. 317 for (auto *loadOp : loadOpsToErase) 318 loadOp->erase(); 319 320 // Check if the store fwd'ed memrefs are now left with only stores and can 321 // thus be completely deleted. Note: the canonicalize pass should be able 322 // to do this as well, but we'll do it here since we collected these anyway. 323 for (auto memref : memrefsToErase) { 324 // If the memref hasn't been alloc'ed in this function, skip. 325 Operation *defOp = memref.getDefiningOp(); 326 if (!defOp || !isa<memref::AllocOp>(defOp)) 327 // TODO: if the memref was returned by a 'call' operation, we 328 // could still erase it if the call had no side-effects. 329 continue; 330 if (llvm::any_of(memref.getUsers(), [&](Operation *ownerOp) { 331 return !isa<AffineWriteOpInterface, memref::DeallocOp>(ownerOp); 332 })) 333 continue; 334 335 // Erase all stores, the dealloc, and the alloc on the memref. 336 for (auto *user : llvm::make_early_inc_range(memref.getUsers())) 337 user->erase(); 338 defOp->erase(); 339 } 340 } 341