blob: 49f30a594e0ee70d87c8c869fa3b58c062ff7877 [file] [edit]
//===-ElideReinterpretCast.cpp - Expansion patterns for MemRef operations-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/Arith/Transforms/Passes.h"
#include "mlir/Dialect/Arith/Utils/Utils.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/Dialect/MemRef/Transforms/Transforms.h"
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/Dialect/Utils/IndexingUtils.h"
#include "mlir/IR/Matchers.h"
#include "mlir/IR/TypeUtilities.h"
#include "mlir/Transforms/DialectConversion.h"
#include <array>
#include <cassert>
#include <optional>
namespace mlir {
namespace memref {
#define GEN_PASS_DEF_ELIDEREINTERPRETCASTPASS
#include "mlir/Dialect/MemRef/Transforms/Passes.h.inc"
} // namespace memref
} // namespace mlir
using namespace mlir;
namespace {
//===----------------------------------------------------------------------===//
// Copy Rewrite Helpers
//===----------------------------------------------------------------------===//
/// Copy-relevant information derived from a reinterpret_cast.
struct ResultNonUnitDimsAndOffsetsForRC {
// Non-unit dimensions of the reinterpret_cast result.
SmallVector<unsigned> nonUnitDimsPos;
// Delinearized offsets to in-bounds reinterpret_cast source indices.
// Optional since it is only supported for static offsets.
std::optional<SmallVector<int64_t>> delinearizedOffsets;
};
/// Returns delinearized offset indices for a static reinterpret_cast offset of
/// an identity-layout source.
static std::optional<SmallVector<int64_t>>
delinearizeStaticRCOffset(memref::ReinterpretCastOp rc) {
ArrayRef<int64_t> rcOffsets = rc.getStaticOffsets();
MemRefType srcType = dyn_cast<MemRefType>(rc.getSource().getType());
// 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::isStatic(rcOffsets[0]) && "expected static offset");
assert(rcOffsets[0] >= 0 &&
"static reinterpret_cast offset must be non-negative");
assert(srcType.getLayout().isIdentity() &&
"Expecting identity source layout.");
if (srcType.getRank() == 0) {
assert(rcOffsets[0] == 0 &&
"non-zero static offset is invalid for rank-0 source memref");
return SmallVector<int64_t>{};
}
SmallVector<int64_t> offsetIdxs(srcType.getRank(), 0);
int64_t remainder = rcOffsets[0];
SmallVector<int64_t> srcStrides = computeStrides(srcType.getShape());
// Convert the scalar reinterpret_cast offset to per-dimension source starting
// indices.
for (auto [dim, stride] : llvm::enumerate(srcStrides)) {
offsetIdxs[dim] = remainder / stride;
assert(offsetIdxs[dim] < srcType.getDimSize(dim) &&
"static reinterpret_cast offset must delinearize to in-bounds "
"source indices");
remainder %= stride;
}
assert(remainder == 0 &&
"Assuming identity source layout, the trailing stride == 1 "
"so, the remainder should be 0 at the end of index calculation.");
return offsetIdxs;
}
static bool hasExactlyOneTruncatedNonUnitDim(memref::ReinterpretCastOp rc) {
MemRefType srcType = dyn_cast<MemRefType>(rc.getSource().getType());
MemRefType resType = dyn_cast<MemRefType>(rc.getType());
assert(srcType.hasStaticShape() && resType.hasStaticShape() &&
"expected static shapes");
assert(srcType.getRank() == resType.getRank() &&
"expected rank-preserving reinterpret_cast");
unsigned truncatedDims = 0;
for (auto [srcSize, resSize] :
llvm::zip_equal(srcType.getShape(), resType.getShape())) {
if (srcSize == resSize)
continue;
// Only one non-unit source dimension may be truncated.
if (srcSize != 1 && resSize < srcSize) {
++truncatedDims;
continue;
}
// The size change is not a supported single non-unit source dimension
// reduction.
return false;
}
// Make sure there is only one truncated dimension.
return truncatedDims == 1;
}
/// Returns the unique non-unit dim or nullopt if # non-unit-dims != 1.
static std::optional<unsigned> getSingleNonUnitDim(MemRefType type) {
assert(type.hasStaticShape() && "expected static shape");
ArrayRef<int64_t> shape = type.getShape();
// Find all non-unit dims
auto nonUnitDims = llvm::make_filter_range(
llvm::enumerate(shape), [](auto it) { return it.value() != 1; });
// Expect single non-unit dims
if (llvm::range_size(nonUnitDims) != 1)
return std::nullopt;
// Return the index of the unique non-unit dim.
return (*nonUnitDims.begin()).index();
}
/// Returns reinterpret_cast's result non-unit dimensions and, for static
/// offsets, delinearized offset.
///
/// Supports ranked, static-shape, rank-preserving reinterpret_casts from
/// identity-layout sources.
/// * Scalar-shaped results may have arbitrary result strides.
/// * Non-scalar results must have static offsets, static result strides
/// identical to the source identity strides, and exactly one non-unit
/// source dimension size truncated.
///
/// Returns nullopt for unsupported reinterpret_casts.
///
/// Examples that return info:
///
/// reinterpret_cast memref<1xMxNxf32, identity-layout>
/// to memref<1xMxKxf32, strided<[M*N, N, 1], offset: OFF>>
/// where K < N
///
/// reinterpret_cast memref<1xMxf32, identity-layout>
/// to memref<1x1xf32, strided<[?, ?], offset: ?>>
///
/// Examples that return no info:
///
/// reinterpret_cast memref<1xMxNxf32, identity-layout>
/// to memref<1xMxKxf32, strided<[?, N, 1]>>
///
/// reinterpret_cast memref<1xMxNxf32, identity-layout>
/// to memref<1xNxPxf32, strided<[M*N, N, 1], offset: OFF>>
/// where M != N && N != P
static std::optional<ResultNonUnitDimsAndOffsetsForRC>
getResultNonUnitDimsAndOffsetsForRC(memref::ReinterpretCastOp rc) {
MemRefType srcType = dyn_cast<MemRefType>(rc.getSource().getType());
MemRefType resType = dyn_cast<MemRefType>(rc.getType());
// Ranked memref types are required to statically build load/store index
// lists.
if (!srcType || !resType)
return std::nullopt;
// TODO: Support rank-modifying reinterpret_casts.
if (srcType.getRank() != resType.getRank())
return std::nullopt;
// TODO: Support dynamic shapes with mixed size operands as loop bounds.
if (!(srcType.hasStaticShape() && resType.hasStaticShape()))
return std::nullopt;
// TODO: Support non-identity source layouts by computing source strides from
// the layout map.
if (!srcType.getLayout().isIdentity())
return std::nullopt;
ResultNonUnitDimsAndOffsetsForRC dimsAndOffs;
// Track non-unit result dimensions; unit dimensions are
// always indexed at 0.
for (auto [dim, resultSize] : llvm::enumerate(resType.getShape())) {
if (resultSize != 1)
dimsAndOffs.nonUnitDimsPos.push_back(static_cast<unsigned>(dim));
}
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");
bool isScalarRes =
llvm::all_of(resType.getShape(), [](int64_t size) { return size == 1; });
bool isOffsetDynamic = ShapedType::isDynamic(rcOffsets[0]);
// Cases with at least one non-unit dimension in reinterpret_cast's result are
// restricted to preserving all but one dimension from the source, which
// collapsed to `1` in the result, and fully static metadata.
if (!isScalarRes) {
if (isOffsetDynamic)
return std::nullopt;
SmallVector<int64_t> srcIdentityStrides =
computeStrides(srcType.getShape());
ArrayRef<int64_t> rcResultStrides = rc.getStaticStrides();
if (!llvm::all_of(llvm::zip_equal(srcIdentityStrides, rcResultStrides),
[](auto pair) {
auto [srcStride, resultStride] = pair;
return !ShapedType::isDynamic(resultStride) &&
srcStride == resultStride;
}))
return std::nullopt;
if (!hasExactlyOneTruncatedNonUnitDim(rc))
return std::nullopt;
}
// CASE 1: Dynamic ReinterpretCast offset.
//
// Dynamic offsets are supported only for effectively-1D to scalar
// reinterpret_casts.
if (isOffsetDynamic) {
// With an effectively-1D source, a dynamic offset can be mapped to its
// unique non-unit dim. For other cases, bail out.
if (llvm::count_if(srcType.getShape(),
[](int64_t size) { return size != 1; }) != 1)
return std::nullopt;
return dimsAndOffs;
}
// CASE 2: Static ReinterpretCast offset
// Delinearize static ReinterpretCast offset as in-bounds indices (one for
// every source dimension).
dimsAndOffs.delinearizedOffsets = delinearizeStaticRCOffset(rc);
return dimsAndOffs;
}
/// Rewrites supported copy operations through `memref.reinterpret_cast` to
/// scalar load/store operations.
///
/// Supported cases:
/// 1. Scalar-shaped reinterpret_cast results. Result strides are ignored;
/// the store index is derived from the reinterpret_cast offset.
///
/// 2. Non-scalar reinterpret_cast results that preserve all non-unit source
/// dimensions sizes except one. Result
/// strides must be static and identical to the identity strides of the
/// source, and the offset must be static.
///
/// // BEFORE (scalar-shaped result)
/// %strided = memref.reinterpret_cast %dst
/// to offset: [OFF], sizes: [1, ..., 1], strides: [...]
/// memref.copy %src, %strided
///
/// // AFTER
/// %v = memref.load %src[0, ..., 0]
/// memref.store %v, %dst[delinearized(OFF)]
///
/// // BEFORE (one truncated non-unit dimension)
/// %strided = memref.reinterpret_cast %dst
/// to offset: [OFF], sizes: [1, M, K], strides: [M*N, N, 1]
/// : memref<1xMxNxf32>
/// to memref<1xMxKxf32, strided<[M*N, N, 1], offset: OFF>>
/// memref.copy %src, %strided
///
/// // AFTER
/// // Assuming OFF delinearizes to [0, 0, DELIN_OFF]:
/// scf.for %i = 0 to M step 1 {
/// scf.for %k = 0 to K step 1 {
/// %v = memref.load %src[0, %i, %k]
/// memref.store %v, %dst[0, %i, DELIN_OFF + %k]
/// }
/// }
struct CopyToLoadAndStore : public OpRewritePattern<memref::CopyOp> {
public:
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(memref::CopyOp op,
PatternRewriter &rewriter) const final {
Value src = op.getSource();
MemRefType cpSrcType = cast<MemRefType>(src.getType());
if (!cpSrcType || !cpSrcType.hasStaticShape())
return rewriter.notifyMatchFailure(
op, "only ranked, static copy sources are supported.");
Value rcOutput = op.getTarget();
auto rc = rcOutput.getDefiningOp<memref::ReinterpretCastOp>();
if (!rc)
return rewriter.notifyMatchFailure(
op, "target is not a memref.reinterpret_cast");
std::optional<ResultNonUnitDimsAndOffsetsForRC> dimsAndOffs =
getResultNonUnitDimsAndOffsetsForRC(rc);
if (!dimsAndOffs)
return rewriter.notifyMatchFailure(
op,
"unsupported reinterpret_cast result dimensions, strides, or offset");
Location loc = op.getLoc();
Value dst = rc.getSource();
MemRefType dstType = cast<MemRefType>(dst.getType());
MemRefType rcResType = cast<MemRefType>(rc.getType());
// Sanity check that the copy doesn't access strided MemRef out-of-bounds.
// Such cases should probably be rejected by Op verifier.
// FIXME: Add run-time verification for cases like this.
if (ShapedType::isStatic(rc.getStaticOffsets()[0]) &&
llvm::any_of(llvm::enumerate(rcResType.getShape()), [&](auto it) {
unsigned dim = it.index();
int64_t rcResultSize = it.value();
return (*dimsAndOffs->delinearizedOffsets)[dim] + rcResultSize >
dstType.getDimSize(dim);
}))
return rewriter.notifyMatchFailure(op, "copy accesses are OOB");
// Constant Op cache to reuse common index constants across bounds, steps,
// and static offsets: 0 is stored at index 0 and 1 is stored at index 1.
std::array<Value, 2> cachedIndexConstants;
auto getOrCreateIndexConstant = [&](int64_t value) -> Value {
if (value == 0 || value == 1) {
Value &cached = cachedIndexConstants[value];
if (!cached)
cached = arith::ConstantIndexOp::create(rewriter, loc, value);
return cached;
}
return arith::ConstantIndexOp::create(rewriter, loc, value);
};
auto getZeroIdxs = [&](int64_t rank) {
SmallVector<Value> idxs;
idxs.reserve(rank);
if (rank != 0)
idxs.append(rank, getOrCreateIndexConstant(0));
return idxs;
};
// Create loop bounds before moving the insertion point into the loop nest,
// so loop-invariant constants are emitted outside the generated loops.
SmallVector<Value> upperBounds;
upperBounds.reserve(dimsAndOffs->nonUnitDimsPos.size());
for (unsigned dim : dimsAndOffs->nonUnitDimsPos) {
upperBounds.push_back(
getOrCreateIndexConstant(rcResType.getDimSize(dim)));
}
// All indices are initialised to zero.
SmallVector<Value> rcSrcStoreIdxs = getZeroIdxs(dstType.getRank());
std::optional<unsigned> srcNonUnitDimPos;
if (dimsAndOffs->delinearizedOffsets) {
// Initialize store indices from the static reinterpret_cast offset,
// delinearized in function gating rewrite.
for (auto [idx, offset] :
llvm::enumerate(*dimsAndOffs->delinearizedOffsets)) {
if (offset == 0)
continue;
rcSrcStoreIdxs[idx] = getOrCreateIndexConstant(offset);
}
} else {
// Dynamic offset is used directly only for effectively-1D sources.
assert(dimsAndOffs->nonUnitDimsPos.size() <= 1 &&
"Expecting at most one non-unit result dimension.");
srcNonUnitDimPos = getSingleNonUnitDim(dstType);
assert(srcNonUnitDimPos &&
"Expecting single non-unit dimension source to receive the "
"dynamic offset.");
SmallVector<OpFoldResult> rcOffsets = rc.getMixedOffsets();
// FIXME: Despite what `getMixedOffsets` implies, `reinterpret_cast` takes
// only a single offset. That should be fixed at the op definition level.
assert(rcOffsets.size() == 1 && "Expecting single offset");
// Only the index corresponding to the single non-unit dim is updated.
rcSrcStoreIdxs[*srcNonUnitDimPos] =
getValueOrCreateConstantIndexOp(rewriter, loc, rcOffsets[0]);
}
// Create the loop nest and emit the load/store at the innermost insertion
// point.
{
OpBuilder::InsertionGuard guard(rewriter);
SmallVector<Value> loadIdxs = getZeroIdxs(cpSrcType.getRank());
SmallVector<Value> storeIdxs(rcSrcStoreIdxs);
if (!dimsAndOffs->nonUnitDimsPos.empty()) {
Value lowerBound = getOrCreateIndexConstant(0);
Value step = getOrCreateIndexConstant(1);
// Build one nested loop per non-unit reinterpret_cast result dimension.
for (auto [loopIndex, dim] :
llvm::enumerate(dimsAndOffs->nonUnitDimsPos)) {
scf::ForOp loop = scf::ForOp::create(rewriter, loc, lowerBound,
upperBounds[loopIndex], step);
rewriter.setInsertionPointToStart(loop.getBody());
Value iv = loop.getInductionVar();
// Since result strides match source identity strides dimension-wise,
// each IV indexes the same dimension in both the copy source and rc
// source.
loadIdxs[dim] = iv;
if (storeIdxs[dim] == getOrCreateIndexConstant(0)) {
storeIdxs[dim] = iv;
} else {
storeIdxs[dim] =
arith::AddIOp::create(rewriter, loc, storeIdxs[dim], iv);
}
}
}
// Emit the scalar load/store at the innermost loop body, or directly at
// the original copy location for scalar copies.
Value val = memref::LoadOp::create(rewriter, loc, src, loadIdxs);
memref::StoreOp::create(rewriter, loc, val, dst, storeIdxs);
}
// If the only user of `rc` is the current Op (which is about to be erased),
// we can safely erase it.
bool eraseRc = rcOutput.hasOneUse();
rewriter.eraseOp(op);
if (eraseRc)
rewriter.eraseOp(rc);
return success();
}
};
//===----------------------------------------------------------------------===//
// Load Rewrite Helpers
//===----------------------------------------------------------------------===//
static bool hasStaticZeroOffset(memref::ReinterpretCastOp rc) {
ArrayRef<int64_t> offsets = rc.getStaticOffsets();
// FIXME: Despite what `getStaticOffsets` implies, `reinterpret_cast` takes
// only a single offset. That should be fixed at the op definition level.
assert(offsets.size() == 1 && "Expecting single offset");
return !ShapedType::isDynamic(offsets[0]) && offsets[0] == 0;
}
static std::optional<int64_t> getConstantIndex(Value v) {
if (auto cst = v.getDefiningOp<arith::ConstantIndexOp>())
return cst.value();
// Non-constant and dynamic indices
return std::nullopt;
}
/// Return true if input index is in bounds, i.e. `0 <= idx < upperBound`.
/// Fully dynamic index values (i.e. non-constant) that cannot be analysed are
/// treated as in-bounds.
static bool isConstantIndexExplicitlyOutOfBounds(Value idx,
int64_t upperBound) {
// Only statically known `arith.constant` indices are checked here.
std::optional<int64_t> idxVal = getConstantIndex(idx);
return idxVal && (*idxVal < 0 || *idxVal >= upperBound);
}
using NonUnitDimMapping = SmallVector<std::pair<int64_t, int64_t>>;
/// Shape restriction accepting only unit-dim insertion/removal
/// reinterpret_casts.
///
/// Examples accepted:
/// memref<1x1x1x108xf32> <-> memref<1x108xf32>
/// memref<100x1xf32> <-> memref<100x1x1xf32>
/// memref<1x33x40xf32> <-> memref<33x1x1x40xf32>
/// memref<1> <-> memref<1x1x1>
///
/// Returns the mapping of non-unit dimensions from the source
/// to the result MemRef if the reinterpret_cast preserved sizes and order (no
/// transposition) of these dimensions.
static std::optional<NonUnitDimMapping>
getNonUnitDimMapping(memref::ReinterpretCastOp rc) {
auto inputTy = cast<MemRefType>(rc.getSource().getType());
auto outputTy = cast<MemRefType>(rc.getResult().getType());
// Only zero, statically known offsets are accepted. Non-zero or dynamic
// offsets would require reasoning about storage shifts in the underlying
// reinterpret_cast, which this helper does not model.
if (!hasStaticZeroOffset(rc))
return std::nullopt;
// Dynamic sizes/strides prevent precise reasoning about the underlying
// reinterpret_cast, so only fully static shape metadata is accepted.
if (llvm::any_of(rc.getStaticSizes(), ShapedType::isDynamic) ||
llvm::any_of(rc.getStaticStrides(), ShapedType::isDynamic))
return std::nullopt;
ArrayRef<int64_t> inputShape = inputTy.getShape();
ArrayRef<int64_t> outputShape = outputTy.getShape();
int64_t inputDim = 0;
int64_t outputDim = 0;
int64_t inputRank = inputTy.getRank();
int64_t outputRank = outputTy.getRank();
NonUnitDimMapping mapping;
// The preserved non-unit dimensions must have the same static sizes and
// appear in the same order.
while (inputDim < inputRank || outputDim < outputRank) {
if (inputDim < inputRank && inputShape[inputDim] == 1) {
++inputDim;
continue;
}
if (outputDim < outputRank && outputShape[outputDim] == 1) {
++outputDim;
continue;
}
if (inputDim == inputRank || outputDim == outputRank)
return std::nullopt;
if (ShapedType::isDynamic(inputShape[inputDim]) ||
ShapedType::isDynamic(outputShape[outputDim]) ||
inputShape[inputDim] != outputShape[outputDim])
return std::nullopt;
mapping.push_back({inputDim, outputDim});
++inputDim;
++outputDim;
}
return mapping;
}
/// Checks statically known and constant indices accessed by a load from a
/// unit-dim insertion/removal reinterpret_cast to ensure in-bounds only access.
/// Fully dynamic indices are skipped (there is no way to verify them).
[[maybe_unused]] static bool areIndicesInBounds(memref::LoadOp load) {
auto rc = load.getMemRef().getDefiningOp<memref::ReinterpretCastOp>();
auto rcOutputTy = cast<MemRefType>(rc.getResult().getType());
for (auto [pos, idx] : llvm::enumerate(load.getIndices())) {
// FIXME: This should be ensured by the memref.load semantics.
// In the long term, this sanity-check may live in the same debug-only
// checks as `MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS`. This rejects
// only explicit constant OOB indices. Dynamic/non-constant indices are not
// filtered here.
if (isConstantIndexExplicitlyOutOfBounds(idx, rcOutputTy.getDimSize(pos)))
return false;
}
return true;
}
/// Rewrites `memref.load` through a reinterpret_cast that only inserts/removes
/// unit dimensions by mapping the load indices directly onto the source MemRef.
///
/// Shape restriction gated by getNonUnitDimMapping().
///
/// BEFORE (rank expansion)
/// %view = memref.reinterpret_cast %src
/// : memref<1xNxMxf32> to memref<Nx1x1xMxf32>
/// %v = memref.load %view[%i, %c0, %c0, %j] : memref<Nx1x1xMxf32>
///
/// AFTER
/// %v = memref.load %src[%c0, %i, %j] : memref<1xNxMxf32>
///
/// BEFORE (rank collapsing)
/// %view = memref.reinterpret_cast %src
/// : memref<Nx1x1xMxf32> to memref<1xNxMxf32>
/// %v = memref.load %view[%c0, %i, %j] : memref<1xNxMxf32>
///
/// AFTER
/// %v = memref.load %src[%i, %c0, %c0, %j] : memref<Nx1x1xMxf32>
struct RewriteLoadFromReinterpretCast
: public OpRewritePattern<memref::LoadOp> {
public:
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(memref::LoadOp op,
PatternRewriter &rewriter) const override {
auto rc = op.getMemRef().getDefiningOp<memref::ReinterpretCastOp>();
if (!rc)
return rewriter.notifyMatchFailure(
op, "target is not a memref.reinterpret_cast");
std::optional<NonUnitDimMapping> dimMapping = getNonUnitDimMapping(rc);
if (!dimMapping)
return rewriter.notifyMatchFailure(
op, "reinterpret_cast is not a unit-dim insertion/removal preserving "
"non-unit dimensions");
assert(areIndicesInBounds(op) &&
"load from reinterpret_cast indexes out of bounds!");
auto rcInputTy = cast<MemRefType>(rc.getSource().getType());
int64_t rcInputRank = rcInputTy.getRank();
SmallVector<Value> oldIdxs(op.getIndices().begin(), op.getIndices().end());
// Prefer reusing an explicit constant-zero index from the old load.
Value zeroIndex;
for (Value idx : oldIdxs) {
std::optional<int64_t> idxVal = getConstantIndex(idx);
if (idxVal && *idxVal == 0) {
zeroIndex = idx;
break;
}
}
if (!zeroIndex)
zeroIndex = arith::ConstantIndexOp::create(rewriter, op.getLoc(), 0);
// Initialize new load indices to all 0s.
SmallVector<Value> rcInputIdxs(rcInputRank, zeroIndex);
for (auto [inputDim, outputDim] : *dimMapping)
rcInputIdxs[inputDim] = oldIdxs[outputDim];
auto rcInput = rc.getSource();
// If the only user of rc is the current Op (which is about to be erased),
// we can safely erase it.
if (rc.getResult().hasOneUse())
rewriter.eraseOp(rc);
rewriter.replaceOpWithNewOp<memref::LoadOp>(op, rcInput, rcInputIdxs);
return success();
}
};
struct ElideReinterpretCastPass
: public memref::impl::ElideReinterpretCastPassBase<
ElideReinterpretCastPass> {
void runOnOperation() override {
MLIRContext &ctx = getContext();
RewritePatternSet patterns(&ctx);
memref::populateElideReinterpretCastPatterns(patterns);
ConversionTarget target(ctx);
target.addDynamicallyLegalOp<memref::CopyOp>([](memref::CopyOp op) {
auto rc = op.getTarget().getDefiningOp<memref::ReinterpretCastOp>();
if (!rc)
return true;
// Pattern applies only when the copy source shape is static and the
// reinterpret_cast result can be mapped back to base memref indices.
MemRefType cpSrcType = dyn_cast<MemRefType>(op.getSource().getType());
return !(cpSrcType && cpSrcType.hasStaticShape() &&
getResultNonUnitDimsAndOffsetsForRC(rc));
});
target.addDynamicallyLegalOp<memref::LoadOp>([](memref::LoadOp op) {
auto rc = op.getMemRef().getDefiningOp<memref::ReinterpretCastOp>();
if (!rc)
return true;
return !getNonUnitDimMapping(rc);
});
target.addLegalDialect<arith::ArithDialect, memref::MemRefDialect,
scf::SCFDialect>();
if (failed(applyPartialConversion(getOperation(), target,
std::move(patterns))))
signalPassFailure();
}
};
} // namespace
void mlir::memref::populateElideReinterpretCastPatterns(
RewritePatternSet &patterns) {
patterns.add<CopyToLoadAndStore, RewriteLoadFromReinterpretCast>(
patterns.getContext());
}