[Mlir-commits] [mlir] [mlir][vector] Promote in-bounds sub-vector transfer reads with Mem2Reg (PR #236320)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Sat Oct 31 03:40:52 PDT 2026
https://github.com/meinie0826 updated https://github.com/llvm/llvm-project/pull/236320
>From f99ef255158cf880c04806a2421cf932c626083b Mon Sep 17 00:00:00 2001
From: meinie0826 <929449726 at qq.com>
Date: Wed, 28 Oct 2026 13:24:58 +0800
Subject: [PATCH 1/9] [mlir][vector] Promote in-bounds sub-vector transfer
reads with Mem2Reg
Extend the read model to extract a fixed-size, same-rank sub-vector from the slot reaching definition. Require constant contained indices, an identity permutation, no mask, and in-bounds dimensions. Preserve write promotion and existing dynamic subview paths.
---
.../Transforms/MemorySlotInterfacesImpl.cpp | 52 ++-
mlir/test/Transforms/Mem2Reg/vector.mlir | 334 +++++++++++++++++-
2 files changed, 381 insertions(+), 5 deletions(-)
diff --git a/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp b/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp
index 33abbf2644f0f..5422e041ceff6 100644
--- a/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp
@@ -12,7 +12,8 @@
// and definitions of a vector SSA value. The models for `memref.subview`
// live in the MemRef dialect.
//
-// A read uses the slot's current value; a write defines its next value.
+// A read uses the slot's current value or extracts an in-bounds sub-vector;
+// a write defines its next value.
// Static subviews use a vector matching the subview's shape: reads extract that
// sub-vector from the parent, and writes insert it back into the parent.
// Supported transfers through dynamic subviews use a vector type matching the
@@ -36,6 +37,7 @@
#include "mlir/Dialect/Vector/IR/VectorOps.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/Interfaces/MemorySlotInterfaces.h"
+#include "llvm/ADT/STLExtras.h"
using namespace mlir;
using namespace mlir::vector;
@@ -88,6 +90,40 @@ isPromotableTransfer(VectorTransferOpInterface xferOp, const MemorySlot &slot,
return true;
}
+/// Return offsets for an unmasked, in-bounds read of a fixed-size sub-vector
+/// of the slot. Dynamic subview slots hold the entire parent vector, not the
+/// runtime extent of the view, so containment in that vector is insufficient.
+static FailureOr<SmallVector<int64_t>> getPromotableReadSliceOffsets(
+ TransferReadOp readOp, const MemorySlot &slot,
+ const SmallPtrSetImpl<OpOperand *> &blockingUses) {
+ if (blockingUses.size() != 1 || (*blockingUses.begin())->get() != slot.ptr ||
+ readOp.getBase() != slot.ptr || !isa<MemRefType>(slot.ptr.getType()))
+ return failure();
+ if (readOp.getMask() || readOp.isMasked() || readOp.hasOutOfBoundsDim() ||
+ !readOp.getPermutationMap().isIdentity() ||
+ memref::isDynamicSubViewSlot(slot.ptr))
+ return failure();
+
+ auto slotType = dyn_cast<VectorType>(slot.valueType);
+ VectorType readType = readOp.getVectorType();
+ if (!slotType || slotType.isScalable() || readType.isScalable() ||
+ readType.getRank() != slotType.getRank() ||
+ readOp.getIndices().size() != static_cast<size_t>(readType.getRank()) ||
+ readType.getElementType() != slotType.getElementType())
+ return failure();
+
+ SmallVector<int64_t> offsets;
+ for (auto [index, size, extent] : llvm::zip_equal(
+ readOp.getIndices(), readType.getShape(), slotType.getShape())) {
+ auto offset = getConstantIntValue(index);
+ // Subtract first to avoid overflowing offset + size in the bounds check.
+ if (!offset || *offset < 0 || size > extent || *offset > extent - size)
+ return failure();
+ offsets.push_back(*offset);
+ }
+ return offsets;
+}
+
//===----------------------------------------------------------------------===//
// PromotableMemOpInterface models
//===----------------------------------------------------------------------===//
@@ -113,7 +149,9 @@ struct TransferReadOpMemOpModel
SmallVectorImpl<OpOperand *> &newBlockingUses,
const DataLayout &dataLayout) const {
return isPromotableTransfer(cast<VectorTransferOpInterface>(op), slot,
- blockingUses);
+ blockingUses) ||
+ succeeded(getPromotableReadSliceOffsets(cast<TransferReadOp>(op),
+ slot, blockingUses));
}
// Replace the read with the reaching definition, selecting padding for lanes
@@ -129,6 +167,16 @@ struct TransferReadOpMemOpModel
const DataLayout &dataLayout) const {
auto readOp = cast<vector::TransferReadOp>(op);
Location loc = op->getLoc();
+ if (readOp.getVectorType() != slot.valueType) {
+ auto offsets = getPromotableReadSliceOffsets(readOp, slot, blockingUses);
+ assert(succeeded(offsets) && "expected a promotable read slice");
+ SmallVector<int64_t> strides(offsets->size(), 1);
+ Value result = vector::ExtractStridedSliceOp::create(
+ builder, loc, reachingDefinition, *offsets,
+ readOp.getVectorType().getShape(), strides);
+ readOp.getVector().replaceAllUsesWith(result);
+ return DeletionKind::Delete;
+ }
Value mask = memref::buildDynamicSubViewMask(builder, loc, slot.ptr);
if (Value opMask = readOp.getMask())
mask = mask
diff --git a/mlir/test/Transforms/Mem2Reg/vector.mlir b/mlir/test/Transforms/Mem2Reg/vector.mlir
index 62a35c90d324e..02e4a9f1ab778 100644
--- a/mlir/test/Transforms/Mem2Reg/vector.mlir
+++ b/mlir/test/Transforms/Mem2Reg/vector.mlir
@@ -1,8 +1,7 @@
// RUN: mlir-opt %s --mem2reg --split-input-file | FileCheck %s
-// A memref that is only ever accessed as a whole buffer through
-// vector.transfer_read / vector.transfer_write is promoted to a single vector
-// SSA value.
+// Whole-buffer transfers and supported sub-vector reads can be promoted to a
+// single vector SSA value.
// CHECK-LABEL: func.func @whole_buffer_write_read
// CHECK-SAME: (%[[PAD:.*]]: f32)
@@ -690,3 +689,332 @@ func.func @negative_masked_write(%v: vector<8xf32>, %w: vector<8xf32>, %m: vecto
%r = vector.transfer_read %a[%c0], %pad {in_bounds = [true]} : memref<8xf32>, vector<8xf32>
return %r : vector<8xf32>
}
+
+// -----
+
+// CHECK-LABEL: func.func @partial_read_origin
+// CHECK-SAME: (%[[V:.*]]: vector<8xf32>,
+// CHECK-NOT: memref.alloca
+// CHECK-NOT: vector.transfer
+// CHECK: %[[R:.*]] = vector.extract_strided_slice %[[V]] offsets = [0], sizes = [4], strides = [1]
+// CHECK: return %[[R]] : vector<4xf32>
+func.func @partial_read_origin(%v: vector<8xf32>, %pad: f32) -> vector<4xf32> {
+ %c0 = arith.constant 0 : index
+ %a = memref.alloca() : memref<8xf32>
+ vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+ %r = vector.transfer_read %a[%c0], %pad {in_bounds = [true]} : memref<8xf32>, vector<4xf32>
+ return %r : vector<4xf32>
+}
+
+// -----
+
+// CHECK-LABEL: func.func @partial_read_offset
+// CHECK-SAME: (%[[V:.*]]: vector<8xf32>,
+// CHECK-NOT: memref.alloca
+// CHECK-NOT: vector.transfer
+// CHECK: %[[R:.*]] = vector.extract_strided_slice %[[V]] offsets = [2], sizes = [4], strides = [1]
+// CHECK: return %[[R]] : vector<4xf32>
+func.func @partial_read_offset(%v: vector<8xf32>, %pad: f32) -> vector<4xf32> {
+ %c0 = arith.constant 0 : index
+ %c2 = arith.constant 2 : index
+ %a = memref.alloca() : memref<8xf32>
+ vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+ %r = vector.transfer_read %a[%c2], %pad {in_bounds = [true]} : memref<8xf32>, vector<4xf32>
+ return %r : vector<4xf32>
+}
+
+// -----
+
+// CHECK-LABEL: func.func @partial_read_at_end
+// CHECK-SAME: (%[[V:.*]]: vector<8xf32>,
+// CHECK-NOT: memref.alloca
+// CHECK-NOT: vector.transfer
+// CHECK: %[[R:.*]] = vector.extract_strided_slice %[[V]] offsets = [4], sizes = [4], strides = [1]
+// CHECK: return %[[R]] : vector<4xf32>
+func.func @partial_read_at_end(%v: vector<8xf32>, %pad: f32) -> vector<4xf32> {
+ %c0 = arith.constant 0 : index
+ %c4 = arith.constant 4 : index
+ %a = memref.alloca() : memref<8xf32>
+ vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+ %r = vector.transfer_read %a[%c4], %pad {in_bounds = [true]} : memref<8xf32>, vector<4xf32>
+ return %r : vector<4xf32>
+}
+
+// -----
+
+// CHECK-LABEL: func.func @partial_read_2d
+// CHECK-SAME: (%[[V:.*]]: vector<4x8xf32>,
+// CHECK-NOT: memref.alloca
+// CHECK-NOT: vector.transfer
+// CHECK: %[[R:.*]] = vector.extract_strided_slice %[[V]] offsets = [1, 2], sizes = [2, 4], strides = [1, 1]
+// CHECK: return %[[R]] : vector<2x4xf32>
+func.func @partial_read_2d(%v: vector<4x8xf32>, %pad: f32) -> vector<2x4xf32> {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %c2 = arith.constant 2 : index
+ %a = memref.alloca() : memref<4x8xf32>
+ vector.transfer_write %v, %a[%c0, %c0] {in_bounds = [true, true]} : vector<4x8xf32>, memref<4x8xf32>
+ %r = vector.transfer_read %a[%c1, %c2], %pad {in_bounds = [true, true]} : memref<4x8xf32>, vector<2x4xf32>
+ return %r : vector<2x4xf32>
+}
+
+// -----
+
+// CHECK-LABEL: func.func @partial_read_dynamic_index
+// CHECK: memref.alloca
+// CHECK: vector.transfer_write
+// CHECK: vector.transfer_read
+func.func @partial_read_dynamic_index(%v: vector<8xf32>, %pad: f32, %offset: index) -> vector<4xf32> {
+ %c0 = arith.constant 0 : index
+ %a = memref.alloca() : memref<8xf32>
+ vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+ %r = vector.transfer_read %a[%offset], %pad {in_bounds = [true]} : memref<8xf32>, vector<4xf32>
+ return %r : vector<4xf32>
+}
+
+// -----
+
+// CHECK-LABEL: func.func @partial_read_negative_index
+// CHECK: memref.alloca
+// CHECK: vector.transfer_write
+// CHECK: vector.transfer_read
+func.func @partial_read_negative_index(%v: vector<8xf32>, %pad: f32) -> vector<4xf32> {
+ %c0 = arith.constant 0 : index
+ %c-1 = arith.constant -1 : index
+ %a = memref.alloca() : memref<8xf32>
+ vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+ %r = vector.transfer_read %a[%c-1], %pad {in_bounds = [true]} : memref<8xf32>, vector<4xf32>
+ return %r : vector<4xf32>
+}
+
+// -----
+
+// CHECK-LABEL: func.func @partial_read_exceeds_extent
+// CHECK: memref.alloca
+// CHECK: vector.transfer_write
+// CHECK: vector.transfer_read
+func.func @partial_read_exceeds_extent(%v: vector<8xf32>, %pad: f32) -> vector<4xf32> {
+ %c0 = arith.constant 0 : index
+ %c6 = arith.constant 6 : index
+ %a = memref.alloca() : memref<8xf32>
+ vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+ %r = vector.transfer_read %a[%c6], %pad {in_bounds = [true]} : memref<8xf32>, vector<4xf32>
+ return %r : vector<4xf32>
+}
+
+// -----
+
+// CHECK-LABEL: func.func @partial_read_large_index
+// CHECK: memref.alloca
+// CHECK: vector.transfer_write
+// CHECK: vector.transfer_read
+func.func @partial_read_large_index(%v: vector<8xf32>, %pad: f32) -> vector<4xf32> {
+ %c0 = arith.constant 0 : index
+ %c9223372036854775807 = arith.constant 9223372036854775807 : index
+ %a = memref.alloca() : memref<8xf32>
+ vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+ %r = vector.transfer_read %a[%c9223372036854775807], %pad {in_bounds = [true]} : memref<8xf32>, vector<4xf32>
+ return %r : vector<4xf32>
+}
+
+// -----
+
+// CHECK-LABEL: func.func @partial_read_unproven_bounds
+// CHECK: memref.alloca
+// CHECK: vector.transfer_write
+// CHECK: vector.transfer_read
+func.func @partial_read_unproven_bounds(%v: vector<8xf32>, %pad: f32) -> vector<4xf32> {
+ %c0 = arith.constant 0 : index
+ %c2 = arith.constant 2 : index
+ %a = memref.alloca() : memref<8xf32>
+ vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+ %r = vector.transfer_read %a[%c2], %pad : memref<8xf32>, vector<4xf32>
+ return %r : vector<4xf32>
+}
+
+// -----
+
+// CHECK-LABEL: func.func @partial_read_masked
+// CHECK: memref.alloca
+// CHECK: vector.transfer_write
+// CHECK: vector.transfer_read
+func.func @partial_read_masked(%v: vector<8xf32>, %pad: f32, %mask: vector<4xi1>) -> vector<4xf32> {
+ %c0 = arith.constant 0 : index
+ %c2 = arith.constant 2 : index
+ %a = memref.alloca() : memref<8xf32>
+ vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+ %r = vector.transfer_read %a[%c2], %pad, %mask {in_bounds = [true]} : memref<8xf32>, vector<4xf32>
+ return %r : vector<4xf32>
+}
+
+// -----
+
+// CHECK-LABEL: func.func @partial_read_transpose
+// CHECK: memref.alloca
+// CHECK: vector.transfer_write
+// CHECK: vector.transfer_read
+func.func @partial_read_transpose(%v: vector<4x8xf32>, %pad: f32) -> vector<2x4xf32> {
+ %c0 = arith.constant 0 : index
+ %a = memref.alloca() : memref<4x8xf32>
+ vector.transfer_write %v, %a[%c0, %c0] {in_bounds = [true, true]} : vector<4x8xf32>, memref<4x8xf32>
+ %r = vector.transfer_read %a[%c0, %c0], %pad {permutation_map = affine_map<(d0, d1) -> (d1, d0)>, in_bounds = [true, true]} : memref<4x8xf32>, vector<2x4xf32>
+ return %r : vector<2x4xf32>
+}
+
+// -----
+
+// CHECK-LABEL: func.func @partial_read_broadcast
+// CHECK: memref.alloca
+// CHECK: vector.transfer_write
+// CHECK: vector.transfer_read
+func.func @partial_read_broadcast(%v: vector<4x8xf32>, %pad: f32) -> vector<4xf32> {
+ %c0 = arith.constant 0 : index
+ %a = memref.alloca() : memref<4x8xf32>
+ vector.transfer_write %v, %a[%c0, %c0] {in_bounds = [true, true]} : vector<4x8xf32>, memref<4x8xf32>
+ %r = vector.transfer_read %a[%c0, %c0], %pad {permutation_map = affine_map<(d0, d1) -> (d1)>, in_bounds = [true]} : memref<4x8xf32>, vector<4xf32>
+ return %r : vector<4xf32>
+}
+
+// -----
+
+// A read projects the definition reaching that program point, not a later write.
+// CHECK-LABEL: func.func @partial_read_before_write
+// CHECK-SAME: (%[[V:.*]]: vector<8xf32>,
+// CHECK-NOT: memref.alloca
+// CHECK: %[[POISON:.*]] = ub.poison : vector<8xf32>
+// CHECK: %[[FIRST:.*]] = vector.extract_strided_slice %[[POISON]] offsets = [2], sizes = [4], strides = [1]
+// CHECK: %[[SECOND:.*]] = vector.extract_strided_slice %[[V]] offsets = [2], sizes = [4], strides = [1]
+// CHECK: return %[[FIRST]], %[[SECOND]]
+func.func @partial_read_before_write(%v: vector<8xf32>, %pad: f32) -> (vector<4xf32>, vector<4xf32>) {
+ %c0 = arith.constant 0 : index
+ %c2 = arith.constant 2 : index
+ %a = memref.alloca() : memref<8xf32>
+ %r0 = vector.transfer_read %a[%c2], %pad {in_bounds = [true]} : memref<8xf32>, vector<4xf32>
+ vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+ %r1 = vector.transfer_read %a[%c2], %pad {in_bounds = [true]} : memref<8xf32>, vector<4xf32>
+ return %r0, %r1 : vector<4xf32>, vector<4xf32>
+}
+
+// -----
+
+// Static aliases provide their own sub-vector; the read extracts within it.
+// CHECK-LABEL: func.func @partial_read_static_alias
+// CHECK-SAME: (%[[V:.*]]: vector<8xf32>,
+// CHECK-NOT: memref.alloca
+// CHECK-NOT: memref.subview
+// CHECK: %[[ALIAS:.*]] = vector.extract_strided_slice %[[V]] offsets = [2], sizes = [4], strides = [1]
+// CHECK: %[[R:.*]] = vector.extract_strided_slice %[[ALIAS]] offsets = [1], sizes = [2], strides = [1]
+// CHECK: return %[[R]]
+func.func @partial_read_static_alias(%v: vector<8xf32>, %pad: f32) -> vector<2xf32> {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %a = memref.alloca() : memref<8xf32>
+ vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+ %sv = memref.subview %a[2] [4] [1] : memref<8xf32> to memref<4xf32, strided<[1], offset: 2>>
+ %r = vector.transfer_read %sv[%c1], %pad {in_bounds = [true]} : memref<4xf32, strided<[1], offset: 2>>, vector<2xf32>
+ return %r : vector<2xf32>
+}
+
+// -----
+
+// Each slice reads the current loop-carried vector.
+// CHECK-LABEL: func.func @partial_read_loop
+// CHECK-NOT: memref.alloca
+// CHECK: %[[LOOP:.*]] = scf.for {{.*}} iter_args(%[[IT:.*]] = %{{.*}}) -> (vector<8xf32>)
+// CHECK: %[[SLICE:.*]] = vector.extract_strided_slice %[[IT]] offsets = [2], sizes = [4], strides = [1]
+// CHECK: %[[NEXT:.*]] = arith.addf %[[SLICE]], %[[SLICE]]
+// CHECK: %[[FULL:.*]] = vector.insert_strided_slice %[[NEXT]], %[[IT]] offsets = [2], strides = [1]
+// CHECK: scf.yield %[[FULL]]
+// CHECK: %[[RESULT:.*]] = vector.extract_strided_slice %[[LOOP]] offsets = [2], sizes = [4], strides = [1]
+// CHECK: return %[[RESULT]]
+func.func @partial_read_loop(%v: vector<8xf32>, %pad: f32, %lb: index, %ub: index, %step: index) -> vector<4xf32> {
+ %c0 = arith.constant 0 : index
+ %c2 = arith.constant 2 : index
+ %a = memref.alloca() : memref<8xf32>
+ vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+ scf.for %iv = %lb to %ub step %step {
+ %whole = vector.transfer_read %a[%c0], %pad {in_bounds = [true]} : memref<8xf32>, vector<8xf32>
+ %part = vector.transfer_read %a[%c2], %pad {in_bounds = [true]} : memref<8xf32>, vector<4xf32>
+ %next = arith.addf %part, %part : vector<4xf32>
+ %full = vector.insert_strided_slice %next, %whole offsets = [2], strides = [1] : vector<4xf32> into vector<8xf32>
+ vector.transfer_write %full, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+ }
+ %r = vector.transfer_read %a[%c2], %pad {in_bounds = [true]} : memref<8xf32>, vector<4xf32>
+ return %r : vector<4xf32>
+}
+
+// -----
+
+// CHECK-LABEL: func.func @partial_read_branch
+// CHECK-NOT: memref.alloca
+// CHECK: %[[DEF:.*]] = scf.if {{.*}} -> (vector<8xf32>)
+// CHECK: scf.yield %{{.*}} : vector<8xf32>
+// CHECK: scf.yield %{{.*}} : vector<8xf32>
+// CHECK: %[[R:.*]] = vector.extract_strided_slice %[[DEF]] offsets = [2], sizes = [4], strides = [1]
+// CHECK: return %[[R]]
+func.func @partial_read_branch(%a0: vector<8xf32>, %a1: vector<8xf32>, %pad: f32, %cond: i1) -> vector<4xf32> {
+ %c0 = arith.constant 0 : index
+ %c2 = arith.constant 2 : index
+ %a = memref.alloca() : memref<8xf32>
+ scf.if %cond {
+ vector.transfer_write %a0, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+ } else {
+ vector.transfer_write %a1, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+ }
+ %r = vector.transfer_read %a[%c2], %pad {in_bounds = [true]} : memref<8xf32>, vector<4xf32>
+ return %r : vector<4xf32>
+}
+
+// -----
+
+// A dynamic subview's slot value represents its parent, not its runtime shape.
+// CHECK-LABEL: func.func @partial_read_dynamic_alias
+// CHECK: memref.alloca
+// CHECK: memref.subview
+// CHECK: vector.transfer_read
+func.func @partial_read_dynamic_alias(%v: vector<8xf32>, %pad: f32, %size: index) -> vector<4xf32> {
+ %c0 = arith.constant 0 : index
+ %a = memref.alloca() : memref<8xf32>
+ vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+ %sv = memref.subview %a[0] [%size] [1] : memref<8xf32> to memref<?xf32, strided<[1]>>
+ %r = vector.transfer_read %sv[%c0], %pad {in_bounds = [true]} : memref<?xf32, strided<[1]>>, vector<4xf32>
+ return %r : vector<4xf32>
+}
+
+// -----
+
+// Scalable transfers keep the whole-buffer requirement.
+// CHECK-LABEL: func.func @partial_read_scalable
+// CHECK: memref.alloca
+// CHECK: vector.transfer_write
+// CHECK: vector.transfer_read
+func.func @partial_read_scalable(%v: vector<[8]xf32>, %pad: f32) -> vector<[4]xf32> {
+ %c0 = arith.constant 0 : index
+ %c8 = arith.constant 8 : index
+ %scale = vector.vscale
+ %size = arith.muli %scale, %c8 : index
+ %a = memref.alloca(%size) : memref<?xf32>
+ vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<[8]xf32>, memref<?xf32>
+ %r = vector.transfer_read %a[%c0], %pad {in_bounds = [true]} : memref<?xf32>, vector<[4]xf32>
+ return %r : vector<[4]xf32>
+}
+
+// -----
+
+// A slice may span lanes written through an alias and untouched parent lanes.
+// CHECK-LABEL: func.func @partial_read_after_alias_write
+// CHECK-SAME: (%[[INIT:.*]]: vector<8xf32>, %[[V:.*]]: vector<4xf32>,
+// CHECK-NOT: memref.alloca
+// CHECK: %[[MERGED:.*]] = vector.insert_strided_slice %[[V]], %[[INIT]] offsets = [2], strides = [1]
+// CHECK: %[[R:.*]] = vector.extract_strided_slice %[[MERGED]] offsets = [1], sizes = [4], strides = [1]
+// CHECK: return %[[R]]
+func.func @partial_read_after_alias_write(%init: vector<8xf32>, %v: vector<4xf32>, %pad: f32) -> vector<4xf32> {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %a = memref.alloca() : memref<8xf32>
+ vector.transfer_write %init, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+ %sv = memref.subview %a[2] [4] [1] : memref<8xf32> to memref<4xf32, strided<[1], offset: 2>>
+ vector.transfer_write %v, %sv[%c0] {in_bounds = [true]} : vector<4xf32>, memref<4xf32, strided<[1], offset: 2>>
+ %r = vector.transfer_read %a[%c1], %pad {in_bounds = [true]} : memref<8xf32>, vector<4xf32>
+ return %r : vector<4xf32>
+}
>From 58bf91cd1d2262dc6932fce9e9709bb0fc87b835 Mon Sep 17 00:00:00 2001
From: meinie0826 <929449726 at qq.com>
Date: Wed, 28 Oct 2026 13:45:09 +0800
Subject: [PATCH 2/9] [mlir][vector] Share Mem2Reg transfer slot access checks
---
.../Transforms/MemorySlotInterfacesImpl.cpp | 24 ++++++++++---------
1 file changed, 13 insertions(+), 11 deletions(-)
diff --git a/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp b/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp
index 5422e041ceff6..85446807b6ff9 100644
--- a/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp
@@ -46,6 +46,15 @@ using namespace mlir::vector;
// Utilities
//===----------------------------------------------------------------------===//
+/// Return whether the transfer's only blocking use is its memref slot base.
+static bool
+isTransferSlotAccess(VectorTransferOpInterface xferOp, const MemorySlot &slot,
+ const SmallPtrSetImpl<OpOperand *> &blockingUses) {
+ return blockingUses.size() == 1 &&
+ (*blockingUses.begin())->get() == slot.ptr &&
+ xferOp.getBase() == slot.ptr && isa<MemRefType>(slot.ptr.getType());
+}
+
/// Returns whether `xferOp` can be promoted to use or define `slot`'s vector
/// value. The transfer's memref base must be the slot pointer, its vector type
/// must equal `slot.valueType`, its indices must be zero, and its permutation
@@ -57,15 +66,7 @@ using namespace mlir::vector;
static bool
isPromotableTransfer(VectorTransferOpInterface xferOp, const MemorySlot &slot,
const SmallPtrSetImpl<OpOperand *> &blockingUses) {
- // The transfer's memref base must be the slot pointer.
- if (blockingUses.size() != 1)
- return false;
- Value blockingUse = (*blockingUses.begin())->get();
- if (blockingUse != slot.ptr || xferOp.getBase() != slot.ptr)
- return false;
-
- // Reject the tensor form (already implied, since slot pointers are memrefs).
- if (!isa<MemRefType>(xferOp.getBase().getType()))
+ if (!isTransferSlotAccess(xferOp, slot, blockingUses))
return false;
// Exact type match pins rank/extents/element type/scalable dims.
@@ -96,8 +97,9 @@ isPromotableTransfer(VectorTransferOpInterface xferOp, const MemorySlot &slot,
static FailureOr<SmallVector<int64_t>> getPromotableReadSliceOffsets(
TransferReadOp readOp, const MemorySlot &slot,
const SmallPtrSetImpl<OpOperand *> &blockingUses) {
- if (blockingUses.size() != 1 || (*blockingUses.begin())->get() != slot.ptr ||
- readOp.getBase() != slot.ptr || !isa<MemRefType>(slot.ptr.getType()))
+ if (!isTransferSlotAccess(
+ cast<VectorTransferOpInterface>(readOp.getOperation()), slot,
+ blockingUses))
return failure();
if (readOp.getMask() || readOp.isMasked() || readOp.hasOutOfBoundsDim() ||
!readOp.getPermutationMap().isIdentity() ||
>From a6cf92eade58cdaab36b2f40e8ff4a1afaa611f8 Mon Sep 17 00:00:00 2001
From: meinie0826 <929449726 at qq.com>
Date: Wed, 28 Oct 2026 13:53:38 +0800
Subject: [PATCH 3/9] [mlir][vector] Promote masked sub-vector reads through
dynamic subviews
---
.../Transforms/MemorySlotInterfacesImpl.cpp | 39 +++--
mlir/test/Transforms/Mem2Reg/vector.mlir | 157 ++++++++++++++++--
2 files changed, 166 insertions(+), 30 deletions(-)
diff --git a/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp b/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp
index 85446807b6ff9..746fef2a40b75 100644
--- a/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp
@@ -16,8 +16,7 @@
// a write defines its next value.
// Static subviews use a vector matching the subview's shape: reads extract that
// sub-vector from the parent, and writes insert it back into the parent.
-// Supported transfers through dynamic subviews use a vector type matching the
-// whole static parent, and the subview's slot holds that whole vector. Reads
+// Dynamic subview slots hold the whole static parent vector. Reads
// pad lanes outside the runtime extent, and writes preserve the previous value
// of those lanes. `arith.select` implements this behavior and also handles
// lanes disabled by a transfer's mask operand.
@@ -91,9 +90,9 @@ isPromotableTransfer(VectorTransferOpInterface xferOp, const MemorySlot &slot,
return true;
}
-/// Return offsets for an unmasked, in-bounds read of a fixed-size sub-vector
-/// of the slot. Dynamic subview slots hold the entire parent vector, not the
-/// runtime extent of the view, so containment in that vector is insufficient.
+/// Return constant offsets for a fixed-size read contained in the slot vector.
+/// Static slots must cover their memref extent. Dynamic subview slots hold the
+/// whole parent vector; reads use an extent mask to pad lanes outside the view.
static FailureOr<SmallVector<int64_t>> getPromotableReadSliceOffsets(
TransferReadOp readOp, const MemorySlot &slot,
const SmallPtrSetImpl<OpOperand *> &blockingUses) {
@@ -101,9 +100,7 @@ static FailureOr<SmallVector<int64_t>> getPromotableReadSliceOffsets(
cast<VectorTransferOpInterface>(readOp.getOperation()), slot,
blockingUses))
return failure();
- if (readOp.getMask() || readOp.isMasked() || readOp.hasOutOfBoundsDim() ||
- !readOp.getPermutationMap().isIdentity() ||
- memref::isDynamicSubViewSlot(slot.ptr))
+ if (readOp.isMasked() || !readOp.getPermutationMap().isIdentity())
return failure();
auto slotType = dyn_cast<VectorType>(slot.valueType);
@@ -114,6 +111,12 @@ static FailureOr<SmallVector<int64_t>> getPromotableReadSliceOffsets(
readType.getElementType() != slotType.getElementType())
return failure();
+ auto memrefType = cast<MemRefType>(slot.ptr.getType());
+ if (!memref::isDynamicSubViewSlot(slot.ptr) &&
+ (!memrefType.hasStaticShape() ||
+ memrefType.getShape() != slotType.getShape()))
+ return failure();
+
SmallVector<int64_t> offsets;
for (auto [index, size, extent] : llvm::zip_equal(
readOp.getIndices(), readType.getShape(), slotType.getShape())) {
@@ -169,28 +172,30 @@ struct TransferReadOpMemOpModel
const DataLayout &dataLayout) const {
auto readOp = cast<vector::TransferReadOp>(op);
Location loc = op->getLoc();
+ Value result = reachingDefinition;
+ Value mask = memref::buildDynamicSubViewMask(builder, loc, slot.ptr);
if (readOp.getVectorType() != slot.valueType) {
auto offsets = getPromotableReadSliceOffsets(readOp, slot, blockingUses);
assert(succeeded(offsets) && "expected a promotable read slice");
SmallVector<int64_t> strides(offsets->size(), 1);
- Value result = vector::ExtractStridedSliceOp::create(
- builder, loc, reachingDefinition, *offsets,
- readOp.getVectorType().getShape(), strides);
- readOp.getVector().replaceAllUsesWith(result);
- return DeletionKind::Delete;
+ auto extractSlice = [&](Value value) -> Value {
+ return vector::ExtractStridedSliceOp::create(
+ builder, loc, value, *offsets, readOp.getVectorType().getShape(),
+ strides);
+ };
+ result = extractSlice(result);
+ if (mask)
+ mask = extractSlice(mask);
}
- Value mask = memref::buildDynamicSubViewMask(builder, loc, slot.ptr);
if (Value opMask = readOp.getMask())
mask = mask
? arith::AndIOp::create(builder, loc, mask, opMask).getResult()
: opMask;
- Value result = reachingDefinition;
if (mask) {
Value padSplat = vector::BroadcastOp::create(
builder, loc, readOp.getVectorType(), readOp.getPadding());
- result = arith::SelectOp::create(builder, loc, mask, reachingDefinition,
- padSplat);
+ result = arith::SelectOp::create(builder, loc, mask, result, padSplat);
}
readOp.getVector().replaceAllUsesWith(result);
return DeletionKind::Delete;
diff --git a/mlir/test/Transforms/Mem2Reg/vector.mlir b/mlir/test/Transforms/Mem2Reg/vector.mlir
index 02e4a9f1ab778..bfed63b61ed09 100644
--- a/mlir/test/Transforms/Mem2Reg/vector.mlir
+++ b/mlir/test/Transforms/Mem2Reg/vector.mlir
@@ -819,25 +819,29 @@ func.func @partial_read_large_index(%v: vector<8xf32>, %pad: f32) -> vector<4xf3
// -----
-// CHECK-LABEL: func.func @partial_read_unproven_bounds
-// CHECK: memref.alloca
-// CHECK: vector.transfer_write
-// CHECK: vector.transfer_read
-func.func @partial_read_unproven_bounds(%v: vector<8xf32>, %pad: f32) -> vector<4xf32> {
+// CHECK-LABEL: func.func @partial_read_static_bounds
+// CHECK-SAME: (%[[V:.*]]: vector<8xf32>,
+// CHECK-NOT: memref.alloca
+// CHECK: %[[R:.*]] = vector.extract_strided_slice %[[V]] offsets = [2], sizes = [4], strides = [1]
+// CHECK: return %[[R]]
+func.func @partial_read_static_bounds(%v: vector<8xf32>, %pad: f32) -> vector<4xf32> {
%c0 = arith.constant 0 : index
%c2 = arith.constant 2 : index
%a = memref.alloca() : memref<8xf32>
vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
- %r = vector.transfer_read %a[%c2], %pad : memref<8xf32>, vector<4xf32>
+ %r = vector.transfer_read %a[%c2], %pad : memref<8xf32>, vector<4xf32>
return %r : vector<4xf32>
}
// -----
// CHECK-LABEL: func.func @partial_read_masked
-// CHECK: memref.alloca
-// CHECK: vector.transfer_write
-// CHECK: vector.transfer_read
+// CHECK-SAME: (%[[V:.*]]: vector<8xf32>, %[[PAD:.*]]: f32, %[[MASK:.*]]: vector<4xi1>)
+// CHECK-NOT: memref.alloca
+// CHECK: %[[SLICE:.*]] = vector.extract_strided_slice %[[V]] offsets = [2], sizes = [4], strides = [1]
+// CHECK: %[[PADDING:.*]] = vector.broadcast %[[PAD]] : f32 to vector<4xf32>
+// CHECK: %[[R:.*]] = arith.select %[[MASK]], %[[SLICE]], %[[PADDING]] : vector<4xi1>, vector<4xf32>
+// CHECK: return %[[R]]
func.func @partial_read_masked(%v: vector<8xf32>, %pad: f32, %mask: vector<4xi1>) -> vector<4xf32> {
%c0 = arith.constant 0 : index
%c2 = arith.constant 2 : index
@@ -969,15 +973,21 @@ func.func @partial_read_branch(%a0: vector<8xf32>, %a1: vector<8xf32>, %pad: f32
// A dynamic subview's slot value represents its parent, not its runtime shape.
// CHECK-LABEL: func.func @partial_read_dynamic_alias
-// CHECK: memref.alloca
-// CHECK: memref.subview
-// CHECK: vector.transfer_read
+// CHECK-SAME: (%[[V:.*]]: vector<8xf32>, %[[PAD:.*]]: f32, %[[SIZE:.*]]: index)
+// CHECK-NOT: memref.alloca
+// CHECK-NOT: memref.subview
+// CHECK: %[[EXTENT:.*]] = vector.create_mask %[[SIZE]] : vector<8xi1>
+// CHECK: %[[SLICE:.*]] = vector.extract_strided_slice %[[V]] offsets = [0], sizes = [4], strides = [1]
+// CHECK: %[[MASK:.*]] = vector.extract_strided_slice %[[EXTENT]] offsets = [0], sizes = [4], strides = [1]
+// CHECK: %[[PADDING:.*]] = vector.broadcast %[[PAD]] : f32 to vector<4xf32>
+// CHECK: %[[R:.*]] = arith.select %[[MASK]], %[[SLICE]], %[[PADDING]] : vector<4xi1>, vector<4xf32>
+// CHECK: return %[[R]]
func.func @partial_read_dynamic_alias(%v: vector<8xf32>, %pad: f32, %size: index) -> vector<4xf32> {
%c0 = arith.constant 0 : index
%a = memref.alloca() : memref<8xf32>
vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
%sv = memref.subview %a[0] [%size] [1] : memref<8xf32> to memref<?xf32, strided<[1]>>
- %r = vector.transfer_read %sv[%c0], %pad {in_bounds = [true]} : memref<?xf32, strided<[1]>>, vector<4xf32>
+ %r = vector.transfer_read %sv[%c0], %pad : memref<?xf32, strided<[1]>>, vector<4xf32>
return %r : vector<4xf32>
}
@@ -1018,3 +1028,124 @@ func.func @partial_read_after_alias_write(%init: vector<8xf32>, %v: vector<4xf32
%r = vector.transfer_read %a[%c1], %pad {in_bounds = [true]} : memref<8xf32>, vector<4xf32>
return %r : vector<4xf32>
}
+
+// -----
+
+// Static containment also proves bounds for a whole-slot read.
+// CHECK-LABEL: func.func @whole_read_static_bounds
+// CHECK-SAME: (%[[V:.*]]: vector<8xf32>,
+// CHECK-NOT: memref.alloca
+// CHECK-NOT: vector.transfer
+// CHECK: return %[[V]]
+func.func @whole_read_static_bounds(%v: vector<8xf32>, %pad: f32) -> vector<8xf32> {
+ %c0 = arith.constant 0 : index
+ %a = memref.alloca() : memref<8xf32>
+ vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+ %r = vector.transfer_read %a[%c0], %pad : memref<8xf32>, vector<8xf32>
+ return %r : vector<8xf32>
+}
+
+// -----
+
+// The view may end inside the slice; combine its extent with the read mask.
+// CHECK-LABEL: func.func @partial_read_dynamic_alias_masked
+// CHECK-SAME: (%[[V:.*]]: vector<8xf32>, %[[PAD:.*]]: f32, %[[SIZE:.*]]: index, %[[OPMASK:.*]]: vector<4xi1>)
+// CHECK-NOT: memref.alloca
+// CHECK-NOT: memref.subview
+// CHECK: %[[EXTENT:.*]] = vector.create_mask %[[SIZE]] : vector<8xi1>
+// CHECK: %[[SLICE:.*]] = vector.extract_strided_slice %[[V]] offsets = [2], sizes = [4], strides = [1]
+// CHECK: %[[SUBMASK:.*]] = vector.extract_strided_slice %[[EXTENT]] offsets = [2], sizes = [4], strides = [1]
+// CHECK: %[[MASK:.*]] = arith.andi %[[SUBMASK]], %[[OPMASK]] : vector<4xi1>
+// CHECK: %[[PADDING:.*]] = vector.broadcast %[[PAD]] : f32 to vector<4xf32>
+// CHECK: %[[R:.*]] = arith.select %[[MASK]], %[[SLICE]], %[[PADDING]] : vector<4xi1>, vector<4xf32>
+// CHECK: return %[[R]]
+func.func @partial_read_dynamic_alias_masked(%v: vector<8xf32>, %pad: f32, %size: index, %mask: vector<4xi1>) -> vector<4xf32> {
+ %c0 = arith.constant 0 : index
+ %c2 = arith.constant 2 : index
+ %a = memref.alloca() : memref<8xf32>
+ vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+ %sv = memref.subview %a[0] [%size] [1] : memref<8xf32> to memref<?xf32, strided<[1]>>
+ %r = vector.transfer_read %sv[%c2], %pad, %mask : memref<?xf32, strided<[1]>>, vector<4xf32>
+ return %r : vector<4xf32>
+}
+
+// -----
+
+// CHECK-LABEL: func.func @partial_read_dynamic_alias_2d
+// CHECK-SAME: (%[[V:.*]]: vector<4x8xf32>, %[[PAD:.*]]: f32, %[[SIZE:.*]]: index)
+// CHECK-NOT: memref.alloca
+// CHECK-NOT: memref.subview
+// CHECK: %[[C4:.*]] = arith.constant 4 : index
+// CHECK: %[[EXTENT:.*]] = vector.create_mask %[[C4]], %[[SIZE]] : vector<4x8xi1>
+// CHECK: %[[SLICE:.*]] = vector.extract_strided_slice %[[V]] offsets = [1, 2], sizes = [2, 4], strides = [1, 1]
+// CHECK: %[[MASK:.*]] = vector.extract_strided_slice %[[EXTENT]] offsets = [1, 2], sizes = [2, 4], strides = [1, 1]
+// CHECK: %[[PADDING:.*]] = vector.broadcast %[[PAD]] : f32 to vector<2x4xf32>
+// CHECK: %[[R:.*]] = arith.select %[[MASK]], %[[SLICE]], %[[PADDING]] : vector<2x4xi1>, vector<2x4xf32>
+// CHECK: return %[[R]]
+func.func @partial_read_dynamic_alias_2d(%v: vector<4x8xf32>, %pad: f32, %size: index) -> vector<2x4xf32> {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %c2 = arith.constant 2 : index
+ %a = memref.alloca() : memref<4x8xf32>
+ vector.transfer_write %v, %a[%c0, %c0] {in_bounds = [true, true]} : vector<4x8xf32>, memref<4x8xf32>
+ %sv = memref.subview %a[0, 0] [4, %size] [1, 1] : memref<4x8xf32> to memref<4x?xf32, strided<[8, 1]>>
+ %r = vector.transfer_read %sv[%c1, %c2], %pad : memref<4x?xf32, strided<[8, 1]>>, vector<2x4xf32>
+ return %r : vector<2x4xf32>
+}
+
+// -----
+
+// A constant-zero size still has a dynamic memref type and pads every lane.
+// CHECK-LABEL: func.func @partial_read_empty_dynamic_alias
+// CHECK-SAME: (%[[V:.*]]: vector<8xf32>, %[[PAD:.*]]: f32)
+// CHECK-NOT: memref.alloca
+// CHECK-NOT: memref.subview
+// CHECK: %[[ZERO:.*]] = arith.constant 0 : index
+// CHECK: %[[EXTENT:.*]] = vector.create_mask %[[ZERO]] : vector<8xi1>
+// CHECK: %[[SLICE:.*]] = vector.extract_strided_slice %[[V]] offsets = [2], sizes = [4], strides = [1]
+// CHECK: %[[MASK:.*]] = vector.extract_strided_slice %[[EXTENT]] offsets = [2], sizes = [4], strides = [1]
+// CHECK: %[[PADDING:.*]] = vector.broadcast %[[PAD]] : f32 to vector<4xf32>
+// CHECK: %[[R:.*]] = arith.select %[[MASK]], %[[SLICE]], %[[PADDING]] : vector<4xi1>, vector<4xf32>
+// CHECK: return %[[R]]
+func.func @partial_read_empty_dynamic_alias(%v: vector<8xf32>, %pad: f32) -> vector<4xf32> {
+ %c0 = arith.constant 0 : index
+ %c2 = arith.constant 2 : index
+ %a = memref.alloca() : memref<8xf32>
+ vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+ %sv = memref.subview %a[0] [%c0] [1] : memref<8xf32> to memref<?xf32, strided<[1]>>
+ %r = vector.transfer_read %sv[%c2], %pad : memref<?xf32, strided<[1]>>, vector<4xf32>
+ return %r : vector<4xf32>
+}
+
+// -----
+
+// Runtime padding does not allow extracting beyond the parent slot vector.
+// CHECK-LABEL: func.func @partial_read_dynamic_alias_exceeds_parent
+// CHECK: memref.alloca
+// CHECK: memref.subview
+// CHECK: vector.transfer_read
+func.func @partial_read_dynamic_alias_exceeds_parent(%v: vector<8xf32>, %pad: f32, %size: index) -> vector<4xf32> {
+ %c0 = arith.constant 0 : index
+ %c6 = arith.constant 6 : index
+ %a = memref.alloca() : memref<8xf32>
+ vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+ %sv = memref.subview %a[0] [%size] [1] : memref<8xf32> to memref<?xf32, strided<[1]>>
+ %r = vector.transfer_read %sv[%c6], %pad : memref<?xf32, strided<[1]>>, vector<4xf32>
+ return %r : vector<4xf32>
+}
+
+// -----
+
+// Nonzero offsets in a dynamic subview are not supported by the alias model.
+// CHECK-LABEL: func.func @partial_read_offset_dynamic_alias
+// CHECK: memref.alloca
+// CHECK: memref.subview
+// CHECK: vector.transfer_read
+func.func @partial_read_offset_dynamic_alias(%v: vector<8xf32>, %pad: f32, %size: index) -> vector<4xf32> {
+ %c0 = arith.constant 0 : index
+ %a = memref.alloca() : memref<8xf32>
+ vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+ %sv = memref.subview %a[2] [%size] [1] : memref<8xf32> to memref<?xf32, strided<[1], offset: 2>>
+ %r = vector.transfer_read %sv[%c0], %pad : memref<?xf32, strided<[1], offset: 2>>, vector<4xf32>
+ return %r : vector<4xf32>
+}
>From 1dda2f9aed9a554b1dfc7dbd9b4e683d45865288 Mon Sep 17 00:00:00 2001
From: meinie0826 <929449726 at qq.com>
Date: Wed, 28 Oct 2026 14:01:19 +0800
Subject: [PATCH 4/9] [mlir][vector] Rely on verifier and region promotion
guarantees for read slices
---
.../Transforms/MemorySlotInterfacesImpl.cpp | 10 ++----
mlir/test/Transforms/Mem2Reg/vector.mlir | 32 +++++++++++++++++++
2 files changed, 35 insertions(+), 7 deletions(-)
diff --git a/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp b/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp
index 746fef2a40b75..c6d8f50e14be4 100644
--- a/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp
@@ -98,23 +98,19 @@ static FailureOr<SmallVector<int64_t>> getPromotableReadSliceOffsets(
const SmallPtrSetImpl<OpOperand *> &blockingUses) {
if (!isTransferSlotAccess(
cast<VectorTransferOpInterface>(readOp.getOperation()), slot,
- blockingUses))
- return failure();
- if (readOp.isMasked() || !readOp.getPermutationMap().isIdentity())
+ blockingUses) ||
+ !readOp.getPermutationMap().isIdentity())
return failure();
auto slotType = dyn_cast<VectorType>(slot.valueType);
VectorType readType = readOp.getVectorType();
if (!slotType || slotType.isScalable() || readType.isScalable() ||
readType.getRank() != slotType.getRank() ||
- readOp.getIndices().size() != static_cast<size_t>(readType.getRank()) ||
readType.getElementType() != slotType.getElementType())
return failure();
- auto memrefType = cast<MemRefType>(slot.ptr.getType());
if (!memref::isDynamicSubViewSlot(slot.ptr) &&
- (!memrefType.hasStaticShape() ||
- memrefType.getShape() != slotType.getShape()))
+ readOp.getShapedType().getShape() != slotType.getShape())
return failure();
SmallVector<int64_t> offsets;
diff --git a/mlir/test/Transforms/Mem2Reg/vector.mlir b/mlir/test/Transforms/Mem2Reg/vector.mlir
index bfed63b61ed09..76ba0c6587e34 100644
--- a/mlir/test/Transforms/Mem2Reg/vector.mlir
+++ b/mlir/test/Transforms/Mem2Reg/vector.mlir
@@ -1149,3 +1149,35 @@ func.func @partial_read_offset_dynamic_alias(%v: vector<8xf32>, %pad: f32, %size
%r = vector.transfer_read %sv[%c0], %pad : memref<?xf32, strided<[1], offset: 2>>, vector<4xf32>
return %r : vector<4xf32>
}
+
+// -----
+
+// Mem2Reg cannot promote accesses through the vector.mask region.
+// CHECK-LABEL: func.func @partial_read_mask_region
+// CHECK: memref.alloca
+// CHECK: vector.transfer_write
+// CHECK: vector.mask
+// CHECK-SAME: vector.transfer_read
+func.func @partial_read_mask_region(%v: vector<8xf32>, %pad: f32, %mask: vector<4xi1>) -> vector<4xf32> {
+ %c0 = arith.constant 0 : index
+ %c2 = arith.constant 2 : index
+ %a = memref.alloca() : memref<8xf32>
+ vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+ %r = vector.mask %mask { vector.transfer_read %a[%c2], %pad {in_bounds = [true]} : memref<8xf32>, vector<4xf32> } : vector<4xi1> -> vector<4xf32>
+ return %r : vector<4xf32>
+}
+
+// -----
+
+// Transfers can change element width, but extracting a slice cannot.
+// CHECK-LABEL: func.func @partial_read_element_width
+// CHECK: memref.alloca
+// CHECK: vector.transfer_write
+// CHECK: vector.transfer_read
+func.func @partial_read_element_width(%v: vector<8xi32>, %pad: i32) -> vector<4xi64> {
+ %c0 = arith.constant 0 : index
+ %a = memref.alloca() : memref<8xi32>
+ vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xi32>, memref<8xi32>
+ %r = vector.transfer_read %a[%c0], %pad {in_bounds = [true]} : memref<8xi32>, vector<4xi64>
+ return %r : vector<4xi64>
+}
>From a97e37b42b95b7ebbeae51575b95e7d4e164a0a4 Mon Sep 17 00:00:00 2001
From: meinie0826 <929449726 at qq.com>
Date: Thu, 29 Oct 2026 14:14:16 +0800
Subject: [PATCH 5/9] [mlir][vector] Clarify read slice requirements and
consolidate position tests
---
.../Transforms/MemorySlotInterfacesImpl.cpp | 18 +++++--
mlir/test/Transforms/Mem2Reg/vector.mlir | 48 ++++---------------
2 files changed, 23 insertions(+), 43 deletions(-)
diff --git a/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp b/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp
index c6d8f50e14be4..2fe4e073e6099 100644
--- a/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp
@@ -12,8 +12,8 @@
// and definitions of a vector SSA value. The models for `memref.subview`
// live in the MemRef dialect.
//
-// A read uses the slot's current value or extracts an in-bounds sub-vector;
-// a write defines its next value.
+// A read uses the slot's current value or extracts a constant-offset sub-vector
+// from the slot vector; a write defines its next value.
// Static subviews use a vector matching the subview's shape: reads extract that
// sub-vector from the parent, and writes insert it back into the parent.
// Dynamic subview slots hold the whole static parent vector. Reads
@@ -90,9 +90,17 @@ isPromotableTransfer(VectorTransferOpInterface xferOp, const MemorySlot &slot,
return true;
}
-/// Return constant offsets for a fixed-size read contained in the slot vector.
-/// Static slots must cover their memref extent. Dynamic subview slots hold the
-/// whole parent vector; reads use an extent mask to pad lanes outside the view.
+/// Return offsets for a constant-offset sub-vector read from the slot vector.
+/// Requires:
+/// - The transfer's only blocking use is its memref slot base.
+/// - Read and slot vectors have fixed sizes, equal ranks and element types.
+/// - The permutation map is identity and indices are constant and nonnegative.
+/// - The read slice is contained in the slot vector.
+/// - The memref shape matches the slot vector, or it is a zero-offset,
+/// unit-stride, same-rank dynamic subview of a static parent.
+///
+/// For dynamic subviews, containment is in the whole parent vector, not the
+/// runtime extent. Reads use an extent mask to select padding outside the view.
static FailureOr<SmallVector<int64_t>> getPromotableReadSliceOffsets(
TransferReadOp readOp, const MemorySlot &slot,
const SmallPtrSetImpl<OpOperand *> &blockingUses) {
diff --git a/mlir/test/Transforms/Mem2Reg/vector.mlir b/mlir/test/Transforms/Mem2Reg/vector.mlir
index 76ba0c6587e34..b742dcfccb066 100644
--- a/mlir/test/Transforms/Mem2Reg/vector.mlir
+++ b/mlir/test/Transforms/Mem2Reg/vector.mlir
@@ -692,52 +692,24 @@ func.func @negative_masked_write(%v: vector<8xf32>, %w: vector<8xf32>, %m: vecto
// -----
-// CHECK-LABEL: func.func @partial_read_origin
+// CHECK-LABEL: func.func @partial_read_positions
// CHECK-SAME: (%[[V:.*]]: vector<8xf32>,
// CHECK-NOT: memref.alloca
// CHECK-NOT: vector.transfer
-// CHECK: %[[R:.*]] = vector.extract_strided_slice %[[V]] offsets = [0], sizes = [4], strides = [1]
-// CHECK: return %[[R]] : vector<4xf32>
-func.func @partial_read_origin(%v: vector<8xf32>, %pad: f32) -> vector<4xf32> {
- %c0 = arith.constant 0 : index
- %a = memref.alloca() : memref<8xf32>
- vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
- %r = vector.transfer_read %a[%c0], %pad {in_bounds = [true]} : memref<8xf32>, vector<4xf32>
- return %r : vector<4xf32>
-}
-
-// -----
-
-// CHECK-LABEL: func.func @partial_read_offset
-// CHECK-SAME: (%[[V:.*]]: vector<8xf32>,
-// CHECK-NOT: memref.alloca
-// CHECK-NOT: vector.transfer
-// CHECK: %[[R:.*]] = vector.extract_strided_slice %[[V]] offsets = [2], sizes = [4], strides = [1]
-// CHECK: return %[[R]] : vector<4xf32>
-func.func @partial_read_offset(%v: vector<8xf32>, %pad: f32) -> vector<4xf32> {
+// CHECK: %[[ORIGIN:.*]] = vector.extract_strided_slice %[[V]] offsets = [0], sizes = [4], strides = [1]
+// CHECK: %[[MIDDLE:.*]] = vector.extract_strided_slice %[[V]] offsets = [2], sizes = [4], strides = [1]
+// CHECK: %[[END:.*]] = vector.extract_strided_slice %[[V]] offsets = [4], sizes = [4], strides = [1]
+// CHECK: return %[[ORIGIN]], %[[MIDDLE]], %[[END]] : vector<4xf32>, vector<4xf32>, vector<4xf32>
+func.func @partial_read_positions(%v: vector<8xf32>, %pad: f32) -> (vector<4xf32>, vector<4xf32>, vector<4xf32>) {
%c0 = arith.constant 0 : index
%c2 = arith.constant 2 : index
- %a = memref.alloca() : memref<8xf32>
- vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
- %r = vector.transfer_read %a[%c2], %pad {in_bounds = [true]} : memref<8xf32>, vector<4xf32>
- return %r : vector<4xf32>
-}
-
-// -----
-
-// CHECK-LABEL: func.func @partial_read_at_end
-// CHECK-SAME: (%[[V:.*]]: vector<8xf32>,
-// CHECK-NOT: memref.alloca
-// CHECK-NOT: vector.transfer
-// CHECK: %[[R:.*]] = vector.extract_strided_slice %[[V]] offsets = [4], sizes = [4], strides = [1]
-// CHECK: return %[[R]] : vector<4xf32>
-func.func @partial_read_at_end(%v: vector<8xf32>, %pad: f32) -> vector<4xf32> {
- %c0 = arith.constant 0 : index
%c4 = arith.constant 4 : index
%a = memref.alloca() : memref<8xf32>
vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
- %r = vector.transfer_read %a[%c4], %pad {in_bounds = [true]} : memref<8xf32>, vector<4xf32>
- return %r : vector<4xf32>
+ %origin = vector.transfer_read %a[%c0], %pad {in_bounds = [true]} : memref<8xf32>, vector<4xf32>
+ %middle = vector.transfer_read %a[%c2], %pad {in_bounds = [true]} : memref<8xf32>, vector<4xf32>
+ %end = vector.transfer_read %a[%c4], %pad {in_bounds = [true]} : memref<8xf32>, vector<4xf32>
+ return %origin, %middle, %end : vector<4xf32>, vector<4xf32>, vector<4xf32>
}
// -----
>From f78b2504857fbb65b2b99bc1699d7b01da7cf673 Mon Sep 17 00:00:00 2001
From: meinie0826 <929449726 at qq.com>
Date: Thu, 29 Oct 2026 14:20:54 +0800
Subject: [PATCH 6/9] [mlir][vector] Remove redundant MemRef check from
transfer slot access
---
mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp b/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp
index 2fe4e073e6099..3308a4c8d45a8 100644
--- a/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp
@@ -51,7 +51,7 @@ isTransferSlotAccess(VectorTransferOpInterface xferOp, const MemorySlot &slot,
const SmallPtrSetImpl<OpOperand *> &blockingUses) {
return blockingUses.size() == 1 &&
(*blockingUses.begin())->get() == slot.ptr &&
- xferOp.getBase() == slot.ptr && isa<MemRefType>(slot.ptr.getType());
+ xferOp.getBase() == slot.ptr;
}
/// Returns whether `xferOp` can be promoted to use or define `slot`'s vector
>From a5de65689b404dda8ec3a9bd79d8a271510a5e09 Mon Sep 17 00:00:00 2001
From: meinie0826 <929449726 at qq.com>
Date: Thu, 29 Oct 2026 14:23:47 +0800
Subject: [PATCH 7/9] [mlir][vector] Shorten read slice requirements comment
---
.../Transforms/MemorySlotInterfacesImpl.cpp | 16 ++++++----------
1 file changed, 6 insertions(+), 10 deletions(-)
diff --git a/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp b/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp
index 3308a4c8d45a8..2c6fcca0e32bd 100644
--- a/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp
@@ -90,17 +90,13 @@ isPromotableTransfer(VectorTransferOpInterface xferOp, const MemorySlot &slot,
return true;
}
-/// Return offsets for a constant-offset sub-vector read from the slot vector.
-/// Requires:
-/// - The transfer's only blocking use is its memref slot base.
-/// - Read and slot vectors have fixed sizes, equal ranks and element types.
-/// - The permutation map is identity and indices are constant and nonnegative.
-/// - The read slice is contained in the slot vector.
-/// - The memref shape matches the slot vector, or it is a zero-offset,
-/// unit-stride, same-rank dynamic subview of a static parent.
+/// Return constant offsets for a slice contained in the slot vector. Requires:
+/// - Fixed-size vectors of equal rank and element type.
+/// - Identity map and constant nonnegative indices.
+/// - Matching memref/slot shapes, or a zero-offset, unit-stride, same-rank
+/// dynamic subview of a static parent.
///
-/// For dynamic subviews, containment is in the whole parent vector, not the
-/// runtime extent. Reads use an extent mask to select padding outside the view.
+/// Dynamic subview reads use an extent mask to pad outside the runtime extent.
static FailureOr<SmallVector<int64_t>> getPromotableReadSliceOffsets(
TransferReadOp readOp, const MemorySlot &slot,
const SmallPtrSetImpl<OpOperand *> &blockingUses) {
>From 29c71d180bd297140502d1f1884242d5a6907c28 Mon Sep 17 00:00:00 2001
From: meinie0826 <929449726 at qq.com>
Date: Thu, 29 Oct 2026 14:31:59 +0800
Subject: [PATCH 8/9] [mlir][vector] Remove transfer test with inconsistent
element types
---
mlir/test/Transforms/Mem2Reg/vector.mlir | 15 ---------------
1 file changed, 15 deletions(-)
diff --git a/mlir/test/Transforms/Mem2Reg/vector.mlir b/mlir/test/Transforms/Mem2Reg/vector.mlir
index b742dcfccb066..f412ce08492db 100644
--- a/mlir/test/Transforms/Mem2Reg/vector.mlir
+++ b/mlir/test/Transforms/Mem2Reg/vector.mlir
@@ -1138,18 +1138,3 @@ func.func @partial_read_mask_region(%v: vector<8xf32>, %pad: f32, %mask: vector<
%r = vector.mask %mask { vector.transfer_read %a[%c2], %pad {in_bounds = [true]} : memref<8xf32>, vector<4xf32> } : vector<4xi1> -> vector<4xf32>
return %r : vector<4xf32>
}
-
-// -----
-
-// Transfers can change element width, but extracting a slice cannot.
-// CHECK-LABEL: func.func @partial_read_element_width
-// CHECK: memref.alloca
-// CHECK: vector.transfer_write
-// CHECK: vector.transfer_read
-func.func @partial_read_element_width(%v: vector<8xi32>, %pad: i32) -> vector<4xi64> {
- %c0 = arith.constant 0 : index
- %a = memref.alloca() : memref<8xi32>
- vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xi32>, memref<8xi32>
- %r = vector.transfer_read %a[%c0], %pad {in_bounds = [true]} : memref<8xi32>, vector<4xi64>
- return %r : vector<4xi64>
-}
>From 34d83c34acfee88183280dab9b5634ef5bfc870b Mon Sep 17 00:00:00 2001
From: meinie0826 <929449726 at qq.com>
Date: Sat, 31 Oct 2026 18:40:36 +0800
Subject: [PATCH 9/9] Clarify vector slot invariants and test names
---
.../Transforms/MemorySlotInterfacesImpl.cpp | 10 +--
mlir/test/Transforms/Mem2Reg/vector.mlir | 76 +++++++++----------
2 files changed, 41 insertions(+), 45 deletions(-)
diff --git a/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp b/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp
index 2c6fcca0e32bd..e9b4e7327bfd0 100644
--- a/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/MemorySlotInterfacesImpl.cpp
@@ -100,9 +100,7 @@ isPromotableTransfer(VectorTransferOpInterface xferOp, const MemorySlot &slot,
static FailureOr<SmallVector<int64_t>> getPromotableReadSliceOffsets(
TransferReadOp readOp, const MemorySlot &slot,
const SmallPtrSetImpl<OpOperand *> &blockingUses) {
- if (!isTransferSlotAccess(
- cast<VectorTransferOpInterface>(readOp.getOperation()), slot,
- blockingUses) ||
+ if (!isTransferSlotAccess(readOp, slot, blockingUses) ||
!readOp.getPermutationMap().isIdentity())
return failure();
@@ -113,10 +111,8 @@ static FailureOr<SmallVector<int64_t>> getPromotableReadSliceOffsets(
readType.getElementType() != slotType.getElementType())
return failure();
- if (!memref::isDynamicSubViewSlot(slot.ptr) &&
- readOp.getShapedType().getShape() != slotType.getShape())
- return failure();
-
+ // Alloca and static subview slots match the memref shape; dynamic subview
+ // slots cover the whole parent vector, with runtime bounds handled by a mask.
SmallVector<int64_t> offsets;
for (auto [index, size, extent] : llvm::zip_equal(
readOp.getIndices(), readType.getShape(), slotType.getShape())) {
diff --git a/mlir/test/Transforms/Mem2Reg/vector.mlir b/mlir/test/Transforms/Mem2Reg/vector.mlir
index f412ce08492db..e09673a4b0971 100644
--- a/mlir/test/Transforms/Mem2Reg/vector.mlir
+++ b/mlir/test/Transforms/Mem2Reg/vector.mlir
@@ -732,11 +732,11 @@ func.func @partial_read_2d(%v: vector<4x8xf32>, %pad: f32) -> vector<2x4xf32> {
// -----
-// CHECK-LABEL: func.func @partial_read_dynamic_index
+// CHECK-LABEL: func.func @negative_partial_read_dynamic_index
// CHECK: memref.alloca
// CHECK: vector.transfer_write
// CHECK: vector.transfer_read
-func.func @partial_read_dynamic_index(%v: vector<8xf32>, %pad: f32, %offset: index) -> vector<4xf32> {
+func.func @negative_partial_read_dynamic_index(%v: vector<8xf32>, %pad: f32, %offset: index) -> vector<4xf32> {
%c0 = arith.constant 0 : index
%a = memref.alloca() : memref<8xf32>
vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
@@ -746,11 +746,11 @@ func.func @partial_read_dynamic_index(%v: vector<8xf32>, %pad: f32, %offset: ind
// -----
-// CHECK-LABEL: func.func @partial_read_negative_index
+// CHECK-LABEL: func.func @negative_partial_read_negative_index
// CHECK: memref.alloca
// CHECK: vector.transfer_write
// CHECK: vector.transfer_read
-func.func @partial_read_negative_index(%v: vector<8xf32>, %pad: f32) -> vector<4xf32> {
+func.func @negative_partial_read_negative_index(%v: vector<8xf32>, %pad: f32) -> vector<4xf32> {
%c0 = arith.constant 0 : index
%c-1 = arith.constant -1 : index
%a = memref.alloca() : memref<8xf32>
@@ -761,11 +761,11 @@ func.func @partial_read_negative_index(%v: vector<8xf32>, %pad: f32) -> vector<4
// -----
-// CHECK-LABEL: func.func @partial_read_exceeds_extent
+// CHECK-LABEL: func.func @negative_partial_read_exceeds_extent
// CHECK: memref.alloca
// CHECK: vector.transfer_write
// CHECK: vector.transfer_read
-func.func @partial_read_exceeds_extent(%v: vector<8xf32>, %pad: f32) -> vector<4xf32> {
+func.func @negative_partial_read_exceeds_extent(%v: vector<8xf32>, %pad: f32) -> vector<4xf32> {
%c0 = arith.constant 0 : index
%c6 = arith.constant 6 : index
%a = memref.alloca() : memref<8xf32>
@@ -776,11 +776,11 @@ func.func @partial_read_exceeds_extent(%v: vector<8xf32>, %pad: f32) -> vector<4
// -----
-// CHECK-LABEL: func.func @partial_read_large_index
+// CHECK-LABEL: func.func @negative_partial_read_large_index
// CHECK: memref.alloca
// CHECK: vector.transfer_write
// CHECK: vector.transfer_read
-func.func @partial_read_large_index(%v: vector<8xf32>, %pad: f32) -> vector<4xf32> {
+func.func @negative_partial_read_large_index(%v: vector<8xf32>, %pad: f32) -> vector<4xf32> {
%c0 = arith.constant 0 : index
%c9223372036854775807 = arith.constant 9223372036854775807 : index
%a = memref.alloca() : memref<8xf32>
@@ -807,14 +807,14 @@ func.func @partial_read_static_bounds(%v: vector<8xf32>, %pad: f32) -> vector<4x
// -----
-// CHECK-LABEL: func.func @partial_read_masked
+// CHECK-LABEL: func.func @partial_read_with_mask
// CHECK-SAME: (%[[V:.*]]: vector<8xf32>, %[[PAD:.*]]: f32, %[[MASK:.*]]: vector<4xi1>)
// CHECK-NOT: memref.alloca
// CHECK: %[[SLICE:.*]] = vector.extract_strided_slice %[[V]] offsets = [2], sizes = [4], strides = [1]
// CHECK: %[[PADDING:.*]] = vector.broadcast %[[PAD]] : f32 to vector<4xf32>
// CHECK: %[[R:.*]] = arith.select %[[MASK]], %[[SLICE]], %[[PADDING]] : vector<4xi1>, vector<4xf32>
// CHECK: return %[[R]]
-func.func @partial_read_masked(%v: vector<8xf32>, %pad: f32, %mask: vector<4xi1>) -> vector<4xf32> {
+func.func @partial_read_with_mask(%v: vector<8xf32>, %pad: f32, %mask: vector<4xi1>) -> vector<4xf32> {
%c0 = arith.constant 0 : index
%c2 = arith.constant 2 : index
%a = memref.alloca() : memref<8xf32>
@@ -825,11 +825,11 @@ func.func @partial_read_masked(%v: vector<8xf32>, %pad: f32, %mask: vector<4xi1>
// -----
-// CHECK-LABEL: func.func @partial_read_transpose
+// CHECK-LABEL: func.func @negative_partial_read_transpose_map
// CHECK: memref.alloca
// CHECK: vector.transfer_write
// CHECK: vector.transfer_read
-func.func @partial_read_transpose(%v: vector<4x8xf32>, %pad: f32) -> vector<2x4xf32> {
+func.func @negative_partial_read_transpose_map(%v: vector<4x8xf32>, %pad: f32) -> vector<2x4xf32> {
%c0 = arith.constant 0 : index
%a = memref.alloca() : memref<4x8xf32>
vector.transfer_write %v, %a[%c0, %c0] {in_bounds = [true, true]} : vector<4x8xf32>, memref<4x8xf32>
@@ -839,11 +839,11 @@ func.func @partial_read_transpose(%v: vector<4x8xf32>, %pad: f32) -> vector<2x4x
// -----
-// CHECK-LABEL: func.func @partial_read_broadcast
+// CHECK-LABEL: func.func @negative_partial_read_broadcast_map
// CHECK: memref.alloca
// CHECK: vector.transfer_write
// CHECK: vector.transfer_read
-func.func @partial_read_broadcast(%v: vector<4x8xf32>, %pad: f32) -> vector<4xf32> {
+func.func @negative_partial_read_broadcast_map(%v: vector<4x8xf32>, %pad: f32) -> vector<4xf32> {
%c0 = arith.constant 0 : index
%a = memref.alloca() : memref<4x8xf32>
vector.transfer_write %v, %a[%c0, %c0] {in_bounds = [true, true]} : vector<4x8xf32>, memref<4x8xf32>
@@ -874,14 +874,14 @@ func.func @partial_read_before_write(%v: vector<8xf32>, %pad: f32) -> (vector<4x
// -----
// Static aliases provide their own sub-vector; the read extracts within it.
-// CHECK-LABEL: func.func @partial_read_static_alias
+// CHECK-LABEL: func.func @subview_static_partial_read
// CHECK-SAME: (%[[V:.*]]: vector<8xf32>,
// CHECK-NOT: memref.alloca
// CHECK-NOT: memref.subview
// CHECK: %[[ALIAS:.*]] = vector.extract_strided_slice %[[V]] offsets = [2], sizes = [4], strides = [1]
// CHECK: %[[R:.*]] = vector.extract_strided_slice %[[ALIAS]] offsets = [1], sizes = [2], strides = [1]
// CHECK: return %[[R]]
-func.func @partial_read_static_alias(%v: vector<8xf32>, %pad: f32) -> vector<2xf32> {
+func.func @subview_static_partial_read(%v: vector<8xf32>, %pad: f32) -> vector<2xf32> {
%c0 = arith.constant 0 : index
%c1 = arith.constant 1 : index
%a = memref.alloca() : memref<8xf32>
@@ -894,7 +894,7 @@ func.func @partial_read_static_alias(%v: vector<8xf32>, %pad: f32) -> vector<2xf
// -----
// Each slice reads the current loop-carried vector.
-// CHECK-LABEL: func.func @partial_read_loop
+// CHECK-LABEL: func.func @partial_buffer_in_loop
// CHECK-NOT: memref.alloca
// CHECK: %[[LOOP:.*]] = scf.for {{.*}} iter_args(%[[IT:.*]] = %{{.*}}) -> (vector<8xf32>)
// CHECK: %[[SLICE:.*]] = vector.extract_strided_slice %[[IT]] offsets = [2], sizes = [4], strides = [1]
@@ -903,7 +903,7 @@ func.func @partial_read_static_alias(%v: vector<8xf32>, %pad: f32) -> vector<2xf
// CHECK: scf.yield %[[FULL]]
// CHECK: %[[RESULT:.*]] = vector.extract_strided_slice %[[LOOP]] offsets = [2], sizes = [4], strides = [1]
// CHECK: return %[[RESULT]]
-func.func @partial_read_loop(%v: vector<8xf32>, %pad: f32, %lb: index, %ub: index, %step: index) -> vector<4xf32> {
+func.func @partial_buffer_in_loop(%v: vector<8xf32>, %pad: f32, %lb: index, %ub: index, %step: index) -> vector<4xf32> {
%c0 = arith.constant 0 : index
%c2 = arith.constant 2 : index
%a = memref.alloca() : memref<8xf32>
@@ -944,7 +944,7 @@ func.func @partial_read_branch(%a0: vector<8xf32>, %a1: vector<8xf32>, %pad: f32
// -----
// A dynamic subview's slot value represents its parent, not its runtime shape.
-// CHECK-LABEL: func.func @partial_read_dynamic_alias
+// CHECK-LABEL: func.func @subview_dynamic_partial_read
// CHECK-SAME: (%[[V:.*]]: vector<8xf32>, %[[PAD:.*]]: f32, %[[SIZE:.*]]: index)
// CHECK-NOT: memref.alloca
// CHECK-NOT: memref.subview
@@ -954,7 +954,7 @@ func.func @partial_read_branch(%a0: vector<8xf32>, %a1: vector<8xf32>, %pad: f32
// CHECK: %[[PADDING:.*]] = vector.broadcast %[[PAD]] : f32 to vector<4xf32>
// CHECK: %[[R:.*]] = arith.select %[[MASK]], %[[SLICE]], %[[PADDING]] : vector<4xi1>, vector<4xf32>
// CHECK: return %[[R]]
-func.func @partial_read_dynamic_alias(%v: vector<8xf32>, %pad: f32, %size: index) -> vector<4xf32> {
+func.func @subview_dynamic_partial_read(%v: vector<8xf32>, %pad: f32, %size: index) -> vector<4xf32> {
%c0 = arith.constant 0 : index
%a = memref.alloca() : memref<8xf32>
vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
@@ -966,11 +966,11 @@ func.func @partial_read_dynamic_alias(%v: vector<8xf32>, %pad: f32, %size: index
// -----
// Scalable transfers keep the whole-buffer requirement.
-// CHECK-LABEL: func.func @partial_read_scalable
+// CHECK-LABEL: func.func @negative_partial_read_scalable
// CHECK: memref.alloca
// CHECK: vector.transfer_write
// CHECK: vector.transfer_read
-func.func @partial_read_scalable(%v: vector<[8]xf32>, %pad: f32) -> vector<[4]xf32> {
+func.func @negative_partial_read_scalable(%v: vector<[8]xf32>, %pad: f32) -> vector<[4]xf32> {
%c0 = arith.constant 0 : index
%c8 = arith.constant 8 : index
%scale = vector.vscale
@@ -984,13 +984,13 @@ func.func @partial_read_scalable(%v: vector<[8]xf32>, %pad: f32) -> vector<[4]xf
// -----
// A slice may span lanes written through an alias and untouched parent lanes.
-// CHECK-LABEL: func.func @partial_read_after_alias_write
+// CHECK-LABEL: func.func @subview_static_partial_read_after_write
// CHECK-SAME: (%[[INIT:.*]]: vector<8xf32>, %[[V:.*]]: vector<4xf32>,
// CHECK-NOT: memref.alloca
// CHECK: %[[MERGED:.*]] = vector.insert_strided_slice %[[V]], %[[INIT]] offsets = [2], strides = [1]
// CHECK: %[[R:.*]] = vector.extract_strided_slice %[[MERGED]] offsets = [1], sizes = [4], strides = [1]
// CHECK: return %[[R]]
-func.func @partial_read_after_alias_write(%init: vector<8xf32>, %v: vector<4xf32>, %pad: f32) -> vector<4xf32> {
+func.func @subview_static_partial_read_after_write(%init: vector<8xf32>, %v: vector<4xf32>, %pad: f32) -> vector<4xf32> {
%c0 = arith.constant 0 : index
%c1 = arith.constant 1 : index
%a = memref.alloca() : memref<8xf32>
@@ -1004,12 +1004,12 @@ func.func @partial_read_after_alias_write(%init: vector<8xf32>, %v: vector<4xf32
// -----
// Static containment also proves bounds for a whole-slot read.
-// CHECK-LABEL: func.func @whole_read_static_bounds
+// CHECK-LABEL: func.func @whole_buffer_read_static_bounds
// CHECK-SAME: (%[[V:.*]]: vector<8xf32>,
// CHECK-NOT: memref.alloca
// CHECK-NOT: vector.transfer
// CHECK: return %[[V]]
-func.func @whole_read_static_bounds(%v: vector<8xf32>, %pad: f32) -> vector<8xf32> {
+func.func @whole_buffer_read_static_bounds(%v: vector<8xf32>, %pad: f32) -> vector<8xf32> {
%c0 = arith.constant 0 : index
%a = memref.alloca() : memref<8xf32>
vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
@@ -1020,7 +1020,7 @@ func.func @whole_read_static_bounds(%v: vector<8xf32>, %pad: f32) -> vector<8xf3
// -----
// The view may end inside the slice; combine its extent with the read mask.
-// CHECK-LABEL: func.func @partial_read_dynamic_alias_masked
+// CHECK-LABEL: func.func @subview_dynamic_partial_read_with_mask
// CHECK-SAME: (%[[V:.*]]: vector<8xf32>, %[[PAD:.*]]: f32, %[[SIZE:.*]]: index, %[[OPMASK:.*]]: vector<4xi1>)
// CHECK-NOT: memref.alloca
// CHECK-NOT: memref.subview
@@ -1031,7 +1031,7 @@ func.func @whole_read_static_bounds(%v: vector<8xf32>, %pad: f32) -> vector<8xf3
// CHECK: %[[PADDING:.*]] = vector.broadcast %[[PAD]] : f32 to vector<4xf32>
// CHECK: %[[R:.*]] = arith.select %[[MASK]], %[[SLICE]], %[[PADDING]] : vector<4xi1>, vector<4xf32>
// CHECK: return %[[R]]
-func.func @partial_read_dynamic_alias_masked(%v: vector<8xf32>, %pad: f32, %size: index, %mask: vector<4xi1>) -> vector<4xf32> {
+func.func @subview_dynamic_partial_read_with_mask(%v: vector<8xf32>, %pad: f32, %size: index, %mask: vector<4xi1>) -> vector<4xf32> {
%c0 = arith.constant 0 : index
%c2 = arith.constant 2 : index
%a = memref.alloca() : memref<8xf32>
@@ -1043,7 +1043,7 @@ func.func @partial_read_dynamic_alias_masked(%v: vector<8xf32>, %pad: f32, %size
// -----
-// CHECK-LABEL: func.func @partial_read_dynamic_alias_2d
+// CHECK-LABEL: func.func @subview_dynamic_partial_read_2d
// CHECK-SAME: (%[[V:.*]]: vector<4x8xf32>, %[[PAD:.*]]: f32, %[[SIZE:.*]]: index)
// CHECK-NOT: memref.alloca
// CHECK-NOT: memref.subview
@@ -1054,7 +1054,7 @@ func.func @partial_read_dynamic_alias_masked(%v: vector<8xf32>, %pad: f32, %size
// CHECK: %[[PADDING:.*]] = vector.broadcast %[[PAD]] : f32 to vector<2x4xf32>
// CHECK: %[[R:.*]] = arith.select %[[MASK]], %[[SLICE]], %[[PADDING]] : vector<2x4xi1>, vector<2x4xf32>
// CHECK: return %[[R]]
-func.func @partial_read_dynamic_alias_2d(%v: vector<4x8xf32>, %pad: f32, %size: index) -> vector<2x4xf32> {
+func.func @subview_dynamic_partial_read_2d(%v: vector<4x8xf32>, %pad: f32, %size: index) -> vector<2x4xf32> {
%c0 = arith.constant 0 : index
%c1 = arith.constant 1 : index
%c2 = arith.constant 2 : index
@@ -1068,7 +1068,7 @@ func.func @partial_read_dynamic_alias_2d(%v: vector<4x8xf32>, %pad: f32, %size:
// -----
// A constant-zero size still has a dynamic memref type and pads every lane.
-// CHECK-LABEL: func.func @partial_read_empty_dynamic_alias
+// CHECK-LABEL: func.func @subview_dynamic_partial_read_empty_extent
// CHECK-SAME: (%[[V:.*]]: vector<8xf32>, %[[PAD:.*]]: f32)
// CHECK-NOT: memref.alloca
// CHECK-NOT: memref.subview
@@ -1079,7 +1079,7 @@ func.func @partial_read_dynamic_alias_2d(%v: vector<4x8xf32>, %pad: f32, %size:
// CHECK: %[[PADDING:.*]] = vector.broadcast %[[PAD]] : f32 to vector<4xf32>
// CHECK: %[[R:.*]] = arith.select %[[MASK]], %[[SLICE]], %[[PADDING]] : vector<4xi1>, vector<4xf32>
// CHECK: return %[[R]]
-func.func @partial_read_empty_dynamic_alias(%v: vector<8xf32>, %pad: f32) -> vector<4xf32> {
+func.func @subview_dynamic_partial_read_empty_extent(%v: vector<8xf32>, %pad: f32) -> vector<4xf32> {
%c0 = arith.constant 0 : index
%c2 = arith.constant 2 : index
%a = memref.alloca() : memref<8xf32>
@@ -1092,11 +1092,11 @@ func.func @partial_read_empty_dynamic_alias(%v: vector<8xf32>, %pad: f32) -> vec
// -----
// Runtime padding does not allow extracting beyond the parent slot vector.
-// CHECK-LABEL: func.func @partial_read_dynamic_alias_exceeds_parent
+// CHECK-LABEL: func.func @negative_subview_dynamic_partial_read_exceeds_parent
// CHECK: memref.alloca
// CHECK: memref.subview
// CHECK: vector.transfer_read
-func.func @partial_read_dynamic_alias_exceeds_parent(%v: vector<8xf32>, %pad: f32, %size: index) -> vector<4xf32> {
+func.func @negative_subview_dynamic_partial_read_exceeds_parent(%v: vector<8xf32>, %pad: f32, %size: index) -> vector<4xf32> {
%c0 = arith.constant 0 : index
%c6 = arith.constant 6 : index
%a = memref.alloca() : memref<8xf32>
@@ -1109,11 +1109,11 @@ func.func @partial_read_dynamic_alias_exceeds_parent(%v: vector<8xf32>, %pad: f3
// -----
// Nonzero offsets in a dynamic subview are not supported by the alias model.
-// CHECK-LABEL: func.func @partial_read_offset_dynamic_alias
+// CHECK-LABEL: func.func @negative_subview_dynamic_partial_read_nonzero_offset
// CHECK: memref.alloca
// CHECK: memref.subview
// CHECK: vector.transfer_read
-func.func @partial_read_offset_dynamic_alias(%v: vector<8xf32>, %pad: f32, %size: index) -> vector<4xf32> {
+func.func @negative_subview_dynamic_partial_read_nonzero_offset(%v: vector<8xf32>, %pad: f32, %size: index) -> vector<4xf32> {
%c0 = arith.constant 0 : index
%a = memref.alloca() : memref<8xf32>
vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
@@ -1125,12 +1125,12 @@ func.func @partial_read_offset_dynamic_alias(%v: vector<8xf32>, %pad: f32, %size
// -----
// Mem2Reg cannot promote accesses through the vector.mask region.
-// CHECK-LABEL: func.func @partial_read_mask_region
+// CHECK-LABEL: func.func @negative_partial_read_mask_region
// CHECK: memref.alloca
// CHECK: vector.transfer_write
// CHECK: vector.mask
// CHECK-SAME: vector.transfer_read
-func.func @partial_read_mask_region(%v: vector<8xf32>, %pad: f32, %mask: vector<4xi1>) -> vector<4xf32> {
+func.func @negative_partial_read_mask_region(%v: vector<8xf32>, %pad: f32, %mask: vector<4xi1>) -> vector<4xf32> {
%c0 = arith.constant 0 : index
%c2 = arith.constant 2 : index
%a = memref.alloca() : memref<8xf32>
More information about the Mlir-commits
mailing list