Skip to content

Commit 6bf043e

Browse files
authored
[mlir][bufferization] Remove allow-return-allocs and create-deallocs pass options, remove bufferization.escape attribute (#66619)
This commit removes the deallocation capabilities of one-shot-bufferization. One-shot-bufferization should never deallocate any memrefs as this should be entirely handled by the ownership-based-buffer-deallocation pass going forward. This means the `allow-return-allocs` pass option will default to true now, `create-deallocs` defaults to false and they, as well as the escape attribute indicating whether a memref escapes the current region, will be removed. A new `allow-return-allocs-from-loops` option is added as a temporary workaround for some bufferization limitations.
1 parent b2ffc86 commit 6bf043e

File tree

58 files changed

+181
-778
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+181
-778
lines changed

mlir/docs/Bufferization.md

Lines changed: 10 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -266,42 +266,16 @@ must be inserted due to a RaW conflict. E.g.:
266266
In the above example, a buffer copy of buffer(`%another_tensor`) (with `%cst`
267267
inserted) is yielded from the "then" branch.
268268

269-
In both examples, a buffer is allocated inside of a block and then yielded from
270-
the block. Deallocation of such buffers is tricky and not currently implemented
271-
in an efficient way. For this reason, One-Shot Bufferize must be explicitly
272-
configured with `allow-return-allocs` to support such IR.
273-
274-
When running with `allow-return-allocs`, One-Shot Bufferize may introduce
275-
allocations that cannot be deallocated by One-Shot Bufferize yet. For that
276-
reason, `-buffer-deallocation` must be run after One-Shot Bufferize. This buffer
277-
deallocation pass resolves yields of newly allocated buffers with copies. E.g.,
278-
the `scf.if` example above would bufferize to IR similar to the following:
279-
280-
```mlir
281-
%0 = scf.if %c -> (memref<?xf32>) {
282-
%1 = memref.alloc(...) : memref<?xf32>
283-
...
284-
scf.yield %1 : memref<?xf32>
285-
} else {
286-
%2 = memref.alloc(...) : memref<?xf32>
287-
memref.copy %another_memref, %2
288-
scf.yield %2 : memref<?xf32>
289-
}
290-
```
291-
292-
In the bufferized IR, both branches return a newly allocated buffer, so it does
293-
not matter which if-branch was taken. In both cases, the resulting buffer `%0`
294-
must be deallocated at some point after the `scf.if` (unless the `%0` is
295-
returned/yielded from its block).
296-
297-
Note: Buffer allocations that are returned from a function are not deallocated,
298-
not even with `-buffer-deallocation`. It is the caller's responsibility to
299-
deallocate the buffer. In the future, this could be automated with allocation
300-
hoisting (across function boundaries) or reference counting.
301-
302-
One-Shot Bufferize can be configured to leak all memory and not generate any
303-
buffer deallocations with `create-deallocs=0`. This can be useful for
304-
compatibility with legacy code that has its own method of deallocating buffers.
269+
Note: Buffer allocations that are returned from a function are not deallocated.
270+
It is the caller's responsibility to deallocate the buffer. For the full
271+
function boundary ABI for MemRefs w.r.t. buffer deallocation refer to the
272+
[*Function Boundary ABI*](#function-boundary-abi) section. In the future, this
273+
could be automated with allocation hoisting (across function boundaries) or
274+
reference counting.
275+
276+
One-Shot Bufferize leaks all memory and does not generate any buffer
277+
deallocations. The `-buffer-deallocation-pipeline` has to be run afterwards to
278+
insert the deallocation operations.
305279

306280
## Ownership-based Buffer Deallocation
307281

mlir/include/mlir/Dialect/Bufferization/IR/BufferizableOpInterface.h

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -361,10 +361,6 @@ struct BufferizationOptions {
361361
/// used.
362362
UnknownTypeConverterFn unknownTypeConverterFn = nullptr;
363363

364-
/// Specifies whether dealloc ops should be generated along with alloc ops. If
365-
/// not, new memory allocations will leak.
366-
bool createDeallocs = true;
367-
368364
/// Seed for the analysis fuzzer. If set to `0`, the fuzzer is deactivated.
369365
/// Should be used only with `testAnalysisOnly = true`.
370366
unsigned analysisFuzzerSeed = 0;
@@ -588,13 +584,9 @@ class AnalysisState {
588584
/// undefined contents is allocated.
589585
FailureOr<Value>
590586
allocateTensorForShapedValue(OpBuilder &b, Location loc, Value shapedValue,
591-
bool escape, const BufferizationOptions &options,
587+
const BufferizationOptions &options,
592588
bool copy = true);
593589

594-
/// Return `true` if the allocation of the given op is guaranteed to not escape
595-
/// the containing block.
596-
bool allocationDoesNotEscape(OpResult opResult);
597-
598590
/// Lookup the buffer for the given value. If the value was not bufferized
599591
/// yet, wrap it in a ToMemrefOp. Otherwise, it is the result of a ToTensorOp,
600592
/// from which the memref operand is returned.
@@ -641,12 +633,6 @@ OpTy replaceOpWithNewBufferizedOp(RewriterBase &rewriter, Operation *op,
641633
return newOp;
642634
}
643635

644-
/// Return `true` if the buffer of given OpResult should be deallocated. This
645-
/// function should be called during `BufferizableOpInterface::bufferize`
646-
/// implementations that allocate a new buffer for the given OpResult.
647-
bool shouldDeallocateOpResult(OpResult opResult,
648-
const BufferizationOptions &options);
649-
650636
/// Return a MemRefType to which the type of the given value can be bufferized.
651637
///
652638
/// If possible, op bufferization implementations should not use this function

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

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,6 @@ def Bufferization_Dialect : Dialect {
6060
/// arguments during One-Shot Module Bufferize.
6161
constexpr const static ::llvm::StringLiteral
6262
kBufferLayoutAttrName = "bufferization.buffer_layout";
63-
64-
/// Attribute name used to mark escaping behavior of buffer allocations.
65-
/// Escaping allocations cannot be deallocated in the same block and must
66-
/// be treated specially: They are currently deallocated with the
67-
/// BufferDeallocation pass.
68-
///
69-
/// Note: Only ops with at least one OpResult that bufferizes to a buffer
70-
/// allocation (as per BufferizableOpInterface) may have this attribute.
71-
constexpr const static ::llvm::StringLiteral
72-
kEscapeAttrName = "bufferization.escape";
7363
}];
7464
let hasOperationAttrVerify = 1;
7565
}

mlir/include/mlir/Dialect/Bufferization/TransformOps/BufferizationTransformOps.td

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,9 @@ def OneShotBufferizeOp
8282
let arguments = (
8383
ins TransformHandleTypeInterface:$target,
8484
OptionalAttr<LayoutMapOption>:$function_boundary_type_conversion,
85-
DefaultValuedAttr<BoolAttr, "false">:$allow_return_allocs,
85+
DefaultValuedAttr<BoolAttr, "false">:$allow_return_allocs_from_loops,
8686
DefaultValuedAttr<BoolAttr, "false">:$allow_unknown_ops,
8787
DefaultValuedAttr<BoolAttr, "false">:$bufferize_function_boundaries,
88-
DefaultValuedAttr<BoolAttr, "true">:$create_deallocs,
8988
DefaultValuedAttr<BoolAttr, "false">:$test_analysis_only,
9089
DefaultValuedAttr<BoolAttr, "false">:$print_conflicts,
9190
DefaultValuedAttr<StrAttr, "\"memref.copy\"">:$memcpy_op);

mlir/include/mlir/Dialect/Bufferization/Transforms/OneShotAnalysis.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ struct OneShotBufferizationOptions : public BufferizationOptions {
2828

2929
OneShotBufferizationOptions() = default;
3030

31-
/// Specifies whether returning newly allocated memrefs should be allowed.
32-
/// Otherwise, a pass failure is triggered.
33-
bool allowReturnAllocs = false;
31+
/// Specifies whether returning newly allocated memrefs from loops should be
32+
/// allowed. Otherwise, a pass failure is triggered.
33+
bool allowReturnAllocsFromLoops = false;
3434

3535
/// Specifies whether the tensor IR should be annotated with alias sets.
3636
bool dumpAliasSets = false;

mlir/include/mlir/Dialect/Bufferization/Transforms/Passes.td

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -387,15 +387,9 @@ def OneShotBufferize : Pass<"one-shot-bufferize", "ModuleOp"> {
387387
example, `tensor.generate` is not in destination-passing style and always
388388
results in a new buffer allocation.
389389

390-
One-Shot Bufferize deallocates all buffers that it allocates. Yielding newly
391-
allocated buffers from a block can lead to bad performance because
392-
additional buffer copies are often needed to make sure that every buffer
393-
allocation is also deallocated again. By default, such IR is rejected by
394-
One-Shot Bufferize. Such IR can be allowed with `allow-return-allocs`. In
395-
that case, the `-buffer-deallocation` pass should be run after One-Shot
396-
Bufferize. Note that new buffer allocations that are returned from a
397-
function can currently not be deallocated by `-buffer-deallocation` and
398-
leak.
390+
One-Shot Bufferize does not deallocate any buffers that it allocates. The
391+
`-buffer-deallocation` pass should be run after One-Shot Bufferize to insert
392+
the deallocation operations necessary to eliminate memory leaks.
399393

400394
One-Shot Bufferize will by default reject IR that contains non-bufferizable
401395
op, i.e., ops that do not implemement BufferizableOpInterface. Such IR can
@@ -462,9 +456,9 @@ def OneShotBufferize : Pass<"one-shot-bufferize", "ModuleOp"> {
462456
`test-analysis-only`.
463457
}];
464458
let options = [
465-
Option<"allowReturnAllocs", "allow-return-allocs", "bool",
466-
/*default=*/"false",
467-
"Allows returning/yielding new allocations from a block.">,
459+
Option<"allowReturnAllocsFromLoops", "allow-return-allocs-from-loops",
460+
"bool", /*default=*/"false",
461+
"Allows returning/yielding new allocations from a loop.">,
468462
Option<"allowUnknownOps", "allow-unknown-ops", "bool",
469463
/*default=*/"false",
470464
"Allows unknown (not bufferizable) ops in the input IR.">,
@@ -479,9 +473,6 @@ def OneShotBufferize : Pass<"one-shot-bufferize", "ModuleOp"> {
479473
"Bufferize function boundaries (experimental).">,
480474
Option<"copyBeforeWrite", "copy-before-write", "bool", /*default=*/"false",
481475
"Skip the analysis. Make a buffer copy on every write.">,
482-
Option<"createDeallocs", "create-deallocs", "bool", /*default=*/"true",
483-
"Specify if buffers should be deallocated. For compatibility with "
484-
"core bufferization passes.">,
485476
ListOption<"dialectFilter", "dialect-filter", "std::string",
486477
"Restrict bufferization to ops from these dialects.">,
487478
Option<"dumpAliasSets", "dump-alias-sets", "bool", /*default=*/"false",
@@ -513,8 +504,6 @@ def OneShotBufferize : Pass<"one-shot-bufferize", "ModuleOp"> {
513504
let statistics = [
514505
Statistic<"numBufferAlloc", "num-buffer-alloc",
515506
"Number of buffer allocations">,
516-
Statistic<"numBufferDealloc", "num-buffer-dealloc",
517-
"Number of buffer deallocations">,
518507
Statistic<"numTensorInPlace", "num-tensor-in-place",
519508
"Number of in-place tensor OpOperands">,
520509
Statistic<"numTensorOutOfPlace", "num-tensor-out-of-place",

mlir/include/mlir/Dialect/SparseTensor/Pipelines/Passes.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,7 @@ struct SparseCompilerOptions
9393
desc("Specify if the temporary buffers created by the sparse "
9494
"compiler should be deallocated. For compatibility with core "
9595
"bufferization passes. "
96-
"This option is only used when enable-runtime-library=false. "
97-
"See also create-deallocs for BufferizationOption."),
96+
"This option is only used when enable-runtime-library=false."),
9897
init(true)};
9998

10099
PassOptions::Option<int32_t> vectorLength{

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

Lines changed: 4 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -140,27 +140,11 @@ Operation *bufferization::getOwnerOfValue(Value value) {
140140
return llvm::cast<BlockArgument>(value).getOwner()->getParentOp();
141141
}
142142

143-
bool bufferization::allocationDoesNotEscape(OpResult opResult) {
144-
#ifndef NDEBUG
145-
auto bufferizableOp = opResult.getDefiningOp<BufferizableOpInterface>();
146-
assert(bufferizableOp && bufferizableOp.bufferizesToAllocation(opResult) &&
147-
"expected op that bufferizes to an allocation");
148-
#endif // NDEBUG
149-
150-
Operation *op = opResult.getDefiningOp();
151-
// If there is no 'escape' attribute, we cannot say for sure.
152-
if (!op->hasAttr(BufferizationDialect::kEscapeAttrName))
153-
return false;
154-
auto attr =
155-
op->getAttrOfType<ArrayAttr>(BufferizationDialect::kEscapeAttrName);
156-
return !llvm::cast<BoolAttr>(attr[opResult.getResultNumber()]).getValue();
157-
}
158-
159143
/// Create an AllocTensorOp for the given shaped value. If `copy` is set, the
160144
/// shaped value is copied. Otherwise, a tensor with undefined contents is
161145
/// allocated.
162146
FailureOr<Value> bufferization::allocateTensorForShapedValue(
163-
OpBuilder &b, Location loc, Value shapedValue, bool escape,
147+
OpBuilder &b, Location loc, Value shapedValue,
164148
const BufferizationOptions &options, bool copy) {
165149
Value tensor;
166150
if (llvm::isa<RankedTensorType>(shapedValue.getType())) {
@@ -202,8 +186,6 @@ FailureOr<Value> bufferization::allocateTensorForShapedValue(
202186
// Create AllocTensorOp.
203187
auto allocTensorOp = b.create<AllocTensorOp>(loc, tensorType, dynamicSizes,
204188
copy ? tensor : Value());
205-
allocTensorOp->setAttr(BufferizationDialect::kEscapeAttrName,
206-
b.getBoolArrayAttr({escape}));
207189

208190
// Add 'memory_space' attribute. Not needed if 'copy' operand is specified.
209191
if (copy)
@@ -224,10 +206,8 @@ LogicalResult BufferizableOpInterface::resolveTensorOpOperandConflicts(
224206
Operation *op = getOperation();
225207
SmallVector<OpOperand *> outOfPlaceOpOperands;
226208
DenseSet<OpOperand *> copiedOpOperands;
227-
DenseSet<OpOperand *> escapingOpOperandCopies;
228209
SmallVector<Value> outOfPlaceValues;
229210
DenseSet<Value> copiedOpValues;
230-
DenseSet<Value> escapingValueCopies;
231211

232212
// Find all out-of-place OpOperands.
233213
for (OpOperand &opOperand : op->getOpOperands()) {
@@ -243,11 +223,6 @@ LogicalResult BufferizableOpInterface::resolveTensorOpOperandConflicts(
243223
// Is the result yielded from a block? Or are deallocations turned off
244224
// entirely? In either case, mark the allocation as "escaping", so that it
245225
// will not be deallocated.
246-
bool escape = !state.getOptions().createDeallocs ||
247-
llvm::any_of(aliasingValues, [&](AliasingValue a) {
248-
return state.isTensorYielded(a.value);
249-
});
250-
251226
if (aliasingValues.getNumAliases() == 1 &&
252227
isa<OpResult>(aliasingValues.getAliases()[0].value) &&
253228
!state.bufferizesToMemoryWrite(opOperand) &&
@@ -265,24 +240,19 @@ LogicalResult BufferizableOpInterface::resolveTensorOpOperandConflicts(
265240
outOfPlaceValues.push_back(value);
266241
if (!state.canOmitTensorCopy(opOperand))
267242
copiedOpValues.insert(value);
268-
if (escape)
269-
escapingValueCopies.insert(value);
270243
} else {
271244
// In all other cases, make a copy of the OpOperand.
272245
outOfPlaceOpOperands.push_back(&opOperand);
273246
if (!state.canOmitTensorCopy(opOperand))
274247
copiedOpOperands.insert(&opOperand);
275-
if (escape)
276-
escapingOpOperandCopies.insert(&opOperand);
277248
}
278249
}
279250

280251
// Insert copies of OpOperands.
281252
rewriter.setInsertionPoint(op);
282253
for (OpOperand *opOperand : outOfPlaceOpOperands) {
283254
FailureOr<Value> copy = allocateTensorForShapedValue(
284-
rewriter, op->getLoc(), opOperand->get(),
285-
escapingOpOperandCopies.contains(opOperand), state.getOptions(),
255+
rewriter, op->getLoc(), opOperand->get(), state.getOptions(),
286256
copiedOpOperands.contains(opOperand));
287257
if (failed(copy))
288258
return failure();
@@ -293,8 +263,8 @@ LogicalResult BufferizableOpInterface::resolveTensorOpOperandConflicts(
293263
rewriter.setInsertionPointAfter(op);
294264
for (Value value : outOfPlaceValues) {
295265
FailureOr<Value> copy = allocateTensorForShapedValue(
296-
rewriter, op->getLoc(), value, escapingValueCopies.contains(value),
297-
state.getOptions(), copiedOpValues.count(value));
266+
rewriter, op->getLoc(), value, state.getOptions(),
267+
copiedOpValues.count(value));
298268
if (failed(copy))
299269
return failure();
300270
SmallVector<OpOperand *> uses = llvm::to_vector(
@@ -314,29 +284,6 @@ LogicalResult BufferizableOpInterface::resolveTensorOpOperandConflicts(
314284
return success();
315285
}
316286

317-
bool bufferization::shouldDeallocateOpResult(
318-
OpResult opResult, const BufferizationOptions &options) {
319-
Operation *op = opResult.getOwner();
320-
assert(options.dynCastBufferizableOp(op).bufferizesToAllocation(opResult) &&
321-
"expected that op allocates");
322-
323-
AnalysisState analysisState(options);
324-
if (op->hasAttr(BufferizationDialect::kEscapeAttrName)) {
325-
// AllocTensorOp has one result.
326-
ArrayAttr escapeAttr = llvm::cast<ArrayAttr>(
327-
op->getAttr(BufferizationDialect::kEscapeAttrName));
328-
return !llvm::cast<BoolAttr>(escapeAttr[0]).getValue();
329-
}
330-
331-
// No "escape" annotation found.
332-
if (options.createDeallocs) {
333-
// Perform an ad-hoc analysis.
334-
return !analysisState.isTensorYielded(opResult);
335-
}
336-
337-
return false;
338-
}
339-
340287
//===----------------------------------------------------------------------===//
341288
// OpFilter
342289
//===----------------------------------------------------------------------===//

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

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

31-
/// Attribute name used to mark escaping behavior of buffer allocations.
32-
constexpr const ::llvm::StringLiteral BufferizationDialect::kEscapeAttrName;
33-
3431
//===----------------------------------------------------------------------===//
3532
// Bufferization Dialect Interfaces
3633
//===----------------------------------------------------------------------===//
@@ -108,38 +105,6 @@ BufferizationDialect::verifyOperationAttribute(Operation *op,
108105
NamedAttribute attr) {
109106
using bufferization::BufferizableOpInterface;
110107

111-
if (attr.getName() == kEscapeAttrName) {
112-
auto arrayAttr = llvm::dyn_cast<ArrayAttr>(attr.getValue());
113-
if (!arrayAttr)
114-
return op->emitError() << "'" << kEscapeAttrName
115-
<< "' is expected to be a bool array attribute";
116-
if (arrayAttr.size() != op->getNumResults())
117-
return op->emitError()
118-
<< "'" << kEscapeAttrName
119-
<< "' has wrong number of elements, expected "
120-
<< op->getNumResults() << ", got " << arrayAttr.size();
121-
auto bufferizableOp = dyn_cast<BufferizableOpInterface>(op);
122-
if (!bufferizableOp)
123-
return op->emitError()
124-
<< "'" << kEscapeAttrName << "' only valid on bufferizable ops";
125-
for (const auto &it : llvm::enumerate(arrayAttr)) {
126-
auto attr = it.value();
127-
auto boolAttr = llvm::dyn_cast<BoolAttr>(attr);
128-
if (!boolAttr)
129-
return op->emitError() << "'" << kEscapeAttrName
130-
<< "' is expected to be a bool array attribute";
131-
if (!boolAttr.getValue())
132-
continue;
133-
if (!llvm::isa<TensorType>(op->getResult(it.index()).getType()))
134-
return op->emitError()
135-
<< "'" << kEscapeAttrName << "' only valid for tensor results";
136-
if (!bufferizableOp.bufferizesToAllocation(op->getOpResult(it.index())))
137-
return op->emitError() << "'" << kEscapeAttrName
138-
<< "' only valid for allocation results";
139-
}
140-
return success();
141-
}
142-
143108
return op->emitError()
144109
<< "attribute '" << attr.getName()
145110
<< "' not supported as an op attribute by the bufferization dialect";

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

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -187,20 +187,9 @@ LogicalResult AllocTensorOp::bufferize(RewriterBase &rewriter,
187187
return failure();
188188
}
189189

190-
// Should the buffer be deallocated?
191-
bool dealloc =
192-
shouldDeallocateOpResult(llvm::cast<OpResult>(getResult()), options);
193-
194190
// Replace op.
195191
replaceOpWithBufferizedValues(rewriter, getOperation(), *alloc);
196192

197-
// Create buffer deallocation (if requested).
198-
if (!dealloc)
199-
return success();
200-
201-
rewriter.setInsertionPoint(rewriter.getInsertionBlock()->getTerminator());
202-
if (failed(options.createDealloc(rewriter, loc, *alloc)))
203-
return failure();
204193
return success();
205194
}
206195

0 commit comments

Comments
 (0)