#include "Utils/CodegenUtils.h"
#include "Utils/IterationGraphSorter.h"
#include "mlir/Dialect/Affine/IR/AffineOps.h"
#include "mlir/Dialect/Bufferization/IR/Bufferization.h"
#include "mlir/Dialect/Linalg/IR/Linalg.h"
#include "mlir/Dialect/Linalg/Utils/Utils.h"
#include "mlir/Dialect/SparseTensor/IR/SparseTensor.h"
#include "mlir/Dialect/SparseTensor/IR/SparseTensorType.h"
#include "mlir/Dialect/SparseTensor/Transforms/Passes.h"
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/IR/AffineExprVisitor.h"
#include "mlir/IR/AffineMap.h"
using namespace mlir;
using namespace mlir::sparse_tensor;
namespace {
template <typename SubClass, typename SourceOp>
struct DemapInsRewriter : public OpRewritePattern<SourceOp> {
using OpRewritePattern<SourceOp>::OpRewritePattern;
using OpAdaptor = typename SourceOp::Adaptor;
LogicalResult matchAndRewrite(SourceOp op,
PatternRewriter &rewriter) const override {
Location loc = op.getLoc();
bool changed = false;
SmallVector<Value> deMappedIns(op->getOperands());
for (Value &in : deMappedIns) {
if (auto stt = tryGetSparseTensorType(in); stt && !stt->isIdentity()) {
in = rewriter.create<ReinterpretMapOp>(loc, stt->getDemappedType(), in);
changed = true;
}
}
OpAdaptor adaptor(deMappedIns, op);
LogicalResult status =
static_cast<const SubClass *>(this)->rewriteOp(op, adaptor, rewriter);
return changed ? success() : status;
}
};
struct AffineDimCollector : public AffineExprVisitor<AffineDimCollector> {
explicit AffineDimCollector(unsigned dimNum) : dims(dimNum){};
void visitDimExpr(AffineDimExpr expr) { dims.set(expr.getPosition()); }
BitVector dims;
};
struct AffineExprAdmissibleVisitor
: public AffineExprVisitor<AffineExprAdmissibleVisitor> {
explicit AffineExprAdmissibleVisitor(bool isOutput)
: admissible(true), isOutput(isOutput){};
void visitAddExpr(AffineBinaryOpExpr expr) {
if (isOutput)
admissible = false;
}
void visitMulExpr(AffineBinaryOpExpr expr) {
if (isOutput)
admissible = false;
}
void visitModExpr(AffineBinaryOpExpr expr) { admissible = false; }
void visitFloorDivExpr(AffineBinaryOpExpr expr) { admissible = false; }
void visitCeilDivExpr(AffineBinaryOpExpr expr) { admissible = false; }
operator bool() { return admissible; }
private:
bool admissible;
bool isOutput;
};
using InadmissInfo = std::pair<BitVector, BitVector>;
}
static InadmissInfo collectInadmissInfo(AffineMap map, bool isOutput) {
auto ret = std::make_pair(BitVector(map.getNumResults()),
BitVector(map.getNumDims()));
AffineDimCollector collector(map.getNumDims());
for (unsigned lvl = 0, e = map.getNumResults(); lvl < e; lvl++) {
AffineExprAdmissibleVisitor admissible(isOutput);
admissible.walkPostOrder(map.getResult(lvl));
if (!admissible) {
ret.first.set(lvl);
collector.walkPostOrder(map.getResult(lvl));
}
}
ret.second = collector.dims;
return ret;
}
static AffineMap
genReplaceDimToLvlMap(const InadmissInfo &info, AffineMap idxMap,
SmallVector<utils::IteratorType> &itTps) {
MLIRContext *ctx = idxMap.getContext();
auto [inAdLvls, usedDims] = info;
auto lvl2Idx = inferLvlToDim(idxMap, ctx);
assert(lvl2Idx.getNumResults() <= idxMap.getNumDims());
if (lvl2Idx.getNumResults() != idxMap.getNumDims()) {
SmallVector<AffineExpr> results;
AffineDimCollector usedInLvl(idxMap.getNumDims());
for (auto e : idxMap.getResults())
usedInLvl.walkPostOrder(e);
unsigned curUsedDimID = 0;
unsigned curUnusedDimID = lvl2Idx.getNumDims();
BitVector unused = usedInLvl.dims.flip();
for (unsigned i = 0; i < idxMap.getNumDims(); i++) {
if (unused.test(i))
results.push_back(getAffineDimExpr(curUnusedDimID++, ctx));
else
results.push_back(lvl2Idx.getResult(curUsedDimID++));
}
lvl2Idx =
AffineMap::get(lvl2Idx.getNumDims() + unused.count(), 0, results, ctx);
}
assert(lvl2Idx.getNumResults() == idxMap.getNumDims());
unsigned curRepID = 0;
unsigned curOriID = inAdLvls.count();
SmallVector<AffineExpr> results;
SmallVector<AffineExpr> dimRep(idxMap.getNumResults(), AffineExpr());
SmallVector<utils::IteratorType> transItTps;
for (unsigned l : inAdLvls.set_bits()) {
dimRep[l] = getAffineDimExpr(curRepID++, ctx);
AffineExpr lvlExp = idxMap.getResult(l);
AffineDimCollector collector(idxMap.getNumDims());
collector.walkPostOrder(lvlExp);
assert(collector.dims.count() == 1);
transItTps.push_back(itTps[collector.dims.find_first()]);
}
for (unsigned d = 0, e = idxMap.getNumDims(); d < e; d++) {
if (usedDims.test(d)) {
results.push_back(lvl2Idx.getResult(d).replaceDims(dimRep));
} else {
results.push_back(getAffineDimExpr(curOriID++, ctx));
transItTps.push_back(itTps[d]);
}
}
unsigned numDim = idxMap.getNumDims() - usedDims.count() + inAdLvls.count();
itTps.assign(transItTps.begin(), transItTps.end());
return AffineMap::get(numDim, 0, results, ctx);
}
static std::optional<std::pair<ArrayAttr, ArrayAttr>>
translateMap(linalg::GenericOp op, PatternRewriter &rewriter) {
MLIRContext *ctx = op.getContext();
SmallVector<AffineMap> idxMapArray = op.getIndexingMapsArray();
SmallVector<utils::IteratorType> itTps = op.getIteratorTypesArray();
for (unsigned i = 0, e = idxMapArray.size(); i < e; i++) {
Value tensor = op->getOpOperand(i).get();
auto stt = tryGetSparseTensorType(tensor);
if (stt && !stt->isIdentity()) {
AffineMap dim2Lvl = stt->getDimToLvl();
idxMapArray[i] = dim2Lvl.compose(idxMapArray[i]);
}
}
auto populateCstMapping = [ctx](DenseMap<AffineExpr, AffineExpr> &cstMapping,
unsigned pos, int64_t lvlSz) {
if (!ShapedType::isDynamic(lvlSz)) {
auto c0 = getAffineConstantExpr(0, ctx);
auto lvlExp = getAffineDimExpr(pos, ctx);
auto szExp = getAffineConstantExpr(lvlSz, ctx);
auto divExp =
getAffineBinaryOpExpr(AffineExprKind::FloorDiv, lvlExp, szExp);
cstMapping.try_emplace(divExp, c0);
auto modExp = getAffineBinaryOpExpr(AffineExprKind::Mod, lvlExp, szExp);
cstMapping.try_emplace(modExp, lvlExp);
}
};
unsigned boundedNum = 0;
bool changed = true;
while (changed) {
changed = false;
for (OpOperand &operand : op->getOpOperands()) {
auto stt = tryGetSparseTensorType(operand.get());
if (!stt || !stt->getEncoding())
continue;
unsigned tid = operand.getOperandNumber();
bool isOutput = &operand == op.getDpsInitOperand(0);
AffineMap idxMap = idxMapArray[tid];
InadmissInfo inAdInfo = collectInadmissInfo(idxMap, isOutput);
auto [inAdLvls, dimExprs] = inAdInfo;
for (unsigned d : dimExprs.set_bits()) {
if (d < boundedNum)
return std::nullopt;
}
if (inAdLvls.count() != 0) {
SmallVector<int64_t> lvlShape = stt->getLvlShape();
DenseMap<AffineExpr, AffineExpr> cstMapping;
unsigned position = 0;
for (unsigned lvl : inAdLvls.set_bits()) {
int64_t lvlSz = lvlShape[lvl];
populateCstMapping(cstMapping, position, lvlSz);
position++;
}
AffineMap lvl2Idx = genReplaceDimToLvlMap(inAdInfo, idxMap, itTps);
for (unsigned tid = 0, e = idxMapArray.size(); tid < e; tid++) {
AffineMap transMap = idxMapArray[tid].compose(lvl2Idx);
idxMapArray[tid] = transMap.replace(
cstMapping, transMap.getNumDims(),
0);
}
changed = true;
boundedNum += inAdLvls.count();
}
}
};
SmallVector<Attribute> iterAttr =
llvm::map_to_vector(itTps, [ctx](auto itTp) -> Attribute {
return linalg::IteratorTypeAttr::get(ctx, itTp);
});
return std::make_pair(rewriter.getAffineMapArrayAttr(idxMapArray),
rewriter.getArrayAttr(iterAttr));
}
static Value genDemap(OpBuilder &builder, SparseTensorEncodingAttr enc,
Value val) {
return builder.create<ReinterpretMapOp>(val.getLoc(), enc.withoutDimToLvl(),
val);
}
static Value genRemap(OpBuilder &builder, SparseTensorEncodingAttr enc,
Value val) {
return builder.create<ReinterpretMapOp>(val.getLoc(), enc, val);
}
static SmallVector<Value> remapValueRange(OpBuilder &rewriter, TypeRange types,
ValueRange outs) {
SmallVector<Value> ret(outs);
assert(outs.size() == types.size());
for (auto [r, t] : llvm::zip(ret, types))
if (r.getType() != t)
r = rewriter.create<ReinterpretMapOp>(r.getLoc(), t, r);
return ret;
}
namespace {
struct GenericOpReinterpretMap
: public DemapInsRewriter<GenericOpReinterpretMap, linalg::GenericOp> {
public:
using DemapInsRewriter::DemapInsRewriter;
LogicalResult rewriteOp(linalg::GenericOp linalgOp, OpAdaptor adaptor,
PatternRewriter &rewriter) const {
if (linalgOp.getNumDpsInits() != 1 || !linalgOp.hasPureTensorSemantics() ||
!hasAnySparseOperandOrResult(linalgOp) ||
!hasAnyNonIdentityOperandsOrResults(linalgOp))
return failure();
auto transMap = translateMap(linalgOp, rewriter);
if (!transMap)
return rewriter.notifyMatchFailure(
linalgOp, "the sparse kernel can not be sparsified.");
Value res = linalgOp.getResult(0);
auto stt = tryGetSparseTensorType(res);
auto [idxMap, itTp] = *transMap;
rewriter.startOpModification(linalgOp);
linalgOp.setIndexingMapsAttr(idxMap);
linalgOp.setIteratorTypesAttr(itTp);
linalgOp.getInputsMutable().assign(adaptor.getInputs());
linalgOp.getDpsInitsMutable().assign(adaptor.getOutputs());
res.setType(adaptor.getOutputs()[0].getType());
rewriter.finalizeOpModification(linalgOp);
rewriter.setInsertionPointAfter(linalgOp);
if (stt && stt->hasEncoding()) {
Value t = genRemap(rewriter, stt->getEncoding(), res);
rewriter.replaceAllUsesExcept(res, t, t.getDefiningOp());
}
return success();
}
};
struct GenericOpScheduler : public OpRewritePattern<linalg::GenericOp> {
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(linalg::GenericOp linalgOp,
PatternRewriter &rewriter) const override {
if (linalgOp.getNumDpsInits() != 1 || !linalgOp.hasPureTensorSemantics() ||
hasAnyNonIdentityOperandsOrResults(linalgOp) ||
!hasAnySparseOperandOrResult(linalgOp)) {
return failure();
}
const StringRef sorted = "sorted";
if (linalgOp->hasAttr(sorted))
return failure();
auto scheduler = IterationGraphSorter::fromGenericOp(linalgOp);
bool isAdmissible = false;
AffineMap order;
const auto allMasks = {SortMask::kIncludeAll, SortMask::kIncludeDense,
SortMask::kIncludeDenseInput,
SortMask::kIncludeDenseOutput,
SortMask::kSparseOnly};
for (const SortMask mask : allMasks) {
order = scheduler.sort(mask);
if (order) {
if (isAdmissibleOrder(linalgOp, order)) {
isAdmissible = true;
break;
}
}
}
if (!order) {
if (failed(resolveCycle(scheduler, linalgOp, rewriter))) {
return rewriter.notifyMatchFailure(
linalgOp, "the sparse kernel can not be scheduled: loop detected.");
}
return success();
}
if (!isAdmissible) {
return rewriter.notifyMatchFailure(
linalgOp, "the sparse kernel can not be scheduled.");
}
rewriter.modifyOpInPlace(linalgOp, [&]() {
linalgOp->setAttr(sorted, rewriter.getBoolAttr(true));
});
if (order.isIdentity())
return success();
assert(order.isPermutation());
ArrayAttr preItTypes = linalgOp.getIteratorTypesAttr();
SmallVector<Attribute> curItTypes;
curItTypes.reserve(preItTypes.size());
for (AffineExpr expr : order.getResults()) {
unsigned loopID = llvm::cast<AffineDimExpr>(expr).getPosition();
curItTypes.push_back(preItTypes[loopID]);
}
order = inversePermutation(order);
SmallVector<AffineMap> idxMaps = linalgOp.getIndexingMapsArray();
for (AffineMap &idxMap : idxMaps)
idxMap = idxMap.compose(order);
rewriter.startOpModification(linalgOp);
linalgOp.setIndexingMapsAttr(rewriter.getAffineMapArrayAttr(idxMaps));
linalgOp.setIteratorTypesAttr(rewriter.getArrayAttr(curItTypes));
rewriter.finalizeOpModification(linalgOp);
return success();
}
private:
static bool isAdmissibleOrder(linalg::GenericOp linalgOp, AffineMap order) {
if (!hasAnySparseResult(linalgOp))
return true;
OpOperand *lhs = linalgOp.getDpsInitOperand(0);
unsigned nest = 0;
const auto iteratorTypes = linalgOp.getIteratorTypesArray();
for (const AffineExpr l : order.getResults()) {
unsigned loopId = llvm::cast<AffineDimExpr>(l).getPosition();
auto itTp =
cast<linalg::IteratorTypeAttr>(linalgOp.getIteratorTypes()[loopId]);
if (linalg::isReductionIterator(itTp.getValue()))
break;
nest++;
}
return static_cast<int64_t>(nest) >= linalgOp.getRank(lhs) - 1;
};
static LogicalResult resolveCycle(IterationGraphSorter &scheduler,
linalg::LinalgOp linalgOp,
PatternRewriter &rewriter) {
for (OpOperand *t : linalgOp.getDpsInputOperands()) {
Value tval = t->get();
auto srcEnc = getSparseTensorEncoding(tval.getType());
AffineMap idxMap = linalgOp.getMatchingIndexingMap(t);
bool hasCompExpr = llvm::any_of(idxMap.getResults(), [](AffineExpr exp) {
return !llvm::isa<AffineDimExpr>(exp);
});
if (!srcEnc || hasCompExpr)
continue;
AffineMap order = scheduler.sort(SortMask::kSparseOnly, tval);
if (!order)
continue;
auto stt = getSparseTensorType(tval);
assert(stt.isIdentity());
order = inversePermutation(order);
idxMap = idxMap.compose(order);
SmallVector<std::pair<unsigned, unsigned>> lvlSeq;
for (AffineExpr expr : idxMap.getResults()) {
unsigned lvl = llvm::cast<AffineDimExpr>(expr).getPosition();
lvlSeq.push_back(std::make_pair(lvl, lvlSeq.size()));
}
llvm::sort(lvlSeq, llvm::less_first());
SmallVector<unsigned> perm =
llvm::to_vector(llvm::make_second_range(lvlSeq));
auto dimToLvl = AffineMap::getPermutationMap(perm, linalgOp.getContext());
assert(!dimToLvl.isIdentity());
rewriter.setInsertionPoint(linalgOp);
RankedTensorType dstTp = stt.withDimToLvl(dimToLvl).getRankedTensorType();
Value dst = rewriter.create<ConvertOp>(tval.getLoc(), dstTp, tval);
rewriter.modifyOpInPlace(linalgOp, [&]() {
linalgOp->setOperand(t->getOperandNumber(), dst);
});
rewriter.setInsertionPointAfter(linalgOp);
rewriter.create<bufferization::DeallocTensorOp>(dst.getLoc(), dst);
return success();
}
return failure();
}
};
template <typename AllocOp>
struct TensorAllocDemapper : public OpRewritePattern<AllocOp> {
using OpRewritePattern<AllocOp>::OpRewritePattern;
LogicalResult matchAndRewrite(AllocOp op,
PatternRewriter &rewriter) const override {
if (!hasAnyNonIdentityOperandsOrResults(op))
return failure();
Location loc = op.getLoc();
auto stt = getSparseTensorType(op.getResult());
SmallVector<Value> maxDimCrds;
maxDimCrds.reserve(stt.getDimRank());
ValueRange dynSz = op.getDynamicSizes();
for (int64_t dimSz : stt.getDimShape()) {
if (ShapedType::isDynamic(dimSz)) {
Value maxCrd = rewriter.create<arith::SubIOp>(
loc, dynSz.front(), constantIndex(rewriter, loc, 1));
maxDimCrds.push_back(maxCrd);
dynSz = dynSz.drop_front();
} else {
maxDimCrds.push_back(constantIndex(rewriter, loc, dimSz - 1));
}
}
ValueRange maxLvlCrds = stt.translateCrds(rewriter, loc, maxDimCrds,
CrdTransDirectionKind::dim2lvl);
auto lvlShape = stt.getLvlShape();
SmallVector<Value> dynLvlSzs;
for (unsigned i = 0, e = lvlShape.size(); i < e; i++) {
if (ShapedType::isDynamic(lvlShape[i])) {
Value sz = rewriter.create<arith::AddIOp>(
loc, maxLvlCrds[i], constantIndex(rewriter, loc, 1));
dynLvlSzs.push_back(sz);
}
}
assert(dynSz.empty());
rewriter.startOpModification(op);
op->setOperands(dynLvlSzs);
op.getResult().setType(stt.getDemappedType());
rewriter.finalizeOpModification(op);
rewriter.setInsertionPointAfter(op);
Value t = genRemap(rewriter, stt.getEncoding(), op.getResult());
rewriter.replaceAllUsesExcept(op.getResult(), t, t.getDefiningOp());
return success();
}
};
struct TensorInsertDemapper
: public DemapInsRewriter<TensorInsertDemapper, tensor::InsertOp> {
using DemapInsRewriter::DemapInsRewriter;
LogicalResult rewriteOp(tensor::InsertOp op, OpAdaptor adaptor,
PatternRewriter &rewriter) const {
if (!hasAnySparseResult(op) || !hasAnyNonIdentityOperandsOrResults(op))
return failure();
Location loc = op.getLoc();
auto stt = getSparseTensorType(op.getResult());
ValueRange lvlCrd = stt.translateCrds(rewriter, loc, op.getIndices(),
CrdTransDirectionKind::dim2lvl);
auto insertOp = rewriter.create<tensor::InsertOp>(
loc, op.getScalar(), adaptor.getDest(), lvlCrd);
Value out = genRemap(rewriter, stt.getEncoding(), insertOp.getResult());
rewriter.replaceOp(op, out);
return success();
}
};
struct SparseAssembleDemapper : public OpRewritePattern<AssembleOp> {
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(AssembleOp op,
PatternRewriter &rewriter) const override {
if (!hasAnyNonIdentityOperandsOrResults(op))
return failure();
assert(hasAnySparseResult(op));
auto stt = getSparseTensorType(op.getResult());
rewriter.modifyOpInPlace(
op, [&op, &stt]() { op.getResult().setType(stt.getDemappedType()); });
rewriter.setInsertionPointAfter(op);
Value out = genRemap(rewriter, stt.getEncoding(), op.getResult());
rewriter.replaceAllUsesExcept(op, out, out.getDefiningOp());
return success();
}
};
struct SparseDisassembleDemapper
: public DemapInsRewriter<SparseDisassembleDemapper, DisassembleOp> {
using DemapInsRewriter::DemapInsRewriter;
LogicalResult rewriteOp(DisassembleOp op, OpAdaptor adaptor,
PatternRewriter &rewriter) const {
if (!hasAnyNonIdentityOperandsOrResults(op))
return failure();
assert(hasAnySparseOperandOrResult(op));
rewriter.modifyOpInPlace(op, [&op, &adaptor]() {
op.getTensorMutable().assign(adaptor.getTensor());
});
return success();
}
};
struct ForeachOpDemapper
: public DemapInsRewriter<ForeachOpDemapper, ForeachOp> {
using DemapInsRewriter::DemapInsRewriter;
LogicalResult rewriteOp(ForeachOp op, OpAdaptor adaptor,
PatternRewriter &rewriter) const {
if (!hasAnyNonIdentityOperandsOrResults(op))
return failure();
if (auto constOp = op.getTensor().getDefiningOp<arith::ConstantOp>())
if (auto attr = dyn_cast<SparseElementsAttr>(constOp.getValue()))
return failure();
Location loc = op.getLoc();
auto srcStt = getSparseTensorType(op.getTensor());
SmallVector<Type> prevRetTps(op.getResultTypes());
rewriter.startOpModification(op);
op.getTensorMutable().assign(adaptor.getTensor());
op.getInitArgsMutable().assign(adaptor.getInitArgs());
for (auto r : op.getResults())
if (auto stt = tryGetSparseTensorType(r); stt && !stt->isIdentity())
r.setType(stt->getDemappedType());
Level lvlRank = getSparseTensorType(adaptor.getTensor()).getLvlRank();
SmallVector<Type> blockArgTps(lvlRank, rewriter.getIndexType());
blockArgTps.push_back(srcStt.getElementType());
blockArgTps.append(adaptor.getInitArgs().getTypes().begin(),
adaptor.getInitArgs().getTypes().end());
Block *body = op.getBody();
unsigned preArgNum = body->getNumArguments();
for (Type t : blockArgTps)
body->addArgument(t, loc);
rewriter.setInsertionPointToStart(body);
ValueRange lvlCrds = body->getArguments().slice(preArgNum, lvlRank);
ValueRange dimCrds = srcStt.translateCrds(rewriter, loc, lvlCrds,
CrdTransDirectionKind::lvl2dim);
rewriter.replaceAllUsesWith(
body->getArguments().take_front(srcStt.getDimRank()), dimCrds);
body->eraseArguments(0, srcStt.getDimRank());
unsigned numInitArgs = op.getInitArgs().size();
rewriter.replaceAllUsesWith(body->getArgument(0),
body->getArgument(lvlRank + numInitArgs + 1));
body->eraseArgument(0);
ValueRange srcArgs = body->getArguments().take_front(numInitArgs);
ValueRange dstArgs = body->getArguments().take_back(numInitArgs);
SmallVector<Value> reMappedArgs =
remapValueRange(rewriter, srcArgs.getTypes(), dstArgs);
rewriter.replaceAllUsesWith(srcArgs, reMappedArgs);
body->eraseArguments(0, numInitArgs);
if (numInitArgs != 0) {
rewriter.setInsertionPointToEnd(body);
auto yield = llvm::cast<YieldOp>(body->getTerminator());
if (auto stt = tryGetSparseTensorType(yield.getSingleResult());
stt && !stt->isIdentity()) {
Value y =
genDemap(rewriter, stt->getEncoding(), yield.getSingleResult());
rewriter.create<YieldOp>(loc, y);
rewriter.eraseOp(yield);
}
}
rewriter.finalizeOpModification(op);
rewriter.setInsertionPointAfter(op);
SmallVector<Value> outs =
remapValueRange(rewriter, prevRetTps, op.getResults());
for (auto [from, to] : llvm::zip(op.getResults(), outs))
rewriter.replaceAllUsesExcept(from, to, to.getDefiningOp());
return success();
}
};
}
void mlir::populateSparseReinterpretMap(RewritePatternSet &patterns,
ReinterpretMapScope scope) {
if (scope == ReinterpretMapScope::kAll ||
scope == ReinterpretMapScope::kGenericOnly) {
patterns.add<GenericOpReinterpretMap, GenericOpScheduler>(
patterns.getContext());
}
if (scope == ReinterpretMapScope::kAll ||
scope == ReinterpretMapScope::kExceptGeneric) {
patterns.add<TensorAllocDemapper<bufferization::AllocTensorOp>,
TensorAllocDemapper<tensor::EmptyOp>, SparseAssembleDemapper,
SparseDisassembleDemapper, TensorInsertDemapper,
ForeachOpDemapper>(patterns.getContext());
}
}