[Mlir-commits] [mlir] [mlir][bufferization] Fix placement walk crashes and invalid captures (PR #228050)
Federico Bruzzone
llvmlistbot at llvm.org
Fri Oct 2 04:12:41 PDT 2026
https://github.com/FedericoBruzzone updated https://github.com/llvm/llvm-project/pull/228050
>From cc6b9d7612b35f92d8d73750ab7233bd35d613e2 Mon Sep 17 00:00:00 2001
From: Matteo Di Iorio <matteo.diiorio2 at studio.unibo.it>
Date: Thu, 1 Oct 2026 13:57:18 +0200
Subject: [PATCH] [mlir][bufferization] Fix placement walk crashes and invalid
captures
Keep the walk inside the analysis root and isolated regions, and stop
on unreachable enclosing blocks
---
.../Transforms/BufferOptimizations.cpp | 16 +++---
.../Transforms/buffer-hoisting.mlir | 17 ++++++
.../Transforms/buffer-loop-hoisting.mlir | 24 +++++++++
.../Transforms/transform-ops.mlir | 53 +++++++++++++++++++
4 files changed, 104 insertions(+), 6 deletions(-)
diff --git a/mlir/lib/Dialect/Bufferization/Transforms/BufferOptimizations.cpp b/mlir/lib/Dialect/Bufferization/Transforms/BufferOptimizations.cpp
index 4f2a4734056b63..e03887c8c59108 100644
--- a/mlir/lib/Dialect/Bufferization/Transforms/BufferOptimizations.cpp
+++ b/mlir/lib/Dialect/Bufferization/Transforms/BufferOptimizations.cpp
@@ -268,6 +268,11 @@ class BufferAllocationHoisting : public BufferPlacementTransformationBase {
(parentBlock = parentOp->getBlock()) &&
(!upperBound ||
dominators.properlyDominates(upperBound, currentBlock))) {
+ // A reachable nested region may have an unreachable enclosing block,
+ // which has no node in the dominator tree.
+ if (!dominators.isReachableFromEntry(currentBlock))
+ break;
+
// Try to find an immediate dominator and check whether the parent block
// is above the immediate dominator (if any).
DominanceInfoNode *idom = nullptr;
@@ -282,12 +287,11 @@ class BufferAllocationHoisting : public BufferPlacementTransformationBase {
currentBlock = idom->getBlock();
state.recordMoveToDominator(currentBlock);
} else {
- // We have to move to our parent block since an immediate dominator does
- // either not exist or is above our parent block. If we cannot move to
- // our parent operation due to constraints given by the StateT
- // implementation, break the walk loop. Furthermore, we should not move
- // allocations out of unknown region-based control-flow operations.
- if (!isKnownControlFlowInterface(parentOp) ||
+ // The target's parent block is outside the liveness analysis. Keep
+ // allocations within the target and preserve isolation boundaries.
+ if (parentOp == scopeOp ||
+ parentOp->hasTrait<OpTrait::IsIsolatedFromAbove>() ||
+ !isKnownControlFlowInterface(parentOp) ||
!state.isLegalPlacement(parentOp))
break;
// Move to our parent block by notifying the current StateT
diff --git a/mlir/test/Dialect/Bufferization/Transforms/buffer-hoisting.mlir b/mlir/test/Dialect/Bufferization/Transforms/buffer-hoisting.mlir
index 4c07df951e2806..827fcbb0a156b8 100644
--- a/mlir/test/Dialect/Bufferization/Transforms/buffer-hoisting.mlir
+++ b/mlir/test/Dialect/Bufferization/Transforms/buffer-hoisting.mlir
@@ -802,3 +802,20 @@ func.func @unreachable_alloc(%arg0: f32) {
// CHECK: memref.alloc
return
}
+
+// -----
+
+// Hoisting must not introduce a capture into an isolated region.
+// CHECK-LABEL: func @no_hoist_isolated_region(
+// CHECK-NOT: memref.alloc
+// CHECK: %[[RESULT:.*]] = "test.isolated_region_branch"
+// CHECK-NEXT: %[[ALLOC:.*]] = memref.alloc()
+// CHECK-NEXT: "test.isolated_region_yield"(%[[ALLOC]])
+// CHECK: return %[[RESULT]]
+func.func @no_hoist_isolated_region() -> memref<1xi32> {
+ %result = "test.isolated_region_branch"() ({
+ %buffer = memref.alloc() : memref<1xi32>
+ "test.isolated_region_yield"(%buffer) : (memref<1xi32>) -> ()
+ }) : () -> memref<1xi32>
+ return %result : memref<1xi32>
+}
diff --git a/mlir/test/Dialect/Bufferization/Transforms/buffer-loop-hoisting.mlir b/mlir/test/Dialect/Bufferization/Transforms/buffer-loop-hoisting.mlir
index ee04564f22f6ba..7498f7af8c5d1b 100644
--- a/mlir/test/Dialect/Bufferization/Transforms/buffer-loop-hoisting.mlir
+++ b/mlir/test/Dialect/Bufferization/Transforms/buffer-loop-hoisting.mlir
@@ -543,3 +543,27 @@ func.func @hoist_alloca(
// CHECK: %[[ALLOCA0:.*]] = memref.alloca({{.*}})
// CHECK-NEXT: %[[ALLOCA1:.*]] = memref.alloca({{.*}})
// CHECK-NEXT: {{.*}} = scf.for
+
+// -----
+
+// A nested entry block can be reachable even when its enclosing block is not.
+// CHECK-LABEL: func @loop_unreachable_parent(
+// CHECK: return
+// CHECK: ^bb1:
+// CHECK: %[[ALLOC:.*]] = memref.alloc()
+// CHECK-NEXT: scf.while
+// CHECK-NEXT: memref.store {{.*}}, %[[ALLOC]]
+func.func @loop_unreachable_parent() {
+ return
+^dead:
+ %c0 = arith.constant 0 : index
+ %false = arith.constant false
+ scf.while : () -> () {
+ %buffer = memref.alloc() : memref<1xindex>
+ memref.store %c0, %buffer[%c0] : memref<1xindex>
+ scf.condition(%false)
+ } do {
+ scf.yield
+ }
+ return
+}
diff --git a/mlir/test/Dialect/Bufferization/Transforms/transform-ops.mlir b/mlir/test/Dialect/Bufferization/Transforms/transform-ops.mlir
index 4e897a951bc6a9..27ff9964c2ca23 100644
--- a/mlir/test/Dialect/Bufferization/Transforms/transform-ops.mlir
+++ b/mlir/test/Dialect/Bufferization/Transforms/transform-ops.mlir
@@ -236,3 +236,56 @@ func.func @empty_to_tensor_alloc() -> tensor<2x2xf32> {
%0 = bufferization.alloc_tensor() : tensor<2x2xf32>
return %0 : tensor<2x2xf32>
}
+
+// -----
+
+// The loop's parent block is outside the analysis rooted at the target loop.
+// CHECK-LABEL: func @buffer_loop_hoisting_while_scope_no_exit_alias(
+// CHECK-NOT: memref.alloc
+// CHECK: scf.while
+// CHECK-NEXT: %[[ALLOC:.*]] = memref.alloc()
+// CHECK-NEXT: memref.store {{.*}}, %[[ALLOC]]
+module attributes {transform.with_named_sequence} {
+ transform.named_sequence @__transform_main(%root: !transform.any_op {transform.readonly}) {
+ %loop = transform.structured.match ops{["scf.while"]} in %root : (!transform.any_op) -> !transform.any_op
+ transform.bufferization.buffer_loop_hoisting %loop : !transform.any_op
+ transform.yield
+ }
+ func.func @buffer_loop_hoisting_while_scope_no_exit_alias(%condition: i1, %value: index) {
+ %c0 = arith.constant 0 : index
+ scf.while : () -> () {
+ %buffer = memref.alloc() : memref<1xindex>
+ memref.store %value, %buffer[%c0] : memref<1xindex>
+ scf.condition(%condition)
+ } do {
+ scf.yield
+ }
+ return
+ }
+}
+
+// -----
+
+// The same loop can be crossed when the function is the analysis target.
+// CHECK-LABEL: func @buffer_loop_hoisting_function_scope_no_exit_alias(
+// CHECK: %[[ALLOC:.*]] = memref.alloc()
+// CHECK-NEXT: scf.while
+// CHECK-NEXT: memref.store {{.*}}, %[[ALLOC]]
+module attributes {transform.with_named_sequence} {
+ transform.named_sequence @__transform_main(%root: !transform.any_op {transform.readonly}) {
+ %func = transform.structured.match ops{["func.func"]} in %root : (!transform.any_op) -> !transform.any_op
+ transform.bufferization.buffer_loop_hoisting %func : !transform.any_op
+ transform.yield
+ }
+ func.func @buffer_loop_hoisting_function_scope_no_exit_alias(%condition: i1, %value: index) {
+ %c0 = arith.constant 0 : index
+ scf.while : () -> () {
+ %buffer = memref.alloc() : memref<1xindex>
+ memref.store %value, %buffer[%c0] : memref<1xindex>
+ scf.condition(%condition)
+ } do {
+ scf.yield
+ }
+ return
+ }
+}
More information about the Mlir-commits
mailing list