[Mlir-commits] [mlir] [mlir][memref][mem2reg] Support expand_shape and collapse_shape aliases (PR #232535)
Jianhui Li
llvmlistbot at llvm.org
Thu Oct 15 15:52:20 PDT 2026
https://github.com/Jianhui-Li created https://github.com/llvm/llvm-project/pull/232535
Extend MLIR Mem2Reg to promote buffers accessed through memref.expand_shape and memref.collapse_shape (deferred from PR#217793). Reshaped views become alias slots whose values are converted with vector.shape_cast.
Support composition with static and dynamic subviews, reshaping dynamic subview masks to preserve read padding and elements outside write bounds. Reject reshapes that cannot preserve the element mapping or count.
Validation: test landed under vector Mem2Reg test files, but will refactor as we land PR#232078.
assisted-by-claude
>From 53ffa154599ebb61788ae3d7f845efcf8dbd5fa0 Mon Sep 17 00:00:00 2001
From: Jianhui Li <jian.hui.li at intel.com>
Date: Thu, 15 Oct 2026 21:03:46 +0000
Subject: [PATCH] [mlir][mem2reg] Support reassociated views of promotable
buffers
Bring back the `memref.expand_shape` / `memref.collapse_shape` models deferred
from #217793 in ccaf73d3df87, now that that PR has landed. A reshape holds the
same elements in a different shape, so it is exposed as an alias slot whose two
projections are both a `vector.shape_cast`.
What makes that cast faithful is one rule, shared by both ops. Within a
reassociation group, the view's extents must equal the ones its alias takes,
except for one: the extent the mask bounds, where the view is smaller than its
parent at runtime. Every extent ahead of that one must be unit, so its index is
always 0 and the differing extent -- a factor in its stride -- never shifts an
address; every extent behind it must be equal, being a factor of the differing
dimension's own stride. So a group carrying a runtime extent reads
`[unit, ..., unit, dynamic, static, ..., static]`.
`getShapeCastableAliasShape` derives the alias shape and checks that rule in a
single pass:
* a collapse multiplies the parent slot's extents group by group;
* an expansion spends one parent extent innermost dimension first, taking the
view's own extent at each step and handing the remainder to the first
dynamic dimension it reaches, so the shape always comes out fully static.
The shape comes from the parent slot's extents rather than from the result
memref, so that a reshape of a dynamically-shaped view is typeable at all.
Nothing here consults a memref layout. With the tile promoted to a vector the
question is only whether `vector.shape_cast` models the reshape, and the extents
answer it: a static source's alias takes the view's own shape -- built from it by
`AllocaOp::getPromotableSlots`, by `isAliasableStaticShapeSubView`, or by the
derivation above -- so the cast reorders the view's own lanes however the source
is laid out. `neg_expand_shape_strided_subview` therefore becomes
`expand_shape_strided_subview`: a `[4, 8]` view of a `memref<8x16xf32>` keeps
strides `[16, 1]`, yet promotes. Its result was checked against the unpromoted
program by execution over a buffer of distinct values.
Reshapes compose with the subview aliases, so a reshaped dynamic subview
promotes as well: `skipReassociativeReshapes` looks through them to recover the
extent, and the mask is built once in the parent's shape and cast to the slot's.
The two exported queries take the vocabulary of a partial view rather than of a
subview, so the Vector transfer models no longer name the kind of view that
produced their slot.
`SubViewOpPromotableModel` becomes `ViewOpPromotableModel<OpTy>` again, as all
three view ops need it.
Restores fourteen of the sixteen reshape tests; the two that promote through
`memref.copy` wait on that support. `expand_shape_dyn_subview` reused one value
as both the subview extent and the expanded row count, which no runtime extent
satisfies, so it takes a separate one now, like
`collapse_then_expand_dyn_subview`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply at anthropic.com>
---
.../Transforms/MemorySlotOpInterfaceImpl.h | 18 +-
.../Transforms/MemorySlotOpInterfaceImpl.cpp | 271 +++++++++++++++---
.../Transforms/MemorySlotOpInterfaceImpl.cpp | 11 +-
.../Vector/mem2reg-dynamic-subview.mlir | 184 ++++++++++++
mlir/test/Dialect/Vector/mem2reg.mlir | 152 ++++++++++
5 files changed, 589 insertions(+), 47 deletions(-)
diff --git a/mlir/include/mlir/Dialect/MemRef/Transforms/MemorySlotOpInterfaceImpl.h b/mlir/include/mlir/Dialect/MemRef/Transforms/MemorySlotOpInterfaceImpl.h
index cb94ccf1a3541b..5ecd6572e55844 100644
--- a/mlir/include/mlir/Dialect/MemRef/Transforms/MemorySlotOpInterfaceImpl.h
+++ b/mlir/include/mlir/Dialect/MemRef/Transforms/MemorySlotOpInterfaceImpl.h
@@ -9,7 +9,7 @@
#ifndef MLIR_DIALECT_MEMREF_TRANSFORMS_MEMORYSLOTOPINTERFACEIMPL_H
#define MLIR_DIALECT_MEMREF_TRANSFORMS_MEMORYSLOTOPINTERFACEIMPL_H
-#include "mlir/IR/Value.h"
+#include "mlir/Interfaces/MemorySlotInterfaces.h"
namespace mlir {
class DialectRegistry;
@@ -19,13 +19,17 @@ class OpBuilder;
namespace memref {
void registerMemorySlotOpInterfaceExternalModels(DialectRegistry ®istry);
-/// Returns whether `slotPtr` is a dynamic `memref.subview` that can be promoted
-/// as an alias of the whole parent vector.
-bool isDynamicSubViewSlot(Value slotPtr);
+/// Returns whether `slot` represents a supported dynamic `memref.subview`,
+/// optionally followed by `memref.expand_shape` or `memref.collapse_shape`.
+/// Such a slot retains the whole parent vector, so accesses must mask out
+/// elements outside the subview's runtime bounds.
+bool isPartialViewSlot(const MemorySlot &slot);
-/// Builds a mask for the subview's valid region in the parent vector, or
-/// returns null if `isDynamicSubViewSlot(slotPtr)` is false.
-Value buildDynamicSubViewMask(OpBuilder &builder, Location loc, Value slotPtr);
+/// Builds a mask of the subview's runtime bounds in the shape of
+/// `slot.valueType`, reshaping the mask if needed. Returns a null value for
+/// slots that do not require a subview mask. Expects a promotable vector slot.
+Value buildPartialViewMask(OpBuilder &builder, Location loc,
+ const MemorySlot &slot);
} // namespace memref
} // namespace mlir
diff --git a/mlir/lib/Dialect/MemRef/Transforms/MemorySlotOpInterfaceImpl.cpp b/mlir/lib/Dialect/MemRef/Transforms/MemorySlotOpInterfaceImpl.cpp
index c562c67433f470..04c4cca765e3d7 100644
--- a/mlir/lib/Dialect/MemRef/Transforms/MemorySlotOpInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/MemRef/Transforms/MemorySlotOpInterfaceImpl.cpp
@@ -8,13 +8,13 @@
//
// This file implements memory slot interface models used by Mem2Reg to promote
// a memref to a single vector SSA value. `PromotableAliaserInterface` exposes
-// `memref.subview` aliases, and `PromotableOpInterface` removes the subviews
-// after promotion. Vector transfer ops are handled by the models in the
-// Vector dialect.
+// `memref.subview`, `memref.expand_shape` and `memref.collapse_shape` aliases,
+// and `PromotableOpInterface` removes those views after promotion. Vector
+// transfer ops are handled by the models in the Vector dialect.
//
// A memory slot pairs a memref with the vector type used to represent its
// contents. Promotion replaces accesses to the slot with uses and definitions
-// of that vector value. All uses, including those through subviews, must be
+// of that vector value. All uses, including those through views, must be
// supported by the models.
//
// The accesses are rewritten as follows:
@@ -31,12 +31,18 @@
// accesses to the subview's extent. The subview must start at the
// parent's origin.
//
-// Subviews require a statically shaped parent buffer. A dynamically
-// shaped buffer can instead promote to a 1-D scalable vector through
-// whole-buffer transfers, but subviews are not supported.
+// * A supported `memref.expand_shape` or `memref.collapse_shape` reshapes
+// the parent slot's vector with `vector.shape_cast` in both directions.
+// For a dynamic subview, the alias retains all elements of the parent
+// vector; the subview mask is cast to the alias shape when reading.
+//
+// Views require a statically shaped parent buffer. A dynamically shaped buffer
+// can instead promote to a 1-D scalable vector through whole-buffer transfers,
+// but views are not supported.
//
// Unsupported accesses, such as dynamic subview offsets, rank reduction,
-// non-unit subview strides, or non-zero transfer indices, prevent promotion.
+// non-unit subview strides, reshapes that cannot be represented by
+// `vector.shape_cast`, or non-zero transfer indices, prevent promotion.
//
//===----------------------------------------------------------------------===//
@@ -130,54 +136,175 @@ static VectorType getWholeParentVectorType(memref::SubViewOp subView) {
return VectorType::get(parentType.getShape(), parentType.getElementType());
}
-/// If `slotPtr` is a dynamic subview, returns it.
-static memref::SubViewOp getDynamicSubView(Value slotPtr) {
- auto subView = slotPtr.getDefiningOp<memref::SubViewOp>();
- if (subView && isAliasableDynamicShapeSubView(subView))
- return subView;
- return {};
+/// Follows the sources of `memref.expand_shape` and `memref.collapse_shape` to
+/// find the underlying value. Used to locate a dynamic subview whose bounds
+/// still apply after reshaping.
+static Value skipReassociativeReshapes(Value viewPtr) {
+ while (Operation *def = viewPtr.getDefiningOp()) {
+ if (auto expand = dyn_cast<memref::ExpandShapeOp>(def))
+ viewPtr = expand.getSrc();
+ else if (auto collapse = dyn_cast<memref::CollapseShapeOp>(def))
+ viewPtr = collapse.getSrc();
+ else
+ break;
+ }
+ return viewPtr;
}
-/// Builds the mask of `subView`'s valid region: a `vector.create_mask` of the
-/// subview's sizes, in the shape of the whole parent that its alias holds.
+/// Builds a mask from the subview's sizes in the parent vector's shape, then
+/// casts it to `vecType`'s shape if needed. Supported reshapes preserve element
+/// order, so a single cast also handles a chain of reshapes.
static Value buildSubViewMask(OpBuilder &builder, Location loc,
- memref::SubViewOp subView) {
+ memref::SubViewOp subView, VectorType vecType) {
VectorType parentVecType = getWholeParentVectorType(subView);
SmallVector<Value> bounds =
getValueOrCreateConstantIndexOp(builder, loc, subView.getMixedSizes());
- return vector::CreateMaskOp::create(
+ Value mask = vector::CreateMaskOp::create(
builder, loc,
VectorType::get(parentVecType.getShape(), builder.getI1Type()), bounds);
+ if (parentVecType.getShape() != vecType.getShape())
+ mask = vector::ShapeCastOp::create(
+ builder, loc, VectorType::get(vecType.getShape(), builder.getI1Type()),
+ mask);
+ return mask;
}
-bool mlir::memref::isDynamicSubViewSlot(Value slotPtr) {
- return static_cast<bool>(getDynamicSubView(slotPtr));
+/// Checks whether a reassociation group preserves the mapping of valid view
+/// elements to vector lanes. `viewExtents` are the memref dimensions;
+/// `aliasExtents` are the corresponding static vector dimensions.
+///
+/// Leading unit dimensions contribute no offset. The first non-unit dimension
+/// may differ because its extent does not affect its own row-major stride.
+/// All subsequent extents must match to keep that stride unchanged. Thus a
+/// supported group with a dynamic extent has the form
+/// `[1, ..., 1, dynamic, static, ..., static]`.
+static bool shapeCastModelsGroup(ArrayRef<int64_t> viewExtents,
+ ArrayRef<int64_t> aliasExtents) {
+ assert(viewExtents.size() == aliasExtents.size() && "group size mismatch");
+ // Leading unit dimensions always have index zero.
+ size_t pos = 0;
+ while (pos < viewExtents.size() && viewExtents[pos] == 1)
+ ++pos;
+ // Allow the first non-unit extent to differ, but require its stride factors
+ // (all following extents) to match.
+ if (pos < viewExtents.size())
+ ++pos;
+ for (; pos < viewExtents.size(); ++pos)
+ if (viewExtents[pos] != aliasExtents[pos])
+ return false;
+ return true;
+}
+
+/// Computes the collapsed alias shape by multiplying the extents from
+/// `parentSlotShape` in each reassociation group. These are the parent slot's
+/// static vector extents, even when the source memref has dynamic dimensions.
+/// Fails if a group cannot be represented by `vector.shape_cast`.
+static FailureOr<SmallVector<int64_t>>
+getShapeCastableAliasShape(memref::CollapseShapeOp op,
+ ArrayRef<int64_t> parentSlotShape) {
+ ArrayRef<int64_t> viewShape = op.getSrcType().getShape();
+ // Reassociation indices must address both the memref and vector shapes.
+ if (viewShape.size() != parentSlotShape.size())
+ return failure();
+
+ SmallVector<int64_t> aliasShape;
+ for (ReassociationIndices group : op.getReassociationIndices()) {
+ SmallVector<int64_t> viewExtents, aliasExtents;
+ for (int64_t dim : group) {
+ viewExtents.push_back(viewShape[dim]);
+ aliasExtents.push_back(parentSlotShape[dim]);
+ }
+ if (!shapeCastModelsGroup(viewExtents, aliasExtents))
+ return failure();
+ aliasShape.push_back(ShapedType::getNumElements(aliasExtents));
+ }
+ if (ShapedType::getNumElements(aliasShape) !=
+ ShapedType::getNumElements(parentSlotShape))
+ return failure();
+ return aliasShape;
}
-Value mlir::memref::buildDynamicSubViewMask(OpBuilder &builder, Location loc,
- Value slotPtr) {
- memref::SubViewOp subView = getDynamicSubView(slotPtr);
- if (!subView)
+/// Computes a static expanded alias shape from `parentSlotShape`, the parent
+/// slot's static vector extents. For each reassociation group, divides the
+/// corresponding extent from `parentSlotShape` by the static result extents,
+/// starting at the innermost dimension. The first dynamic dimension receives
+/// the remaining extent; dimensions before it remain 1.
+///
+/// Fails if the parent extent cannot be evenly divided among the static result
+/// dimensions, or if a group fails `shapeCastModelsGroup`. A group with
+/// dynamic dimensions must have the form
+/// `[1, ..., 1, dynamic, static, ..., static]`.
+static FailureOr<SmallVector<int64_t>>
+getShapeCastableAliasShape(memref::ExpandShapeOp op,
+ ArrayRef<int64_t> parentSlotShape) {
+ ArrayRef<int64_t> viewShape = op.getType().getShape();
+ SmallVector<ReassociationIndices, 4> groups = op.getReassociationIndices();
+ if (groups.size() != parentSlotShape.size())
+ return failure();
+
+ SmallVector<int64_t> aliasShape(viewShape.size(), 1);
+ for (auto [srcDim, group] : llvm::enumerate(groups)) {
+ int64_t remaining = parentSlotShape[srcDim];
+ for (int64_t dim : llvm::reverse(group)) {
+ if (ShapedType::isDynamic(viewShape[dim])) {
+ aliasShape[dim] = remaining;
+ break;
+ }
+ if (viewShape[dim] == 0 || remaining % viewShape[dim] != 0)
+ return failure();
+ aliasShape[dim] = viewShape[dim];
+ remaining /= viewShape[dim];
+ }
+
+ SmallVector<int64_t> viewExtents, aliasExtents;
+ for (int64_t dim : group) {
+ viewExtents.push_back(viewShape[dim]);
+ aliasExtents.push_back(aliasShape[dim]);
+ }
+ if (!shapeCastModelsGroup(viewExtents, aliasExtents))
+ return failure();
+ }
+ if (ShapedType::getNumElements(aliasShape) !=
+ ShapedType::getNumElements(parentSlotShape))
+ return failure();
+ return aliasShape;
+}
+
+bool mlir::memref::isPartialViewSlot(const MemorySlot &slot) {
+ auto subView =
+ skipReassociativeReshapes(slot.ptr).getDefiningOp<memref::SubViewOp>();
+ return subView && isAliasableDynamicShapeSubView(subView);
+}
+
+Value mlir::memref::buildPartialViewMask(OpBuilder &builder, Location loc,
+ const MemorySlot &slot) {
+ auto subView =
+ skipReassociativeReshapes(slot.ptr).getDefiningOp<memref::SubViewOp>();
+ // Static subview slots contain only the selected slice and need no mask.
+ // Dynamic subviews reaching here have already passed alias eligibility
+ // checks when their slots were created.
+ if (!subView || subView.getType().hasStaticShape())
return {};
- return buildSubViewMask(builder, loc, subView);
+ return buildSubViewMask(builder, loc, subView,
+ cast<VectorType>(slot.valueType));
}
namespace {
//===----------------------------------------------------------------------===//
-// PromotableOpInterface model for memref.subview
+// PromotableOpInterface model for the memref view ops
//===----------------------------------------------------------------------===//
-/// `PromotableOpInterface` model for `memref.subview`. Works with the
-/// `PromotableAliaserInterface` model below to remove the subview once its
-/// result's uses have been promoted.
-struct SubViewOpPromotableModel
- : public PromotableOpInterface::ExternalModel<SubViewOpPromotableModel,
- memref::SubViewOp> {
+/// Removes `memref.subview`, `memref.expand_shape`, and `memref.collapse_shape`
+/// once all uses of their results have been promoted.
+template <typename OpTy>
+struct ViewOpPromotableModel
+ : public PromotableOpInterface::ExternalModel<ViewOpPromotableModel<OpTy>,
+ OpTy> {
bool canUsesBeRemoved(Operation *op,
const SmallPtrSetImpl<OpOperand *> &blockingUses,
SmallVectorImpl<OpOperand *> &newBlockingUses,
const DataLayout &dataLayout) const {
- // Removing the subview requires removing all uses of its memref result.
+ // Removing the view requires removing all uses of its memref result.
for (OpOperand &use : op->getResult(0).getUses())
newBlockingUses.push_back(&use);
return true;
@@ -272,7 +399,8 @@ struct SubViewOpAliasModel
// of the parent value untouched.
if (isAliasableDynamicShapeSubView(subView)) {
Location loc = op->getLoc();
- Value mask = buildSubViewMask(builder, loc, subView);
+ VectorType parentVecType = getWholeParentVectorType(subView);
+ Value mask = buildSubViewMask(builder, loc, subView, parentVecType);
return arith::SelectOp::create(builder, loc, mask, aliasValue,
reachingDef);
}
@@ -284,6 +412,70 @@ struct SubViewOpAliasModel
.getResult();
}
};
+
+//===----------------------------------------------------------------------===//
+// PromotableAliaserInterface model for the memref reshape ops
+//===----------------------------------------------------------------------===//
+
+/// Exposes supported `memref.expand_shape` and `memref.collapse_shape` ops as
+/// aliases of vector slots. Both projections use `vector.shape_cast`, which
+/// preserves element order. `getShapeCastableAliasShape` checks eligibility
+/// and derives the alias shape from the parent vector's static extents.
+template <typename OpTy>
+struct ReassociativeViewOpAliasModel
+ : public PromotableAliaserInterface::ExternalModel<
+ ReassociativeViewOpAliasModel<OpTy>, OpTy> {
+ void getPromotableSlotAliases(Operation *op,
+ OpOperand &aliasedSlotPointerOperand,
+ const MemorySlot &parentSlot,
+ SmallVectorImpl<MemorySlot> &newSlots) const {
+ auto viewOp = cast<OpTy>(op);
+ // Called once per operand holding the slot pointer; only the reshaped
+ // source exposes an alias.
+ if (aliasedSlotPointerOperand.get() != viewOp.getSrc())
+ return;
+
+ // Only fixed-size vector slots support these shape casts.
+ auto parentVecType = dyn_cast<VectorType>(parentSlot.valueType);
+ if (!parentVecType || parentVecType.isScalable())
+ return;
+
+ auto resType = cast<MemRefType>(viewOp.getResult().getType());
+ if (!VectorType::isValidElementType(resType.getElementType()) ||
+ resType.getElementType() != parentVecType.getElementType())
+ return;
+ FailureOr<SmallVector<int64_t>> aliasShape =
+ getShapeCastableAliasShape(viewOp, parentVecType.getShape());
+ if (failed(aliasShape))
+ return;
+ auto aliasVecType = VectorType::get(*aliasShape, resType.getElementType());
+ newSlots.push_back(MemorySlot{viewOp.getResult(), aliasVecType});
+ }
+
+ Value projectSlotValueToAliasValue(Operation *op,
+ OpOperand & /*aliasedSlotPointerOperand*/,
+ const MemorySlot & /*parentSlot*/,
+ const MemorySlot &aliasSlot,
+ Value slotValue,
+ OpBuilder &builder) const {
+ return vector::ShapeCastOp::create(builder, op->getLoc(),
+ cast<VectorType>(aliasSlot.valueType),
+ slotValue)
+ .getResult();
+ }
+
+ Value projectAliasValueToSlotValue(Operation *op,
+ OpOperand & /*aliasedSlotPointerOperand*/,
+ const MemorySlot &parentSlot,
+ const MemorySlot & /*aliasSlot*/,
+ Value aliasValue, Value /*reachingDef*/,
+ OpBuilder &builder) const {
+ return vector::ShapeCastOp::create(builder, op->getLoc(),
+ cast<VectorType>(parentSlot.valueType),
+ aliasValue)
+ .getResult();
+ }
+};
} // namespace
//===----------------------------------------------------------------------===//
@@ -294,6 +486,15 @@ void mlir::memref::registerMemorySlotOpInterfaceExternalModels(
DialectRegistry ®istry) {
registry.addExtension(+[](MLIRContext *ctx, memref::MemRefDialect *dialect) {
memref::SubViewOp::attachInterface<SubViewOpAliasModel>(*ctx);
- memref::SubViewOp::attachInterface<SubViewOpPromotableModel>(*ctx);
+ memref::SubViewOp::attachInterface<
+ ViewOpPromotableModel<memref::SubViewOp>>(*ctx);
+ memref::ExpandShapeOp::attachInterface<
+ ReassociativeViewOpAliasModel<memref::ExpandShapeOp>>(*ctx);
+ memref::ExpandShapeOp::attachInterface<
+ ViewOpPromotableModel<memref::ExpandShapeOp>>(*ctx);
+ memref::CollapseShapeOp::attachInterface<
+ ReassociativeViewOpAliasModel<memref::CollapseShapeOp>>(*ctx);
+ memref::CollapseShapeOp::attachInterface<
+ ViewOpPromotableModel<memref::CollapseShapeOp>>(*ctx);
});
}
diff --git a/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp b/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp
index c8c17f2039b025..71256e514058d4 100644
--- a/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp
@@ -9,7 +9,7 @@
// This file implements memory slot interface models for
// `vector.transfer_read` and `vector.transfer_write`. These models implement
// `PromotableMemOpInterface` so Mem2Reg can replace vector transfers with uses
-// and definitions of a vector SSA value. The models for `memref.subview`
+// and definitions of a vector SSA value. The models for the `memref` view ops
// live in the MemRef dialect.
//
// A read uses the slot's current value; a write defines its next value.
@@ -19,7 +19,8 @@
// whole static parent, and the subview's slot holds that whole 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.
+// lanes disabled by a transfer's mask operand. For reads through reshaped
+// dynamic subviews, the subview mask is cast to the slot's vector shape.
//
// Transfers wrapped in `vector.mask` prevent promotion because that op does
// not implement `PromotableRegionOpInterface`, which Mem2Reg requires to
@@ -48,7 +49,7 @@ using namespace mlir::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
/// map must be the identity. Out-of-bounds dimensions are allowed only for a
-/// supported dynamic subview.
+/// supported dynamic subview, possibly reached through a reassociative reshape.
///
/// More general transfers may be supportable, but their value depends on
/// motivating use cases, implementation complexity, and generated code quality.
@@ -82,7 +83,7 @@ isPromotableTransfer(VectorTransferOpInterface xferOp, const MemorySlot &slot,
return false;
// Out-of-bounds is allowed only for a dynamic view.
- if (xferOp.hasOutOfBoundsDim() && !memref::isDynamicSubViewSlot(slot.ptr))
+ if (xferOp.hasOutOfBoundsDim() && !memref::isPartialViewSlot(slot))
return false;
return true;
@@ -129,7 +130,7 @@ struct TransferReadOpMemOpModel
const DataLayout &dataLayout) const {
auto readOp = cast<vector::TransferReadOp>(op);
Location loc = op->getLoc();
- Value mask = memref::buildDynamicSubViewMask(builder, loc, slot.ptr);
+ Value mask = memref::buildPartialViewMask(builder, loc, slot);
if (Value opMask = readOp.getMask())
mask = mask
? arith::AndIOp::create(builder, loc, mask, opMask).getResult()
diff --git a/mlir/test/Dialect/Vector/mem2reg-dynamic-subview.mlir b/mlir/test/Dialect/Vector/mem2reg-dynamic-subview.mlir
index e650f7e3fb09a7..eea9e36a92e5b9 100644
--- a/mlir/test/Dialect/Vector/mem2reg-dynamic-subview.mlir
+++ b/mlir/test/Dialect/Vector/mem2reg-dynamic-subview.mlir
@@ -166,3 +166,187 @@ func.func @negative_read_dyn_subview_static_offset_dyn_size(%v: vector<8x16xf32>
%r = vector.transfer_read %sv[%c0, %c0], %pad {in_bounds = [false, true]} : memref<?x16xf32, strided<[16, 1], offset: 32>>, vector<8x16xf32>
return %r : vector<8x16xf32>
}
+
+// -----
+
+// Collapse a dynamic subview by reshaping both the parent vector and the
+// subview mask. Lanes outside the runtime bounds receive the read's padding.
+// CHECK-LABEL: func.func @collapse_shape_dyn_subview(
+// CHECK-SAME: %[[V:.*]]: vector<8x16xf32>, %[[N:.*]]: index, %[[PAD:.*]]: f32
+// CHECK-NOT: memref.alloca
+// CHECK-NOT: memref.collapse_shape
+// CHECK: %[[SC:.*]] = vector.shape_cast %[[V]] : vector<8x16xf32> to vector<128xf32>
+// CHECK: %[[CM:.*]] = vector.create_mask %[[N]], %{{.*}} : vector<8x16xi1>
+// CHECK: %[[MASK:.*]] = vector.shape_cast %[[CM]] : vector<8x16xi1> to vector<128xi1>
+// CHECK: %[[PS:.*]] = vector.broadcast %[[PAD]] : f32 to vector<128xf32>
+// CHECK: %[[SEL:.*]] = arith.select %[[MASK]], %[[SC]], %[[PS]]
+// CHECK: return %[[SEL]]
+func.func @collapse_shape_dyn_subview(%v: vector<8x16xf32>, %n: index, %pad: f32) -> vector<128xf32> {
+ %c0 = arith.constant 0 : index
+ %a = memref.alloca() : memref<8x16xf32>
+ vector.transfer_write %v, %a[%c0, %c0] {in_bounds = [true, true]} : vector<8x16xf32>, memref<8x16xf32>
+ %sv = memref.subview %a[0, 0] [%n, 16] [1, 1] : memref<8x16xf32> to memref<?x16xf32, strided<[16, 1]>>
+ %flat = memref.collapse_shape %sv [[0, 1]] : memref<?x16xf32, strided<[16, 1]>> into memref<?xf32, strided<[1]>>
+ %r = vector.transfer_read %flat[%c0], %pad {in_bounds = [false]} : memref<?xf32, strided<[1]>>, vector<128xf32>
+ return %r : vector<128xf32>
+}
+
+
+// -----
+
+// Expanding the dynamic subview gives a static alias shape of 8x2, derived
+// from the parent vector's 16 elements. The memref extent remains dynamic;
+// the reshaped subview mask determines which lanes are valid.
+// CHECK-LABEL: func.func @expand_shape_dyn_subview(
+// CHECK-SAME: %[[V:[a-z0-9_]+]]: vector<16xf32>, %[[M:[a-z0-9_]+]]: index,
+// CHECK-SAME: %{{[a-z0-9_]+}}: index, %[[PAD:[a-z0-9_]+]]: f32
+// CHECK-NOT: memref.alloca
+// CHECK-NOT: memref.expand_shape
+// CHECK: %[[SC:.*]] = vector.shape_cast %[[V]] : vector<16xf32> to vector<8x2xf32>
+// CHECK: %[[CM:.*]] = vector.create_mask %[[M]] : vector<16xi1>
+// CHECK: %[[MASK:.*]] = vector.shape_cast %[[CM]] : vector<16xi1> to vector<8x2xi1>
+// CHECK: %[[PS:.*]] = vector.broadcast %[[PAD]] : f32 to vector<8x2xf32>
+// CHECK: %[[SEL:.*]] = arith.select %[[MASK]], %[[SC]], %[[PS]]
+// CHECK: return %[[SEL]]
+func.func @expand_shape_dyn_subview(%v: vector<16xf32>, %m: index, %d: index,
+ %pad: f32) -> vector<8x2xf32> {
+ %c0 = arith.constant 0 : index
+ %a = memref.alloca() : memref<16xf32>
+ vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<16xf32>, memref<16xf32>
+ %sv = memref.subview %a[0] [%m] [1] : memref<16xf32> to memref<?xf32, strided<[1]>>
+ %exp = memref.expand_shape %sv [[0, 1]] output_shape [%d, 2] : memref<?xf32, strided<[1]>> into memref<?x2xf32>
+ %r = vector.transfer_read %exp[%c0, %c0], %pad {in_bounds = [false, true]} : memref<?x2xf32>, vector<8x2xf32>
+ return %r : vector<8x2xf32>
+}
+
+
+// -----
+
+// A chain of reshapes casts the value at each step (8x16 -> 128 -> 64x2).
+// The read builds the subview mask in the parent shape and casts it directly
+// to the final shape.
+
+// CHECK-LABEL: func.func @collapse_then_expand_dyn_subview(
+// CHECK-SAME: %[[V:[a-z0-9_]+]]: vector<8x16xf32>, %[[N:[a-z0-9_]+]]: index
+// CHECK-NOT: memref.alloca
+// CHECK-NOT: memref.collapse_shape
+// CHECK-NOT: memref.expand_shape
+// CHECK: %[[F:.*]] = vector.shape_cast %[[V]] : vector<8x16xf32> to vector<128xf32>
+// CHECK: %[[E:.*]] = vector.shape_cast %[[F]] : vector<128xf32> to vector<64x2xf32>
+// CHECK: %[[CM:.*]] = vector.create_mask %[[N]], %{{.*}} : vector<8x16xi1>
+// CHECK: %[[MASK:.*]] = vector.shape_cast %[[CM]] : vector<8x16xi1> to vector<64x2xi1>
+// CHECK: %[[SEL:.*]] = arith.select %[[MASK]], %[[E]], %{{.*}}
+// CHECK: return %[[SEL]]
+func.func @collapse_then_expand_dyn_subview(%v: vector<8x16xf32>, %n: index,
+ %d: index, %pad: f32) -> vector<64x2xf32> {
+ %c0 = arith.constant 0 : index
+ %a = memref.alloca() : memref<8x16xf32>
+ vector.transfer_write %v, %a[%c0, %c0] {in_bounds = [true, true]} : vector<8x16xf32>, memref<8x16xf32>
+ %sv = memref.subview %a[0, 0] [%n, 16] [1, 1] : memref<8x16xf32> to memref<?x16xf32, strided<[16, 1]>>
+ %f = memref.collapse_shape %sv [[0, 1]] : memref<?x16xf32, strided<[16, 1]>> into memref<?xf32, strided<[1]>>
+ %e = memref.expand_shape %f [[0, 1]] output_shape [%d, 2] : memref<?xf32, strided<[1]>> into memref<?x2xf32>
+ %r = vector.transfer_read %e[%c0, %c0], %pad {in_bounds = [false, true]} : memref<?x2xf32>, vector<64x2xf32>
+ return %r : vector<64x2xf32>
+}
+
+
+// -----
+
+// Reject two dynamic dimensions in one group: the parent extent alone cannot
+// determine their individual extents or the resulting row stride.
+// CHECK-LABEL: func.func @neg_two_dynamic_dims_in_group(
+// CHECK: memref.alloca
+// CHECK: memref.expand_shape
+// CHECK: vector.transfer_read
+func.func @neg_two_dynamic_dims_in_group(%v: vector<16xf32>, %m: index, %d0: index,
+ %d1: index, %pad: f32) -> vector<8x2xf32> {
+ %c0 = arith.constant 0 : index
+ %a = memref.alloca() : memref<16xf32>
+ vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<16xf32>, memref<16xf32>
+ %sv = memref.subview %a[0] [%m] [1] : memref<16xf32> to memref<?xf32, strided<[1]>>
+ %exp = memref.expand_shape %sv [[0, 1]] output_shape [%d0, %d1] : memref<?xf32, strided<[1]>> into memref<?x?xf32>
+ %r = vector.transfer_read %exp[%c0, %c0], %pad : memref<?x?xf32>, vector<8x2xf32>
+ return %r : vector<8x2xf32>
+}
+
+
+// -----
+
+// Reject expansion to 2x?: the memref's row stride is %m, whereas a 2x4 vector
+// has row stride 4. A mask cannot correct this difference in element positions.
+// CHECK-LABEL: func.func @neg_nonunit_dim_before_dynamic(
+// CHECK: memref.alloca
+// CHECK: memref.subview
+// CHECK: memref.expand_shape
+// CHECK: vector.transfer_read
+func.func @neg_nonunit_dim_before_dynamic(%v: vector<8xf32>, %n: index, %m: index,
+ %pad: f32) -> vector<2x4xf32> {
+ %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] [%n] [1] : memref<8xf32> to memref<?xf32, strided<[1]>>
+ %exp = memref.expand_shape %sv [[0, 1]] output_shape [2, %m] : memref<?xf32, strided<[1]>> into memref<2x?xf32>
+ %r = vector.transfer_read %exp[%c0, %c0], %pad {in_bounds = [true, false]} : memref<2x?xf32>, vector<2x4xf32>
+ return %r : vector<2x4xf32>
+}
+
+
+// -----
+
+// Reject a zero static extent without dividing by zero during shape inference.
+// CHECK-LABEL: func.func @neg_zero_extent_in_group(
+// CHECK: memref.alloca
+// CHECK: memref.subview
+// CHECK: memref.expand_shape
+// CHECK: vector.transfer_read
+func.func @neg_zero_extent_in_group(%v: vector<8x16xf32>, %n: index, %d: index,
+ %pad: f32) -> vector<8x16xf32> {
+ %c0 = arith.constant 0 : index
+ %a = memref.alloca() : memref<8x16xf32>
+ vector.transfer_write %v, %a[%c0, %c0] {in_bounds = [true, true]} : vector<8x16xf32>, memref<8x16xf32>
+ %sv = memref.subview %a[0, 0] [%n, 16] [1, 1] : memref<8x16xf32> to memref<?x16xf32, strided<[16, 1]>>
+ %e = memref.expand_shape %sv [[0, 1], [2]] output_shape [%d, 0, 16] : memref<?x16xf32, strided<[16, 1]>> into memref<?x0x16xf32, strided<[0, 16, 1]>>
+ %r = vector.transfer_read %e[%c0, %c0, %c0], %pad : memref<?x0x16xf32, strided<[0, 16, 1]>>, vector<8x16xf32>
+ return %r : vector<8x16xf32>
+}
+
+
+// -----
+
+// Reject collapsing 4x?: the subview's rows have stride 4, so they are not
+// guaranteed contiguous when the inner extent is %n.
+// CHECK-LABEL: func.func @neg_collapse_dyn_inner_dim(
+// CHECK: memref.alloca
+// CHECK: memref.collapse_shape
+// CHECK: vector.transfer_read
+func.func @neg_collapse_dyn_inner_dim(%v: vector<4x4xf32>, %n: index,
+ %pad: f32) -> vector<16xf32> {
+ %c0 = arith.constant 0 : index
+ %a = memref.alloca() : memref<4x4xf32>
+ vector.transfer_write %v, %a[%c0, %c0] {in_bounds = [true, true]} : vector<4x4xf32>, memref<4x4xf32>
+ %sv = memref.subview %a[0, 0] [4, %n] [1, 1] : memref<4x4xf32> to memref<4x?xf32, strided<[4, 1]>>
+ %f = memref.collapse_shape %sv [[0, 1]] : memref<4x?xf32, strided<[4, 1]>> into memref<?xf32, strided<[?]>>
+ %r = vector.transfer_read %f[%c0], %pad {in_bounds = [false]} : memref<?xf32, strided<[?]>>, vector<16xf32>
+ return %r : vector<16xf32>
+}
+
+// -----
+
+// Reject an expansion whose fully static group accounts for only 8 of the
+// parent dimension's 16 elements. The inferred alias shape would lose half
+// the parent vector's elements, violating the shape_cast element-count rule.
+// CHECK-LABEL: func.func @neg_static_group_narrowed_subview(
+// CHECK: memref.alloca
+// CHECK: memref.subview
+// CHECK: memref.expand_shape
+// CHECK: vector.transfer_read
+func.func @neg_static_group_narrowed_subview(%v: vector<8x16xf32>, %n: index,
+ %d: index, %pad: f32) -> vector<8x2x8xf32> {
+ %c0 = arith.constant 0 : index
+ %a = memref.alloca() : memref<8x16xf32>
+ vector.transfer_write %v, %a[%c0, %c0] {in_bounds = [true, true]} : vector<8x16xf32>, memref<8x16xf32>
+ %sv = memref.subview %a[0, 0] [%n, 8] [1, 1] : memref<8x16xf32> to memref<?x8xf32, strided<[16, 1]>>
+ %exp = memref.expand_shape %sv [[0, 1], [2]] output_shape [%d, 2, 8] : memref<?x8xf32, strided<[16, 1]>> into memref<?x2x8xf32, strided<[32, 16, 1]>>
+ %r = vector.transfer_read %exp[%c0, %c0, %c0], %pad {in_bounds = [false, true, true]} : memref<?x2x8xf32, strided<[32, 16, 1]>>, vector<8x2x8xf32>
+ return %r : vector<8x2x8xf32>
+}
diff --git a/mlir/test/Dialect/Vector/mem2reg.mlir b/mlir/test/Dialect/Vector/mem2reg.mlir
index 62a35c90d324e3..d1ec13f7262dc7 100644
--- a/mlir/test/Dialect/Vector/mem2reg.mlir
+++ b/mlir/test/Dialect/Vector/mem2reg.mlir
@@ -690,3 +690,155 @@ 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>
}
+
+// -----
+
+// A read through collapse_shape becomes a shape_cast of the parent vector,
+// preserving all elements and their order.
+
+// CHECK-LABEL: func.func @collapse_shape_read
+// CHECK-SAME: %[[V:.*]]: vector<8x16xf32>
+// CHECK-NOT: memref.alloca
+// CHECK-NOT: memref.collapse_shape
+// CHECK: %[[SC:.*]] = vector.shape_cast %[[V]] : vector<8x16xf32> to vector<128xf32>
+// CHECK: return %[[SC]]
+func.func @collapse_shape_read(%v: vector<8x16xf32>, %pad: f32) -> vector<128xf32> {
+ %c0 = arith.constant 0 : index
+ %a = memref.alloca() : memref<8x16xf32>
+ vector.transfer_write %v, %a[%c0, %c0] {in_bounds = [true, true]} : vector<8x16xf32>, memref<8x16xf32>
+ %flat = memref.collapse_shape %a [[0, 1]] : memref<8x16xf32> into memref<128xf32>
+ %r = vector.transfer_read %flat[%c0], %pad {in_bounds = [true]} : memref<128xf32>, vector<128xf32>
+ return %r : vector<128xf32>
+}
+
+
+// -----
+
+// A read through expand_shape becomes a shape_cast to the expanded shape.
+
+// CHECK-LABEL: func.func @expand_shape_read
+// CHECK-SAME: %[[V:.*]]: vector<128xf32>
+// CHECK-NOT: memref.alloca
+// CHECK-NOT: memref.expand_shape
+// CHECK: %[[SC:.*]] = vector.shape_cast %[[V]] : vector<128xf32> to vector<8x16xf32>
+// CHECK: return %[[SC]]
+func.func @expand_shape_read(%v: vector<128xf32>, %pad: f32) -> vector<8x16xf32> {
+ %c0 = arith.constant 0 : index
+ %a = memref.alloca() : memref<128xf32>
+ vector.transfer_write %v, %a[%c0] {in_bounds = [true]} : vector<128xf32>, memref<128xf32>
+ %exp = memref.expand_shape %a [[0, 1]] output_shape [8, 16] : memref<128xf32> into memref<8x16xf32>
+ %r = vector.transfer_read %exp[%c0, %c0], %pad {in_bounds = [true, true]} : memref<8x16xf32>, vector<8x16xf32>
+ return %r : vector<8x16xf32>
+}
+
+
+// -----
+
+// A write through the collapsed view replaces the whole parent vector with
+// the written value cast back to the parent shape.
+
+// CHECK-LABEL: func.func @collapse_shape_write
+// CHECK-SAME: %{{.*}}: vector<8x16xf32>, %[[W:.*]]: vector<128xf32>
+// CHECK-NOT: memref.alloca
+// CHECK: %[[SC:.*]] = vector.shape_cast %[[W]] : vector<128xf32> to vector<8x16xf32>
+// CHECK: return %[[SC]]
+func.func @collapse_shape_write(%v: vector<8x16xf32>, %w: vector<128xf32>, %pad: f32) -> vector<8x16xf32> {
+ %c0 = arith.constant 0 : index
+ %a = memref.alloca() : memref<8x16xf32>
+ vector.transfer_write %v, %a[%c0, %c0] {in_bounds = [true, true]} : vector<8x16xf32>, memref<8x16xf32>
+ %flat = memref.collapse_shape %a [[0, 1]] : memref<8x16xf32> into memref<128xf32>
+ vector.transfer_write %w, %flat[%c0] {in_bounds = [true]} : vector<128xf32>, memref<128xf32>
+ %r = vector.transfer_read %a[%c0, %c0], %pad {in_bounds = [true, true]} : memref<8x16xf32>, vector<8x16xf32>
+ return %r : vector<8x16xf32>
+}
+
+
+// -----
+
+// A static subview extracts a vector slice, which collapse_shape then reshapes.
+
+// CHECK-LABEL: func.func @subview_then_collapse_shape
+// CHECK-SAME: %[[V:.*]]: vector<8x16xf32>
+// CHECK-NOT: memref.alloca
+// CHECK: %[[EX:.*]] = vector.extract_strided_slice %[[V]] offsets = [0, 0], sizes = [4, 16]
+// CHECK: %[[SC:.*]] = vector.shape_cast %[[EX]] : vector<4x16xf32> to vector<64xf32>
+// CHECK: return %[[SC]]
+func.func @subview_then_collapse_shape(%v: vector<8x16xf32>, %pad: f32) -> vector<64xf32> {
+ %c0 = arith.constant 0 : index
+ %a = memref.alloca() : memref<8x16xf32>
+ vector.transfer_write %v, %a[%c0, %c0] {in_bounds = [true, true]} : vector<8x16xf32>, memref<8x16xf32>
+ %sv = memref.subview %a[0, 0] [4, 16] [1, 1] : memref<8x16xf32> to memref<4x16xf32, strided<[16, 1]>>
+ %flat = memref.collapse_shape %sv [[0, 1]] : memref<4x16xf32, strided<[16, 1]>> into memref<64xf32, strided<[1]>>
+ %r = vector.transfer_read %flat[%c0], %pad {in_bounds = [true]} : memref<64xf32, strided<[1]>>, vector<64xf32>
+ return %r : vector<64xf32>
+}
+
+
+// -----
+
+// Expansion also composes with a static subview: extract the 4x16 slice,
+// then reshape it to 1x4x16.
+
+// CHECK-LABEL: func.func @subview_then_expand_shape
+// CHECK-SAME: %[[V:.*]]: vector<8x16xf32>
+// CHECK-NOT: memref.alloca
+// CHECK-NOT: memref.expand_shape
+// CHECK: %[[EX:.*]] = vector.extract_strided_slice %[[V]] offsets = [0, 0], sizes = [4, 16]
+// CHECK: %[[SC:.*]] = vector.shape_cast %[[EX]] : vector<4x16xf32> to vector<1x4x16xf32>
+// CHECK: return %[[SC]]
+func.func @subview_then_expand_shape(%v: vector<8x16xf32>, %pad: f32) -> vector<1x4x16xf32> {
+ %c0 = arith.constant 0 : index
+ %a = memref.alloca() : memref<8x16xf32>
+ vector.transfer_write %v, %a[%c0, %c0] {in_bounds = [true, true]} : vector<8x16xf32>, memref<8x16xf32>
+ %sv = memref.subview %a[0, 0] [4, 16] [1, 1] : memref<8x16xf32> to memref<4x16xf32, strided<[16, 1]>>
+ %exp = memref.expand_shape %sv [[0, 1], [2]] output_shape [1, 4, 16] : memref<4x16xf32, strided<[16, 1]>> into memref<1x4x16xf32, strided<[64, 16, 1]>>
+ %r = vector.transfer_read %exp[%c0, %c0, %c0], %pad {in_bounds = [true, true, true]} : memref<1x4x16xf32, strided<[64, 16, 1]>>, vector<1x4x16xf32>
+ return %r : vector<1x4x16xf32>
+}
+
+
+// -----
+
+// A non-zero constant subview offset changes the extracted slice's origin
+// without affecting the subsequent shape_cast.
+
+// CHECK-LABEL: func.func @subview_offset_then_expand_shape
+// CHECK-SAME: %[[V:.*]]: vector<8x16xf32>
+// CHECK-NOT: memref.alloca
+// CHECK: %[[EX:.*]] = vector.extract_strided_slice %[[V]] offsets = [2, 0], sizes = [4, 16]
+// CHECK: %[[SC:.*]] = vector.shape_cast %[[EX]] : vector<4x16xf32> to vector<1x4x16xf32>
+// CHECK: return %[[SC]]
+func.func @subview_offset_then_expand_shape(%v: vector<8x16xf32>, %pad: f32) -> vector<1x4x16xf32> {
+ %c0 = arith.constant 0 : index
+ %a = memref.alloca() : memref<8x16xf32>
+ vector.transfer_write %v, %a[%c0, %c0] {in_bounds = [true, true]} : vector<8x16xf32>, memref<8x16xf32>
+ %sv = memref.subview %a[2, 0] [4, 16] [1, 1] : memref<8x16xf32> to memref<4x16xf32, strided<[16, 1], offset: 32>>
+ %exp = memref.expand_shape %sv [[0, 1], [2]] output_shape [1, 4, 16] : memref<4x16xf32, strided<[16, 1], offset: 32>> into memref<1x4x16xf32, strided<[64, 16, 1], offset: 32>>
+ %r = vector.transfer_read %exp[%c0, %c0, %c0], %pad {in_bounds = [true, true, true]} : memref<1x4x16xf32, strided<[64, 16, 1], offset: 32>>, vector<1x4x16xf32>
+ return %r : vector<1x4x16xf32>
+}
+
+
+// -----
+
+// Expansion also supports a static subview with gaps between rows. Extracting
+// the 4x8 slice packs the selected elements into its alias vector; shape_cast
+// then preserves their order when reshaping to 1x4x8.
+
+// CHECK-LABEL: func.func @expand_shape_strided_subview
+// CHECK-SAME: (%[[V:.*]]: vector<8x16xf32>, %[[PAD:.*]]: f32)
+// CHECK-NOT: memref.alloca
+// CHECK-NOT: memref.subview
+// CHECK-NOT: memref.expand_shape
+// CHECK: %[[EXT:.*]] = vector.extract_strided_slice %[[V]] offsets = [0, 0], sizes = [4, 8], strides = [1, 1] : vector<8x16xf32> to vector<4x8xf32>
+// CHECK: %[[SC:.*]] = vector.shape_cast %[[EXT]] : vector<4x8xf32> to vector<1x4x8xf32>
+// CHECK: return %[[SC]] : vector<1x4x8xf32>
+func.func @expand_shape_strided_subview(%v: vector<8x16xf32>, %pad: f32) -> vector<1x4x8xf32> {
+ %c0 = arith.constant 0 : index
+ %a = memref.alloca() : memref<8x16xf32>
+ vector.transfer_write %v, %a[%c0, %c0] {in_bounds = [true, true]} : vector<8x16xf32>, memref<8x16xf32>
+ %sv = memref.subview %a[0, 0] [4, 8] [1, 1] : memref<8x16xf32> to memref<4x8xf32, strided<[16, 1]>>
+ %exp = memref.expand_shape %sv [[0, 1], [2]] output_shape [1, 4, 8] : memref<4x8xf32, strided<[16, 1]>> into memref<1x4x8xf32, strided<[64, 16, 1]>>
+ %r = vector.transfer_read %exp[%c0, %c0, %c0], %pad {in_bounds = [true, true, true]} : memref<1x4x8xf32, strided<[64, 16, 1]>>, vector<1x4x8xf32>
+ return %r : vector<1x4x8xf32>
+}
More information about the Mlir-commits
mailing list