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