33#define GEN_PASS_DEF_OWNERSHIPBASEDBUFFERDEALLOCATIONPASS
34#include "mlir/Dialect/Bufferization/Transforms/Passes.h.inc"
46 return arith::ConstantOp::create(builder, loc, builder.
getBoolAttr(value));
66 if (llvm::any_of(region.front().getArguments(),
isMemref))
81 using BlockSetT = SmallPtrSet<Block *, 16>;
82 using BackedgeSetT = llvm::DenseSet<std::pair<Block *, Block *>>;
86 Backedges(Operation *op) { recurse(op); }
89 size_t size()
const {
return edgeSet.size(); }
92 BackedgeSetT::const_iterator begin()
const {
return edgeSet.begin(); }
95 BackedgeSetT::const_iterator end()
const {
return edgeSet.end(); }
101 bool enter(
Block ¤t,
Block *predecessor) {
102 bool inserted = visited.insert(¤t).second;
104 edgeSet.insert(std::make_pair(predecessor, ¤t));
109 void exit(
Block ¤t) { visited.erase(¤t); }
113 void recurse(Operation *op) {
117 if (isa<BranchOpInterface>(op)) {
119 recurse(*succ, current);
125 recurse(region.front(), current);
131 void recurse(
Block &block,
Block *predecessor) {
134 if (!enter(block, predecessor))
149 BackedgeSetT edgeSet;
161class BufferDeallocation {
163 BufferDeallocation(Operation *op, DeallocationOptions
options,
164 SymbolTableCollection &symbolTables)
168 LogicalResult deallocate(FunctionOpInterface op);
172 template <
typename... T>
173 typename std::enable_if<
sizeof...(T) == 0, FailureOr<Operation *>>::type
174 handleOp(Operation *op) {
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);
216 return FailureOr<Operation *>(
nullptr);
217 return handleOp<InterfacesU...>(next);
221 FailureOr<Operation *> handleAllInterfaces(Operation *op) {
222 if (
auto deallocOpInterface = dyn_cast<BufferDeallocationOpInterface>(op))
223 return deallocOpInterface.process(state,
options);
225 if (
failed(verifyOperationPreconditions(op)))
228 return handleOp<MemoryEffectOpInterface, RegionBranchOpInterface,
229 CallOpInterface, BranchOpInterface,
230 RegionBranchTerminatorOpInterface>(op);
253 FailureOr<Operation *> handleInterface(BranchOpInterface op);
285 FailureOr<Operation *> handleInterface(RegionBranchOpInterface op);
314 FailureOr<Operation *> handleInterface(CallOpInterface op);
334 FailureOr<Operation *> handleInterface(MemoryEffectOpInterface op);
357 FailureOr<Operation *> handleInterface(RegionBranchTerminatorOpInterface op);
366 Operation *appendOpResults(Operation *op, ArrayRef<Type> types);
370 template <
typename OpTy>
371 OpTy appendOpResults(OpTy op, ArrayRef<Type> types) {
372 return cast<OpTy>(appendOpResults(op.getOperation(), types));
378 LogicalResult deallocate(
Block *block);
391 void populateRemainingOwnerships(Operation *op);
396 Value materializeMemrefWithGuaranteedOwnership(OpBuilder &builder,
397 Value memref,
Block *block);
402 bool isFunctionWithoutDynamicOwnership(Operation *op);
411 std::pair<Value, Value>
412 materializeUniqueOwnership(OpBuilder &builder, Value memref,
Block *block);
418 static LogicalResult verifyFunctionPreconditions(FunctionOpInterface op);
434 static LogicalResult verifyOperationPreconditions(Operation *op);
441 static LogicalResult updateFunctionSignature(FunctionOpInterface op);
447 DeallocationState state;
459std::pair<Value, Value>
464 if (
memref.getParentBlock() != block)
465 return state.getMemrefWithUniqueOwnership(builder,
memref, block);
469 owner =
memref.getParentBlock()->getParentOp();
473 if (
auto deallocOpInterface = dyn_cast<BufferDeallocationOpInterface>(owner))
474 return deallocOpInterface.materializeUniqueOwnershipForMemref(
478 return state.getMemrefWithUniqueOwnership(builder,
memref, block);
482BufferDeallocation::verifyFunctionPreconditions(FunctionOpInterface op) {
485 Backedges backedges(op);
486 if (backedges.size()) {
487 op->emitError(
"Only structured control-flow loops are supported.");
494LogicalResult BufferDeallocation::verifyOperationPreconditions(
Operation *op) {
502 if (isa<DeallocOp>(op))
504 "No deallocation operations must be present when running this pass!");
516 "ops with unknown memory side effects are not supported");
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.");
537 return op->
emitError(
"NoTerminator trait is not supported");
542 if (!isa<BranchOpInterface, RegionBranchTerminatorOpInterface>(op) ||
543 (isa<BranchOpInterface>(op) &&
544 isa<RegionBranchTerminatorOpInterface>(op)))
547 "Terminators must implement either BranchOpInterface or "
548 "RegionBranchTerminatorOpInterface (but not both)!");
554 return op->
emitError(
"Terminators with more than one successor "
555 "are not supported!");
562BufferDeallocation::updateFunctionSignature(FunctionOpInterface op) {
564 op.getFunctionBody().getOps<RegionBranchTerminatorOpInterface>(),
565 [&](RegionBranchTerminatorOpInterface branchOp) {
567 .getSuccessorOperands(RegionSuccessor(
568 op.getOperation(), op.getOperation()->getResults()))
571 if (!llvm::all_equal(returnOperandTypes))
572 return op->emitError(
573 "there are multiple return operations with different operand types");
575 TypeRange resultTypes = op.getResultTypes();
580 if (!returnOperandTypes.empty())
581 resultTypes = returnOperandTypes[0];
583 op.setFunctionTypeAttr(TypeAttr::get(FunctionType::get(
584 op->getContext(), op.getFunctionBody().front().getArgumentTypes(),
590LogicalResult BufferDeallocation::deallocate(FunctionOpInterface op) {
592 if (
failed(verifyFunctionPreconditions(op)))
598 if (
failed(deallocate(block)))
602 if (
result.wasInterrupted())
607 return updateFunctionSignature(op);
610LogicalResult BufferDeallocation::deallocate(
Block *block) {
615 state.getLiveMemrefsIn(block, liveMemrefs);
616 for (
auto li : liveMemrefs) {
620 if (li.getParentRegion() == block->
getParent()) {
621 state.updateOwnership(li, state.getOwnership(li, li.getParentBlock()),
623 state.addMemrefToDeallocate(li, block);
627 if (li.getParentRegion()->isProperAncestor(block->
getParent())) {
629 state.updateOwnership(li, falseVal, block);
640 if (isa<FunctionOpInterface>(block->
getParentOp()) &&
643 state.updateOwnership(arg, newArg);
644 state.addMemrefToDeallocate(arg, block);
650 state.updateOwnership(arg, newArg);
651 state.addMemrefToDeallocate(arg, block);
656 for (
Operation &op : llvm::make_early_inc_range(*block)) {
657 FailureOr<Operation *>
result = handleAllInterfaces(&op);
663 populateRemainingOwnerships(*
result);
673 newTypes.append(types.begin(), types.end());
678 for (
auto [oldRegion, newRegion] :
679 llvm::zip(op->
getRegions(), newOp->getRegions()))
680 newRegion.takeBody(oldRegion);
689FailureOr<Operation *>
690BufferDeallocation::handleInterface(RegionBranchOpInterface op) {
708 assert(!regions.empty() &&
"Must have at least one successor region");
710 op.getEntrySuccessorOperands(regions.front()));
711 unsigned numMemrefOperands = llvm::count_if(entryOperands,
isMemref);
716 op->insertOperands(op->getNumOperands(),
719 int counter = op->getNumResults();
720 unsigned numMemrefResults = llvm::count_if(op->getResults(),
isMemref);
722 RegionBranchOpInterface newOp = appendOpResults(op, ownershipResults);
724 for (
auto result : llvm::make_filter_range(newOp->getResults(),
isMemref)) {
725 state.updateOwnership(
result, newOp->getResult(counter++));
726 state.addMemrefToDeallocate(
result, newOp->getBlock());
729 return newOp.getOperation();
732Value BufferDeallocation::materializeMemrefWithGuaranteedOwnership(
735 std::pair<Value, Value> newMemrefAndOnwership =
736 materializeUniqueOwnership(builder,
memref, block);
737 Value newMemref = newMemrefAndOnwership.first;
738 Value condition = newMemrefAndOnwership.second;
748 Value maybeClone = scf::IfOp::create(
749 builder,
memref.getLoc(), condition,
751 scf::YieldOp::create(builder, loc, newMemref);
754 Value clone = bufferization::CloneOp::create(
755 builder, loc, newMemref);
756 scf::YieldOp::create(builder, loc, clone);
760 state.updateOwnership(maybeClone, trueVal);
761 state.addMemrefToDeallocate(maybeClone, maybeClone.
getParentBlock());
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");
771 if (op->getNumSuccessors() != 1)
773 "only BranchOpInterface operations with exactly "
774 "one successor are supported yet");
776 if (op.getSuccessorOperands(0).getProducedOperandCount() > 0)
777 return op.emitError(
"produced operands are not supported");
781 Block *block = op->getBlock();
784 if (
failed(state.getMemrefsAndConditionsToDeallocate(
785 builder, op.getLoc(), block, memrefs, conditions)))
789 op.getSuccessorOperands(0).getForwardedOperands();
790 state.getMemrefsToRetain(block, op->getSuccessor(0), forwardedOperands,
793 auto deallocOp = bufferization::DeallocOp::create(
794 builder, op.getLoc(), memrefs, conditions, toRetain);
798 state.resetOwnerships(deallocOp.getRetained(), block);
799 for (
auto [retained, ownership] :
800 llvm::zip(deallocOp.getRetained(), deallocOp.getUpdatedConditions())) {
801 state.updateOwnership(retained, ownership, block);
804 unsigned numAdditionalReturns = llvm::count_if(forwardedOperands,
isMemref);
806 auto additionalConditions =
807 deallocOp.getUpdatedConditions().take_front(numAdditionalReturns);
808 newOperands.append(additionalConditions.begin(), additionalConditions.end());
809 op.getSuccessorOperands(0).getMutableForwardedOperands().assign(newOperands);
811 return op.getOperation();
814FailureOr<Operation *> BufferDeallocation::handleInterface(CallOpInterface op) {
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();
830 if (
options.privateFuncDynamicOwnership && isPrivate) {
831 unsigned numMemrefs = llvm::count_if(op->getResults(),
isMemref);
833 unsigned ownershipCounter = op->getNumResults();
834 op = appendOpResults(op, ownershipTypesToAppend);
836 for (
auto result : llvm::make_filter_range(op->getResults(),
isMemref)) {
837 state.updateOwnership(
result, op->getResult(ownershipCounter++));
838 state.addMemrefToDeallocate(
result,
result.getParentBlock());
841 return op.getOperation();
848 for (
auto result : llvm::make_filter_range(op->getResults(),
isMemref)) {
849 state.updateOwnership(
result, trueVal);
850 state.addMemrefToDeallocate(
result,
result.getParentBlock());
853 return op.getOperation();
856FailureOr<Operation *>
857BufferDeallocation::handleInterface(MemoryEffectOpInterface op) {
858 auto *block = op->getBlock();
861 for (
auto operand : llvm::make_filter_range(op->getOperands(),
isMemref)) {
869 if (!op->hasAttr(BufferizationDialect::kManualDeallocation))
870 return op->emitError(
871 "memory free side-effect on MemRef value not supported!");
879 Ownership ownership = state.getOwnership(operand, block);
881 Value ownershipInverted = arith::XOrIOp::create(
884 cf::AssertOp::create(builder, op.getLoc(), ownershipInverted,
885 "expected that the block does not have ownership");
890 for (
auto res : llvm::make_filter_range(op->getResults(),
isMemref)) {
892 if (allocEffect.has_value()) {
893 if (isa<SideEffects::AutomaticAllocationScopeResource>(
894 allocEffect->getResource())) {
902 state.resetOwnerships(res, block);
903 state.updateOwnership(res,
buildBoolValue(builder, op.getLoc(),
false));
907 if (op->hasAttr(BufferizationDialect::kManualDeallocation)) {
911 state.resetOwnerships(res, block);
917 state.addMemrefToDeallocate(res, block);
921 return op.getOperation();
924FailureOr<Operation *>
925BufferDeallocation::handleInterface(RegionBranchTerminatorOpInterface op) {
932 bool funcWithoutDynamicOwnership =
933 isFunctionWithoutDynamicOwnership(op->getParentOp());
934 if (funcWithoutDynamicOwnership) {
935 for (
OpOperand &val : op->getOpOperands()) {
939 val.set(materializeMemrefWithGuaranteedOwnership(builder, val.get(),
958 if (!funcWithoutDynamicOwnership) {
960 newOperands.append(updatedOwnerships.begin(), updatedOwnerships.end());
961 operands.
assign(newOperands);
964 return op.getOperation();
967bool BufferDeallocation::isFunctionWithoutDynamicOwnership(
Operation *op) {
968 auto funcOp = dyn_cast<FunctionOpInterface>(op);
969 return funcOp && (!
options.privateFuncDynamicOwnership ||
970 !funcOp.isPrivate() || funcOp.isExternal());
973void BufferDeallocation::populateRemainingOwnerships(
Operation *op) {
977 if (!state.getOwnership(res, op->
getBlock()).isUninitialized())
987 state.updateOwnership(
988 res, state.getOwnership(operand, operand.getParentBlock()),
998 if (state.getOwnership(res, op->
getBlock()).isUninitialized()) {
1014struct OwnershipBasedBufferDeallocationPass
1016 OwnershipBasedBufferDeallocationPass> {
1021 options.privateFuncDynamicOwnership = privateFuncDynamicOwnership;
1023 mlir::SymbolTableCollection symbolTables;
1025 auto status =
getOperation()->walk([&](func::FuncOp func) {
1026 if (func.isExternal())
1034 if (status.wasInterrupted())
1049 BufferDeallocation deallocation(op,
options, symbolTables);
1052 return deallocation.deallocate(op);
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.
Location getLoc() const
Return the location for this argument.
Block represents an ordered list of Operations.
BlockArgument getArgument(unsigned i)
unsigned getNumArguments()
Region * getParent() const
Provide a 'getParent' method for ilist_node_with_parent methods.
OpListType & getOperations()
SuccessorRange getSuccessors()
BlockArgument addArgument(Type type, Location loc)
Add one value to the argument list.
bool isEntryBlock()
Return if this block is the entry block in the parent region.
Operation * getParentOp()
Returns the closest surrounding operation that contains this block.
BoolAttr getBoolAttr(bool value)
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
This class provides a mutable adaptor for a range of operands.
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.
This class helps build Operations.
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...
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Operation * insert(Operation *op)
Insert the given operation at the current insertion point and return it.
This class represents an operand of an operation.
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.
OpT getOperation()
Return the current operation being transformed.
Operation is the basic unit of execution within MLIR.
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.
Block * getBlock()
Returns the operation block that contains this operation.
unsigned getNumRegions()
Returns the number of regions held by this operation.
Location getLoc()
The source location the operation was defined or derived from.
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.
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.
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
result_type_range getResultTypes()
operand_range getOperands()
Returns an iterator on the underlying Value's.
void replaceAllUsesWith(ValuesT &&values)
Replace all uses of results of this operation with the provided 'values'.
SuccessorRange getSuccessors()
result_range getResults()
OpaqueProperties getPropertiesStorage()
Returns the properties storage.
void erase()
Remove this operation from its parent block and delete it.
unsigned getNumResults()
Return the number of results held by this operation.
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.
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.
This class represents a collection of SymbolTables.
This class provides an abstraction over the various different ranges of value types.
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Type getType() const
Return the type of this value.
Block * getParentBlock()
Return the Block in which this Value is defined.
static WalkResult advance()
static WalkResult interrupt()
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.
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
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.
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.
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.