#include "mlir/Dialect/Affine/IR/AffineOps.h"
#include "mlir/Dialect/Linalg/IR/Linalg.h"
#include "mlir/Dialect/Linalg/Transforms/Transforms.h"
#include "mlir/Dialect/Linalg/Utils/Utils.h"
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/Dialect/UB/IR/UBOps.h"
#include "mlir/Dialect/Utils/IndexingUtils.h"
#include "mlir/IR/Dominance.h"
#include "mlir/IR/TypeUtilities.h"
#include "llvm/ADT/SetOperations.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/TypeSwitch.h"
#include "llvm/Support/Debug.h"
#include <optional>
namespace mlir {
#define GEN_PASS_DEF_LINALGDATALAYOUTPROPAGATION
#include "mlir/Dialect/Linalg/Passes.h.inc"
}
using namespace mlir;
using namespace mlir::linalg;
#define DEBUG_TYPE "linalg-data-layout-propagation"
namespace {
static bool hasGatherSemantics(linalg::GenericOp genericOp) {
for (Operation &op : genericOp.getBody()->getOperations())
if (isa<tensor::ExtractOp, linalg::IndexOp>(op))
return true;
return false;
}
struct PackInfo {
int64_t getNumTiledLoops() const { return tileToPointMapping.size(); };
SmallVector<int64_t> tiledDimsPos;
llvm::DenseMap<int64_t, OpFoldResult> domainDimAndTileMapping;
llvm::DenseMap<int64_t, int64_t> tileToPointMapping;
SmallVector<int64_t> outerDimsOnDomainPerm;
};
template <typename OpTy>
static FailureOr<PackInfo>
getPackingInfoFromOperand(OpOperand *opOperand, linalg::GenericOp genericOp,
OpTy packOrUnPackOp) {
static_assert(llvm::is_one_of<OpTy, linalg::PackOp, linalg::UnPackOp>::value,
"applies to only pack or unpack operations");
LLVM_DEBUG(
{ llvm::dbgs() << "--- Construct PackInfo From an operand ---\n"; });
AffineMap indexingMap = genericOp.getMatchingIndexingMap(opOperand);
SmallVector<AffineMap> indexingMaps = genericOp.getIndexingMapsArray();
SmallVector<utils::IteratorType> iterators =
genericOp.getIteratorTypesArray();
PackInfo packInfo;
int64_t origNumDims = indexingMap.getNumDims();
SmallVector<AffineExpr> exprs(indexingMap.getResults());
ArrayRef<int64_t> innerDimsPos = packOrUnPackOp.getInnerDimsPos();
for (auto [index, innerDimPos, tileSize] :
llvm::zip_equal(llvm::seq<unsigned>(0, innerDimsPos.size()),
innerDimsPos, packOrUnPackOp.getMixedTiles())) {
auto expr = exprs[innerDimPos];
if (!isa<AffineDimExpr>(expr))
return failure();
int64_t domainDimPos =
cast<AffineDimExpr>(exprs[innerDimPos]).getPosition();
if (!isParallelIterator(iterators[domainDimPos]))
return failure();
packInfo.tiledDimsPos.push_back(domainDimPos);
packInfo.domainDimAndTileMapping[domainDimPos] = tileSize;
packInfo.tileToPointMapping[domainDimPos] = origNumDims + index;
LLVM_DEBUG({
llvm::dbgs() << "map innerDimPos=" << innerDimPos
<< " to iteration dimension (d" << domainDimPos << ", d"
<< packInfo.tileToPointMapping[domainDimPos]
<< "), which has size=("
<< packInfo.domainDimAndTileMapping[domainDimPos] << ")\n";
});
}
auto areAllAffineDimExpr = [&](int dim) {
for (AffineMap map : indexingMaps) {
if (llvm::any_of(map.getResults(), [dim](AffineExpr expr) {
return expr.isFunctionOfDim(dim) && !isa<AffineDimExpr>(expr);
})) {
return false;
}
}
return true;
};
for (int64_t i : packInfo.tiledDimsPos)
if (!areAllAffineDimExpr(i))
return failure();
SmallVector<int64_t> permutedOuterDims;
for (auto [index, dim] : llvm::enumerate(packOrUnPackOp.getOuterDimsPerm())) {
auto permutedExpr = indexingMap.getResult(dim);
if (auto dimExpr = dyn_cast<AffineDimExpr>(permutedExpr)) {
permutedOuterDims.push_back(dimExpr.getPosition());
continue;
}
if (static_cast<int64_t>(index) != dim)
return failure();
}
if (!permutedOuterDims.empty()) {
int64_t outerDimIndex = 0;
llvm::DenseSet<int64_t> permutedDomainDims(permutedOuterDims.begin(),
permutedOuterDims.end());
for (int i = 0, e = indexingMap.getNumDims(); i < e; i++)
packInfo.outerDimsOnDomainPerm.push_back(
permutedDomainDims.contains(i) ? permutedOuterDims[outerDimIndex++]
: i);
LLVM_DEBUG({
llvm::dbgs() << "map outer dimsDimsPerm to ";
for (auto dim : packInfo.outerDimsOnDomainPerm)
llvm::dbgs() << dim << " ";
llvm::dbgs() << "\n";
});
}
return packInfo;
}
static SmallVector<int64_t> computeOuterDims(ArrayRef<int64_t> perm,
ArrayRef<AffineExpr> exprs) {
assert(!perm.empty() && "expect perm not to be empty");
assert(!exprs.empty() && "expect exprs not to be empty");
if (exprs.size() == 1)
return {};
SmallVector<int64_t> outerDimsPerm;
DenseMap<int64_t, int64_t> currentPositionTileLoops;
for (auto [pos, expr] : llvm::enumerate(exprs)) {
if (auto dimExpr = dyn_cast<AffineDimExpr>(expr))
currentPositionTileLoops[dimExpr.getPosition()] = pos;
else
currentPositionTileLoops[pos] = pos;
}
for (int64_t loopIdx : perm) {
if (currentPositionTileLoops.count(loopIdx))
outerDimsPerm.push_back(currentPositionTileLoops.lookup(loopIdx));
}
return outerDimsPerm;
}
struct PackedOperandDetails {
SmallVector<OpFoldResult> innerTileSizes;
SmallVector<int64_t> innerDimsPos;
SmallVector<int64_t> outerDimsPerm;
AffineMap indexingMap;
};
static bool getPackedOperandDetails(
OpBuilder &b, PackInfo packInfo, GenericOp genericOp, OpOperand *opOperand,
DenseMap<OpOperand *, PackedOperandDetails> &packedOperandMap) {
PackedOperandDetails currOperandDetails;
int64_t numOrigLoops = genericOp.getNumLoops();
int64_t numInnerLoops = packInfo.getNumTiledLoops();
int64_t numLoops = numOrigLoops + numInnerLoops;
AffineMap origIndexingMap = genericOp.getMatchingIndexingMap(opOperand);
llvm::DenseMap<int64_t, int64_t> domainDimToOperandDim;
SmallVector<AffineExpr> exprs(origIndexingMap.getResults());
if (genericOp.isScalar(opOperand) || exprs.empty()) {
currOperandDetails.indexingMap =
AffineMap::get(numLoops, 0, exprs, b.getContext());
packedOperandMap[opOperand] = currOperandDetails;
return false;
}
for (auto [index, expr] : llvm::enumerate(exprs)) {
if (auto dimExpr = dyn_cast<AffineDimExpr>(expr)) {
int64_t dimPos = dimExpr.getPosition();
domainDimToOperandDim[dimPos] = index;
continue;
}
}
SmallVector<int64_t> innerDimsPos;
SmallVector<OpFoldResult> innerTileSizes;
for (auto dimPos : packInfo.tiledDimsPos) {
if (!domainDimToOperandDim.count(dimPos))
continue;
int64_t index = domainDimToOperandDim[dimPos];
innerTileSizes.push_back(packInfo.domainDimAndTileMapping[dimPos]);
innerDimsPos.push_back(index);
exprs.push_back(b.getAffineDimExpr(packInfo.tileToPointMapping[dimPos]));
}
SmallVector<int64_t> outerDimsPerm;
if (!packInfo.outerDimsOnDomainPerm.empty()) {
outerDimsPerm = computeOuterDims(packInfo.outerDimsOnDomainPerm, exprs);
SmallVector<int64_t> inversedOuterPerm =
invertPermutationVector(packInfo.outerDimsOnDomainPerm);
for (auto i : llvm::seq<unsigned>(0, origIndexingMap.getNumResults())) {
if (auto dimExpr = dyn_cast<AffineDimExpr>(exprs[i])) {
int64_t dimPos = dimExpr.getPosition();
exprs[i] = b.getAffineDimExpr(inversedOuterPerm[dimPos]);
continue;
}
assert(isa<AffineConstantExpr>(exprs[i]) &&
"Attempted to permute non-constant and non-affine dim expression");
}
if (!outerDimsPerm.empty()) {
SmallVector<AffineExpr> auxVec = exprs;
for (const auto &en : enumerate(outerDimsPerm))
auxVec[en.index()] = exprs[en.value()];
exprs = auxVec;
}
}
currOperandDetails.indexingMap =
AffineMap::get(numLoops, 0, exprs, b.getContext());
if (innerDimsPos.empty() && outerDimsPerm.empty()) {
packedOperandMap[opOperand] = currOperandDetails;
return false;
}
auto inputType = cast<RankedTensorType>(opOperand->get().getType());
auto maybeIntInnerTileSizes =
llvm::map_to_vector(innerTileSizes, [](OpFoldResult ofr) -> int64_t {
std::optional<int64_t> maybeCst = getConstantIntValue(ofr);
return maybeCst.value_or(ShapedType::kDynamic);
});
bool requirePadding = linalg::PackOp::requirePaddingValueStrict(
inputType.getShape(), innerDimsPos,
linalg::PackOp::inferPackedType(inputType, maybeIntInnerTileSizes,
innerDimsPos, outerDimsPerm)
.getShape(),
outerDimsPerm, innerTileSizes);
currOperandDetails.innerDimsPos = innerDimsPos;
currOperandDetails.innerTileSizes = innerTileSizes;
currOperandDetails.outerDimsPerm = outerDimsPerm;
packedOperandMap[opOperand] = currOperandDetails;
return requirePadding;
}
static std::tuple<Value, AffineMap> getOrCreatePackedViewOfOperand(
OpBuilder &b, Location loc, OpOperand *opOperand,
const DenseMap<OpOperand *, PackedOperandDetails> &packedOperandMap) {
assert(packedOperandMap.contains(opOperand) &&
"packed operand details expected to be populated");
auto currOperandDetails = packedOperandMap.at(opOperand);
auto innerDimsPos = currOperandDetails.innerDimsPos;
auto outerDimsPerm = currOperandDetails.outerDimsPerm;
auto innerTileSizes = currOperandDetails.innerTileSizes;
if (innerDimsPos.empty() && outerDimsPerm.empty())
return std::make_tuple(opOperand->get(), currOperandDetails.indexingMap);
auto empty = linalg::PackOp::createDestinationTensor(
b, loc, opOperand->get(), innerTileSizes, innerDimsPos, outerDimsPerm);
auto poison = ub::PoisonOp::create(
b, loc, getElementTypeOrSelf(opOperand->get().getType()));
Value packedOperand =
linalg::PackOp::create(b, loc, opOperand->get(), empty, innerDimsPos,
innerTileSizes, poison, outerDimsPerm);
return std::make_tuple(packedOperand, currOperandDetails.indexingMap);
}
static FailureOr<GenericOp>
packGenericOp(RewriterBase &rewriter, GenericOp genericOp, Value dest,
AffineMap packedOutIndexingMap, const PackInfo &packInfo,
bool isFoldableUnpackPack, bool poisonPaddingOk) {
Location loc = genericOp.getLoc();
SmallVector<Value> inputOperands;
SmallVector<Value> inputOperandsFromUnpackedSource;
SmallVector<AffineMap> indexingMaps;
auto hasEquivalentTiles = [](PackOp packOp, UnPackOp unPackOp) {
return packOp.getOuterDimsPerm() == unPackOp.getOuterDimsPerm() &&
packOp.getInnerDimsPos() == unPackOp.getInnerDimsPos() &&
llvm::equal(packOp.getMixedTiles(), unPackOp.getMixedTiles());
};
DenseMap<OpOperand *, PackedOperandDetails> packedOperandMap;
bool requiresPadding = false;
for (OpOperand *inputOperand : genericOp.getDpsInputOperands()) {
requiresPadding |= getPackedOperandDetails(rewriter, packInfo, genericOp,
inputOperand, packedOperandMap);
}
if (requiresPadding && !poisonPaddingOk)
return failure();
for (OpOperand *inputOperand : genericOp.getDpsInputOperands()) {
auto [packedOperand, packedIndexingMap] = getOrCreatePackedViewOfOperand(
rewriter, loc, inputOperand, packedOperandMap);
auto unpackOp = inputOperand->get().getDefiningOp<linalg::UnPackOp>();
auto packOp = packedOperand.getDefiningOp<linalg::PackOp>();
if (packOp && unpackOp && hasEquivalentTiles(packOp, unpackOp)) {
inputOperandsFromUnpackedSource.push_back(unpackOp.getSource());
} else {
inputOperandsFromUnpackedSource.push_back(packedOperand);
}
inputOperands.push_back(packedOperand);
indexingMaps.push_back(packedIndexingMap);
}
if (isFoldableUnpackPack) {
inputOperands = inputOperandsFromUnpackedSource;
if (auto destPack = dest.getDefiningOp<linalg::PackOp>()) {
auto destUnPack = destPack.getSource().getDefiningOp<linalg::UnPackOp>();
if (destUnPack && hasEquivalentTiles(destPack, destUnPack)) {
dest = destUnPack.getSource();
}
}
}
int64_t numInnerLoops = packInfo.getNumTiledLoops();
SmallVector<utils::IteratorType> iterTypes =
genericOp.getIteratorTypesArray();
iterTypes.append(numInnerLoops, utils::IteratorType::parallel);
indexingMaps.push_back(packedOutIndexingMap);
auto newGenericOp = linalg::GenericOp::create(
rewriter, loc, dest.getType(), inputOperands, dest, indexingMaps,
iterTypes,
nullptr, linalg::getPrunedAttributeList(genericOp));
rewriter.cloneRegionBefore(genericOp.getRegion(), newGenericOp.getRegion(),
newGenericOp.getRegion().begin());
return newGenericOp;
}
static bool isGenericOutsNotUsed(linalg::GenericOp genericOp) {
return llvm::all_of(genericOp.getDpsInitsMutable(), [&](OpOperand &operand) {
return genericOp.getMatchingBlockArgument(&operand).use_empty();
});
}
static FailureOr<GenericOp>
bubbleUpPackOpThroughGenericOp(RewriterBase &rewriter, linalg::PackOp packOp,
const ControlPropagationFn &controlFn,
bool poisonPaddingOk) {
auto genericOp = packOp.getSource().getDefiningOp<GenericOp>();
if (!genericOp)
return failure();
if (!controlFn(&packOp.getSourceMutable()))
return failure();
if (hasGatherSemantics(genericOp))
return failure();
if (genericOp.getNumResults() != 1)
return failure();
if (!genericOp->getResult(0).hasOneUse())
return failure();
if (packOp.getPaddingValue())
return failure();
OpOperand *opOperand = genericOp.getDpsInitOperand(0);
auto packInfo = getPackingInfoFromOperand(opOperand, genericOp, packOp);
if (failed(packInfo))
return failure();
OpBuilder::InsertionGuard guard(rewriter);
rewriter.setInsertionPoint(genericOp);
Value packOpDest = packOp.getDest();
if (!packOpDest.hasOneUse())
return failure();
if (auto emptyOp = packOpDest.getDefiningOp<tensor::EmptyOp>()) {
packOpDest = tensor::EmptyOp::create(rewriter, genericOp->getLoc(),
emptyOp.getMixedSizes(),
emptyOp.getType().getElementType());
} else {
DominanceInfo dom(genericOp);
if (!dom.properlyDominates(packOpDest, genericOp))
return failure();
}
DenseMap<OpOperand *, PackedOperandDetails> packedOperandMap;
bool requiresPadding = getPackedOperandDetails(rewriter, *packInfo, genericOp,
opOperand, packedOperandMap);
if (requiresPadding && !poisonPaddingOk)
return failure();
auto [packedOutOperand, packedOutIndexingMap] =
getOrCreatePackedViewOfOperand(rewriter, genericOp.getLoc(), opOperand,
packedOperandMap);
Value dest = packedOutOperand;
auto initTensor =
genericOp.getDpsInitOperand(0)->get().getDefiningOp<tensor::EmptyOp>();
if (initTensor || isGenericOutsNotUsed(genericOp)) {
dest = packOpDest;
}
return packGenericOp(rewriter, genericOp, dest, packedOutIndexingMap,
*packInfo, false,
poisonPaddingOk);
}
struct BubbleUpPackOpThroughGenericOpPattern
: public OpRewritePattern<linalg::PackOp> {
public:
BubbleUpPackOpThroughGenericOpPattern(MLIRContext *context,
ControlPropagationFn fun,
bool poisonPaddingOk)
: OpRewritePattern<linalg::PackOp>(context), controlFn(std::move(fun)),
poisonPaddingOk(std::move(poisonPaddingOk)) {}
LogicalResult matchAndRewrite(linalg::PackOp packOp,
PatternRewriter &rewriter) const override {
auto genericOp = bubbleUpPackOpThroughGenericOp(rewriter, packOp, controlFn,
poisonPaddingOk);
if (failed(genericOp))
return failure();
rewriter.replaceOp(packOp, genericOp->getResults());
return success();
}
private:
ControlPropagationFn controlFn;
bool poisonPaddingOk;
};
class BubbleUpPackThroughPadOp final : public OpRewritePattern<linalg::PackOp> {
public:
BubbleUpPackThroughPadOp(MLIRContext *context, ControlPropagationFn fun)
: OpRewritePattern<linalg::PackOp>(context), controlFn(std::move(fun)) {}
LogicalResult matchAndRewrite(linalg::PackOp packOp,
PatternRewriter &rewriter) const override {
auto padOp = packOp.getSource().getDefiningOp<tensor::PadOp>();
if (!padOp)
return failure();
if (!controlFn(&packOp.getSourceMutable()))
return failure();
if (packOp.getPaddingValue())
return failure();
Value paddingVal = padOp.getConstantPaddingValue();
if (!paddingVal)
return failure();
if (!packOp.getDest().getDefiningOp<tensor::EmptyOp>())
return failure();
ArrayRef<int64_t> innerDimsPos = packOp.getInnerDimsPos();
llvm::SmallBitVector paddedDims = padOp.getPaddedDims();
llvm::SmallBitVector innerDims(paddedDims.size());
for (int64_t dim : innerDimsPos)
innerDims.flip(dim);
if (paddedDims.anyCommon(innerDims))
return failure();
Location loc = padOp->getLoc();
OpBuilder::InsertionGuard guard(rewriter);
rewriter.setInsertionPoint(padOp);
ArrayRef<int64_t> outerDimsPerm = packOp.getOuterDimsPerm();
SmallVector<OpFoldResult> mixedTiles = packOp.getMixedTiles();
auto empty = linalg::PackOp::createDestinationTensor(
rewriter, loc, padOp.getSource(), mixedTiles, innerDimsPos,
outerDimsPerm);
auto sourcePack = linalg::PackOp::create(
rewriter, loc, padOp.getSource(), empty, innerDimsPos, mixedTiles,
std::nullopt, outerDimsPerm);
SmallVector<OpFoldResult> lowPad = padOp.getMixedLowPad();
SmallVector<OpFoldResult> highPad = padOp.getMixedHighPad();
if (!outerDimsPerm.empty()) {
applyPermutationToVector<OpFoldResult>(lowPad, outerDimsPerm);
applyPermutationToVector<OpFoldResult>(highPad, outerDimsPerm);
}
size_t pointLoopsSize = innerDimsPos.size();
lowPad.append(pointLoopsSize, rewriter.getIndexAttr(0));
highPad.append(pointLoopsSize, rewriter.getIndexAttr(0));
auto newPadOp =
tensor::PadOp::create(rewriter, loc, Type(), sourcePack,
lowPad, highPad, paddingVal, padOp.getNofold());
if (!padOp->hasOneUse()) {
auto unpackEmpty = linalg::UnPackOp::createDestinationTensor(
rewriter, loc, newPadOp, mixedTiles, innerDimsPos, outerDimsPerm);
Value unpackedPad =
linalg::UnPackOp::create(rewriter, loc, newPadOp, unpackEmpty,
innerDimsPos, mixedTiles, outerDimsPerm);
rewriter.replaceAllUsesExcept(padOp, unpackedPad, sourcePack);
}
rewriter.replaceOp(packOp, newPadOp.getResult());
return success();
}
private:
ControlPropagationFn controlFn;
};
static SmallVector<int64_t>
projectToInnerMostNonUnitDimsPos(ArrayRef<int64_t> dimsPos,
ArrayRef<ReassociationIndices> reassocIndices,
ArrayRef<int64_t> targetShape) {
SmallVector<int64_t> projectedDimsPos;
for (auto pos : dimsPos) {
int64_t projectedPos = reassocIndices[pos].back();
for (auto i : llvm::reverse(reassocIndices[pos])) {
int64_t dim = targetShape[i];
if (dim > 1 || ShapedType::isDynamic(dim)) {
projectedPos = i;
break;
}
}
projectedDimsPos.push_back(projectedPos);
}
return projectedDimsPos;
}
static bool isDimsDivisibleByTileSizes(ArrayRef<int64_t> dimsPos,
ArrayRef<int64_t> shape,
ArrayRef<int64_t> tileSizes) {
for (auto [pos, tileSize] : llvm::zip_equal(dimsPos, tileSizes)) {
int64_t dim = shape[pos];
if (ShapedType::isDynamic(dim) || (dim % tileSize) != 0)
return false;
}
return true;
}
static int64_t applyPermutationAndReindexReassoc(
SmallVector<ReassociationIndices> &reassocIndices,
ArrayRef<int64_t> permutation) {
if (!permutation.empty())
applyPermutationToVector<ReassociationIndices>(reassocIndices, permutation);
int64_t nextPos = 0;
for (ReassociationIndices &indices : reassocIndices) {
for (auto &index : indices) {
index = nextPos;
nextPos += 1;
}
}
return nextPos;
}
static LogicalResult
bubbleUpPackOpThroughCollapseShape(tensor::CollapseShapeOp collapseOp,
linalg::PackOp packOp,
PatternRewriter &rewriter) {
SmallVector<int64_t> innerTileSizes = packOp.getStaticTiles();
ArrayRef<int64_t> innerDimsPos = packOp.getInnerDimsPos();
ArrayRef<int64_t> outerDimsPerm = packOp.getOuterDimsPerm();
ArrayRef<int64_t> srcShape = collapseOp.getSrcType().getShape();
SmallVector<ReassociationIndices> reassocIndices =
collapseOp.getReassociationIndices();
SmallVector<int64_t> projectedInnerDimsPos =
projectToInnerMostNonUnitDimsPos(innerDimsPos, reassocIndices, srcShape);
if (!isDimsDivisibleByTileSizes(projectedInnerDimsPos, srcShape,
innerTileSizes)) {
return failure();
}
SmallVector<int64_t> newOuterDimsPerm;
for (auto outerPos : outerDimsPerm)
llvm::append_range(newOuterDimsPerm, reassocIndices[outerPos]);
auto emptyOp = linalg::PackOp::createDestinationTensor(
rewriter, packOp.getLoc(), collapseOp.getSrc(), packOp.getMixedTiles(),
projectedInnerDimsPos, newOuterDimsPerm);
auto newPackOp = linalg::PackOp::create(
rewriter, packOp.getLoc(), collapseOp.getSrc(), emptyOp,
projectedInnerDimsPos, packOp.getMixedTiles(), packOp.getPaddingValue(),
newOuterDimsPerm);
SmallVector<ReassociationIndices> newReassocIndices = reassocIndices;
int64_t nextPos =
applyPermutationAndReindexReassoc(newReassocIndices, outerDimsPerm);
for (size_t i = 0; i < innerDimsPos.size(); ++i) {
newReassocIndices.push_back({nextPos});
nextPos += 1;
}
auto newCollapseOp = tensor::CollapseShapeOp::create(
rewriter, collapseOp.getLoc(), packOp.getType(), newPackOp,
newReassocIndices);
rewriter.replaceOp(packOp, newCollapseOp);
return success();
}
static SmallVector<int64_t>
projectDimsPosIntoReassocPos(ArrayRef<int64_t> dimsPos,
ArrayRef<ReassociationIndices> reassocIndices) {
SmallVector<int64_t> projectedPos;
for (auto pos : dimsPos) {
for (auto [idx, indices] : llvm::enumerate(reassocIndices)) {
if (llvm::is_contained(indices, pos)) {
projectedPos.push_back(idx);
break;
}
}
}
assert(projectedPos.size() == dimsPos.size() && "Invalid dim pos projection");
return projectedPos;
}
static LogicalResult
bubbleUpPackOpThroughExpandShape(tensor::ExpandShapeOp expandOp,
linalg::PackOp packOp,
PatternRewriter &rewriter) {
ArrayRef<int64_t> outerDimsPerm = packOp.getOuterDimsPerm();
if (!outerDimsPerm.empty() && !isIdentityPermutation(outerDimsPerm)) {
return rewriter.notifyMatchFailure(packOp,
"non-identity outer dims perm NYI");
}
SmallVector<ReassociationIndices, 4> reassoc =
expandOp.getReassociationIndices();
ArrayRef<int64_t> packInnerDims = packOp.getInnerDimsPos();
llvm::SetVector<int64_t> packDimsPos(llvm::from_range, packInnerDims);
for (auto [idx, indices] : llvm::enumerate(reassoc)) {
llvm::SetVector<int64_t> expandDimPos(llvm::from_range, indices);
llvm::SetVector<int64_t> packedDims =
llvm::set_intersection(packDimsPos, expandDimPos);
if (packedDims.empty())
continue;
if (packedDims.size() != 1)
return rewriter.notifyMatchFailure(
packOp, "only one of the expanded dimensions can be packed");
if (packedDims.front() != indices.back())
return rewriter.notifyMatchFailure(
packOp, "can only pack the inner-most expanded dimension");
}
SmallVector<int64_t> projectedInnerDimsPos =
projectDimsPosIntoReassocPos(packInnerDims, reassoc);
RankedTensorType newPackType = linalg::PackOp::inferPackedType(
expandOp.getSrcType(), packOp.getStaticInnerTiles(),
projectedInnerDimsPos, SmallVector<int64_t>{});
auto reassocExpand =
getReassociationIndicesForReshape(newPackType, packOp.getDestType());
if (!reassocExpand)
return rewriter.notifyMatchFailure(
packOp, "could not reassociate dims after bubbling up");
Value destTensor = linalg::PackOp::createDestinationTensor(
rewriter, packOp.getLoc(), expandOp.getSrc(), packOp.getMixedTiles(),
projectedInnerDimsPos, SmallVector<int64_t>{});
Value packedVal = linalg::PackOp::create(
rewriter, packOp.getLoc(), expandOp.getSrc(), destTensor,
projectedInnerDimsPos, packOp.getMixedTiles(), packOp.getPaddingValue(),
SmallVector<int64_t>{});
Value newExpandOp = tensor::ExpandShapeOp::create(rewriter, packOp.getLoc(),
packOp.getDestType(),
packedVal, *reassocExpand);
rewriter.replaceOp(packOp, newExpandOp);
return success();
}
class BubbleUpPackOpThroughReshapeOp final
: public OpRewritePattern<linalg::PackOp> {
public:
BubbleUpPackOpThroughReshapeOp(MLIRContext *context, ControlPropagationFn fun)
: OpRewritePattern<linalg::PackOp>(context), controlFn(std::move(fun)) {}
LogicalResult matchAndRewrite(linalg::PackOp packOp,
PatternRewriter &rewriter) const override {
Operation *srcOp = packOp.getSource().getDefiningOp();
if (!srcOp || !(srcOp->getNumResults() == 1) ||
!srcOp->getResult(0).hasOneUse()) {
return failure();
}
if (llvm::any_of(packOp.getStaticTiles(), ShapedType::isDynamic))
return failure();
if (!controlFn(&packOp.getSourceMutable()))
return failure();
return TypeSwitch<Operation *, LogicalResult>(srcOp)
.Case([&](tensor::CollapseShapeOp op) {
return bubbleUpPackOpThroughCollapseShape(op, packOp, rewriter);
})
.Case([&](tensor::ExpandShapeOp op) {
return bubbleUpPackOpThroughExpandShape(op, packOp, rewriter);
})
.Default([](Operation *) { return failure(); });
}
private:
ControlPropagationFn controlFn;
};
static LogicalResult pushDownUnPackOpThroughExpandShape(
linalg::UnPackOp unPackOp, tensor::ExpandShapeOp expandOp,
PatternRewriter &rewriter, ControlPropagationFn controlFn) {
if (!controlFn(&expandOp.getSrcMutable()))
return failure();
SmallVector<int64_t> innerTileSizes = unPackOp.getStaticTiles();
ArrayRef<int64_t> innerDimsPos = unPackOp.getInnerDimsPos();
ArrayRef<int64_t> outerDimsPerm = unPackOp.getOuterDimsPerm();
auto expandTy = dyn_cast<RankedTensorType>(expandOp.getType());
if (!expandTy)
return failure();
ArrayRef<int64_t> dstShape = expandTy.getShape();
SmallVector<ReassociationIndices> reassocIndices =
expandOp.getReassociationIndices();
SmallVector<int64_t> projectedInnerDimsPos =
projectToInnerMostNonUnitDimsPos(innerDimsPos, reassocIndices, dstShape);
if (!isDimsDivisibleByTileSizes(projectedInnerDimsPos, dstShape,
innerTileSizes)) {
return failure();
}
SmallVector<int64_t> newOuterDimsPerm;
for (auto outerPos : outerDimsPerm)
llvm::append_range(newOuterDimsPerm, reassocIndices[outerPos]);
SmallVector<ReassociationIndices> newReassocIndices = reassocIndices;
int64_t nextPos =
applyPermutationAndReindexReassoc(newReassocIndices, outerDimsPerm);
for (size_t i = 0; i < innerDimsPos.size(); ++i) {
newReassocIndices.push_back({nextPos});
nextPos += 1;
}
RankedTensorType newExpandType = linalg::PackOp::inferPackedType(
expandTy, innerTileSizes, projectedInnerDimsPos, newOuterDimsPerm);
auto newExpandOp =
tensor::ExpandShapeOp::create(rewriter, expandOp.getLoc(), newExpandType,
unPackOp.getSource(), newReassocIndices);
auto emptyOp = linalg::UnPackOp::createDestinationTensor(
rewriter, unPackOp.getLoc(), newExpandOp, unPackOp.getMixedTiles(),
projectedInnerDimsPos, newOuterDimsPerm);
auto newUnPackOp = linalg::UnPackOp::create(
rewriter, unPackOp.getLoc(), newExpandOp.getResult(), emptyOp,
projectedInnerDimsPos, unPackOp.getMixedTiles(), newOuterDimsPerm);
rewriter.replaceOp(expandOp, newUnPackOp);
return success();
}
class PushDownUnPackOpThroughReshapeOp final
: public OpRewritePattern<linalg::UnPackOp> {
public:
PushDownUnPackOpThroughReshapeOp(MLIRContext *context,
ControlPropagationFn fun)
: OpRewritePattern<linalg::UnPackOp>(context), controlFn(std::move(fun)) {
}
LogicalResult matchAndRewrite(linalg::UnPackOp unPackOp,
PatternRewriter &rewriter) const override {
Value result = unPackOp.getResult();
if (!result.hasOneUse()) {
return failure();
}
if (llvm::any_of(unPackOp.getStaticTiles(), ShapedType::isDynamic))
return failure();
Operation *consumerOp = *result.user_begin();
return TypeSwitch<Operation *, LogicalResult>(consumerOp)
.Case([&](tensor::ExpandShapeOp op) {
return pushDownUnPackOpThroughExpandShape(unPackOp, op, rewriter,
controlFn);
})
.Default([](Operation *) { return failure(); });
}
private:
ControlPropagationFn controlFn;
};
static FailureOr<OpOperand *> getUnPackedOperand(GenericOp genericOp) {
OpOperand *unPackedOperand = nullptr;
for (OpOperand &operand : genericOp->getOpOperands()) {
auto unPackOp = operand.get().getDefiningOp<linalg::UnPackOp>();
if (!unPackOp)
continue;
if (unPackedOperand)
return failure();
unPackedOperand = &operand;
}
if (!unPackedOperand)
return failure();
return unPackedOperand;
}
static FailureOr<std::tuple<GenericOp, Value>>
pushDownUnPackOpThroughGenericOp(RewriterBase &rewriter, GenericOp genericOp,
ControlPropagationFn controlFn,
bool poisonPaddingOk) {
if (genericOp.getNumResults() != 1)
return failure();
if (hasGatherSemantics(genericOp))
return failure();
auto maybeUnPackedOperand = getUnPackedOperand(genericOp);
if (failed(maybeUnPackedOperand))
return failure();
OpOperand *unPackedOperand = *(maybeUnPackedOperand);
linalg::UnPackOp producerUnPackOp =
unPackedOperand->get().getDefiningOp<linalg::UnPackOp>();
assert(producerUnPackOp && "expect a valid UnPackOp");
if (!controlFn(unPackedOperand))
return failure();
auto packInfo =
getPackingInfoFromOperand(unPackedOperand, genericOp, producerUnPackOp);
if (failed(packInfo))
return failure();
DenseMap<OpOperand *, PackedOperandDetails> packedOperandMap;
bool requiresPadding =
getPackedOperandDetails(rewriter, *packInfo, genericOp,
genericOp.getDpsInitOperand(0), packedOperandMap);
if (requiresPadding && !poisonPaddingOk)
return failure();
auto [packedOutOperand, packedOutIndexingMap] =
getOrCreatePackedViewOfOperand(rewriter, genericOp.getLoc(),
genericOp.getDpsInitOperand(0),
packedOperandMap);
auto destPack = packedOutOperand.getDefiningOp<linalg::PackOp>();
Value dest = packedOutOperand;
auto initTensor =
genericOp.getDpsInitOperand(0)->get().getDefiningOp<tensor::EmptyOp>();
if (initTensor || isGenericOutsNotUsed(genericOp)) {
if (destPack)
dest = destPack.getDest();
}
auto maybeGenericOp =
packGenericOp(rewriter, genericOp, dest, packedOutIndexingMap, *packInfo,
true, poisonPaddingOk);
if (failed(maybeGenericOp))
return failure();
GenericOp newGenericOp = *maybeGenericOp;
Value newResult =
newGenericOp.getTiedOpResult(newGenericOp.getDpsInitOperand(0));
if (!destPack)
return std::make_tuple(newGenericOp, newResult);
auto mixedTiles = destPack.getMixedTiles();
auto innerDimsPos = destPack.getInnerDimsPos();
auto outerDimsPerm = destPack.getOuterDimsPerm();
Value unPackOpRes =
linalg::UnPackOp::create(rewriter, genericOp.getLoc(), newResult,
destPack.getSource(), innerDimsPos, mixedTiles,
outerDimsPerm)
.getResult();
return std::make_tuple(newGenericOp, unPackOpRes);
}
struct PushDownUnPackOpThroughGenericOp : public OpRewritePattern<GenericOp> {
public:
PushDownUnPackOpThroughGenericOp(MLIRContext *context,
ControlPropagationFn fun,
bool poisonPaddingOk)
: OpRewritePattern<GenericOp>(context), controlFn(std::move(fun)),
poisonPaddingOk(std::move(poisonPaddingOk)) {}
LogicalResult matchAndRewrite(GenericOp genericOp,
PatternRewriter &rewriter) const override {
auto genericAndRepl = pushDownUnPackOpThroughGenericOp(
rewriter, genericOp, controlFn, poisonPaddingOk);
if (failed(genericAndRepl))
return failure();
rewriter.replaceOp(genericOp, std::get<1>(*genericAndRepl));
return success();
}
private:
ControlPropagationFn controlFn;
bool poisonPaddingOk;
};
struct PushDownUnPackThroughPadOp : public OpRewritePattern<tensor::PadOp> {
PushDownUnPackThroughPadOp(MLIRContext *context, ControlPropagationFn fun)
: OpRewritePattern<tensor::PadOp>(context), controlFn(std::move(fun)) {}
LogicalResult matchAndRewrite(tensor::PadOp padOp,
PatternRewriter &rewriter) const override {
linalg::UnPackOp unpackOp =
padOp.getSource().getDefiningOp<linalg::UnPackOp>();
if (!unpackOp)
return failure();
if (!controlFn(&padOp.getSourceMutable()))
return failure();
Location loc = padOp.getLoc();
llvm::SmallBitVector paddedDims = padOp.getPaddedDims();
ArrayRef<int64_t> innerDimsPos = unpackOp.getInnerDimsPos();
llvm::SmallBitVector innerDims(paddedDims.size());
for (int64_t dim : innerDimsPos)
innerDims.flip(dim);
if (paddedDims.anyCommon(innerDims))
return failure();
Value paddingVal = padOp.getConstantPaddingValue();
if (!paddingVal)
return failure();
ArrayRef<int64_t> outerDimsPerm = unpackOp.getOuterDimsPerm();
SmallVector<OpFoldResult> lowPad = padOp.getMixedLowPad();
SmallVector<OpFoldResult> highPad = padOp.getMixedHighPad();
if (!outerDimsPerm.empty()) {
applyPermutationToVector<OpFoldResult>(lowPad, outerDimsPerm);
applyPermutationToVector<OpFoldResult>(highPad, outerDimsPerm);
}
size_t pointLoopsSize = innerDimsPos.size();
lowPad.append(pointLoopsSize, rewriter.getIndexAttr(0));
highPad.append(pointLoopsSize, rewriter.getIndexAttr(0));
auto newPadOp = tensor::PadOp::create(rewriter, loc, Type(),
unpackOp.getSource(), lowPad, highPad,
paddingVal, padOp.getNofold());
Value outputUnPack =
tensor::EmptyOp::create(rewriter, loc, padOp.getResultType().getShape(),
padOp.getResultType().getElementType());
Value replacement = linalg::UnPackOp::create(
rewriter, loc, newPadOp.getResult(), outputUnPack, innerDimsPos,
unpackOp.getMixedTiles(), outerDimsPerm);
rewriter.replaceOp(padOp, replacement);
return success();
}
private:
ControlPropagationFn controlFn;
};
struct SliceDimInfo {
OpFoldResult offset;
OpFoldResult sliceSize;
OpFoldResult outputSize;
};
static FailureOr<SmallVector<OpOperand *>>
getSliceOperands(GenericOp genericOp) {
SmallVector<OpOperand *> sliceOperands;
for (auto operand : genericOp.getDpsInputOperands()) {
auto extractOp = operand->get().getDefiningOp<tensor::ExtractSliceOp>();
if (!extractOp)
continue;
sliceOperands.push_back(operand);
}
if (sliceOperands.empty()) {
return failure();
}
return sliceOperands;
}
static FailureOr<llvm::DenseMap<int64_t, SliceDimInfo>>
getPartialSliceDimInfo(GenericOp genericOp, OpOperand *sliceOperand) {
tensor::ExtractSliceOp producerSliceOp =
sliceOperand->get().getDefiningOp<tensor::ExtractSliceOp>();
assert(producerSliceOp && "expect a valid ExtractSliceOp");
llvm::DenseMap<int64_t, SliceDimInfo> partialSliceDimMap;
SmallVector<OpFoldResult> offsets = producerSliceOp.getMixedOffsets();
SmallVector<OpFoldResult> sizes = producerSliceOp.getMixedSizes();
SmallVector<OpFoldResult> shape = getAsIndexOpFoldResult(
genericOp.getContext(), producerSliceOp.getSourceType().getShape());
for (auto [idx, expr] : llvm::enumerate(
genericOp.getMatchingIndexingMap(sliceOperand).getResults())) {
if (isConstantIntValue(offsets[idx], 0) &&
isEqualConstantIntOrValue(sizes[idx], shape[idx])) {
continue;
}
if (!isa<AffineDimExpr>(expr)) {
return failure();
}
SliceDimInfo sliceDimInfo{offsets[idx], sizes[idx], shape[idx]};
int64_t dimPos = cast<AffineDimExpr>(expr).getPosition();
partialSliceDimMap[dimPos] = sliceDimInfo;
}
for (OpOperand &operand : genericOp->getOpOperands()) {
if (operand == *sliceOperand) {
continue;
}
AffineMap IndexingMap = genericOp.getMatchingIndexingMap(&operand);
if (llvm::any_of(IndexingMap.getResults(), [&](AffineExpr expr) {
if (isa<AffineDimExpr>(expr)) {
return false;
}
WalkResult status = expr.walk([&](AffineExpr expr) {
if (auto dimExpr = dyn_cast<AffineDimExpr>(expr)) {
if (partialSliceDimMap.contains(dimExpr.getPosition())) {
return WalkResult::interrupt();
}
}
return WalkResult::advance();
});
if (status.wasInterrupted()) {
return true;
}
return false;
})) {
return failure();
}
}
return partialSliceDimMap;
}
static FailureOr<std::tuple<GenericOp, Value>>
pushDownExtractSliceOpThroughGenericOp(RewriterBase &rewriter,
GenericOp genericOp,
ControlPropagationFn controlFn) {
if (genericOp.getNumResults() != 1)
return rewriter.notifyMatchFailure(
genericOp, "propagation through multi-result generic is unsupported.");
if (hasGatherSemantics(genericOp))
return rewriter.notifyMatchFailure(
genericOp,
"propagation through generic with gather semantics is unsupported.");
auto maybeSliceOperands = getSliceOperands(genericOp);
if (failed(maybeSliceOperands))
return failure();
SmallVector<OpOperand *> sliceOperands = *maybeSliceOperands;
OpOperand *sliceOperand;
bool foundValidOperand = false;
for (auto currSliceOperand : sliceOperands) {
if (controlFn(currSliceOperand)) {
sliceOperand = currSliceOperand;
foundValidOperand = true;
break;
}
}
if (!foundValidOperand) {
return failure();
}
unsigned OperandIndex = sliceOperand->getOperandNumber();
tensor::ExtractSliceOp producerSliceOp =
sliceOperand->get().getDefiningOp<tensor::ExtractSliceOp>();
assert(producerSliceOp && "expect a valid ExtractSliceOp");
if (producerSliceOp.getSource().getType().getRank() !=
producerSliceOp.getResult().getType().getRank()) {
return rewriter.notifyMatchFailure(
genericOp,
"propagation of rank-reducing extract slice is unsupported.");
}
SmallVector<OpFoldResult> strides = producerSliceOp.getMixedStrides();
if (!areAllConstantIntValue(strides, 1))
return rewriter.notifyMatchFailure(
genericOp, "propagation of strided extract slice is unsupported.");
auto maybePartialSliceDimMap =
getPartialSliceDimInfo(genericOp, sliceOperand);
if (failed(maybePartialSliceDimMap)) {
return failure();
}
auto partialSliceDimMap = *maybePartialSliceDimMap;
SmallVector<utils::IteratorType> iterators =
genericOp.getIteratorTypesArray();
bool hasPartialReductionDimSlice =
llvm::any_of(partialSliceDimMap, [&](const auto &slice) {
int64_t sliceDim = slice.first;
return iterators[sliceDim] == utils::IteratorType::reduction;
});
Location loc = genericOp->getLoc();
AffineExpr dim0, dim1;
bindDims(rewriter.getContext(), dim0, dim1);
auto subMap = AffineMap::get(2, 0, {dim0 - dim1});
auto sub = [&](OpFoldResult v1, OpFoldResult v2) {
return affine::makeComposedFoldedAffineApply(rewriter, loc, subMap,
{v1, v2});
};
MLIRContext *ctx = genericOp.getContext();
SmallVector<Value> paddedInputs;
for (auto [idx, operand] : llvm::enumerate(genericOp.getDpsInputOperands())) {
if (idx == OperandIndex && !hasPartialReductionDimSlice) {
paddedInputs.push_back(producerSliceOp.getSource());
continue;
}
AffineMap IndexingMap = genericOp.getMatchingIndexingMap(operand);
if (IndexingMap.getNumResults() == 0) {
paddedInputs.push_back(operand->get());
continue;
}
SmallVector<OpFoldResult> operandLowPads(IndexingMap.getNumResults(),
getAsIndexOpFoldResult(ctx, 0));
SmallVector<OpFoldResult> operandHighPads(IndexingMap.getNumResults(),
getAsIndexOpFoldResult(ctx, 0));
for (auto [idx, expr] : llvm::enumerate(IndexingMap.getResults())) {
if (!isa<AffineDimExpr>(expr)) {
continue;
}
AffineDimExpr dimExpr = cast<AffineDimExpr>(expr);
if (!partialSliceDimMap.contains(dimExpr.getPosition())) {
continue;
}
SliceDimInfo sliceDimInfo = partialSliceDimMap[dimExpr.getPosition()];
operandLowPads[idx] = sliceDimInfo.offset;
operandHighPads[idx] =
sub(sub(sliceDimInfo.outputSize, sliceDimInfo.offset),
sliceDimInfo.sliceSize);
}
auto paddingValue = ub::PoisonOp::create(
rewriter, loc, getElementTypeOrSelf(operand->get().getType()));
auto paddedOperand = tensor::PadOp::create(
rewriter, loc, Type(), operand->get(), operandLowPads, operandHighPads,
paddingValue, false);
paddedInputs.push_back(paddedOperand);
}
AffineMap outputIndexingMap =
genericOp.getMatchingIndexingMap(genericOp.getDpsInitOperand(0));
auto outputShapeType =
llvm::cast<ShapedType>(genericOp.getDpsInitOperand(0)->get().getType());
SmallVector<OpFoldResult> OutputShape = llvm::map_to_vector(
outputShapeType.getShape(),
[&](int64_t sz) -> OpFoldResult { return rewriter.getIndexAttr(sz); });
SmallVector<OpFoldResult> newSizes = OutputShape;
SmallVector<OpFoldResult> outputLowPads(outputIndexingMap.getNumResults(),
getAsIndexOpFoldResult(ctx, 0));
SmallVector<OpFoldResult> outputHighPads(outputIndexingMap.getNumResults(),
getAsIndexOpFoldResult(ctx, 0));
SmallVector<OpFoldResult> newStrides(outputIndexingMap.getNumResults(),
getAsIndexOpFoldResult(ctx, 1));
for (auto [idx, expr] : llvm::enumerate(outputIndexingMap.getResults())) {
if (!isa<AffineDimExpr>(expr)) {
continue;
}
AffineDimExpr dimExpr = cast<AffineDimExpr>(expr);
if (!partialSliceDimMap.contains(dimExpr.getPosition())) {
continue;
}
SliceDimInfo sliceDimInfo = partialSliceDimMap[dimExpr.getPosition()];
outputLowPads[idx] = sliceDimInfo.offset;
outputHighPads[idx] = sub(sub(sliceDimInfo.outputSize, sliceDimInfo.offset),
sliceDimInfo.sliceSize);
OutputShape[idx] = sliceDimInfo.outputSize;
newSizes[idx] = sliceDimInfo.sliceSize;
}
Value newPadOutput;
auto outputElType =
getElementTypeOrSelf(genericOp.getDpsInits()[0].getType());
if (isGenericOutsNotUsed(genericOp)) {
newPadOutput =
tensor::EmptyOp::create(rewriter, loc, OutputShape, outputElType);
} else {
auto paddingValue = ub::PoisonOp::create(rewriter, loc, outputElType);
newPadOutput = tensor::PadOp::create(
rewriter, loc, Type(), genericOp.getDpsInits()[0], outputLowPads,
outputHighPads, paddingValue, false);
}
auto newGenericOp = linalg::GenericOp::create(
rewriter, loc, newPadOutput.getType(), paddedInputs, {newPadOutput},
genericOp.getIndexingMapsArray(), genericOp.getIteratorTypesArray(),
nullptr, linalg::getPrunedAttributeList(genericOp));
rewriter.cloneRegionBefore(genericOp.getRegion(), newGenericOp.getRegion(),
newGenericOp.getRegion().begin());
auto extractOp = tensor::ExtractSliceOp::create(
rewriter, loc,
newGenericOp.getTiedOpResult(newGenericOp.getDpsInitOperand(0)),
outputLowPads, newSizes, newStrides);
Value extractRes = extractOp.getResult();
return std::make_tuple(newGenericOp, extractRes);
}
class PushDownExtractSliceOpThroughGenericOp final
: public OpRewritePattern<GenericOp> {
public:
PushDownExtractSliceOpThroughGenericOp(MLIRContext *context,
ControlPropagationFn fun)
: OpRewritePattern<GenericOp>(context), controlFn(std::move(fun)) {}
LogicalResult matchAndRewrite(GenericOp genericOp,
PatternRewriter &rewriter) const override {
auto genericAndRepl =
pushDownExtractSliceOpThroughGenericOp(rewriter, genericOp, controlFn);
if (failed(genericAndRepl))
return failure();
rewriter.replaceOp(genericOp, std::get<1>(*genericAndRepl));
return success();
}
private:
ControlPropagationFn controlFn;
};
}
void mlir::linalg::populateDataLayoutPropagationPatterns(
RewritePatternSet &patterns,
const ControlPropagationFn &controlPackUnPackPropagation,
bool PoisonPaddingOk) {
patterns.insert<BubbleUpPackThroughPadOp, BubbleUpPackOpThroughReshapeOp,
PushDownUnPackThroughPadOp, PushDownUnPackOpThroughReshapeOp>(
patterns.getContext(), controlPackUnPackPropagation);
patterns.insert<BubbleUpPackOpThroughGenericOpPattern,
PushDownUnPackOpThroughGenericOp>(
patterns.getContext(), controlPackUnPackPropagation, PoisonPaddingOk);
}
void mlir::linalg::populateExtractSliceSinkingPatterns(
RewritePatternSet &patterns,
const ControlPropagationFn &controlPackUnPackPropagation) {
patterns.insert<PushDownExtractSliceOpThroughGenericOp>(
patterns.getContext(), controlPackUnPackPropagation);
}