[Mlir-commits] [mlir] [mlir][mem2reg] Promote whole-buffer memref to a vector SSA value (PR #211880)

Jianhui Li llvmlistbot at llvm.org
Sun Aug 2 19:43:16 PDT 2026


https://github.com/Jianhui-Li updated https://github.com/llvm/llvm-project/pull/211880

>From 7b298c473292e8ea53c2b398df384713b908bafb Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Fri, 24 Jul 2026 16:54:30 +0000
Subject: [PATCH 1/8] [mlir][mem2reg] Promote whole-buffer memref to vector via
 transfer ops

Mem2Reg previously only promoted memref allocas holding a single element
(to a scalar SSA value). The pass machinery itself is type-agnostic: a
MemorySlot carries an arbitrary elemType, the reaching definition is
threaded at that type, and scf.for already threads any slot type as an
iter_arg/result. The scalar-only behavior lived entirely in
memref.alloca's getPromotableSlots and the load/store type checks.

This teaches Mem2Reg to promote a statically-shaped multi-element memref
whose only accesses are whole-buffer vector.transfer_read/transfer_write
into a single vector SSA value:

- memref::AllocaOp::getPromotableSlots now additionally offers a
  VectorType slot (shape+element type of the memref) for static
  multi-element memrefs with a valid vector element type. This is purely
  additive and self-guarding: any non-whole-buffer access fails its
  canUsesBeRemoved check and promotion of the slot safely aborts.

- vector.transfer_read/transfer_write implement PromotableMemOpInterface
  via an out-of-line ExternalModel (matching how the Vector dialect
  attaches its other interfaces). A transfer is treated as a whole-buffer
  load/store only when it targets the slot pointer with a matching vector
  type, all-zero constant indices, an identity permutation map, all
  in-bounds dims, and no mask -- which also rejects scalable vectors.

No changes to the Mem2Reg pass or SCF are required.

Co-Authored-By: Claude Opus 4.8 <noreply at anthropic.com>
---
 .../Transforms/MemorySlotOpInterfaceImpl.h    |  21 +++
 .../Dialect/MemRef/IR/MemRefMemorySlot.cpp    |  19 +-
 .../Dialect/Vector/Transforms/CMakeLists.txt  |   2 +
 .../Transforms/MemorySlotOpInterfaceImpl.cpp  | 165 +++++++++++++++++
 mlir/lib/RegisterAllDialects.cpp              |   2 +
 mlir/test/Dialect/Vector/mem2reg.mlir         | 172 ++++++++++++++++++
 6 files changed, 377 insertions(+), 4 deletions(-)
 create mode 100644 mlir/include/mlir/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.h
 create mode 100644 mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp
 create mode 100644 mlir/test/Dialect/Vector/mem2reg.mlir

diff --git a/mlir/include/mlir/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.h b/mlir/include/mlir/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.h
new file mode 100644
index 0000000000000..4c2e386728419
--- /dev/null
+++ b/mlir/include/mlir/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.h
@@ -0,0 +1,21 @@
+//===- MemorySlotOpInterfaceImpl.h - Mem2Reg for vector ops -----*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef MLIR_DIALECT_VECTOR_TRANSFORMS_MEMORYSLOTOPINTERFACEIMPL_H
+#define MLIR_DIALECT_VECTOR_TRANSFORMS_MEMORYSLOTOPINTERFACEIMPL_H
+
+namespace mlir {
+
+class DialectRegistry;
+
+namespace vector {
+void registerMemorySlotOpInterfaceExternalModels(DialectRegistry &registry);
+} // namespace vector
+} // namespace mlir
+
+#endif // MLIR_DIALECT_VECTOR_TRANSFORMS_MEMORYSLOTOPINTERFACEIMPL_H
diff --git a/mlir/lib/Dialect/MemRef/IR/MemRefMemorySlot.cpp b/mlir/lib/Dialect/MemRef/IR/MemRefMemorySlot.cpp
index 6748e2cf71804..999abc7354106 100644
--- a/mlir/lib/Dialect/MemRef/IR/MemRefMemorySlot.cpp
+++ b/mlir/lib/Dialect/MemRef/IR/MemRefMemorySlot.cpp
@@ -66,11 +66,22 @@ SmallVector<MemorySlot> memref::AllocaOp::getPromotableSlots() {
   MemRefType type = getType();
   if (!type.hasStaticShape())
     return {};
-  // Make sure the memref contains only a single element.
-  if (type.getNumElements() != 1)
-    return {};
 
-  return {MemorySlot{getResult(), type.getElementType()}};
+  // A single-element memref is promoted to a scalar SSA value.
+  if (type.getNumElements() == 1)
+    return {MemorySlot{getResult(), type.getElementType()}};
+
+  // A multi-element memref can be promoted to a single vector SSA value when it
+  // is only ever accessed as a whole buffer (e.g. through whole-buffer
+  // `vector.transfer_read`/`vector.transfer_write`). Offering the slot is safe
+  // and purely additive: any access that is not a whole-buffer transfer will
+  // fail its `canUsesBeRemoved` check and abort promotion of this slot.
+  if (VectorType::isValidElementType(type.getElementType()))
+    return {MemorySlot{
+        getResult(),
+        VectorType::get(type.getShape(), type.getElementType())}};
+
+  return {};
 }
 
 Value memref::AllocaOp::getDefaultValue(const MemorySlot &slot,
diff --git a/mlir/lib/Dialect/Vector/Transforms/CMakeLists.txt b/mlir/lib/Dialect/Vector/Transforms/CMakeLists.txt
index 112a1db6fe93b..dfe873f1a1b8d 100644
--- a/mlir/lib/Dialect/Vector/Transforms/CMakeLists.txt
+++ b/mlir/lib/Dialect/Vector/Transforms/CMakeLists.txt
@@ -15,6 +15,7 @@ add_mlir_dialect_library(MLIRVectorTransforms
   LowerVectorToFromElementsToShuffleTree.cpp
   LowerVectorTransfer.cpp
   LowerVectorTranspose.cpp
+  MemorySlotOpInterfaceImpl.cpp
   SubsetOpInterfaceImpl.cpp
   VectorDistribute.cpp
   VectorDropLeadUnitDim.cpp
@@ -44,6 +45,7 @@ add_mlir_dialect_library(MLIRVectorTransforms
   MLIRGPUUtils
   MLIRIR
   MLIRLinalgDialect
+  MLIRMemorySlotInterfaces
   MLIRMemRefDialect
   MLIRMemRefTransforms
   MLIRMemRefUtils
diff --git a/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp b/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp
new file mode 100644
index 0000000000000..e911c38542e7a
--- /dev/null
+++ b/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp
@@ -0,0 +1,165 @@
+//===- MemorySlotOpInterfaceImpl.cpp - Mem2Reg for vector ops -------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements Mem2Reg-related interfaces for Vector dialect
+// operations. It allows a memref that is only ever accessed as a whole buffer
+// through `vector.transfer_read`/`vector.transfer_write` to be promoted into a
+// single vector SSA value.
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.h"
+
+#include "mlir/Dialect/Utils/StaticValueUtils.h"
+#include "mlir/Dialect/Vector/IR/VectorOps.h"
+#include "mlir/IR/BuiltinTypes.h"
+#include "mlir/Interfaces/MemorySlotInterfaces.h"
+
+using namespace mlir;
+using namespace mlir::vector;
+
+//===----------------------------------------------------------------------===//
+//  Utilities
+//===----------------------------------------------------------------------===//
+
+/// Returns whether the transfer operation `xferOp` accesses exactly the whole
+/// contents of the memory slot `slot`, so that it can be treated as a plain
+/// whole-buffer load or store during Mem2Reg. `blockingUses` are the uses of
+/// the slot pointer that this operation must stop using for promotion.
+template <typename TransferOpTy>
+static bool
+isWholeBufferTransfer(TransferOpTy xferOp, const MemorySlot &slot,
+                      const SmallPtrSetImpl<OpOperand *> &blockingUses) {
+  // The only blocking use must be the slot pointer itself.
+  if (blockingUses.size() != 1)
+    return false;
+  Value blockingUse = (*blockingUses.begin())->get();
+  if (blockingUse != slot.ptr || xferOp.getBase() != slot.ptr)
+    return false;
+
+  // Only the memref form can access a memref slot. This is already implied by
+  // `getBase() == slot.ptr` above (slot pointers are always memrefs), but guard
+  // defensively against the tensor form.
+  if (!isa<MemRefType>(xferOp.getBase().getType()))
+    return false;
+
+  // The transferred vector must match the slot type exactly. This pins the
+  // rank, per-dimension extent and element type, and rejects scalable vectors
+  // (which never equal the fixed slot type).
+  if (xferOp.getVectorType() != slot.elemType)
+    return false;
+
+  // All indices must be constant zero so the access starts at the buffer
+  // origin in every dimension.
+  for (Value index : xferOp.getIndices()) {
+    std::optional<int64_t> constIndex = getConstantIntValue(index);
+    if (!constIndex || *constIndex != 0)
+      return false;
+  }
+
+  // The permutation map must be the identity: no broadcast, no transpose.
+  if (!xferOp.getPermutationMap().isIdentity())
+    return false;
+
+  // Every dimension must be in bounds so no element lies outside the buffer and
+  // no padding takes effect.
+  if (xferOp.hasOutOfBoundsDim())
+    return false;
+
+  // A mask could disable some elements, making the access partial.
+  if (xferOp.getMask())
+    return false;
+
+  return true;
+}
+
+//===----------------------------------------------------------------------===//
+//  Interface models
+//===----------------------------------------------------------------------===//
+
+namespace {
+
+struct TransferReadOpMemOpModel
+    : public PromotableMemOpInterface::ExternalModel<TransferReadOpMemOpModel,
+                                                     vector::TransferReadOp> {
+  bool loadsFrom(Operation *op, const MemorySlot &slot) const {
+    return cast<vector::TransferReadOp>(op).getBase() == slot.ptr;
+  }
+
+  bool storesTo(Operation *op, const MemorySlot &slot) const { return false; }
+
+  Value getStored(Operation *op, const MemorySlot &slot, OpBuilder &builder,
+                  Value reachingDef, const DataLayout &dataLayout) const {
+    llvm_unreachable("getStored should not be called on TransferReadOp");
+  }
+
+  bool canUsesBeRemoved(Operation *op, const MemorySlot &slot,
+                        const SmallPtrSetImpl<OpOperand *> &blockingUses,
+                        SmallVectorImpl<OpOperand *> &newBlockingUses,
+                        const DataLayout &dataLayout) const {
+    return isWholeBufferTransfer(cast<vector::TransferReadOp>(op), slot,
+                                 blockingUses);
+  }
+
+  DeletionKind removeBlockingUses(
+      Operation *op, const MemorySlot &slot,
+      const SmallPtrSetImpl<OpOperand *> &blockingUses, OpBuilder &builder,
+      Value reachingDefinition, const DataLayout &dataLayout) const {
+    // `canUsesBeRemoved` guaranteed a whole-buffer read of the slot.
+    cast<vector::TransferReadOp>(op).getVector().replaceAllUsesWith(
+        reachingDefinition);
+    return DeletionKind::Delete;
+  }
+};
+
+struct TransferWriteOpMemOpModel
+    : public PromotableMemOpInterface::ExternalModel<TransferWriteOpMemOpModel,
+                                                     vector::TransferWriteOp> {
+  bool loadsFrom(Operation *op, const MemorySlot &slot) const { return false; }
+
+  bool storesTo(Operation *op, const MemorySlot &slot) const {
+    return cast<vector::TransferWriteOp>(op).getBase() == slot.ptr;
+  }
+
+  Value getStored(Operation *op, const MemorySlot &slot, OpBuilder &builder,
+                  Value reachingDef, const DataLayout &dataLayout) const {
+    return cast<vector::TransferWriteOp>(op).getValueToStore();
+  }
+
+  bool canUsesBeRemoved(Operation *op, const MemorySlot &slot,
+                        const SmallPtrSetImpl<OpOperand *> &blockingUses,
+                        SmallVectorImpl<OpOperand *> &newBlockingUses,
+                        const DataLayout &dataLayout) const {
+    auto xferOp = cast<vector::TransferWriteOp>(op);
+    // The stored value must not be the slot pointer itself.
+    if (xferOp.getValueToStore() == slot.ptr)
+      return false;
+    return isWholeBufferTransfer(xferOp, slot, blockingUses);
+  }
+
+  DeletionKind removeBlockingUses(
+      Operation *op, const MemorySlot &slot,
+      const SmallPtrSetImpl<OpOperand *> &blockingUses, OpBuilder &builder,
+      Value reachingDefinition, const DataLayout &dataLayout) const {
+    return DeletionKind::Delete;
+  }
+};
+
+} // namespace
+
+//===----------------------------------------------------------------------===//
+//  Register external models
+//===----------------------------------------------------------------------===//
+
+void mlir::vector::registerMemorySlotOpInterfaceExternalModels(
+    DialectRegistry &registry) {
+  registry.addExtension(+[](MLIRContext *ctx, vector::VectorDialect *dialect) {
+    TransferReadOp::attachInterface<TransferReadOpMemOpModel>(*ctx);
+    TransferWriteOp::attachInterface<TransferWriteOpMemOpModel>(*ctx);
+  });
+}
diff --git a/mlir/lib/RegisterAllDialects.cpp b/mlir/lib/RegisterAllDialects.cpp
index 974b5f533860a..62304ae88b43c 100644
--- a/mlir/lib/RegisterAllDialects.cpp
+++ b/mlir/lib/RegisterAllDialects.cpp
@@ -97,6 +97,7 @@
 #include "mlir/Dialect/Vector/IR/VectorOps.h"
 #include "mlir/Dialect/Vector/Transforms/BufferizableOpInterfaceImpl.h"
 #include "mlir/Dialect/Vector/Transforms/IndexedAccessOpInterfaceImpl.h"
+#include "mlir/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.h"
 #include "mlir/Dialect/Vector/Transforms/SubsetOpInterfaceImpl.h"
 #include "mlir/Dialect/WasmSSA/IR/WasmSSA.h"
 #include "mlir/Dialect/X86/X86Dialect.h"
@@ -200,6 +201,7 @@ void mlir::registerAllDialects(DialectRegistry &registry) {
   tosa::registerShardingInterfaceExternalModels(registry);
   vector::registerBufferizableOpInterfaceExternalModels(registry);
   vector::registerIndexedAccessOpInterfaceExternalModels(registry);
+  vector::registerMemorySlotOpInterfaceExternalModels(registry);
   vector::registerSubsetOpInterfaceExternalModels(registry);
   vector::registerValueBoundsOpInterfaceExternalModels(registry);
   NVVM::registerNVVMTargetInterfaceExternalModels(registry);
diff --git a/mlir/test/Dialect/Vector/mem2reg.mlir b/mlir/test/Dialect/Vector/mem2reg.mlir
new file mode 100644
index 0000000000000..ec6e19d0a5c19
--- /dev/null
+++ b/mlir/test/Dialect/Vector/mem2reg.mlir
@@ -0,0 +1,172 @@
+// RUN: mlir-opt %s --pass-pipeline='builtin.module(func.func(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.
+
+// CHECK-LABEL: func.func @whole_buffer_write_read
+//   CHECK-SAME:   (%[[PAD:.*]]: f32)
+//    CHECK-NOT:   memref.alloca
+//    CHECK-NOT:   vector.transfer_write
+//    CHECK-NOT:   vector.transfer_read
+//        CHECK:   %[[CST:.*]] = arith.constant dense<1.000000e+00> : vector<4xf32>
+//        CHECK:   return %[[CST]] : vector<4xf32>
+func.func @whole_buffer_write_read(%pad: f32) -> vector<4xf32> {
+  %c0 = arith.constant 0 : index
+  %cst = arith.constant dense<1.0> : vector<4xf32>
+  %a = memref.alloca() : memref<4xf32>
+  vector.transfer_write %cst, %a[%c0] {in_bounds = [true]} : vector<4xf32>, memref<4xf32>
+  %r = vector.transfer_read %a[%c0], %pad {in_bounds = [true]} : memref<4xf32>, vector<4xf32>
+  return %r : vector<4xf32>
+}
+
+// -----
+
+// A whole-buffer slot carried across scf.for is threaded as an iter_arg/result.
+
+// CHECK-LABEL: func.func @whole_buffer_in_loop
+//    CHECK-NOT:   memref.alloca
+//    CHECK-NOT:   vector.transfer_write
+//    CHECK-NOT:   vector.transfer_read
+//        CHECK:   %[[RES:.*]] = scf.for {{.*}} iter_args(%[[IT:.*]] = %{{.*}}) -> (vector<4xf32>)
+//        CHECK:     %[[NEXT:.*]] = arith.addf %[[IT]], %[[IT]] : vector<4xf32>
+//        CHECK:     scf.yield %[[NEXT]] : vector<4xf32>
+//        CHECK:   return %[[RES]] : vector<4xf32>
+func.func @whole_buffer_in_loop(%pad: f32, %lb: index, %ub: index, %step: index) -> vector<4xf32> {
+  %c0 = arith.constant 0 : index
+  %cst = arith.constant dense<1.0> : vector<4xf32>
+  %a = memref.alloca() : memref<4xf32>
+  vector.transfer_write %cst, %a[%c0] {in_bounds = [true]} : vector<4xf32>, memref<4xf32>
+  scf.for %i = %lb to %ub step %step {
+    %v = vector.transfer_read %a[%c0], %pad {in_bounds = [true]} : memref<4xf32>, vector<4xf32>
+    %n = arith.addf %v, %v : vector<4xf32>
+    vector.transfer_write %n, %a[%c0] {in_bounds = [true]} : vector<4xf32>, memref<4xf32>
+  }
+  %r = vector.transfer_read %a[%c0], %pad {in_bounds = [true]} : memref<4xf32>, vector<4xf32>
+  return %r : vector<4xf32>
+}
+
+// -----
+
+// A multi-dimensional whole-buffer memref is promoted to a matching vector.
+
+// CHECK-LABEL: func.func @whole_buffer_2d
+//    CHECK-NOT:   memref.alloca
+//    CHECK-NOT:   vector.transfer
+//        CHECK:   return %{{.*}} : vector<2x4xf32>
+func.func @whole_buffer_2d(%pad: f32) -> vector<2x4xf32> {
+  %c0 = arith.constant 0 : index
+  %cst = arith.constant dense<1.0> : vector<2x4xf32>
+  %a = memref.alloca() : memref<2x4xf32>
+  vector.transfer_write %cst, %a[%c0, %c0] {in_bounds = [true, true]} : vector<2x4xf32>, memref<2x4xf32>
+  %r = vector.transfer_read %a[%c0, %c0], %pad {in_bounds = [true, true]} : memref<2x4xf32>, vector<2x4xf32>
+  return %r : vector<2x4xf32>
+}
+
+// -----
+
+// Non-zero access offset: the transfer does not cover the whole buffer, so the
+// slot must NOT be promoted.
+
+// CHECK-LABEL: func.func @negative_nonzero_index
+//        CHECK:   memref.alloca
+//        CHECK:   vector.transfer_write
+//        CHECK:   vector.transfer_read
+func.func @negative_nonzero_index(%pad: f32) -> vector<4xf32> {
+  %c1 = arith.constant 1 : index
+  %cst = arith.constant dense<1.0> : vector<4xf32>
+  %a = memref.alloca() : memref<8xf32>
+  vector.transfer_write %cst, %a[%c1] {in_bounds = [true]} : vector<4xf32>, memref<8xf32>
+  %r = vector.transfer_read %a[%c1], %pad {in_bounds = [true]} : memref<8xf32>, vector<4xf32>
+  return %r : vector<4xf32>
+}
+
+// -----
+
+// A masked transfer only touches part of the buffer: must NOT be promoted.
+
+// CHECK-LABEL: func.func @negative_masked
+//        CHECK:   memref.alloca
+//        CHECK:   vector.transfer_write
+//        CHECK:   vector.transfer_read
+func.func @negative_masked(%pad: f32, %m: vector<4xi1>) -> vector<4xf32> {
+  %c0 = arith.constant 0 : index
+  %cst = arith.constant dense<1.0> : vector<4xf32>
+  %a = memref.alloca() : memref<4xf32>
+  vector.transfer_write %cst, %a[%c0], %m {in_bounds = [true]} : vector<4xf32>, memref<4xf32>
+  %r = vector.transfer_read %a[%c0], %pad {in_bounds = [true]} : memref<4xf32>, vector<4xf32>
+  return %r : vector<4xf32>
+}
+
+// -----
+
+// A partial (out-of-bounds) transfer must NOT be promoted.
+
+// CHECK-LABEL: func.func @negative_out_of_bounds
+//        CHECK:   memref.alloca
+//        CHECK:   vector.transfer_write
+//        CHECK:   vector.transfer_read
+func.func @negative_out_of_bounds(%pad: f32) -> vector<8xf32> {
+  %c0 = arith.constant 0 : index
+  %cst = arith.constant dense<1.0> : vector<8xf32>
+  %a = memref.alloca() : memref<4xf32>
+  vector.transfer_write %cst, %a[%c0] : vector<8xf32>, memref<4xf32>
+  %r = vector.transfer_read %a[%c0], %pad : memref<4xf32>, vector<8xf32>
+  return %r : vector<8xf32>
+}
+
+// -----
+
+// A non-identity (transposing) permutation map is not a whole-buffer identity
+// access: must NOT be promoted.
+
+// CHECK-LABEL: func.func @negative_transpose_map
+//        CHECK:   memref.alloca
+//        CHECK:   vector.transfer_write
+//        CHECK:   vector.transfer_read
+func.func @negative_transpose_map(%pad: f32) -> vector<4x2xf32> {
+  %c0 = arith.constant 0 : index
+  %cst = arith.constant dense<1.0> : vector<2x4xf32>
+  %a = memref.alloca() : memref<2x4xf32>
+  vector.transfer_write %cst, %a[%c0, %c0] {in_bounds = [true, true]} : vector<2x4xf32>, memref<2x4xf32>
+  %r = vector.transfer_read %a[%c0, %c0], %pad {in_bounds = [true, true], permutation_map = affine_map<(d0, d1) -> (d1, d0)>} : memref<2x4xf32>, vector<4x2xf32>
+  return %r : vector<4x2xf32>
+}
+
+// -----
+
+// An alloca also accessed through a scalar memref.load cannot be promoted to a
+// vector: must NOT be promoted.
+
+// CHECK-LABEL: func.func @negative_mixed_scalar_access
+//        CHECK:   memref.alloca
+//        CHECK:   vector.transfer_write
+//        CHECK:   vector.transfer_read
+//        CHECK:   memref.load
+func.func @negative_mixed_scalar_access(%pad: f32) -> (vector<4xf32>, f32) {
+  %c0 = arith.constant 0 : index
+  %cst = arith.constant dense<1.0> : vector<4xf32>
+  %a = memref.alloca() : memref<4xf32>
+  vector.transfer_write %cst, %a[%c0] {in_bounds = [true]} : vector<4xf32>, memref<4xf32>
+  %r = vector.transfer_read %a[%c0], %pad {in_bounds = [true]} : memref<4xf32>, vector<4xf32>
+  %s = memref.load %a[%c0] : memref<4xf32>
+  return %r, %s : vector<4xf32>, f32
+}
+
+// -----
+
+// A scalable vector never equals the fixed-size slot type: must NOT be
+// promoted.
+
+// CHECK-LABEL: func.func @negative_scalable
+//        CHECK:   memref.alloca
+//        CHECK:   vector.transfer_write
+//        CHECK:   vector.transfer_read
+func.func @negative_scalable(%pad: f32) -> vector<[4]xf32> {
+  %c0 = arith.constant 0 : index
+  %cst = arith.constant dense<1.0> : vector<[4]xf32>
+  %a = memref.alloca() : memref<4xf32>
+  vector.transfer_write %cst, %a[%c0] {in_bounds = [true]} : vector<[4]xf32>, memref<4xf32>
+  %r = vector.transfer_read %a[%c0], %pad {in_bounds = [true]} : memref<4xf32>, vector<[4]xf32>
+  return %r : vector<[4]xf32>
+}

>From 24c11489338115c200a48d648a8787901c4bb604 Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Fri, 24 Jul 2026 17:40:45 +0000
Subject: [PATCH 2/8] [mlir] Simplify comments and drop dead self-store guard
 in vector mem2reg

Trim verbose comments in the whole-buffer transfer promotion logic and
remove the unreachable self-store check in TransferWriteOp's
canUsesBeRemoved: a transfer_write's stored value is always a vector and
the slot pointer always a memref, so they can never be the same Value.

Co-Authored-By: Claude Opus 4.8 <noreply at anthropic.com>
---
 .../Dialect/MemRef/IR/MemRefMemorySlot.cpp    |  4 +--
 .../Transforms/MemorySlotOpInterfaceImpl.cpp  | 36 +++++++------------
 2 files changed, 14 insertions(+), 26 deletions(-)

diff --git a/mlir/lib/Dialect/MemRef/IR/MemRefMemorySlot.cpp b/mlir/lib/Dialect/MemRef/IR/MemRefMemorySlot.cpp
index 999abc7354106..59798a65f72e8 100644
--- a/mlir/lib/Dialect/MemRef/IR/MemRefMemorySlot.cpp
+++ b/mlir/lib/Dialect/MemRef/IR/MemRefMemorySlot.cpp
@@ -73,9 +73,7 @@ SmallVector<MemorySlot> memref::AllocaOp::getPromotableSlots() {
 
   // A multi-element memref can be promoted to a single vector SSA value when it
   // is only ever accessed as a whole buffer (e.g. through whole-buffer
-  // `vector.transfer_read`/`vector.transfer_write`). Offering the slot is safe
-  // and purely additive: any access that is not a whole-buffer transfer will
-  // fail its `canUsesBeRemoved` check and abort promotion of this slot.
+  // `vector.transfer_read`/`vector.transfer_write`).
   if (VectorType::isValidElementType(type.getElementType()))
     return {MemorySlot{
         getResult(),
diff --git a/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp b/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp
index e911c38542e7a..1789c59da0720 100644
--- a/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp
@@ -27,51 +27,43 @@ using namespace mlir::vector;
 //  Utilities
 //===----------------------------------------------------------------------===//
 
-/// Returns whether the transfer operation `xferOp` accesses exactly the whole
-/// contents of the memory slot `slot`, so that it can be treated as a plain
-/// whole-buffer load or store during Mem2Reg. `blockingUses` are the uses of
-/// the slot pointer that this operation must stop using for promotion.
+/// Returns whether `xferOp` accesses exactly the whole contents of `slot`, so
+/// it can act as a plain whole-buffer load/store during Mem2Reg.
 template <typename TransferOpTy>
 static bool
 isWholeBufferTransfer(TransferOpTy xferOp, const MemorySlot &slot,
                       const SmallPtrSetImpl<OpOperand *> &blockingUses) {
-  // The only blocking use must be the slot pointer itself.
+  // The sole blocking use must be the slot pointer as the transfer's base.
   if (blockingUses.size() != 1)
     return false;
   Value blockingUse = (*blockingUses.begin())->get();
   if (blockingUse != slot.ptr || xferOp.getBase() != slot.ptr)
     return false;
 
-  // Only the memref form can access a memref slot. This is already implied by
-  // `getBase() == slot.ptr` above (slot pointers are always memrefs), but guard
-  // defensively against the tensor form.
+  // Reject the tensor form (already implied, since slot pointers are memrefs).
   if (!isa<MemRefType>(xferOp.getBase().getType()))
     return false;
 
-  // The transferred vector must match the slot type exactly. This pins the
-  // rank, per-dimension extent and element type, and rejects scalable vectors
-  // (which never equal the fixed slot type).
+  // Exact type match pins rank/extents/element type and rejects scalable vectors.
   if (xferOp.getVectorType() != slot.elemType)
     return false;
 
-  // All indices must be constant zero so the access starts at the buffer
-  // origin in every dimension.
+  // Access must start at the buffer origin in every dimension.
   for (Value index : xferOp.getIndices()) {
     std::optional<int64_t> constIndex = getConstantIntValue(index);
     if (!constIndex || *constIndex != 0)
       return false;
   }
 
-  // The permutation map must be the identity: no broadcast, no transpose.
+  // Identity map: no broadcast or transpose.
   if (!xferOp.getPermutationMap().isIdentity())
     return false;
 
-  // Every dimension must be in bounds so no element lies outside the buffer and
-  // no padding takes effect.
+  // All dimensions in bounds: no out-of-buffer element, no padding.
   if (xferOp.hasOutOfBoundsDim())
     return false;
 
-  // A mask could disable some elements, making the access partial.
+  // A mask would make the access partial.
   if (xferOp.getMask())
     return false;
 
@@ -110,7 +102,7 @@ struct TransferReadOpMemOpModel
       Operation *op, const MemorySlot &slot,
       const SmallPtrSetImpl<OpOperand *> &blockingUses, OpBuilder &builder,
       Value reachingDefinition, const DataLayout &dataLayout) const {
-    // `canUsesBeRemoved` guaranteed a whole-buffer read of the slot.
+    // Whole-buffer read: replace the loaded vector with the reaching definition.
     cast<vector::TransferReadOp>(op).getVector().replaceAllUsesWith(
         reachingDefinition);
     return DeletionKind::Delete;
@@ -135,11 +127,9 @@ struct TransferWriteOpMemOpModel
                         const SmallPtrSetImpl<OpOperand *> &blockingUses,
                         SmallVectorImpl<OpOperand *> &newBlockingUses,
                         const DataLayout &dataLayout) const {
-    auto xferOp = cast<vector::TransferWriteOp>(op);
-    // The stored value must not be the slot pointer itself.
-    if (xferOp.getValueToStore() == slot.ptr)
-      return false;
-    return isWholeBufferTransfer(xferOp, slot, blockingUses);
+    // No self-store guard needed: a vector value can never equal a memref slot.
+    return isWholeBufferTransfer(cast<vector::TransferWriteOp>(op), slot,
+                                 blockingUses);
   }
 
   DeletionKind removeBlockingUses(

>From 1075f1a57a3eff4effb6071b00574a1f9980efc4 Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Fri, 24 Jul 2026 17:44:13 +0000
Subject: [PATCH 3/8] [mlir] clang-format vector mem2reg files

Co-Authored-By: Claude Opus 4.8 <noreply at anthropic.com>
---
 .../Dialect/MemRef/IR/MemRefMemorySlot.cpp    |  3 +--
 .../Transforms/MemorySlotOpInterfaceImpl.cpp  | 24 +++++++++++--------
 2 files changed, 15 insertions(+), 12 deletions(-)

diff --git a/mlir/lib/Dialect/MemRef/IR/MemRefMemorySlot.cpp b/mlir/lib/Dialect/MemRef/IR/MemRefMemorySlot.cpp
index 59798a65f72e8..17b1417550524 100644
--- a/mlir/lib/Dialect/MemRef/IR/MemRefMemorySlot.cpp
+++ b/mlir/lib/Dialect/MemRef/IR/MemRefMemorySlot.cpp
@@ -76,8 +76,7 @@ SmallVector<MemorySlot> memref::AllocaOp::getPromotableSlots() {
   // `vector.transfer_read`/`vector.transfer_write`).
   if (VectorType::isValidElementType(type.getElementType()))
     return {MemorySlot{
-        getResult(),
-        VectorType::get(type.getShape(), type.getElementType())}};
+        getResult(), VectorType::get(type.getShape(), type.getElementType())}};
 
   return {};
 }
diff --git a/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp b/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp
index 1789c59da0720..352983ba153fa 100644
--- a/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp
@@ -44,7 +44,8 @@ isWholeBufferTransfer(TransferOpTy xferOp, const MemorySlot &slot,
   if (!isa<MemRefType>(xferOp.getBase().getType()))
     return false;
 
-  // Exact type match pins rank/extents/element type and rejects scalable vectors.
+  // Exact type match pins rank/extents/element type and rejects scalable
+  // vectors.
   if (xferOp.getVectorType() != slot.elemType)
     return false;
 
@@ -98,11 +99,13 @@ struct TransferReadOpMemOpModel
                                  blockingUses);
   }
 
-  DeletionKind removeBlockingUses(
-      Operation *op, const MemorySlot &slot,
-      const SmallPtrSetImpl<OpOperand *> &blockingUses, OpBuilder &builder,
-      Value reachingDefinition, const DataLayout &dataLayout) const {
-    // Whole-buffer read: replace the loaded vector with the reaching definition.
+  DeletionKind
+  removeBlockingUses(Operation *op, const MemorySlot &slot,
+                     const SmallPtrSetImpl<OpOperand *> &blockingUses,
+                     OpBuilder &builder, Value reachingDefinition,
+                     const DataLayout &dataLayout) const {
+    // Whole-buffer read: replace the loaded vector with the reaching
+    // definition.
     cast<vector::TransferReadOp>(op).getVector().replaceAllUsesWith(
         reachingDefinition);
     return DeletionKind::Delete;
@@ -132,10 +135,11 @@ struct TransferWriteOpMemOpModel
                                  blockingUses);
   }
 
-  DeletionKind removeBlockingUses(
-      Operation *op, const MemorySlot &slot,
-      const SmallPtrSetImpl<OpOperand *> &blockingUses, OpBuilder &builder,
-      Value reachingDefinition, const DataLayout &dataLayout) const {
+  DeletionKind
+  removeBlockingUses(Operation *op, const MemorySlot &slot,
+                     const SmallPtrSetImpl<OpOperand *> &blockingUses,
+                     OpBuilder &builder, Value reachingDefinition,
+                     const DataLayout &dataLayout) const {
     return DeletionKind::Delete;
   }
 };

>From 9699ca7b9767d5a28cb79aa6aa4439bca9a86895 Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Sat, 25 Jul 2026 02:45:38 +0000
Subject: [PATCH 4/8] add test

---
 mlir/test/Dialect/Vector/mem2reg.mlir | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/mlir/test/Dialect/Vector/mem2reg.mlir b/mlir/test/Dialect/Vector/mem2reg.mlir
index ec6e19d0a5c19..7b2f0bd75ec31 100644
--- a/mlir/test/Dialect/Vector/mem2reg.mlir
+++ b/mlir/test/Dialect/Vector/mem2reg.mlir
@@ -170,3 +170,23 @@ func.func @negative_scalable(%pad: f32) -> vector<[4]xf32> {
   %r = vector.transfer_read %a[%c0], %pad {in_bounds = [true]} : memref<4xf32>, vector<[4]xf32>
   return %r : vector<[4]xf32>
 }
+
+// -----
+
+// The buffer has a use (subview) that mem2reg does not recognize as a
+// removable load or store, so the slot is left untouched: must NOT be promoted.
+
+// CHECK-LABEL: func.func @negative_subview
+//        CHECK:   memref.alloca
+//        CHECK:   vector.transfer_write
+//        CHECK:   memref.subview
+//        CHECK:   vector.transfer_read
+func.func @negative_subview(%pad: f32) -> vector<4xf32> {
+  %c0 = arith.constant 0 : index
+  %cst = arith.constant dense<1.0> : vector<8xf32>
+  %a = memref.alloca() : memref<8xf32>
+  vector.transfer_write %cst, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+  %sv = memref.subview %a[0] [4] [1] : memref<8xf32> to memref<4xf32, strided<[1]>>
+  %r = vector.transfer_read %sv[%c0], %pad {in_bounds = [true]} : memref<4xf32, strided<[1]>>, vector<4xf32>
+  return %r : vector<4xf32>
+}

>From a5346dafe9a769d209dc12e5f3ac3e47d094016d Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Sat, 1 Aug 2026 01:40:03 +0000
Subject: [PATCH 5/8] [mlir][mem2reg] Address PR review feedback

- Replace the isWholeBufferTransfer template with the common
  VectorTransferOpInterface (adam-smnk).
- Simplify the lit RUN line to --mem2reg (adam-smnk).
- Reword the mask comment ("would" -> "could") and expand the
  in-bounds/padding comment (rengolin).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply at anthropic.com>
---
 .../Transforms/MemorySlotOpInterfaceImpl.cpp       | 14 ++++++++------
 mlir/test/Dialect/Vector/mem2reg.mlir              |  2 +-
 2 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp b/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp
index 352983ba153fa..cace1f9042898 100644
--- a/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp
@@ -29,9 +29,8 @@ using namespace mlir::vector;
 
 /// Returns whether `xferOp` accesses exactly the whole contents of `slot`, so
 /// it can act as a plain whole-buffer load/store during Mem2Reg.
-template <typename TransferOpTy>
 static bool
-isWholeBufferTransfer(TransferOpTy xferOp, const MemorySlot &slot,
+isWholeBufferTransfer(VectorTransferOpInterface xferOp, const MemorySlot &slot,
                       const SmallPtrSetImpl<OpOperand *> &blockingUses) {
   // The sole blocking use must be the slot pointer as the transfer's base.
   if (blockingUses.size() != 1)
@@ -60,11 +59,14 @@ isWholeBufferTransfer(TransferOpTy xferOp, const MemorySlot &slot,
   if (!xferOp.getPermutationMap().isIdentity())
     return false;
 
-  // All dimensions in bounds: no out-of-buffer element, no padding.
+  // All dimensions must be in bounds. An out-of-bounds dimension means the
+  // transfer reaches past the buffer, so a read would materialize padding
+  // rather than buffer contents and a write would only cover part of the
+  // buffer: in neither case does the transfer stand in for the whole slot.
   if (xferOp.hasOutOfBoundsDim())
     return false;
 
-  // A mask would make the access partial.
+  // A mask could make the access partial.
   if (xferOp.getMask())
     return false;
 
@@ -95,7 +97,7 @@ struct TransferReadOpMemOpModel
                         const SmallPtrSetImpl<OpOperand *> &blockingUses,
                         SmallVectorImpl<OpOperand *> &newBlockingUses,
                         const DataLayout &dataLayout) const {
-    return isWholeBufferTransfer(cast<vector::TransferReadOp>(op), slot,
+    return isWholeBufferTransfer(cast<VectorTransferOpInterface>(op), slot,
                                  blockingUses);
   }
 
@@ -131,7 +133,7 @@ struct TransferWriteOpMemOpModel
                         SmallVectorImpl<OpOperand *> &newBlockingUses,
                         const DataLayout &dataLayout) const {
     // No self-store guard needed: a vector value can never equal a memref slot.
-    return isWholeBufferTransfer(cast<vector::TransferWriteOp>(op), slot,
+    return isWholeBufferTransfer(cast<VectorTransferOpInterface>(op), slot,
                                  blockingUses);
   }
 
diff --git a/mlir/test/Dialect/Vector/mem2reg.mlir b/mlir/test/Dialect/Vector/mem2reg.mlir
index 7b2f0bd75ec31..8578f6bae905d 100644
--- a/mlir/test/Dialect/Vector/mem2reg.mlir
+++ b/mlir/test/Dialect/Vector/mem2reg.mlir
@@ -1,4 +1,4 @@
-// RUN: mlir-opt %s --pass-pipeline='builtin.module(func.func(mem2reg))' --split-input-file | FileCheck %s
+// 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

>From 19a839c205a8b831617e9b869bb44530ec7a82fd Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Sat, 1 Aug 2026 02:48:13 +0000
Subject: [PATCH 6/8] [mlir][mem2reg] Add negative test for dynamic-shape
 memref

A dynamic-shape memref never yields a promotable slot (its extents are
not known statically, so it cannot map to a fixed-size vector), so the
buffer is left untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply at anthropic.com>
---
 mlir/test/Dialect/Vector/mem2reg.mlir | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/mlir/test/Dialect/Vector/mem2reg.mlir b/mlir/test/Dialect/Vector/mem2reg.mlir
index 8578f6bae905d..96ea2bc952414 100644
--- a/mlir/test/Dialect/Vector/mem2reg.mlir
+++ b/mlir/test/Dialect/Vector/mem2reg.mlir
@@ -190,3 +190,22 @@ func.func @negative_subview(%pad: f32) -> vector<4xf32> {
   %r = vector.transfer_read %sv[%c0], %pad {in_bounds = [true]} : memref<4xf32, strided<[1]>>, vector<4xf32>
   return %r : vector<4xf32>
 }
+
+// -----
+
+// A dynamic-shape memref never yields a promotable slot (its extents are not
+// known statically, so it cannot map to a fixed-size vector): must NOT be
+// promoted.
+
+// CHECK-LABEL: func.func @negative_dynamic_shape
+//        CHECK:   memref.alloca
+//        CHECK:   vector.transfer_write
+//        CHECK:   vector.transfer_read
+func.func @negative_dynamic_shape(%pad: f32, %d: index) -> vector<4xf32> {
+  %c0 = arith.constant 0 : index
+  %cst = arith.constant dense<1.0> : vector<4xf32>
+  %a = memref.alloca(%d) : memref<?xf32>
+  vector.transfer_write %cst, %a[%c0] {in_bounds = [true]} : vector<4xf32>, memref<?xf32>
+  %r = vector.transfer_read %a[%c0], %pad {in_bounds = [true]} : memref<?xf32>, vector<4xf32>
+  return %r : vector<4xf32>
+}

>From d6d450e210654c35b1a5729a20e2efa2db6b8aec Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Sun, 2 Aug 2026 18:16:13 +0000
Subject: [PATCH 7/8] [mlir][mem2reg] Promote memref through static subview
 sub-slices

Extends the vector whole-buffer promotion so a memref accessed via a static,
same-rank `memref.subview` still promotes to a vector SSA value. The subview is
exposed as a sub-slice alias of the parent slot via `PromotableAliaserInterface`:

  * reads of the subview project the parent value through
    `vector.extract_strided_slice`,
  * writes into the subview reconstruct the parent value through
    `vector.insert_strided_slice` into the current reaching definition (so
    partial and overlapping writes compose in program order).

Eligibility is gated to what strided-slice projection can represent: fully
static offsets, unit strides, no rank reduction (result rank == source rank),
and a vector-promotable element type. Dynamic offsets, rank-reducing subviews,
and masked/partial sub-accesses are left unpromoted.

The model attaches to `memref.subview` but lives in the Vector transforms unit
because the projections build Vector ops (Vector already depends on MemRef).

Verified by execution that the promoted form matches the memref semantics,
including overlapping subview writes. Replaces the former negative_subview test
(which documented the old limitation) with positive read/write cases and
negative cases for the rejected shapes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply at anthropic.com>
---
 .../Transforms/MemorySlotOpInterfaceImpl.cpp  | 175 +++++++++++++++-
 mlir/test/Dialect/Vector/mem2reg.mlir         | 190 ++++++++++++++++--
 2 files changed, 343 insertions(+), 22 deletions(-)

diff --git a/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp b/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp
index cace1f9042898..757a3b14db526 100644
--- a/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp
@@ -6,15 +6,33 @@
 //
 //===----------------------------------------------------------------------===//
 //
-// This file implements Mem2Reg-related interfaces for Vector dialect
-// operations. It allows a memref that is only ever accessed as a whole buffer
-// through `vector.transfer_read`/`vector.transfer_write` to be promoted into a
-// single vector SSA value.
+// This file implements Mem2Reg-related interfaces that let a statically-shaped
+// memref be promoted into a single vector SSA value, provided every access to
+// the buffer is a whole-buffer read or write (or a whole-sub-region access of
+// such a buffer via a subview). With these models, Mem2Reg replaces the memory
+// slot with a vector value, threading it as the reaching definition:
+//
+//   * `vector.transfer_read` of the whole buffer becomes a use of the current
+//     vector value; `vector.transfer_write` of the whole buffer becomes a new
+//     definition of it (see the `PromotableMemOpInterface` models below).
+//
+//   * a static, same-rank `memref.subview` is exposed as a promotable sub-slice
+//     alias of the buffer's slot (via `PromotableAliaserInterface`): a read of
+//     the subview projects out of the vector value with
+//     `vector.extract_strided_slice`, and a write into it composes back into the
+//     value with `vector.insert_strided_slice`. This lets a buffer that is only
+//     ever accessed through static subviews promote as well, with partial and
+//     overlapping sub-writes composing in program order.
+//
+// Accesses that are not whole-(sub-)buffer -- dynamic offsets, rank-reducing or
+// non-unit-stride subviews, masked or partial transfers, non-zero transfer
+// indices -- are left untouched, so the buffer is not promoted.
 //
 //===----------------------------------------------------------------------===//
 
 #include "mlir/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.h"
 
+#include "mlir/Dialect/MemRef/IR/MemRef.h"
 #include "mlir/Dialect/Utils/StaticValueUtils.h"
 #include "mlir/Dialect/Vector/IR/VectorOps.h"
 #include "mlir/IR/BuiltinTypes.h"
@@ -148,6 +166,149 @@ struct TransferWriteOpMemOpModel
 
 } // namespace
 
+//===----------------------------------------------------------------------===//
+//  memref.subview aliaser
+//===----------------------------------------------------------------------===//
+
+/// Returns the offsets of `subView` as a static, contiguous, same-rank slice of
+/// its source, or nullopt if the subview is not promotable as a whole-buffer
+/// sub-slice. Promotion projects the parent buffer's vector value through
+/// `vector.extract_strided_slice` / `insert_strided_slice`, which require:
+///   * fully static offsets and sizes,
+///   * unit strides,
+///   * no rank reduction (result rank == source rank),
+/// so a dropped or dynamic dimension disqualifies the subview.
+static std::optional<SmallVector<int64_t>>
+getPromotableSubViewOffsets(memref::SubViewOp subView) {
+  auto srcType = dyn_cast<MemRefType>(subView.getSource().getType());
+  auto resType = dyn_cast<MemRefType>(subView.getResult().getType());
+  if (!srcType || !resType || !srcType.hasStaticShape() ||
+      !resType.hasStaticShape())
+    return std::nullopt;
+
+  // No rank reduction: extract/insert_strided_slice operate at a single rank.
+  if (srcType.getRank() != resType.getRank())
+    return std::nullopt;
+
+  // Unit strides only.
+  for (OpFoldResult stride : subView.getMixedStrides()) {
+    std::optional<int64_t> s = getConstantIntValue(stride);
+    if (!s || *s != 1)
+      return std::nullopt;
+  }
+
+  // Static offsets.
+  SmallVector<int64_t> offsets;
+  for (OpFoldResult offset : subView.getMixedOffsets()) {
+    std::optional<int64_t> o = getConstantIntValue(offset);
+    if (!o)
+      return std::nullopt;
+    offsets.push_back(*o);
+  }
+
+  // Static sizes (already implied by the result's static shape, but the sizes
+  // must match the result shape so the slice covers exactly the subview).
+  for (auto [size, dim] :
+       llvm::zip_equal(subView.getMixedSizes(), resType.getShape())) {
+    std::optional<int64_t> s = getConstantIntValue(size);
+    if (!s || *s != dim)
+      return std::nullopt;
+  }
+  return offsets;
+}
+
+namespace {
+
+/// Exposes a static, same-rank `memref.subview` as a sub-slice alias of a
+/// whole-buffer vector slot. Reads of the subview become
+/// `vector.extract_strided_slice` of the parent value; writes become
+/// `vector.insert_strided_slice` into the current reaching definition.
+struct SubViewOpAliasModel
+    : public PromotableAliaserInterface::ExternalModel<SubViewOpAliasModel,
+                                                       memref::SubViewOp> {
+  void getPromotableSlotAliases(Operation *op,
+                                OpOperand &aliasedSlotPointerOperand,
+                                const MemorySlot &parentSlot,
+                                SmallVectorImpl<MemorySlot> &newSlots) const {
+    auto subView = cast<memref::SubViewOp>(op);
+    if (aliasedSlotPointerOperand.get() != subView.getSource())
+      return;
+
+    // The parent slot must promote to a vector (whole-buffer promotion). A
+    // scalar (single-element) parent slot cannot be sliced.
+    auto parentVecType = dyn_cast<VectorType>(parentSlot.elemType);
+    if (!parentVecType)
+      return;
+
+    if (!getPromotableSubViewOffsets(subView))
+      return;
+
+    // The alias's value type is the sub-vector matching the subview's shape.
+    auto resType = cast<MemRefType>(subView.getResult().getType());
+    if (!VectorType::isValidElementType(resType.getElementType()))
+      return;
+    VectorType aliasVecType =
+        VectorType::get(resType.getShape(), resType.getElementType());
+    newSlots.push_back(MemorySlot{subView.getResult(), aliasVecType});
+  }
+
+  Value projectSlotValueToAliasValue(Operation *op,
+                                     OpOperand & /*aliasedSlotPointerOperand*/,
+                                     const MemorySlot & /*parentSlot*/,
+                                     const MemorySlot &aliasSlot,
+                                     Value slotValue, OpBuilder &builder) const {
+    auto subView = cast<memref::SubViewOp>(op);
+    SmallVector<int64_t> offsets = *getPromotableSubViewOffsets(subView);
+    auto aliasVecType = cast<VectorType>(aliasSlot.elemType);
+    SmallVector<int64_t> strides(offsets.size(), 1);
+    return vector::ExtractStridedSliceOp::create(
+               builder, op->getLoc(), slotValue, offsets,
+               aliasVecType.getShape(), strides)
+        .getResult();
+  }
+
+  Value projectAliasValueToSlotValue(Operation *op,
+                                     OpOperand & /*aliasedSlotPointerOperand*/,
+                                     const MemorySlot & /*parentSlot*/,
+                                     const MemorySlot & /*aliasSlot*/,
+                                     Value aliasValue, Value reachingDef,
+                                     OpBuilder &builder) const {
+    auto subView = cast<memref::SubViewOp>(op);
+    SmallVector<int64_t> offsets = *getPromotableSubViewOffsets(subView);
+    SmallVector<int64_t> strides(offsets.size(), 1);
+    return vector::InsertStridedSliceOp::create(builder, op->getLoc(),
+                                                aliasValue, reachingDef, offsets,
+                                                strides)
+        .getResult();
+  }
+};
+
+/// Companion `PromotableOpInterface` model: once the slot is promoted, the
+/// subview has no remaining memory uses and is erased.
+struct SubViewOpPromotableModel
+    : public PromotableOpInterface::ExternalModel<SubViewOpPromotableModel,
+                                                  memref::SubViewOp> {
+  bool canUsesBeRemoved(Operation *op,
+                        const SmallPtrSetImpl<OpOperand *> &blockingUses,
+                        SmallVectorImpl<OpOperand *> &newBlockingUses,
+                        const DataLayout &dataLayout) const {
+    // The subview result is itself a blocking use of the parent slot; its own
+    // users (the transfers) are resolved through the alias projections.
+    for (OpOperand &use : op->getResult(0).getUses())
+      newBlockingUses.push_back(&use);
+    return true;
+  }
+
+  DeletionKind
+  removeBlockingUses(Operation *op,
+                     const SmallPtrSetImpl<OpOperand *> &blockingUses,
+                     OpBuilder &builder) const {
+    return DeletionKind::Delete;
+  }
+};
+
+} // namespace
+
 //===----------------------------------------------------------------------===//
 //  Register external models
 //===----------------------------------------------------------------------===//
@@ -158,4 +319,10 @@ void mlir::vector::registerMemorySlotOpInterfaceExternalModels(
     TransferReadOp::attachInterface<TransferReadOpMemOpModel>(*ctx);
     TransferWriteOp::attachInterface<TransferWriteOpMemOpModel>(*ctx);
   });
+  // The subview aliaser attaches to a MemRef op but lives here because the
+  // projections build Vector ops; Vector already depends on MemRef.
+  registry.addExtension(+[](MLIRContext *ctx, memref::MemRefDialect *dialect) {
+    memref::SubViewOp::attachInterface<SubViewOpAliasModel>(*ctx);
+    memref::SubViewOp::attachInterface<SubViewOpPromotableModel>(*ctx);
+  });
 }
diff --git a/mlir/test/Dialect/Vector/mem2reg.mlir b/mlir/test/Dialect/Vector/mem2reg.mlir
index 96ea2bc952414..15ef6c18723c2 100644
--- a/mlir/test/Dialect/Vector/mem2reg.mlir
+++ b/mlir/test/Dialect/Vector/mem2reg.mlir
@@ -173,24 +173,6 @@ func.func @negative_scalable(%pad: f32) -> vector<[4]xf32> {
 
 // -----
 
-// The buffer has a use (subview) that mem2reg does not recognize as a
-// removable load or store, so the slot is left untouched: must NOT be promoted.
-
-// CHECK-LABEL: func.func @negative_subview
-//        CHECK:   memref.alloca
-//        CHECK:   vector.transfer_write
-//        CHECK:   memref.subview
-//        CHECK:   vector.transfer_read
-func.func @negative_subview(%pad: f32) -> vector<4xf32> {
-  %c0 = arith.constant 0 : index
-  %cst = arith.constant dense<1.0> : vector<8xf32>
-  %a = memref.alloca() : memref<8xf32>
-  vector.transfer_write %cst, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
-  %sv = memref.subview %a[0] [4] [1] : memref<8xf32> to memref<4xf32, strided<[1]>>
-  %r = vector.transfer_read %sv[%c0], %pad {in_bounds = [true]} : memref<4xf32, strided<[1]>>, vector<4xf32>
-  return %r : vector<4xf32>
-}
-
 // -----
 
 // A dynamic-shape memref never yields a promotable slot (its extents are not
@@ -209,3 +191,175 @@ func.func @negative_dynamic_shape(%pad: f32, %d: index) -> vector<4xf32> {
   %r = vector.transfer_read %a[%c0], %pad {in_bounds = [true]} : memref<?xf32>, vector<4xf32>
   return %r : vector<4xf32>
 }
+
+// -----
+
+// A static, same-rank memref.subview is a promotable sub-slice alias: a write
+// into the subview becomes vector.insert_strided_slice into the buffer's value,
+// and the whole-buffer read returns that composed value.
+
+// CHECK-LABEL: func.func @subview_static_write
+//   CHECK-SAME:   (%[[V:.*]]: vector<4xf32>, %[[INIT:.*]]: vector<8xf32>, %[[PAD:.*]]: f32)
+//    CHECK-NOT:   memref.alloca
+//    CHECK-NOT:   memref.subview
+//    CHECK-NOT:   vector.transfer_write
+//    CHECK-NOT:   vector.transfer_read
+//        CHECK:   %[[INS:.*]] = vector.insert_strided_slice %[[V]], %[[INIT]] {offsets = [2], strides = [1]}
+//        CHECK:   return %[[INS]] : vector<8xf32>
+func.func @subview_static_write(%v: vector<4xf32>, %init: vector<8xf32>, %pad: f32) -> vector<8xf32> {
+  %c0 = arith.constant 0 : 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[%c0], %pad {in_bounds = [true]} : memref<8xf32>, vector<8xf32>
+  return %r : vector<8xf32>
+}
+
+// -----
+
+// A read of a static subview becomes vector.extract_strided_slice of the value.
+
+// CHECK-LABEL: func.func @subview_static_read
+//    CHECK-NOT:   memref.alloca
+//    CHECK-NOT:   memref.subview
+//        CHECK:   %[[EXT:.*]] = vector.extract_strided_slice %{{.*}} {offsets = [2], sizes = [4], strides = [1]}
+//        CHECK:   return %[[EXT]] : vector<4xf32>
+func.func @subview_static_read(%init: vector<8xf32>, %pad: f32) -> vector<4xf32> {
+  %c0 = arith.constant 0 : 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>>
+  %r = vector.transfer_read %sv[%c0], %pad {in_bounds = [true]} : memref<4xf32, strided<[1], offset: 2>>, vector<4xf32>
+  return %r : vector<4xf32>
+}
+
+// -----
+
+// A buffer accessed *only* through subviews (no whole-buffer transfer) still
+// promotes: the slot comes from the alloca, not from any transfer. The memref,
+// the subviews, and the transfers are all removed; the written slice is
+// inserted into the buffer value and read back out. (canonicalize/cse would
+// then fold this to a plain forward of the written vector.)
+
+// CHECK-LABEL: func.func @subview_only_write_read
+//   CHECK-SAME:   (%[[V:.*]]: vector<4xf32>, %[[PAD:.*]]: f32)
+//    CHECK-NOT:   memref.alloca
+//    CHECK-NOT:   memref.subview
+//    CHECK-NOT:   vector.transfer_write
+//    CHECK-NOT:   vector.transfer_read
+//        CHECK:   vector.insert_strided_slice %[[V]], %{{.*}} {offsets = [0], strides = [1]}
+func.func @subview_only_write_read(%v: vector<4xf32>, %pad: f32) -> vector<4xf32> {
+  %c0 = arith.constant 0 : index
+  %a = memref.alloca() : memref<8xf32>
+  %svW = memref.subview %a[0] [4] [1] : memref<8xf32> to memref<4xf32, strided<[1]>>
+  vector.transfer_write %v, %svW[%c0] {in_bounds = [true]} : vector<4xf32>, memref<4xf32, strided<[1]>>
+  %svR = memref.subview %a[0] [4] [1] : memref<8xf32> to memref<4xf32, strided<[1]>>
+  %r = vector.transfer_read %svR[%c0], %pad {in_bounds = [true]} : memref<4xf32, strided<[1]>>, vector<4xf32>
+  return %r : vector<4xf32>
+}
+
+// -----
+
+// Two disjoint subview writes that together cover the buffer, followed by a
+// subview read spanning the boundary between them. The read composes both
+// writes through the parent value: it returns the low half of the first write
+// and the high half of the second (i.e. insert both slices, then extract).
+
+// CHECK-LABEL: func.func @subview_disjoint_writes_boundary_read
+//   CHECK-SAME:   (%[[VA:.*]]: vector<4xf32>, %[[VB:.*]]: vector<4xf32>, %[[PAD:.*]]: f32)
+//    CHECK-NOT:   memref.alloca
+//    CHECK-NOT:   memref.subview
+//        CHECK:   %[[D0:.*]] = vector.insert_strided_slice %[[VA]], %{{.*}} {offsets = [0], strides = [1]}
+//        CHECK:   %[[D1:.*]] = vector.insert_strided_slice %[[VB]], %[[D0]] {offsets = [4], strides = [1]}
+//        CHECK:   %[[R:.*]] = vector.extract_strided_slice %[[D1]] {offsets = [2], sizes = [4], strides = [1]}
+//        CHECK:   return %[[R]] : vector<4xf32>
+func.func @subview_disjoint_writes_boundary_read(%vA: vector<4xf32>, %vB: vector<4xf32>, %pad: f32) -> vector<4xf32> {
+  %c0 = arith.constant 0 : index
+  %a = memref.alloca() : memref<8xf32>
+  %s0 = memref.subview %a[0] [4] [1] : memref<8xf32> to memref<4xf32, strided<[1]>>
+  %s4 = memref.subview %a[4] [4] [1] : memref<8xf32> to memref<4xf32, strided<[1], offset: 4>>
+  %s2 = memref.subview %a[2] [4] [1] : memref<8xf32> to memref<4xf32, strided<[1], offset: 2>>
+  vector.transfer_write %vA, %s0[%c0] {in_bounds = [true]} : vector<4xf32>, memref<4xf32, strided<[1]>>
+  vector.transfer_write %vB, %s4[%c0] {in_bounds = [true]} : vector<4xf32>, memref<4xf32, strided<[1], offset: 4>>
+  %r = vector.transfer_read %s2[%c0], %pad {in_bounds = [true]} : memref<4xf32, strided<[1], offset: 2>>, vector<4xf32>
+  return %r : vector<4xf32>
+}
+
+// -----
+
+// Reads and writes may occur in any order: a read of the subview before any
+// write returns the slot's default value (ub.poison), matching the semantics of
+// reading an uninitialized alloca. A later read observes the written value.
+
+// CHECK-LABEL: func.func @subview_read_before_write
+//   CHECK-SAME:   (%[[V:.*]]: vector<4xf32>, %[[PAD:.*]]: f32)
+//    CHECK-NOT:   memref.alloca
+//    CHECK-NOT:   memref.subview
+//        CHECK:   %[[POISON:.*]] = ub.poison : vector<8xf32>
+//        CHECK:   %[[R0:.*]] = vector.extract_strided_slice %[[POISON]] {offsets = [0], sizes = [4], strides = [1]}
+//        CHECK:   %[[INS:.*]] = vector.insert_strided_slice %[[V]], %[[POISON]] {offsets = [0], strides = [1]}
+//        CHECK:   %[[R1:.*]] = vector.extract_strided_slice %[[INS]] {offsets = [0], sizes = [4], strides = [1]}
+//        CHECK:   return %[[R0]], %[[R1]] : vector<4xf32>, vector<4xf32>
+func.func @subview_read_before_write(%v: vector<4xf32>, %pad: f32) -> (vector<4xf32>, vector<4xf32>) {
+  %c0 = arith.constant 0 : index
+  %a = memref.alloca() : memref<8xf32>
+  %s = memref.subview %a[0] [4] [1] : memref<8xf32> to memref<4xf32, strided<[1]>>
+  %r0 = vector.transfer_read %s[%c0], %pad {in_bounds = [true]} : memref<4xf32, strided<[1]>>, vector<4xf32>
+  vector.transfer_write %v, %s[%c0] {in_bounds = [true]} : vector<4xf32>, memref<4xf32, strided<[1]>>
+  %r1 = vector.transfer_read %s[%c0], %pad {in_bounds = [true]} : memref<4xf32, strided<[1]>>, vector<4xf32>
+  return %r0, %r1 : vector<4xf32>, vector<4xf32>
+}
+
+// -----
+
+// A dynamic subview offset cannot be expressed as a static strided slice: the
+// buffer is left untouched.
+
+// CHECK-LABEL: func.func @no_promote_subview_dynamic_offset
+//        CHECK:   memref.alloca
+//        CHECK:   memref.subview
+func.func @no_promote_subview_dynamic_offset(%v: vector<4xf32>, %init: vector<8xf32>, %pad: f32, %off: index) -> vector<8xf32> {
+  %c0 = arith.constant 0 : index
+  %a = memref.alloca() : memref<8xf32>
+  vector.transfer_write %init, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+  %sv = memref.subview %a[%off] [4] [1] : memref<8xf32> to memref<4xf32, strided<[1], offset: ?>>
+  vector.transfer_write %v, %sv[%c0] {in_bounds = [true]} : vector<4xf32>, memref<4xf32, strided<[1], offset: ?>>
+  %r = vector.transfer_read %a[%c0], %pad {in_bounds = [true]} : memref<8xf32>, vector<8xf32>
+  return %r : vector<8xf32>
+}
+
+// -----
+
+// A rank-reducing subview (2d -> 1d) is not promotable: strided-slice projection
+// requires equal rank.
+
+// CHECK-LABEL: func.func @no_promote_subview_rank_reducing
+//        CHECK:   memref.alloca
+//        CHECK:   memref.subview
+func.func @no_promote_subview_rank_reducing(%v: vector<4xf32>, %init: vector<2x4xf32>, %pad: f32) -> vector<2x4xf32> {
+  %c0 = arith.constant 0 : index
+  %a = memref.alloca() : memref<2x4xf32>
+  vector.transfer_write %init, %a[%c0, %c0] {in_bounds = [true, true]} : vector<2x4xf32>, memref<2x4xf32>
+  %sv = memref.subview %a[1, 0] [1, 4] [1, 1] : memref<2x4xf32> to memref<4xf32, strided<[1], offset: 4>>
+  vector.transfer_write %v, %sv[%c0] {in_bounds = [true]} : vector<4xf32>, memref<4xf32, strided<[1], offset: 4>>
+  %r = vector.transfer_read %a[%c0, %c0], %pad {in_bounds = [true, true]} : memref<2x4xf32>, vector<2x4xf32>
+  return %r : vector<2x4xf32>
+}
+
+// -----
+
+// A masked write into the subview is not a whole-sub-region access: not promoted.
+
+// CHECK-LABEL: func.func @no_promote_subview_masked
+//        CHECK:   memref.alloca
+//        CHECK:   memref.subview
+func.func @no_promote_subview_masked(%v: vector<4xf32>, %init: vector<8xf32>, %pad: f32, %m: vector<4xi1>) -> vector<8xf32> {
+  %c0 = arith.constant 0 : 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], %m {in_bounds = [true]} : vector<4xf32>, memref<4xf32, strided<[1], offset: 2>>
+  %r = vector.transfer_read %a[%c0], %pad {in_bounds = [true]} : memref<8xf32>, vector<8xf32>
+  return %r : vector<8xf32>
+}

>From 17f54c83ef4ba8c61d8b1138953feb919a34174c Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Mon, 3 Aug 2026 02:36:06 +0000
Subject: [PATCH 8/8] [mlir][mem2reg] Test subview promotion across scf.for
 iterations

Adds a test where a buffer allocated before an scf.for is accessed inside the
loop body only through a subview, with a cross-iteration dependence (each
iteration reads the value the previous iteration wrote). The subview aliaser
composes with region promotion: the whole buffer is carried across iterations
as a vector iter_arg, and the in-body read/write become
extract/insert_strided_slice on that value. Verified by execution.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply at anthropic.com>
---
 mlir/test/Dialect/Vector/mem2reg.mlir | 36 +++++++++++++++++++++++++--
 1 file changed, 34 insertions(+), 2 deletions(-)

diff --git a/mlir/test/Dialect/Vector/mem2reg.mlir b/mlir/test/Dialect/Vector/mem2reg.mlir
index 15ef6c18723c2..f145907d6938f 100644
--- a/mlir/test/Dialect/Vector/mem2reg.mlir
+++ b/mlir/test/Dialect/Vector/mem2reg.mlir
@@ -173,8 +173,6 @@ func.func @negative_scalable(%pad: f32) -> vector<[4]xf32> {
 
 // -----
 
-// -----
-
 // A dynamic-shape memref never yields a promotable slot (its extents are not
 // known statically, so it cannot map to a fixed-size vector): must NOT be
 // promoted.
@@ -288,6 +286,40 @@ func.func @subview_disjoint_writes_boundary_read(%vA: vector<4xf32>, %vB: vector
 
 // -----
 
+// A buffer allocated before an scf.for and accessed inside the loop body only
+// through a subview, with a cross-iteration dependence (each iteration reads the
+// value the previous iteration wrote). The subview aliaser composes with region
+// promotion: the whole buffer is carried across iterations as a vector iter_arg,
+// the in-body read/write become extract/insert_strided_slice on that value.
+
+// CHECK-LABEL: func.func @subview_in_scf_for_cross_iter
+//    CHECK-NOT:   memref.alloca
+//    CHECK-NOT:   memref.subview
+//    CHECK-NOT:   vector.transfer_write
+//    CHECK-NOT:   vector.transfer_read
+//        CHECK:   %[[R:.*]] = scf.for %{{.*}} iter_args(%[[IT:.*]] = %{{.*}}) -> (vector<8xf32>)
+//        CHECK:     %[[V:.*]] = vector.extract_strided_slice %[[IT]] {offsets = [0], sizes = [4], strides = [1]}
+//        CHECK:     %[[N:.*]] = arith.addf %[[V]], %[[V]]
+//        CHECK:     %[[INS:.*]] = vector.insert_strided_slice %[[N]], %[[IT]] {offsets = [0], strides = [1]}
+//        CHECK:     scf.yield %[[INS]] : vector<8xf32>
+//        CHECK:   vector.extract_strided_slice %[[R]] {offsets = [0], sizes = [4], strides = [1]}
+func.func @subview_in_scf_for_cross_iter(%lb: index, %ub: index, %step: index, %init: vector<8xf32>, %pad: f32) -> vector<4xf32> {
+  %c0 = arith.constant 0 : index
+  %a = memref.alloca() : memref<8xf32>
+  vector.transfer_write %init, %a[%c0] {in_bounds = [true]} : vector<8xf32>, memref<8xf32>
+  scf.for %i = %lb to %ub step %step {
+    %sv = memref.subview %a[0] [4] [1] : memref<8xf32> to memref<4xf32, strided<[1]>>
+    %v = vector.transfer_read %sv[%c0], %pad {in_bounds = [true]} : memref<4xf32, strided<[1]>>, vector<4xf32>
+    %n = arith.addf %v, %v : vector<4xf32>
+    vector.transfer_write %n, %sv[%c0] {in_bounds = [true]} : vector<4xf32>, memref<4xf32, strided<[1]>>
+  }
+  %svr = memref.subview %a[0] [4] [1] : memref<8xf32> to memref<4xf32, strided<[1]>>
+  %r = vector.transfer_read %svr[%c0], %pad {in_bounds = [true]} : memref<4xf32, strided<[1]>>, vector<4xf32>
+  return %r : vector<4xf32>
+}
+
+// -----
+
 // Reads and writes may occur in any order: a read of the subview before any
 // write returns the slot's default value (ub.poison), matching the semantics of
 // reading an uninitialized alloca. A later read observes the written value.



More information about the Mlir-commits mailing list