Skip to content

Commit 4d80eff

Browse files
[mlir][bufferization] Ownership-based deallocation: Allow manual (de)allocs (#68648)
Add a new attribute `bufferization.manual_deallocation` that can be attached to allocation and deallocation ops. Buffers that are allocated with this attribute are assigned an ownership of "false". Such buffers can be deallocated manually (e.g., with `memref.dealloc`) if the deallocation op also has the attribute set. Previously, the ownership-based buffer deallocation pass used to reject IR with existing deallocation ops. This is no longer the case if such ops have this new attribute. This change is useful for the sparse compiler, which currently deallocates the sparse tensor buffers by itself.
1 parent 7a3db65 commit 4d80eff

File tree

5 files changed

+104
-5
lines changed

5 files changed

+104
-5
lines changed

mlir/include/mlir/Dialect/Bufferization/IR/BufferizationBase.td

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,17 @@ def Bufferization_Dialect : Dialect {
6060
/// arguments during One-Shot Module Bufferize.
6161
constexpr const static ::llvm::StringLiteral
6262
kBufferLayoutAttrName = "bufferization.buffer_layout";
63+
64+
/// An attribute that can be attached to ops with an allocation and/or
65+
/// deallocation side effect. It indicates that the op is under a "manual
66+
/// deallocation" scheme. In the case of an allocation op, the returned
67+
/// value is *not* an automatically managed allocation and assigned an
68+
/// ownership of "false". Furthermore, only deallocation ops that are
69+
/// guaranteed to deallocate a buffer under "manual deallocation" are
70+
/// allowed to have this attribute. (Deallocation ops without this
71+
/// attribute are rejected by the ownership-based buffer deallocation pass.)
72+
constexpr const static ::llvm::StringLiteral
73+
kManualDeallocation = "bufferization.manual_deallocation";
6374
}];
6475
let hasOperationAttrVerify = 1;
6576
}

mlir/lib/Dialect/Bufferization/IR/BufferizationDialect.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ constexpr const ::llvm::StringLiteral BufferizationDialect::kWritableAttrName;
2828
constexpr const ::llvm::StringLiteral
2929
BufferizationDialect::kBufferLayoutAttrName;
3030

31+
/// An attribute that can be attached to ops with an allocation and/or
32+
/// deallocation side effect. It indicates that the op is under a "manual
33+
/// deallocation" scheme. In the case of an allocation op, the returned
34+
/// value is *not* an automatically managed allocation and assigned an
35+
/// ownership of "false". Furthermore, only deallocation ops that are
36+
/// guaranteed to deallocate a buffer under "manual deallocation" are
37+
/// allowed to have this attribute. (Deallocation ops without this
38+
/// attribute are rejected by the ownership-based buffer deallocation pass.)
39+
constexpr const ::llvm::StringLiteral BufferizationDialect::kManualDeallocation;
40+
3141
//===----------------------------------------------------------------------===//
3242
// Bufferization Dialect Interfaces
3343
//===----------------------------------------------------------------------===//
@@ -105,6 +115,16 @@ BufferizationDialect::verifyOperationAttribute(Operation *op,
105115
NamedAttribute attr) {
106116
using bufferization::BufferizableOpInterface;
107117

118+
if (attr.getName() == kManualDeallocation) {
119+
if (!mlir::hasEffect<MemoryEffects::Allocate>(op) &&
120+
!mlir::hasEffect<MemoryEffects::Free>(op))
121+
return op->emitOpError("attribute '")
122+
<< kManualDeallocation
123+
<< "' can be used only on ops that have an allocation and/or free "
124+
"side effect";
125+
return success();
126+
}
127+
108128
return op->emitError()
109129
<< "attribute '" << attr.getName()
110130
<< "' not supported as an op attribute by the bufferization dialect";

mlir/lib/Dialect/Bufferization/Transforms/OwnershipBasedBufferDeallocation.cpp

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "mlir/Dialect/Bufferization/IR/BufferDeallocationOpInterface.h"
2222
#include "mlir/Dialect/Bufferization/IR/Bufferization.h"
2323
#include "mlir/Dialect/Bufferization/Transforms/Passes.h"
24+
#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
2425
#include "mlir/Dialect/Func/IR/FuncOps.h"
2526
#include "mlir/Dialect/MemRef/IR/MemRef.h"
2627
#include "mlir/Dialect/SCF/IR/SCF.h"
@@ -856,13 +857,32 @@ FailureOr<Operation *> BufferDeallocation::handleInterface(CallOpInterface op) {
856857
FailureOr<Operation *>
857858
BufferDeallocation::handleInterface(MemoryEffectOpInterface op) {
858859
auto *block = op->getBlock();
860+
OpBuilder builder = OpBuilder::atBlockBegin(block);
859861

860-
for (auto operand : llvm::make_filter_range(op->getOperands(), isMemref))
861-
if (op.getEffectOnValue<MemoryEffects::Free>(operand).has_value())
862-
return op->emitError(
863-
"memory free side-effect on MemRef value not supported!");
862+
for (auto operand : llvm::make_filter_range(op->getOperands(), isMemref)) {
863+
if (op.getEffectOnValue<MemoryEffects::Free>(operand).has_value()) {
864+
if (!op->hasAttr(BufferizationDialect::kManualDeallocation))
865+
return op->emitError(
866+
"memory free side-effect on MemRef value not supported!");
867+
868+
// Buffers that were allocated under "manual deallocation" may be
869+
// manually deallocated. We insert a runtime assertion to cover certain
870+
// cases of invalid IR where an automatically managed buffer allocation
871+
// is manually deallocated. This is not a bulletproof check!
872+
OpBuilder::InsertionGuard g(builder);
873+
builder.setInsertionPoint(op);
874+
Ownership ownership = state.getOwnership(operand, block);
875+
if (ownership.isUnique()) {
876+
Value ownershipInverted = builder.create<arith::XOrIOp>(
877+
op.getLoc(), ownership.getIndicator(),
878+
buildBoolValue(builder, op.getLoc(), true));
879+
builder.create<cf::AssertOp>(
880+
op.getLoc(), ownershipInverted,
881+
"expected that the block does not have ownership");
882+
}
883+
}
884+
}
864885

865-
OpBuilder builder = OpBuilder::atBlockBegin(block);
866886
for (auto res : llvm::make_filter_range(op->getResults(), isMemref)) {
867887
auto allocEffect = op.getEffectOnValue<MemoryEffects::Allocate>(res);
868888
if (allocEffect.has_value()) {
@@ -880,6 +900,15 @@ BufferDeallocation::handleInterface(MemoryEffectOpInterface op) {
880900
continue;
881901
}
882902

903+
if (op->hasAttr(BufferizationDialect::kManualDeallocation)) {
904+
// This allocation will be deallocated manually. Assign an ownership of
905+
// "false", so that it will never be deallocated by the buffer
906+
// deallocation pass.
907+
state.resetOwnerships(res, block);
908+
state.updateOwnership(res, buildBoolValue(builder, op.getLoc(), false));
909+
continue;
910+
}
911+
883912
state.updateOwnership(res, buildBoolValue(builder, op.getLoc(), true));
884913
state.addMemrefToDeallocate(res, block);
885914
}

mlir/test/Dialect/Bufferization/Transforms/OwnershipBasedBufferDeallocation/dealloc-memoryeffect-interface.mlir

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,35 @@ func.func @op_without_aliasing_and_allocation() -> memref<4xf32> {
124124
// CHECK: [[CLONE:%.+]] = bufferization.clone [[GLOBAL]]
125125
// CHECK: scf.yield [[CLONE]] :
126126
// CHECK: return [[RES]] :
127+
128+
// -----
129+
130+
// Allocations with "bufferization.manual_deallocation" are assigned an
131+
// ownership of "false".
132+
133+
func.func @manual_deallocation(%c: i1, %f: f32, %idx: index) -> f32 {
134+
%0 = memref.alloc() {bufferization.manual_deallocation} : memref<5xf32>
135+
linalg.fill ins(%f : f32) outs(%0 : memref<5xf32>)
136+
%1 = memref.alloc() : memref<5xf32>
137+
linalg.fill ins(%f : f32) outs(%1 : memref<5xf32>)
138+
%2 = arith.select %c, %0, %1 : memref<5xf32>
139+
%3 = memref.load %2[%idx] : memref<5xf32>
140+
141+
// Only buffers that are under "manual deallocation" are allowed to be
142+
// deallocated with memref.dealloc. For consistency reasons, the
143+
// manual_deallocation attribute must also be specified. A runtime insertion
144+
// is inserted to ensure that we do not have ownership. (This is not a
145+
// bulletproof check, but covers some cases of invalid IR.)
146+
memref.dealloc %0 {bufferization.manual_deallocation} : memref<5xf32>
147+
148+
return %3 : f32
149+
}
150+
151+
// CHECK-LABEL: func @manual_deallocation(
152+
// CHECK: %[[true:.*]] = arith.constant true
153+
// CHECK: %[[manual_alloc:.*]] = memref.alloc() {bufferization.manual_deallocation} : memref<5xf32>
154+
// CHECK: %[[managed_alloc:.*]] = memref.alloc() : memref<5xf32>
155+
// CHECK: %[[selected:.*]] = arith.select
156+
// CHECK: cf.assert %[[true]], "expected that the block does not have ownership"
157+
// CHECK: memref.dealloc %[[manual_alloc]]
158+
// CHECK: bufferization.dealloc (%[[managed_alloc]] : memref<5xf32>) if (%[[true]])

mlir/test/Dialect/Bufferization/invalid.mlir

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,10 @@ func.func @invalid_dealloc_wrong_number_of_results(%arg0: memref<2xf32>, %arg1:
136136
%0:3 = "bufferization.dealloc"(%arg0, %arg1, %arg2, %arg2, %arg1) <{operandSegmentSizes = array<i32: 2, 2, 1>}> : (memref<2xf32>, memref<4xi32>, i1, i1, memref<4xi32>) -> (i1, i1, i1)
137137
return %0#0 : i1
138138
}
139+
140+
// -----
141+
142+
func.func @invalid_manual_deallocation() {
143+
// expected-error @below{{op attribute 'bufferization.manual_deallocation' can be used only on ops that have an allocation and/or free side effect}}
144+
arith.constant {bufferization.manual_deallocation} 0 : index
145+
}

0 commit comments

Comments
 (0)