-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[MLIR][Bufferization] Bail on automatic deallocation to enable reentrant behaviour #72289
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Currently, the ownership based dealloc pass fails hard on existing deallocations, but it introduces its own deallocs, and if we run the pass twice (ex. when piping the IR through different tools with their own pipelines), we get a hard crash. However, having the deallocs is not an IR error or verification failure, it's just a pattern that the pass itself cannot handle, and therefore the correct behaviour is to bail, not crash.
@llvm/pr-subscribers-mlir-bufferization @llvm/pr-subscribers-mlir Author: Renato Golin (rengolin) ChangesCurrently, the ownership based dealloc pass fails hard on existing deallocations, but it introduces its own deallocs, and if we run the pass twice (ex. when piping the IR through different tools with their own pipelines), we get a hard crash. However, having the deallocs is not an IR error or verification failure, it's just a pattern that the pass itself cannot handle, and therefore the correct behaviour is to bail, not crash. Full diff: https://github.com/llvm/llvm-project/pull/72289.diff 3 Files Affected:
diff --git a/mlir/lib/Dialect/Bufferization/Transforms/OwnershipBasedBufferDeallocation.cpp b/mlir/lib/Dialect/Bufferization/Transforms/OwnershipBasedBufferDeallocation.cpp
index 9459cc43547fafc..73a88362756bda7 100644
--- a/mlir/lib/Dialect/Bufferization/Transforms/OwnershipBasedBufferDeallocation.cpp
+++ b/mlir/lib/Dialect/Bufferization/Transforms/OwnershipBasedBufferDeallocation.cpp
@@ -861,9 +861,15 @@ BufferDeallocation::handleInterface(MemoryEffectOpInterface op) {
for (auto operand : llvm::make_filter_range(op->getOperands(), isMemref)) {
if (op.getEffectOnValue<MemoryEffects::Free>(operand).has_value()) {
- if (!op->hasAttr(BufferizationDialect::kManualDeallocation))
- return op->emitError(
- "memory free side-effect on MemRef value not supported!");
+ // This should not be an error because the ownership based buffer
+ // deallocation introduces deallocs itself, so running it twice over (say
+ // when piping IR over different tools with their own pipelines) crashes
+ // the compiler on perfectly valid code.
+ if (!op->hasAttr(BufferizationDialect::kManualDeallocation)) {
+ state.resetOwnerships(operand, block);
+ state.updateOwnership(operand, buildBoolValue(builder, op.getLoc(), false));
+ continue;
+ }
// Buffers that were allocated under "manual deallocation" may be
// manually deallocated. We insert a runtime assertion to cover certain
diff --git a/mlir/test/Dialect/Bufferization/Transforms/OwnershipBasedBufferDeallocation/invalid-buffer-deallocation-reentrant.mlir b/mlir/test/Dialect/Bufferization/Transforms/OwnershipBasedBufferDeallocation/invalid-buffer-deallocation-reentrant.mlir
new file mode 100644
index 000000000000000..89271d3422bb028
--- /dev/null
+++ b/mlir/test/Dialect/Bufferization/Transforms/OwnershipBasedBufferDeallocation/invalid-buffer-deallocation-reentrant.mlir
@@ -0,0 +1,20 @@
+// RUN: mlir-opt -ownership-based-buffer-deallocation -split-input-file %s | \
+// RUN: mlir-opt -ownership-based-buffer-deallocation -split-input-file %s
+
+// This should not be an error because the ownership based buffer deallocation introduces
+// deallocs itself, so running it twice over (say when piping IR over different tools with
+// their own pipelines) crashes the compiler on perfectly valid code.
+
+func.func @free_effect() {
+ %alloc = memref.alloc() : memref<2xi32>
+ %new_alloc = memref.realloc %alloc : memref<2xi32> to memref<4xi32>
+ return
+}
+
+// -----
+
+func.func @free_effect() {
+ %alloc = memref.alloc() : memref<2xi32>
+ memref.dealloc %alloc : memref<2xi32>
+ return
+}
diff --git a/mlir/test/Dialect/Bufferization/Transforms/OwnershipBasedBufferDeallocation/invalid-buffer-deallocation.mlir b/mlir/test/Dialect/Bufferization/Transforms/OwnershipBasedBufferDeallocation/invalid-buffer-deallocation.mlir
index c623891e48362fa..113980d02f92a2b 100644
--- a/mlir/test/Dialect/Bufferization/Transforms/OwnershipBasedBufferDeallocation/invalid-buffer-deallocation.mlir
+++ b/mlir/test/Dialect/Bufferization/Transforms/OwnershipBasedBufferDeallocation/invalid-buffer-deallocation.mlir
@@ -66,24 +66,6 @@ func.func @do_loop_alloc(
// -----
-func.func @free_effect() {
- %alloc = memref.alloc() : memref<2xi32>
- // expected-error @below {{memory free side-effect on MemRef value not supported!}}
- %new_alloc = memref.realloc %alloc : memref<2xi32> to memref<4xi32>
- return
-}
-
-// -----
-
-func.func @free_effect() {
- %alloc = memref.alloc() : memref<2xi32>
- // expected-error @below {{memory free side-effect on MemRef value not supported!}}
- memref.dealloc %alloc : memref<2xi32>
- return
-}
-
-// -----
-
func.func @free_effect() {
%true = arith.constant true
%alloc = memref.alloc() : memref<2xi32>
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
mlir/lib/Dialect/Bufferization/Transforms/OwnershipBasedBufferDeallocation.cpp
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change can lead to double deallocs, e.g.:
func.func private @foo(%c: i1) {
%0 = memref.alloc() : memref<5xf32>
%1 = memref.alloc() : memref<5xf32>
%2 = arith.select %c, %0, %1 : memref<5xf32>
memref.dealloc %2 : memref<5xf32>
// additional deallocs will be added for %0 and %1
return
}
In the above case, we may be able to add a workaround in the buffer deallocation pass to fix the double dealloc. The real problem are deallocs within nested regions. In such cases, the block that contains the dealloc must take ownership of the memref. Otherwise, another block (the one that has ownership instead) will deallocate the memref. There is currently no way to force ownership without redesigning parts of the algorithm. (I think it gets even trickier with loops where we do not know if it has 0 or more iterations.)
Ideally, the buffer deallocation pass should run a single time towards the end of the compilation process. (Note, we have a similar situation with One-Shot Bufferize: if you do partial bufferization, you cannot run One-Shot Bufferize a second time and bufferize the rest. There are certain to_tensor
ops that it will reject.)
If you have pre-existing deallocs in your IR, there are two options:
- Remove all deallocs and let the buffer deallocation pass re-insert them. We have a pass that does that for
memref.realloc
:--expand-realloc=emit-deallocs=false
. Deallocs may not be inserted at the same position. E.g., deallocs will always be inserted in the same block as the alloc. - Annotate
memref.alloc
+memref.dealloc
withbufferization.manual_deallocation
. (You could do that after running the buffer deallocation pass.) Such ops will be ignored by the buffer deallocation pass. Note, this would not work with the example shown above if only one alloc has that attribute because the dealloc may receive a buffer that is not under manual deallocation.
@maerhart (author of the ownership-based buffer deallocation pass) to make sure that what I said is accurate |
I agree with what @matthias-springer said. This PR tries to add support for existing deallocation ops without considering all necessary cases. If we want to add support, we need to do it in a way that doesn't break the existing specification of the pass (no double free, etc.). But this is hard to do properly, as Matthias already pointed out. To add to the second point, we could add the attribute in the deallocation pass itself to those allocs it considered and the deallocs it inserts. It won't be possible to pass it IR as Matthias showed, but it would allow the deallocation pipeline to be run multiple times in sequence with the same configuration. Maybe we'd want a separate attribute, though, to differentiate manual from already automatically deallocated. |
Absolutely agree. My PR was more asking the question than proposing a solution (hence some comments were incomplete). The answer is clear: not enough. :)
If these pre-existing deallocs are added by the user or another pass, then these could be reasonable steps. But since they're added by the ownership pass itself, it wouldn't be self-consistent.
Perhaps. I assumed (from the field comment) that if the dealloc wasn't marked as Reading it again after your input, I think my understanding of the pass itself was mistaken. I'll have to re-evaluate the strategy, thanks for the feedback! |
Currently, the ownership based dealloc pass fails hard on existing deallocations, but it introduces its own deallocs, and if we run the pass twice (ex. when piping the IR through different tools with their own pipelines), we get a hard crash.
However, having the deallocs is not an IR error or verification failure, it's just a pattern that the pass itself cannot handle, and therefore the correct behaviour is to bail, not crash.