MLIR 24.0.0git
Hoisting.cpp
Go to the documentation of this file.
1//===- Hoisting.cpp - Linalg hoisting transformations ---------------------===//
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 functions concerned with hoisting invariant operations
10// in the context of Linalg transformations.
11//
12//===----------------------------------------------------------------------===//
13
26#include "mlir/IR/Dominance.h"
28#include "llvm/Support/Debug.h"
29
30using llvm::dbgs;
31
32#define DEBUG_TYPE "linalg-hoisting"
33
34#define DBGS() (dbgs() << '[' << DEBUG_TYPE << "] ")
35
36using namespace mlir;
37using namespace mlir::linalg;
38
39/// Replace `loop` with a new loop that has a different init operand at
40/// position `index`. The body of this loop is moved over to the new loop.
41///
42/// `newInitOperands` specifies the replacement "init" operands.
43/// `newYieldValue` is the replacement yield value of the loop at position
44/// `index`.
45static scf::ForOp replaceWithDifferentYield(RewriterBase &rewriter,
46 scf::ForOp loop,
47 Value newInitOperand,
48 unsigned index,
49 Value newYieldValue) {
50 OpBuilder::InsertionGuard g(rewriter);
51 rewriter.setInsertionPoint(loop.getOperation());
52 auto inits = llvm::to_vector(loop.getInits());
53
54 // Replace the init value with the new operand.
55 assert(index < inits.size());
56 inits[index] = newInitOperand;
57
58 scf::ForOp newLoop = scf::ForOp::create(
59 rewriter, loop.getLoc(), loop.getLowerBound(), loop.getUpperBound(),
60 loop.getStep(), inits, [](OpBuilder &, Location, Value, ValueRange) {},
61 loop.getUnsignedCmp());
62
63 // Generate the new yield with the replaced operand.
64 auto yieldOp = cast<scf::YieldOp>(loop.getBody()->getTerminator());
65 yieldOp.setOperand(index, newYieldValue);
66
67 // Move the loop body to the new op.
68 rewriter.mergeBlocks(loop.getBody(), newLoop.getBody(),
69 newLoop.getBody()->getArguments());
70
71 // Replace the old loop.
72 rewriter.replaceOp(loop.getOperation(), newLoop->getResults());
73 return newLoop;
74}
75
76// Hoist out a pair of corresponding vector.extract+vector.broadcast
77// operations. This function transforms a loop like this:
78// %res = scf.for _ = _ to _ step _ iter_args(%iarg = %v) -> (t1) {
79// %e = vector.extract %iarg : t1 to t2
80// %u = "some_use"(%e) : (t2) -> t2
81// %b = vector.broadcast %u : t2 to t1
82// scf.yield %b : t1
83// }
84// into the following:
85// %e = vector.extract %v: t1 to t2
86// %res' = scf.for _ = _ to _ step _ iter_args(%iarg = %e) -> (t2) {
87// %u' = "some_use"(%iarg) : (t2) -> t2
88// scf.yield %u' : t2
89// }
90// %res = vector.broadcast %res' : t2 to t1
92 Operation *root) {
93 bool changed = true;
94 while (changed) {
95 changed = false;
96 // First move loop invariant ops outside of their loop. This needs to be
97 // done before as we cannot move ops without interrupting the function walk.
98 root->walk(
99 [&](LoopLikeOpInterface loopLike) { moveLoopInvariantCode(loopLike); });
100
101 root->walk([&](vector::ExtractOp extractOp) {
102 LLVM_DEBUG(DBGS() << "Candidate for hoisting: "
103 << *extractOp.getOperation() << "\n");
104
105 auto loop = dyn_cast<scf::ForOp>(extractOp->getParentOp());
106 if (!loop)
107 return WalkResult::advance();
108
109 // Check that the vector to extract from is a BlockArgument.
110 auto blockArg = dyn_cast<BlockArgument>(extractOp.getSource());
111 if (!blockArg)
112 return WalkResult::advance();
113
114 // Check that the blockArg is an iter_arg of the loop.
115 OpOperand *initArg = loop.getTiedLoopInit(blockArg);
116 if (!initArg)
117 return WalkResult::advance();
118
119 // If the iter_arg does not have only one use, it won't be possible to
120 // hoist the extractOp out.
121 if (!blockArg.hasOneUse())
122 return WalkResult::advance();
123
124 unsigned index = blockArg.getArgNumber() - loop.getNumInductionVars();
125
126 // Check that the loop yields a broadcast that has just one use.
127 Operation *yieldedVal =
128 loop.getTiedLoopYieldedValue(blockArg)->get().getDefiningOp();
129 auto broadcast = dyn_cast<vector::BroadcastOp>(yieldedVal);
130 if (!broadcast || !broadcast.getResult().hasOneUse())
131 return WalkResult::advance();
132
133 LLVM_DEBUG(DBGS() << "Candidate broadcast: " << broadcast << "\n");
134
135 Type broadcastInputType = broadcast.getSourceType();
136 if (broadcastInputType != extractOp.getType())
137 return WalkResult::advance();
138
139 // The position of the extract must be defined outside of the loop if
140 // it is dynamic.
141 for (auto operand : extractOp.getDynamicPosition())
142 if (!loop.isDefinedOutsideOfLoop(operand))
143 return WalkResult::advance();
144
145 rewriter.modifyOpInPlace(broadcast, [&] {
146 extractOp.getSourceMutable().assign(initArg->get());
147 });
148 loop.moveOutOfLoop(extractOp);
149 rewriter.moveOpAfter(broadcast, loop);
150
151 scf::ForOp newLoop = replaceWithDifferentYield(
152 rewriter, loop, extractOp.getResult(), index, broadcast.getSource());
153
154 LLVM_DEBUG(DBGS() << "New loop: " << newLoop << "\n");
155
156 rewriter.replaceAllUsesWith(newLoop.getResult(index), broadcast);
157 rewriter.modifyOpInPlace(
158 broadcast, [&] { broadcast.setOperand(newLoop.getResult(index)); });
159
160 changed = true;
161 return WalkResult::interrupt();
162 });
163 }
164}
165
167 bool verifyNonZeroTrip) {
168 bool changed = true;
169 while (changed) {
170 changed = false;
171 // First move loop invariant ops outside of their loop. This needs to be
172 // done before as we cannot move ops without interrupting the function walk.
173 root->walk(
174 [&](LoopLikeOpInterface loopLike) { moveLoopInvariantCode(loopLike); });
175
176 // Find all loops that are certain to have non zero trip count. Any loops
177 // that are not part of this set cannot be hoisted from, since hoisting from
178 // a potentially zero trip count loop may cause a vector transfer to be
179 // executed when it shouldn't be.
180 llvm::DenseSet<LoopLikeOpInterface> definiteNonZeroTripCountLoops;
181 if (verifyNonZeroTrip) {
182 root->walk([&](LoopLikeOpInterface loopLike) {
183 std::optional<SmallVector<OpFoldResult>> lbs =
184 loopLike.getLoopLowerBounds();
185 std::optional<SmallVector<OpFoldResult>> ubs =
186 loopLike.getLoopUpperBounds();
187 // If loop bounds cannot be found, assume possibly zero trip count.
188 if (!lbs || !ubs)
189 return;
190
191 // Otherwise, use ValueBounds to find the maximum lower bound and
192 // minimum upper bound. If the bounds are found, and maxLb is less
193 // than the minUb, then the loop will not have zero trip count.
194 for (auto [lb, ub] : llvm::zip_equal(lbs.value(), ubs.value())) {
195 FailureOr<int64_t> maxLb =
198 /*stopCondition=*/nullptr,
199 ValueBoundsOptions{/*closedUB=*/true});
200 if (failed(maxLb))
201 return;
202 FailureOr<int64_t> minUb =
205 if (failed(minUb))
206 return;
207 if (minUb.value() <= maxLb.value())
208 return;
209 definiteNonZeroTripCountLoops.insert(loopLike);
210 }
211 });
212 }
213
214 root->walk([&](vector::TransferReadOp transferRead) {
215 if (!isa<MemRefType>(transferRead.getShapedType()))
216 return WalkResult::advance();
217
218 LLVM_DEBUG(DBGS() << "Candidate for hoisting: "
219 << *transferRead.getOperation() << "\n");
220 auto loop = dyn_cast<LoopLikeOpInterface>(transferRead->getParentOp());
221 LLVM_DEBUG(DBGS() << "Parent op: " << *transferRead->getParentOp()
222 << "\n");
223 if (!isa_and_nonnull<scf::ForOp, affine::AffineForOp>(loop))
224 return WalkResult::advance();
225
226 if (verifyNonZeroTrip && !definiteNonZeroTripCountLoops.contains(loop)) {
227 LLVM_DEBUG(DBGS() << "Loop may have zero trip count: " << *loop
228 << "\n");
229 return WalkResult::advance();
230 }
231
232 LLVM_DEBUG(DBGS() << "Candidate read: " << *transferRead.getOperation()
233 << "\n");
234
235 SetVector<Operation *> forwardSlice;
236 getForwardSlice(transferRead.getOperation(), &forwardSlice);
237
238 // Look for the last TransferWriteOp in the forwardSlice of
239 // `transferRead` that operates on the same memref.
240 vector::TransferWriteOp transferWrite;
241 for (auto *sliceOp : llvm::reverse(forwardSlice)) {
242 auto candidateWrite = dyn_cast<vector::TransferWriteOp>(sliceOp);
243 if (!candidateWrite ||
244 candidateWrite.getBase() != transferRead.getBase())
245 continue;
246 transferWrite = candidateWrite;
247 }
248
249 // All operands of the TransferRead must be defined outside of the loop.
250 for (auto operand : transferRead.getOperands())
251 if (!loop.isDefinedOutsideOfLoop(operand))
252 return WalkResult::advance();
253
254 // Only hoist transfer_read / transfer_write pairs and singleton
255 // transfer_reads for now.
256 if (!transferWrite) {
257 // Hoisting a lone read is safe as long as no aliasing write remains in
258 // the loop; other reads never conflict.
259 if (memref::hasNoAliasingAccessInScope(transferRead.getBase(), loop,
260 /*excludedOps=*/{},
261 /*readsAreSafe=*/true))
262 loop.moveOutOfLoop(transferRead);
263 return WalkResult::advance();
264 }
265
266 LLVM_DEBUG(DBGS() << "Candidate: " << *transferWrite.getOperation()
267 << "\n");
268
269 // Approximate aliasing by checking that:
270 // 1. indices, vector type and permutation map are the same (i.e., the
271 // transfer_read/transfer_write ops are matching),
272 // 2. source operands for transfer.{read|write} do not originate from
273 // nor have users that are Ops implementing ViewLikeOpInterface.
274 // 3. no other operations in the loop access the same memref except
275 // for transfer_read/transfer_write accessing statically disjoint
276 // slices.
277
278 // Check 1.
279 if (transferRead.getIndices() != transferWrite.getIndices() ||
280 transferRead.getVectorType() != transferWrite.getVectorType() ||
281 transferRead.getPermutationMap() != transferWrite.getPermutationMap())
282 return WalkResult::advance();
283
284 // Check 2. Note, since both xfer Ops share the source, we only need to
285 // look at one of them.
286 auto base = transferRead.getBase();
287 // Whether hoisting is safe despite a view base. Computed lazily and
288 // cached since it is only consulted when a view is present.
289 std::optional<bool> viewAliasingIsSafeCache;
290 auto viewAliasingIsSafe = [&]() {
291 if (!viewAliasingIsSafeCache) {
292 Operation *hoistedPair[] = {transferRead, transferWrite};
293 viewAliasingIsSafeCache =
294 memref::hasNoAliasingAccessInScope(base, loop, hoistedPair);
295 }
296 return *viewAliasingIsSafeCache;
297 };
298 auto *source = base.getDefiningOp();
299 if (source) {
300 // NOTE: We treat `memref.assume_alignment` as a special case.
301 //
302 // The idea is that it is safe to look past AssumeAlignmemtOp (i.e.
303 // MemRef _before_ alignment) iff:
304 // 1. It has exactly two uses (these have to be the xfer Ops
305 // being looked at).
306 // 2. The original MemRef has only one use (i.e.
307 // AssumeAlignmentOp).
308 //
309 // Relaxing these conditions will most likely require proper alias
310 // analysis.
311 if (auto assume = dyn_cast<memref::AssumeAlignmentOp>(source)) {
312 Value memPreAlignment = assume.getMemref();
313 auto numInLoopUses =
314 llvm::count_if(base.getUses(), [&loop](OpOperand &use) {
315 return loop->isAncestor(use.getOwner());
316 });
317
318 if (numInLoopUses && memPreAlignment.hasOneUse())
319 source = memPreAlignment.getDefiningOp();
320 }
321 if (isa_and_nonnull<ViewLikeOpInterface>(source) &&
322 !viewAliasingIsSafe())
323 return WalkResult::advance();
324 }
325
326 if (llvm::any_of(base.getUsers(), llvm::IsaPred<ViewLikeOpInterface>) &&
327 !viewAliasingIsSafe())
328 return WalkResult::advance();
329
330 // Check 3.
331 // TODO: may want to memoize this information for performance but it
332 // likely gets invalidated often.
333 DominanceInfo dom(loop);
334 if (!dom.properlyDominates(transferRead.getOperation(), transferWrite))
335 return WalkResult::advance();
336 for (auto &use : transferRead.getBase().getUses()) {
337 if (!loop->isAncestor(use.getOwner()))
338 continue;
339 if (use.getOwner() == transferRead.getOperation() ||
340 use.getOwner() == transferWrite.getOperation())
341 continue;
342 if (auto transferWriteUse =
343 dyn_cast<vector::TransferWriteOp>(use.getOwner())) {
345 cast<VectorTransferOpInterface>(*transferWrite),
346 cast<VectorTransferOpInterface>(*transferWriteUse),
347 /*testDynamicValueUsingBounds=*/true))
348 return WalkResult::advance();
349 } else if (auto transferReadUse =
350 dyn_cast<vector::TransferReadOp>(use.getOwner())) {
352 cast<VectorTransferOpInterface>(*transferWrite),
353 cast<VectorTransferOpInterface>(*transferReadUse),
354 /*testDynamicValueUsingBounds=*/true))
355 return WalkResult::advance();
356 } else {
357 // Unknown use, we cannot prove that it doesn't alias with the
358 // transferRead/transferWrite operations.
359 return WalkResult::advance();
360 }
361 }
362
363 // Hoist read before.
364 loop.moveOutOfLoop(transferRead);
365
366 // Hoist write after.
367 transferWrite->moveAfter(loop);
368
369 // Rewrite `loop` with new yields by cloning and erase the original
370 // loop.
371 IRRewriter rewriter(transferRead.getContext());
372 NewYieldValuesFn yieldFn = [&](OpBuilder &b, Location loc,
373 ArrayRef<BlockArgument> newBBArgs) {
374 return SmallVector<Value>{transferWrite.getVector()};
375 };
376
377 auto maybeNewLoop = loop.replaceWithAdditionalYields(
378 rewriter, transferRead.getVector(),
379 /*replaceInitOperandUsesInLoop=*/true, yieldFn);
380 if (failed(maybeNewLoop))
381 return WalkResult::interrupt();
382
383 transferWrite.getValueToStoreMutable().assign(
384 maybeNewLoop->getOperation()->getResults().back());
385 changed = true;
386 // Need to interrupt and restart because erasing the loop messes up
387 // the walk.
388 return WalkResult::interrupt();
389 });
390 }
391}
static scf::ForOp replaceWithDifferentYield(RewriterBase &rewriter, scf::ForOp loop, Value newInitOperand, unsigned index, Value newYieldValue)
Replace loop with a new loop that has a different init operand at position index.
Definition Hoisting.cpp:45
#define DBGS()
Definition Hoisting.cpp:34
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
static Value broadcast(Location loc, Value toBroadcast, unsigned numElements, const TypeConverter &typeConverter, ConversionPatternRewriter &rewriter)
Broadcasts the value to vector with numElements number of elements.
A class for computing basic dominance information.
Definition Dominance.h:143
bool properlyDominates(Operation *a, Operation *b, bool enclosingOpOk=true) const
Return true if operation A properly dominates operation B, i.e.
IRValueT get() const
Return the current value being used by this operand.
This class coordinates rewriting a piece of IR outside of a pattern rewrite, providing a way to keep ...
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
RAII guard to reset the insertion point of the builder when destroyed.
Definition Builders.h:351
This class helps build Operations.
Definition Builders.h:210
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:401
This class represents an operand of an operation.
Definition Value.h:254
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
std::enable_if_t< llvm::function_traits< std::decay_t< FnT > >::num_args==1, RetT > walk(FnT &&callback)
Walk the operation by calling the callback for each nested operation (including this one),...
Definition Operation.h:849
This class coordinates the application of a rewrite on a set of IR, providing a way for clients to tr...
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
void mergeBlocks(Block *source, Block *dest, ValueRange argValues={})
Inline the operations of block 'source' into the end of block 'dest'.
void modifyOpInPlace(Operation *root, CallableT &&callable)
This method is a utility wrapper around an in-place modification of an operation.
void moveOpAfter(Operation *op, Operation *existingOp)
Unlink this operation from its current block and insert it right after existingOp which may be in the...
virtual void replaceAllUsesWith(Value from, Value to)
Find uses of from and replace them with to.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
static FailureOr< int64_t > computeConstantBound(presburger::BoundType type, const Variable &var, const StopConditionFn &stopCondition=nullptr, ValueBoundsOptions options={})
Compute a constant bound for the given variable.
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
bool hasOneUse() const
Returns true if this value has exactly one use.
Definition Value.h:197
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
static WalkResult advance()
Definition WalkResult.h:47
static WalkResult interrupt()
Definition WalkResult.h:46
void hoistRedundantVectorBroadcasts(RewriterBase &rewriter, Operation *root)
Hoist vector.extract/vector.broadcast pairs out of immediately enclosing scf::ForOp iteratively,...
Definition Hoisting.cpp:91
void hoistRedundantVectorTransfers(Operation *root, bool verifyNonZeroTrip=false)
Hoist vector.transfer_read/vector.transfer_write on buffers pairs out of immediately enclosing scf::F...
Definition Hoisting.cpp:166
bool hasNoAliasingAccessInScope(Value base, Operation *scope, ArrayRef< Operation * > excludedOps={}, bool readsAreSafe=false)
Return "true" when no other access nested in scope can alias base.
bool isDisjointTransferSet(VectorTransferOpInterface transferA, VectorTransferOpInterface transferB, bool testDynamicValueUsingBounds=false)
Return true if we can prove that the transfer operations access disjoint memory, requiring the operat...
Include the generated interface declarations.
std::function< SmallVector< Value >( OpBuilder &b, Location loc, ArrayRef< BlockArgument > newBbArgs)> NewYieldValuesFn
A function that returns the additional yielded values during replaceWithAdditionalYields.
llvm::SetVector< T, Vector, Set, N > SetVector
Definition LLVM.h:125
size_t moveLoopInvariantCode(ArrayRef< Region * > regions, function_ref< bool(Value, Region *)> isDefinedOutsideRegion, function_ref< bool(Operation *, Region *)> shouldMoveOutOfRegion, function_ref< void(Operation *, Region *)> moveOutOfRegion)
Given a list of regions, perform loop-invariant code motion.
void getForwardSlice(Operation *op, SetVector< Operation * > *forwardSlice, const ForwardSliceOptions &options={})
Fills forwardSlice with the computed forward slice (i.e.
Options that control value bound computation.