MLIR 24.0.0git
BufferOptimizations.cpp
Go to the documentation of this file.
1//===- BufferOptimizations.cpp - pre-pass optimizations for bufferization -===//
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 logic for three optimization passes. The first two
10// passes try to move alloc nodes out of blocks to reduce the number of
11// allocations and copies during buffer deallocation. The third pass tries to
12// convert heap-based allocations to stack-based allocations, if possible.
13
15
21#include "mlir/IR/Operation.h"
24#include "mlir/Pass/Pass.h"
25
26namespace mlir {
27namespace bufferization {
28#define GEN_PASS_DEF_BUFFERHOISTINGPASS
29#define GEN_PASS_DEF_BUFFERLOOPHOISTINGPASS
30#define GEN_PASS_DEF_PROMOTEBUFFERSTOSTACKPASS
31#include "mlir/Dialect/Bufferization/Transforms/Passes.h.inc"
32} // namespace bufferization
33} // namespace mlir
34
35using namespace mlir;
36using namespace mlir::bufferization;
37
38/// Returns true if the given operation implements a known high-level region-
39/// based control-flow interface.
41 return isa<LoopLikeOpInterface, RegionBranchOpInterface>(op);
42}
43
44/// Returns true if the given operation represents a loop by testing whether it
45/// implements the `LoopLikeOpInterface` or the `RegionBranchOpInterface`. In
46/// the case of a `RegionBranchOpInterface`, it checks all region-based control-
47/// flow edges for cycles.
48static bool isLoop(Operation *op) {
49 // If the operation implements the `LoopLikeOpInterface` it can be considered
50 // a loop.
51 if (isa<LoopLikeOpInterface>(op))
52 return true;
53
54 // If the operation does not implement the `RegionBranchOpInterface`, it is
55 // (currently) not possible to detect a loop.
56 auto regionInterface = dyn_cast<RegionBranchOpInterface>(op);
57 if (!regionInterface)
58 return false;
59
60 return regionInterface.hasLoop();
61}
62
63/// Return whether the given operation is a loop with sequential execution
64/// semantics.
65static bool isSequentialLoop(Operation *op) {
66 return !op->hasTrait<OpTrait::HasParallelRegion>() && isLoop(op);
67}
68
69/// Returns true if the given operation implements the AllocationOpInterface
70/// and it supports the dominate block hoisting.
72 auto allocOp = dyn_cast<AllocationOpInterface>(op);
73 return allocOp &&
74 static_cast<uint8_t>(allocOp.getHoistingKind() & HoistingKind::Block);
75}
76
77/// Returns true if the given operation implements the AllocationOpInterface
78/// and it supports the loop hoisting.
80 auto allocOp = dyn_cast<AllocationOpInterface>(op);
81 return allocOp &&
82 static_cast<uint8_t>(allocOp.getHoistingKind() & HoistingKind::Loop);
83}
84
85/// Check if the size of the allocation is less than the given size. The
86/// transformation is only applied to small buffers since large buffers could
87/// exceed the stack space.
88static bool defaultIsSmallAlloc(Value alloc, unsigned maximumSizeInBytes,
89 unsigned maxRankOfAllocatedMemRef) {
90 auto type = dyn_cast<ShapedType>(alloc.getType());
91 if (!type || !alloc.getDefiningOp<memref::AllocOp>())
92 return false;
93 if (!type.hasStaticShape()) {
94 // Check if the dynamic shape dimension of the alloc is produced by
95 // `memref.rank`. If this is the case, it is likely to be small.
96 // Furthermore, the dimension is limited to the maximum rank of the
97 // allocated memref to avoid large values by multiplying several small
98 // values.
99 if (type.getRank() <= maxRankOfAllocatedMemRef) {
100 return llvm::all_of(alloc.getDefiningOp()->getOperands(),
101 [&](Value operand) {
102 return operand.getDefiningOp<memref::RankOp>();
103 });
104 }
105 return false;
106 }
107 // Skip if the element type has no data layout support (e.g., nested memrefs).
108 Type elemType = type.getElementType();
109 if (!elemType.isIntOrFloat() &&
110 !isa<ComplexType, IndexType, VectorType>(elemType) &&
111 !isa<DataLayoutTypeInterface>(elemType))
112 return false;
113 unsigned bitwidth = mlir::DataLayout::closest(alloc.getDefiningOp())
114 .getTypeSizeInBits(elemType);
115 // Use tryGetNumElements to avoid an assertion on integer overflow (e.g. for
116 // very large statically-shaped memrefs). If the element count overflows
117 // int64_t the allocation is certainly not "small", so return false.
118 std::optional<int64_t> numElements = type.tryGetNumElements();
119 if (!numElements)
120 return false;
121 // Guard against overflow in the size computation as well.
122 if (bitwidth != 0 &&
123 *numElements > static_cast<int64_t>(maximumSizeInBytes * 8ULL / bitwidth))
124 return false;
125 return *numElements * bitwidth <=
126 static_cast<int64_t>(maximumSizeInBytes * 8ULL);
127}
128
129/// Checks whether the given aliases leave the allocation scope.
130static bool
132 const BufferViewFlowAnalysis::ValueSetT &aliases) {
133 for (Value alias : aliases) {
134 for (auto *use : alias.getUsers()) {
135 // If there is at least one alias that leaves the parent region, we know
136 // that this alias escapes the whole region and hence the associated
137 // allocation leaves allocation scope.
138 if (isa<RegionBranchTerminatorOpInterface>(use) &&
139 use->getParentRegion() == parentRegion)
140 return true;
141 }
142 }
143 return false;
144}
145
146/// Checks, if an automated allocation scope for a given alloc value exists.
147static bool hasAllocationScope(Value alloc,
148 const BufferViewFlowAnalysis &aliasAnalysis) {
149 Region *region = alloc.getParentRegion();
150 do {
151 if (Operation *parentOp = region->getParentOp()) {
152 // Check if the operation is an automatic allocation scope and whether an
153 // alias leaves the scope. This means, an allocation yields out of
154 // this scope and can not be transformed in a stack-based allocation.
155 if (parentOp->hasTrait<OpTrait::AutomaticAllocationScope>() &&
156 !leavesAllocationScope(region, aliasAnalysis.resolve(alloc)))
157 return true;
158 // Check if the operation is a known control flow interface and break the
159 // loop to avoid transformation in loops. Furthermore skip transformation
160 // if the operation does not implement a RegionBeanchOpInterface.
161 if (isLoop(parentOp) || !isKnownControlFlowInterface(parentOp))
162 break;
163 }
164 } while ((region = region->getParentRegion()));
165 return false;
166}
167
168namespace {
169
170//===----------------------------------------------------------------------===//
171// BufferAllocationHoisting
172//===----------------------------------------------------------------------===//
173
174/// A base implementation compatible with the `BufferAllocationHoisting` class.
175struct BufferAllocationHoistingStateBase {
176 /// A pointer to the current dominance info.
177 DominanceInfo *dominators;
178
179 /// The current allocation value.
180 Value allocValue;
181
182 /// The current placement block (if any).
183 Block *placementBlock;
184
185 /// Initializes the state base.
186 BufferAllocationHoistingStateBase(DominanceInfo *dominators, Value allocValue,
187 Block *placementBlock)
188 : dominators(dominators), allocValue(allocValue),
189 placementBlock(placementBlock) {}
190};
191
192/// Implements the actual hoisting logic for allocation nodes.
193template <typename StateT>
194class BufferAllocationHoisting : public BufferPlacementTransformationBase {
195public:
196 BufferAllocationHoisting(Operation *op)
197 : BufferPlacementTransformationBase(op), dominators(op),
198 postDominators(op), scopeOp(op) {}
199
200 /// Moves allocations upwards.
201 void hoist() {
202 SmallVector<Value> allocsAndAllocas;
203 for (BufferPlacementAllocs::AllocEntry &entry : allocs)
204 allocsAndAllocas.push_back(std::get<0>(entry));
205 scopeOp->walk([&](memref::AllocaOp op) {
206 allocsAndAllocas.push_back(op.getMemref());
207 });
208
209 for (auto allocValue : allocsAndAllocas) {
210 if (!StateT::shouldHoistOpType(allocValue.getDefiningOp()))
211 continue;
212 Operation *definingOp = allocValue.getDefiningOp();
213 assert(definingOp && "No defining op");
214 // Skip allocations in blocks that are not reachable from the function
215 // entry. Such blocks are dead code and the dominator tree analysis may
216 // not have nodes for them, which would cause crashes below.
217 if (!dominators.isReachableFromEntry(allocValue.getParentBlock()))
218 continue;
219 auto operands = definingOp->getOperands();
220 auto resultAliases = aliases.resolve(allocValue);
221 // Determine the common dominator block of all aliases.
222 Block *dominatorBlock =
223 findCommonDominator(allocValue, resultAliases, dominators);
224 // Init the initial hoisting state.
225 StateT state(&dominators, allocValue, allocValue.getParentBlock());
226 // Check for additional allocation dependencies to compute an upper bound
227 // for hoisting.
228 Block *dependencyBlock = nullptr;
229 // If this node has dependencies, check all dependent nodes. This ensures
230 // that all dependency values have been computed before allocating the
231 // buffer.
232 for (Value depValue : operands) {
233 Block *depBlock = depValue.getParentBlock();
234 if (!dependencyBlock || dominators.dominates(dependencyBlock, depBlock))
235 dependencyBlock = depBlock;
236 }
237
238 // Find the actual placement block and determine the start operation using
239 // an upper placement-block boundary. The idea is that placement block
240 // cannot be moved any further upwards than the given upper bound.
241 Block *placementBlock = findPlacementBlock(
242 state, state.computeUpperBound(dominatorBlock, dependencyBlock));
243 Operation *startOperation = BufferPlacementAllocs::getStartOperation(
244 allocValue, placementBlock, liveness);
245
246 // Move the alloc in front of the start operation.
247 Operation *allocOperation = allocValue.getDefiningOp();
248 allocOperation->moveBefore(startOperation);
249 }
250 }
251
252private:
253 /// Finds a valid placement block by walking upwards in the CFG until we
254 /// either cannot continue our walk due to constraints (given by the StateT
255 /// implementation) or we have reached the upper-most dominator block.
256 Block *findPlacementBlock(StateT &state, Block *upperBound) {
257 Block *currentBlock = state.placementBlock;
258 // Walk from the innermost regions/loops to the outermost regions/loops and
259 // find an appropriate placement block that satisfies the constraint of the
260 // current StateT implementation. Walk until we reach the upperBound block
261 // (if any).
262
263 // If we are not able to find a valid parent operation or an associated
264 // parent block, break the walk loop.
265 Operation *parentOp;
266 Block *parentBlock;
267 while ((parentOp = currentBlock->getParentOp()) &&
268 (parentBlock = parentOp->getBlock()) &&
269 (!upperBound ||
270 dominators.properlyDominates(upperBound, currentBlock))) {
271 // A reachable nested region may have an unreachable enclosing block,
272 // which has no node in the dominator tree.
273 if (!dominators.isReachableFromEntry(currentBlock))
274 break;
275
276 // Try to find an immediate dominator and check whether the parent block
277 // is above the immediate dominator (if any).
278 DominanceInfoNode *idom = nullptr;
279
280 // DominanceInfo doesn't support getNode queries for single-block regions.
281 if (!currentBlock->isEntryBlock())
282 idom = dominators.getNode(currentBlock)->getIDom();
283
284 if (idom && dominators.properlyDominates(parentBlock, idom->getBlock())) {
285 // If the current immediate dominator is below the placement block, move
286 // to the immediate dominator block.
287 currentBlock = idom->getBlock();
288 state.recordMoveToDominator(currentBlock);
289 } else {
290 // The target's parent block is outside the liveness analysis. Keep
291 // allocations within the target and preserve isolation boundaries.
292 if (parentOp == scopeOp ||
293 parentOp->hasTrait<OpTrait::IsIsolatedFromAbove>() ||
294 !isKnownControlFlowInterface(parentOp) ||
295 !state.isLegalPlacement(parentOp))
296 break;
297 // Move to our parent block by notifying the current StateT
298 // implementation.
299 currentBlock = parentBlock;
300 state.recordMoveToParent(currentBlock);
301 }
302 }
303 // Return the finally determined placement block.
304 return state.placementBlock;
305 }
306
307 /// The dominator info to find the appropriate start operation to move the
308 /// allocs.
309 DominanceInfo dominators;
310
311 /// The post dominator info to move the dependent allocs in the right
312 /// position.
313 PostDominanceInfo postDominators;
314
315 /// The map storing the final placement blocks of a given alloc value.
316 llvm::DenseMap<Value, Block *> placementBlocks;
317
318 /// The operation that this transformation is working on. It is used to also
319 /// gather allocas.
320 Operation *scopeOp;
321};
322
323/// A state implementation compatible with the `BufferAllocationHoisting` class
324/// that hoists allocations into dominator blocks while keeping them inside of
325/// loops.
326struct BufferAllocationHoistingState : BufferAllocationHoistingStateBase {
327 using BufferAllocationHoistingStateBase::BufferAllocationHoistingStateBase;
328
329 /// Computes the upper bound for the placement block search.
330 Block *computeUpperBound(Block *dominatorBlock, Block *dependencyBlock) {
331 // If we do not have a dependency block, the upper bound is given by the
332 // dominator block.
333 if (!dependencyBlock)
334 return dominatorBlock;
335
336 // Find the "lower" block of the dominator and the dependency block to
337 // ensure that we do not move allocations above this block.
338 return dominators->properlyDominates(dominatorBlock, dependencyBlock)
339 ? dependencyBlock
340 : dominatorBlock;
341 }
342
343 /// Returns true if the given operation does not represent a loop.
344 bool isLegalPlacement(Operation *op) { return !isLoop(op); }
345
346 /// Returns true if the given operation should be considered for hoisting.
347 static bool shouldHoistOpType(Operation *op) {
349 }
350
351 /// Sets the current placement block to the given block.
352 void recordMoveToDominator(Block *block) { placementBlock = block; }
353
354 /// Sets the current placement block to the given block.
355 void recordMoveToParent(Block *block) { recordMoveToDominator(block); }
356};
357
358/// A state implementation compatible with the `BufferAllocationHoisting` class
359/// that hoists allocations out of loops.
360struct BufferAllocationLoopHoistingState : BufferAllocationHoistingStateBase {
361 using BufferAllocationHoistingStateBase::BufferAllocationHoistingStateBase;
362
363 /// Remembers the dominator block of all aliases.
364 Block *aliasDominatorBlock = nullptr;
365
366 /// Computes the upper bound for the placement block search.
367 Block *computeUpperBound(Block *dominatorBlock, Block *dependencyBlock) {
368 aliasDominatorBlock = dominatorBlock;
369 // If there is a dependency block, we have to use this block as an upper
370 // bound to satisfy all allocation value dependencies.
371 return dependencyBlock ? dependencyBlock : nullptr;
372 }
373
374 /// Returns true if the given operation represents a loop with sequential
375 /// execution semantics and one of the aliases caused the
376 /// `aliasDominatorBlock` to be "above" the block of the given loop operation.
377 /// If this is the case, it indicates that the allocation is passed via a back
378 /// edge.
379 bool isLegalPlacement(Operation *op) {
380 return isSequentialLoop(op) &&
381 !dominators->dominates(aliasDominatorBlock, op->getBlock());
382 }
383
384 /// Returns true if the given operation should be considered for hoisting.
385 static bool shouldHoistOpType(Operation *op) {
386 return allowAllocLoopHoisting(op);
387 }
388
389 /// Does not change the internal placement block, as we want to move
390 /// operations out of loops only.
391 void recordMoveToDominator(Block *block) {}
392
393 /// Sets the current placement block to the given block.
394 void recordMoveToParent(Block *block) { placementBlock = block; }
395};
396
397//===----------------------------------------------------------------------===//
398// BufferPlacementPromotion
399//===----------------------------------------------------------------------===//
400
401/// Promotes heap-based allocations to stack-based allocations (if possible).
402class BufferPlacementPromotion : BufferPlacementTransformationBase {
403public:
404 BufferPlacementPromotion(Operation *op)
405 : BufferPlacementTransformationBase(op) {}
406
407 /// Promote buffers to stack-based allocations.
408 void promote(function_ref<bool(Value)> isSmallAlloc) {
409 for (BufferPlacementAllocs::AllocEntry &entry : allocs) {
410 Value alloc = std::get<0>(entry);
411 Operation *dealloc = std::get<1>(entry);
412 // Checking several requirements to transform an AllocOp into an AllocaOp.
413 // The transformation is done if the allocation is limited to a given
414 // size. Furthermore, a deallocation must not be defined for this
415 // allocation entry and a parent allocation scope must exist.
416 if (!isSmallAlloc(alloc) || dealloc ||
417 !hasAllocationScope(alloc, aliases))
418 continue;
419
420 Operation *startOperation = BufferPlacementAllocs::getStartOperation(
421 alloc, alloc.getParentBlock(), liveness);
422 // Build a new alloca that is associated with its parent
423 // `AutomaticAllocationScope` determined during the initialization phase.
424 OpBuilder builder(startOperation);
425 Operation *allocOp = alloc.getDefiningOp();
426 if (auto allocInterface = dyn_cast<AllocationOpInterface>(allocOp)) {
427 std::optional<Operation *> alloca =
428 allocInterface.buildPromotedAlloc(builder, alloc);
429 if (!alloca)
430 continue;
431 // Replace the original alloc by a newly created alloca.
432 allocOp->replaceAllUsesWith(alloca.value());
433 allocOp->erase();
434 }
435 }
436 }
437};
438
439//===----------------------------------------------------------------------===//
440// BufferOptimizationPasses
441//===----------------------------------------------------------------------===//
442
443/// The buffer hoisting pass that hoists allocation nodes into dominating
444/// blocks.
445struct BufferHoistingPass
446 : public bufferization::impl::BufferHoistingPassBase<BufferHoistingPass> {
447
448 void runOnOperation() override {
449 // Hoist all allocations into dominator blocks.
450 BufferAllocationHoisting<BufferAllocationHoistingState> optimizer(
451 getOperation());
452 optimizer.hoist();
453 }
454};
455
456/// The buffer loop hoisting pass that hoists allocation nodes out of loops.
457struct BufferLoopHoistingPass
458 : public bufferization::impl::BufferLoopHoistingPassBase<
459 BufferLoopHoistingPass> {
460
461 void runOnOperation() override {
462 // Hoist all allocations out of loops.
463 hoistBuffersFromLoops(getOperation());
464 }
465};
466
467/// The promote buffer to stack pass that tries to convert alloc nodes into
468/// alloca nodes.
469class PromoteBuffersToStackPass
470 : public bufferization::impl::PromoteBuffersToStackPassBase<
471 PromoteBuffersToStackPass> {
472 using Base::Base;
473
474public:
475 explicit PromoteBuffersToStackPass(std::function<bool(Value)> isSmallAlloc)
476 : isSmallAlloc(std::move(isSmallAlloc)) {}
477
478 LogicalResult initialize(MLIRContext *context) override {
479 if (isSmallAlloc == nullptr) {
480 isSmallAlloc = [=](Value alloc) {
481 return defaultIsSmallAlloc(alloc, maxAllocSizeInBytes,
482 maxRankOfAllocatedMemRef);
483 };
484 }
485 return success();
486 }
487
488 void runOnOperation() override {
489 // Move all allocation nodes and convert candidates into allocas.
490 BufferPlacementPromotion optimizer(getOperation());
491 optimizer.promote(isSmallAlloc);
492 }
493
494private:
495 std::function<bool(Value)> isSmallAlloc;
496};
497
498} // namespace
499
501 BufferAllocationHoisting<BufferAllocationLoopHoistingState> optimizer(op);
502 optimizer.hoist();
503}
504
506 std::function<bool(Value)> isSmallAlloc) {
507 return std::make_unique<PromoteBuffersToStackPass>(std::move(isSmallAlloc));
508}
return success()
static bool leavesAllocationScope(Region *parentRegion, const BufferViewFlowAnalysis::ValueSetT &aliases)
Checks whether the given aliases leave the allocation scope.
static bool isKnownControlFlowInterface(Operation *op)
Returns true if the given operation implements a known high-level region- based control-flow interfac...
static bool hasAllocationScope(Value alloc, const BufferViewFlowAnalysis &aliasAnalysis)
Checks, if an automated allocation scope for a given alloc value exists.
static bool isSequentialLoop(Operation *op)
Return whether the given operation is a loop with sequential execution semantics.
static bool isLoop(Operation *op)
Returns true if the given operation represents a loop by testing whether it implements the LoopLikeOp...
static bool allowAllocDominateBlockHoisting(Operation *op)
Returns true if the given operation implements the AllocationOpInterface and it supports the dominate...
static bool allowAllocLoopHoisting(Operation *op)
Returns true if the given operation implements the AllocationOpInterface and it supports the loop hoi...
static bool defaultIsSmallAlloc(Value alloc, unsigned maximumSizeInBytes, unsigned maxRankOfAllocatedMemRef)
Check if the size of the allocation is less than the given size.
LogicalResult initialize(unsigned origNumLoops, ArrayRef< ReassociationIndices > foldedIterationDims)
bool isEntryBlock()
Return if this block is the entry block in the parent region.
Definition Block.cpp:36
Operation * getParentOp()
Returns the closest surrounding operation that contains this block.
Definition Block.cpp:31
A straight-forward alias analysis which ensures that all dependencies of all values will be determine...
SmallPtrSet< Value, 16 > ValueSetT
ValueSetT resolve(Value value) const
Find all immediate and indirect views upon this value.
static DataLayout closest(Operation *op)
Returns the layout of the closest parent operation carrying layout info.
llvm::TypeSize getTypeSizeInBits(Type t) const
Returns the size in bits of the given type in the current scope.
A trait of region holding operations that define a new scope for automatic allocations,...
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
Definition Operation.h:801
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:230
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:403
void moveBefore(Operation *existingOp)
Unlink this operation from its current block and insert it right before existingOp which may be in th...
void replaceAllUsesWith(ValuesT &&values)
Replace all uses of results of this operation with the provided 'values'.
Definition Operation.h:297
void erase()
Remove this operation from its parent block and delete it.
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
Region * getParentRegion()
Return the region containing this region or nullptr if the region is attached to a top-level operatio...
Definition Region.cpp:45
Operation * getParentOp()
Return the parent operation this region is attached to.
Definition Region.h:198
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
bool isIntOrFloat() const
Return true if this is an integer (of any signedness) or a float type.
Definition Types.cpp:118
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
Block * getParentBlock()
Return the Block in which this Value is defined.
Definition Value.cpp:46
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
Region * getParentRegion()
Return the Region in which this Value is defined.
Definition Value.cpp:39
static Operation * getStartOperation(Value allocValue, Block *placementBlock, const Liveness &liveness)
Get the start operation to place the given alloc value within the specified placement block.
std::tuple< Value, Operation * > AllocEntry
Represents a tuple of allocValue and deallocOperation.
Definition BufferUtils.h:38
The base class for all BufferPlacement transformations.
void hoistBuffersFromLoops(Operation *op)
Within the given operation, hoist buffers from loops where possible.
std::unique_ptr< Pass > createPromoteBuffersToStackPass(std::function< bool(Value)> isSmallAlloc)
Creates a pass that promotes heap-based allocations to stack-based ones.
Block * findCommonDominator(Value value, const BufferViewFlowAnalysis::ValueSetT &values, const DominatorT &doms)
Finds a common dominator for the given value while taking the positions of the values in the value se...
Definition BufferUtils.h:82
void promote(RewriterBase &rewriter, scf::ForallOp forallOp)
Promotes the loop body of a scf::ForallOp to its containing block.
Definition SCF.cpp:753
Include the generated interface declarations.
llvm::DomTreeNodeBase< Block > DominanceInfoNode
Definition Dominance.h:30
llvm::function_ref< Fn > function_ref
Definition LLVM.h:147