MLIR 24.0.0git
PackAndUnpackPatterns.cpp
Go to the documentation of this file.
1//===- FoldIntoPackAndUnpackPatterns.cpp ----------------------------------===//
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
16
17namespace mlir {
18namespace linalg {
19namespace {
20
21/// Returns the number of shape sizes that is either dynamic or greater than 1.
22static int64_t getNumGtOneDims(ArrayRef<int64_t> shape) {
23 return llvm::count_if(
24 shape, [](int64_t v) { return ShapedType::isDynamic(v) || v > 1; });
25}
26
27/// Returns the index of the first non-unit size in `sizes`. Returns -1 if
28/// there are no non-unit sizes.
29static int64_t getFirstNonUnitSizeIdx(ArrayRef<int64_t> sizes) {
30 const auto *it = llvm::find_if(sizes, [](int64_t dim) { return dim != 1; });
31 return (it != sizes.end()) ? std::distance(sizes.begin(), it) : -1;
32}
33
34/// Check whether `op` is effectively a 1D pack/unpack. Example:
35///
36/// %pack = linalg.pack %src
37/// inner_dims_pos = [0, 1]
38/// inner_tiles = [1, 2] into %dest
39/// : tensor<1x32xf32> -> tensor<1x16x1x2xf32>
40///
41/// Returns success() if there is:
42/// * only 1 non-unit dim in the un-packed domain,
43/// * only 1 non-unit inner tile size, and
44/// * the unique non-unit tile size is applied to the unique non-unit
45/// un-packed dim.
46template <typename PackOrUnpackOp>
47static LogicalResult isPackOnEffectively1D(RewriterBase &rewriter,
48 PackOrUnpackOp *op) {
49 // Obtain the unpacked shape.
50 auto pack = dyn_cast<linalg::PackOp>(op);
51 auto unpack = dyn_cast<linalg::UnPackOp>(op);
52
53 ArrayRef<int64_t> unpackedShape = pack ? pack->getSourceType().getShape()
54 : unpack->getDestType().getShape();
55
56 // Obtain the inner tile sizes.
57 ArrayRef<int64_t> innerTileSizes = op->getStaticInnerTiles();
58
59 // Make sure that there is exactly single non-unit unpacked dim.
60 if (getNumGtOneDims(unpackedShape) != 1) {
61 return rewriter.notifyMatchFailure(
62 *op, "expects non-packed domain to have at most one non-unit dims");
63 }
64
65 // Make sure that there is at most one non-unit inner tile size.
66 auto numNonUnitInnerTiles = getNumGtOneDims(innerTileSizes);
67 if (numNonUnitInnerTiles > 1) {
68 return rewriter.notifyMatchFailure(
69 *op, "expects at most one non-unit inner tiles");
70 }
71
72 // If there are no non-unit tiles, there is nothing else to check.
73 if (numNonUnitInnerTiles == 0)
74 return success();
75
76 // Get the index of the unique non-unit unpacked dim.
77 int64_t nonUnitDimIdx = getFirstNonUnitSizeIdx(unpackedShape);
78
79 // Get the index of the dim that the unique non-unit tile is applied to.
80 int64_t nonUnitTileDestDimIdx = getFirstNonUnitSizeIdx(innerTileSizes);
81
82 // Make sure that the unique non-unit tile is applied to the unique unit dim.
83 if (nonUnitTileDestDimIdx != nonUnitDimIdx) {
84 return rewriter.notifyMatchFailure(
85 *op, "expects at most one non-unit inner tiles");
86 }
87
88 return success();
89}
90
91// If the `linalgOp` represents a transpose, return the permutation vector for
92// the transpose. Otherwise, return failure.
93static FailureOr<SmallVector<int64_t>>
94getTransposeOpPermutation(linalg::LinalgOp linalgOp) {
95 if (auto transposeOp = dyn_cast<linalg::TransposeOp>(linalgOp.getOperation()))
96 return SmallVector<int64_t>(transposeOp.getPermutation());
97 if (linalgOp.getNumParallelLoops() != linalgOp.getNumLoops())
98 return failure();
99
100 if (linalgOp.getNumDpsInputs() != 1 || linalgOp.getNumDpsInits() != 1)
101 return failure();
102 auto mapRange = linalgOp.getIndexingMapsArray();
103 if (!mapRange.front().isPermutation() || !mapRange.back().isPermutation() ||
104 mapRange.front() == mapRange.back()) {
105 return failure();
106 }
107 if (!llvm::hasSingleElement(linalgOp.getBlock()->getOperations()))
108 return failure();
109 AffineMap outMap = mapRange.back();
110 AffineMap inMap = mapRange.front();
111 // To get the permutation, look at each output index and find which
112 // dimension in the input we're reading from for that index.
113 return llvm::map_to_vector(outMap.getResults(),
114 [&](AffineExpr expr) -> int64_t {
115 return *inMap.getResultPosition(expr);
116 });
117}
118
119/// Packing one-dimensional tensor can be expressed as an expand shape op.
120struct SimplifyPackToExpandShape : public OpRewritePattern<PackOp> {
121 using OpRewritePattern<PackOp>::OpRewritePattern;
122
123 FailureOr<Value>
124 insertExpand(RewriterBase &rewriter, Location loc, Value operand,
125 Type newOperandType,
126 ArrayRef<ReassociationIndices> reassociation) const {
127 if (operand.getType() == newOperandType)
128 return operand;
129 return tensor::ExpandShapeOp::create(rewriter, loc, newOperandType, operand,
130 reassociation)
131 .getResult();
132 }
133
134 /// Returns success() if it is only packing on the innermost dimension.
135 LogicalResult isPackOnInnerMostDim(RewriterBase &rewriter,
136 PackOp packOp) const {
137 auto outerDimsPerm = packOp.getOuterDimsPerm();
138 if (!outerDimsPerm.empty() && !isIdentityPermutation(outerDimsPerm)) {
139 return rewriter.notifyMatchFailure(
140 packOp,
141 "expects outer_dims_perm is empty or an identity permutation");
142 }
143
144 int64_t srcRank = packOp.getSourceRank();
145 ArrayRef<int64_t> dimsPos = packOp.getInnerDimsPos();
146 if (dimsPos.size() != 1 || (dimsPos[0] + 1 != srcRank)) {
147 return rewriter.notifyMatchFailure(
148 packOp, "expects packing at the innermost dimension");
149 }
150 return success();
151 }
152
153 LogicalResult matchAndRewrite(PackOp packOp,
154 PatternRewriter &rewriter) const override {
155 if (packOp.getPaddingValue())
156 return rewriter.notifyMatchFailure(packOp, "expects no padding value");
157 // TODO: Support Memref PackOp. Temporarily return failure.
158 if (!packOp.hasPureTensorSemantics())
159 return failure();
160
161 ShapedType sourceType = packOp.getSourceType();
162 if (failed(isPackOnInnerMostDim(rewriter, packOp)) &&
163 failed(isPackOnEffectively1D(rewriter, &packOp)) &&
164 !packOp.isLikePad()) {
165 return failure();
166 }
167
168 ShapedType destType = packOp.getDestType();
169 auto reassociation =
170 getReassociationIndicesForReshape(sourceType, destType);
171 if (!reassociation)
172 return failure();
173 FailureOr<Value> expanded =
174 insertExpand(rewriter, packOp.getLoc(), packOp.getSource(), destType,
175 *reassociation);
176 if (failed(expanded)) {
177 return rewriter.notifyMatchFailure(
178 packOp, "unable to expand source of tensor.pack");
179 }
180 rewriter.replaceOp(packOp, *expanded);
181 return success();
182 }
183};
184
185struct SimplifyUnPackToCollapseShape : public OpRewritePattern<UnPackOp> {
186 using OpRewritePattern<UnPackOp>::OpRewritePattern;
187
188 Value insertCollapse(RewriterBase &rewriter, Location loc, Value operand,
189 Type newOperandType, ArrayAttr reassociation) const {
190 if (operand.getType() == newOperandType)
191 return operand;
192 return tensor::CollapseShapeOp::create(rewriter, loc, newOperandType,
193 operand, reassociation);
194 }
195
196 /// Returns success() if it is unpacking on the innermost dimension.
197 LogicalResult isUnpackOnInnerMostDim(RewriterBase &rewriter,
198 UnPackOp unpackOp) const {
199 auto outerDimsPerm = unpackOp.getOuterDimsPerm();
200 if (!outerDimsPerm.empty() && !isIdentityPermutation(outerDimsPerm)) {
201 return rewriter.notifyMatchFailure(
202 unpackOp,
203 "expects outer_dims_perm is empty or an identity permutation");
204 }
205
206 ShapedType sourceType = unpackOp.getSourceType();
207 ShapedType destType = unpackOp.getDestType();
208 if (!sourceType.hasStaticShape() || !destType.hasStaticShape())
209 return rewriter.notifyMatchFailure(unpackOp, "expects static shapes");
210
211 ArrayRef<int64_t> dimsPos = unpackOp.getInnerDimsPos();
212 if (dimsPos.size() != 1 || (dimsPos[0] + 1 != destType.getRank())) {
213 return rewriter.notifyMatchFailure(
214 unpackOp, "expects unpacking on the innermost dimension");
215 }
216
217 return success();
218 }
219
220 LogicalResult matchAndRewrite(UnPackOp unpackOp,
221 PatternRewriter &rewriter) const override {
222 // TODO: Support Memref UnPackOp. Temporarily return failure.
223 if (!unpackOp.hasPureTensorSemantics())
224 return failure();
225
226 ShapedType destType = unpackOp.getDestType();
227 if (failed(isUnpackOnInnerMostDim(rewriter, unpackOp)) &&
228 failed(isPackOnEffectively1D(rewriter, &unpackOp)) &&
229 !unpackOp.isLikeUnPad()) {
230 return failure();
231 }
232
233 ShapedType sourceType = unpackOp.getSourceType();
234 auto reassociation =
235 getReassociationIndicesForReshape(sourceType, destType);
236 if (!reassociation)
237 return failure();
238 Value collapsed = insertCollapse(
239 rewriter, unpackOp.getLoc(), unpackOp.getSource(), destType,
240 getReassociationIndicesAttribute(rewriter, *reassociation));
241 rewriter.replaceOp(unpackOp, collapsed);
242 return success();
243 }
244};
245
246/// Fold a `pad` -> `pack` into `pack` if they have the same padding values and
247/// the pad op has zero low paddings, or if `pack` has no padding values.
248struct FoldPadWithPackOp : public OpRewritePattern<PackOp> {
249public:
250 FoldPadWithPackOp(MLIRContext *context, ControlFoldIntoPackUnpackFn controlFn)
251 : OpRewritePattern<PackOp>(context), controlFn(std::move(controlFn)) {}
252
253 LogicalResult matchAndRewrite(PackOp packOp,
254 PatternRewriter &rewriter) const override {
255 auto padOp = packOp.getSource().getDefiningOp<tensor::PadOp>();
256
257 if (!padOp || padOp.getNofold() || !padOp.hasZeroLowPad())
258 return failure();
259
260 // User controlled folding function.
261 if (controlFn && !controlFn(&packOp.getSourceMutable()))
262 return failure();
263
264 Value constantPaddingValue = padOp.getConstantPaddingValue();
265 if (!constantPaddingValue)
266 return failure();
267
268 if (auto paddingValue = packOp.getPaddingValue())
269 if (!isEqualConstantIntOrValue(paddingValue, constantPaddingValue))
270 return failure();
271
272 // Folding is not allowed if it were to introduce artificial padding.
273 // Folding is also disabled in the case of dynamic dimensions and/or tile
274 // sizes - that is because it would be impossible to compute the padding
275 // size and hence to establish whether "artificial" padding would be
276 // created.
277 ShapedType unpackedType = packOp.getSourceType();
278 SmallVector<int64_t> outerShapeWithoutTranspose =
280 for (auto [pos, tileSize, high] :
281 llvm::zip_equal(packOp.getInnerDimsPos(), packOp.getStaticInnerTiles(),
282 padOp.getMixedHighPad())) {
283 if (unpackedType.isDynamicDim(pos))
284 return failure();
285 if (ShapedType::isDynamic(outerShapeWithoutTranspose[pos]))
286 return failure();
287 if (ShapedType::isDynamic(tileSize))
288 return failure();
289 std::optional<int64_t> cstHigh = getConstantIntValue(high);
290 if (!cstHigh)
291 return failure();
292 int64_t paddingSize = outerShapeWithoutTranspose[pos] * tileSize -
293 unpackedType.getDimSize(pos);
294 // Do not fold the op if it requires artificial padding.
295 if (paddingSize + cstHigh.value() >= tileSize)
296 return failure();
297 }
298
299 rewriter.replaceOpWithNewOp<PackOp>(
300 packOp, padOp.getSource(), packOp.getDest(), packOp.getInnerDimsPos(),
301 packOp.getMixedTiles(), constantPaddingValue,
302 packOp.getOuterDimsPerm());
303 return success();
304 }
305
306private:
308};
309
310/// Fold a `unpack` -> `extract_slice` into the `unpack` since it already
311/// has extract_slice semantics.
312struct FoldUnpackWithExtractSliceOp
313 : public OpRewritePattern<tensor::ExtractSliceOp> {
314public:
315 FoldUnpackWithExtractSliceOp(MLIRContext *context,
317 : OpRewritePattern<tensor::ExtractSliceOp>(context),
318 controlFn(std::move(controlFn)) {}
319
320 LogicalResult matchAndRewrite(tensor::ExtractSliceOp sliceOp,
321 PatternRewriter &rewriter) const override {
322 auto unpackOp = sliceOp.getSource().getDefiningOp<UnPackOp>();
323 if (!unpackOp)
324 return failure();
325
326 // TODO: Support Memref UnPackOp. Temporarily return failure.
327 if (!unpackOp.hasPureTensorSemantics())
328 return failure();
329
330 // User controlled folding function.
331 if (controlFn && !controlFn(&sliceOp.getSourceMutable()))
332 return failure();
333
334 if (!unpackOp.canFoldSliceOp(sliceOp))
335 return failure();
336
337 // Create a new empty output tensor.
338 Type elementType = unpackOp.getDestType().getElementType();
339 Value output = tensor::EmptyOp::create(
340 rewriter, sliceOp.getLoc(), sliceOp.getMixedSizes(), elementType);
341 rewriter.replaceOpWithNewOp<UnPackOp>(
342 sliceOp, unpackOp.getSource(), output, unpackOp.getInnerDimsPos(),
343 unpackOp.getMixedTiles(), unpackOp.getOuterDimsPerm());
344 return success();
345 }
346
347private:
349};
350
351// Applies 'permutation' on 'inVec' and stores the result in resVec.
352// 'inVec' may be empty, in that case it's one-to-one mapping with permutation.
353// `rank` sets the boundary for permutation i.e., the permutation dim can't be
354// greater than the rank specified. If it's so then return false.
355// For e.g., permutation {1, 0, 3, 2} with rank 2 is allowed since the values in
356// permutation[:rank] doesn't exceed rank, whereas, permutation {1, 3, 0, 2} is
357// not allowed since `3` exceeds the value of the rank in the given range.
358static bool checkAndPermute(ArrayRef<int64_t> permutation,
359 ArrayRef<int64_t> inVec,
360 SmallVectorImpl<int64_t> &resVec, int64_t rank) {
361
362 for (unsigned int i = 0; i < rank; ++i) {
363 int64_t remappedPosition = permutation[i];
364 if (remappedPosition >= rank)
365 return false;
366 if (!inVec.empty())
367 remappedPosition = inVec[remappedPosition];
368 resVec.push_back(remappedPosition);
369 }
370
371 return true;
372}
373
374/// Fold 'pack' -> 'transpose' into 'pack' since 'pack' already has transpose
375/// semantics.
376struct FoldProducerPackWithConsumerLinalgTransposeOp
377 : public OpInterfaceRewritePattern<linalg::LinalgOp> {
378
379public:
380 FoldProducerPackWithConsumerLinalgTransposeOp(
381 MLIRContext *context, ControlFoldIntoPackUnpackFn controlFn)
382 : OpInterfaceRewritePattern<linalg::LinalgOp>(context),
383 controlFn(std::move(controlFn)) {}
384
385 LogicalResult matchAndRewrite(linalg::LinalgOp linalgOp,
386 PatternRewriter &rewriter) const override {
387 auto packOp = linalgOp->getOperand(0).getDefiningOp<PackOp>();
388
389 if (!packOp)
390 return failure();
391
392 // TODO: Support Memref PackOp. Temporarily return failure.
393 if (!packOp.hasPureTensorSemantics())
394 return failure();
395
396 // User controlled folding function.
397 if (controlFn && !controlFn(&linalgOp->getOpOperand(0)))
398 return failure();
399
400 FailureOr<SmallVector<int64_t>> maybePerm =
401 getTransposeOpPermutation(linalgOp);
402 if (failed(maybePerm))
403 return failure();
404
405 auto innerDimsPos = packOp.getInnerDimsPos();
406 auto mixedInnerTiles = packOp.getMixedTiles();
407 auto outerDimsPerm = packOp.getOuterDimsPerm();
408 const auto &transposePerm = maybePerm.value();
409 SmallVector<int64_t> newOuterDimsPermVec;
410 SmallVector<int64_t> newInnerDimsPosVec;
411 SmallVector<OpFoldResult> newMixedInnerTilesVec;
412 int64_t srcRank = packOp.getSourceRank();
413
414 if (!checkAndPermute(transposePerm, outerDimsPerm, newOuterDimsPermVec,
415 srcRank))
416 return rewriter.notifyMatchFailure(
417 linalgOp,
418 "Cannot fold in tensor.pack if a tile dimension was transposed "
419 "with a non-tile dimension in linalg.transpose.");
420
421 // Process transpose operation for tiled inner dimensions
422 for (unsigned int i = srcRank; i < transposePerm.size(); ++i) {
423 int64_t remappedPosition = transposePerm[i] - srcRank;
424 newMixedInnerTilesVec.push_back(mixedInnerTiles[remappedPosition]);
425 newInnerDimsPosVec.push_back(innerDimsPos[remappedPosition]);
426 }
427
428 Value output = packOp.createDestinationTensor(
429 rewriter, linalgOp.getLoc(), packOp.getSource(), newMixedInnerTilesVec,
430 newInnerDimsPosVec, newOuterDimsPermVec);
431
432 rewriter.replaceOpWithNewOp<PackOp>(
433 linalgOp, packOp.getSource(), output, newInnerDimsPosVec,
434 newMixedInnerTilesVec, packOp.getPaddingValue(), newOuterDimsPermVec);
435
436 return success();
437 }
438
439private:
441};
442
443/// Fold 'transpose' -> 'pack' into 'pack' since 'pack' already has transpose
444/// semantics.
445struct FoldConsumerPackWithProducerLinalgTransposeOp
446 : public OpRewritePattern<PackOp> {
447
448public:
449 FoldConsumerPackWithProducerLinalgTransposeOp(
450 MLIRContext *context, ControlFoldIntoPackUnpackFn controlFn)
451 : OpRewritePattern<PackOp>(context), controlFn(std::move(controlFn)) {}
452
453 LogicalResult matchAndRewrite(PackOp packOp,
454 PatternRewriter &rewriter) const override {
455 // TODO: Support Memref PackOp. Temporarily return failure.
456 if (!packOp.hasPureTensorSemantics())
457 return failure();
458
459 auto linalgOp = packOp.getSource().getDefiningOp<linalg::LinalgOp>();
460 if (!linalgOp)
461 return failure();
462
463 // User controlled folding function.
464 if (controlFn && !controlFn(&packOp.getSourceMutable()))
465 return failure();
466
467 FailureOr<SmallVector<int64_t>> maybePerm =
468 getTransposeOpPermutation(linalgOp);
469 if (failed(maybePerm))
470 return failure();
471
472 auto transposePermutation = maybePerm.value();
473 auto outerDimsPerm = packOp.getOuterDimsPerm();
474 auto innerDimsPos = packOp.getInnerDimsPos();
475 SmallVector<int64_t> newInnerDimsPosVec;
476 SmallVector<int64_t> newOuterDimsPermVec =
477 llvm::to_vector(transposePermutation);
478
479 if (!outerDimsPerm.empty())
480 applyPermutationToVector(newOuterDimsPermVec, outerDimsPerm);
481
482 // Can't use applyPermutationToVector for newInnerDimsPosVec since input and
483 // permutation rank won't necessarily be equal in all cases.
484 for (auto dim : innerDimsPos)
485 newInnerDimsPosVec.push_back(transposePermutation[dim]);
486
487 Value output = packOp.createDestinationTensor(
488 rewriter, packOp.getLoc(), linalgOp->getOperand(0),
489 packOp.getMixedTiles(), newInnerDimsPosVec, newOuterDimsPermVec);
490
491 rewriter.replaceOpWithNewOp<PackOp>(
492 packOp, linalgOp->getOperand(0), output, newInnerDimsPosVec,
493 packOp.getMixedTiles(), packOp.getPaddingValue(), newOuterDimsPermVec);
494
495 return success();
496 }
497
498private:
500};
501
502/// Fold 'unpack' -> 'transpose' into 'unpack' since 'unpack' already has
503/// transpose semantics.
504struct FoldProducerUnPackWithConsumerLinalgTransposeOp
505 : public OpInterfaceRewritePattern<linalg::LinalgOp> {
506
507public:
508 FoldProducerUnPackWithConsumerLinalgTransposeOp(
509 MLIRContext *context, ControlFoldIntoPackUnpackFn controlFn)
510 : OpInterfaceRewritePattern<linalg::LinalgOp>(context),
511 controlFn(std::move(controlFn)) {}
512
513 LogicalResult matchAndRewrite(linalg::LinalgOp linalgOp,
514 PatternRewriter &rewriter) const override {
515 auto unPackOp = linalgOp->getOperand(0).getDefiningOp<UnPackOp>();
516
517 if (!unPackOp)
518 return failure();
519
520 // TODO: Support Memref UnPackOp. Temporarily return failure.
521 if (!unPackOp.hasPureTensorSemantics())
522 return failure();
523
524 // User controlled folding function.
525 if (controlFn && !controlFn(&linalgOp->getOpOperand(0)))
526 return failure();
527
528 FailureOr<SmallVector<int64_t>> maybePerm =
529 getTransposeOpPermutation(linalgOp);
530 if (failed(maybePerm))
531 return failure();
532
533 auto outerDimsPerm = unPackOp.getOuterDimsPerm();
534 auto innerDimsPos = unPackOp.getInnerDimsPos();
535 SmallVector<int64_t> newInnerDimsPosVec;
536 SmallVector<int64_t> newOuterDimsPermVec =
537 invertPermutationVector(maybePerm.value());
538
539 // Can't use applyPermutationToVector for newInnerDimsPosVec since input and
540 // permutation rank won't necessarily be equal in all cases.
541 for (auto dim : innerDimsPos)
542 newInnerDimsPosVec.push_back(newOuterDimsPermVec[dim]);
543
544 if (!outerDimsPerm.empty())
545 applyPermutationToVector(newOuterDimsPermVec, outerDimsPerm);
546
547 // Reuse the destination of the transpose op.
548 rewriter.replaceOpWithNewOp<UnPackOp>(
549 linalgOp, unPackOp.getSource(), linalgOp.getDpsInits()[0],
550 newInnerDimsPosVec, unPackOp.getMixedTiles(), newOuterDimsPermVec);
551
552 return success();
553 }
554
555private:
557};
558
559/// Fold 'transpose' -> 'unpack' into 'unpack' since 'unpack' already has
560/// transpose semantics.
561struct FoldConsumerUnPackWithProducerLinalgTransposeOp
562 : public OpRewritePattern<UnPackOp> {
563 using OpRewritePattern<UnPackOp>::OpRewritePattern;
564
565public:
566 FoldConsumerUnPackWithProducerLinalgTransposeOp(
567 MLIRContext *context, ControlFoldIntoPackUnpackFn controlFn)
568 : OpRewritePattern<UnPackOp>(context), controlFn(std::move(controlFn)) {}
569
570 LogicalResult matchAndRewrite(UnPackOp unPackOp,
571 PatternRewriter &rewriter) const override {
572 // TODO: Support Memref UnPackOp. Temporarily return failure.
573 if (!unPackOp.hasPureTensorSemantics())
574 return failure();
575
576 auto linalgOp = unPackOp.getSource().getDefiningOp<linalg::LinalgOp>();
577 if (!linalgOp)
578 return failure();
579
580 // User controlled folding function.
581 if (controlFn && !controlFn(&unPackOp.getSourceMutable()))
582 return failure();
583
584 FailureOr<SmallVector<int64_t>> maybePerm =
585 getTransposeOpPermutation(linalgOp);
586 if (failed(maybePerm))
587 return failure();
588
589 SmallVector<SmallVector<OpFoldResult>> unpackOpResultDims;
590 if (failed(reifyResultShapes(rewriter, unPackOp, unpackOpResultDims))) {
591 return failure();
592 }
593
594 SmallVector<int64_t> inverseTransposePerm =
595 invertPermutationVector(maybePerm.value());
596 auto outerDimsPerm = unPackOp.getOuterDimsPerm();
597 auto innerDimsPos = unPackOp.getInnerDimsPos();
598 int64_t destRank = unPackOp.getSourceRank() - innerDimsPos.size();
599 auto mixedInnerTilesVec = unPackOp.getMixedTiles();
600 SmallVector<int64_t> newOuterDimsPermVec;
601 SmallVector<int64_t> newInnerDimsPosVec;
602 SmallVector<OpFoldResult> newMixedInnerTilesVec;
603 if (!checkAndPermute(inverseTransposePerm, outerDimsPerm,
604 newOuterDimsPermVec, destRank))
605 return rewriter.notifyMatchFailure(
606 unPackOp,
607 "Cannot fold in tensor.unpack if a tile dimension was transposed "
608 "with a non-tile dimension in linalg.transpose.");
609
610 // Process transpose operation for tiled inner dimensions
611 for (unsigned int i = destRank; i < inverseTransposePerm.size(); ++i) {
612 int64_t remappedPosition = inverseTransposePerm[i] - destRank;
613 newMixedInnerTilesVec.push_back(mixedInnerTilesVec[remappedPosition]);
614 newInnerDimsPosVec.push_back(innerDimsPos[remappedPosition]);
615 }
616
617 auto elemType =
618 cast<ShapedType>(unPackOp->getResultTypes()[0]).getElementType();
619 Value output = tensor::EmptyOp::create(rewriter, unPackOp->getLoc(),
620 unpackOpResultDims[0], elemType);
621
622 rewriter.replaceOpWithNewOp<UnPackOp>(
623 unPackOp, linalgOp->getOperand(0), output, newInnerDimsPosVec,
624 newMixedInnerTilesVec, newOuterDimsPermVec);
625
626 return success();
627 }
628
629private:
631};
632
633/// tensor.empty does not define any tensor contents, so an unpadded pack
634/// can be folded away.
635struct FoldEmptyTensorWithPackOp : public OpRewritePattern<PackOp> {
636 using OpRewritePattern<PackOp>::OpRewritePattern;
637
638 LogicalResult matchAndRewrite(PackOp packOp,
639 PatternRewriter &rewriter) const override {
640 // TODO: Support Memref PackOp. Temporarily return failure.
641 if (!packOp.hasPureTensorSemantics())
642 return failure();
643
644 // Check for tensor.empty source.
645 auto emptyOp = packOp.getSource().getDefiningOp<tensor::EmptyOp>();
646 if (!emptyOp)
647 return failure();
648
649 // Check for padding.
650 // Packing with padding cannot be simply removed.
651 if (packOp.getPaddingValue())
652 return rewriter.notifyMatchFailure(packOp, "expects no padding value");
653
654 // Replace the pack directly with its destination.
655 rewriter.replaceOp(packOp, packOp.getDest());
656
657 return success();
658 }
659};
660
661/// tensor.empty does not define any tensor contents, so an unpack
662/// can be folded away.
663struct FoldEmptyTensorWithUnPackOp : public OpRewritePattern<UnPackOp> {
664 using OpRewritePattern<UnPackOp>::OpRewritePattern;
665
666 LogicalResult matchAndRewrite(UnPackOp unPackOp,
667 PatternRewriter &rewriter) const override {
668 // TODO: Support Memref UnPackOp. Temporarily return failure.
669 if (!unPackOp.hasPureTensorSemantics())
670 return failure();
671
672 // Check for tensor.empty source.
673 auto emptyOp = unPackOp.getSource().getDefiningOp<tensor::EmptyOp>();
674 if (!emptyOp)
675 return failure();
676
677 // Replace the unpack directly with its destination.
678 rewriter.replaceOp(unPackOp, unPackOp.getDest());
679
680 return success();
681 }
682};
683
684} // namespace
685
687 RewritePatternSet &patterns, const ControlFoldIntoPackUnpackFn &controlFn) {
688 patterns.insert<FoldUnpackWithExtractSliceOp, FoldPadWithPackOp,
689 FoldProducerPackWithConsumerLinalgTransposeOp,
690 FoldConsumerPackWithProducerLinalgTransposeOp,
691 FoldConsumerUnPackWithProducerLinalgTransposeOp,
692 FoldProducerUnPackWithConsumerLinalgTransposeOp>(
693 patterns.getContext(), controlFn);
694}
695
697 patterns.add<SimplifyPackToExpandShape, SimplifyUnPackToCollapseShape>(
698 patterns.getContext());
699}
700
702 RewritePatternSet &patterns) {
703 patterns.add<FoldEmptyTensorWithPackOp, FoldEmptyTensorWithUnPackOp>(
704 patterns.getContext());
705}
706
707} // namespace linalg
708} // namespace mlir
return success()
ArrayAttr()
RewritePatternSet & insert(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
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.
void populateSimplifyPackAndUnpackPatterns(RewritePatternSet &patterns)
Populates patterns with patterns that simplify tensor.pack and tensor.unpack operations.
void populateFoldPackUnpackIntoTensorEmptyPatterns(RewritePatternSet &patterns)
Populates patterns with patterns that fold operations like linalg.pack and linalg....
void populateFoldIntoPackAndUnpackPatterns(RewritePatternSet &patterns, const ControlFoldIntoPackUnpackFn &controlFn=nullptr)
Populates patterns with patterns that fold operations like tensor.pad and tensor.extract_slice into t...
FailureOr< PackResult > pack(RewriterBase &rewriter, linalg::LinalgOp linalgOp, ArrayRef< OpFoldResult > packedSizes)
Implement packing of a single LinalgOp by packedSizes.
std::function< bool(OpOperand *opOperand)> ControlFoldIntoPackUnpackFn
Function type which is used to control folding operations like tensor.pad and tensor....
SmallVector< int64_t > getPackedOuterShapeWithoutTransposition(OpTy packOrUnPack)
Returns the outer shape in the packed domain before applying the transposition.
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
Include the generated interface declarations.
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
LogicalResult reifyResultShapes(OpBuilder &b, Operation *op, ReifiedRankedShapedTypeDims &reifiedReturnShapes)
Reify the shape of the result of an operation (typically in terms of the shape of its operands).
bool isEqualConstantIntOrValue(OpFoldResult ofr1, OpFoldResult ofr2)
Return true if ofr1 and ofr2 are the same integer constant attribute values or the same SSA value.
std::optional< SmallVector< ReassociationIndices > > getReassociationIndicesForReshape(ShapedType sourceType, ShapedType targetType)
Return the reassociations maps to use to reshape given the source type and the target type when possi...
bool isIdentityPermutation(ArrayRef< int64_t > permutation)
Returns true if permutation is an identity permutation.
void applyPermutationToVector(SmallVector< T, N > &inVec, ArrayRef< int64_t > permutation)
Apply the permutation defined by permutation to inVec.
ArrayAttr getReassociationIndicesAttribute(Builder &b, ArrayRef< ReassociationIndices > reassociation)
Wraps a list of reassociations in an ArrayAttr.
SmallVector< int64_t > invertPermutationVector(ArrayRef< int64_t > permutation)
Helper method to apply to inverse a permutation.