MLIR 23.0.0git
OwnershipBasedBufferDeallocation.cpp
Go to the documentation of this file.
1//===- OwnershipBasedBufferDeallocation.cpp - impl. for buffer dealloc. ---===//
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 computing correct `bufferization.dealloc`
10// positions. Furthermore, buffer deallocation also adds required new clone
11// operations to ensure that memrefs returned by functions never alias an
12// argument.
13//
14// TODO:
15// The current implementation does not support explicit-control-flow loops and
16// the resulting code will be invalid with respect to program semantics.
17// However, structured control-flow loops are fully supported.
18//
19//===----------------------------------------------------------------------===//
20
28#include "mlir/IR/Iterators.h"
30
31namespace mlir {
32namespace bufferization {
33#define GEN_PASS_DEF_OWNERSHIPBASEDBUFFERDEALLOCATIONPASS
34#include "mlir/Dialect/Bufferization/Transforms/Passes.h.inc"
35} // namespace bufferization
36} // namespace mlir
37
38using namespace mlir;
39using namespace mlir::bufferization;
40
41//===----------------------------------------------------------------------===//
42// Helpers
43//===----------------------------------------------------------------------===//
44
45static Value buildBoolValue(OpBuilder &builder, Location loc, bool value) {
46 return arith::ConstantOp::create(builder, loc, builder.getBoolAttr(value));
47}
48
49static bool isMemref(Value v) { return isa<BaseMemRefType>(v.getType()); }
50
51/// Return "true" if the given op is guaranteed to have neither "Allocate" nor
52/// "Free" side effects.
57
58/// Return "true" if the given op has buffer semantics. I.e., it has buffer
59/// operands, buffer results and/or buffer region entry block arguments.
60static bool hasBufferSemantics(Operation *op) {
61 if (llvm::any_of(op->getOperands(), isMemref) ||
62 llvm::any_of(op->getResults(), isMemref))
63 return true;
64 for (Region &region : op->getRegions())
65 if (!region.empty())
66 if (llvm::any_of(region.front().getArguments(), isMemref))
67 return true;
68 return false;
69}
70
71//===----------------------------------------------------------------------===//
72// Backedges analysis
73//===----------------------------------------------------------------------===//
74
75namespace {
76
77/// A straight-forward program analysis which detects loop backedges induced by
78/// explicit control flow.
79class Backedges {
80public:
81 using BlockSetT = SmallPtrSet<Block *, 16>;
82 using BackedgeSetT = llvm::DenseSet<std::pair<Block *, Block *>>;
83
84public:
85 /// Constructs a new backedges analysis using the op provided.
86 Backedges(Operation *op) { recurse(op); }
87
88 /// Returns the number of backedges formed by explicit control flow.
89 size_t size() const { return edgeSet.size(); }
90
91 /// Returns the start iterator to loop over all backedges.
92 BackedgeSetT::const_iterator begin() const { return edgeSet.begin(); }
93
94 /// Returns the end iterator to loop over all backedges.
95 BackedgeSetT::const_iterator end() const { return edgeSet.end(); }
96
97private:
98 /// Enters the current block and inserts a backedge into the `edgeSet` if we
99 /// have already visited the current block. The inserted edge links the given
100 /// `predecessor` with the `current` block.
101 bool enter(Block &current, Block *predecessor) {
102 bool inserted = visited.insert(&current).second;
103 if (!inserted)
104 edgeSet.insert(std::make_pair(predecessor, &current));
105 return inserted;
106 }
107
108 /// Leaves the current block.
109 void exit(Block &current) { visited.erase(&current); }
110
111 /// Recurses into the given operation while taking all attached regions into
112 /// account.
113 void recurse(Operation *op) {
114 Block *current = op->getBlock();
115 // If the current op implements the `BranchOpInterface`, there can be
116 // cycles in the scope of all successor blocks.
117 if (isa<BranchOpInterface>(op)) {
118 for (Block *succ : current->getSuccessors())
119 recurse(*succ, current);
120 }
121 // Recurse into all distinct regions and check for explicit control-flow
122 // loops.
123 for (Region &region : op->getRegions()) {
124 if (!region.empty())
125 recurse(region.front(), current);
126 }
127 }
128
129 /// Recurses into explicit control-flow structures that are given by
130 /// the successor relation defined on the block level.
131 void recurse(Block &block, Block *predecessor) {
132 // Try to enter the current block. If this is not possible, we are
133 // currently processing this block and can safely return here.
134 if (!enter(block, predecessor))
135 return;
136
137 // Recurse into all operations and successor blocks.
138 for (Operation &op : block.getOperations())
139 recurse(&op);
140
141 // Leave the current block.
142 exit(block);
143 }
144
145 /// Stores all blocks that are currently visited and on the processing stack.
146 BlockSetT visited;
147
148 /// Stores all backedges in the format (source, target).
149 BackedgeSetT edgeSet;
150};
151
152} // namespace
153
154//===----------------------------------------------------------------------===//
155// BufferDeallocation
156//===----------------------------------------------------------------------===//
157
158namespace {
159/// The buffer deallocation transformation which ensures that all allocs in the
160/// program have a corresponding de-allocation.
161class BufferDeallocation {
162public:
163 BufferDeallocation(Operation *op, DeallocationOptions options,
164 SymbolTableCollection &symbolTables)
165 : state(op, symbolTables), options(options) {}
166
167 /// Performs the actual placement/creation of all dealloc operations.
168 LogicalResult deallocate(FunctionOpInterface op);
169
170private:
171 /// The base case for the recursive template below.
172 template <typename... T>
173 typename std::enable_if<sizeof...(T) == 0, FailureOr<Operation *>>::type
174 handleOp(Operation *op) {
175 return op;
176 }
177
178 /// Applies all the handlers of the interfaces in the template list
179 /// implemented by 'op'. In particular, if an operation implements more than
180 /// one of the interfaces in the template list, all the associated handlers
181 /// will be applied to the operation in the same order as the template list
182 /// specifies. If a handler reports a failure or removes the operation without
183 /// replacement (indicated by returning 'nullptr'), no further handlers are
184 /// applied and the return value is propagated to the caller of 'handleOp'.
185 ///
186 /// The interface handlers job is to update the deallocation state, most
187 /// importantly the ownership map and list of memrefs to potentially be
188 /// deallocated per block, but also to insert `bufferization.dealloc`
189 /// operations where needed. Obviously, no MemRefs that may be used at a later
190 /// point in the control-flow may be deallocated and the ownership map has to
191 /// be updated to reflect potential ownership changes caused by the dealloc
192 /// operation (e.g., if two interfaces on the same op insert a dealloc
193 /// operation each, the second one should query the ownership map and use them
194 /// as deallocation condition such that MemRefs already deallocated in the
195 /// first dealloc operation are not deallocated a second time (double-free)).
196 /// Note that currently only the interfaces on terminators may insert dealloc
197 /// operations and it is verified as a precondition that a terminator op must
198 /// implement exactly one of the interfaces handling dealloc insertion.
199 ///
200 /// The return value of the 'handleInterface' functions should be a
201 /// FailureOr<Operation *> indicating whether there was a failure or otherwise
202 /// returning the operation itself or a replacement operation.
203 ///
204 /// Note: The difference compared to `TypeSwitch` is that all
205 /// matching cases are applied instead of just the first match.
206 template <typename InterfaceT, typename... InterfacesU>
207 FailureOr<Operation *> handleOp(Operation *op) {
208 Operation *next = op;
209 if (auto concreteOp = dyn_cast<InterfaceT>(op)) {
210 FailureOr<Operation *> result = handleInterface(concreteOp);
211 if (failed(result))
212 return failure();
213 next = *result;
214 }
215 if (!next)
216 return FailureOr<Operation *>(nullptr);
217 return handleOp<InterfacesU...>(next);
218 }
219
220 /// Apply all supported interface handlers to the given op.
221 FailureOr<Operation *> handleAllInterfaces(Operation *op) {
222 if (auto deallocOpInterface = dyn_cast<BufferDeallocationOpInterface>(op))
223 return deallocOpInterface.process(state, options);
224
225 if (failed(verifyOperationPreconditions(op)))
226 return failure();
227
228 return handleOp<MemoryEffectOpInterface, RegionBranchOpInterface,
229 CallOpInterface, BranchOpInterface,
230 RegionBranchTerminatorOpInterface>(op);
231 }
232
233 /// Make sure that for each forwarded MemRef value, an ownership indicator
234 /// `i1` value is forwarded as well such that the successor block knows
235 /// whether the MemRef has to be deallocated.
236 ///
237 /// Example:
238 /// ```
239 /// ^bb1:
240 /// <more ops...>
241 /// cf.br ^bb2(<forward-to-bb2>)
242 /// ```
243 /// becomes
244 /// ```
245 /// // let (m, c) = getMemrefsAndConditionsToDeallocate(bb1)
246 /// // let r = getMemrefsToRetain(bb1, bb2, <forward-to-bb2>)
247 /// ^bb1:
248 /// <more ops...>
249 /// o = bufferization.dealloc m if c retain r
250 /// // replace ownership(r) with o element-wise
251 /// cf.br ^bb2(<forward-to-bb2>, o)
252 /// ```
253 FailureOr<Operation *> handleInterface(BranchOpInterface op);
254
255 /// Add an ownership indicator for every forwarding MemRef operand and result.
256 /// Nested regions never take ownership of MemRefs owned by a parent region
257 /// (neither via forwarding operand nor when captured implicitly when the
258 /// region is not isolated from above). Ownerships will only be passed to peer
259 /// regions (when an operation has multiple regions, such as scf.while), or to
260 /// parent regions.
261 /// Note that the block arguments in the nested region are currently handled
262 /// centrally in the 'dealloc' function, but better interface support could
263 /// allow us to do this here for the nested region specifically to reduce the
264 /// amount of assumptions we make on the structure of ops implementing this
265 /// interface.
266 ///
267 /// Example:
268 /// ```
269 /// %ret = scf.for %i = %c0 to %c10 step %c1 iter_args(%m = %memref) {
270 /// <more ops...>
271 /// scf.yield %m : memref<2xi32>, i1
272 /// }
273 /// ```
274 /// becomes
275 /// ```
276 /// %ret:2 = scf.for %i = %c0 to %c10 step %c1
277 /// iter_args(%m = %memref, %own = %false) {
278 /// <more ops...>
279 /// // Note that the scf.yield is handled by the
280 /// // RegionBranchTerminatorOpInterface (not this handler)
281 /// // let o = getMemrefWithUniqueOwnership(%own)
282 /// scf.yield %m, o : memref<2xi32>, i1
283 /// }
284 /// ```
285 FailureOr<Operation *> handleInterface(RegionBranchOpInterface op);
286
287 /// If the private-function-dynamic-ownership pass option is enabled and the
288 /// called function is private, additional results are added for each MemRef
289 /// result to pass the dynamic ownership indicator along. Otherwise, updates
290 /// the ownership map and list of memrefs to be deallocated according to the
291 /// function boundary ABI, i.e., assume ownership of all returned MemRefs.
292 ///
293 /// Example (assume `private-function-dynamic-ownership` is enabled):
294 /// ```
295 /// func.func @f(%arg0: memref<2xi32>) -> memref<2xi32> {...}
296 /// func.func private @g(%arg0: memref<2xi32>) -> memref<2xi32> {...}
297 ///
298 /// %ret_f = func.call @f(%memref) : (memref<2xi32>) -> memref<2xi32>
299 /// %ret_g = func.call @g(%memref) : (memref<2xi32>) -> memref<2xi32>
300 /// ```
301 /// becomes
302 /// ```
303 /// func.func @f(%arg0: memref<2xi32>) -> memref<2xi32> {...}
304 /// func.func private @g(%arg0: memref<2xi32>) -> (memref<2xi32>, i1) {...}
305 ///
306 /// %ret_f = func.call @f(%memref) : (memref<2xi32>) -> memref<2xi32>
307 /// // set ownership(%ret_f) := true
308 /// // remember to deallocate %ret_f
309 ///
310 /// %ret_g:2 = func.call @g(%memref) : (memref<2xi32>) -> (memref<2xi32>, i1)
311 /// // set ownership(%ret_g#0) := %ret_g#1
312 /// // remember to deallocate %ret_g if it comes with ownership
313 /// ```
314 FailureOr<Operation *> handleInterface(CallOpInterface op);
315
316 /// Takes care of allocation and free side-effects. It collects allocated
317 /// MemRefs that we have to add to manually deallocate, but also removes
318 /// values again that are already deallocated before the end of the block. It
319 /// also updates the ownership map accordingly.
320 ///
321 /// Example:
322 /// ```
323 /// %alloc = memref.alloc()
324 /// %alloca = memref.alloca()
325 /// ```
326 /// becomes
327 /// ```
328 /// %alloc = memref.alloc()
329 /// %alloca = memref.alloca()
330 /// // set ownership(alloc) := true
331 /// // set ownership(alloca) := false
332 /// // remember to deallocate %alloc
333 /// ```
334 FailureOr<Operation *> handleInterface(MemoryEffectOpInterface op);
335
336 /// Takes care that the function boundary ABI is adhered to if the parent
337 /// operation implements FunctionOpInterface, inserting a
338 /// `bufferization.clone` if necessary, and inserts the
339 /// `bufferization.dealloc` operation according to the ops operands.
340 ///
341 /// Example:
342 /// ```
343 /// ^bb1:
344 /// <more ops...>
345 /// func.return <return-vals>
346 /// ```
347 /// becomes
348 /// ```
349 /// // let (m, c) = getMemrefsAndConditionsToDeallocate(bb1)
350 /// // let r = getMemrefsToRetain(bb1, nullptr, <return-vals>)
351 /// ^bb1:
352 /// <more ops...>
353 /// o = bufferization.dealloc m if c retain r
354 /// func.return <return-vals>
355 /// (if !isFunctionWithoutDynamicOwnership: append o)
356 /// ```
357 FailureOr<Operation *> handleInterface(RegionBranchTerminatorOpInterface op);
358
359 /// Construct a new operation which is exactly the same as the passed 'op'
360 /// except that the OpResults list is appended by new results of the passed
361 /// 'types'.
362 /// TODO: ideally, this would be implemented using an OpInterface because it
363 /// is used to append function results, loop iter_args, etc. and thus makes
364 /// some assumptions that the variadic list of those is at the end of the
365 /// OpResults range.
366 Operation *appendOpResults(Operation *op, ArrayRef<Type> types);
367
368 /// A convenience template for the generic 'appendOpResults' function above to
369 /// avoid manual casting of the result.
370 template <typename OpTy>
371 OpTy appendOpResults(OpTy op, ArrayRef<Type> types) {
372 return cast<OpTy>(appendOpResults(op.getOperation(), types));
373 }
374
375 /// Performs deallocation of a single basic block. This is a private function
376 /// because some internal data structures have to be set up beforehand and
377 /// this function has to be called on blocks in a region in dominance order.
378 LogicalResult deallocate(Block *block);
379
380 /// After all relevant interfaces of an operation have been processed by the
381 /// 'handleInterface' functions, this function sets the ownership of operation
382 /// results that have not been set yet by the 'handleInterface' functions. It
383 /// generally assumes that each result can alias with every operand of the
384 /// operation, if there are MemRef typed results but no MemRef operands it
385 /// assigns 'false' as ownership. This happens, e.g., for the
386 /// memref.get_global operation. It would also be possible to query some alias
387 /// analysis to get more precise ownerships, however, the analysis would have
388 /// to be updated according to the IR modifications this pass performs (e.g.,
389 /// re-building operations to have more result values, inserting clone
390 /// operations, etc.).
391 void populateRemainingOwnerships(Operation *op);
392
393 /// Given an SSA value of MemRef type, returns the same of a new SSA value
394 /// which has 'Unique' ownership where the ownership indicator is guaranteed
395 /// to be always 'true'.
396 Value materializeMemrefWithGuaranteedOwnership(OpBuilder &builder,
397 Value memref, Block *block);
398
399 /// Returns whether the given operation implements FunctionOpInterface, has
400 /// private visibility, and the private-function-dynamic-ownership pass option
401 /// is enabled.
402 bool isFunctionWithoutDynamicOwnership(Operation *op);
403
404 /// Given an SSA value of MemRef type, this function queries the
405 /// BufferDeallocationOpInterface of the defining operation of 'memref' for a
406 /// materialized ownership indicator for 'memref'. If the op does not
407 /// implement the interface or if the block for which the materialized value
408 /// is requested does not match the block in which 'memref' is defined, the
409 /// default implementation in
410 /// `DeallocationState::getMemrefWithUniqueOwnership` is queried instead.
411 std::pair<Value, Value>
412 materializeUniqueOwnership(OpBuilder &builder, Value memref, Block *block);
413
414 /// Checks all the preconditions for operations implementing the
415 /// FunctionOpInterface that have to hold for the deallocation to be
416 /// applicable:
417 /// (1) Checks that there are not explicit control flow loops.
418 static LogicalResult verifyFunctionPreconditions(FunctionOpInterface op);
419
420 /// Checks all the preconditions for operations inside the region of
421 /// operations implementing the FunctionOpInterface that have to hold for the
422 /// deallocation to be applicable:
423 /// (1) Checks if all operations that have at least one attached region
424 /// implement the RegionBranchOpInterface. This is not required in edge cases,
425 /// where we have a single attached region and the parent operation has no
426 /// results.
427 /// (2) Checks that no deallocations already exist. Especially deallocations
428 /// in nested regions are not properly supported yet since this requires
429 /// ownership of the memref to be transferred to the nested region, which does
430 /// not happen by default. This constrained can be lifted in the future.
431 /// (3) Checks that terminators with more than one successor except
432 /// `cf.cond_br` are not present and that either BranchOpInterface or
433 /// RegionBranchTerminatorOpInterface is implemented.
434 static LogicalResult verifyOperationPreconditions(Operation *op);
435
436 /// When the 'private-function-dynamic-ownership' pass option is enabled,
437 /// additional `i1` return values are added for each MemRef result in the
438 /// function signature. This function takes care of updating the
439 /// `function_type` attribute of the function according to the actually
440 /// returned values from the terminators.
441 static LogicalResult updateFunctionSignature(FunctionOpInterface op);
442
443private:
444 /// Collects all analysis state and including liveness, caches, ownerships of
445 /// already processed values and operations, and the MemRefs that have to be
446 /// deallocated at the end of each block.
447 DeallocationState state;
448
449 /// Collects all pass options in a single place.
450 DeallocationOptions options;
451};
452
453} // namespace
454
455//===----------------------------------------------------------------------===//
456// BufferDeallocation Implementation
457//===----------------------------------------------------------------------===//
458
459std::pair<Value, Value>
460BufferDeallocation::materializeUniqueOwnership(OpBuilder &builder, Value memref,
461 Block *block) {
462 // The interface can only materialize ownership indicators in the same block
463 // as the defining op.
464 if (memref.getParentBlock() != block)
465 return state.getMemrefWithUniqueOwnership(builder, memref, block);
466
467 Operation *owner = memref.getDefiningOp();
468 if (!owner)
469 owner = memref.getParentBlock()->getParentOp();
470
471 // If the op implements the interface, query it for a materialized ownership
472 // value.
473 if (auto deallocOpInterface = dyn_cast<BufferDeallocationOpInterface>(owner))
474 return deallocOpInterface.materializeUniqueOwnershipForMemref(
475 state, options, builder, memref);
476
477 // Otherwise use the default implementation.
478 return state.getMemrefWithUniqueOwnership(builder, memref, block);
479}
480
481LogicalResult
482BufferDeallocation::verifyFunctionPreconditions(FunctionOpInterface op) {
483 // (1) Ensure that there are supported loops only (no explicit control flow
484 // loops).
485 Backedges backedges(op);
486 if (backedges.size()) {
487 op->emitError("Only structured control-flow loops are supported.");
488 return failure();
489 }
490
491 return success();
492}
493
494LogicalResult BufferDeallocation::verifyOperationPreconditions(Operation *op) {
495 // We do not care about ops that do not operate on buffers and have no
496 // Allocate/Free side effect.
498 return success();
499
500 // (1) The pass does not work properly when deallocations are already present.
501 // Alternatively, we could also remove all deallocations as a pre-pass.
502 if (isa<DeallocOp>(op))
503 return op->emitError(
504 "No deallocation operations must be present when running this pass!");
505
506 // (2) Memory side effects of unregistered ops are unknown. In particular, we
507 // do not know whether an unregistered op allocates memory or not.
508 // - Ops with recursive memory effects are allowed. All nested ops in the
509 // regions of `op` will be analyzed separately.
510 // - Call ops are allowed even though they typically do not implement the
511 // MemoryEffectOpInterface. They usually do not have side effects apart
512 // from the callee, which will be analyzed separately. (This is similar to
513 // "recursive memory effects".)
514 if (hasUnknownEffects(op) && !isa<CallOpInterface>(op))
515 return op->emitError(
516 "ops with unknown memory side effects are not supported");
517
518 // (3) Check that the control flow structures are supported.
519 auto regions = op->getRegions();
520 // Check that if the operation has at
521 // least one region it implements the RegionBranchOpInterface. If there
522 // is an operation that does not fulfill this condition, we cannot apply
523 // the deallocation steps. Furthermore, we accept cases, where we have a
524 // region that returns no results, since, in that case, the intra-region
525 // control flow does not affect the transformation.
526 size_t size = regions.size();
527 if (((size == 1 && !op->getResults().empty()) || size > 1) &&
528 !dyn_cast<RegionBranchOpInterface>(op)) {
529 return op->emitError("All operations with attached regions need to "
530 "implement the RegionBranchOpInterface.");
531 }
532
533 // (3) Check that terminators with more than one successor except `cf.cond_br`
534 // are not present and that either BranchOpInterface or
535 // RegionBranchTerminatorOpInterface is implemented.
537 return op->emitError("NoTerminator trait is not supported");
538
539 if (op->hasTrait<OpTrait::IsTerminator>()) {
540 // Either one of those interfaces has to be implemented on terminators, but
541 // not both.
542 if (!isa<BranchOpInterface, RegionBranchTerminatorOpInterface>(op) ||
543 (isa<BranchOpInterface>(op) &&
544 isa<RegionBranchTerminatorOpInterface>(op)))
545
546 return op->emitError(
547 "Terminators must implement either BranchOpInterface or "
548 "RegionBranchTerminatorOpInterface (but not both)!");
549
550 // We only support terminators with 0 or 1 successors for now and
551 // special-case the conditional branch op.
552 if (op->getSuccessors().size() > 1)
553
554 return op->emitError("Terminators with more than one successor "
555 "are not supported!");
556 }
557
558 return success();
559}
560
561LogicalResult
562BufferDeallocation::updateFunctionSignature(FunctionOpInterface op) {
563 SmallVector<TypeRange> returnOperandTypes(llvm::map_range(
564 op.getFunctionBody().getOps<RegionBranchTerminatorOpInterface>(),
565 [&](RegionBranchTerminatorOpInterface branchOp) {
566 return branchOp.getSuccessorOperands(RegionSuccessor::parent())
567 .getTypes();
568 }));
569 if (!llvm::all_equal(returnOperandTypes))
570 return op->emitError(
571 "there are multiple return operations with different operand types");
572
573 TypeRange resultTypes = op.getResultTypes();
574 // Check if we found a return operation because that doesn't necessarily
575 // always have to be the case, e.g., consider a function with one block that
576 // has a cf.br at the end branching to itself again (i.e., an infinite loop).
577 // In that case we don't want to crash but just not update the return types.
578 if (!returnOperandTypes.empty())
579 resultTypes = returnOperandTypes[0];
580
581 op.setFunctionTypeAttr(TypeAttr::get(FunctionType::get(
582 op->getContext(), op.getFunctionBody().front().getArgumentTypes(),
583 resultTypes)));
584
585 return success();
586}
587
588LogicalResult BufferDeallocation::deallocate(FunctionOpInterface op) {
589 // Stop and emit a proper error message if we don't support the input IR.
590 if (failed(verifyFunctionPreconditions(op)))
591 return failure();
592
593 // Process the function block by block.
595 [&](Block *block) {
596 if (failed(deallocate(block)))
597 return WalkResult::interrupt();
598 return WalkResult::advance();
599 });
600 if (result.wasInterrupted())
601 return failure();
602
603 // Update the function signature if the function is private, dynamic ownership
604 // is enabled, and the function has memrefs as arguments or results.
605 return updateFunctionSignature(op);
606}
607
608LogicalResult BufferDeallocation::deallocate(Block *block) {
609 OpBuilder builder = OpBuilder::atBlockBegin(block);
610
611 // Compute liveness transfers of ownership to this block.
612 SmallVector<Value> liveMemrefs;
613 state.getLiveMemrefsIn(block, liveMemrefs);
614 for (auto li : liveMemrefs) {
615 // Ownership of implicitly captured memrefs from other regions is never
616 // taken, but ownership of memrefs in the same region (but different block)
617 // is taken.
618 if (li.getParentRegion() == block->getParent()) {
619 state.updateOwnership(li, state.getOwnership(li, li.getParentBlock()),
620 block);
621 state.addMemrefToDeallocate(li, block);
622 continue;
623 }
624
625 if (li.getParentRegion()->isProperAncestor(block->getParent())) {
626 Value falseVal = buildBoolValue(builder, li.getLoc(), false);
627 state.updateOwnership(li, falseVal, block);
628 }
629 }
630
631 for (unsigned i = 0, e = block->getNumArguments(); i < e; ++i) {
632 BlockArgument arg = block->getArgument(i);
633 if (!isMemref(arg))
634 continue;
635
636 // Adhere to function boundary ABI: no ownership of function argument
637 // MemRefs is taken.
638 if (isa<FunctionOpInterface>(block->getParentOp()) &&
639 block->isEntryBlock()) {
640 Value newArg = buildBoolValue(builder, arg.getLoc(), false);
641 state.updateOwnership(arg, newArg);
642 state.addMemrefToDeallocate(arg, block);
643 continue;
644 }
645
646 // Pass MemRef ownerships along via `i1` values.
647 Value newArg = block->addArgument(builder.getI1Type(), arg.getLoc());
648 state.updateOwnership(arg, newArg);
649 state.addMemrefToDeallocate(arg, block);
650 }
651
652 // For each operation in the block, handle the interfaces that affect aliasing
653 // and ownership of memrefs.
654 for (Operation &op : llvm::make_early_inc_range(*block)) {
655 FailureOr<Operation *> result = handleAllInterfaces(&op);
656 if (failed(result))
657 return failure();
658 if (!*result)
659 continue;
660
661 populateRemainingOwnerships(*result);
662 }
663
664 // TODO: if block has no terminator, handle dealloc insertion here.
665 return success();
666}
667
668Operation *BufferDeallocation::appendOpResults(Operation *op,
669 ArrayRef<Type> types) {
670 SmallVector<Type> newTypes(op->getResultTypes());
671 newTypes.append(types.begin(), types.end());
672 auto *newOp = Operation::create(op->getLoc(), op->getName(), newTypes,
673 op->getOperands(), op->getAttrDictionary(),
675 op->getSuccessors(), op->getNumRegions());
676 for (auto [oldRegion, newRegion] :
677 llvm::zip(op->getRegions(), newOp->getRegions()))
678 newRegion.takeBody(oldRegion);
679
680 OpBuilder(op).insert(newOp);
681 op->replaceAllUsesWith(newOp->getResults().take_front(op->getNumResults()));
682 op->erase();
683
684 return newOp;
685}
686
687FailureOr<Operation *>
688BufferDeallocation::handleInterface(RegionBranchOpInterface op) {
689 OpBuilder builder = OpBuilder::atBlockBegin(op->getBlock());
690
691 // TODO: the RegionBranchOpInterface does not provide all the necessary
692 // methods to perform this transformation without additional assumptions on
693 // the structure. In particular, that
694 // * additional values to be passed to the next region can be added to the end
695 // of the operand list, the end of the block argument list, and the end of
696 // the result value list. However, it seems to be the general guideline for
697 // operations implementing this interface to follow this structure.
698 // * and that the block arguments and result values match the forwarded
699 // operands one-to-one (i.e., that there are no other values appended to the
700 // front).
701 // These assumptions are satisfied by the `scf.if`, `scf.for`, and `scf.while`
702 // operations.
703
705 op.getSuccessorRegions(RegionBranchPoint::parent(), regions);
706 assert(!regions.empty() && "Must have at least one successor region");
707 SmallVector<Value> entryOperands(
708 op.getEntrySuccessorOperands(regions.front()));
709 unsigned numMemrefOperands = llvm::count_if(entryOperands, isMemref);
710
711 // No ownership is acquired for any MemRefs that are passed to the region from
712 // the outside.
713 Value falseVal = buildBoolValue(builder, op.getLoc(), false);
714 op->insertOperands(op->getNumOperands(),
715 SmallVector<Value>(numMemrefOperands, falseVal));
716
717 int counter = op->getNumResults();
718 unsigned numMemrefResults = llvm::count_if(op->getResults(), isMemref);
719 SmallVector<Type> ownershipResults(numMemrefResults, builder.getI1Type());
720 RegionBranchOpInterface newOp = appendOpResults(op, ownershipResults);
721
722 for (auto result : llvm::make_filter_range(newOp->getResults(), isMemref)) {
723 state.updateOwnership(result, newOp->getResult(counter++));
724 state.addMemrefToDeallocate(result, newOp->getBlock());
725 }
726
727 return newOp.getOperation();
728}
729
730Value BufferDeallocation::materializeMemrefWithGuaranteedOwnership(
731 OpBuilder &builder, Value memref, Block *block) {
732 // First, make sure we at least have 'Unique' ownership already.
733 std::pair<Value, Value> newMemrefAndOnwership =
734 materializeUniqueOwnership(builder, memref, block);
735 Value newMemref = newMemrefAndOnwership.first;
736 Value condition = newMemrefAndOnwership.second;
737
738 // Avoid inserting additional IR if ownership is already guaranteed. In
739 // particular, this is already the case when we had 'Unknown' ownership
740 // initially and a clone was inserted to get to 'Unique' ownership.
741 if (matchPattern(condition, m_One()))
742 return newMemref;
743
744 // Insert a runtime check and only clone if we still don't have ownership at
745 // runtime.
746 Value maybeClone = scf::IfOp::create(
747 builder, memref.getLoc(), condition,
748 [&](OpBuilder &builder, Location loc) {
749 scf::YieldOp::create(builder, loc, newMemref);
750 },
751 [&](OpBuilder &builder, Location loc) {
752 Value clone = bufferization::CloneOp::create(
753 builder, loc, newMemref);
754 scf::YieldOp::create(builder, loc, clone);
755 })
756 .getResult(0);
757 Value trueVal = buildBoolValue(builder, memref.getLoc(), true);
758 state.updateOwnership(maybeClone, trueVal);
759 state.addMemrefToDeallocate(maybeClone, maybeClone.getParentBlock());
760 return maybeClone;
761}
762
763FailureOr<Operation *>
764BufferDeallocation::handleInterface(BranchOpInterface op) {
765 if (op->getNumSuccessors() > 1)
766 return op->emitError("BranchOpInterface operations with multiple "
767 "successors are not supported yet");
768
769 if (op->getNumSuccessors() != 1)
770 return emitError(op.getLoc(),
771 "only BranchOpInterface operations with exactly "
772 "one successor are supported yet");
773
774 if (op.getSuccessorOperands(0).getProducedOperandCount() > 0)
775 return op.emitError("produced operands are not supported");
776
777 // Collect the values to deallocate and retain and use them to create the
778 // dealloc operation.
779 Block *block = op->getBlock();
780 OpBuilder builder(op);
781 SmallVector<Value> memrefs, conditions, toRetain;
782 if (failed(state.getMemrefsAndConditionsToDeallocate(
783 builder, op.getLoc(), block, memrefs, conditions)))
784 return failure();
785
786 OperandRange forwardedOperands =
787 op.getSuccessorOperands(0).getForwardedOperands();
788 state.getMemrefsToRetain(block, op->getSuccessor(0), forwardedOperands,
789 toRetain);
790
791 auto deallocOp = bufferization::DeallocOp::create(
792 builder, op.getLoc(), memrefs, conditions, toRetain);
793
794 // We want to replace the current ownership of the retained values with the
795 // result values of the dealloc operation as they are always unique.
796 state.resetOwnerships(deallocOp.getRetained(), block);
797 for (auto [retained, ownership] :
798 llvm::zip(deallocOp.getRetained(), deallocOp.getUpdatedConditions())) {
799 state.updateOwnership(retained, ownership, block);
800 }
801
802 unsigned numAdditionalReturns = llvm::count_if(forwardedOperands, isMemref);
803 SmallVector<Value> newOperands(forwardedOperands);
804 auto additionalConditions =
805 deallocOp.getUpdatedConditions().take_front(numAdditionalReturns);
806 newOperands.append(additionalConditions.begin(), additionalConditions.end());
807 op.getSuccessorOperands(0).getMutableForwardedOperands().assign(newOperands);
808
809 return op.getOperation();
810}
811
812FailureOr<Operation *> BufferDeallocation::handleInterface(CallOpInterface op) {
813 OpBuilder builder(op);
814
815 // Lookup the function operation and check if it has private visibility. If
816 // the function is referenced by SSA value instead of a Symbol, it's assumed
817 // to be public. (And we cannot easily change the type of the SSA value
818 // anyway.)
819 Operation *funcOp = op.resolveCallableInTable(state.getSymbolTable());
820 bool isPrivate = false;
821 if (auto symbol = dyn_cast_or_null<SymbolOpInterface>(funcOp))
822 isPrivate = symbol.isPrivate() && !symbol.isDeclaration();
823
824 // If the private-function-dynamic-ownership option is enabled and we are
825 // calling a private function, we need to add an additional `i1` result for
826 // each MemRef result to dynamically pass the current ownership indicator
827 // rather than adhering to the function boundary ABI.
828 if (options.privateFuncDynamicOwnership && isPrivate) {
829 unsigned numMemrefs = llvm::count_if(op->getResults(), isMemref);
830 SmallVector<Type> ownershipTypesToAppend(numMemrefs, builder.getI1Type());
831 unsigned ownershipCounter = op->getNumResults();
832 op = appendOpResults(op, ownershipTypesToAppend);
833
834 for (auto result : llvm::make_filter_range(op->getResults(), isMemref)) {
835 state.updateOwnership(result, op->getResult(ownershipCounter++));
836 state.addMemrefToDeallocate(result, result.getParentBlock());
837 }
838
839 return op.getOperation();
840 }
841
842 // According to the function boundary ABI we are guaranteed to get ownership
843 // of all MemRefs returned by the function. Thus we set ownership to constant
844 // 'true' and remember to deallocate it.
845 Value trueVal = buildBoolValue(builder, op.getLoc(), true);
846 for (auto result : llvm::make_filter_range(op->getResults(), isMemref)) {
847 state.updateOwnership(result, trueVal);
848 state.addMemrefToDeallocate(result, result.getParentBlock());
849 }
850
851 return op.getOperation();
852}
853
854FailureOr<Operation *>
855BufferDeallocation::handleInterface(MemoryEffectOpInterface op) {
856 auto *block = op->getBlock();
857 OpBuilder builder = OpBuilder::atBlockBegin(block);
858
859 for (auto operand : llvm::make_filter_range(op->getOperands(), isMemref)) {
860 if (op.getEffectOnValue<MemoryEffects::Free>(operand).has_value()) {
861 // The bufferization.manual_deallocation attribute can be attached to ops
862 // with an allocation and/or deallocation side effect. It indicates that
863 // the op is under a "manual deallocation" scheme. Deallocation ops are
864 // usually forbidden in the input IR (not supported by the buffer
865 // deallocation pass). However, if they are under manual deallocation,
866 // they can be safely ignored by the buffer deallocation pass.
867 if (!op->hasAttr(BufferizationDialect::kManualDeallocation))
868 return op->emitError(
869 "memory free side-effect on MemRef value not supported!");
870
871 // Buffers that were allocated under "manual deallocation" may be
872 // manually deallocated. We insert a runtime assertion to cover certain
873 // cases of invalid IR where an automatically managed buffer allocation
874 // is manually deallocated. This is not a bulletproof check!
875 OpBuilder::InsertionGuard g(builder);
876 builder.setInsertionPoint(op);
877 Ownership ownership = state.getOwnership(operand, block);
878 if (ownership.isUnique()) {
879 Value ownershipInverted = arith::XOrIOp::create(
880 builder, op.getLoc(), ownership.getIndicator(),
881 buildBoolValue(builder, op.getLoc(), true));
882 cf::AssertOp::create(builder, op.getLoc(), ownershipInverted,
883 "expected that the block does not have ownership");
884 }
885 }
886 }
887
888 for (auto res : llvm::make_filter_range(op->getResults(), isMemref)) {
889 auto allocEffect = op.getEffectOnValue<MemoryEffects::Allocate>(res);
890 if (allocEffect.has_value()) {
891 if (isa<SideEffects::AutomaticAllocationScopeResource>(
892 allocEffect->getResource())) {
893 // Make sure that the ownership of auto-managed allocations is set to
894 // false. This is important for operations that have at least one memref
895 // typed operand. E.g., consider an operation like `bufferization.clone`
896 // that lowers to a `memref.alloca + memref.copy` instead of a
897 // `memref.alloc`. If we wouldn't set the ownership of the result here,
898 // the default ownership population in `populateRemainingOwnerships`
899 // would assume aliasing with the MemRef operand.
900 state.resetOwnerships(res, block);
901 state.updateOwnership(res, buildBoolValue(builder, op.getLoc(), false));
902 continue;
903 }
904
905 if (op->hasAttr(BufferizationDialect::kManualDeallocation)) {
906 // This allocation will be deallocated manually. Assign an ownership of
907 // "false", so that it will never be deallocated by the buffer
908 // deallocation pass.
909 state.resetOwnerships(res, block);
910 state.updateOwnership(res, buildBoolValue(builder, op.getLoc(), false));
911 continue;
912 }
913
914 state.updateOwnership(res, buildBoolValue(builder, op.getLoc(), true));
915 state.addMemrefToDeallocate(res, block);
916 }
917 }
918
919 return op.getOperation();
920}
921
922FailureOr<Operation *>
923BufferDeallocation::handleInterface(RegionBranchTerminatorOpInterface op) {
924 OpBuilder builder(op);
925
926 // If this is a return operation of a function that is not private or the
927 // dynamic function boundary ownership is disabled, we need to return memref
928 // values for which we have guaranteed ownership to pass on to adhere to the
929 // function boundary ABI.
930 bool funcWithoutDynamicOwnership =
931 isFunctionWithoutDynamicOwnership(op->getParentOp());
932 if (funcWithoutDynamicOwnership) {
933 for (OpOperand &val : op->getOpOperands()) {
934 if (!isMemref(val.get()))
935 continue;
936
937 val.set(materializeMemrefWithGuaranteedOwnership(builder, val.get(),
938 op->getBlock()));
939 }
940 }
941
942 // TODO: getSuccessorRegions is not implemented by all operations we care
943 // about, but we would need to check how many successors there are and under
944 // which condition they are taken, etc.
945
946 MutableOperandRange operands =
947 op.getMutableSuccessorOperands(RegionSuccessor::parent());
948
949 SmallVector<Value> updatedOwnerships;
951 state, op, operands.getAsOperandRange(), updatedOwnerships);
952 if (failed(result) || !*result)
953 return result;
954
955 // Add an additional operand for every MemRef for the ownership indicator.
956 if (!funcWithoutDynamicOwnership) {
957 SmallVector<Value> newOperands{operands.getAsOperandRange()};
958 newOperands.append(updatedOwnerships.begin(), updatedOwnerships.end());
959 operands.assign(newOperands);
960 }
961
962 return op.getOperation();
963}
964
965bool BufferDeallocation::isFunctionWithoutDynamicOwnership(Operation *op) {
966 auto funcOp = dyn_cast<FunctionOpInterface>(op);
967 return funcOp && (!options.privateFuncDynamicOwnership ||
968 !funcOp.isPrivate() || funcOp.isExternal());
969}
970
971void BufferDeallocation::populateRemainingOwnerships(Operation *op) {
972 for (auto res : op->getResults()) {
973 if (!isMemref(res))
974 continue;
975 if (!state.getOwnership(res, op->getBlock()).isUninitialized())
976 continue;
977
978 // The op does not allocate memory, otherwise, it would have been assigned
979 // an ownership during `handleInterface`. Assume the result may alias with
980 // any memref operand and thus combine all their ownerships.
981 for (auto operand : op->getOperands()) {
982 if (!isMemref(operand))
983 continue;
984
985 state.updateOwnership(
986 res, state.getOwnership(operand, operand.getParentBlock()),
987 op->getBlock());
988 }
989
990 // If the ownership value is still uninitialized (e.g., because the op has
991 // no memref operands), assume that no ownership is taken. E.g., this is the
992 // case for "memref.get_global".
993 //
994 // Note: This can lead to memory leaks if memory side effects are not
995 // properly specified on the op.
996 if (state.getOwnership(res, op->getBlock()).isUninitialized()) {
997 OpBuilder builder(op);
998 state.updateOwnership(res, buildBoolValue(builder, op->getLoc(), false));
999 }
1000 }
1001}
1002
1003//===----------------------------------------------------------------------===//
1004// OwnershipBasedBufferDeallocationPass
1005//===----------------------------------------------------------------------===//
1006
1007namespace {
1008
1009/// The actual buffer deallocation pass that inserts and moves dealloc nodes
1010/// into the right positions. Furthermore, it inserts additional clones if
1011/// necessary. It uses the algorithm described at the top of the file.
1012struct OwnershipBasedBufferDeallocationPass
1013 : public bufferization::impl::OwnershipBasedBufferDeallocationPassBase<
1014 OwnershipBasedBufferDeallocationPass> {
1015 using Base::Base;
1016
1017 void runOnOperation() override {
1018 DeallocationOptions options;
1019 options.privateFuncDynamicOwnership = privateFuncDynamicOwnership;
1020
1021 mlir::SymbolTableCollection symbolTables;
1022
1023 auto status = getOperation()->walk([&](func::FuncOp func) {
1024 if (func.isExternal())
1025 return WalkResult::skip();
1026
1027 if (failed(deallocateBuffersOwnershipBased(func, options, symbolTables)))
1028 return WalkResult::interrupt();
1029
1030 return WalkResult::advance();
1031 });
1032 if (status.wasInterrupted())
1033 signalPassFailure();
1034 }
1035};
1036
1037} // namespace
1038
1039//===----------------------------------------------------------------------===//
1040// Implement bufferization API
1041//===----------------------------------------------------------------------===//
1042
1044 FunctionOpInterface op, DeallocationOptions options,
1045 SymbolTableCollection &symbolTables) {
1046 // Gather all required allocation nodes and prepare the deallocation phase.
1047 BufferDeallocation deallocation(op, options, symbolTables);
1048
1049 // Place all required temporary clone and dealloc nodes.
1050 return deallocation.deallocate(op);
1051}
return success()
static bool isMemref(Value v)
static Value buildBoolValue(OpBuilder &builder, Location loc, bool value)
*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 bool isMemref(Value v)
static Value buildBoolValue(OpBuilder &builder, Location loc, bool value)
static bool hasBufferSemantics(Operation *op)
Return "true" if the given op has buffer semantics.
static bool hasNeitherAllocateNorFreeSideEffect(Operation *op)
Return "true" if the given op is guaranteed to have neither "Allocate" nor "Free" side effects.
static llvm::ManagedStatic< PassManagerOptions > options
This class represents an argument of a Block.
Definition Value.h:309
Location getLoc() const
Return the location for this argument.
Definition Value.h:324
Block represents an ordered list of Operations.
Definition Block.h:33
BlockArgument getArgument(unsigned i)
Definition Block.h:139
unsigned getNumArguments()
Definition Block.h:138
Region * getParent() const
Provide a 'getParent' method for ilist_node_with_parent methods.
Definition Block.cpp:27
OpListType & getOperations()
Definition Block.h:147
SuccessorRange getSuccessors()
Definition Block.h:280
BlockArgument addArgument(Type type, Location loc)
Add one value to the argument list.
Definition Block.cpp:158
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
BoolAttr getBoolAttr(bool value)
Definition Builders.cpp:100
IntegerType getI1Type()
Definition Builders.cpp:53
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
This class provides a mutable adaptor for a range of operands.
Definition ValueRange.h:118
OperandRange getAsOperandRange() const
Explicit conversion to an OperandRange.
void assign(ValueRange values)
Assign this range to the given values.
RAII guard to reset the insertion point of the builder when destroyed.
Definition Builders.h:348
This class helps build Operations.
Definition Builders.h:207
static OpBuilder atBlockBegin(Block *block, Listener *listener=nullptr)
Create a builder and set the insertion point to before the first operation in the block but still ins...
Definition Builders.h:240
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:398
Operation * insert(Operation *op)
Insert the given operation at the current insertion point and return it.
Definition Builders.cpp:421
This class represents an operand of an operation.
Definition Value.h:257
This class provides the API for ops that are known to be terminators.
This class indicates that the regions associated with this op don't have terminators.
This class implements the operand iterators for the Operation class.
Definition ValueRange.h:43
Operation is the basic unit of execution within MLIR.
Definition Operation.h:88
DictionaryAttr getAttrDictionary()
Return all of the attributes on this operation as a DictionaryAttr.
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
Definition Operation.h:749
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:213
unsigned getNumRegions()
Returns the number of regions held by this operation.
Definition Operation.h:674
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:223
static Operation * create(Location location, OperationName name, TypeRange resultTypes, ValueRange operands, NamedAttrList &&attributes, OpaqueProperties properties, BlockRange successors, unsigned numRegions)
Create a new Operation with the specific fields.
Definition Operation.cpp:67
InFlightDiagnostic emitError(const Twine &message={})
Emit an error about fatal conditions with this operation, reporting up to any diagnostic handlers tha...
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:119
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
Definition Operation.h:677
result_type_range getResultTypes()
Definition Operation.h:428
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:378
void replaceAllUsesWith(ValuesT &&values)
Replace all uses of results of this operation with the provided 'values'.
Definition Operation.h:272
SuccessorRange getSuccessors()
Definition Operation.h:703
result_range getResults()
Definition Operation.h:415
OpaqueProperties getPropertiesStorage()
Returns the properties storage.
Definition Operation.h:900
void erase()
Remove this operation from its parent block and delete it.
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:404
static constexpr RegionBranchPoint parent()
Returns an instance of RegionBranchPoint representing the parent operation.
static RegionSuccessor parent()
Initialize a successor that branches after/out of the parent operation.
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
This class represents a collection of SymbolTables.
This class provides an abstraction over the various different ranges of value types.
Definition TypeRange.h:37
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
static WalkResult skip()
Definition WalkResult.h:48
static WalkResult advance()
Definition WalkResult.h:47
static WalkResult interrupt()
Definition WalkResult.h:46
This class is used to track the ownership of values.
bool isUnique() const
Check if this ownership value is in the 'Unique' state.
Value getIndicator() const
If this ownership value is in 'Unique' state, this function can be used to get the indicator paramete...
FailureOr< Operation * > insertDeallocOpForReturnLike(DeallocationState &state, Operation *op, ValueRange operands, SmallVectorImpl< Value > &updatedOperandOwnerships)
Insert a bufferization.dealloc operation right before op which has to be a terminator without any suc...
LogicalResult deallocateBuffersOwnershipBased(FunctionOpInterface op, DeallocationOptions options, SymbolTableCollection &symbolTables)
Run the ownership-based buffer deallocation.
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:573
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
Definition Matchers.h:490
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
detail::constant_int_predicate_matcher m_One()
Matches a constant scalar / vector splat / tensor splat integer one.
Definition Matchers.h:478
bool hasUnknownEffects(Operation *op)
Return "true" if op has unknown effects.
bool mightHaveEffect(Operation *op)
Returns "true" if op might have an effect of type EffectTy.
This iterator enumerates elements according to their dominance relationship.
Definition Iterators.h:48
The following effect indicates that the operation allocates from some resource.
The following effect indicates that the operation frees some resource that has been allocated.
Options for BufferDeallocationOpInterface-based buffer deallocation.