[Mlir-commits] [mlir] [mlir][mem2reg] Promote buffers accessed via dynamic subviews, reshapes and memref.copy (PR #217793)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Thu Sep 17 10:52:11 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir-vector
Author: Jianhui Li (Jianhui-Li)
<details>
<summary>Changes</summary>
**Motivation**
An op on dynamically-shaped tensors is often padded to static tile sizes before vectorization: its operands are padded up to a fixed size, and the real region is extracted back from the result. After bufferization those padded tensors become fixed-size buffers, and the IR around them reaches each buffer through dynamic-shaped views — the buffer's shape is static, but the part that really holds data is not.
The pattern looks like this: a fixed-size staging buffer holding one tile, a dynamic subview covering the part with real data, a reshape lifting that tile to the rank of the original tensor it came from, and a memref.copy that stores the tile back into it or loads a tile out.
mlir
%buf = memref.alloca() : memref<128x64xf16> // padded staging buffer
vector.transfer_write %v, %buf[%c0, %c0] {in_bounds = [true, true]}
: vector<128x64xf16>, memref<128x64xf16>
%sv = memref.subview %buf[0, 0] [%n, 64] [1,real data
: memref<128x64xf16> to memref<?x64xf16, strided<[64, 1]>>
... // some operations on the dynamic subview like padding + elewise/reduction
%ex = memref.expand_shape %sv [[0, 1, 2], [3]
: memref<?x64xf16, strided<[64, 1]>> into memref<1x1x?x64xf16, strided<[?, ?, 64, 1]>>
%dst = memref.subview %out[%i, %j, %k, 0] [1,
memref.copy %ex, %dst
#<!-- -->211880 taught Mem2Reg to promote a staticall vector SSA value, but only for whole-buffer,in-bounds, unmasked vector.transfer_read/transfer_write, plus fully-static memref.subview sub-slices.
This PR extends the vector Mem2Reg models so dynamic subviews, memref.copy, reshapes and masked transfers promote as well. Any partial access is reconstructed with an arith.select during promotion instead of bailing out. The chain above then promotes to vector values, and the allocation goes away.
This PR closes a gap in the typical lowering pipeline for linalg IR on dynamically-shaped tensors: tile-and-fuse → padding → vectorize → bufferization → mem2reg. The current Mem2Reg handles only whole-buffer transfers and static-shaped subviews, so the staging buffers remain in the final IR. Now they promote to vector values instead: the padding introduced at the linalg level becomes a vector.create_mask and an arith.select on a fixed-size vector, and the pipeline finishes in pure vector IR.
**What this PR adds**
**Dynamic-shaped subviews.** A vector's shape has to be static, so a dynamic subview cannot have a vector of its own shape. It uses the whole buffer's vector instead, and reads or writes through it are masked to the runtime size with vector.create_mask and arith.select. The subview has to start at the buffer's origin, so the live region on each dimension is just [0, size).
**memref.copy.** A copy is treated as a transfer of the buffer's value. Copying into the buffer becomes a read of the source; copying out becomes a write of the buffer's value into the destination. If both sides are promotable buffers, the destination picks up the value promoted from the source, and the copy and both buffers disappear.
**expand_shape and collapse_shape.** A contiguous reshape leaves the elements in the same order, so once the buffer is a vector the reshape is just a vector.shape_cast.
A reshape can sit on top of a subview, or on top of another reshape. In the dyanmic subview case the view's own type gives us no shape to cast to, since a vector's shape must be static. Instead we take the result shape by applying the reshape's reassociation to the whole buffer's shape: 128x64 split as [[0, 1, 2], [3]] gives 1x1x128x64. The runtime extent is not lost — it stays in the mask, which is shape_cast the same way and so keeps marking the same elements.
**Masked transfers.** A masked transfer touches only some lanes, so rather than reject it, promotion rebuilds the result with an arith.select.
**Restrictions**
- a transfer that does not start at the buffer's origin, or whose permutation map is not the identity
- a subview whose offsets are not constants
- a dynamic subview that does not start at the buffer's origin
- a rank-reducing or non-unit-stride subview
- a non-contiguous reshape, or a reshape group whose dynamic extent cannot be worked out from the buffer's
- a reshape group with a non-unit dimension in front of its dynamic one, where the view's strides and the alias's would disagree
- subviewing, reshaping or copying a scalable buffer — it still promotes through whole-buffer transfers, just not through these
**Tests**
- mem2reg.mlir covers whole-buffer, static-subview, masked, reshape and whole-buffer-copy cases.
- mem2reg-dynamic-subview.mlir covers dynamic views: plain, nested on a static subview, reshaped, two reshapes in a row, copied in and out, and merged across a branch by a block argument.
---
Patch is 89.30 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/217793.diff
3 Files Affected:
- (modified) mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp (+680-122)
- (added) mlir/test/Dialect/Vector/mem2reg-dynamic-subview.mlir (+474)
- (modified) mlir/test/Dialect/Vector/mem2reg.mlir (+297-36)
``````````diff
diff --git a/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp b/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp
index 1acebcb215439..751f0abc2364e 100644
--- a/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.cpp
@@ -6,33 +6,74 @@
//
//===----------------------------------------------------------------------===//
//
-// 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:
+// This file implements Mem2Reg-related interfaces that let a memref be promoted
+// into a single vector SSA value: `PromotableMemOpInterface` models for the ops
+// that access such a memref and `PromotableAliaserInterface` models for the ops
+// that view it. Mem2Reg calls the memory it promotes a *slot*: a pointer paired
+// with the type of the value it becomes, here a memref and its vector type.
+// Promoting a slot replaces it with that value, used as the slot's 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 slot is promoted when each of its uses is an access these models can
+// rewrite: a `vector.transfer_read` or `vector.transfer_write` meeting the
+// criteria in `isPromotableTransfer`, or a `memref.copy` that has the slot as
+// its source or its target. A view of the slot (`memref.subview`,
+// `memref.expand_shape`, `memref.collapse_shape`) is allowed as well: the view
+// becomes an alias slot of its own, promoted with its parent, as long as every
+// use of the view is in turn such an access, or another such view.
//
-// * 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.
+// The accesses are rewritten as follows:
//
-// 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.
+// * `vector.transfer_read` becomes a use of the current vector value;
+// `vector.transfer_write` becomes a new definition of it. A masked transfer
+// covers only its active lanes, so it is composed with an `arith.select`.
+//
+// * `memref.copy` is modeled as a transfer of the slot value: if the slot is
+// the copy's target, the value stored into it is a `vector.transfer_read`
+// of the copy's source; if the slot is the copy's source, its value is
+// written to the target with a `vector.transfer_write`. Either way the copy
+// itself is removed. A copy between two slots is resolved one slot at a
+// time, each promotion rewriting its own side (see `CopyOpMemOpModel`).
+//
+// * a same-rank, unit-stride `memref.subview` is exposed as an alias slot
+// (via `PromotableAliaserInterface`), so a slot accessed through subviews
+// promotes as well:
+//
+// - a static sub-slice becomes an alias of that sub-vector: a read projects
+// it out of the slot value with `vector.extract_strided_slice` and a
+// write composes back into the value with `vector.insert_strided_slice`;
+//
+// - a dynamic sub-slice has no vector type, since vector shapes must be
+// static. Its alias therefore holds the WHOLE parent vector, and accesses
+// through it are out-of-bounds transfers of that shape. Promotion masks
+// the value down to the extent carried by the subview's size operands,
+// using `vector.create_mask` and `arith.select`. Such a subview must
+// start at the buffer origin.
+//
+// * a `memref.expand_shape` / `memref.collapse_shape` is exposed as an alias
+// of the reshaped value: a contiguous reshape keeps the element order, so
+// both projections are a `vector.shape_cast`. Reshapes compose with the
+// subview aliases above, so a reshaped dynamic view (e.g. `collapse_shape`
+// of a dynamic subview) promotes as well: its alias shape comes from the
+// parent's extents and its mask is reshaped along with the value.
+//
+// A memref accessed through the subviews, reshapes or copies above must be
+// statically shaped, so that its slot has a fixed-shape vector type. A scalable
+// slot may instead be dynamically shaped, but it is 1-D and promotes through
+// whole-buffer transfers alone: it cannot be subviewed, reshaped or copied.
+//
+// Accesses that do not meet the criteria above -- dynamic offsets,
+// rank-reducing or non-unit-stride subviews, non-contiguous reshapes, non-zero
+// transfer indices -- are left untouched, so the memref is not promoted.
//
//===----------------------------------------------------------------------===//
#include "mlir/Dialect/Vector/Transforms/MemorySlotOpInterfaceImpl.h"
+#include "mlir/Dialect/Arith/IR/Arith.h"
+#include "mlir/Dialect/Arith/Utils/Utils.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
+#include "mlir/Dialect/MemRef/Utils/MemRefUtils.h"
#include "mlir/Dialect/Utils/StaticValueUtils.h"
#include "mlir/Dialect/Vector/IR/VectorOps.h"
#include "mlir/IR/BuiltinTypes.h"
@@ -45,11 +86,24 @@ using namespace mlir::vector;
// Utilities
//===----------------------------------------------------------------------===//
-/// Returns whether `xferOp` accesses exactly the whole contents of `slot`, so
-/// it can act as a plain whole-buffer load/store during Mem2Reg.
+// Defined below, after the subview helpers it builds on.
+static memref::SubViewOp getUnderlyingDynamicSubView(Value slotPtr);
+
+/// Returns whether `xferOp` can be promoted to a load/store of `slot`'s vector
+/// value. This requires that the transfer's sole use of the slot is as its
+/// base, the transferred vector type equals `slot.elemType`, the indices are
+/// all zero (origin), and the permutation map is the identity.
+///
+/// Two forms of partial access are accepted (rather than rejected) and
+/// reconstructed with a `select` during promotion (see the transfer models):
+/// - a masked transfer, and
+/// - an out-of-bounds transfer through a dynamic-subview alias, possibly
+/// reached through a reassociative reshape.
+/// Their active lanes take the reaching value; the inactive lanes take the
+/// transfer's padding (read) or keep the reaching value (write).
static bool
-isWholeBufferTransfer(VectorTransferOpInterface xferOp, const MemorySlot &slot,
- const SmallPtrSetImpl<OpOperand *> &blockingUses) {
+isPromotableTransfer(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)
return false;
@@ -76,26 +130,353 @@ isWholeBufferTransfer(VectorTransferOpInterface xferOp, const MemorySlot &slot,
if (!xferOp.getPermutationMap().isIdentity())
return false;
- // 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())
+ // Out-of-bounds is allowed only for a dynamic view.
+ if (xferOp.hasOutOfBoundsDim() && !getUnderlyingDynamicSubView(slot.ptr))
+ return false;
+
+ return true;
+}
+
+/// Whether `subView` is a statically-shaped slice that can be aliased as the
+/// sub-vector it covers, the alias its promotion uses. 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. A dynamically-
+/// shaped slice aliases the whole parent buffer instead
+/// (`isAliasableDynamicShapeSubView`).
+static bool isAliasableStaticShapeSubView(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 false;
- // A mask could make the access partial.
- if (xferOp.getMask())
+ // No rank reduction: extract/insert_strided_slice operate at a single rank.
+ if (srcType.getRank() != resType.getRank())
return false;
+ // Unit strides only.
+ for (OpFoldResult stride : subView.getMixedStrides()) {
+ std::optional<int64_t> s = getConstantIntValue(stride);
+ if (!s || *s != 1)
+ return false;
+ }
+
+ // Static offsets.
+ for (OpFoldResult offset : subView.getMixedOffsets()) {
+ if (!getConstantIntValue(offset))
+ return false;
+ }
+
+ // 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 false;
+ }
return true;
}
+/// The offsets at which the alias sub-vector of a statically-shaped `subView`
+/// sits within the parent slot's value, for `vector.extract_strided_slice` /
+/// `insert_strided_slice`.
+static SmallVector<int64_t> getStaticSubViewOffsets(memref::SubViewOp subView) {
+ SmallVector<int64_t> offsets;
+ for (OpFoldResult offset : subView.getMixedOffsets()) {
+ std::optional<int64_t> value = getConstantIntValue(offset);
+ assert(value && "expected a static offset");
+ offsets.push_back(*value);
+ }
+ return offsets;
+}
+
+/// Whether `subView` is a dynamically-shaped slice that can be aliased as the
+/// whole parent buffer, the alias its promotion uses. A statically-shaped slice
+/// aliases just its sub-vector instead (`isAliasableStaticShapeSubView`).
+static bool isAliasableDynamicShapeSubView(memref::SubViewOp subView) {
+ // The parent must be statically shaped so the slot has a fixed-shape vector
+ // type; only the subview's sizes may be dynamic.
+ auto srcType = dyn_cast<MemRefType>(subView.getSource().getType());
+ auto resType = dyn_cast<MemRefType>(subView.getResult().getType());
+ if (!srcType || !resType || !srcType.hasStaticShape())
+ return false;
+
+ // At least one dynamic result dimension; a fully static sub-slice is handled
+ // by `isAliasableStaticShapeSubView` instead.
+ if (resType.hasStaticShape())
+ return false;
+
+ // Same rank: the read/write vector matches the parent rank.
+ if (srcType.getRank() != resType.getRank())
+ return false;
+
+ // Unit strides and zero offsets: a leading, contiguous sub-region starting at
+ // the parent's origin, so the valid region on each dim is exactly [0, size).
+ if (!subView.hasUnitStride() || !subView.hasZeroOffset())
+ return false;
+
+ return VectorType::isValidElementType(srcType.getElementType());
+}
+
+/// The vector type covering `subView`'s whole parent buffer, which is the type
+/// the alias slot of a dynamically-shaped subview takes.
+static VectorType getWholeParentVectorType(memref::SubViewOp subView) {
+ auto parentType = cast<MemRefType>(subView.getSource().getType());
+ assert(parentType.hasStaticShape() && "expected a statically-shaped parent");
+ return VectorType::get(parentType.getShape(), parentType.getElementType());
+}
+
+/// Returns the dynamic subview `slotPtr` is a view of, looking through
+/// reassociative reshapes (`expand_shape` / `collapse_shape`), or null if there
+/// is none. A slot carries only a pointer and a vector type, so the dynamic
+/// extent is not part of it: it is recovered from this subview's size operands
+/// whenever a mask is needed. A reshaped dynamic subview is still a dynamic
+/// view of the parent: the reshape preserves element order, so the valid region
+/// is the same set of elements, just indexed differently (see
+/// `buildDynamicViewMask`).
+static memref::SubViewOp getUnderlyingDynamicSubView(Value slotPtr) {
+ Operation *def = slotPtr.getDefiningOp();
+ while (def) {
+ if (auto subView = dyn_cast<memref::SubViewOp>(def)) {
+ if (isAliasableDynamicShapeSubView(subView))
+ return subView;
+ return {};
+ }
+ if (auto expand = dyn_cast<memref::ExpandShapeOp>(def)) {
+ def = expand.getSrc().getDefiningOp();
+ continue;
+ }
+ if (auto collapse = dyn_cast<memref::CollapseShapeOp>(def)) {
+ def = collapse.getSrc().getDefiningOp();
+ continue;
+ }
+ return {};
+ }
+ return {};
+}
+
+/// Builds the mask of `subView`'s valid region, expressed in `vecType`'s shape:
+/// a `vector.create_mask` of the subview's sizes in the parent's shape,
+/// reshaped with `vector.shape_cast` when the slot is a reassociated view of
+/// the parent (both describe the same elements in row-major order). One cast
+/// suffices however many reshapes the slot is behind, since each preserves that
+/// order and so they compose.
+static Value buildDynamicViewMask(OpBuilder &builder, Location loc,
+ memref::SubViewOp subView,
+ VectorType vecType) {
+ VectorType parentVecType = getWholeParentVectorType(subView);
+ SmallVector<Value> bounds =
+ getValueOrCreateConstantIndexOp(builder, loc, subView.getMixedSizes());
+ 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;
+}
+
+/// Produces the value that a `memref.copy` stores into a slot: reads `mem` at
+/// the origin into `vecType` with an identity map, marking a dimension
+/// in-bounds only when the memref extent is statically at least the vector
+/// extent.
+///
+/// The padding is a don't-care: `memref.copy` requires both sides to have the
+/// same shape, so the source covers the whole vector at runtime and a dynamic
+/// extent only forces a conservative out-of-bounds marking. For a
+/// dynamic-subview slot, lanes past its extent are in any case discarded by the
+/// aliaser's masked select.
+static Value readMemRefAsVector(OpBuilder &builder, Location loc, Value mem,
+ VectorType vecType) {
+ assert(!vecType.isScalable() && "expected a fixed-size vector");
+ auto memType = cast<MemRefType>(mem.getType());
+ int64_t rank = vecType.getRank();
+ Value zero = arith::ConstantIndexOp::create(builder, loc, 0);
+ SmallVector<Value> indices(rank, zero);
+ Value padding = arith::ConstantOp::create(
+ builder, loc, builder.getZeroAttr(vecType.getElementType()));
+ SmallVector<bool> inBounds(rank);
+ for (int64_t d = 0; d < rank; ++d)
+ inBounds[d] = !memType.isDynamicDim(d) &&
+ memType.getDimSize(d) >= vecType.getDimSize(d);
+ return vector::TransferReadOp::create(
+ builder, loc, vecType, mem, indices,
+ AffineMapAttr::get(builder.getMultiDimIdentityMap(rank)), padding,
+ /*mask=*/Value(), builder.getBoolArrayAttr(inBounds));
+}
+
+/// Stores a slot's value to the target of a `memref.copy`: writes `vec` into
+/// `mem` at the origin, with the identity map and in-bounds rule of
+/// `readMemRefAsVector`. `mask`, when set, restricts the write to the lanes it
+/// selects.
+static void writeVectorToMemRef(OpBuilder &builder, Location loc, Value vec,
+ Value mem, Value mask = {}) {
+ auto memType = cast<MemRefType>(mem.getType());
+ auto vecType = cast<VectorType>(vec.getType());
+ assert(!vecType.isScalable() && "expected a fixed-size vector");
+ int64_t rank = vecType.getRank();
+ Value zero = arith::ConstantIndexOp::create(builder, loc, 0);
+ SmallVector<Value> indices(rank, zero);
+ SmallVector<bool> inBounds(rank);
+ for (int64_t d = 0; d < rank; ++d)
+ inBounds[d] = !memType.isDynamicDim(d) &&
+ memType.getDimSize(d) >= vecType.getDimSize(d);
+ vector::TransferWriteOp::create(
+ builder, loc, vec, mem, indices,
+ AffineMapAttr::get(builder.getMultiDimIdentityMap(rank)), mask,
+ builder.getBoolArrayAttr(inBounds));
+}
+
+/// Returns whether the view lays its elements out in the same (row-major) order
+/// as `parentShape`, the parent slot's vector shape, which is what makes a
+/// `vector.shape_cast` model it. These are the conditions under which
+/// `getReassociatedShape` below is meaningful, and it may be called only when
+/// they hold.
+///
+/// Collapsing is order preserving exactly when each reassociation group is
+/// contiguous in the source; that also admits a dynamic but contiguous source,
+/// such as a dynamic subview whose trailing dimensions are full.
+static bool isOrderPreservingReshape(memref::CollapseShapeOp op,
+ ArrayRef<int64_t> parentShape) {
+ SmallVector<ReassociationIndices, 4> groups = op.getReassociationIndices();
+ // The groups must partition the parent's dimensions, which they index into.
+ int64_t numGroupedDims = 0;
+ for (ReassociationIndices group : groups) {
+ for (int64_t dim : group) {
+ if (dim < 0 || dim >= static_cast<int64_t>(parentShape.size()))
+ return false;
+ }
+ numGroupedDims += group.size();
+ }
+ if (numGroupedDims != static_cast<int64_t>(parentShape.size()))
+ return false;
+
+ return memref::CollapseShapeOp::isGuaranteedCollapsible(
+ op.getSrcType(), op.getReassociationIndices());
+}
+
+/// An expansion only subdivides dimensions, so element order carries over once
+/// the source's own elements are row-major.
+///
+/// A STATIC source must have row-major strides -- read off the strides, since a
+/// subview always carries a strided layout: `[4, 16]` of `memref<8x16xf32>`
+/// qualifies (strides `[16, 1]`), `[4, 8]` does not (strides stay `[16, 1]`
+/// where `[8, 1]` is needed). Nothing more: a static view's alias takes the
+/// view's own shape.
+///
+/// A DYNAMIC source needs no strides. It qualifies as a supported dynamic view
+/// (`getUnderlyingDynamicSubView`), whose index space coincides with its static
+/// parent's.
+///
+/// Its alias, though, is shaped from the parent's extents, as if the view were
+/// as large as the parent -- and at runtime the view can be smaller. Take a
+/// `vector<8xf32>` parent and `[2, %m]`: the alias is always a `shape_cast` to
+/// `2 x 4`, while a subview extent of 4 makes the view `2 x 2`, so `%m` is 2,
+/// not 4. Row 1 then begins at element 4 in the alias and at element 2 in the
+/// view -- the same lane reads different data, which no mask can repair.
+///
+/// So the group rules below require every dimension ahead of the dynamic one to
+/// be unit: its index is then always 0, so a wrong extent behind it shifts
+/// nothing. `[%m, 2]` passes for that reason -- nothing precedes the dynamic
+/// dimension, and the `2` after it is the same in both shapes.
+static bool isOrderPreservingReshape(memref::ExpandShapeOp op,
+ ArrayRef<int64_t> parentShape) {
+ if (!memref::isStaticShapeAndContiguousRowMajor(op.getSrcType()) &&
+ !getUnderlyingDynamicSubView(op.getSrc()))
+ return false;
+
+ ArrayRef<int64_t> resShape = cast<MemRefType>(op.getType(...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/217793
More information about the Mlir-commits
mailing list