MLIR 22.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
567 .getSuccessorOperands(RegionSuccessor(
568 op.getOperation(), op.getOperation()->getResults()))
569 .getTypes();
570 }));
571 if (!llvm::all_equal(returnOperandTypes))
572 return op->emitError(
573 "there are multiple return operations with different operand types");
574
575 TypeRange resultTypes = op.getResultTypes();
576 // Check if we found a return operation because that doesn't necessarily
577 // always have to be the case, e.g., consider a function with one block that
578 // has a cf.br at the end branching to itself again (i.e., an infinite loop).
579 // In that case we don't want to crash but just not update the return types.
580 if (!returnOperandTypes.empty())
581 resultTypes = returnOperandTypes[0];
582
583 op.setFunctionTypeAttr(TypeAttr::get(FunctionType::get(
584 op->getContext(), op.getFunctionBody().front().getArgumentTypes(),
585 resultTypes)));
586
587 return success();
588}
589
590LogicalResult BufferDeallocation::deallocate(FunctionOpInterface op) {
591 // Stop and emit a proper error message if we don't support the input IR.
592 if (failed(verifyFunctionPreconditions(op)))
593 return failure();
594
595 // Process the function block by block.
597 [&](Block *block) {
598 if (failed(deallocate(block)))
599 return WalkResult::interrupt();
600 return WalkResult::advance();
601 });
602 if (result.wasInterrupted())
603 return failure();
604
605 // Update the function signature if the function is private, dynamic ownership
606 // is enabled, and the function has memrefs as arguments or results.
607 return updateFunctionSignature(op);
608}
609
610LogicalResult BufferDeallocation::deallocate(Block *block) {
611 OpBuilder builder = OpBuilder::atBlockBegin(block);
612
613 // Compute liveness transfers of ownership to this block.
614 SmallVector<Value> liveMemrefs;
615 state.getLiveMemrefsIn(block, liveMemrefs);
616 for (auto li : liveMemrefs) {
617 // Ownership of implicitly captured memrefs from other regions is never
618 // taken, but ownership of memrefs in the same region (but different block)
619 // is taken.
620 if (li.getParentRegion() == block->getParent()) {
621 state.updateOwnership(li, state.getOwnership(li, li.getParentBlock()),
622 block);
623 state.addMemrefToDeallocate(li, block);
624 continue;
625 }
626
627 if (li.getParentRegion()->isProperAncestor(block->getParent())) {
628 Value falseVal = buildBoolValue(builder, li.getLoc(), false);
629 state.updateOwnership(li, falseVal, block);
630 }
631 }
632
633 for (unsigned i = 0, e = block->getNumArguments(); i < e; ++i) {
634 BlockArgument arg = block->getArgument(i);
635 if (!isMemref(arg))
636 continue;
637
638 // Adhere to function boundary ABI: no ownership of function argument
639 // MemRefs is taken.
640 if (isa<FunctionOpInterface>(block->getParentOp()) &&
641 block->isEntryBlock()) {
642 Value newArg = buildBoolValue(builder, arg.getLoc(), false);
643 state.updateOwnership(arg, newArg);
644 state.addMemrefToDeallocate(arg, block);
645 continue;
646 }
647
648 // Pass MemRef ownerships along via `i1` values.
649 Value newArg = block->addArgument(builder.getI1Type(), arg.getLoc());
650 state.updateOwnership(arg, newArg);
651 state.addMemrefToDeallocate(arg, block);
652 }
653
654 // For each operation in the block, handle the interfaces that affect aliasing
655 // and ownership of memrefs.
656 for (Operation &op : llvm::make_early_inc_range(*block)) {
657 FailureOr<Operation *> result = handleAllInterfaces(&op);
658 if (failed(result))
659 return failure();
660 if (!*result)
661 continue;
662
663 populateRemainingOwnerships(*result);
664 }
665
666 // TODO: if block has no terminator, handle dealloc insertion here.
667 return success();
668}
669
670Operation *BufferDeallocation::appendOpResults(Operation *op,
671 ArrayRef<Type> types) {
672 SmallVector<Type> newTypes(op->getResultTypes());
673 newTypes.append(types.begin(), types.end());
674 auto *newOp = Operation::create(op->getLoc(), op->getName(), newTypes,
675 op->getOperands(), op->getAttrDictionary(),
677 op->getSuccessors(), op->getNumRegions());
678 for (auto [oldRegion, newRegion] :
679 llvm::zip(op->getRegions(), newOp->getRegions()))
680 newRegion.takeBody(oldRegion);
681
682 OpBuilder(op).insert(newOp);
683 op->replaceAllUsesWith(newOp->getResults().take_front(op->getNumResults()));
684 op->erase();
685
686 return newOp;
687}
688
689FailureOr<Operation *>
690BufferDeallocation::handleInterface(RegionBranchOpInterface op) {
691 OpBuilder builder = OpBuilder::atBlockBegin(op->getBlock());
692
693 // TODO: the RegionBranchOpInterface does not provide all the necessary
694 // methods to perform this transformation without additional assumptions on
695 // the structure. In particular, that
696 // * additional values to be passed to the next region can be added to the end
697 // of the operand list, the end of the block argument list, and the end of
698 // the result value list. However, it seems to be the general guideline for
699 // operations implementing this interface to follow this structure.
700 // * and that the block arguments and result values match the forwarded
701 // operands one-to-one (i.e., that there are no other values appended to the
702 // front).
703 // These assumptions are satisfied by the `scf.if`, `scf.for`, and `scf.while`
704 // operations.
705
707 op.getSuccessorRegions(RegionBranchPoint::parent(), regions);
708 assert(!regions.empty() && "Must have at least one successor region");
709 SmallVector<Value> entryOperands(
710 op.getEntrySuccessorOperands(regions.front()));
711 unsigned numMemrefOperands = llvm::count_if(entryOperands, isMemref);
712
713 // No ownership is acquired for any MemRefs that are passed to the region from
714 // the outside.
715 Value falseVal = buildBoolValue(builder, op.getLoc(), false);
716 op->insertOperands(op->getNumOperands(),
717 SmallVector<Value>(numMemrefOperands, falseVal));
718
719 int counter = op->getNumResults();
720 unsigned numMemrefResults = llvm::count_if(op->getResults(), isMemref);
721 SmallVector<Type> ownershipResults(numMemrefResults, builder.getI1Type());
722 RegionBranchOpInterface newOp = appendOpResults(op, ownershipResults);
723
724 for (auto result : llvm::make_filter_range(newOp->getResults(), isMemref)) {
725 state.updateOwnership(result, newOp->getResult(counter++));
726 state.addMemrefToDeallocate(result, newOp->getBlock());
727 }
728
729 return newOp.getOperation();
730}
731
732Value BufferDeallocation::materializeMemrefWithGuaranteedOwnership(
733 OpBuilder &builder, Value memref, Block *block) {
734 // First, make sure we at least have 'Unique' ownership already.
735 std::pair<Value, Value> newMemrefAndOnwership =
736 materializeUniqueOwnership(builder, memref, block);
737 Value newMemref = newMemrefAndOnwership.first;
738 Value condition = newMemrefAndOnwership.second;
739
740 // Avoid inserting additional IR if ownership is already guaranteed. In
741 // particular, this is already the case when we had 'Unknown' ownership
742 // initially and a clone was inserted to get to 'Unique' ownership.
743 if (matchPattern(condition, m_One()))
744 return newMemref;
745
746 // Insert a runtime check and only clone if we still don't have ownership at
747 // runtime.
748 Value maybeClone = scf::IfOp::create(
749 builder, memref.getLoc(), condition,
750 [&](OpBuilder &builder, Location loc) {
751 scf::YieldOp::create(builder, loc, newMemref);
752 },
753 [&](OpBuilder &builder, Location loc) {
754 Value clone = bufferization::CloneOp::create(
755 builder, loc, newMemref);
756 scf::YieldOp::create(builder, loc, clone);
757 })
758 .getResult(0);
759 Value trueVal = buildBoolValue(builder, memref.getLoc(), true);
760 state.updateOwnership(maybeClone, trueVal);
761 state.addMemrefToDeallocate(maybeClone, maybeClone.getParentBlock());
762 return maybeClone;
763}
764
765FailureOr<Operation *>
766BufferDeallocation::handleInterface(BranchOpInterface op) {
767 if (op->getNumSuccessors() > 1)
768 return op->emitError("BranchOpInterface operations with multiple "
769 "successors are not supported yet");
770
771 if (op->getNumSuccessors() != 1)
772 return emitError(op.getLoc(),
773 "only BranchOpInterface operations with exactly "
774 "one successor are supported yet");
775
776 if (op.getSuccessorOperands(0).getProducedOperandCount() > 0)
777 return op.emitError("produced operands are not supported");
778
779 // Collect the values to deallocate and retain and use them to create the
780 // dealloc operation.
781 Block *block = op->getBlock();
782 OpBuilder builder(op);
783 SmallVector<Value> memrefs, conditions, toRetain;
784 if (failed(state.getMemrefsAndConditionsToDeallocate(
785 builder, op.getLoc(), block, memrefs, conditions)))
786 return failure();
787
788 OperandRange forwardedOperands =
789 op.getSuccessorOperands(0).getForwardedOperands();
790 state.getMemrefsToRetain(block, op->getSuccessor(0), forwardedOperands,
791 toRetain);
792
793 auto deallocOp = bufferization::DeallocOp::create(
794 builder, op.getLoc(), memrefs, conditions, toRetain);
795
796 // We want to replace the current ownership of the retained values with the
797 // result values of the dealloc operation as they are always unique.
798 state.resetOwnerships(deallocOp.getRetained(), block);
799 for (auto [retained, ownership] :
800 llvm::zip(deallocOp.getRetained(), deallocOp.getUpdatedConditions())) {
801 state.updateOwnership(retained, ownership, block);
802 }
803
804 unsigned numAdditionalReturns = llvm::count_if(forwardedOperands, isMemref);
805 SmallVector<Value> newOperands(forwardedOperands);
806 auto additionalConditions =
807 deallocOp.getUpdatedConditions().take_front(numAdditionalReturns);
808 newOperands.append(additionalConditions.begin(), additionalConditions.end());
809 op.getSuccessorOperands(0).getMutableForwardedOperands().assign(newOperands);
810
811 return op.getOperation();
812}
813
814FailureOr<Operation *> BufferDeallocation::handleInterface(CallOpInterface op) {
815 OpBuilder builder(op);
816
817 // Lookup the function operation and check if it has private visibility. If
818 // the function is referenced by SSA value instead of a Symbol, it's assumed
819 // to be public. (And we cannot easily change the type of the SSA value
820 // anyway.)
821 Operation *funcOp = op.resolveCallableInTable(state.getSymbolTable());
822 bool isPrivate = false;
823 if (auto symbol = dyn_cast_or_null<SymbolOpInterface>(funcOp))
824 isPrivate = symbol.isPrivate() && !symbol.isDeclaration();
825
826 // If the private-function-dynamic-ownership option is enabled and we are
827 // calling a private function, we need to add an additional `i1` result for
828 // each MemRef result to dynamically pass the current ownership indicator
829 // rather than adhering to the function boundary ABI.
830 if (options.privateFuncDynamicOwnership && isPrivate) {
831 unsigned numMemrefs = llvm::count_if(op->getResults(), isMemref);
832 SmallVector<Type> ownershipTypesToAppend(numMemrefs, builder.getI1Type());
833 unsigned ownershipCounter = op->getNumResults();
834 op = appendOpResults(op, ownershipTypesToAppend);
835
836 for (auto result : llvm::make_filter_range(op->getResults(), isMemref)) {
837 state.updateOwnership(result, op->getResult(ownershipCounter++));
838 state.addMemrefToDeallocate(result, result.getParentBlock());
839 }
840
841 return op.getOperation();
842 }
843
844 // According to the function boundary ABI we are guaranteed to get ownership
845 // of all MemRefs returned by the function. Thus we set ownership to constant
846 // 'true' and remember to deallocate it.
847 Value trueVal = buildBoolValue(builder, op.getLoc(), true);
848 for (auto result : llvm::make_filter_range(op->getResults(), isMemref)) {
849 state.updateOwnership(result, trueVal);
850 state.addMemrefToDeallocate(result, result.getParentBlock());
851 }
852
853 return op.getOperation();
854}
855
856FailureOr<Operation *>
857BufferDeallocation::handleInterface(MemoryEffectOpInterface op) {
858 auto *block = op->getBlock();
859 OpBuilder builder = OpBuilder::atBlockBegin(block);
860
861 for (auto operand : llvm::make_filter_range(op->getOperands(), isMemref)) {
862 if (op.getEffectOnValue<MemoryEffects::Free>(operand).has_value()) {
863 // The bufferization.manual_deallocation attribute can be attached to ops
864 // with an allocation and/or deallocation side effect. It indicates that
865 // the op is under a "manual deallocation" scheme. Deallocation ops are
866 // usually forbidden in the input IR (not supported by the buffer
867 // deallocation pass). However, if they are under manual deallocation,
868 // they can be safely ignored by the buffer deallocation pass.
869 if (!op->hasAttr(BufferizationDialect::kManualDeallocation))
870 return op->emitError(
871 "memory free side-effect on MemRef value not supported!");
872
873 // Buffers that were allocated under "manual deallocation" may be
874 // manually deallocated. We insert a runtime assertion to cover certain
875 // cases of invalid IR where an automatically managed buffer allocation
876 // is manually deallocated. This is not a bulletproof check!
877 OpBuilder::InsertionGuard g(builder);
878 builder.setInsertionPoint(op);
879 Ownership ownership = state.getOwnership(operand, block);
880 if (ownership.isUnique()) {
881 Value ownershipInverted = arith::XOrIOp::create(
882 builder, op.getLoc(), ownership.getIndicator(),
883 buildBoolValue(builder, op.getLoc(), true));
884 cf::AssertOp::create(builder, op.getLoc(), ownershipInverted,
885 "expected that the block does not have ownership");
886 }
887 }
888 }
889
890 for (auto res : llvm::make_filter_range(op->getResults(), isMemref)) {
891 auto allocEffect = op.getEffectOnValue<MemoryEffects::Allocate>(res);
892 if (allocEffect.has_value()) {
893 if (isa<SideEffects::AutomaticAllocationScopeResource>(
894 allocEffect->getResource())) {
895 // Make sure that the ownership of auto-managed allocations is set to
896 // false. This is important for operations that have at least one memref
897 // typed operand. E.g., consider an operation like `bufferization.clone`
898 // that lowers to a `memref.alloca + memref.copy` instead of a
899 // `memref.alloc`. If we wouldn't set the ownership of the result here,
900 // the default ownership population in `populateRemainingOwnerships`
901 // would assume aliasing with the MemRef operand.
902 state.resetOwnerships(res, block);
903 state.updateOwnership(res, buildBoolValue(builder, op.getLoc(), false));
904 continue;
905 }
906
907 if (op->hasAttr(BufferizationDialect::kManualDeallocation)) {
908 // This allocation will be deallocated manually. Assign an ownership of
909 // "false", so that it will never be deallocated by the buffer
910 // deallocation pass.
911 state.resetOwnerships(res, block);
912 state.updateOwnership(res, buildBoolValue(builder, op.getLoc(), false));
913 continue;
916 state.updateOwnership(res, buildBoolValue(builder, op.getLoc(), true));
917 state.addMemrefToDeallocate(res, block);
918 }
920
921 return op.getOperation();
923
924FailureOr<Operation *>
925BufferDeallocation::handleInterface(RegionBranchTerminatorOpInterface op) {
926 OpBuilder builder(op);
928 // If this is a return operation of a function that is not private or the
929 // dynamic function boundary ownership is disabled, we need to return memref
930 // values for which we have guaranteed ownership to pass on to adhere to the
931 // function boundary ABI.
932 bool funcWithoutDynamicOwnership =
933 isFunctionWithoutDynamicOwnership(op->getParentOp());
934 if (funcWithoutDynamicOwnership) {
935 for (OpOperand &val : op->getOpOperands()) {
936 if (!isMemref(val.get()))
937 continue;
939 val.set(materializeMemrefWithGuaranteedOwnership(builder, val.get(),
940 op->getBlock()));
941 }
942 }
944 // TODO: getSuccessorRegions is not implemented by all operations we care
945 // about, but we would need to check how many successors there are and under
946 // which condition they are taken, etc.
947
948 MutableOperandRange operands = op.getMutableSuccessorOperands(
949 RegionSuccessor(op.getOperation(), op.getOperation()->getResults()));
950
951 SmallVector<Value> updatedOwnerships;
953 state, op, operands.getAsOperandRange(), updatedOwnerships);
954 if (failed(result) || !*result)
955 return result;
956
957 // Add an additional operand for every MemRef for the ownership indicator.
958 if (!funcWithoutDynamicOwnership) {
959 SmallVector<Value> newOperands{operands.getAsOperandRange()};
960 newOperands.append(updatedOwnerships.begin(), updatedOwnerships.end());
961 operands.assign(newOperands);
963
964 return op.getOperation();
965}
967bool BufferDeallocation::isFunctionWithoutDynamicOwnership(Operation *op) {
968 auto funcOp = dyn_cast<FunctionOpInterface>(op);
969 return funcOp && (!options.privateFuncDynamicOwnership ||
970 !funcOp.isPrivate() || funcOp.isExternal());
971}
973void BufferDeallocation::populateRemainingOwnerships(Operation *op) {
974 for (auto res : op->getResults()) {
975 if (!isMemref(res))
976 continue;
977 if (!state.getOwnership(res, op->getBlock()).isUninitialized())
978 continue;
979
980 // The op does not allocate memory, otherwise, it would have been assigned
981 // an ownership during `handleInterface`. Assume the result may alias with
982 // any memref operand and thus combine all their ownerships.
983 for (auto operand : op->getOperands()) {
984 if (!isMemref(operand))
985 continue;
986
987 state.updateOwnership(
988 res, state.getOwnership(operand, operand.getParentBlock()),
989 op->getBlock());
990 }
991
992 // If the ownership value is still uninitialized (e.g., because the op has
993 // no memref operands), assume that no ownership is taken. E.g., this is the
994 // case for "memref.get_global".
995 //
996 // Note: This can lead to memory leaks if memory side effects are not
997 // properly specified on the op.
998 if (state.getOwnership(res, op->getBlock()).isUninitialized()) {
999 OpBuilder builder(op);
1000 state.updateOwnership(res, buildBoolValue(builder, op->getLoc(), false));
1001 }
1002 }
1003}
1004
1005//===----------------------------------------------------------------------===//
1006// OwnershipBasedBufferDeallocationPass
1007//===----------------------------------------------------------------------===//
1008
1009namespace {
1010
1011/// The actual buffer deallocation pass that inserts and moves dealloc nodes
1012/// into the right positions. Furthermore, it inserts additional clones if
1013/// necessary. It uses the algorithm described at the top of the file.
1014struct OwnershipBasedBufferDeallocationPass
1016 OwnershipBasedBufferDeallocationPass> {
1017 using Base::Base;
1018
1019 void runOnOperation() override {
1020 DeallocationOptions options;
1021 options.privateFuncDynamicOwnership = privateFuncDynamicOwnership;
1022
1023 mlir::SymbolTableCollection symbolTables;
1024
1025 auto status = getOperation()->walk([&](func::FuncOp func) {
1026 if (func.isExternal())
1027 return WalkResult::skip();
1028
1029 if (failed(deallocateBuffersOwnershipBased(func, options, symbolTables)))
1030 return WalkResult::interrupt();
1031
1032 return WalkResult::advance();
1033 });
1034 if (status.wasInterrupted())
1036 }
1037};
1038
1039} // namespace
1040
1041//===----------------------------------------------------------------------===//
1042// Implement bufferization API
1043//===----------------------------------------------------------------------===//
1044
1046 FunctionOpInterface op, DeallocationOptions options,
1047 SymbolTableCollection &symbolTables) {
1048 // Gather all required allocation nodes and prepare the deallocation phase.
1049 BufferDeallocation deallocation(op, options, symbolTables);
1050
1051 // Place all required temporary clone and dealloc nodes.
1052 return deallocation.deallocate(op);
1053}
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:129
unsigned getNumArguments()
Definition Block.h:128
Region * getParent() const
Provide a 'getParent' method for ilist_node_with_parent methods.
Definition Block.cpp:27
OpListType & getOperations()
Definition Block.h:137
SuccessorRange getSuccessors()
Definition Block.h:270
BlockArgument addArgument(Type type, Location loc)
Add one value to the argument list.
Definition Block.cpp:153
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
OpT getOperation()
Return the current operation being transformed.
Definition Pass.h:378
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
virtual void runOnOperation()=0
The polymorphic API that runs the pass over the currently held operation.
void signalPassFailure()
Signal that some invariant was broken when running.
Definition Pass.h:218
static constexpr RegionBranchPoint parent()
Returns an instance of RegionBranchPoint representing the parent operation.
This class represents a successor of a region.
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:561
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.