MLIR 24.0.0git
SparseReinterpretMap.cpp
Go to the documentation of this file.
1//===- SparseReinterpretMap.cpp - reinterpret sparse tensor maps ----------===/
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
11
20#include "mlir/IR/AffineMap.h"
21
22using namespace mlir;
23using namespace mlir::sparse_tensor;
24
25namespace {
26
27//===----------------------------------------------------------------------===//
28// File Local Helper classes.
29//===----------------------------------------------------------------------===//
30
31// CRTP to help implementing a rewriter that demaps all its inputs.
32template <typename SubClass, typename SourceOp>
33struct DemapInsRewriter : public OpRewritePattern<SourceOp> {
34 using OpRewritePattern<SourceOp>::OpRewritePattern;
35 using OpAdaptor = typename SourceOp::Adaptor;
36
37 LogicalResult matchAndRewrite(SourceOp op,
38 PatternRewriter &rewriter) const override {
39 Location loc = op.getLoc();
40
41 for (Value in : op->getOperands())
42 if (auto stt = tryGetSparseTensorType(in);
43 stt && !stt->isIdentity() &&
44 stt->getEncoding().getDimToLvl().getNumSymbols() != 0)
45 return failure();
46
47 // Demaps non-trivial inputs.
48 bool changed = false;
49 SmallVector<Value> deMappedIns(op->getOperands());
50 for (Value &in : deMappedIns) {
51 if (auto stt = tryGetSparseTensorType(in); stt && !stt->isIdentity()) {
52 in =
53 ReinterpretMapOp::create(rewriter, loc, stt->getDemappedType(), in);
54 changed = true;
55 }
56 }
57
58 // CRTP call.
59 OpAdaptor adaptor(deMappedIns, op);
60 LogicalResult status =
61 static_cast<const SubClass *>(this)->rewriteOp(op, adaptor, rewriter);
62 return changed ? success() : status;
63 }
64};
65
66// Flattens an affine expression into a list of AffineDimExprs.
67struct AffineDimCollector : public AffineExprVisitor<AffineDimCollector> {
68 explicit AffineDimCollector(unsigned dimNum) : dims(dimNum) {};
69 void visitDimExpr(AffineDimExpr expr) { dims.set(expr.getPosition()); }
70 BitVector dims;
71};
72
73// Flattens an affine expression into a list of AffineDimExprs.
74struct AffineExprAdmissibleVisitor
75 : public AffineExprVisitor<AffineExprAdmissibleVisitor> {
76 explicit AffineExprAdmissibleVisitor(bool isOutput) : isOutput(isOutput) {};
77
78 // We only allow AffineDimExpr on output.
79 void visitAddExpr(AffineBinaryOpExpr expr) {
80 if (isOutput)
81 admissible = false;
82 }
83 void visitMulExpr(AffineBinaryOpExpr expr) {
84 if (isOutput)
85 admissible = false;
86 }
87
88 // We disallow mod, floor div and ceil div on inputs.
89 void visitModExpr(AffineBinaryOpExpr expr) { admissible = false; }
90 void visitFloorDivExpr(AffineBinaryOpExpr expr) { admissible = false; }
91 void visitCeilDivExpr(AffineBinaryOpExpr expr) { admissible = false; }
92 operator bool() { return admissible; }
93
94private:
95 bool admissible = true;
96 bool isOutput;
97};
98
99// The first BitVector stores levels where inadmissible exprs are used.
100// The second BitVector stores the AffineDimExp that are used by the
101// inadmissible expressions.
102using InadmissInfo = std::pair<BitVector, BitVector>;
103
104} // namespace
105
106//===----------------------------------------------------------------------===//
107// File Local Helper methods.
108//===----------------------------------------------------------------------===//
109
110// Collects the inadmissible affine expression imposed on levels.
111static InadmissInfo collectInadmissInfo(AffineMap map, bool isOutput) {
112 auto ret = std::make_pair(BitVector(map.getNumResults()),
113 BitVector(map.getNumDims()));
114 AffineDimCollector collector(map.getNumDims());
115 for (unsigned lvl = 0, e = map.getNumResults(); lvl < e; lvl++) {
116 AffineExprAdmissibleVisitor admissible(isOutput);
117 admissible.walkPostOrder(map.getResult(lvl));
118 if (!admissible) {
119 // Record the inadmissible level.
120 ret.first.set(lvl);
121 // Record the AffineDimExpr that is used in the inadmissible expr.
122 collector.walkPostOrder(map.getResult(lvl));
123 }
124 }
125 ret.second = collector.dims;
126 return ret;
127}
128
129// Builds the AffineMap to replace the idx in idxMap to lvl such that all tht
130// inadmissible affine expressions can be eliminated.
131// For example, we can rewrite
132// idxMap = (d0, d1) -> (d0 floordiv 2, d1 floordiv 3, d0 mod 2, d1 mod 3)
133// to
134// idxMap = (l0, l1, l2, l3) -> (l0, l1, l2, l3)
135// by composing inverse(idxMap), that is
136// inverse(idxMap) . idxMap = (l0, l1, l2, l3) -> (l0 * 2 + l2, l1 * 3 + l3)
137// -> ((l0 * 2 + l2) floordiv 2,
138// (l1 * 3 + l3) floordiv 3,
139// (l0 * 2 + l2) mod 2,
140// (l1 * 3 + l3) mod 3) = (l0, l1, l2, l3)
141//
142// This function builds the inverse(idxMap) that replace every dimensions used
143// in `info` to levels, and updates the iterator type array `itTps` for the new
144// index variable introduced.
145//
146// Note that the returned affine map does not retain the order of the input
147// affine map. Instead, it always uses the first `info.inAdlvls.count()` for the
148// replaced levels, and remaining ones for unused dimensions.
149// For example, to handle
150// idxMap = (d0, d1) -> (d0, d1 floordiv 4, d2 mod 4)
151// which is a typical map for block_2to4. The function returns:
152// inverse(idxMap) = (l0, l1, d0) -> (d0, l0 * 4 + l1)
153// in which, (l0, l1) together replaces `d1`, yet they appear
154// before `d0` in the resulting affine map.
155// The index (loop) order can later be canonicalized by a topo sort.
156static AffineMap
157genReplaceDimToLvlMap(const InadmissInfo &info, AffineMap idxMap,
159 MLIRContext *ctx = idxMap.getContext();
160 auto [inAdLvls, usedDims] = info;
161 // Note that idxMap does not equal to dim2Lvl map, it is computed by
162 // composing idx2Dim(dim2Lvl). They are only equal when idx2Dim is an
163 // ID map.
164 // TODO: we might fail here, in those case we should really return
165 // failure instead of assertion error.
166 auto lvl2Idx = inferLvlToDim(idxMap, ctx);
167
168 assert(lvl2Idx.getNumResults() <= idxMap.getNumDims());
169 if (lvl2Idx.getNumResults() != idxMap.getNumDims()) {
170 // This could happen when some dimensions are projected.
171 // E.g., idx2Lvl = (*i*, j, k) -> (j, k)
172 // ==> lvl2Idx = (j, k) -> (j, k)
173 // In this case, we append the unused dimesion at the end.
174 // ==> lvl2Idx = (j, k, *i*) -> (*i*, j, k)
176 AffineDimCollector usedInLvl(idxMap.getNumDims());
177 for (auto e : idxMap.getResults())
178 usedInLvl.walkPostOrder(e);
179
180 unsigned curUsedDimID = 0;
181 unsigned curUnusedDimID = lvl2Idx.getNumDims();
182
183 BitVector unused = usedInLvl.dims.flip();
184 for (unsigned i = 0; i < idxMap.getNumDims(); i++) {
185 if (unused.test(i))
186 results.push_back(getAffineDimExpr(curUnusedDimID++, ctx));
187 else
188 results.push_back(lvl2Idx.getResult(curUsedDimID++));
189 }
190 lvl2Idx =
191 AffineMap::get(lvl2Idx.getNumDims() + unused.count(), 0, results, ctx);
192 }
193 assert(lvl2Idx.getNumResults() == idxMap.getNumDims());
194
195 // We do not need to replace the DimExpr that is not used in inadmissible
196 // level expressions. We use the first inAdLvl.count() dim to represent the
197 // replaced level, the remainings are reserved for unchanged ones.
198 // Note that results from the inverse map computed previously does not follow
199 // the convention we used, and we need to fix the mismatch below.
200 unsigned curRepID = 0;
201 unsigned curOriID = inAdLvls.count();
205
206 for (unsigned l : inAdLvls.set_bits()) {
207 // By our convention, the inadmissible level `l` always appears in the
208 // leading part (accumulated by curRepID) of the affine map's parameter
209 // list. Record the mapping so that we can replace all the uses of `l` to
210 // the correct position after the translation.
211 dimRep[l] = getAffineDimExpr(curRepID++, ctx);
212 // A new index variable is introduced for the inadmissible level, inherit
213 // the iterator type. E.g., if l0 = d0 floordiv 2, the
214 // iterator type of l0 equals to the iterator type of d0.
215 AffineExpr lvlExp = idxMap.getResult(l);
216 AffineDimCollector collector(idxMap.getNumDims());
217 collector.walkPostOrder(lvlExp);
218 // We assumes a level can only be derived from one dimension.
219 assert(collector.dims.count() == 1);
220 transItTps.push_back(itTps[collector.dims.find_first()]);
221 }
222
223 for (unsigned d = 0, e = idxMap.getNumDims(); d < e; d++) {
224 if (usedDims.test(d)) {
225 // The dimension is used in some of the inadmissible levels, and it need
226 // to be inversed. Get the inversion from the inverse map, and fix the
227 // mismatch captured by the above loop.
228 results.push_back(lvl2Idx.getResult(d).replaceDims(dimRep));
229 } else {
230 // The dimension is not used in any of the inadmissible levels, and it
231 // does not need to be inversed. Fix the mismatch by mapping it to the
232 // trailing part of the affine map (accumulated by curOriID).
233 results.push_back(getAffineDimExpr(curOriID++, ctx));
234 transItTps.push_back(itTps[d]);
235 }
236 }
237 unsigned numDim = idxMap.getNumDims() - usedDims.count() + inAdLvls.count();
238 // Update iterator type.
239 itTps.assign(transItTps.begin(), transItTps.end());
240 return AffineMap::get(numDim, 0, results, ctx);
241}
242
243// Translates the index map in the linalg::GenericOp from idx->dim map to
244// idx->lvl map. Returns failure if the index map can not be translated to an
245// admissible form.
246// Returns the translated index map array and the iterator type array.
247static std::optional<std::pair<ArrayAttr, ArrayAttr>>
248translateMap(linalg::GenericOp op, PatternRewriter &rewriter) {
249 // idxMap is a idx2dim map before reinterpretation.
250 MLIRContext *ctx = op.getContext();
251 SmallVector<AffineMap> idxMapArray = op.getIndexingMapsArray();
252 SmallVector<utils::IteratorType> itTps = op.getIteratorTypesArray();
253 for (unsigned i = 0, e = idxMapArray.size(); i < e; i++) {
254 Value tensor = op->getOpOperand(i).get();
255 auto stt = tryGetSparseTensorType(tensor);
256 if (stt && !stt->isIdentity()) {
257 AffineMap dim2Lvl = stt->getDimToLvl();
258 // By composing the idx2dim(dim2lvl), we got a idx2lvl Map
259 idxMapArray[i] = dim2Lvl.compose(idxMapArray[i]);
260 }
261 }
262
263 // A naive way to handle common constant expressions that arise during dim2lvl
264 // translation.
265 auto populateCstMapping = [ctx](DenseMap<AffineExpr, AffineExpr> &cstMapping,
266 unsigned pos, int64_t lvlSz) {
267 if (ShapedType::isStatic(lvlSz)) {
268 auto c0 = getAffineConstantExpr(0, ctx);
269 auto lvlExp = getAffineDimExpr(pos, ctx);
270 auto szExp = getAffineConstantExpr(lvlSz, ctx);
271
272 // lvl floordiv lvlSz = 0
273 auto divExp =
275 cstMapping.try_emplace(divExp, c0);
276
277 // lvl mod lvlSz = lvl
278 auto modExp = getAffineBinaryOpExpr(AffineExprKind::Mod, lvlExp, szExp);
279 cstMapping.try_emplace(modExp, lvlExp);
280 }
281 };
282
283 unsigned boundedNum = 0;
284 // A fixed-point algorithm.
285 bool changed = true;
286 while (changed) {
287 changed = false;
288 for (OpOperand &operand : op->getOpOperands()) {
289 auto stt = tryGetSparseTensorType(operand.get());
290 // Skip on dense operands.
291 if (!stt || !stt->getEncoding())
292 continue;
293
294 unsigned tid = operand.getOperandNumber();
295 bool isOutput = &operand == op.getDpsInitOperand(0);
296 AffineMap idxMap = idxMapArray[tid];
297 InadmissInfo inAdInfo = collectInadmissInfo(idxMap, isOutput);
298 auto [inAdLvls, dimExprs] = inAdInfo;
299 for (unsigned d : dimExprs.set_bits()) {
300 // The first `boundedNum` used in the AffineMap is introduced to
301 // resolve previous inadmissible expressions. We can not replace them
302 // as it might bring back the inadmissible expressions.
303 if (d < boundedNum)
304 return std::nullopt;
305 }
306
307 if (inAdLvls.count() != 0) {
308 // Naive constant progagation, should be sufficient to handle block
309 // sparsity in our cases.
310 SmallVector<int64_t> lvlShape = stt->getLvlShape();
312 unsigned position = 0;
313 for (unsigned lvl : inAdLvls.set_bits()) {
314 int64_t lvlSz = lvlShape[lvl];
315 populateCstMapping(cstMapping, position, lvlSz);
316 position++;
317 }
318
319 AffineMap lvl2Idx = genReplaceDimToLvlMap(inAdInfo, idxMap, itTps);
320 // Compose the lvl2Idx Map to all AffineIdxMap to eliminate
321 // inadmissible expressions.
322 for (unsigned tid = 0, e = idxMapArray.size(); tid < e; tid++) {
323 AffineMap transMap = idxMapArray[tid].compose(lvl2Idx);
324 idxMapArray[tid] = transMap.replace(
325 cstMapping, /*numResultDims=*/transMap.getNumDims(),
326 /*numResultSyms=*/0);
327 }
328 changed = true;
329 boundedNum += inAdLvls.count();
330 }
331 }
332 };
333
334 SmallVector<Attribute> iterAttr =
335 llvm::map_to_vector(itTps, [ctx](auto itTp) -> Attribute {
336 return linalg::IteratorTypeAttr::get(ctx, itTp);
337 });
338
339 return std::make_pair(rewriter.getAffineMapArrayAttr(idxMapArray),
340 rewriter.getArrayAttr(iterAttr));
341}
342
343// Generates a "de"mapping reinterpretation of the map.
344static Value genDemap(OpBuilder &builder, SparseTensorEncodingAttr enc,
345 Value val) {
346 return ReinterpretMapOp::create(builder, val.getLoc(), enc.withoutDimToLvl(),
347 val);
348}
349
350// Generates a "re"mapping reinterpretation of the map.
351static Value genRemap(OpBuilder &builder, SparseTensorEncodingAttr enc,
352 Value val) {
353 return ReinterpretMapOp::create(builder, val.getLoc(), enc, val);
354}
355
357 ValueRange outs) {
358 SmallVector<Value> ret(outs);
359 assert(outs.size() == types.size());
360 for (auto [r, t] : llvm::zip(ret, types))
361 if (r.getType() != t)
362 r = ReinterpretMapOp::create(rewriter, r.getLoc(), t, r);
363 return ret;
364}
365
366namespace {
367
368//===----------------------------------------------------------------------===//
369// Rewriting rules for linalg generic ops.
370//===----------------------------------------------------------------------===//
371
372/// Sparse rewriting rule for the generic `linalg` operation.
373struct GenericOpReinterpretMap
374 : public DemapInsRewriter<GenericOpReinterpretMap, linalg::GenericOp> {
375public:
376 using DemapInsRewriter::DemapInsRewriter;
377 LogicalResult rewriteOp(linalg::GenericOp linalgOp, OpAdaptor adaptor,
378 PatternRewriter &rewriter) const {
379 // Only rewrite single output operations with pure (sparse) tensor
380 // semantics.
381 if (linalgOp.getNumDpsInits() != 1 || !linalgOp.hasPureTensorSemantics() ||
382 !hasAnySparseOperandOrResult(linalgOp) ||
384 return failure();
385
386 // Try translating the index map.
387 auto transMap = translateMap(linalgOp, rewriter);
388 if (!transMap)
389 return rewriter.notifyMatchFailure(
390 linalgOp, "the sparse kernel can not be sparsified.");
391
392 // On success, replace update the linalg operands and maps in place.
393 Value res = linalgOp.getResult(0);
394 auto stt = tryGetSparseTensorType(res);
395 auto [idxMap, itTp] = *transMap;
396
397 rewriter.startOpModification(linalgOp);
398 linalgOp.setIndexingMapsAttr(idxMap);
399 linalgOp.setIteratorTypesAttr(itTp);
400 // Use demapped arguments.
401 linalgOp.getInputsMutable().assign(adaptor.getInputs());
402 linalgOp.getDpsInitsMutable().assign(adaptor.getOutputs());
403 res.setType(adaptor.getOutputs()[0].getType());
404 rewriter.finalizeOpModification(linalgOp);
405
406 rewriter.setInsertionPointAfter(linalgOp);
407 if (stt && stt->hasEncoding()) {
408 Value t = genRemap(rewriter, stt->getEncoding(), res);
409 rewriter.replaceAllUsesExcept(res, t, t.getDefiningOp());
410 }
411 return success();
412 }
413};
414
415struct GenericOpScheduler : public OpRewritePattern<linalg::GenericOp> {
416 GenericOpScheduler(MLIRContext *context,
418 : OpRewritePattern<linalg::GenericOp>(context), strategy(strategy) {}
419
420 LogicalResult matchAndRewrite(linalg::GenericOp linalgOp,
421 PatternRewriter &rewriter) const override {
422 if (linalgOp.getNumDpsInits() != 1 || !linalgOp.hasPureTensorSemantics() ||
423 hasAnyNonIdentityOperandsOrResults(linalgOp) || // need demap first
424 !hasAnySparseOperandOrResult(linalgOp)) {
425 return failure();
426 }
427
428 const StringRef sorted = "sorted";
429 if (linalgOp->hasAttr(sorted))
430 return failure();
431
432 // Pass strategy to IterationGraphSorter.
433 auto scheduler = IterationGraphSorter::fromGenericOp(linalgOp, strategy);
434 bool isAdmissible = false;
435 AffineMap order;
436 // A const list of all masks that we used for iteration graph
437 // computation. Must be ordered from more strict to less strict.
438 // Ideally (though might not be guaranteed), the earlier a constraint mask
439 // can be satisfied, the faster the generated kernel will be.
440 const auto allMasks = {SortMask::kIncludeAll, SortMask::kIncludeDense,
441 SortMask::kIncludeDenseInput,
442 SortMask::kIncludeDenseOutput,
443 SortMask::kSparseOnly};
444 for (const SortMask mask : allMasks) {
445 order = scheduler.sort(mask);
446 if (order) {
447 if (isAdmissibleOrder(linalgOp, order)) {
448 isAdmissible = true;
449 break;
450 }
451 // else try a set of less strict constraints.
452 }
453 }
454
455 if (!order) {
456 // Cycles detected.
457 if (failed(resolveCycle(scheduler, linalgOp, rewriter))) {
458 return rewriter.notifyMatchFailure(
459 linalgOp, "the sparse kernel can not be scheduled: loop detected.");
460 }
461 return success();
462 }
463
464 if (!isAdmissible) {
465 return rewriter.notifyMatchFailure(
466 linalgOp, "the sparse kernel can not be scheduled.");
467 }
468
469 // Marks the GenericOp to avoid recursive matching.
470 rewriter.modifyOpInPlace(linalgOp, [&]() {
471 linalgOp->setAttr(sorted, rewriter.getBoolAttr(true));
472 });
473
474 // Already sorted.
475 if (order.isIdentity())
476 return success();
477
478 assert(order.isPermutation());
479 // `order` is orignial loop -> sorted loop map
480 ArrayAttr preItTypes = linalgOp.getIteratorTypesAttr();
481 SmallVector<Attribute> curItTypes;
482 curItTypes.reserve(preItTypes.size());
483 for (AffineExpr expr : order.getResults()) {
484 unsigned loopID = llvm::cast<AffineDimExpr>(expr).getPosition();
485 curItTypes.push_back(preItTypes[loopID]);
486 }
487
488 // Inverse `order` to get sorted loop -> original loop map
489 order = inversePermutation(order);
490 SmallVector<AffineMap> idxMaps = linalgOp.getIndexingMapsArray();
491 for (AffineMap &idxMap : idxMaps)
492 idxMap = idxMap.compose(order); // sorted loop -> lvl map
493
494 rewriter.startOpModification(linalgOp);
495 linalgOp.setIndexingMapsAttr(rewriter.getAffineMapArrayAttr(idxMaps));
496 linalgOp.setIteratorTypesAttr(rewriter.getArrayAttr(curItTypes));
497 rewriter.finalizeOpModification(linalgOp);
498
499 return success();
500 }
501
502private:
503 /// Whether the loop order is admissible by sparsification.
504 static bool isAdmissibleOrder(linalg::GenericOp linalgOp, AffineMap order) {
505 if (!hasAnySparseResult(linalgOp))
506 return true;
507
508 OpOperand *lhs = linalgOp.getDpsInitOperand(0);
509 unsigned nest = 0;
510 const auto iteratorTypes = linalgOp.getIteratorTypesArray();
511 for (const AffineExpr l : order.getResults()) {
512 unsigned loopId = llvm::cast<AffineDimExpr>(l).getPosition();
513 auto itTp =
514 cast<linalg::IteratorTypeAttr>(linalgOp.getIteratorTypes()[loopId]);
515 if (linalg::isReductionIterator(itTp.getValue()))
516 break; // terminate at first reduction
517 nest++;
518 }
519 // Determine admissible dynamic insertion situations:
520 // (1) fully injective, since there are no reductions,
521 // (2) admissible 1-d expansion in innermost dimension.
522 return static_cast<int64_t>(nest) >= linalgOp.getRank(lhs) - 1;
523 };
524
525 // Last resort cycle resolution.
526 static LogicalResult resolveCycle(IterationGraphSorter &scheduler,
527 linalg::LinalgOp linalgOp,
528 PatternRewriter &rewriter) {
529 // Compute topological sort while leaving out every sparse input tensor in
530 // succession until an acylic iteration graph results.
531 for (OpOperand *t : linalgOp.getDpsInputOperands()) {
532 Value tval = t->get();
533 auto srcEnc = getSparseTensorEncoding(tval.getType());
534 // The constraints introduced by compound index expression are
535 // complicated. Skip them.
536 AffineMap idxMap = linalgOp.getMatchingIndexingMap(t);
537 bool hasCompExpr = llvm::any_of(idxMap.getResults(), [](AffineExpr exp) {
538 return !llvm::isa<AffineDimExpr>(exp);
539 });
540 if (!srcEnc || hasCompExpr)
541 continue;
542
543 // Try scheduling loop without constraints from `tval`.
544 AffineMap order = scheduler.sort(SortMask::kSparseOnly, tval);
545 if (!order) // still cyclic
546 continue;
547
548 // Found an input tensor that resolves the cycle by inserting a
549 // conversion into a sparse tensor that adheres to the iteration
550 // graph order.
551 auto stt = getSparseTensorType(tval);
552 assert(stt.isIdentity());
553 order = inversePermutation(order);
554 // sorted loop -> lvl map.
555 idxMap = idxMap.compose(order);
556
557 // Found a permutation such that the results in `idxMap` is sorted.
558 // For example,
559 // (d0, d1, d2, d3) -> (d2, d1, d0)
560 // loops are scheduled in order of d0->d1->d2->d3, to resolve the cycle,
561 // we find a permutation, perm(d2, d1, d0) -> (d0, d1, d2), such that the
562 // transposed tensor's levels are visited in the same order as the loop
563 // scheduling order.
564 SmallVector<std::pair<unsigned, unsigned>> lvlSeq;
565 for (AffineExpr expr : idxMap.getResults()) {
566 unsigned lvl = llvm::cast<AffineDimExpr>(expr).getPosition();
567 lvlSeq.push_back(std::make_pair(lvl, lvlSeq.size()));
568 }
569 llvm::sort(lvlSeq, llvm::less_first());
570 SmallVector<unsigned> perm =
571 llvm::to_vector(llvm::make_second_range(lvlSeq));
572 auto dimToLvl = AffineMap::getPermutationMap(perm, linalgOp.getContext());
573 // The result of the idxMap must be unsorted.
574 assert(!dimToLvl.isIdentity());
575
576 // Inserting the transpose
577 rewriter.setInsertionPoint(linalgOp);
578 RankedTensorType dstTp = stt.withDimToLvl(dimToLvl).getRankedTensorType();
579 Value dst = ConvertOp::create(rewriter, tval.getLoc(), dstTp, tval);
580 rewriter.modifyOpInPlace(linalgOp, [&]() {
581 linalgOp->setOperand(t->getOperandNumber(), dst);
582 });
583
584 // Release the transposed form afterwards.
585 // TODO: CSE when used in more than one following op?
586 rewriter.setInsertionPointAfter(linalgOp);
587 bufferization::DeallocTensorOp::create(rewriter, dst.getLoc(), dst);
588
589 return success();
590 }
591 // Cannot be resolved with a single conversion.
592 // TODO: convert more than one?
593 return failure();
594 }
595
596private:
598};
599
600//===----------------------------------------------------------------------===//
601// Reinterpret Map Rewriters for operations other than linalg.generics
602//===----------------------------------------------------------------------===//
603
604template <typename AllocOp>
605struct TensorAllocDemapper : public OpRewritePattern<AllocOp> {
606 using OpRewritePattern<AllocOp>::OpRewritePattern;
607 LogicalResult matchAndRewrite(AllocOp op,
608 PatternRewriter &rewriter) const override {
610 return failure();
611
612 Location loc = op.getLoc();
613 auto stt = getSparseTensorType(op.getResult());
614 if (stt.getEncoding().getDimToLvl().getNumSymbols() != 0)
615 return failure();
616
617 SmallVector<Value> maxDimCrds;
618 maxDimCrds.reserve(stt.getDimRank());
619 ValueRange dynSz = op.getDynamicSizes();
620 for (int64_t dimSz : stt.getDimShape()) {
621 if (ShapedType::isDynamic(dimSz)) {
622 Value maxCrd = arith::SubIOp::create(rewriter, loc, dynSz.front(),
623 constantIndex(rewriter, loc, 1));
624 maxDimCrds.push_back(maxCrd);
625 dynSz = dynSz.drop_front();
626 } else {
627 maxDimCrds.push_back(constantIndex(rewriter, loc, dimSz - 1));
628 }
629 }
630
631 ValueRange maxLvlCrds = stt.translateCrds(rewriter, loc, maxDimCrds,
632 CrdTransDirectionKind::dim2lvl);
633 auto lvlShape = stt.getLvlShape();
634 SmallVector<Value> dynLvlSzs;
635 for (unsigned i = 0, e = lvlShape.size(); i < e; i++) {
636 if (ShapedType::isDynamic(lvlShape[i])) {
637 Value sz = arith::AddIOp::create(rewriter, loc, maxLvlCrds[i],
638 constantIndex(rewriter, loc, 1));
639 dynLvlSzs.push_back(sz);
640 }
641 }
642
643 assert(dynSz.empty()); // should have consumed all.
644
645 // Create a new op to let the MLIR builder calculate the correct metadata.
646 auto allocOp =
647 AllocOp::create(rewriter, loc, stt.getDemappedType(), dynLvlSzs);
648
649 Value t = genRemap(rewriter, stt.getEncoding(), allocOp.getResult());
650 rewriter.replaceOp(op, t);
651 return success();
652 }
653};
654
655struct TensorInsertDemapper
656 : public DemapInsRewriter<TensorInsertDemapper, tensor::InsertOp> {
657 using DemapInsRewriter::DemapInsRewriter;
658 LogicalResult rewriteOp(tensor::InsertOp op, OpAdaptor adaptor,
659 PatternRewriter &rewriter) const {
661 return failure();
662
663 Location loc = op.getLoc();
664 auto stt = getSparseTensorType(op.getResult());
665 ValueRange lvlCrd = stt.translateCrds(rewriter, loc, op.getIndices(),
666 CrdTransDirectionKind::dim2lvl);
667 auto insertOp = tensor::InsertOp::create(rewriter, loc, op.getScalar(),
668 adaptor.getDest(), lvlCrd);
669
670 Value out = genRemap(rewriter, stt.getEncoding(), insertOp.getResult());
671 rewriter.replaceOp(op, out);
672 return success();
673 }
674};
675
676struct SparseAssembleDemapper : public OpRewritePattern<AssembleOp> {
678 LogicalResult matchAndRewrite(AssembleOp op,
679 PatternRewriter &rewriter) const override {
681 return failure();
682
683 assert(hasAnySparseResult(op));
684 auto stt = getSparseTensorType(op.getResult());
685 if (stt.getEncoding().getDimToLvl().getNumSymbols() != 0)
686 return failure();
687 rewriter.modifyOpInPlace(
688 op, [&op, &stt]() { op.getResult().setType(stt.getDemappedType()); });
689 rewriter.setInsertionPointAfter(op);
690 Value out = genRemap(rewriter, stt.getEncoding(), op.getResult());
691 rewriter.replaceAllUsesExcept(op, out, out.getDefiningOp());
692 return success();
693 }
694};
695
696struct SparseDisassembleDemapper
697 : public DemapInsRewriter<SparseDisassembleDemapper, DisassembleOp> {
698 using DemapInsRewriter::DemapInsRewriter;
699 LogicalResult rewriteOp(DisassembleOp op, OpAdaptor adaptor,
700 PatternRewriter &rewriter) const {
702 return failure();
703
704 assert(hasAnySparseOperandOrResult(op));
705 rewriter.modifyOpInPlace(op, [&op, &adaptor]() {
706 op.getTensorMutable().assign(adaptor.getTensor());
707 });
708 return success();
709 }
710};
711
712struct ForeachOpDemapper
713 : public DemapInsRewriter<ForeachOpDemapper, ForeachOp> {
714 using DemapInsRewriter::DemapInsRewriter;
715 LogicalResult rewriteOp(ForeachOp op, OpAdaptor adaptor,
716 PatternRewriter &rewriter) const {
717 // Only handle operations with sparse input/output with non-identity dim2lvl
718 // maps.
720 return failure();
721
722 // TODO: demap constant as well.
723 if (auto constOp = op.getTensor().getDefiningOp<arith::ConstantOp>())
724 if (auto attr = dyn_cast<SparseElementsAttr>(constOp.getValue()))
725 return failure();
726
727 Location loc = op.getLoc();
728 // Cache the type information since we update the foreach op in-place.
729 auto srcStt = getSparseTensorType(op.getTensor());
730 SmallVector<Type> prevRetTps(op.getResultTypes());
731
732 rewriter.startOpModification(op);
733 op.getTensorMutable().assign(adaptor.getTensor());
734 op.getInitArgsMutable().assign(adaptor.getInitArgs());
735 // Update results' types.
736 for (auto r : op.getResults())
737 if (auto stt = tryGetSparseTensorType(r); stt && !stt->isIdentity())
738 r.setType(stt->getDemappedType());
739
740 Level lvlRank = getSparseTensorType(adaptor.getTensor()).getLvlRank();
741 // Update the foreach body.
742 SmallVector<Type> blockArgTps(lvlRank, rewriter.getIndexType());
743 blockArgTps.push_back(srcStt.getElementType());
744 blockArgTps.append(adaptor.getInitArgs().getTypes().begin(),
745 adaptor.getInitArgs().getTypes().end());
746 Block *body = op.getBody();
747 // Block Args: [dimCrd, val, initArgs]
748 unsigned preArgNum = body->getNumArguments();
749 for (Type t : blockArgTps)
750 body->addArgument(t, loc);
751
752 // Block Args: [dimCrd, val, initArgs, lvlCrds, val, DemappedArgs]
753 rewriter.setInsertionPointToStart(body);
754 ValueRange lvlCrds = body->getArguments().slice(preArgNum, lvlRank);
755
756 ValueRange dimCrds = srcStt.translateCrds(rewriter, loc, lvlCrds,
757 CrdTransDirectionKind::lvl2dim);
758 rewriter.replaceAllUsesWith(
759 body->getArguments().take_front(srcStt.getDimRank()), dimCrds);
760 body->eraseArguments(0, srcStt.getDimRank());
761 // Block Args: [val, initArgs, lvlCrds, val, DemappedArgs]
762 unsigned numInitArgs = op.getInitArgs().size();
763 rewriter.replaceAllUsesWith(body->getArgument(0),
764 body->getArgument(lvlRank + numInitArgs + 1));
765 body->eraseArgument(0);
766 // Block Args: [initArgs, lvlCrds, val, DemappedArgs]
767 ValueRange srcArgs = body->getArguments().take_front(numInitArgs);
768 ValueRange dstArgs = body->getArguments().take_back(numInitArgs);
769 // Remap back before replacement.
770 SmallVector<Value> reMappedArgs =
771 remapValueRange(rewriter, srcArgs.getTypes(), dstArgs);
772 rewriter.replaceAllUsesWith(srcArgs, reMappedArgs);
773 body->eraseArguments(0, numInitArgs);
774 // Block Args: [lvlCrds, DemappedArgs] and we are done.
775
776 // Update yield operations.
777 if (numInitArgs != 0) {
778 rewriter.setInsertionPointToEnd(body);
779 auto yield = llvm::cast<YieldOp>(body->getTerminator());
780 if (auto stt = tryGetSparseTensorType(yield.getSingleResult());
781 stt && !stt->isIdentity()) {
782 Value y =
783 genDemap(rewriter, stt->getEncoding(), yield.getSingleResult());
784 YieldOp::create(rewriter, loc, y);
785 rewriter.eraseOp(yield);
786 }
787 }
788 rewriter.finalizeOpModification(op);
789
790 rewriter.setInsertionPointAfter(op);
791 SmallVector<Value> outs =
792 remapValueRange(rewriter, prevRetTps, op.getResults());
793
794 // Replace all the uses of the foreach results, expect the use in
795 // reinterpret_map used to remap the output.
796 for (auto [from, to] : llvm::zip(op.getResults(), outs))
797 rewriter.replaceAllUsesExcept(from, to, to.getDefiningOp());
798
799 return success();
800 }
801};
802
803} // namespace
804
806 RewritePatternSet &patterns, ReinterpretMapScope scope,
808 if (scope == ReinterpretMapScope::kAll ||
810 patterns.add<GenericOpReinterpretMap>(patterns.getContext());
811 patterns.add<GenericOpScheduler>(patterns.getContext(), strategy);
812 }
813 if (scope == ReinterpretMapScope::kAll ||
815 patterns.add<TensorAllocDemapper<bufferization::AllocTensorOp>,
816 TensorAllocDemapper<tensor::EmptyOp>, SparseAssembleDemapper,
817 SparseDisassembleDemapper, TensorInsertDemapper,
818 ForeachOpDemapper>(patterns.getContext());
819 }
820}
return success()
lhs
ArrayAttr()
static Value genDemap(OpBuilder &builder, SparseTensorEncodingAttr enc, Value val)
static SmallVector< Value > remapValueRange(OpBuilder &rewriter, TypeRange types, ValueRange outs)
static AffineMap genReplaceDimToLvlMap(const InadmissInfo &info, AffineMap idxMap, SmallVector< utils::IteratorType > &itTps)
static std::optional< std::pair< ArrayAttr, ArrayAttr > > translateMap(linalg::GenericOp op, PatternRewriter &rewriter)
static Value genRemap(OpBuilder &builder, SparseTensorEncodingAttr enc, Value val)
static InadmissInfo collectInadmissInfo(AffineMap map, bool isOutput)
unsigned getPosition() const
See documentation for AffineExprVisitorBase.
Base type for affine expression.
Definition AffineExpr.h:68
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition AffineMap.h:46
MLIRContext * getContext() const
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
unsigned getNumDims() const
ArrayRef< AffineExpr > getResults() const
unsigned getNumResults() const
AffineExpr getResult(unsigned idx) const
AffineMap replace(AffineExpr expr, AffineExpr replacement, unsigned numResultDims, unsigned numResultSyms) const
Sparse replace method.
static AffineMap getPermutationMap(ArrayRef< unsigned > permutation, MLIRContext *context)
Returns an AffineMap representing a permutation.
AffineMap compose(AffineMap map) const
Returns the AffineMap resulting from composing this with map.
bool isIdentity() const
Returns true if this affine map is an identity affine map.
bool isPermutation() const
Returns true if the AffineMap represents a symbol-less permutation map.
Attributes are known-constant values of operations.
Definition Attributes.h:25
BlockArgument getArgument(unsigned i)
Definition Block.h:153
unsigned getNumArguments()
Definition Block.h:152
Operation * getTerminator()
Get the terminator operation of this block.
Definition Block.cpp:249
BlockArgument addArgument(Type type, Location loc)
Add one value to the argument list.
Definition Block.cpp:158
void eraseArguments(unsigned start, unsigned num)
Erases 'num' arguments from the index 'start'.
Definition Block.cpp:206
BlockArgListType getArguments()
Definition Block.h:111
void eraseArgument(unsigned index)
Erase the argument at 'index' and remove it from the argument list.
Definition Block.cpp:198
BoolAttr getBoolAttr(bool value)
Definition Builders.cpp:108
ArrayAttr getArrayAttr(ArrayRef< Attribute > value)
Definition Builders.cpp:275
IndexType getIndexType()
Definition Builders.cpp:59
ArrayAttr getAffineMapArrayAttr(ArrayRef< AffineMap > values)
Definition Builders.cpp:327
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
This class helps build Operations.
Definition Builders.h:210
void setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
Definition Builders.h:434
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:401
void setInsertionPointToEnd(Block *block)
Sets the insertion point to the end of the specified block.
Definition Builders.h:439
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Definition Builders.h:415
This class represents an operand of an operation.
Definition Value.h:254
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
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 replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
virtual void finalizeOpModification(Operation *op)
This method is used to signal the end of an in-place modification of the given operation.
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
void replaceAllUsesExcept(Value from, Value to, Operation *exceptedUser)
Find uses of from and replace them with to except if the user is exceptedUser.
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,...
void modifyOpInPlace(Operation *root, CallableT &&callable)
This method is a utility wrapper around an in-place modification of an operation.
virtual void replaceAllUsesWith(Value from, Value to)
Find uses of from and replace them with to.
virtual void startOpModification(Operation *op)
This method is used to notify the rewriter that an in-place operation modification is about to happen...
This class provides an abstraction over the various different ranges of value types.
Definition TypeRange.h:40
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
type_range getTypes() const
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
void setType(Type newType)
Mutate the type of this Value to be of the specified type.
Definition Value.h:116
Type getType() const
Return the type of this value.
Definition Value.h:105
Location getLoc() const
Return the location of this value.
Definition Value.cpp:24
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
static IterationGraphSorter fromGenericOp(linalg::GenericOp genericOp, sparse_tensor::LoopOrderingStrategy strategy)
Factory method that constructs an iteration graph sorter for the given linalg.generic operation with ...
AffineMap sort(SortMask mask, Value ignored=nullptr)
Returns a permutation that represents the scheduled loop order.
Level getLvlRank() const
Returns the level-rank.
bool isReductionIterator(utils::IteratorType iteratorType)
Check if iterator type has "reduction" semantics.
Definition Utils.cpp:236
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
Value constantIndex(OpBuilder &builder, Location loc, int64_t i)
Generates a constant of index type.
bool hasAnySparseOperandOrResult(Operation *op)
Returns true iff MLIR operand has any sparse operand or result.
uint64_t Level
The type of level identifiers and level-ranks.
LoopOrderingStrategy
Defines a strategy for loop ordering during sparse code generation.
Definition Passes.h:62
AffineMap inferLvlToDim(AffineMap dimToLvl, MLIRContext *context)
Given the dimToLvl map, infers the lvlToDim map, or returns empty Affine map when inference fails.
SparseTensorEncodingAttr getSparseTensorEncoding(Type type)
Convenience method to get a sparse encoding attribute from a type.
std::optional< SparseTensorType > tryGetSparseTensorType(Value val)
bool hasAnyNonIdentityOperandsOrResults(Operation *op)
Returns true iff MLIR operation has any sparse tensor with non-identity dim2lvl maps.
SparseTensorType getSparseTensorType(Value val)
Convenience methods to obtain a SparseTensorType from a Value.
SortMask
Iteration graph sorting mask,.
bool hasAnySparseResult(Operation *op)
Returns true iff MLIR operand has any sparse result.
Include the generated interface declarations.
AffineMap inversePermutation(AffineMap map)
Returns a map of codomain to domain dimensions such that the first codomain dimension for a particula...
@ Mod
RHS of mod is always a constant or a symbolic expression with a positive value.
Definition AffineExpr.h:46
@ FloorDiv
RHS of floordiv is always a constant or a symbolic expression.
Definition AffineExpr.h:48
AffineExpr getAffineBinaryOpExpr(AffineExprKind kind, AffineExpr lhs, AffineExpr rhs)
ReinterpretMapScope
Defines a scope for reinterpret map pass.
Definition Passes.h:45
AffineExpr getAffineConstantExpr(int64_t constant, MLIRContext *context)
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
AffineExpr getAffineDimExpr(unsigned position, MLIRContext *context)
These free functions allow clients of the API to not use classes in detail.
void populateSparseReinterpretMap(RewritePatternSet &patterns, ReinterpretMapScope scope, sparse_tensor::LoopOrderingStrategy strategy=sparse_tensor::LoopOrderingStrategy::kDefault)
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...