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