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) {
566 return branchOp.getSuccessorOperands(RegionSuccessor::parent())
569 if (!llvm::all_equal(returnOperandTypes))
570 return op->emitError(
571 "there are multiple return operations with different operand types");
573 TypeRange resultTypes = op.getResultTypes();
578 if (!returnOperandTypes.empty())
579 resultTypes = returnOperandTypes[0];
581 op.setFunctionTypeAttr(TypeAttr::get(FunctionType::get(
582 op->getContext(), op.getFunctionBody().front().getArgumentTypes(),
588LogicalResult BufferDeallocation::deallocate(FunctionOpInterface op) {
590 if (
failed(verifyFunctionPreconditions(op)))
596 if (
failed(deallocate(block)))
600 if (
result.wasInterrupted())
605 return updateFunctionSignature(op);
608LogicalResult BufferDeallocation::deallocate(
Block *block) {
613 state.getLiveMemrefsIn(block, liveMemrefs);
614 for (
auto li : liveMemrefs) {
618 if (li.getParentRegion() == block->
getParent()) {
619 state.updateOwnership(li, state.getOwnership(li, li.getParentBlock()),
621 state.addMemrefToDeallocate(li, block);
625 if (li.getParentRegion()->isProperAncestor(block->
getParent())) {
627 state.updateOwnership(li, falseVal, block);
638 if (isa<FunctionOpInterface>(block->
getParentOp()) &&
641 state.updateOwnership(arg, newArg);
642 state.addMemrefToDeallocate(arg, block);
648 state.updateOwnership(arg, newArg);
649 state.addMemrefToDeallocate(arg, block);
654 for (
Operation &op : llvm::make_early_inc_range(*block)) {
655 FailureOr<Operation *>
result = handleAllInterfaces(&op);
661 populateRemainingOwnerships(*
result);
671 newTypes.append(types.begin(), types.end());
676 for (
auto [oldRegion, newRegion] :
677 llvm::zip(op->
getRegions(), newOp->getRegions()))
678 newRegion.takeBody(oldRegion);
687FailureOr<Operation *>
688BufferDeallocation::handleInterface(RegionBranchOpInterface op) {
706 assert(!regions.empty() &&
"Must have at least one successor region");
708 op.getEntrySuccessorOperands(regions.front()));
709 unsigned numMemrefOperands = llvm::count_if(entryOperands,
isMemref);
714 op->insertOperands(op->getNumOperands(),
717 int counter = op->getNumResults();
718 unsigned numMemrefResults = llvm::count_if(op->getResults(),
isMemref);
720 RegionBranchOpInterface newOp = appendOpResults(op, ownershipResults);
722 for (
auto result : llvm::make_filter_range(newOp->getResults(),
isMemref)) {
723 state.updateOwnership(
result, newOp->getResult(counter++));
724 state.addMemrefToDeallocate(
result, newOp->getBlock());
727 return newOp.getOperation();
730Value BufferDeallocation::materializeMemrefWithGuaranteedOwnership(
733 std::pair<Value, Value> newMemrefAndOnwership =
734 materializeUniqueOwnership(builder,
memref, block);
735 Value newMemref = newMemrefAndOnwership.first;
736 Value condition = newMemrefAndOnwership.second;
746 Value maybeClone = scf::IfOp::create(
747 builder,
memref.getLoc(), condition,
749 scf::YieldOp::create(builder, loc, newMemref);
752 Value clone = bufferization::CloneOp::create(
753 builder, loc, newMemref);
754 scf::YieldOp::create(builder, loc, clone);
758 state.updateOwnership(maybeClone, trueVal);
759 state.addMemrefToDeallocate(maybeClone, maybeClone.
getParentBlock());
763FailureOr<Operation *>
764BufferDeallocation::handleInterface(BranchOpInterface op) {
765 if (op->getNumSuccessors() > 1)
766 return op->emitError(
"BranchOpInterface operations with multiple "
767 "successors are not supported yet");
769 if (op->getNumSuccessors() != 1)
771 "only BranchOpInterface operations with exactly "
772 "one successor are supported yet");
774 if (op.getSuccessorOperands(0).getProducedOperandCount() > 0)
775 return op.emitError(
"produced operands are not supported");
779 Block *block = op->getBlock();
782 if (
failed(state.getMemrefsAndConditionsToDeallocate(
783 builder, op.getLoc(), block, memrefs, conditions)))
787 op.getSuccessorOperands(0).getForwardedOperands();
788 state.getMemrefsToRetain(block, op->getSuccessor(0), forwardedOperands,
791 auto deallocOp = bufferization::DeallocOp::create(
792 builder, op.getLoc(), memrefs, conditions, toRetain);
796 state.resetOwnerships(deallocOp.getRetained(), block);
797 for (
auto [retained, ownership] :
798 llvm::zip(deallocOp.getRetained(), deallocOp.getUpdatedConditions())) {
799 state.updateOwnership(retained, ownership, block);
802 unsigned numAdditionalReturns = llvm::count_if(forwardedOperands,
isMemref);
804 auto additionalConditions =
805 deallocOp.getUpdatedConditions().take_front(numAdditionalReturns);
806 newOperands.append(additionalConditions.begin(), additionalConditions.end());
807 op.getSuccessorOperands(0).getMutableForwardedOperands().assign(newOperands);
809 return op.getOperation();
812FailureOr<Operation *> BufferDeallocation::handleInterface(CallOpInterface op) {
819 Operation *funcOp = op.resolveCallableInTable(state.getSymbolTable());
820 bool isPrivate =
false;
821 if (
auto symbol = dyn_cast_or_null<SymbolOpInterface>(funcOp))
822 isPrivate = symbol.isPrivate() && !symbol.isDeclaration();
828 if (
options.privateFuncDynamicOwnership && isPrivate) {
829 unsigned numMemrefs = llvm::count_if(op->getResults(),
isMemref);
831 unsigned ownershipCounter = op->getNumResults();
832 op = appendOpResults(op, ownershipTypesToAppend);
834 for (
auto result : llvm::make_filter_range(op->getResults(),
isMemref)) {
835 state.updateOwnership(
result, op->getResult(ownershipCounter++));
836 state.addMemrefToDeallocate(
result,
result.getParentBlock());
839 return op.getOperation();
846 for (
auto result : llvm::make_filter_range(op->getResults(),
isMemref)) {
847 state.updateOwnership(
result, trueVal);
848 state.addMemrefToDeallocate(
result,
result.getParentBlock());
851 return op.getOperation();
854FailureOr<Operation *>
855BufferDeallocation::handleInterface(MemoryEffectOpInterface op) {
856 auto *block = op->getBlock();
859 for (
auto operand : llvm::make_filter_range(op->getOperands(),
isMemref)) {
867 if (!op->hasAttr(BufferizationDialect::kManualDeallocation))
868 return op->emitError(
869 "memory free side-effect on MemRef value not supported!");
877 Ownership ownership = state.getOwnership(operand, block);
879 Value ownershipInverted = arith::XOrIOp::create(
882 cf::AssertOp::create(builder, op.getLoc(), ownershipInverted,
883 "expected that the block does not have ownership");
888 for (
auto res : llvm::make_filter_range(op->getResults(),
isMemref)) {
890 if (allocEffect.has_value()) {
891 if (isa<SideEffects::AutomaticAllocationScopeResource>(
892 allocEffect->getResource())) {
900 state.resetOwnerships(res, block);
901 state.updateOwnership(res,
buildBoolValue(builder, op.getLoc(),
false));
905 if (op->hasAttr(BufferizationDialect::kManualDeallocation)) {
909 state.resetOwnerships(res, block);
910 state.updateOwnership(res,
buildBoolValue(builder, op.getLoc(),
false));
914 state.updateOwnership(res,
buildBoolValue(builder, op.getLoc(),
true));
915 state.addMemrefToDeallocate(res, block);
919 return op.getOperation();
922FailureOr<Operation *>
923BufferDeallocation::handleInterface(RegionBranchTerminatorOpInterface op) {
930 bool funcWithoutDynamicOwnership =
931 isFunctionWithoutDynamicOwnership(op->getParentOp());
932 if (funcWithoutDynamicOwnership) {
933 for (
OpOperand &val : op->getOpOperands()) {
937 val.set(materializeMemrefWithGuaranteedOwnership(builder, val.get(),
956 if (!funcWithoutDynamicOwnership) {
958 newOperands.append(updatedOwnerships.begin(), updatedOwnerships.end());
959 operands.
assign(newOperands);
962 return op.getOperation();
965bool BufferDeallocation::isFunctionWithoutDynamicOwnership(
Operation *op) {
966 auto funcOp = dyn_cast<FunctionOpInterface>(op);
967 return funcOp && (!
options.privateFuncDynamicOwnership ||
968 !funcOp.isPrivate() || funcOp.isExternal());
971void BufferDeallocation::populateRemainingOwnerships(
Operation *op) {
975 if (!state.getOwnership(res, op->
getBlock()).isUninitialized())
985 state.updateOwnership(
986 res, state.getOwnership(operand, operand.getParentBlock()),
996 if (state.getOwnership(res, op->
getBlock()).isUninitialized()) {
1012struct OwnershipBasedBufferDeallocationPass
1013 :
public bufferization::impl::OwnershipBasedBufferDeallocationPassBase<
1014 OwnershipBasedBufferDeallocationPass> {
1017 void runOnOperation()
override {
1019 options.privateFuncDynamicOwnership = privateFuncDynamicOwnership;
1021 mlir::SymbolTableCollection symbolTables;
1023 auto status = getOperation()->walk([&](func::FuncOp func) {
1024 if (func.isExternal())
1032 if (status.wasInterrupted())
1033 signalPassFailure();
1047 BufferDeallocation deallocation(op,
options, symbolTables);
1050 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.
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.
static constexpr RegionBranchPoint parent()
Returns an instance of RegionBranchPoint representing the parent operation.
static RegionSuccessor parent()
Initialize a successor that branches after/out of the parent operation.
This class contains a list of basic blocks and a link to the parent operation it is attached to.
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.