[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