25#define GEN_PASS_DEF_ELIDEREINTERPRETCASTPASS
26#include "mlir/Dialect/MemRef/Transforms/Passes.h.inc"
39struct ResultNonUnitDimsAndOffsetsForRC {
44 std::optional<SmallVector<int64_t>> delinearizedOffsets;
49static std::optional<SmallVector<int64_t>>
50delinearizeStaticRCOffset(memref::ReinterpretCastOp rc) {
52 MemRefType srcType = dyn_cast<MemRefType>(rc.getSource().getType());
55 assert(rcOffsets.size() == 1 &&
"Expecting single offset");
57 assert(ShapedType::isStatic(rcOffsets[0]) &&
"expected static offset");
58 assert(rcOffsets[0] >= 0 &&
59 "static reinterpret_cast offset must be non-negative");
60 assert(srcType.getLayout().isIdentity() &&
61 "Expecting identity source layout.");
62 if (srcType.getRank() == 0) {
63 assert(rcOffsets[0] == 0 &&
64 "non-zero static offset is invalid for rank-0 source memref");
69 int64_t remainder = rcOffsets[0];
73 for (
auto [dim, stride] : llvm::enumerate(srcStrides)) {
74 offsetIdxs[dim] = remainder / stride;
75 assert(offsetIdxs[dim] < srcType.getDimSize(dim) &&
76 "static reinterpret_cast offset must delinearize to in-bounds "
81 assert(remainder == 0 &&
82 "Assuming identity source layout, the trailing stride == 1 "
83 "so, the remainder should be 0 at the end of index calculation.");
87static bool hasExactlyOneTruncatedNonUnitDim(memref::ReinterpretCastOp rc) {
88 MemRefType srcType = dyn_cast<MemRefType>(rc.getSource().getType());
89 MemRefType resType = dyn_cast<MemRefType>(rc.getType());
90 assert(srcType.hasStaticShape() && resType.hasStaticShape() &&
91 "expected static shapes");
92 assert(srcType.getRank() == resType.getRank() &&
93 "expected rank-preserving reinterpret_cast");
95 unsigned truncatedDims = 0;
97 for (
auto [srcSize, resSize] :
98 llvm::zip_equal(srcType.getShape(), resType.getShape())) {
99 if (srcSize == resSize)
103 if (srcSize != 1 && resSize < srcSize) {
114 return truncatedDims == 1;
118static std::optional<unsigned> getSingleNonUnitDim(MemRefType type) {
119 assert(type.hasStaticShape() &&
"expected static shape");
123 auto nonUnitDims = llvm::make_filter_range(
124 llvm::enumerate(
shape), [](
auto it) {
return it.value() != 1; });
127 if (llvm::range_size(nonUnitDims) != 1)
131 return (*nonUnitDims.begin()).index();
163static std::optional<ResultNonUnitDimsAndOffsetsForRC>
164getResultNonUnitDimsAndOffsetsForRC(memref::ReinterpretCastOp rc) {
165 MemRefType srcType = dyn_cast<MemRefType>(rc.getSource().getType());
166 MemRefType resType = dyn_cast<MemRefType>(rc.getType());
170 if (!srcType || !resType)
174 if (srcType.getRank() != resType.getRank())
178 if (!(srcType.hasStaticShape() && resType.hasStaticShape()))
183 if (!srcType.getLayout().isIdentity())
186 ResultNonUnitDimsAndOffsetsForRC dimsAndOffs;
190 for (
auto [dim, resultSize] : llvm::enumerate(resType.getShape())) {
192 dimsAndOffs.nonUnitDimsPos.push_back(
static_cast<unsigned>(dim));
198 assert(rcOffsets.size() == 1 &&
"Expecting single offset");
201 llvm::all_of(resType.getShape(), [](
int64_t size) { return size == 1; });
203 bool isOffsetDynamic = ShapedType::isDynamic(rcOffsets[0]);
216 if (!llvm::all_of(llvm::zip_equal(srcIdentityStrides, rcResultStrides),
218 auto [srcStride, resultStride] = pair;
219 return !ShapedType::isDynamic(resultStride) &&
220 srcStride == resultStride;
224 if (!hasExactlyOneTruncatedNonUnitDim(rc))
232 if (isOffsetDynamic) {
235 if (llvm::count_if(srcType.getShape(),
236 [](
int64_t size) { return size != 1; }) != 1)
245 dimsAndOffs.delinearizedOffsets = delinearizeStaticRCOffset(rc);
290 LogicalResult matchAndRewrite(memref::CopyOp op,
291 PatternRewriter &rewriter)
const final {
292 Value src = op.getSource();
293 MemRefType cpSrcType = cast<MemRefType>(src.
getType());
294 if (!cpSrcType || !cpSrcType.hasStaticShape())
295 return rewriter.notifyMatchFailure(
296 op,
"only ranked, static copy sources are supported.");
298 Value rcOutput = op.getTarget();
299 auto rc = rcOutput.
getDefiningOp<memref::ReinterpretCastOp>();
301 return rewriter.notifyMatchFailure(
302 op,
"target is not a memref.reinterpret_cast");
304 std::optional<ResultNonUnitDimsAndOffsetsForRC> dimsAndOffs =
305 getResultNonUnitDimsAndOffsetsForRC(rc);
307 return rewriter.notifyMatchFailure(
309 "unsupported reinterpret_cast result dimensions, strides, or offset");
311 Location loc = op.getLoc();
312 Value dst = rc.getSource();
313 MemRefType dstType = cast<MemRefType>(dst.
getType());
314 MemRefType rcResType = cast<MemRefType>(rc.getType());
319 if (ShapedType::isStatic(rc.getStaticOffsets()[0]) &&
320 llvm::any_of(llvm::enumerate(rcResType.getShape()), [&](
auto it) {
321 unsigned dim = it.index();
322 int64_t rcResultSize = it.value();
323 return (*dimsAndOffs->delinearizedOffsets)[dim] + rcResultSize >
324 dstType.getDimSize(dim);
326 return rewriter.notifyMatchFailure(op,
"copy accesses are OOB");
330 std::array<Value, 2> cachedIndexConstants;
331 auto getOrCreateIndexConstant = [&](int64_t value) -> Value {
332 if (value == 0 || value == 1) {
333 Value &cached = cachedIndexConstants[value];
341 auto getZeroIdxs = [&](int64_t rank) {
342 SmallVector<Value> idxs;
345 idxs.append(rank, getOrCreateIndexConstant(0));
351 SmallVector<Value> upperBounds;
352 upperBounds.reserve(dimsAndOffs->nonUnitDimsPos.size());
353 for (
unsigned dim : dimsAndOffs->nonUnitDimsPos) {
354 upperBounds.push_back(
355 getOrCreateIndexConstant(rcResType.getDimSize(dim)));
359 SmallVector<Value> rcSrcStoreIdxs = getZeroIdxs(dstType.getRank());
360 std::optional<unsigned> srcNonUnitDimPos;
361 if (dimsAndOffs->delinearizedOffsets) {
364 for (
auto [idx, offset] :
365 llvm::enumerate(*dimsAndOffs->delinearizedOffsets)) {
368 rcSrcStoreIdxs[idx] = getOrCreateIndexConstant(offset);
372 assert(dimsAndOffs->nonUnitDimsPos.size() <= 1 &&
373 "Expecting at most one non-unit result dimension.");
375 srcNonUnitDimPos = getSingleNonUnitDim(dstType);
376 assert(srcNonUnitDimPos &&
377 "Expecting single non-unit dimension source to receive the "
380 SmallVector<OpFoldResult> rcOffsets = rc.getMixedOffsets();
383 assert(rcOffsets.size() == 1 &&
"Expecting single offset");
385 rcSrcStoreIdxs[*srcNonUnitDimPos] =
392 OpBuilder::InsertionGuard guard(rewriter);
394 SmallVector<Value> loadIdxs = getZeroIdxs(cpSrcType.getRank());
395 SmallVector<Value> storeIdxs(rcSrcStoreIdxs);
397 if (!dimsAndOffs->nonUnitDimsPos.empty()) {
398 Value lowerBound = getOrCreateIndexConstant(0);
399 Value step = getOrCreateIndexConstant(1);
402 for (
auto [loopIndex, dim] :
403 llvm::enumerate(dimsAndOffs->nonUnitDimsPos)) {
404 scf::ForOp loop = scf::ForOp::create(rewriter, loc, lowerBound,
405 upperBounds[loopIndex], step);
407 rewriter.setInsertionPointToStart(loop.getBody());
409 Value iv = loop.getInductionVar();
415 if (storeIdxs[dim] == getOrCreateIndexConstant(0)) {
419 arith::AddIOp::create(rewriter, loc, storeIdxs[dim], iv);
426 Value val = memref::LoadOp::create(rewriter, loc, src, loadIdxs);
427 memref::StoreOp::create(rewriter, loc, val, dst, storeIdxs);
433 rewriter.eraseOp(op);
435 rewriter.eraseOp(rc);
444static bool hasStaticZeroOffset(memref::ReinterpretCastOp rc) {
448 assert(offsets.size() == 1 &&
"Expecting single offset");
449 return !ShapedType::isDynamic(offsets[0]) && offsets[0] == 0;
452static std::optional<int64_t> getConstantIndex(
Value v) {
462static bool isConstantIndexExplicitlyOutOfBounds(
Value idx,
465 std::optional<int64_t> idxVal = getConstantIndex(idx);
466 return idxVal && (*idxVal < 0 || *idxVal >= upperBound);
483static std::optional<NonUnitDimMapping>
484getNonUnitDimMapping(memref::ReinterpretCastOp rc) {
485 auto inputTy = cast<MemRefType>(rc.getSource().getType());
486 auto outputTy = cast<MemRefType>(rc.getResult().getType());
491 if (!hasStaticZeroOffset(rc))
496 if (llvm::any_of(rc.getStaticSizes(), ShapedType::isDynamic) ||
497 llvm::any_of(rc.getStaticStrides(), ShapedType::isDynamic))
504 int64_t inputRank = inputTy.getRank();
505 int64_t outputRank = outputTy.getRank();
506 NonUnitDimMapping mapping;
510 while (inputDim < inputRank || outputDim < outputRank) {
511 if (inputDim < inputRank && inputShape[inputDim] == 1) {
515 if (outputDim < outputRank && outputShape[outputDim] == 1) {
520 if (inputDim == inputRank || outputDim == outputRank)
523 if (ShapedType::isDynamic(inputShape[inputDim]) ||
524 ShapedType::isDynamic(outputShape[outputDim]) ||
525 inputShape[inputDim] != outputShape[outputDim])
528 mapping.push_back({inputDim, outputDim});
538[[maybe_unused]]
static bool areIndicesInBounds(memref::LoadOp
load) {
539 auto rc =
load.getMemRef().getDefiningOp<memref::ReinterpretCastOp>();
540 auto rcOutputTy = cast<MemRefType>(rc.getResult().getType());
542 for (
auto [pos, idx] : llvm::enumerate(
load.getIndices())) {
548 if (isConstantIndexExplicitlyOutOfBounds(idx, rcOutputTy.getDimSize(pos)))
574struct RewriteLoadFromReinterpretCast
579 LogicalResult matchAndRewrite(memref::LoadOp op,
580 PatternRewriter &rewriter)
const override {
581 auto rc = op.getMemRef().getDefiningOp<memref::ReinterpretCastOp>();
584 op,
"target is not a memref.reinterpret_cast");
585 std::optional<NonUnitDimMapping> dimMapping = getNonUnitDimMapping(rc);
588 op,
"reinterpret_cast is not a unit-dim insertion/removal preserving "
589 "non-unit dimensions");
591 assert(areIndicesInBounds(op) &&
592 "load from reinterpret_cast indexes out of bounds!");
594 auto rcInputTy = cast<MemRefType>(rc.getSource().getType());
596 int64_t rcInputRank = rcInputTy.getRank();
598 SmallVector<Value> oldIdxs(op.getIndices().begin(), op.getIndices().end());
602 for (Value idx : oldIdxs) {
603 std::optional<int64_t> idxVal = getConstantIndex(idx);
604 if (idxVal && *idxVal == 0) {
613 SmallVector<Value> rcInputIdxs(rcInputRank, zeroIndex);
614 for (
auto [inputDim, outputDim] : *dimMapping)
615 rcInputIdxs[inputDim] = oldIdxs[outputDim];
617 auto rcInput = rc.getSource();
620 if (rc.getResult().hasOneUse())
627struct ElideReinterpretCastPass
629 ElideReinterpretCastPass> {
630 void runOnOperation()
override {
633 RewritePatternSet patterns(&ctx);
635 ConversionTarget
target(ctx);
636 target.addDynamicallyLegalOp<memref::CopyOp>([](memref::CopyOp op) {
637 auto rc = op.getTarget().getDefiningOp<memref::ReinterpretCastOp>();
642 MemRefType cpSrcType = dyn_cast<MemRefType>(op.getSource().getType());
643 return !(cpSrcType && cpSrcType.hasStaticShape() &&
644 getResultNonUnitDimsAndOffsetsForRC(rc));
646 target.addDynamicallyLegalOp<memref::LoadOp>([](memref::LoadOp op) {
647 auto rc = op.getMemRef().getDefiningOp<memref::ReinterpretCastOp>();
650 return !getNonUnitDimMapping(rc);
652 target.addLegalDialect<arith::ArithDialect, memref::MemRefDialect,
654 if (
failed(applyPartialConversion(getOperation(),
target,
655 std::move(patterns))))
664 patterns.
add<CopyToLoadAndStore, RewriteLoadFromReinterpretCast>(
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
std::enable_if_t<!std::is_convertible< CallbackT, Twine >::value, LogicalResult > notifyMatchFailure(Location loc, CallbackT &&reasonCallback)
Used to notify the listener that the IR failed to be rewritten because of a match failure,...
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Type getType() const
Return the type of this value.
bool hasOneUse() const
Returns true if this value has exactly one use.
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Specialization of arith.constant op that returns an integer of index type.
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
void populateElideReinterpretCastPatterns(RewritePatternSet &patterns)
Collects a set of patterns that bypass memref.reinterpet_cast Ops.
Include the generated interface declarations.
SmallVector< int64_t > computeStrides(ArrayRef< int64_t > sizes)
Value getValueOrCreateConstantIndexOp(OpBuilder &b, Location loc, OpFoldResult ofr)
Converts an OpFoldResult to a Value.
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
OpRewritePattern(MLIRContext *context, PatternBenefit benefit=1, ArrayRef< StringRef > generatedNames={})
Patterns must specify the root operation name they match against, and can also specify the benefit of...