[Mlir-commits] [mlir] [memref] Support non-scalar copies in `reinterpret_cast` elision (PR #203873)
ioana ghiban
llvmlistbot at llvm.org
Wed Jun 24 05:16:05 PDT 2026
================
@@ -29,128 +31,285 @@ using namespace mlir;
namespace {
-/// Returns true if `rc` represents a scalar view (all sizes == 1)
-/// into a memref that has exactly one non-unit dimension located at
-/// either the first or last position (i.e. a "row" or "column").
-///
-/// Examples that return true:
-///
-/// // Row-major slice (last dim is non-unit)
-/// memref.reinterpret_cast %buff to offset: [%off],
-/// sizes: [1, 1, 1], strides: [1, 1, 1]
-/// : memref<1x1x8xi32> to memref<1x1x1xi32>
+//===----------------------------------------------------------------------===//
+// Copy Rewrite Helpers
+//===----------------------------------------------------------------------===//
+
+/// Per-dimension loop nest info.
+struct CopyLoopDimInfo {
+ unsigned copyDim;
+ unsigned baseDim;
+ int64_t size;
+};
+
+/// Rewrite info from reinterpret_cast layout, captured after passing legality
+/// checks.
+struct CopyFromReinterCastInfo {
+ // Loop bounds that non-scalar loads "lower" to
+ SmallVector<CopyLoopDimInfo> loopDims;
+ // Deinearized offsets to in-bounds base indices.
+ std::optional<SmallVector<int64_t>> staticOffsetIdxs;
+ // reinterpret_cast dynamic offsets only supported for single non-unit
+ // dimension base, stored here to receive them.
+ std::optional<unsigned> dynamicOffsetDim;
+};
+
+/// Maps non-unit reinterpret_cast result dimensions to distinct base
+/// dimensions.
+static bool findBaseDimForResultDim(memref::ReinterpretCastOp rc,
+ CopyFromReinterCastInfo &info) {
+ MemRefType resType = dyn_cast<MemRefType>(rc.getType());
+ MemRefType baseType = dyn_cast<MemRefType>(rc.getSource().getType());
+ SmallVector<int64_t> baseIdentityStrides =
+ computeStrides(baseType.getShape());
+
+ // Each result loop IV is added directly to one base index. Reusing a base
+ // dimension would require delinearizing the combined linear offset.
+ SmallVector<bool> usedBaseDims(baseType.getRank(), false);
+
+ // Populate one loop-dimension entry for each non-unit result dimension.
+ for (auto [resultDim, resultSize] : llvm::enumerate(resType.getShape())) {
+ if (resultSize == 1)
+ continue;
+
+ // TODO: Support dynamic strides on copied dimensions.
+ if (ShapedType::isDynamic(rc.getStaticStrides()[resultDim]))
+ return false;
+
+ int64_t resultStride = rc.getStaticStrides()[resultDim];
+ std::optional<unsigned> baseDim;
+ // Find an unused base dimension with matching stride and enough elements.
+ for (auto [idx, stride] : llvm::enumerate(baseIdentityStrides)) {
+ if (usedBaseDims[idx] || stride != resultStride ||
+ baseType.getDimSize(idx) < resultSize)
+ continue;
+
+ if (!baseDim || baseType.getDimSize(idx) < baseType.getDimSize(*baseDim))
+ baseDim = idx;
+ }
+ if (!baseDim)
+ return false;
+
+ usedBaseDims[*baseDim] = true;
+ info.loopDims.push_back(CopyLoopDimInfo{static_cast<unsigned>(resultDim),
+ *baseDim, resultSize});
+ }
+ return true;
+}
+
+/// Returns base indices for a static reinterpret_cast offset.
+static std::optional<SmallVector<int64_t>>
+delinearizeStaticRCOffset(memref::ReinterpretCastOp rc) {
+ ArrayRef<int64_t> rcOffsets = rc.getStaticOffsets();
+ // FIXME: Despite what `getStaticOffsets` implies, `reinterpret_cast` takes
+ // only a single offset. That should be fixed at the op definition level.
+ assert(rcOffsets.size() == 1 && "Expecting single offset");
+ assert(!ShapedType::isDynamic(rcOffsets[0]) && "expected static offset");
+
+ if (rcOffsets[0] < 0)
+ return std::nullopt;
+
+ MemRefType baseType = dyn_cast<MemRefType>(rc.getSource().getType());
+ SmallVector<int64_t> indices(baseType.getRank(), 0);
+ int64_t remainder = rcOffsets[0];
+ SmallVector<int64_t> baseStrides = computeStrides(baseType.getShape());
+ for (auto [idx, stride] : llvm::enumerate(baseStrides)) {
+ indices[idx] = remainder / stride;
+ if (indices[idx] >= baseType.getDimSize(idx))
+ return std::nullopt;
+ remainder %= stride;
+ }
+
+ if (remainder != 0)
+ return std::nullopt;
+ return indices;
+}
+
+/// Returns the dimension whose static size is not one if it is unique.
+static std::optional<unsigned> getSingleNonUnitDim(MemRefType type) {
+ assert(type.hasStaticShape() && "expected static shape");
+ ArrayRef<int64_t> shape = type.getShape();
+ if (shape.empty())
+ return std::nullopt;
+
+ std::optional<unsigned> nonUnitDim;
+ for (auto [idx, dim] : llvm::enumerate(shape)) {
+ if (dim == 1)
+ continue;
+ if (nonUnitDim)
+ return std::nullopt;
+ nonUnitDim = idx;
+ }
+ return nonUnitDim;
+}
+
+/// Builds the index mapping needed to replace a copy into a reinterpret_cast
+/// strided memref with scalar stores into the reinterpret_cast base.
///
-/// // Column-major slice (first dim is non-unit)
-/// memref.reinterpret_cast %buff to offset: [%off],
-/// sizes: [1, 1], strides: [1, 1]
-/// : memref<2x1xf32> to memref<1x1xf32>
+/// Examples that return rewrite info:
///
-/// // Random strides
-/// memref.reinterpret_cast %buff to offset: [%off],
-/// sizes: [1, 1], strides: [10, 100]
-/// : memref<2x1xf32, strided<[10, 100]>>
-/// to memref<1x1xf32>
+/// // Scalar-shaped copy. There are no copied non-unit dimensions, so dynamic
+/// // strides in the strided memref do not affect index mapping.
+/// copy memref<1 x ... x 1 x f32>
+/// to reinterpret_cast memref<base-shape>
+/// to memref<1 x ... x 1 x f32, strided<[?, ..., ?], offset: ?>>
///
-/// // Rank-1 case
-/// memref.reinterpret_cast %buf to offset: [%off],
-/// sizes: [1], strides: [1]
-/// : memref<8xi32> to memref<1xi32>
+/// // Effectively-1D copy. The single non-unit strided memref dimension is
+/// // mapped to an identity-layout base dimension by its static stride.
+/// copy memref<1 x ... x N x ... x 1 x f32>
+/// to reinterpret_cast memref<base-shape>
+/// to memref<1 x ... x N x ... x 1 x f32, strided<[..., S, ...]>>
///
-/// Examples that return false:
+/// // Multidimensional copy with static offset. Each non-unit strided memref
+/// // dimension is mapped independently by its static stride.
+/// copy memref<1 x ... x N_0 x ... x N_K x ... x 1 x f32>
+/// to reinterpret_cast memref<base-shape>
+/// to memref<1 x ... x N_0 x ... x N_K x ... x 1 x f32,
+/// strided<[..., S_0, ..., S_1, ...], offset: O>>
///
-/// // More non-unit dims
-/// memref.reinterpret_cast %buff to offset: [%off],
-/// sizes: [1, 1, 1], strides: [1, 1, 1]
-/// : memref<1x2x8xi32> to memref<1x1x1xi32>
+/// Examples that return no info:
///
-/// // View is not scalar (size != 1)
-/// memref.reinterpret_cast %buff to offset: [%off],
-/// sizes: [2, 1], strides: [1, 1]
-/// : memref<1x2xf32> to memref<2x1xf32>
+/// // Dynamic stride on a copied strided memref dimension.
+/// copy memref<1xNxf32>
+/// to reinterpret_cast memref<1xNxMxf32>
+/// to memref<1xNxf32, strided<[?, ?]>>
///
-/// // Base has non-identity layout
-/// %buff = memref.alloc() : memref<1x2xf32, strided<[1, 3]>>
-/// memref.reinterpret_cast %buff to offset: [%off],
-/// sizes: [1, 1], strides: [1, 1]
-/// : memref<1x2xf32, strided<[1, 3]>> to memref<1x1xf32>
-static bool isScalarSlice(memref::ReinterpretCastOp rc) {
- auto rcInputTy = dyn_cast<MemRefType>(rc.getSource().getType());
- auto rcOutputTy = dyn_cast<MemRefType>(rc.getType());
-
- // Reject strided base - logic for computing linear idx is TODO
- if (!rcInputTy.getLayout().isIdentity())
- return false;
+/// // Multidimensional copy with dynamic linear offset.
+/// copy memref<1xNxKxf32>
+/// to reinterpret_cast memref<1xNxMxf32>
+/// to memref<1xNxKxf32, strided<[N*M, M, 1], offset: ?>>
+static std::optional<CopyFromReinterCastInfo>
+getCopyFromReinterCastInfo(memref::CopyOp copy, memref::ReinterpretCastOp rc) {
+ MemRefType cpSrcType = dyn_cast<MemRefType>(copy.getSource().getType());
+ MemRefType rcBaseType = dyn_cast<MemRefType>(rc.getSource().getType());
+ MemRefType rcResType = dyn_cast<MemRefType>(rc.getType());
+
+ // Ranked memref types are required to statically build load/store index
+ // lists.
+ if (!cpSrcType || !rcBaseType || !rcResType)
+ return std::nullopt;
----------------
ioghiban wrote:
Currently no. Will add unranked memref test case
https://github.com/llvm/llvm-project/pull/203873
More information about the Mlir-commits
mailing list