CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
491 lines
19 KiB
C++
491 lines
19 KiB
C++
//===-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/IR/Matchers.h"
|
|
#include "mlir/IR/TypeUtilities.h"
|
|
#include "mlir/Transforms/DialectConversion.h"
|
|
#include "llvm/ADT/Repeated.h"
|
|
#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 {
|
|
|
|
/// 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>
|
|
///
|
|
/// // 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>
|
|
///
|
|
/// // Random strides
|
|
/// memref.reinterpret_cast %buff to offset: [%off],
|
|
/// sizes: [1, 1], strides: [10, 100]
|
|
/// : memref<2x1xf32, strided<[10, 100]>>
|
|
/// to memref<1x1xf32>
|
|
///
|
|
/// // Rank-1 case
|
|
/// memref.reinterpret_cast %buf to offset: [%off],
|
|
/// sizes: [1], strides: [1]
|
|
/// : memref<8xi32> to memref<1xi32>
|
|
///
|
|
/// Examples that return false:
|
|
///
|
|
/// // More non-unit dims
|
|
/// memref.reinterpret_cast %buff to offset: [%off],
|
|
/// sizes: [1, 1, 1], strides: [1, 1, 1]
|
|
/// : memref<1x2x8xi32> to memref<1x1x1xi32>
|
|
///
|
|
/// // View is not scalar (size != 1)
|
|
/// memref.reinterpret_cast %buff to offset: [%off],
|
|
/// sizes: [2, 1], strides: [1, 1]
|
|
/// : memref<1x2xf32> to memref<2x1xf32>
|
|
///
|
|
/// // 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;
|
|
|
|
// Reject non-matching ranks
|
|
unsigned srcRank = rcInputTy.getRank();
|
|
if (srcRank != rcOutputTy.getRank())
|
|
return false;
|
|
|
|
ArrayRef<int64_t> sizes = rc.getStaticSizes();
|
|
|
|
// View must be scalar: memref<1x...x1>
|
|
if (!llvm::all_of(rcOutputTy.getShape(),
|
|
[](int64_t dim) { return dim == 1; }))
|
|
return false;
|
|
|
|
// Sizes must all be statically 1
|
|
if (!llvm::all_of(sizes, [](int64_t size) {
|
|
return !ShapedType::isDynamic(size) && size == 1;
|
|
}))
|
|
return false;
|
|
|
|
// Rank-1 special case
|
|
if (srcRank == 1) {
|
|
// Reject non-scalar output
|
|
if (rcOutputTy.getDimSize(0) > 1)
|
|
return false;
|
|
}
|
|
|
|
int nonUnitCount =
|
|
std::count_if(rcInputTy.getShape().begin(), rcInputTy.getShape().end(),
|
|
[](int dim) { return dim != 1; });
|
|
return nonUnitCount == 1;
|
|
}
|
|
|
|
/// Rewrites `memref.copy` of a 1-element MemRef as a scalar load-store pair
|
|
///
|
|
/// The pattern matches a reinterpret_cast that creates a scalar view
|
|
/// (`sizes = [1, ..., 1]`) into a memref with a single non-unit dimension.
|
|
/// Since the view contains only one element, the accessed address is
|
|
/// determined solely by the base pointer and the offset.
|
|
///
|
|
/// Two layouts are supported:
|
|
/// * row-major slice (stride pattern [N, ..., 1])
|
|
/// * column-major slice (stride pattern [1, ..., N])
|
|
///
|
|
/// BEFORE (row-major slice)
|
|
/// %view = memref.reinterpret_cast %base
|
|
/// to offset: [%off], sizes: [1, ..., 1], strides: [N, ..., 1]
|
|
/// : memref<1x...xNxf32>
|
|
/// to memref<1x...x1xf32, strided<[N, ..., 1], offset: ?>>
|
|
/// memref.copy %src, %view
|
|
/// : memref<1x...x1xf32>
|
|
/// to memref<1x...x1xf32, strided<[N, ..., 1], offset: ?>>
|
|
///
|
|
/// AFTER
|
|
/// %c0 = arith.constant 0 : index
|
|
/// %v = memref.load %src[%c0, ..., %c0] : memref<1x...x1xf32>
|
|
/// memref.store %v, %base[%c0, ..., %off] : memref<1x...xNxf32>
|
|
///
|
|
/// BEFORE (column-major slice)
|
|
/// %view = memref.reinterpret_cast %base
|
|
/// to offset: [%off], sizes: [1, ..., 1], strides: [1, ..., N]
|
|
/// : memref<Nx...x1xf32>
|
|
/// to memref<1x...x1xf32, strided<[1, ..., N], offset: ?>>
|
|
/// memref.copy %src, %view
|
|
/// : memref<1x...x1xf32>
|
|
/// to memref<1x...x1xf32, strided<[1, ..., N], offset: ?>>
|
|
///
|
|
/// AFTER
|
|
/// %c0 = arith.constant 0 : index
|
|
/// %v = memref.load %src[%c0, ..., %c0] : memref<1x...x1xf32>
|
|
/// memref.store %v, %base[%off, ..., %c0] : memref<Nx...x1xf32>
|
|
struct CopyToScalarLoadAndStore : public OpRewritePattern<memref::CopyOp> {
|
|
public:
|
|
using OpRewritePattern::OpRewritePattern;
|
|
|
|
LogicalResult matchAndRewrite(memref::CopyOp op,
|
|
PatternRewriter &rewriter) const final {
|
|
Value rcOutput = op.getTarget();
|
|
auto rc = rcOutput.getDefiningOp<memref::ReinterpretCastOp>();
|
|
if (!rc)
|
|
return rewriter.notifyMatchFailure(
|
|
op, "target is not a memref.reinterpret_cast");
|
|
|
|
if (!isScalarSlice(rc))
|
|
return rewriter.notifyMatchFailure(
|
|
op, "reinterpret_cast does not match scalar slice");
|
|
|
|
Location loc = op.getLoc();
|
|
|
|
Value src = op.getSource();
|
|
Value dst = rc.getSource();
|
|
|
|
auto dstType = cast<MemRefType>(dst.getType());
|
|
unsigned dstRank = dstType.getRank();
|
|
|
|
Value zero = arith::ConstantIndexOp::create(rewriter, loc, 0);
|
|
|
|
auto srcType = cast<MemRefType>(src.getType());
|
|
Repeated<Value> loadIndices(srcType.getRank(), zero);
|
|
auto offsets = rc.getMixedOffsets();
|
|
assert(offsets.size() == 1 && "Expecting single offset");
|
|
OpFoldResult offset = offsets[0];
|
|
Value storeOffset = getValueOrCreateConstantIndexOp(rewriter, loc, offset);
|
|
unsigned offsetDim = dstType.getDimSize(0) == 1 ? dstRank - 1 : 0;
|
|
SmallVector<Value> storeIndices(dstRank, zero);
|
|
storeIndices[offsetDim] = storeOffset;
|
|
// If the only user of `rc` is the current Op (which is about to be erased),
|
|
// we can safely erase it.
|
|
if (rcOutput.hasOneUse())
|
|
rewriter.eraseOp(rc);
|
|
|
|
Value val = memref::LoadOp::create(rewriter, loc, src, loadIndices);
|
|
memref::StoreOp::create(rewriter, loc, val, dst, storeIndices);
|
|
|
|
rewriter.eraseOp(op);
|
|
return success();
|
|
}
|
|
};
|
|
|
|
/// Captures info about MemRefs that are effectively 1D (the leading or trailing
|
|
/// dims are all 1). The only accepted non-unit dim is either the leading of the
|
|
/// trailing dim.
|
|
///
|
|
/// Examples:
|
|
/// memref<1x1x4xf32>, memref<4x1x1xf32>, memref<1x1x1xf32>
|
|
///
|
|
struct ShapeInfoFor1DMemRef {
|
|
// Are all dims == 1? `false` means that there is exactly one dim != 1.
|
|
bool allOnes = true;
|
|
// If there is a non-unit boundary dim, is it the leading or the trailing dim?
|
|
bool isLeadingDimNonUnit = false;
|
|
};
|
|
|
|
/// Returns information about a MemRef if it contains at most one non-unit
|
|
/// dimension.
|
|
///
|
|
/// The single non-unit dimension, if present, must be on the left or right
|
|
/// boundary. Rank-1 non-unit MemRefs are treated as being on both boundaries.
|
|
static std::optional<ShapeInfoFor1DMemRef>
|
|
getShapeInfoFor1DMemRef(MemRefType type) {
|
|
ArrayRef<int64_t> shape = type.getShape();
|
|
int64_t nonUnitCount =
|
|
llvm::count_if(shape, [](int64_t dim) { return dim != 1; });
|
|
// Return default values if missing non-unit dimension (all-ones MemRef).
|
|
if (nonUnitCount == 0)
|
|
return ShapeInfoFor1DMemRef{};
|
|
// Return no info if MemRef has more non-unit dimensions.
|
|
if (nonUnitCount > 1)
|
|
return std::nullopt;
|
|
// Return no info if MemRef has non-unit dimension in non-boundary positions.
|
|
if (shape.front() == 1 && shape.back() == 1)
|
|
return std::nullopt;
|
|
|
|
return ShapeInfoFor1DMemRef{/*allOnes=*/false,
|
|
/*isLeadingDimNonUnit=*/shape.front() != 1};
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/// Examples accepted by this shape restriction:
|
|
/// memref<999xf32> <-> memref<1x1x999xf32>
|
|
/// memref<1x108xf32> <-> memref<1x1x1x108xf32>
|
|
/// memref<100x1xf32> <-> memref<100x1x1xf32>
|
|
/// memref<1> <-> memref<1x1x1>
|
|
///
|
|
/// General reinterpret_casts are intentionally rejected.
|
|
static bool isPureRankExpansionOrCollapsingRC(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 false;
|
|
|
|
// 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 false;
|
|
|
|
// Only shapes with at most one non-unit dimension are accepted. This rules
|
|
// out more general multi-dimensional reinterpret_casts and restricts the
|
|
// helper to unit-dim insertion/removal around a single logical dimension.
|
|
std::optional<ShapeInfoFor1DMemRef> inputNonUnitDim =
|
|
getShapeInfoFor1DMemRef(inputTy);
|
|
std::optional<ShapeInfoFor1DMemRef> outputNonUnitDim =
|
|
getShapeInfoFor1DMemRef(outputTy);
|
|
// Bail out if either type does not satisfy the single-boundary-non-unit-dim
|
|
// restriction described above.
|
|
if (!inputNonUnitDim || !outputNonUnitDim)
|
|
return false;
|
|
|
|
// The source and result must either both have a single non-unit dimension
|
|
// or both be all-ones.
|
|
if (inputNonUnitDim->allOnes != outputNonUnitDim->allOnes)
|
|
return false;
|
|
if (inputNonUnitDim->allOnes)
|
|
return true;
|
|
|
|
// The preserved non-unit dimension must have the same size.
|
|
if (inputTy.getDimSize(
|
|
inputNonUnitDim->isLeadingDimNonUnit ? 0 : inputTy.getRank() - 1) !=
|
|
outputTy.getDimSize(
|
|
outputNonUnitDim->isLeadingDimNonUnit ? 0 : outputTy.getRank() - 1))
|
|
return false;
|
|
|
|
// If both sides have rank > 1, the non-unit dimension must be on the same
|
|
// boundary. Rank-1 MemRefs are accepted against either boundary.
|
|
if (inputTy.getRank() != 1 && outputTy.getRank() != 1 &&
|
|
inputNonUnitDim->isLeadingDimNonUnit !=
|
|
outputNonUnitDim->isLeadingDimNonUnit)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Checks statically known and constant indices accessed by a load from a pure
|
|
/// rank expansion/collapsing 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 pure rank-only `reinterpret_cast` by
|
|
/// mapping the load indices directly onto the source MemRef.
|
|
|
|
/// Shape restriction gated by isPureRankExpansionOrCollapsingRC().
|
|
///
|
|
/// BEFORE (rank expansion)
|
|
/// %view = memref.reinterpret_cast %src
|
|
/// : memref<Nxf32> to memref<1x1xNxf32>
|
|
/// %v = memref.load %view[%c0, %c0, %i] : memref<1x1xNxf32>
|
|
///
|
|
/// AFTER
|
|
/// %v = memref.load %src[%i] : memref<Nxf32>
|
|
///
|
|
/// BEFORE (rank collapsing)
|
|
/// %view = memref.reinterpret_cast %src
|
|
/// : memref<1x1xNxf32> to memref<Nxf32>
|
|
/// %v = memref.load %view[%i] : memref<Nxf32>
|
|
///
|
|
/// AFTER
|
|
/// %c0 = arith.constant 0 : index
|
|
/// %v = memref.load %src[%c0, %c0, %i] : memref<1x1xNxf32>
|
|
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");
|
|
if (!isPureRankExpansionOrCollapsingRC(rc))
|
|
return rewriter.notifyMatchFailure(
|
|
op, "reinterpret_cast is not a pure rank expansion or collapsing of "
|
|
"a single dimension");
|
|
|
|
assert(areIndicesInBounds(op) &&
|
|
"load from reinterpret_cast indexes out of bounds!");
|
|
|
|
auto rcOutputTy = cast<MemRefType>(rc.getResult().getType());
|
|
auto rcInputTy = cast<MemRefType>(rc.getSource().getType());
|
|
|
|
int64_t rcOutputRank = rcOutputTy.getRank();
|
|
int64_t rcInputRank = rcInputTy.getRank();
|
|
|
|
SmallVector<Value> idxs(op.getIndices().begin(), op.getIndices().end());
|
|
SmallVector<Value> rcInputIdxs;
|
|
rcInputIdxs.reserve(rcInputRank);
|
|
|
|
// The rewrite only supports reinterpret_casts with at most one non-unit
|
|
// dimension, located at the left or right boundary.
|
|
//
|
|
// The higher-rank side tells which side the reinterpret_cast has
|
|
// expanded/collapsed.
|
|
//
|
|
// expansion: rcOutput has the higher rank
|
|
// collapsing : rcInput has the higher rank
|
|
//
|
|
// Example:
|
|
// memref<999> -> memref<1x1x999> : leading extra dims
|
|
// memref<999x1x1> -> memref<999> : trailing extra dims
|
|
MemRefType expandedTy =
|
|
rcOutputRank >= rcInputRank ? rcOutputTy : rcInputTy;
|
|
std::optional<ShapeInfoFor1DMemRef> expandedNonUnitDim =
|
|
getShapeInfoFor1DMemRef(expandedTy);
|
|
assert(expandedNonUnitDim && "expected a single boundary non-unit dim");
|
|
bool keepLeadingIndices = expandedNonUnitDim->isLeadingDimNonUnit;
|
|
|
|
if (rcOutputRank >= rcInputRank) {
|
|
// Rank expansion:
|
|
// memref<N> -> memref<1x1xN> : keep the last rcInputRank indices
|
|
// memref<N> -> memref<Nx1x1> : keep the first rcInputRank indices
|
|
// memref<1> -> memref<1x1x1> : all indices are zero
|
|
//
|
|
// Any discarded indices are known to be zero from
|
|
// areIndicesInBounds().
|
|
int64_t firstKeptPos =
|
|
keepLeadingIndices ? 0 : rcOutputRank - rcInputRank;
|
|
rcInputIdxs.append(idxs.begin() + firstKeptPos,
|
|
idxs.begin() + firstKeptPos + rcInputRank);
|
|
} else {
|
|
// Rank collapsing:
|
|
// memref<1x1xN> -> memref<N> : reinsert leading zeros
|
|
// memref<Nx1x1> -> memref<N> : reinsert trailing zeros
|
|
// memref<1x1x1> -> memref<1> : all indices are zero
|
|
//
|
|
// The collapsed-away dimensions are unit dims, so re-adding them with
|
|
// zero indices preserves semantics.
|
|
Value c0 = arith::ConstantIndexOp::create(rewriter, op.getLoc(), 0);
|
|
int64_t rankDiff = rcInputRank - rcOutputRank;
|
|
|
|
if (keepLeadingIndices) {
|
|
rcInputIdxs.append(idxs.begin(), idxs.end());
|
|
rcInputIdxs.append(rankDiff, c0);
|
|
} else {
|
|
rcInputIdxs.append(rankDiff, c0);
|
|
rcInputIdxs.append(idxs.begin(), idxs.end());
|
|
}
|
|
}
|
|
|
|
assert(rcInputIdxs.size() == static_cast<size_t>(rcInputRank) &&
|
|
"Incorrect number of indices!");
|
|
|
|
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;
|
|
return !isScalarSlice(rc);
|
|
});
|
|
target.addDynamicallyLegalOp<memref::LoadOp>([](memref::LoadOp op) {
|
|
auto rc = op.getMemRef().getDefiningOp<memref::ReinterpretCastOp>();
|
|
if (!rc)
|
|
return true;
|
|
return !isPureRankExpansionOrCollapsingRC(rc);
|
|
});
|
|
target.addLegalDialect<arith::ArithDialect, memref::MemRefDialect>();
|
|
if (failed(applyPartialConversion(getOperation(), target,
|
|
std::move(patterns))))
|
|
signalPassFailure();
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
void mlir::memref::populateElideReinterpretCastPatterns(
|
|
RewritePatternSet &patterns) {
|
|
patterns.add<CopyToScalarLoadAndStore, RewriteLoadFromReinterpretCast>(
|
|
patterns.getContext());
|
|
}
|