[Mlir-commits] [mlir] [mlir][bufferization] Hoist allocations through exactly-once regions (PR #224921)
purnima shrivastava
llvmlistbot at llvm.org
Sun Sep 20 05:12:59 PDT 2026
https://github.com/purnima-nlp created https://github.com/llvm/llvm-project/pull/224921
Fixed`buffer-loop-hoisting` so that the placement search can traverse non-loop regions that are guaranteed to execute exactly once, such as `scf.execute_region`.
The case Previously was , the search stopped at `scf.execute_region` because `BufferAllocationLoopHoistingState::isLegalPlacement` only accepted sequential loops. That is why, allocations nested inside `scf.execute_region` could not be hoisted out of an enclosing `scf.for`.
The solution uses `RegionBranchOpInterface::getRegionInvocationBounds` and only crosses the current region when its invocation bounds are exactly `[1, 1]`. Conditional regions such as `scf.if` remain unchanged.
Fixes #224892
AI tools were used for code base exploration.
>From bf2fd8bc329c49ab85baade5e9a3224a39a90882 Mon Sep 17 00:00:00 2001
From: Purnima Shrivastava <purnimashrivastava05 at .com>
Date: Sun, 20 Sep 2026 17:33:06 +0530
Subject: [PATCH] [mlir][bufferization] Hoist allocations through exactly-once
regions
---
.../Transforms/BufferOptimizations.cpp | 41 +++++++++++++------
.../Transforms/buffer-loop-hoisting.mlir | 27 ++++++++++++
2 files changed, 56 insertions(+), 12 deletions(-)
diff --git a/mlir/lib/Dialect/Bufferization/Transforms/BufferOptimizations.cpp b/mlir/lib/Dialect/Bufferization/Transforms/BufferOptimizations.cpp
index 4f2a4734056b6..97fbbb77a740e 100644
--- a/mlir/lib/Dialect/Bufferization/Transforms/BufferOptimizations.cpp
+++ b/mlir/lib/Dialect/Bufferization/Transforms/BufferOptimizations.cpp
@@ -288,12 +288,12 @@ class BufferAllocationHoisting : public BufferPlacementTransformationBase {
// implementation, break the walk loop. Furthermore, we should not move
// allocations out of unknown region-based control-flow operations.
if (!isKnownControlFlowInterface(parentOp) ||
- !state.isLegalPlacement(parentOp))
+ !state.isLegalPlacement(parentOp, currentBlock->getParent()))
break;
// Move to our parent block by notifying the current StateT
// implementation.
currentBlock = parentBlock;
- state.recordMoveToParent(currentBlock);
+ state.recordMoveToParent(parentOp, currentBlock);
}
}
// Return the finally determined placement block.
@@ -337,7 +337,7 @@ struct BufferAllocationHoistingState : BufferAllocationHoistingStateBase {
}
/// Returns true if the given operation does not represent a loop.
- bool isLegalPlacement(Operation *op) { return !isLoop(op); }
+ bool isLegalPlacement(Operation *op, Region *) { return !isLoop(op); }
/// Returns true if the given operation should be considered for hoisting.
static bool shouldHoistOpType(Operation *op) {
@@ -348,7 +348,9 @@ struct BufferAllocationHoistingState : BufferAllocationHoistingStateBase {
void recordMoveToDominator(Block *block) { placementBlock = block; }
/// Sets the current placement block to the given block.
- void recordMoveToParent(Block *block) { recordMoveToDominator(block); }
+ void recordMoveToParent(Operation *, Block *block) {
+ recordMoveToDominator(block);
+ }
};
/// A state implementation compatible with the `BufferAllocationHoisting` class
@@ -367,12 +369,24 @@ struct BufferAllocationLoopHoistingState : BufferAllocationHoistingStateBase {
return dependencyBlock ? dependencyBlock : nullptr;
}
- /// Returns true if the given operation represents a loop with sequential
- /// execution semantics and one of the aliases caused the
- /// `aliasDominatorBlock` to be "above" the block of the given loop operation.
- /// If this is the case, it indicates that the allocation is passed via a back
- /// edge.
- bool isLegalPlacement(Operation *op) {
+ /// Returns true if the placement search can cross the given parent operation.
+ /// Non-loop regions may be crossed if the current region is executed exactly
+ /// once. Loops must have sequential execution semantics and must not violate
+ /// the alias back-edge constraint.
+ bool isLegalPlacement(Operation *op, Region *region) {
+ if (!isLoop(op)) {
+ auto branchOp = dyn_cast<RegionBranchOpInterface>(op);
+ if (!branchOp)
+ return false;
+
+ SmallVector<Attribute> operands(op->getNumOperands(), nullptr);
+ SmallVector<InvocationBounds> bounds;
+ branchOp.getRegionInvocationBounds(operands, bounds);
+
+ const InvocationBounds &bound = bounds[region->getRegionNumber()];
+ return bound.getLowerBound() == 1 && bound.getUpperBound() == 1;
+ }
+
return isSequentialLoop(op) &&
!dominators->dominates(aliasDominatorBlock, op->getBlock());
}
@@ -386,8 +400,11 @@ struct BufferAllocationLoopHoistingState : BufferAllocationHoistingStateBase {
/// operations out of loops only.
void recordMoveToDominator(Block *block) {}
- /// Sets the current placement block to the given block.
- void recordMoveToParent(Block *block) { placementBlock = block; }
+ /// Updates the placement block when crossing a sequential loop.
+ void recordMoveToParent(Operation *parentOp, Block *block) {
+ if (isSequentialLoop(parentOp))
+ placementBlock = block;
+ }
};
//===----------------------------------------------------------------------===//
diff --git a/mlir/test/Dialect/Bufferization/Transforms/buffer-loop-hoisting.mlir b/mlir/test/Dialect/Bufferization/Transforms/buffer-loop-hoisting.mlir
index ee7571b2c8466..b3131966570c7 100644
--- a/mlir/test/Dialect/Bufferization/Transforms/buffer-loop-hoisting.mlir
+++ b/mlir/test/Dialect/Bufferization/Transforms/buffer-loop-hoisting.mlir
@@ -519,3 +519,30 @@ func.func @hoist_alloca(
// CHECK: %[[ALLOCA0:.*]] = memref.alloca({{.*}})
// CHECK-NEXT: %[[ALLOCA1:.*]] = memref.alloca({{.*}})
// CHECK-NEXT: {{.*}} = scf.for
+
+// -----
+
+// CHECK-LABEL: func @loop_execute_region
+func.func @loop_execute_region(%output: memref<8xindex>) {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+
+ scf.for %i = %c0 to %c8 step %c1 {
+ %value = scf.execute_region -> index {
+ %buffer = memref.alloc() : memref<1xindex>
+ memref.store %i, %buffer[%c0] : memref<1xindex>
+ %loaded = memref.load %buffer[%c0] : memref<1xindex>
+ scf.yield %loaded : index
+ }
+ memref.store %value, %output[%i] : memref<8xindex>
+ }
+
+ return
+}
+
+// CHECK: %[[ALLOC:.*]] = memref.alloc() : memref<1xindex>
+// CHECK-NEXT: scf.for
+// CHECK: scf.execute_region
+// CHECK-NOT: memref.alloc()
+// CHECK: memref.store
More information about the Mlir-commits
mailing list