[Mlir-commits] [mlir] [mlir][Vector] Reuse args buffers in WarpOpToScfIfPattern (PR #204311)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Wed Jun 17 01:04:47 PDT 2026
https://github.com/edisongz created https://github.com/llvm/llvm-project/pull/204311
Args buffers are dead after lane 0 reads them inside the scf.if (the
Step 3 barrier fences other lanes), so reuse their storage for yielded
values via `memref.subview` when sizes are compatible. Reduces shared
memory footprint with no runtime overhead.
>From 59ee01b3d9a94fc047d946dff77a57e74d7108bb Mon Sep 17 00:00:00 2001
From: Yijie Jiang <edisongz123 at gmail.com>
Date: Wed, 17 Jun 2026 15:54:54 +0800
Subject: [PATCH] [mlir][Vector] Reuse args buffers in WarpOpToScfIfPattern
Args buffers are unused after lane 0 reads them inside the scf.if (the
Step 3 barrier fences other lanes), so reuse their storage for yielded
values via `memref.subview` when sizes are compatible. Reduces shared
memory footprint with no runtime overhead.
---
.../Vector/Transforms/VectorDistribute.cpp | 62 +++++++++++++++++--
.../Vector/vector-warp-distribute.mlir | 33 ++++++++--
2 files changed, 85 insertions(+), 10 deletions(-)
diff --git a/mlir/lib/Dialect/Vector/Transforms/VectorDistribute.cpp b/mlir/lib/Dialect/Vector/Transforms/VectorDistribute.cpp
index c500942af7942..d88c8030e9a18 100644
--- a/mlir/lib/Dialect/Vector/Transforms/VectorDistribute.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/VectorDistribute.cpp
@@ -200,6 +200,52 @@ static Operation *cloneOpWithOperandsAndTypes(RewriterBase &rewriter,
namespace {
+/// Reuse an args buffer from `buffers` to transit a value of `targetType`, or
+/// return null if none fits.
+static Value tryReuseArgBuffer(SmallVectorImpl<std::pair<Value, bool>> &buffers,
+ Type targetType, Location loc,
+ RewriterBase &rewriter) {
+ auto targetVecType = dyn_cast<VectorType>(targetType);
+ if (!targetVecType || !targetVecType.hasStaticShape())
+ return Value();
+
+ for (auto &entry : buffers) {
+ if (entry.second)
+ continue;
+ Value candidate = entry.first;
+ auto candidateMemref = dyn_cast<MemRefType>(candidate.getType());
+ if (!candidateMemref || !candidateMemref.hasStaticShape())
+ continue;
+ if (candidateMemref.getElementType() != targetVecType.getElementType())
+ continue;
+ if (candidateMemref.getNumElements() < targetVecType.getNumElements())
+ continue;
+ // Skip rank mismatch up-front to avoid guessing a reshape.
+ if (candidateMemref.getRank() != targetVecType.getRank())
+ continue;
+
+ // All checks passed: claim the buffer.
+ entry.second = true;
+
+ // Same shape: reuse directly.
+ if (candidateMemref.getShape() == targetVecType.getShape())
+ return candidate;
+
+ // Larger candidate: take a leading subview of the target shape.
+ int64_t candidateRank = candidateMemref.getRank();
+ SmallVector<OpFoldResult> offsets(candidateRank, rewriter.getIndexAttr(0));
+ SmallVector<OpFoldResult> strides(candidateRank, rewriter.getIndexAttr(1));
+ SmallVector<OpFoldResult> sizes;
+ sizes.reserve(candidateRank);
+ for (int64_t dim : targetVecType.getShape())
+ sizes.push_back(rewriter.getIndexAttr(dim));
+
+ return memref::SubViewOp::create(rewriter, loc, candidate, offsets, sizes,
+ strides);
+ }
+ return Value();
+}
+
/// Rewrite a WarpExecuteOnLane0Op into a predicated scf.if op where the single
/// thread `laneId` executes the entirety of the computation.
///
@@ -256,6 +302,10 @@ struct WarpOpToScfIfPattern : public WarpDistributionPattern {
// Step 2: insert appropriate (alloc, write)-pairs before the scf.if and
// reads within the scf.if to transit the values captured from above.
SmallVector<Value> bbArgReplacements;
+ // Pool of args buffers paired with a "consumed" flag, so Step 5 can try
+ // to reuse them for yielded values' transit buffers.
+ SmallVector<std::pair<Value, /*consumed=*/bool>> reusableArgBuffers;
+ reusableArgBuffers.reserve(warpOp.getArgs().size());
for (const auto &it : llvm::enumerate(warpOp.getArgs())) {
Value sequentialVal = warpOpBody->getArgument(it.index());
Value distributedVal = it.value();
@@ -266,6 +316,7 @@ struct WarpOpToScfIfPattern : public WarpDistributionPattern {
rewriter.setInsertionPoint(ifOp);
Value buffer = options.warpAllocationFn(loc, rewriter, warpOp,
sequentialVal.getType());
+ reusableArgBuffers.emplace_back(buffer, /*consumed=*/false);
// Store distributed vector into buffer, before the ifOp.
helper.buildStore(rewriter, loc, distributedVal, buffer);
// Load sequential vector from buffer, inside the ifOp.
@@ -285,7 +336,7 @@ struct WarpOpToScfIfPattern : public WarpDistributionPattern {
// Step 5. Insert appropriate writes within scf.if and reads after the
// scf.if to transit the values returned by the op.
- // TODO: at this point, we can reuse the shared memory from previous
+ // The args buffers from Step 2 are unused by now, try to reuse previous
// buffers.
SmallVector<Value> replacements;
auto yieldOp = cast<gpu::YieldOp>(ifOp.thenBlock()->getTerminator());
@@ -296,10 +347,13 @@ struct WarpOpToScfIfPattern : public WarpDistributionPattern {
DistributedLoadStoreHelper helper(sequentialVal, distributedVal,
warpOp.getLaneid(), c0);
- // Create buffer before the ifOp.
+ // Reuse an args buffer if possible, otherwise allocate.
rewriter.setInsertionPoint(ifOp);
- Value buffer = options.warpAllocationFn(loc, rewriter, warpOp,
- sequentialVal.getType());
+ Value buffer = tryReuseArgBuffer(reusableArgBuffers,
+ sequentialVal.getType(), loc, rewriter);
+ if (!buffer)
+ buffer = options.warpAllocationFn(loc, rewriter, warpOp,
+ sequentialVal.getType());
// Store yielded value into buffer, inside the ifOp, before the
// terminator.
diff --git a/mlir/test/Dialect/Vector/vector-warp-distribute.mlir b/mlir/test/Dialect/Vector/vector-warp-distribute.mlir
index 691913b3bd5dc..917c8c1c803a9 100644
--- a/mlir/test/Dialect/Vector/vector-warp-distribute.mlir
+++ b/mlir/test/Dialect/Vector/vector-warp-distribute.mlir
@@ -19,10 +19,10 @@
// CHECK-SCF-IF-DAG: #[[$TIMES2:.*]] = affine_map<()[s0] -> (s0 * 2)>
// CHECK-SCF-IF-DAG: #[[$TIMES4:.*]] = affine_map<()[s0] -> (s0 * 4)>
// CHECK-SCF-IF-DAG: #[[$TIMES8:.*]] = affine_map<()[s0] -> (s0 * 8)>
-// CHECK-SCF-IF-DAG: memref.global "private" @__shared_32xf32 : memref<32xf32, 3>
-// CHECK-SCF-IF-DAG: memref.global "private" @__shared_64xf32 : memref<64xf32, 3>
// CHECK-SCF-IF-DAG: memref.global "private" @__shared_128xf32 : memref<128xf32, 3>
// CHECK-SCF-IF-DAG: memref.global "private" @__shared_256xf32 : memref<256xf32, 3>
+// CHECK-SCF-IF-NOT: memref.global "private" @__shared_32xf32
+// CHECK-SCF-IF-NOT: memref.global "private" @__shared_64xf32
// CHECK-SCF-IF-LABEL: func @rewrite_warp_op_to_scf_if(
// CHECK-SCF-IF-SAME: %[[laneid:.*]]: index,
@@ -40,8 +40,11 @@ func.func @rewrite_warp_op_to_scf_if(%laneid: index,
// CHECK-SCF-IF: vector.transfer_write %[[v1]], %[[buffer_v1]][%[[s1]]]
// CHECK-SCF-IF-DAG: gpu.barrier memfence [#gpu.address_space<workgroup>]
-// CHECK-SCF-IF-DAG: %[[buffer_def_0:.*]] = memref.get_global @__shared_32xf32
-// CHECK-SCF-IF-DAG: %[[buffer_def_1:.*]] = memref.get_global @__shared_64xf32
+// Step 5 reuses the args buffers for the result buffers via memref.subview.
+// CHECK-SCF-IF-DAG: %[[buffer_def_0:.*]] = memref.subview %[[buffer_v0]][0] [32] [1] : memref<128xf32, 3> to memref<32xf32, strided<[1]>, 3>
+// CHECK-SCF-IF-DAG: %[[buffer_def_1:.*]] = memref.subview %[[buffer_v1]][0] [64] [1] : memref<256xf32, 3> to memref<64xf32, strided<[1]>, 3>
+// CHECK-SCF-IF-NOT: memref.get_global @__shared_32xf32
+// CHECK-SCF-IF-NOT: memref.get_global @__shared_64xf32
// CHECK-SCF-IF: scf.if %[[is_lane_0]] {
%r:2 = gpu.warp_execute_on_lane_0(%laneid)[32]
@@ -60,8 +63,8 @@ func.func @rewrite_warp_op_to_scf_if(%laneid: index,
// CHECK-SCF-IF: }
// CHECK-SCF-IF: gpu.barrier memfence [#gpu.address_space<workgroup>]
// CHECK-SCF-IF: %[[o1:.*]] = affine.apply #[[$TIMES2]]()[%[[laneid]]]
-// CHECK-SCF-IF: %[[r1:.*]] = vector.transfer_read %[[buffer_def_1]][%[[o1]]], %{{.*}} {in_bounds = [true]} : memref<64xf32, 3>, vector<2xf32>
-// CHECK-SCF-IF: %[[r0:.*]] = vector.transfer_read %[[buffer_def_0]][%[[laneid]]], %{{.*}} {in_bounds = [true]} : memref<32xf32, 3>, vector<1xf32>
+// CHECK-SCF-IF: %[[r1:.*]] = vector.transfer_read %[[buffer_def_1]][%[[o1]]], %{{.*}} {in_bounds = [true]} : memref<64xf32, strided<[1]>, 3>, vector<2xf32>
+// CHECK-SCF-IF: %[[r0:.*]] = vector.transfer_read %[[buffer_def_0]][%[[laneid]]], %{{.*}} {in_bounds = [true]} : memref<32xf32, strided<[1]>, 3>, vector<1xf32>
// CHECK-SCF-IF: "some_use"(%[[r0]]) : (vector<1xf32>) -> ()
// CHECK-SCF-IF: "some_use"(%[[r1]]) : (vector<2xf32>) -> ()
"some_use"(%r#0) : (vector<1xf32>) -> ()
@@ -71,6 +74,24 @@ func.func @rewrite_warp_op_to_scf_if(%laneid: index,
// -----
+// No args: nothing to reuse, result buffer is allocated fresh.
+
+// CHECK-SCF-IF-LABEL: func @rewrite_warp_op_to_scf_if_no_args(
+// CHECK-SCF-IF-SAME: %[[laneid:.*]]: index)
+func.func @rewrite_warp_op_to_scf_if_no_args(%laneid: index) {
+// CHECK-SCF-IF: %[[buffer:.*]] = memref.get_global @__shared_32xindex
+// CHECK-SCF-IF-NOT: memref.subview
+// CHECK-SCF-IF: scf.if
+ %r = gpu.warp_execute_on_lane_0(%laneid)[32] -> (vector<1xindex>) {
+ %step = vector.step : vector<32xindex>
+ gpu.yield %step : vector<32xindex>
+ }
+ "some_use"(%r) : (vector<1xindex>) -> ()
+ return
+}
+
+// -----
+
// CHECK-D-DAG: #[[MAP1:.*]] = affine_map<()[s0] -> (s0 * 2 + 32)>
// CHECK-DIST-AND-PROP-LABEL: func @warp(
More information about the Mlir-commits
mailing list