MLIR 24.0.0git
OptimizeAllocationLiveness.cpp
Go to the documentation of this file.
1//===- OptimizeAllocationLiveness.cpp - impl. optimize allocation liveness pass
2//-===//
3//
4// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5// See https://llvm.org/LICENSE.txt for license information.
6// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7//
8//===----------------------------------------------------------------------===//
9//
10// This file implements a pass for optimizing allocation liveness.
11// The pass moves the deallocation operation after the last user of the
12// allocated buffer.
13//===----------------------------------------------------------------------===//
14
18#include "mlir/IR/Dominance.h"
19#include "mlir/IR/Operation.h"
20#include "mlir/IR/Value.h"
22#include "llvm/Support/DebugLog.h"
23
24#define DEBUG_TYPE "optimize-allocation-liveness"
25
26namespace mlir {
27namespace bufferization {
28#define GEN_PASS_DEF_OPTIMIZEALLOCATIONLIVENESSPASS
29#include "mlir/Dialect/Bufferization/Transforms/Passes.h.inc"
30} // namespace bufferization
31} // namespace mlir
32
33using namespace mlir;
34
35namespace {
36
37//===----------------------------------------------------------------------===//
38// Helper functions
39//===----------------------------------------------------------------------===//
40
41/// Return true if `a` happens before `b`, i.e., `a` or one of its ancestors
42/// properly dominates `b` and `b` is not inside `a`.
43static bool happensBefore(Operation *a, Operation *b) {
44 do {
45 if (a->isProperAncestor(b))
46 return false;
47 if (Operation *bAncestor = a->getBlock()->findAncestorOpInBlock(*b)) {
48 return a->isBeforeInBlock(bAncestor);
49 }
50 } while ((a = a->getParentOp()));
51 return false;
52}
53
54/// This method searches for a user of value that is a dealloc operation.
55/// If multiple users with free effect are found, return nullptr.
56Operation *findUserWithFreeSideEffect(Value value) {
57 Operation *freeOpUser = nullptr;
58 for (Operation *user : value.getUsers()) {
59 if (MemoryEffectOpInterface memEffectOp =
60 dyn_cast<MemoryEffectOpInterface>(user)) {
62 memEffectOp.getEffects(effects);
63
64 for (const auto &effect : effects) {
65 if (isa<MemoryEffects::Free>(effect.getEffect())) {
66 if (freeOpUser) {
67 LDBG() << "Multiple users with free effect found: " << *freeOpUser
68 << " and " << *user;
69 return nullptr;
70 }
71 freeOpUser = user;
72 }
73 }
74 }
75 }
76 return freeOpUser;
77}
78
79/// Checks if the given op allocates memory.
80static bool hasMemoryAllocEffect(MemoryEffectOpInterface memEffectOp) {
82 memEffectOp.getEffects(effects);
83 for (const auto &effect : effects) {
84 if (isa<MemoryEffects::Allocate>(effect.getEffect())) {
85 return true;
86 }
87 }
88 return false;
89}
90
91/// Extracts OpResult's with Allocate effects from given op
93collectAllocations(MemoryEffectOpInterface allocOp) {
95 allocOp.getEffects(effects);
96 SmallVector<OpResult> allocResults;
97 for (const MemoryEffects::EffectInstance &it : effects)
98 if (isa<MemoryEffects::Allocate>(it.getEffect()))
99 if (auto val = it.getValue(); val && val.getDefiningOp() == allocOp)
100 allocResults.push_back(cast<OpResult>(val));
101 return allocResults;
102}
103
104struct OptimizeAllocationLiveness
106 OptimizeAllocationLiveness> {
107public:
108 OptimizeAllocationLiveness() = default;
109
110 /// When moving a dealloc operation to be closer to its companion alloc we
111 /// have to consider the following. The dealloc should be:
112 /// - After all the operand definitions that the dealloc uses
113 /// - Before any ops that consume the results produced by the dealloc
114 /// To do this, we first ensure the "target" position is after all the
115 /// operands. Then we ensure this target dominates all consumers of the
116 /// dealloc's results. If not, no valid position exists and nullptr is
117 /// returned.
118 static Operation *findValidInsertionPoint(DominanceInfo *domInfo,
119 Operation *allocOp,
120 Operation *deallocOp,
121 Operation *lastUser) {
122
123 auto *target = lastUser;
124
125 // Start at the last user of the buffer, then move later if an operand of
126 // the dealloc is defined after it. E.g. target starts at %x but moves to
127 // %w:
128 // %x = memref.load %a[%c0] // lastUser
129 // %w = gpu.wait async [%d] // target
130 // %done = gpu.dealloc async [%w] %a : memref<8xf32> // deallocOp
131 for (Value operand : deallocOp->getOperands()) {
132 auto *definingOp = operand.getDefiningOp();
133 if (!definingOp)
134 continue;
135
136 if (!target || domInfo->properlyDominates(target, definingOp))
137 target = definingOp;
138 }
139
140 if (!target)
141 target = allocOp;
142
143 // Ensure target dominates all result users. Here the realloc is the
144 // deallocOp since it frees %a, and it also produces %b. The first loop
145 // leaves target at the memref.dealloc (the last user of the aliased
146 // buffer), but that op uses %b, so placing the realloc after it is invalid:
147 // %a = memref.alloc() : memref<8xf32>
148 // %b = memref.realloc %a : memref<8xf32> to memref<16xf32> // deallocOp
149 // memref.dealloc %b : memref<16xf32> // target
150 // nullptr is returned as a result.
151 for (Value result : deallocOp->getResults()) {
152 for (Operation *user : result.getUsers()) {
153 if (user == target ||
154 !domInfo->properlyDominates(target, user, /*enclosingOpOk=*/false))
155 return nullptr;
156 }
157 }
158 // Return the op to insert before
159 return target->getNextNode();
160 }
161
162 void runOnOperation() override {
163 func::FuncOp func = getOperation();
164
165 if (func.isExternal())
166 return;
167
168 BufferViewFlowAnalysis analysis = BufferViewFlowAnalysis(func);
169 auto domInfo = DominanceInfo();
170
171 func.walk([&](MemoryEffectOpInterface memEffectOp) -> WalkResult {
172 if (!hasMemoryAllocEffect(memEffectOp))
173 return WalkResult::advance();
174
175 auto allocOp = memEffectOp;
176 LDBG() << "Checking alloc op: " << allocOp;
177
178 SmallVector<OpResult> allocationResults = collectAllocations(allocOp);
179 // Multiple allocations from a single op are not considered here yet.
180 if (allocationResults.size() != 1)
181 return WalkResult::advance();
182
183 OpResult allocResult = allocationResults[0];
184 LDBG() << "On allocation result: " << allocResult;
185
186 auto *deallocOp = findUserWithFreeSideEffect(allocResult);
187 if (!deallocOp || (deallocOp->getBlock() != allocOp->getBlock())) {
188 // The pass handles allocations that have a single dealloc op in the
189 // same block. We also should not hoist the dealloc op out of
190 // conditionals.
191 return WalkResult::advance();
192 }
193
194 Operation *lastUser = nullptr;
196 analysis.resolve(allocResult);
197 for (auto dep : llvm::make_early_inc_range(deps)) {
198 for (auto *user : dep.getUsers()) {
199 // We are looking for a non dealloc op user.
200 // check if user is the dealloc op itself.
201 if (user == deallocOp)
202 continue;
203
204 // find the ancestor of user that is in the same block as the allocOp.
205 auto *topUser = allocOp->getBlock()->findAncestorOpInBlock(*user);
206 if (!lastUser || happensBefore(lastUser, topUser)) {
207 lastUser = topUser;
208 }
209 }
210 }
211 if (lastUser == nullptr) {
212 return WalkResult::advance();
213 }
214 LDBG() << "Last user found: " << *lastUser;
215 assert(lastUser->getBlock() == allocOp->getBlock());
216 assert(lastUser->getBlock() == deallocOp->getBlock());
217
218 Operation *target =
219 findValidInsertionPoint(&domInfo, allocOp, deallocOp, lastUser);
220 if (!target) {
221 LDBG() << "No valid insertion point found; skipping move";
222 return WalkResult::advance();
223 }
224
225 deallocOp->moveBefore(target);
226 LDBG() << "Moved dealloc op before: " << *target;
227
228 return WalkResult::advance();
229 });
230 }
231};
232
233} // end anonymous namespace
static Operation * findValidInsertionPoint(Operation *emptyTensorOp, Operation *user, const SmallVector< Value > &neededValues)
Find a valid insertion point for a replacement of emptyTensorOp's use of user operation,...
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
static bool happensBefore(Operation *a, Operation *b)
Return true if a happens before b, i.e., a or one of its ancestors properly dominates b and b is not ...
Operation * findAncestorOpInBlock(Operation &op)
Returns 'op' if 'op' lies in this block, or otherwise finds the ancestor operation of 'op' that lies ...
Definition Block.cpp:74
SmallPtrSet< Value, 16 > ValueSetT
bool properlyDominates(Operation *a, Operation *b, bool enclosingOpOk=true) const
Return true if operation A properly dominates operation B, i.e.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
bool isBeforeInBlock(Operation *other)
Given an operation 'other' that is within the same parent block, return whether the current operation...
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:230
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:251
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...
result_range getResults()
Definition Operation.h:440
bool isProperAncestor(Operation *other)
Return true if this operation is a proper ancestor of the other operation.
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
user_range getUsers() const
Definition Value.h:218
static WalkResult advance()
Definition WalkResult.h:47
SideEffects::EffectInstance< Effect > EffectInstance
detail::InFlightRemark analysis(Location loc, RemarkOpts opts)
Report an optimization analysis remark.
Definition Remarks.h:740
Include the generated interface declarations.