[Mlir-commits] [mlir] [mlir][vector] Fix transfer_write bufferization coverage checks (PR #234906)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Fri Oct 23 03:33:52 PDT 2026


https://github.com/Pecco-314 created https://github.com/llvm/llvm-project/pull/234906

The overwrite check can miss unwritten elements when a transfer has a mask, writes a lower-rank vector, or permutes dimensions. Bufferization may then skip a required copy and lose the original values of those elements.

The check now includes the transfer's mask and uses `getTransferChunkAccessed()` to determine coverage in destination dimensions. Transposed writes and writes that omit unit dimensions can still avoid a copy when they cover the entire destination.

Fixes #233726

Assisted-by: Codex (OpenAI)

>From dc3a48e582ae708fa444a9071da005f9f4f7871d Mon Sep 17 00:00:00 2001
From: Yanfeng Gao <gaoyanfeng.gyf at alibaba-inc.com>
Date: Fri, 23 Oct 2026 18:31:28 +0800
Subject: [PATCH] [mlir][vector] Fix transfer_write bufferization coverage
 checks

The overwrite check can miss unwritten elements when a transfer has a
mask, writes a lower-rank vector, or permutes dimensions. Bufferization
may then skip a required copy and lose the original values of those
elements.

The check now includes the transfer's mask and uses
`getTransferChunkAccessed()` to determine coverage in destination
dimensions. Transposed writes and writes that omit unit dimensions
can still avoid a copy when they cover the entire destination.

Fixes #233726

Assisted-by: Codex (OpenAI)
---
 .../BufferizableOpInterfaceImpl.cpp           |   4 +-
 .../Dialect/Vector/one-shot-bufferize.mlir    | 116 ++++++++++++++++++
 2 files changed, 118 insertions(+), 2 deletions(-)

diff --git a/mlir/lib/Dialect/Vector/Transforms/BufferizableOpInterfaceImpl.cpp b/mlir/lib/Dialect/Vector/Transforms/BufferizableOpInterfaceImpl.cpp
index 352f477a8746e2..037e9ad07572fe 100644
--- a/mlir/lib/Dialect/Vector/Transforms/BufferizableOpInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/BufferizableOpInterfaceImpl.cpp
@@ -92,12 +92,12 @@ struct TransferWriteOpInterface
     }
 
     // There is no mask.
-    if (writeOp.isMasked())
+    if (writeOp.getMask() || writeOp.isMasked())
       return true;
 
     // Must write at least the full dimension size.
     for (auto [d0, d1] : llvm::zip(writeOp.getShapedType().getShape(),
-                                   writeOp.getVectorType().getShape())) {
+                                   writeOp.getTransferChunkAccessed())) {
       if (d0 > d1)
         return true;
     }
diff --git a/mlir/test/Dialect/Vector/one-shot-bufferize.mlir b/mlir/test/Dialect/Vector/one-shot-bufferize.mlir
index a190c947530086..14cd6132117203 100644
--- a/mlir/test/Dialect/Vector/one-shot-bufferize.mlir
+++ b/mlir/test/Dialect/Vector/one-shot-bufferize.mlir
@@ -12,6 +12,8 @@ func.func @mask(%t0: tensor<?xf32>, %val: vector<16xf32>, %idx: index, %m0: vect
   return %0 : tensor<?xf32>
 }
 
+// -----
+
 // CHECK-LABEL: func @mask_scalable(
 //  CHECK-SAME:     %[[t0:.*]]: memref<?xf32, strided<[?], offset: ?>>
 func.func @mask_scalable(%t0: tensor<?xf32>, %val: vector<[16]xf32>, %idx: index, %m0: vector<[16]xi1>) -> tensor<?xf32> {
@@ -125,3 +127,117 @@ func.func @unknown_transfer_read_write(
       : tensor<8xf32>, vector<4xf32>
   return %written, %read : tensor<8xf32>, vector<4xf32>
 }
+
+// -----
+
+// Masked-off elements retain their values from the destination.
+
+// CHECK-LABEL: func @masked_xfer_write(
+//  CHECK-SAME: %[[T:.*]]: memref<4xf32,
+//       CHECK: %[[ALLOC:.*]] = memref.alloc
+//       CHECK: memref.copy %[[T]], %[[ALLOC]]
+//       CHECK: vector.transfer_write %{{.*}}, %[[ALLOC]][
+//       CHECK: return %[[ALLOC]]
+
+// CHECK-ANALYSIS-LABEL: func @masked_xfer_write(
+// CHECK-ANALYSIS-SAME: tensor<4xf32> {bufferization.access = "read",
+func.func @masked_xfer_write(
+    %t: tensor<4xf32> {bufferization.writable = false},
+    %v: vector<4xf32>, %mask: vector<4xi1>) -> tensor<4xf32> {
+  %c0 = arith.constant 0 : index
+  %written = vector.transfer_write %v, %t[%c0], %mask
+      : vector<4xf32>, tensor<4xf32>
+  return %written : tensor<4xf32>
+}
+
+// -----
+
+// A lower-rank write updates one row, so the other rows must be copied.
+
+// CHECK-LABEL: func @lower_rank_xfer_write(
+//  CHECK-SAME: %[[T:.*]]: memref<4x4xf32,
+//       CHECK: %[[ALLOC:.*]] = memref.alloc
+//       CHECK: memref.copy %[[T]], %[[ALLOC]]
+//       CHECK: vector.transfer_write %{{.*}}, %[[ALLOC]][
+//       CHECK: return %[[ALLOC]]
+
+// CHECK-ANALYSIS-LABEL: func @lower_rank_xfer_write(
+// CHECK-ANALYSIS-SAME: tensor<4x4xf32> {bufferization.access = "read",
+func.func @lower_rank_xfer_write(
+    %t: tensor<4x4xf32> {bufferization.writable = false},
+    %v: vector<4xf32>) -> tensor<4x4xf32> {
+  %c0 = arith.constant 0 : index
+  %written = vector.transfer_write %v, %t[%c0, %c0]
+      : vector<4xf32>, tensor<4x4xf32>
+  return %written : tensor<4x4xf32>
+}
+
+// -----
+
+// The transpose makes the write cover an 8x4 slice of a 4x8 tensor.
+// The last four columns must be preserved.
+
+// CHECK-LABEL: func @partial_permuted_xfer_write(
+//  CHECK-SAME: %[[T:.*]]: memref<4x8xf32,
+//       CHECK: %[[ALLOC:.*]] = memref.alloc
+//       CHECK: memref.copy %[[T]], %[[ALLOC]]
+//       CHECK: vector.transfer_write %{{.*}}, %[[ALLOC]][
+//       CHECK: return %[[ALLOC]]
+
+// CHECK-ANALYSIS-LABEL: func @partial_permuted_xfer_write(
+// CHECK-ANALYSIS-SAME: tensor<4x8xf32> {bufferization.access = "read",
+func.func @partial_permuted_xfer_write(
+    %t: tensor<4x8xf32> {bufferization.writable = false},
+    %v: vector<4x8xf32>) -> tensor<4x8xf32> {
+  %c0 = arith.constant 0 : index
+  %written = vector.transfer_write %v, %t[%c0, %c0]
+      {permutation_map = affine_map<(d0, d1) -> (d1, d0)>}
+      : vector<4x8xf32>, tensor<4x8xf32>
+  return %written : tensor<4x8xf32>
+}
+
+// -----
+
+// The transpose makes an 8x4 vector cover the entire 4x8 destination.
+
+// CHECK-LABEL: func @full_permuted_xfer_write(
+//       CHECK: %[[ALLOC:.*]] = memref.alloc
+//   CHECK-NOT: memref.copy
+//       CHECK: vector.transfer_write %{{.*}}, %[[ALLOC]][
+//   CHECK-NOT: memref.copy
+//       CHECK: return %[[ALLOC]]
+
+// CHECK-ANALYSIS-LABEL: func @full_permuted_xfer_write(
+// CHECK-ANALYSIS-SAME: tensor<4x8xf32> {bufferization.access = "none",
+func.func @full_permuted_xfer_write(
+    %t: tensor<4x8xf32> {bufferization.writable = false},
+    %v: vector<8x4xf32>) -> tensor<4x8xf32> {
+  %c0 = arith.constant 0 : index
+  %written = vector.transfer_write %v, %t[%c0, %c0]
+      {permutation_map = affine_map<(d0, d1) -> (d1, d0)>}
+      : vector<8x4xf32>, tensor<4x8xf32>
+  return %written : tensor<4x8xf32>
+}
+
+// -----
+
+// The dimension omitted by the map has size one, so the write covers the
+// entire destination.
+
+// CHECK-LABEL: func @full_lower_rank_xfer_write(
+//       CHECK: %[[ALLOC:.*]] = memref.alloc
+//   CHECK-NOT: memref.copy
+//       CHECK: vector.transfer_write %{{.*}}, %[[ALLOC]][
+//   CHECK-NOT: memref.copy
+//       CHECK: return %[[ALLOC]]
+
+// CHECK-ANALYSIS-LABEL: func @full_lower_rank_xfer_write(
+// CHECK-ANALYSIS-SAME: tensor<1x4xf32> {bufferization.access = "none",
+func.func @full_lower_rank_xfer_write(
+    %t: tensor<1x4xf32> {bufferization.writable = false},
+    %v: vector<4xf32>) -> tensor<1x4xf32> {
+  %c0 = arith.constant 0 : index
+  %written = vector.transfer_write %v, %t[%c0, %c0]
+      : vector<4xf32>, tensor<1x4xf32>
+  return %written : tensor<1x4xf32>
+}



More information about the Mlir-commits mailing list