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