Skip to content

Commit a9190c1

Browse files
committed
[mlir][bufferization] Remove allow-return-allocs and create-deallocs pass options, remove bufferization.escape attribute
This is the first commit in a series with the goal to rework the BufferDeallocation pass. Currently, this pass heavily relies on copies to perform correct deallocations, which leads to very slow code and potentially high memory usage. Additionally, there are unsupported cases such as returning memrefs which this series of commits aims to add support for as well. This first 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 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. The documentation should w.r.t. these pass option changes should also be updated in this commit. Already reviewed in https://reviews.llvm.org/D156662
1 parent 1a4dd8d commit a9190c1

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)