32 namespace bufferization {
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));
54 return !mightHaveEffect<MemoryEffects::Allocate>(op) &&
55 !mightHaveEffect<MemoryEffects::Free>(op);
66 if (llvm::any_of(region.front().getArguments(),
isMemref))
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); }
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;
161 class BufferDeallocation {
168 LogicalResult deallocate(FunctionOpInterface op);
172 template <
typename... T>
173 typename std::enable_if<
sizeof...(T) == 0, FailureOr<Operation *>>::type
206 template <
typename InterfaceT,
typename... InterfacesU>
207 FailureOr<Operation *> handleOp(
Operation *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);
370 template <
typename OpTy>
372 return cast<OpTy>(appendOpResults(op.getOperation(), types));
378 LogicalResult deallocate(
Block *block);
391 void populateRemainingOwnerships(
Operation *op);
396 Value materializeMemrefWithGuaranteedOwnership(
OpBuilder &builder,
402 bool isFunctionWithoutDynamicOwnership(
Operation *op);
411 std::pair<Value, Value>
418 static LogicalResult verifyFunctionPreconditions(FunctionOpInterface op);
434 static LogicalResult verifyOperationPreconditions(
Operation *op);
441 static LogicalResult updateFunctionSignature(FunctionOpInterface op);
459 std::pair<Value, Value>
460 BufferDeallocation::materializeUniqueOwnership(
OpBuilder &builder,
Value memref,
465 return state.getMemrefWithUniqueOwnership(builder, memref, block);
473 if (
auto deallocOpInterface = dyn_cast<BufferDeallocationOpInterface>(owner))
474 return deallocOpInterface.materializeUniqueOwnershipForMemref(
475 state,
options, builder, memref);
478 return state.getMemrefWithUniqueOwnership(builder, memref, block);
482 BufferDeallocation::verifyFunctionPreconditions(FunctionOpInterface op) {
485 Backedges backedges(op);
486 if (backedges.size()) {
487 op->emitError(
"Only structured control-flow loops are supported.");
494 LogicalResult 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!");
562 BufferDeallocation::updateFunctionSignature(FunctionOpInterface op) {
564 op.getFunctionBody().getOps<RegionBranchTerminatorOpInterface>(),
565 [](RegionBranchTerminatorOpInterface op) {
566 return op.getSuccessorOperands(RegionBranchPoint::parent()).getTypes();
568 if (!llvm::all_equal(returnOperandTypes))
569 return op->emitError(
570 "there are multiple return operations with different operand types");
572 TypeRange resultTypes = op.getResultTypes();
577 if (!returnOperandTypes.empty())
578 resultTypes = returnOperandTypes[0];
581 op->getContext(), op.getFunctionBody().front().getArgumentTypes(),
587 LogicalResult BufferDeallocation::deallocate(FunctionOpInterface op) {
589 if (
failed(verifyFunctionPreconditions(op)))
595 if (
failed(deallocate(block)))
599 if (result.wasInterrupted())
604 return updateFunctionSignature(op);
607 LogicalResult BufferDeallocation::deallocate(
Block *block) {
612 state.getLiveMemrefsIn(block, liveMemrefs);
613 for (
auto li : liveMemrefs) {
617 if (li.getParentRegion() == block->
getParent()) {
618 state.updateOwnership(li, state.getOwnership(li, li.getParentBlock()),
620 state.addMemrefToDeallocate(li, block);
624 if (li.getParentRegion()->isProperAncestor(block->
getParent())) {
626 state.updateOwnership(li, falseVal, block);
637 if (isa<FunctionOpInterface>(block->
getParentOp()) &&
640 state.updateOwnership(arg, newArg);
641 state.addMemrefToDeallocate(arg, block);
647 state.updateOwnership(arg, newArg);
648 state.addMemrefToDeallocate(arg, block);
653 for (
Operation &op : llvm::make_early_inc_range(*block)) {
654 FailureOr<Operation *> result = handleAllInterfaces(&op);
660 populateRemainingOwnerships(*result);
670 newTypes.append(types.begin(), types.end());
675 for (
auto [oldRegion, newRegion] :
676 llvm::zip(op->
getRegions(), newOp->getRegions()))
677 newRegion.takeBody(oldRegion);
686 FailureOr<Operation *>
687 BufferDeallocation::handleInterface(RegionBranchOpInterface op) {
705 assert(!regions.empty() &&
"Must have at least one successor region");
707 op.getEntrySuccessorOperands(regions.front()));
708 unsigned numMemrefOperands = llvm::count_if(entryOperands,
isMemref);
713 op->insertOperands(op->getNumOperands(),
716 int counter = op->getNumResults();
717 unsigned numMemrefResults = llvm::count_if(op->getResults(),
isMemref);
719 RegionBranchOpInterface newOp = appendOpResults(op, ownershipResults);
721 for (
auto result : llvm::make_filter_range(newOp->getResults(),
isMemref)) {
722 state.updateOwnership(result, newOp->getResult(counter++));
723 state.addMemrefToDeallocate(result, newOp->getBlock());
726 return newOp.getOperation();
729 Value BufferDeallocation::materializeMemrefWithGuaranteedOwnership(
732 std::pair<Value, Value> newMemrefAndOnwership =
733 materializeUniqueOwnership(builder, memref, block);
734 Value newMemref = newMemrefAndOnwership.first;
735 Value condition = newMemrefAndOnwership.second;
745 Value maybeClone = scf::IfOp::create(
746 builder, memref.
getLoc(), condition,
748 scf::YieldOp::create(builder, loc, newMemref);
751 Value clone = bufferization::CloneOp::create(
752 builder, loc, newMemref);
753 scf::YieldOp::create(builder, loc, clone);
757 state.updateOwnership(maybeClone, trueVal);
758 state.addMemrefToDeallocate(maybeClone, maybeClone.
getParentBlock());
762 FailureOr<Operation *>
763 BufferDeallocation::handleInterface(BranchOpInterface op) {
764 if (op->getNumSuccessors() > 1)
765 return op->emitError(
"BranchOpInterface operations with multiple "
766 "successors are not supported yet");
768 if (op->getNumSuccessors() != 1)
770 "only BranchOpInterface operations with exactly "
771 "one successor are supported yet");
773 if (op.getSuccessorOperands(0).getProducedOperandCount() > 0)
774 return op.emitError(
"produced operands are not supported");
778 Block *block = op->getBlock();
781 if (
failed(state.getMemrefsAndConditionsToDeallocate(
782 builder, op.getLoc(), block, memrefs, conditions)))
786 op.getSuccessorOperands(0).getForwardedOperands();
787 state.getMemrefsToRetain(block, op->getSuccessor(0), forwardedOperands,
790 auto deallocOp = bufferization::DeallocOp::create(
791 builder, op.getLoc(), memrefs, conditions, toRetain);
795 state.resetOwnerships(deallocOp.getRetained(), block);
796 for (
auto [retained, ownership] :
797 llvm::zip(deallocOp.getRetained(), deallocOp.getUpdatedConditions())) {
798 state.updateOwnership(retained, ownership, block);
801 unsigned numAdditionalReturns = llvm::count_if(forwardedOperands,
isMemref);
803 auto additionalConditions =
804 deallocOp.getUpdatedConditions().take_front(numAdditionalReturns);
805 newOperands.append(additionalConditions.begin(), additionalConditions.end());
806 op.getSuccessorOperands(0).getMutableForwardedOperands().assign(newOperands);
808 return op.getOperation();
811 FailureOr<Operation *> BufferDeallocation::handleInterface(CallOpInterface op) {
818 Operation *funcOp = op.resolveCallableInTable(state.getSymbolTable());
819 bool isPrivate =
false;
820 if (
auto symbol = dyn_cast_or_null<SymbolOpInterface>(funcOp))
821 isPrivate = symbol.isPrivate() && !symbol.isDeclaration();
827 if (
options.privateFuncDynamicOwnership && isPrivate) {
828 unsigned numMemrefs = llvm::count_if(op->getResults(),
isMemref);
830 unsigned ownershipCounter = op->getNumResults();
831 op = appendOpResults(op, ownershipTypesToAppend);
833 for (
auto result : llvm::make_filter_range(op->getResults(),
isMemref)) {
834 state.updateOwnership(result, op->getResult(ownershipCounter++));
835 state.addMemrefToDeallocate(result, result.getParentBlock());
838 return op.getOperation();
845 for (
auto result : llvm::make_filter_range(op->getResults(),
isMemref)) {
846 state.updateOwnership(result, trueVal);
847 state.addMemrefToDeallocate(result, result.getParentBlock());
850 return op.getOperation();
853 FailureOr<Operation *>
854 BufferDeallocation::handleInterface(MemoryEffectOpInterface op) {
855 auto *block = op->getBlock();
858 for (
auto operand : llvm::make_filter_range(op->getOperands(),
isMemref)) {
866 if (!op->hasAttr(BufferizationDialect::kManualDeallocation))
867 return op->emitError(
868 "memory free side-effect on MemRef value not supported!");
876 Ownership ownership = state.getOwnership(operand, block);
878 Value ownershipInverted = arith::XOrIOp::create(
881 cf::AssertOp::create(builder, op.getLoc(), ownershipInverted,
882 "expected that the block does not have ownership");
887 for (
auto res : llvm::make_filter_range(op->getResults(),
isMemref)) {
889 if (allocEffect.has_value()) {
890 if (isa<SideEffects::AutomaticAllocationScopeResource>(
891 allocEffect->getResource())) {
899 state.resetOwnerships(res, block);
900 state.updateOwnership(res,
buildBoolValue(builder, op.getLoc(),
false));
904 if (op->hasAttr(BufferizationDialect::kManualDeallocation)) {
908 state.resetOwnerships(res, block);
909 state.updateOwnership(res,
buildBoolValue(builder, op.getLoc(),
false));
913 state.updateOwnership(res,
buildBoolValue(builder, op.getLoc(),
true));
914 state.addMemrefToDeallocate(res, block);
918 return op.getOperation();
921 FailureOr<Operation *>
922 BufferDeallocation::handleInterface(RegionBranchTerminatorOpInterface op) {
929 bool funcWithoutDynamicOwnership =
930 isFunctionWithoutDynamicOwnership(op->getParentOp());
931 if (funcWithoutDynamicOwnership) {
932 for (
OpOperand &val : op->getOpOperands()) {
936 val.set(materializeMemrefWithGuaranteedOwnership(builder, val.get(),
951 if (
failed(result) || !*result)
955 if (!funcWithoutDynamicOwnership) {
957 newOperands.append(updatedOwnerships.begin(), updatedOwnerships.end());
958 operands.
assign(newOperands);
961 return op.getOperation();
964 bool BufferDeallocation::isFunctionWithoutDynamicOwnership(
Operation *op) {
965 auto funcOp = dyn_cast<FunctionOpInterface>(op);
966 return funcOp && (!
options.privateFuncDynamicOwnership ||
967 !funcOp.isPrivate() || funcOp.isExternal());
970 void BufferDeallocation::populateRemainingOwnerships(
Operation *op) {
974 if (!state.getOwnership(res, op->
getBlock()).isUninitialized())
984 state.updateOwnership(
985 res, state.getOwnership(operand, operand.getParentBlock()),
995 if (state.getOwnership(res, op->
getBlock()).isUninitialized()) {
1011 struct OwnershipBasedBufferDeallocationPass
1012 :
public bufferization::impl::OwnershipBasedBufferDeallocationPassBase<
1013 OwnershipBasedBufferDeallocationPass> {
1016 void runOnOperation()
override {
1018 options.privateFuncDynamicOwnership = privateFuncDynamicOwnership;
1022 auto status = getOperation()->walk([&](func::FuncOp func) {
1023 if (func.isExternal())
1031 if (status.wasInterrupted())
1032 signalPassFailure();
1046 BufferDeallocation deallocation(op,
options, symbolTables);
1049 return deallocation.deallocate(op);
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.
SuccessorRange getSuccessors()
BlockArgument addArgument(Type type, Location loc)
Add one value to the argument list.
OpListType & getOperations()
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.
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...
Block * getBlock()
Returns the operation block that contains this operation.
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
OperationName getName()
The name of an operation is the key identifier for it.
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.
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.
Location getLoc() const
Return the location of this value.
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
static WalkResult advance()
static WalkResult interrupt()
This class collects all the state that we need to perform the buffer deallocation pass with associate...
This class is used to track the ownership of values.
bool isUnique() const
Check if this ownership value is in the 'Unique' state.
Value getIndicator() const
If this ownership value is in 'Unique' state, this function can be used to get the indicator paramete...
FailureOr< Operation * > insertDeallocOpForReturnLike(DeallocationState &state, Operation *op, ValueRange operands, SmallVectorImpl< Value > &updatedOperandOwnerships)
Insert a bufferization.dealloc operation right before op which has to be a terminator without any suc...
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.
auto get(MLIRContext *context, Ts &&...params)
Helper method that injects context only if needed, this helps unify some of the attribute constructio...
bool hasUnknownEffects(Operation *op)
Return "true" if op has unknown effects.
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.