MLIR 22.0.0git
CheckUses.cpp
Go to the documentation of this file.
1//===- CheckUses.cpp - Expensive transform value validity checks ----------===//
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 defines a pass that performs expensive opt-in checks for Transform
10// dialect values being potentially used after they have been consumed.
11//
12//===----------------------------------------------------------------------===//
13
15
18#include "llvm/ADT/SetOperations.h"
19
20namespace mlir {
21namespace transform {
22#define GEN_PASS_DEF_CHECKUSESPASS
23#include "mlir/Dialect/Transform/Transforms/Passes.h.inc"
24} // namespace transform
25} // namespace mlir
26
27using namespace mlir;
29namespace {
31/// Returns a reference to a cached set of blocks that are reachable from the
32/// given block via edges computed by the `getNextNodes` function. For example,
33/// if `getNextNodes` returns successors of a block, this will return the set of
34/// reachable blocks; if it returns predecessors of a block, this will return
35/// the set of blocks from which the given block can be reached. The block is
36/// considered reachable form itself only if there is a cycle.
37template <typename FnTy>
39getReachableImpl(Block *block, FnTy getNextNodes,
41 auto [it, inserted] = cache.try_emplace(block);
42 if (!inserted)
43 return it->getSecond();
44
45 llvm::SmallPtrSet<Block *, 4> &reachable = it->second;
46 SmallVector<Block *> worklist;
47 worklist.push_back(block);
48 while (!worklist.empty()) {
49 Block *current = worklist.pop_back_val();
50 for (Block *predecessor : getNextNodes(current)) {
51 // The block is reachable from its transitive predecessors. Only add
52 // them to the worklist if they weren't already visited.
53 if (reachable.insert(predecessor).second)
54 worklist.push_back(predecessor);
55 }
56 }
57 return reachable;
58}
60/// An analysis that identifies whether a value allocated by a Transform op may
61/// be used by another such op after it may have been freed by a third op on
62/// some control flow path. This is conceptually similar to a data flow
63/// analysis, but relies on side effects related to particular values that
64/// currently cannot be modeled by the MLIR data flow analysis framework (also,
65/// the lattice element would be rather expensive as it would need to include
66/// live and/or freed values for each operation).
67///
68/// This analysis is conservatively pessimisic: it will consider that a value
69/// may be freed if it is freed on any possible control flow path between its
70/// allocation and a relevant use, even if the control never actually flows
71/// through the operation that frees the value. It also does not differentiate
72/// between may- (freed on at least one control flow path) and must-free (freed
73/// on all possible control flow paths) because it would require expensive graph
74/// algorithms.
75///
76/// It is intended as an additional non-blocking verification or debugging aid
77/// for ops in the Transform dialect. It leverages the requirement for Transform
78/// dialect ops to implement the MemoryEffectsOpInterface, and expects the
79/// values in the Transform IR to have an allocation effect on the
80/// TransformMappingResource when defined.
81class TransformOpMemFreeAnalysis {
82public:
83 MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(TransformOpMemFreeAnalysis)
84
85 /// Computes the analysis for Transform ops nested in the given operation.
86 explicit TransformOpMemFreeAnalysis(Operation *root) {
87 root->walk([&](Operation *op) {
88 if (isa<transform::TransformOpInterface>(op)) {
89 collectFreedValues(op);
90 return WalkResult::skip();
91 }
92 return WalkResult::advance();
93 });
94 }
95
96 /// A list of operations that may be deleting a value. Non-empty list
97 /// contextually converts to boolean "true" value.
98 class PotentialDeleters {
99 public:
100 /// Creates an empty list that corresponds to the value being live.
101 static PotentialDeleters live() { return PotentialDeleters({}); }
102
103 /// Creates a list from the operations that may be deleting the value.
104 static PotentialDeleters maybeFreed(ArrayRef<Operation *> deleters) {
105 return PotentialDeleters(deleters);
106 }
107
108 /// Converts to "true" if there are operations that may be deleting the
109 /// value.
110 explicit operator bool() const { return !deleters.empty(); }
111
112 /// Concatenates the lists of operations that may be deleting the value. The
113 /// value is known to be live if the reuslting list is still empty.
114 PotentialDeleters &operator|=(const PotentialDeleters &other) {
115 llvm::append_range(deleters, other.deleters);
116 return *this;
117 }
118
119 /// Returns the list of ops that may be deleting the value.
120 ArrayRef<Operation *> getOps() const { return deleters; }
121
122 private:
123 /// Constructs the list from the given operations.
124 explicit PotentialDeleters(ArrayRef<Operation *> ops) {
125 llvm::append_range(deleters, ops);
126 }
127
128 /// The list of operations that may be deleting the value.
129 SmallVector<Operation *> deleters;
130 };
131
132 /// Returns the list of operations that may be deleting the operand value on
133 /// any control flow path between the definition of the value and its use as
134 /// the given operand. For the purposes of this analysis, the value is
135 /// considered to be allocated at its definition point and never re-allocated.
136 PotentialDeleters isUseLive(OpOperand &operand) {
137 const llvm::SmallPtrSet<Operation *, 2> &deleters = freedBy[operand.get()];
138 if (deleters.empty())
139 return live();
140
141#ifndef NDEBUG
142 // Check that the definition point actually allocates the value. If the
143 // definition is a block argument, it may be just forwarding the operand of
144 // the parent op without doing a new allocation, allow that. We currently
145 // don't have the capability to analyze region-based control flow here.
146 //
147 // TODO: when this ported to the dataflow analysis infra, we should have
148 // proper support for region-based control flow.
149 Operation *valueSource =
150 isa<OpResult>(operand.get())
151 ? operand.get().getDefiningOp()
152 : operand.get().getParentBlock()->getParentOp();
153 auto iface = cast<MemoryEffectOpInterface>(valueSource);
154 SmallVector<MemoryEffects::EffectInstance> instances;
155 iface.getEffectsOnResource(transform::TransformMappingResource::get(),
156 instances);
157 assert((isa<BlockArgument>(operand.get()) ||
158 hasEffect<MemoryEffects::Allocate>(instances, operand.get())) &&
159 "expected the op defining the value to have an allocation effect "
160 "on it");
161#endif
162
163 // Collect ancestors of the use operation.
164 Block *defBlock = operand.get().getParentBlock();
165 SmallVector<Operation *> ancestors;
166 Operation *ancestor = operand.getOwner();
167 do {
168 ancestors.push_back(ancestor);
169 if (ancestor->getParentRegion() == defBlock->getParent())
170 break;
171 ancestor = ancestor->getParentOp();
172 } while (true);
173 std::reverse(ancestors.begin(), ancestors.end());
174
175 // Consider the control flow from the definition point of the value to its
176 // use point. If the use is located in some nested region, consider the path
177 // from the entry block of the region to the use.
178 for (Operation *ancestor : ancestors) {
179 // The block should be considered partially if it is the block that
180 // contains the definition (allocation) of the value being used, and the
181 // value is defined in the middle of the block, i.e., is not a block
182 // argument.
183 bool isOutermost = ancestor == ancestors.front();
184 bool isFromBlockPartial = isOutermost && isa<OpResult>(operand.get());
185
186 // Check if the value may be freed by operations between its definition
187 // (allocation) point in its block and the terminator of the block or the
188 // ancestor of the use if it is located in the same block. This is only
189 // done for partial blocks here, full blocks will be considered below
190 // similarly to other blocks.
191 if (isFromBlockPartial) {
192 bool defUseSameBlock = ancestor->getBlock() == defBlock;
193 // Consider all ops from the def to its block terminator, except the
194 // when the use is in the same block, in which case only consider the
195 // ops until the user.
196 if (PotentialDeleters potentialDeleters = isFreedInBlockAfter(
197 operand.get().getDefiningOp(), operand.get(),
198 defUseSameBlock ? ancestor : nullptr))
199 return potentialDeleters;
200 }
201
202 // Check if the value may be freed by opeations preceding the ancestor in
203 // its block. Skip the check for partial blocks that contain both the
204 // definition and the use point, as this has been already checked above.
205 if (!isFromBlockPartial || ancestor->getBlock() != defBlock) {
206 if (PotentialDeleters potentialDeleters =
207 isFreedInBlockBefore(ancestor, operand.get()))
208 return potentialDeleters;
209 }
210
211 // Check if the value may be freed by operations in any of the blocks
212 // between the definition point (in the outermost region) or the entry
213 // block of the region (in other regions) and the operand or its ancestor
214 // in the region. This includes the entire "form" block if (1) the block
215 // has not been considered as partial above and (2) the block can be
216 // reached again through some control-flow loop. This includes the entire
217 // "to" block if it can be reached form itself through some control-flow
218 // cycle, regardless of whether it has been visited before.
219 Block *ancestorBlock = ancestor->getBlock();
220 Block *from =
221 isOutermost ? defBlock : &ancestorBlock->getParent()->front();
222 if (PotentialDeleters potentialDeleters =
223 isMaybeFreedOnPaths(from, ancestorBlock, operand.get(),
224 /*alwaysIncludeFrom=*/!isFromBlockPartial))
225 return potentialDeleters;
226 }
227 return live();
228 }
229
230private:
231 /// Make PotentialDeleters constructors available with shorter names.
232 static PotentialDeleters maybeFreed(ArrayRef<Operation *> deleters) {
233 return PotentialDeleters::maybeFreed(deleters);
234 }
235 static PotentialDeleters live() { return PotentialDeleters::live(); }
236
237 /// Returns the list of operations that may be deleting the given value betwen
238 /// the first and last operations, non-inclusive. `getNext` indicates the
239 /// direction of the traversal.
240 PotentialDeleters
241 isFreedBetween(Value value, Operation *first, Operation *last,
242 llvm::function_ref<Operation *(Operation *)> getNext) const {
243 auto it = freedBy.find(value);
244 if (it == freedBy.end())
245 return live();
246 const llvm::SmallPtrSet<Operation *, 2> &deleters = it->getSecond();
247 for (Operation *op = getNext(first); op != last; op = getNext(op)) {
248 if (deleters.contains(op))
249 return maybeFreed(op);
250 }
251 return live();
252 }
253
254 /// Returns the list of operations that may be deleting the given value
255 /// between `root` and `before` values. `root` is expected to be in the same
256 /// block as `before` and precede it. If `before` is null, consider all
257 /// operations until the end of the block including the terminator.
258 PotentialDeleters isFreedInBlockAfter(Operation *root, Value value,
259 Operation *before = nullptr) const {
260 return isFreedBetween(value, root, before,
261 [](Operation *op) { return op->getNextNode(); });
262 }
263
264 /// Returns the list of operations that may be deleting the given value
265 /// between the entry of the block and the `root` operation.
266 PotentialDeleters isFreedInBlockBefore(Operation *root, Value value) const {
267 return isFreedBetween(value, root, nullptr,
268 [](Operation *op) { return op->getPrevNode(); });
269 }
270
271 /// Returns the list of operations that may be deleting the given value on
272 /// any of the control flow paths between the "form" and the "to" block. The
273 /// operations from any block visited on any control flow path are
274 /// consdiered. The "from" block is considered if there is a control flow
275 /// cycle going through it, i.e., if there is a possibility that all
276 /// operations in this block are visited or if the `alwaysIncludeFrom` flag is
277 /// set. The "to" block is considered only if there is a control flow cycle
278 /// going through it.
279 PotentialDeleters isMaybeFreedOnPaths(Block *from, Block *to, Value value,
280 bool alwaysIncludeFrom) {
281 // Find all blocks that lie on any path between "from" and "to", i.e., the
282 // intersection of blocks reachable from "from" and blocks from which "to"
283 // is rechable.
284 const llvm::SmallPtrSet<Block *, 4> &sources = getReachableFrom(to);
285 if (!sources.contains(from))
286 return live();
287
288 llvm::SmallPtrSet<Block *, 4> reachable(getReachable(from));
289 llvm::set_intersect(reachable, sources);
290
291 // If requested, include the "from" block that may not be present in the set
292 // of visited blocks when there is no cycle going through it.
293 if (alwaysIncludeFrom)
294 reachable.insert(from);
295
296 // Join potential deleters from all blocks as we don't know here which of
297 // the paths through the control flow is taken.
298 PotentialDeleters potentialDeleters = live();
299 for (Block *block : reachable) {
300 for (Operation &op : *block) {
301 if (freedBy[value].count(&op))
302 potentialDeleters |= maybeFreed(&op);
303 }
304 }
305 return potentialDeleters;
306 }
307
308 /// Popualtes `reachable` with the set of blocks that are rechable from the
309 /// given block. A block is considered reachable from itself if there is a
310 /// cycle in the control-flow graph that invovles the block.
311 const llvm::SmallPtrSet<Block *, 4> &getReachable(Block *block) {
312 return getReachableImpl(
313 block, [](Block *b) { return b->getSuccessors(); }, reachableCache);
314 }
315
316 /// Populates `sources` with the set of blocks from which the given block is
317 /// reachable.
318 const llvm::SmallPtrSet<Block *, 4> &getReachableFrom(Block *block) {
319 return getReachableImpl(
320 block, [](Block *b) { return b->getPredecessors(); },
321 reachableFromCache);
322 }
323
324 /// Returns true of `instances` contains an effect of `EffectTy` on `value`.
325 template <typename EffectTy>
326 static bool hasEffect(ArrayRef<MemoryEffects::EffectInstance> instances,
327 Value value) {
328 return llvm::any_of(instances,
329 [&](const MemoryEffects::EffectInstance &instance) {
330 return instance.getValue() == value &&
331 isa<EffectTy>(instance.getEffect());
332 });
333 }
334
335 /// Records the values that are being freed by an operation or any of its
336 /// children in `freedBy`.
337 void collectFreedValues(Operation *root) {
338 SmallVector<MemoryEffects::EffectInstance> instances;
339 root->walk([&](Operation *child) {
340 if (isa<transform::PatternDescriptorOpInterface>(child))
341 return;
342 // TODO: extend this to conservatively handle operations with undeclared
343 // side effects as maybe freeing the operands.
344 auto iface = cast<MemoryEffectOpInterface>(child);
345 instances.clear();
346 iface.getEffectsOnResource(transform::TransformMappingResource::get(),
347 instances);
348 for (Value operand : child->getOperands()) {
349 if (hasEffect<MemoryEffects::Free>(instances, operand)) {
350 // All parents of the operation that frees a value should be
351 // considered as potentially freeing the value as well.
352 //
353 // TODO: differentiate between must-free/may-free as well as between
354 // this op having the effect and children having the effect. This may
355 // require some analysis of all control flow paths through the nested
356 // regions as well as a mechanism to separate proper side effects from
357 // those obtained by nesting.
358 Operation *parent = child;
359 do {
360 freedBy[operand].insert(parent);
361 if (parent == root)
362 break;
363 parent = parent->getParentOp();
364 } while (true);
365 }
366 }
367 });
368 }
369
370 /// The mapping from a value to operations that have a Free memory effect on
371 /// the TransformMappingResource and associated with this value, or to
372 /// Transform operations transitively containing such operations.
374
375 /// Caches for sets of reachable blocks.
378};
379
380//// A simple pass that warns about any use of a value by a transform operation
381// that may be using the value after it has been freed.
382class CheckUsesPass : public transform::impl::CheckUsesPassBase<CheckUsesPass> {
383public:
384 void runOnOperation() override {
385 auto &analysis = getAnalysis<TransformOpMemFreeAnalysis>();
386
387 getOperation()->walk([&](Operation *child) {
388 for (OpOperand &operand : child->getOpOperands()) {
389 TransformOpMemFreeAnalysis::PotentialDeleters deleters =
390 analysis.isUseLive(operand);
391 if (!deleters)
392 continue;
393
394 InFlightDiagnostic diag = child->emitWarning()
395 << "operand #" << operand.getOperandNumber()
396 << " may be used after free";
397 diag.attachNote(operand.get().getLoc()) << "allocated here";
398 for (Operation *d : deleters.getOps()) {
399 diag.attachNote(d->getLoc()) << "freed here";
400 }
401 }
402 });
403 }
404};
405
406} // namespace
407
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
*if copies could not be generated due to yet unimplemented cases *copyInPlacementStart and copyOutPlacementStart in copyPlacementBlock *specify the insertion points where the incoming copies and outgoing should be inserted(the insertion happens right before the *insertion point). Since `begin` can itself be invalidated due to the memref *rewriting done from this method
static std::string diag(const llvm::Value &value)
template bool mlir::hasEffect< MemoryEffects::Allocate >(Operation *)
template bool mlir::hasEffect< MemoryEffects::Free >(Operation *)
#define MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(CLASS_NAME)
Definition TypeID.h:331
Block represents an ordered list of Operations.
Definition Block.h:33
Region * getParent() const
Provide a 'getParent' method for ilist_node_with_parent methods.
Definition Block.cpp:27
Operation * getParentOp()
Returns the closest surrounding operation that contains this block.
Definition Block.cpp:31
IRValueT get() const
Return the current value being used by this operand.
unsigned getOperandNumber()
Return which operand this is in the OpOperand list of the Operation.
Definition Value.cpp:226
Operation is the basic unit of execution within MLIR.
Definition Operation.h:88
InFlightDiagnostic emitWarning(const Twine &message={})
Emit a warning about this operation, reporting up to any diagnostic handlers that may be listening.
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:213
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:234
MutableArrayRef< OpOperand > getOpOperands()
Definition Operation.h:383
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:378
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:797
Region * getParentRegion()
Returns the region to which the instruction belongs.
Definition Operation.h:230
Block & front()
Definition Region.h:65
EffectT * getEffect() const
Return the effect being applied.
Value getValue() const
Return the value the effect is applied on, or nullptr if there isn't a known value being affected.
Block * getParentBlock()
Return the Block in which this Value is defined.
Definition Value.cpp:46
Location getLoc() const
Return the location of this value.
Definition Value.cpp:24
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
static WalkResult skip()
Definition WalkResult.h:48
static WalkResult advance()
Definition WalkResult.h:47
Operation * getOwner() const
Return the owner of this operand.
Definition UseDefLists.h:38
SideEffects::EffectInstance< Effect > EffectInstance
detail::InFlightRemark analysis(Location loc, RemarkOpts opts)
Report an optimization analysis remark.
Definition Remarks.h:567
Include the generated interface declarations.
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:126
bool hasEffect(Operation *op)
Returns "true" if op has an effect of type EffectTy.
ChangeResult & operator|=(ChangeResult &lhs, ChangeResult rhs)