MLIR 23.0.0git
Transforms.cpp
Go to the documentation of this file.
1//===- Transforms.cpp - Linalg transformations as patterns ----------------===//
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//
9// This file implements logic and helpers to expose Linalg transforms as rewrite
10// patterns.
11//
12//===----------------------------------------------------------------------===//
13
28#include "mlir/IR/AffineExpr.h"
31#include "mlir/Support/LLVM.h"
32#include "llvm/ADT/SmallVectorExtras.h"
33#include "llvm/ADT/TypeSwitch.h"
34#include "llvm/Support/Debug.h"
35#include "llvm/Support/DebugLog.h"
36#include "llvm/Support/InterleavedRange.h"
37#include "llvm/Support/raw_ostream.h"
38#include <type_traits>
39#include <utility>
40
41#define DEBUG_TYPE "linalg-transforms"
42
43using namespace mlir;
44using namespace mlir::linalg;
45
46//===----------------------------------------------------------------------===//
47// Transformations exposed as functional-style API calls.
48//===----------------------------------------------------------------------===//
49
50//===----------------------------------------------------------------------===//
51// peelLoop transformation.
52//===----------------------------------------------------------------------===//
53
54/// Try to peel and canonicalize loop `op` and return the new result.
55/// Also applies affine_min/max bounds simplification on the fly where relevant.
56// TODO: Add support for scf.parallel and affine.for loops.
58 Operation *op) {
60 .Case([&](scf::ForOp forOp) {
61 scf::ForOp partialIteration;
62 if (succeeded(scf::peelForLoopAndSimplifyBounds(rewriter, forOp,
63 partialIteration)))
64 return partialIteration->getResults();
65 assert(!partialIteration && "expected that loop was not peeled");
66 return forOp->getResults();
67 })
68 .Default([&](Operation *op) { return op->getResults(); });
69}
70
71/// Peel 'loops' and applies affine_min/max bounds simplification on the fly
72/// where relevant.
75 for (auto loopOp : loops)
76 peelLoop(rewriter, loopOp);
77}
78
79//===----------------------------------------------------------------------===//
80// pack transformation.
81//===----------------------------------------------------------------------===//
82
83#ifndef NDEBUG
84/// Return true if `map` has 0 or 1 result function of AffineDimExpr(dim).
86 bool found = false;
87 for (AffineExpr e : map.getResults()) {
88 if (!e.isFunctionOfDim(dim))
89 continue;
90 if (found)
91 return false;
92 found = true;
93 }
94 return true;
95}
96#endif // NDEBUG
97
99 return llvm::interleaved(ri, ", ", /*Prefix=*/"|", /*Suffix=*/"");
100}
101
102/// Return the index of the first result of `map` that is a function of
103/// AffineDimExpr(dim), std::nullopt otherwise.
104static std::optional<int64_t> getFirstResultIndexFunctionOf(AffineMap map,
105 int64_t dim) {
106 for (int64_t i = 0, e = map.getNumResults(); i < e; ++i) {
107 AffineExpr expr = map.getResult(i);
108 if (!expr.isFunctionOfDim(dim))
109 continue;
110 return i;
111 }
112 return std::nullopt;
113}
114
115/// Perform one step of packing of a LinalgOp's metadata along `dim` into the
116/// `newDim` at `iteratorTypes.size()` by:
117/// 1. Appending `iteratorTypes[newDim]`, equal to `iteratorTypes[dim]`.
118/// 2. Appending a `newDim` to the domain of every indexing map.
119/// 3. For each operand (i.e. for each map in `indexingMaps`), perform packing
120/// by potentially adding a `newDim` result to `map`.
121/// The preserved invariant is that `iteratorTypes.size()` is always equal to
122/// `map.getNumDims()` for every map in `indexingMaps`.
123///
124/// Update `indexingMaps` and `iteratorTypes` inplace as one step of the update.
125/// Return a vector that records the optional packing for each operand.
126/// Return failure if the packed indexing cannot be represented with a LinalgOp.
127///
128/// Further details:
129/// ================
130/// The current implementation of packing (i.e. data tiling) consists of
131/// rewriting a linearized strip-mined form into a higher-dimensional access.
132/// e.g. consider an access `A[I][f(j, k, l)]` and packing by 4; we rewrite
133/// `I` into `4 * i + ii`, where `0 <= ii < 4`.
134/// The access is further rewritten as `A[i][f(j, k, l)][ii]`.
135///
136/// This rewrite into higher dimensional access is not possible for general
137/// AffineExpr in Linalg atm, it is restricted to an AffineDimExpr:
138/// e.g. consider an access `A[I + J][f(j, k, l)]` and packing by 4; we
139/// rewrite `I + J` into `4 * i + ii + J`, where `0 <= ii < 4`.
140/// The rewrite of the access would be a form not representable in Linalg:
141/// `A[i + (ii + J) / 4][f(j, k, l)][(ii + J) % 4]`.
142/// Note however that as `J` and `ii` iterate, the accesses do not have a
143/// particular alignment, so packing does not achieve alignment in this case
144///
145/// In the future, we may want to consider a mixed-form that allows some
146/// alignment in the presence of multiple accesses:
147/// `A[I][f(j, k, l)]` and `B[I + J][f(j, k, l)]`
148/// And would rewrite accesses as:
149/// `A[i][f(j, k, l)][ii]` and `B[4 * i + ii + J][f(j, k, l)]`
150static FailureOr<SmallVector<std::optional<int64_t>>>
153 int64_t dim) {
154 int64_t newDim = iteratorTypes.size();
155 iteratorTypes.push_back(iteratorTypes[dim]);
156
157 SmallVector<std::optional<int64_t>> packedDimPerIndexingMap(
158 indexingMaps.size(), std::nullopt);
160 for (int64_t operandIdx = 0, e = indexingMaps.size(); operandIdx < e;
161 ++operandIdx) {
162 AffineMap map = indexingMaps[operandIdx];
163
164 // Add the `newDim` to map whatever the case.
165 assert(map.getNumDims() == newDim && "num dims invariant violation");
166 map = map.shiftDims(1, newDim);
167
168 // Get the at-most-1 index of the result that is a function of `dim`.
169 // If we can find one, we insert `AffineDimExpr(newDim)` to the map, which
170 // logically chunks dimension `dim` into `K * dim + newDim`, where the
171 // packing factor `K` is specified separately.
172 assert(hasAtMostOneResultFunctionOfDim(map, dim) &&
173 "num results invariant violation");
174 auto maybeOperandDimensionToPack = getFirstResultIndexFunctionOf(map, dim);
175 if (!maybeOperandDimensionToPack.has_value()) {
176 newMaps.push_back(map);
177 continue;
178 }
179
180 // We can only pack AffineDimExpr atm.
181 if (!isa<AffineDimExpr>(map.getResult(maybeOperandDimensionToPack.value())))
182 return failure();
183
184 // Add `newDim` to the results of the map.
185 map = map.insertResult(Builder(map.getContext()).getAffineDimExpr(newDim),
186 map.getNumResults());
187 newMaps.push_back(map);
188
189 // Record the that `operandIdx` is packed.
190 packedDimPerIndexingMap[operandIdx] = maybeOperandDimensionToPack;
191 }
192 indexingMaps = newMaps;
193
194 return packedDimPerIndexingMap;
195}
196
197namespace {
198
199/// Helper struct to encode packing along one dimension of a LinalgOp.
200struct PackedOperandsDim {
201 OpFoldResult packedSize;
202 SmallVector<std::optional<int64_t>> packedDimForEachOperand;
203};
204
205/// Helper struct to encode packing along all dimensions of a LinalgOp.
206struct PackedOperandsDimList {
207 void pushBack(PackedOperandsDim &&packedOperandsDims) {
208 spec.emplace_back(packedOperandsDims);
209 }
210 /// Return all the dims that have been packed for operand @ `operandPos`.
211 SmallVector<int64_t> extractPackedDimsForOperand(int64_t operandPos);
212 /// Return all the pack sizes by which an operand @ `operandPos` is packed.
213 SmallVector<OpFoldResult> extractPackSizesForOperand(int64_t operandPos);
214
215private:
216 SmallVector<PackedOperandsDim> spec;
217};
218
219} // namespace
220
221FailureOr<LowerPackResult> linalg::lowerPack(RewriterBase &rewriter,
222 linalg::PackOp packOp,
223 bool lowerPadLikeWithInsertSlice) {
224 // TODO: Support Memref PackOp. Temporarily return failure.
225 if (!packOp.hasPureTensorSemantics())
226 return failure();
227
228 auto packedTensorType =
229 cast<RankedTensorType>(packOp->getResultTypes().front());
230
231 Location loc = packOp->getLoc();
232 OpBuilder::InsertionGuard g(rewriter);
233 rewriter.setInsertionPoint(packOp);
234
235 // 2. Compute the permutation vector to shuffle packed shape into the shape
236 // before any outer or inner permutations have been applied.
237 PackingMetadata packingMetadata;
238 SmallVector<int64_t> packedToStripMinedShapePerm =
239 getPackInverseDestPerm(packOp, packingMetadata);
240
241 // 3. Compute the stripMinedShape: this is the packed shape before any outer
242 // or inner permutations have been applied.
243 SmallVector<int64_t> stripMinedShape(packedTensorType.getShape());
244 applyPermutationToVector(stripMinedShape, packedToStripMinedShapePerm);
245
246 // Also compute the mixed (static+dynamic) strip-mined sizes for the
247 // expand_shape output. This is needed to support dynamic inner tile sizes,
248 // since the shapes cannot be inferred automatically when multiple dynamic
249 // dims appear in a single reassociation group during ExpandShapeOp
250 // construction.
251 SmallVector<OpFoldResult> stripMinedMixedSizes =
252 tensor::getMixedSizes(rewriter, loc, packOp.getDest());
253 applyPermutationToVector(stripMinedMixedSizes, packedToStripMinedShapePerm);
254
255 // 4. Pad the source of packOp to a shape we can expand into stripMinedShape.
256 SmallVector<OpFoldResult> lows(packOp.getSourceRank(),
257 rewriter.getIndexAttr(0));
258 SmallVector<OpFoldResult> highs(packOp.getSourceRank(),
259 rewriter.getIndexAttr(0));
260 for (auto [pos, innerSize] :
261 llvm::zip_equal(packOp.getInnerDimsPos(), packOp.getMixedTiles())) {
262 int outerPos =
263 packedToStripMinedShapePerm[packingMetadata.outerPositions[pos]];
264 OpFoldResult origSize =
265 tensor::getMixedSize(rewriter, loc, packOp.getSource(), pos);
266 OpFoldResult outerSize =
267 tensor::getMixedSize(rewriter, loc, packOp.getDest(), outerPos);
268 AffineExpr s0, d0, d1;
269 bindDims(rewriter.getContext(), d0, d1);
270 bindSymbols(rewriter.getContext(), s0);
271 auto map = AffineMap::get(/*dimCount=*/2, /*symbolCount=*/1, d0 * s0 - d1);
273 rewriter, loc, map, {outerSize, origSize, innerSize});
274 }
275 RankedTensorType collapsed = tensor::CollapseShapeOp::inferCollapsedType(
276 RankedTensorType::Builder(packedTensorType).setShape(stripMinedShape),
277 packingMetadata.reassociations);
278 Value paddingValue = packOp.getPaddingValue();
279 if (!paddingValue) {
280 paddingValue = arith::ConstantOp::create(
281 rewriter, loc, rewriter.getZeroAttr(getElementTypeOrSelf(collapsed)));
282 }
283 auto padOp =
284 tensor::PadOp::create(rewriter, loc, collapsed, packOp.getSource(), lows,
285 highs, paddingValue, /*nofold=*/false);
286
287 LDBG() << "insertPositions: "
288 << llvm::interleaved(packingMetadata.insertPositions);
289 LDBG() << "outerPositions: "
290 << llvm::interleaved(packingMetadata.outerPositions);
291 LDBG() << "packedShape: " << llvm::interleaved(packedTensorType.getShape());
292 LDBG() << "packedToStripMinedShapePerm: "
293 << llvm::interleaved(packedToStripMinedShapePerm);
294 LDBG() << "reassociations: "
295 << llvm::interleaved(llvm::map_range(packingMetadata.reassociations,
297 LDBG() << "stripMinedShape: " << llvm::interleaved(stripMinedShape);
298 LDBG() << "collapsed type: " << collapsed;
299
300 if (lowerPadLikeWithInsertSlice && packOp.isLikePad()) {
301 // Pack ops which operate as simple pads may not produce legal
302 // tensor.insert_slice operations when the packed type does not rank reduce
303 // to the padded type.
304 SliceVerificationResult rankReduces =
305 isRankReducedType(packedTensorType, padOp.getResultType());
306
307 if (rankReduces == SliceVerificationResult::Success) {
308 // This pack is just a plain pad.
309 // Just insert the pad in the higher ranked tensor.
310 // Offsets.
311 SmallVector<OpFoldResult> zeros(packOp.getDestRank(),
312 rewriter.getIndexAttr(0));
313 // Strides.
314 SmallVector<OpFoldResult> ones(packOp.getDestRank(),
315 rewriter.getIndexAttr(1));
317 tensor::getMixedSizes(rewriter, loc, packOp.getDest());
318
319 auto insertSliceOp = tensor::InsertSliceOp::create(
320 rewriter, loc, /*source=*/padOp, /*dest=*/packOp.getDest(),
321 /*offsets=*/zeros, sizes, /*strides=*/ones);
322
323 LDBG() << "insert_slice op: " << insertSliceOp;
324
325 rewriter.replaceOp(packOp, insertSliceOp->getResults());
326
327 return LowerPackResult{padOp, /*reshapeOp=*/nullptr,
328 /*transposeOp=*/nullptr};
329 }
330 }
331
332 // 5. Expand from the padded result to the stripMinedShape.
333 auto expandShapeResultType =
334 RankedTensorType::Builder(packedTensorType).setShape(stripMinedShape);
335 auto reshapeOp = tensor::ExpandShapeOp::create(
336 rewriter, loc, expandShapeResultType, padOp.getResult(),
337 packingMetadata.reassociations, stripMinedMixedSizes);
338
339 // 6. Transpose stripMinedShape to packedShape.
340 SmallVector<int64_t> transpPerm =
341 invertPermutationVector(packedToStripMinedShapePerm);
342 auto transposeOp = linalg::TransposeOp::create(
343 rewriter, loc, reshapeOp.getResult(), packOp.getDest(), transpPerm);
344
345 LDBG() << "reshape op: " << reshapeOp;
346 LDBG() << "transpPerm: " << llvm::interleaved(transpPerm);
347 LDBG() << "transpose op: " << transposeOp;
348
349 // 7. Replace packOp by transposeOp.
350 rewriter.replaceOp(packOp, transposeOp->getResults());
351
352 return LowerPackResult{padOp, reshapeOp, transposeOp};
353}
354
355FailureOr<LowerUnPackOpResult>
356linalg::lowerUnPack(RewriterBase &rewriter, linalg::UnPackOp unPackOp,
357 bool lowerUnpadLikeWithExtractSlice) {
358 // TODO: Support Memref UnPackOp. Temporarily return failure.
359 if (!unPackOp.hasPureTensorSemantics())
360 return failure();
361
362 Location loc = unPackOp->getLoc();
363 OpBuilder::InsertionGuard g(rewriter);
364 rewriter.setInsertionPoint(unPackOp);
365
366 auto packedTensorType = cast<RankedTensorType>(unPackOp.getSourceType());
367 int64_t packedRank = packedTensorType.getRank();
368
369 OpFoldResult zero = rewriter.getIndexAttr(0), one = rewriter.getIndexAttr(1);
370 auto destTensorType = cast<RankedTensorType>(unPackOp.getDest().getType());
371 if (lowerUnpadLikeWithExtractSlice && unPackOp.isLikeUnPad()) {
372 // This unpack is just a plain unpad.
373 // Just extract the slice from the higher ranked tensor.
374 ArrayRef<int64_t> destShape = destTensorType.getShape();
375 // The inner dimensions stay the same as the destination tensor, but the
376 // outer ones are additional 1s.
377 SmallVector<OpFoldResult> sizes(packedRank - destShape.size(), one);
378 sizes.append(tensor::getMixedSizes(rewriter, loc, unPackOp.getDest()));
379
380 auto extractSliceOp = tensor::ExtractSliceOp::create(
381 rewriter, loc, destTensorType, unPackOp.getSource(),
382 SmallVector<OpFoldResult>(packedRank, zero), sizes,
383 SmallVector<OpFoldResult>(packedRank, one));
384
385 rewriter.replaceOp(unPackOp, extractSliceOp->getResults());
386
387 return LowerUnPackOpResult{/*emptyOp=*/nullptr, /*transposeOp=*/nullptr,
388 /*reshapeOp=*/nullptr, extractSliceOp,
389 /*copyOp=*/nullptr};
390 }
391
392 // 1. Compute the permutation vector to shuffle packed shape into the shape
393 // before any outer or inner permutations have been applied.
394 PackingMetadata packingMetadata;
395 SmallVector<int64_t> packedToStripMinedShapePerm =
396 getUnPackInverseSrcPerm(unPackOp, packingMetadata);
397
398 // 2. Compute the stripMinedShape: this is the packed shape without outer and
399 // inner permutations.
400 SmallVector<int64_t> stripMinedShape(packedTensorType.getShape());
401 applyPermutationToVector(stripMinedShape, packedToStripMinedShapePerm);
402
403 // 3. Transpose packedShape to stripMinedShape.
404 RankedTensorType stripMinedTensorType =
405 RankedTensorType::Builder(packedTensorType).setShape(stripMinedShape);
406 RankedTensorType collapsedType = tensor::CollapseShapeOp::inferCollapsedType(
407 stripMinedTensorType, packingMetadata.reassociations);
408
409 // Get dynamic dims from input tensor based on packedToStripMinedShapePerm
410 // permutation.
412 tensor::getMixedSizes(rewriter, loc, unPackOp.getSource());
413 applyPermutationToVector(dims, packedToStripMinedShapePerm);
414 auto emptyOp = tensor::EmptyOp::create(rewriter, loc, dims,
415 stripMinedTensorType.getElementType());
416 auto transposeOp =
417 linalg::TransposeOp::create(rewriter, loc, unPackOp.getSource(), emptyOp,
418 packedToStripMinedShapePerm);
419
420 LDBG() << "insertPositions: "
421 << llvm::interleaved(packingMetadata.insertPositions);
422 LDBG() << "packedShape: " << llvm::interleaved(packedTensorType.getShape());
423 LDBG() << "packedToStripMinedShapePerm: "
424 << llvm::interleaved(packedToStripMinedShapePerm);
425 LDBG() << "reassociations: "
426 << llvm::interleaved(llvm::map_range(packingMetadata.reassociations,
428 LDBG() << "stripMinedShape: " << llvm::interleaved(stripMinedShape);
429 LDBG() << "collapsed type: " << collapsedType;
430
431 // 4. Collapse from the stripMinedShape to the padded result.
432 auto reshapeOp = tensor::CollapseShapeOp::create(
433 rewriter, loc, collapsedType, transposeOp->getResult(0),
434 packingMetadata.reassociations);
435
436 // 5. ExtractSlice.
437 int64_t destRank = destTensorType.getRank();
438 auto extractSliceOp = tensor::ExtractSliceOp::create(
439 rewriter, loc, destTensorType, reshapeOp->getResult(0),
440 SmallVector<OpFoldResult>(destRank, zero),
441 tensor::getMixedSizes(rewriter, loc, unPackOp.getDest()),
442 SmallVector<OpFoldResult>(destRank, one));
443
444 // 6. Inject a copy to preserve DPS.
445 auto copyOp = linalg::CopyOp::create(
446 rewriter, loc, extractSliceOp->getResult(0), unPackOp.getDest());
447
448 // 7. Replace unPackOp by copyOp.
449 rewriter.replaceOp(unPackOp, copyOp->getResults());
450
451 return LowerUnPackOpResult{emptyOp, transposeOp, reshapeOp, extractSliceOp,
452 copyOp};
453}
454
456PackedOperandsDimList::extractPackedDimsForOperand(int64_t operandPos) {
458 for (auto &i : spec) {
459 if (!i.packedDimForEachOperand[operandPos].has_value())
460 continue;
461 res.push_back(i.packedDimForEachOperand[operandPos].value());
462 }
463 return res;
464}
465
466SmallVector<OpFoldResult>
467PackedOperandsDimList::extractPackSizesForOperand(int64_t operandPos) {
468 SmallVector<OpFoldResult> res;
469 for (auto &i : spec) {
470 if (!i.packedDimForEachOperand[operandPos].has_value())
471 continue;
472 res.push_back(i.packedSize);
473 }
474 return res;
475}
476
477/// Implement packing of a single LinalgOp by performing packing by
478/// `packedSizes`. There must be one packedSizes entry per `linalgOp` iterator.
479/// Return the packed Linalg op on success, failure otherwise.
480FailureOr<PackResult> linalg::pack(RewriterBase &rewriter,
481 linalg::LinalgOp linalgOp,
482 ArrayRef<OpFoldResult> packedSizes) {
483 if (packedSizes.size() != linalgOp.getNumLoops()) {
484 return rewriter.notifyMatchFailure(linalgOp,
485 "incorrect number of pack sizes");
486 }
487 if (!linalgOp.hasPureTensorSemantics()) {
488 return rewriter.notifyMatchFailure(
489 linalgOp, "expects LinalgOp with pure tensor semantics");
490 }
491
492 Location loc = linalgOp->getLoc();
493 SmallVector<AffineMap> indexingMaps = linalgOp.getIndexingMapsArray();
495 linalgOp.getIteratorTypesArray();
496 LDBG() << "Start packing: " << linalgOp;
497 LDBG() << "maps: " << llvm::interleaved(indexingMaps);
498 LDBG() << "iterators: " << llvm::interleaved(iteratorTypes);
499
502 // Step 1. Pack each dim of the LinalgOp metadata by packedSizes[i].
503 PackedOperandsDimList listOfPackedOperandsDim;
504 for (int64_t i = 0, e = packedSizes.size(); i < e; ++i) {
505 std::optional<int64_t> maybeConstant = getConstantIntValue(packedSizes[i]);
506 // Skip tile sizes explicitly set to 0.
507 if (maybeConstant.has_value() && maybeConstant.value() == 0)
508 continue;
509
510 PackedOperandsDim packedOperandsDims;
511 packedOperandsDims.packedSize = packedSizes[i];
512 FailureOr<SmallVector<std::optional<int64_t>>>
513 maybePackedDimForEachOperand =
514 packLinalgMetadataOnce(indexingMaps, iteratorTypes, i);
515 if (failed(maybePackedDimForEachOperand))
516 return failure();
517 packedOperandsDims.packedDimForEachOperand = *maybePackedDimForEachOperand;
518
519 LDBG() << "++++ After pack size #" << i << ": " << packedSizes[i];
520 LDBG() << "maps: " << llvm::interleaved(indexingMaps);
521 LDBG() << "iterators: " << llvm::interleaved(iteratorTypes);
522 LDBG() << "packedDimForEachOperand: "
523 << llvm::interleaved(packedOperandsDims.packedDimForEachOperand);
524
525 listOfPackedOperandsDim.pushBack(std::move(packedOperandsDims));
526 }
527
528 // Step 2. Propagate packing to all LinalgOp operands.
529 SmallVector<Value> inputsAndInits, results;
530 SmallVector<OpOperand *> initOperands =
531 llvm::to_vector(llvm::make_pointer_range(linalgOp.getDpsInitsMutable()));
532 SmallVector<OpOperand *> inputOperands = linalgOp.getDpsInputOperands();
533 for (const auto &operandsList : {inputOperands, initOperands}) {
534 for (OpOperand *opOperand : operandsList) {
535 int64_t pos = opOperand->getOperandNumber();
536 Value operand = opOperand->get();
537 SmallVector<int64_t> innerPos =
538 listOfPackedOperandsDim.extractPackedDimsForOperand(pos);
539 SmallVector<OpFoldResult> innerPackSizes =
540 listOfPackedOperandsDim.extractPackSizesForOperand(pos);
541 LDBG() << "operand: " << operand;
542 LDBG() << "innerPos: " << llvm::interleaved(innerPos);
543 LDBG() << "innerPackSizes: " << llvm::interleaved(innerPackSizes);
544 if (innerPackSizes.empty()) {
545 inputsAndInits.push_back(operand);
546 continue;
547 }
548 Value dest = linalg::PackOp::createDestinationTensor(
549 rewriter, loc, operand, innerPackSizes, innerPos,
550 /*outerDimsPerm=*/{});
551 ShapedType operandType = cast<ShapedType>(operand.getType());
552 bool areConstantTiles =
553 llvm::all_of(innerPackSizes, [](OpFoldResult tile) {
554 return getConstantIntValue(tile).has_value();
555 });
556 if (areConstantTiles && operandType.hasStaticShape() &&
557 !linalg::PackOp::requirePaddingValue(
558 operandType.getShape(), innerPos,
559 cast<ShapedType>(dest.getType()).getShape(), {},
560 innerPackSizes)) {
561 packOps.push_back(linalg::PackOp::create(rewriter, loc, operand, dest,
562 innerPos, innerPackSizes));
563 } else {
564 // TODO: value of the padding attribute should be determined by
565 // consumers.
566 auto zeroAttr =
567 rewriter.getZeroAttr(getElementTypeOrSelf(dest.getType()));
568 Value zero = arith::ConstantOp::create(rewriter, loc, zeroAttr);
569 packOps.push_back(linalg::PackOp::create(
570 rewriter, loc, operand, dest, innerPos, innerPackSizes, zero));
571 }
572 inputsAndInits.push_back(packOps.back().getResult());
573 }
574 }
575
576 // Step 3. Build the packed op, use the type of `inits` as result types.
577 ValueRange inputs =
578 ValueRange{inputsAndInits}.take_front(linalgOp.getNumDpsInputs());
579 ValueRange inits =
580 ValueRange{inputsAndInits}.take_back(linalgOp.getNumDpsInits());
581 auto packedLinalgOp =
582 linalg::GenericOp::create(rewriter, linalgOp.getLoc(), inits.getTypes(),
583 inputs, inits, indexingMaps, iteratorTypes);
584 packedLinalgOp.getRegion().takeBody(linalgOp->getRegion(0));
585
586 // Step 4. Propagate packing to all the op results.
587 for (OpResult result : packedLinalgOp->getResults()) {
588 int64_t resultNum = result.getResultNumber();
589 linalg::PackOp maybePackedInit =
590 inits[resultNum].getDefiningOp<linalg::PackOp>();
591 if (!maybePackedInit) {
592 results.push_back(result);
593 continue;
594 }
595 // Build the symmetrical UnPackOp to the existing PackOp.
596 unPackOps.push_back(linalg::UnPackOp::create(
597 rewriter, packedLinalgOp->getLoc(), result, maybePackedInit.getSource(),
598 maybePackedInit.getInnerDimsPos(), maybePackedInit.getMixedTiles()));
599 results.push_back(unPackOps.back().getResult());
600 }
601
602 // Step 5. Replace `linalgOp`.
603 rewriter.replaceOp(linalgOp, results);
604
605 // Return packedLinalgOp.
606 return PackResult{packOps,
607 cast<linalg::LinalgOp>(packedLinalgOp.getOperation()),
608 unPackOps};
609}
610
611//===----------------------------------------------------------------------===//
612// packTranspose transformation.
613//===----------------------------------------------------------------------===//
614
615/// Return a copy of `tensorType` after permutation by `permutationVector`.
616// Note: Should be a new method in of MemRef/RankedTensor/VectorType::Builder
617// but this would introduce a dependence on Dialect in IR.
618// TODO: Restructure.
619static RankedTensorType permuteShape(RankedTensorType tensorType,
620 ArrayRef<int64_t> permutationVector) {
621 SmallVector<int64_t> shape(tensorType.getShape());
622 applyPermutationToVector(shape, permutationVector);
623 return RankedTensorType::Builder(tensorType).setShape(shape);
624}
625
626/// Return a new GenericOp obtained by transposing opOperand by the permutation
627/// vector:
628/// - the corresponding indexing map is transposed by `permutation`
629/// - the corresponding operand value is replaced by `transposedValue`
630/// `linalgOp` is replaced by the return op in the process.
631/// Asserts that `transposedValue` is of the proper transposed ShapedType.
633 RewriterBase &rewriter, LinalgOp linalgOp, OpOperand &opOperand,
634 ArrayRef<int64_t> permutation, Value transposedValue) {
635 // Sanity check the operand.
636 assert(linalgOp == opOperand.getOwner() && "linalg op must own the operand");
637
638 // Sanity check of the expected transposed tensor type.
639 auto tensorType = permuteShape(
640 cast<RankedTensorType>(opOperand.get().getType()), permutation);
641 (void)tensorType;
642 assert(tensorType == transposedValue.getType() &&
643 "expected tensor type mismatch");
644
645 // Compute the transposed indexing map.
646 // Sigh unsigned pollution.
647 SmallVector<unsigned> tmpTransposition =
648 llvm::map_to_vector(permutation, [](int64_t i) -> unsigned { return i; });
649 AffineMap permutationMap =
650 AffineMap::getPermutationMap(tmpTransposition, rewriter.getContext());
651 AffineMap transposedMap =
652 permutationMap.compose(linalgOp.getMatchingIndexingMap(&opOperand));
653
654 // Set the transposed indexing map in the proper position.
655 SmallVector<AffineMap> indexingMaps = linalgOp.getIndexingMapsArray();
656 indexingMaps[linalgOp.getIndexingMapIndex(&opOperand)] = transposedMap;
657 // Set the transposedValue in the proper operand position.
658 SmallVector<Value> operands = linalgOp->getOperands();
659 operands[opOperand.getOperandNumber()] = transposedValue;
660
661 ValueRange operandsRef(operands);
662 auto transposedGenericOp = linalg::GenericOp::create(
663 rewriter,
664 /*location=*/linalgOp->getLoc(),
665 /*resultTensorTypes=*/
666 operandsRef.drop_front(linalgOp.getNumDpsInputs()).getTypes(),
667 /*inputs=*/operandsRef.take_front(linalgOp.getNumDpsInputs()),
668 /*outputs=*/operandsRef.drop_front(linalgOp.getNumDpsInputs()),
669 /*indexingMaps=*/indexingMaps,
670 /*iteratorTypes=*/linalgOp.getIteratorTypesArray());
671 transposedGenericOp.getRegion().takeBody(linalgOp->getRegion(0));
672 rewriter.replaceOp(linalgOp, transposedGenericOp->getResults());
673
674 return cast<linalg::LinalgOp>(transposedGenericOp.getOperation());
675}
676
677FailureOr<PackTransposeResult>
678linalg::packTranspose(RewriterBase &rewriter, linalg::PackOp packOp,
679 linalg::LinalgOp linalgOp, linalg::UnPackOp maybeUnPackOp,
680 ArrayRef<int64_t> outerPerm,
681 ArrayRef<int64_t> innerPerm) {
682 Location loc = linalgOp.getLoc();
683
684 // Step 1. Transpose packOp.
685 rewriter.setInsertionPoint(packOp);
686 linalg::PackOp transposedPackOp =
687 packOp.createTransposedClone(rewriter, loc, innerPerm, outerPerm);
688
689 if (packOp.hasPureBufferSemantics() || !packOp.getResult().hasOneUse())
690 return rewriter.notifyMatchFailure(linalgOp, "expect single pack use");
691
692 OpOperand &packUse = *packOp->getUses().begin();
693 if (packUse.getOwner() != linalgOp) {
694 return rewriter.notifyMatchFailure(
695 linalgOp, "not a single use by the LinalgOp target");
696 }
697 if (maybeUnPackOp &&
698 (!linalgOp.isDpsInit(&packUse) ||
699 maybeUnPackOp.getSource() != linalgOp.getTiedOpResult(&packUse))) {
700 return rewriter.notifyMatchFailure(linalgOp,
701 "not produced by the LinalgOp target");
702 }
703
704 // Step 2. Transpose linalgOp.
705 // transposedPackOp.getOuterDimsPerm() may be empty, in which case it is the
706 // identity. Don't rely on it.
707 int64_t numLeadingDims = packOp.getSourceRank();
708 int64_t numTrailingDims = packOp.getInnerDimsPos().size();
709 // Step 2.a. Compute the permutation on the whole operand.
710 // Leading part just reuse the outerPerm.
711 SmallVector<int64_t> permutation(outerPerm);
712 if (permutation.empty())
713 llvm::append_range(permutation, llvm::seq<int64_t>(0, numLeadingDims));
714 // Trailing part needs to reindex positions by `numLeadingDims`.
715 if (innerPerm.empty()) {
716 llvm::append_range(
717 permutation,
718 llvm::seq<int64_t>(numLeadingDims, numLeadingDims + numTrailingDims));
719 } else {
720 llvm::append_range(permutation,
721 llvm::map_range(innerPerm, [&](int64_t pos) {
722 return numLeadingDims + pos;
723 }));
724 }
725 if (!isPermutationVector(permutation))
726 return rewriter.notifyMatchFailure(linalgOp, "invalid permutation");
727
728 // Step 2.b. Save the transposedPackUse operand number in case we need to
729 // get the tied OpResult after `linalgOp` has been replaced.
730 int64_t packUseOperandNumber = packUse.getOperandNumber();
731 // Step 2.c. Actually perform the transposition.
732 rewriter.setInsertionPoint(linalgOp);
733 linalg::LinalgOp transposedLinalgOp = transposeOneLinalgOperandAndReplace(
734 rewriter, linalgOp, packUse, permutation, transposedPackOp.getResult());
735
736 // Step 3. Maybe transpose unPackOp.
737 linalg::UnPackOp transposedUnPackOp;
738 if (maybeUnPackOp) {
739 OpOperand &opOperand =
740 transposedLinalgOp->getOpOperand(packUseOperandNumber);
741 OpResult transposedResult = transposedLinalgOp.getTiedOpResult(&opOperand);
742 rewriter.setInsertionPoint(maybeUnPackOp);
743 transposedUnPackOp = maybeUnPackOp.createTransposedClone(
744 rewriter, loc, transposedResult, innerPerm, outerPerm);
745
746 rewriter.replaceOp(maybeUnPackOp, transposedUnPackOp->getResults());
747 }
748
749 // Step 4. Finally, replace packOp now that we don't need it anymore.
750 if (packOp.hasPureTensorSemantics())
751 rewriter.replaceOp(packOp, transposedPackOp->getResults());
752 else
753 rewriter.eraseOp(packOp);
754
755 return PackTransposeResult{transposedPackOp, transposedLinalgOp,
756 transposedUnPackOp};
757}
758
759//===----------------------------------------------------------------------===//
760// packMatmulGreedily transformation.
761//===----------------------------------------------------------------------===//
762
763/// Pack a LinalgOp by greedily inferring matmul dimensions (m, n, k) where m
764/// and n are proper parallel dimensions and k is a proper reduction
765/// dimension. Packing occurs by rewriting the op as a linalg.generic and
766/// calling linalg::pack by `mnkPackedSizes`. The order of the packed
767/// dimensions is customizable: the `mnkOrder` is a permutation of {0, 1, 2}
768/// to reorder {m, n, k} into one of the 8 possible forms. The outer
769/// dimensions of the operands are not permuted at this time, this is left for
770/// future work.
771FailureOr<PackResult>
772linalg::packMatmulGreedily(RewriterBase &rewriter, LinalgOp linalgOp,
773 ArrayRef<OpFoldResult> mnkPackedSizes,
774 ArrayRef<int64_t> mnkPaddedSizesNextMultipleOf,
775 ArrayRef<int64_t> mnkOrder) {
776 assert(mnkPackedSizes.size() == 3 && "unexpected num of packing sizes");
777 assert((mnkPaddedSizesNextMultipleOf.empty() ||
778 mnkPaddedSizesNextMultipleOf.size() == 3) &&
779 "num of packing sizes next multiple should be empty or of size 3");
780 assert(mnkOrder.size() == 3 && "unexpected mnkOrder size");
781 assert(isPermutationVector(mnkOrder) && "expected a permutation");
782
783 int64_t numLoops = linalgOp.getNumLoops();
784 if (numLoops <= 2) {
785 LDBG() << "need 3+ loops to find a matmul to pack, got " << numLoops
786 << " in: " << linalgOp;
787 return rewriter.notifyMatchFailure(
788 linalgOp, "need 3+ loops to find a matmul to pack");
789 }
790
791 // Locally adjust the desired iterator position of mnk and packing sizes.
792 int64_t numPackedDims = mnkPackedSizes.size();
793 SmallVector<int64_t> mmnnkkPos(numPackedDims);
794 for (int64_t i = 0, e = numPackedDims; i < e; ++i)
795 mmnnkkPos[i] = numLoops - numPackedDims + mnkOrder[i];
796 SmallVector<OpFoldResult> packedSizes(numPackedDims);
797 for (int64_t i = 0, e = numPackedDims; i < e; ++i)
798 packedSizes[mnkOrder[i]] = mnkPackedSizes[i];
799 SmallVector<int64_t> paddedSizesNextMultipleOf(numPackedDims);
800 for (int64_t i = 0, e = numPackedDims; i < e; ++i) {
801 paddedSizesNextMultipleOf[mnkOrder[i]] =
802 mnkPaddedSizesNextMultipleOf.empty() ? 0
803 : mnkPaddedSizesNextMultipleOf[i];
804 }
805
806 // 1. Infer dims that are important for matmul.
807 FailureOr<ContractionDimensions> maybeDimensions =
808 inferContractionDims(linalgOp);
809 if (failed(maybeDimensions)) {
810 LDBG() << "couldn't infer matmul iterators in: " << linalgOp;
811 return rewriter.notifyMatchFailure(linalgOp,
812 "couldn't infer matmul iterators");
813 }
814
815 // 2. Normalize linalgOp to an kmn-matmul-like with [red, par, par] most
816 // minor iterators. In cases with multiple options for m, n, k bias towards
817 // the most minor embedding.
818 // If we wanted a different normalization order, this is where it would have
819 // to plug a heuristic.
820 int64_t mPos = maybeDimensions->m.back(), nPos = maybeDimensions->n.back(),
821 kPos = maybeDimensions->k.back();
822 LDBG() << "Start packing generic op greedily with (m@" << mPos << ", n@"
823 << nPos << ", k@" << kPos << "): " << linalgOp;
824
825 // 2.a. Rewrite as a generic.
826 auto genericOp = dyn_cast<GenericOp>(linalgOp.getOperation());
827 if (!genericOp) {
828 FailureOr<GenericOp> generalizeResult =
829 generalizeNamedOp(rewriter, linalgOp);
830 assert(succeeded(generalizeResult) && "unexpected failure generalizing op");
831 genericOp = *generalizeResult;
832 }
833
834 // 2.b. Interchange to move the dimensions (k, m, n) as most-minor
835 // iterators. Note that this only normalized the iteration order and does
836 // not change the indexings of any operand.
837 SmallVector<int64_t> permutation =
838 computePermutationVector(numLoops, {mPos, nPos, kPos}, mmnnkkPos);
839 LDBG() << "perm: " << llvm::interleaved(permutation);
840 // Sign .. unsigned pollution.
841 SmallVector<unsigned> unsignedPerm(permutation.begin(), permutation.end());
842 FailureOr<GenericOp> interchangeResult =
843 interchangeGenericOp(rewriter, genericOp, unsignedPerm);
844 assert(succeeded(interchangeResult) && "unexpected failure interchanging op");
845 genericOp = *interchangeResult;
846 LDBG() << "Generalized Op to pack: " << genericOp;
847
848 // At this point, the op iterators are normalized to {leading, k, m, n}.
849 // The layouts induced by packing will always be:
850 // - LHS{leading_lhs, kk, mm}
851 // - RHS{leading_rhs, kk, nn}
852 // - RES{leading_res, mm, nn}
853 // If we wanted to change the packed order, we would reorder (k, m, n) to
854 // something else above.
855 //
856 // Additional permutations of the outer dims of the operands (i.e.
857 // leading_lhs, leading_rhs and leading_res) could follow by computing the
858 // desired outerPerm for each operand.
859 // This is left for future work.
860
861 // TODO: this creates too much IR, go use reifyResultShapes.
862 SmallVector<Range, 4> loopRanges =
863 cast<LinalgOp>(genericOp.getOperation())
864 .createLoopRanges(rewriter, genericOp.getLoc());
865
866 // Add leading zeros to match numLoops, we only pack the last 3 dimensions
867 // post interchange.
868 LDBG() << "paddedSizesNextMultipleOf: "
869 << llvm::interleaved(paddedSizesNextMultipleOf);
870 LDBG() << "loopRanges: "
871 << llvm::interleaved(
872 llvm::map_range(loopRanges, [](Range r) { return r.size; }));
873 SmallVector<OpFoldResult> adjustedPackedSizes(numLoops - packedSizes.size(),
874 rewriter.getIndexAttr(0));
875 for (int64_t i = 0, e = numPackedDims; i < e; ++i) {
876 if (paddedSizesNextMultipleOf[i] == 0) {
877 adjustedPackedSizes.push_back(packedSizes[i]);
878 continue;
879 }
880 AffineExpr d0, s0;
881 bindDims(rewriter.getContext(), d0);
882 bindSymbols(rewriter.getContext(), s0);
883 adjustedPackedSizes.push_back(affine::makeComposedFoldedAffineApply(
884 rewriter, genericOp->getLoc(), d0.ceilDiv(s0) * s0,
885 {loopRanges[adjustedPackedSizes.size()].size,
886 rewriter.getIndexAttr(paddedSizesNextMultipleOf[i])}));
887 }
888 LDBG() << "adjustedPackedSizes: " << llvm::interleaved(adjustedPackedSizes);
889
890 // TODO: If we wanted to give the genericOp a name after packing, after
891 // calling `pack` would be a good time. One would still need to check that
892 // `containsMostMinorMatmul(packingRes->packedLinalgOp)` is true, since we
893 // also allow degenerate matmul cases (i.e. matvec, dot).
894 return pack(rewriter, genericOp, adjustedPackedSizes);
895}
896
897//===----------------------------------------------------------------------===//
898// Transformations exposed as rewrite patterns.
899//===----------------------------------------------------------------------===//
900
903 assert(!tileSizeComputationFunction && "tile sizes already set");
904 SmallVector<int64_t, 4> tileSizes(ts);
905 tileSizeComputationFunction = [tileSizes](OpBuilder &b, Operation *op) {
907 b.setInsertionPointToStart(
908 &op->getParentOfType<func::FuncOp>().getBody().front());
909 return llvm::map_to_vector<4>(tileSizes, [&](int64_t s) {
910 Value v = arith::ConstantIndexOp::create(b, op->getLoc(), s);
911 return v;
912 });
913 };
914 return *this;
915}
916
918 memref::CopyOp copyOp, PatternRewriter &rewriter) const {
919 return vectorizeCopy(rewriter, copyOp);
920}
921
922/// Filling `dest` using FillOp constant padding value if possible.
923/// Otherwise, generate a tensor::GenerateOp.
925 RewriterBase &rewriter, tensor::PadOp padOp, Value dest,
926 const SmallVector<Value> &dynSizes) const {
927 auto padValue = padOp.getConstantPaddingValue();
928 if (padValue) {
929 // Move the padding value defined inside the PadOp block to outside.
930 if (padValue.getParentBlock() == &padOp.getRegion().front())
931 rewriter.moveOpBefore(padValue.getDefiningOp(), padOp);
932 return FillOp::create(rewriter, padOp.getLoc(), padValue, dest).result();
933 }
934
935 // Fill could not be optimized: Lower to tensor::GenerateOp with region.
936 auto generateOp = tensor::GenerateOp::create(rewriter, padOp.getLoc(),
937 padOp.getResultType(), dynSizes);
938 // Copy region to new op.
939 IRMapping bvm;
940 padOp.getRegion().cloneInto(&generateOp.getRegion(), bvm);
941 return generateOp;
942}
943
944LogicalResult
946 PatternRewriter &rewriter) const {
947 // Given an OpFoldResult, return an index-typed value.
948 auto getIdxValue = [&](OpFoldResult ofr) {
949 if (auto val = llvm::dyn_cast_if_present<Value>(ofr))
950 return val;
952 rewriter, padOp.getLoc(),
953 cast<IntegerAttr>(cast<Attribute>(ofr)).getInt())
954 .getResult();
955 };
956
957 auto resultType = padOp.getResultType();
958 // Compute size of EmptyOp. Any combination of static/dynamic is supported.
959 SmallVector<Value> dynSizes;
960 SmallVector<int64_t> staticSizes;
961 for (unsigned dim = 0; dim < resultType.getRank(); ++dim) {
962 if (resultType.isDynamicDim(dim)) {
963 auto srcSize = getIdxValue(tensor::getMixedSize(rewriter, padOp.getLoc(),
964 padOp.getSource(), dim));
965 // Add low and high padding value.
966 auto plusLow = rewriter.createOrFold<arith::AddIOp>(
967 padOp.getLoc(), srcSize, getIdxValue(padOp.getMixedLowPad()[dim]));
968 auto plusHigh = rewriter.createOrFold<arith::AddIOp>(
969 padOp.getLoc(), plusLow, getIdxValue(padOp.getMixedHighPad()[dim]));
970 dynSizes.push_back(plusHigh);
971 }
972 staticSizes.push_back(resultType.getDimSize(dim));
973 }
974
975 // Init tensor and fill it with padding.
976 Value emptyTensor =
977 tensor::EmptyOp::create(rewriter, padOp.getLoc(), staticSizes,
978 resultType.getElementType(), dynSizes);
979 Value fill = createFillOrGenerateOp(rewriter, padOp, emptyTensor, dynSizes);
980
981 // Generate a InsertSliceOp for copying the PadOp source.
982 auto sourceType = padOp.getSourceType();
983 // Compute size of source of tensor::PadOp.
985 tensor::getMixedSizes(rewriter, padOp.getLoc(), padOp.getSource());
986 // Strides of InsertSliceOp are all 1.
987 SmallVector<OpFoldResult> strides(sourceType.getRank(),
988 rewriter.getIndexAttr(1));
989 rewriter.replaceOpWithNewOp<tensor::InsertSliceOp>(
990 padOp, padOp.getSource(), fill, padOp.getMixedLowPad(), srcSizes,
991 strides);
992
993 return success();
994}
995
997 tensor::ExtractSliceOp sliceOp, PatternRewriter &rewriter) const {
998 if (!sliceOp.hasUnitStride())
999 return failure();
1000
1001 auto padOp = sliceOp.getSource().getDefiningOp<tensor::PadOp>();
1002 if (!padOp)
1003 return failure();
1004
1005 bool zeroSliceGuard = true;
1006 if (controlFn) {
1007 if (std::optional<bool> control = controlFn(sliceOp))
1008 zeroSliceGuard = *control;
1009 else
1010 return failure();
1011 }
1012
1013 FailureOr<TilingResult> tilingResult =
1014 tensor::bubbleUpPadSlice(rewriter, padOp, sliceOp.getMixedOffsets(),
1015 sliceOp.getMixedSizes(), zeroSliceGuard);
1016 if (failed(tilingResult))
1017 return failure();
1018
1019 RankedTensorType sourceType = sliceOp.getSourceType();
1020 RankedTensorType resultType = sliceOp.getResultType();
1021
1022 // If the extract_slice is not rank-reduced, all shapes are static and the
1023 // data source is actually used. Rewrite into pad(extract_slice(x)).
1024 if (sourceType.getRank() == resultType.getRank()) {
1025 rewriter.replaceOp(sliceOp, tilingResult->tiledValues);
1026 return success();
1027 }
1028
1029 // Handle rank-reduced slice by creating another extract_slice op.
1031 rewriter, sliceOp.getLoc(), tilingResult->tiledValues[0], resultType);
1032
1033 rewriter.replaceOp(sliceOp, rankReduced);
1034 return success();
1035}
1036
1037/// If padding value is set, returns a tensor.pad Op for the source tensor,
1038/// with the output shape matching the output of `packOp`. Otherwise, returns
1039/// the source directly.
1040///
1041/// This method assumes that all outer dims for this pack Op are 1.
1043 linalg::PackOp packOp) {
1044 Value input = packOp.getSource();
1045 // TODO: Support Memref PackOp. Temporarily return just Op Source.
1046 if (!packOp.hasPureTensorSemantics())
1047 return input;
1048
1049 if (!packOp.getPaddingValue()) {
1050 return input;
1051 }
1052
1053 assert(llvm::all_of(packOp.getAllOuterDims(),
1054 [](int64_t val) { return val == 1; }) &&
1055 "some outer dims are != 1");
1056
1057 Location loc = packOp.getLoc();
1058 ShapedType inputType = packOp.getSourceType();
1059 int64_t inputRank = inputType.getRank();
1060
1061 DenseMap<int64_t, OpFoldResult> tileAndPosMapping =
1062 packOp.getDimAndTileMapping();
1063
1064 // The sizes of dynamic tiles
1065 SmallVector<Value> dynamicTileSizes;
1066
1067 // Collect dims for the padded shape.
1068 SmallVector<int64_t> paddedShape;
1069 for (int64_t dimIdx = 0; dimIdx < inputRank; ++dimIdx) {
1070 // 1. Non-tiled outer dims.
1071 // These dims should be 1 and we simply preserve them.
1072 if (!tileAndPosMapping.count(dimIdx)) {
1073 int64_t inputDimSize = inputType.getDimSize(dimIdx);
1074 assert(inputDimSize == 1 &&
1075 "with all outer dims == 1, this non-tiled input dim should be 1!");
1076 paddedShape.push_back(inputDimSize);
1077 continue;
1078 }
1079
1080 // 2. Tiled outer dims
1081 // As all outer dims == 1, it is safe to use the tile size for the padded
1082 // shape.
1083 OpFoldResult tileSizeForDim = tileAndPosMapping.lookup(dimIdx);
1084
1085 // 2.1 Static tile sizes
1086 std::optional<int64_t> cstTileSize = getConstantIntValue(tileSizeForDim);
1087 if (cstTileSize.has_value()) {
1088 paddedShape.push_back(cstTileSize.value());
1089 continue;
1090 }
1091
1092 // 2.2 Dynamic tile sizes
1093 paddedShape.push_back(ShapedType::kDynamic);
1094
1095 // Get the value that holds the dynamic size.
1096 dynamicTileSizes.push_back(llvm::dyn_cast<Value>(tileSizeForDim));
1097 }
1098 auto resultType =
1099 RankedTensorType::get(paddedShape, inputType.getElementType());
1100 return tensor::createPadHighOp(resultType, input, packOp.getPaddingValue(),
1101 /*nofold=*/false, loc, builder,
1102 dynamicTileSizes);
1103}
1104
1105// Normalizes a permutation on a higher rank space to its actual size, e.g.
1106// perm = [1, 4, 2]
1107// becomes
1108// norm = [0, 2, 1]
1109static SmallVector<int64_t>
1111 constexpr int64_t kNonTiledMarker = -1;
1112 SmallVector<int64_t> vec(rank, kNonTiledMarker);
1113 for (auto [index, value] : llvm::enumerate(perm))
1114 vec[value] = index;
1115 SmallVector<int64_t> normalizedPerm = llvm::filter_to_vector(
1116 vec, [&](int64_t v) { return v != kNonTiledMarker; });
1117 // This inverts the permutation in addition to normalizing so invert back.
1118 return invertPermutationVector(normalizedPerm);
1119}
1120
1121// Gets the normalized permutation implied by innerDimsPos and outerDimsPerm
1122// assuming rank reduction of unit outer dims.
1123static SmallVector<int64_t>
1125 ArrayRef<int64_t> innerDimsPos,
1126 ArrayRef<int64_t> outerDimsPerm) {
1127 SmallVector<int64_t> rankReducedOuterDimsPerm;
1128 SmallVector<int64_t> outerDims;
1129 SmallVector<int64_t> innerDims;
1130 int64_t dim = 0;
1131 int64_t unpackedRank = shape.size();
1132 for (auto i : llvm::seq<unsigned>(0, unpackedRank)) {
1133 if (llvm::is_contained(innerDimsPos, i)) {
1134 innerDims.push_back(dim++);
1135 continue;
1136 }
1137 if (shape[i] == 1)
1138 continue;
1139 outerDims.push_back(dim++);
1140 if (!outerDimsPerm.empty())
1141 rankReducedOuterDimsPerm.push_back(outerDimsPerm[i]);
1142 }
1143
1144 // Get the position of the inner dims after permutation.
1145 SmallVector<int64_t> innerPerm =
1146 getPackUnpackNormalizedPerm(unpackedRank, innerDimsPos);
1147 applyPermutationToVector<int64_t>(innerDims, innerPerm);
1148
1149 // Ditto for the outer dims.
1150 SmallVector<int64_t> perm = outerDims;
1151
1152 rankReducedOuterDimsPerm =
1153 getPackUnpackNormalizedPerm(unpackedRank, rankReducedOuterDimsPerm);
1154 if (!rankReducedOuterDimsPerm.empty())
1155 applyPermutationToVector<int64_t>(perm, rankReducedOuterDimsPerm);
1156
1157 // The tile always ends up as the inner most dims after packing.
1158 perm.append(innerDims);
1159
1160 return perm;
1161}
1162
1164 linalg::PackOp packOp, PatternRewriter &rewriter) const {
1165 // TODO: Support Memref PackOp. Temporarily return failure.
1166 if (!packOp.hasPureTensorSemantics())
1167 return failure();
1168
1169 if (llvm::any_of(packOp.getTiledOuterDims(),
1170 [](int64_t dim) { return dim != 1; })) {
1171 return rewriter.notifyMatchFailure(
1172 packOp, "not all outer dimensions of the result are 1s");
1173 }
1174
1175 ArrayRef<int64_t> innerDimsPos = packOp.getInnerDimsPos();
1176 auto outerDimsPerm = packOp.getOuterDimsPerm();
1177
1178 // Verify that there are no:
1179 // * non-unit + un-tiled-outer-dims,
1180 // that are permuted. Supporting such cases would require refining the logic
1181 // that generates the Transpose Op.
1182 if (!llvm::all_of(outerDimsPerm, [&innerDimsPos, &packOp](int64_t dim) {
1183 static int prev = 0;
1184 // Skip tiled dims - these can be permuted.
1185 if (llvm::is_contained(innerDimsPos, dim))
1186 return true;
1187
1188 // Check whether this dim has been permuted. Permuting unit dims is fine
1189 // as that's effectively a no-op.
1190 if (dim < prev && (packOp.getResult().getType().getShape()[prev] != 1 ||
1191 packOp.getResult().getType().getShape()[dim] != 1))
1192 return false;
1193
1194 prev = dim;
1195 return true;
1196 })) {
1197 return rewriter.notifyMatchFailure(
1198 packOp, "At least one non-unit and un-tiled outer dim is permuted, "
1199 "this is not supported ATM!");
1200 }
1201
1202 Location loc = packOp.getLoc();
1203
1204 int64_t srcRank = packOp.getSourceRank();
1205
1206 // 1. Get the input that is going to be packed. If the input requires padding,
1207 // add a padding operation and return that as the input.
1208 Value input = getPackOpSourceOrPaddedSource(rewriter, packOp);
1209
1210 // 2. Transpose the input to match the inner tile order:
1211 // %init = tensor.empty()
1212 // %transposed_tile = linalg.transpose ins(%source_or_padded_source),
1213 // outs(%init)
1214 // Assumptions made:
1215 // - All tiled outer dims are 1 - the corresponding transposition order
1216 // doesn't matter, but requires all dim indices to be present.
1217 // - Un-tiled outer dims remain un-permuted.
1218
1219 // 2.1 Get the permutation for linalg.transpose:
1220 // [ untiled-dims, inner-dims-pos ]
1221 // Note, this logic assumes that the untiled dims are not permuted.
1222 SmallVector<int64_t> srcPermForTranspose;
1223 for (int64_t i = 0; i < srcRank; i++) {
1224 // We assume the `k` dimensions of the inner dim position, where `k` is the
1225 // rank of the inner tiling, correspond to the last `k` indices of the
1226 // transpose permutation. This is done by adding the indices not contained
1227 // in the inner dimension position in order from 0 to `n`. Where n is the
1228 // rank of the source tensor. For example if we have a source tensor with
1229 // indices [0, 1, 2, 3] and inner dim position of [3, 0], the remaining
1230 // indices are [1, 2]. and the transpose will be [1, 2, 3, 0].
1231 if (llvm::is_contained(innerDimsPos, i))
1232 continue;
1233 srcPermForTranspose.push_back(i);
1234 }
1235 srcPermForTranspose.append(innerDimsPos.begin(), innerDimsPos.end());
1236
1237 // 2.2 Create the init tensor for linalg.transpose with the correct shape:
1238 // [ untiled-dims, tiled-dims ]
1239 ShapedType inputTy = cast<ShapedType>(input.getType());
1240 SmallVector<OpFoldResult> shapeForEmptyOp;
1241 for (int64_t i = 0; i < srcRank; i++) {
1242 if (llvm::is_contained(innerDimsPos, i)) {
1243 // The tiled dims are appended after this loop.
1244 continue;
1245 }
1246 if (inputTy.isStaticDim(i))
1247 shapeForEmptyOp.push_back(rewriter.getIndexAttr(inputTy.getShape()[i]));
1248 else
1249 shapeForEmptyOp.emplace_back(
1250 tensor::DimOp::create(rewriter, loc, input, i).getResult());
1251 }
1252 shapeForEmptyOp.append(packOp.getMixedTiles());
1253
1254 // getMixedTiles() may contain Values pointing to constant ops (as opposed to
1255 // constant attributes with the corresponding value). Replace those with
1256 // attributes. This is to match the behaviour in
1257 // `getPackOpSourceOrPaddedSource`, which replaces constant SSA values with
1258 // attributes.
1259 llvm::transform(shapeForEmptyOp, shapeForEmptyOp.begin(),
1260 [&](OpFoldResult ofr) {
1261 if (auto val = llvm::dyn_cast<Value>(ofr))
1262 return getAsOpFoldResult(val);
1263 return ofr;
1264 });
1265
1266 LDBG() << "Pack permutation: " << packOp;
1267 LDBG() << "perm: " << llvm::interleaved(srcPermForTranspose);
1268 LDBG() << "Shape of empty tensor: " << llvm::interleaved(shapeForEmptyOp);
1269
1270 Value empty = tensor::EmptyOp::create(
1271 rewriter, loc, shapeForEmptyOp, packOp.getSourceType().getElementType());
1272
1273 // 2.3 Create linalg.transpose
1274 auto transposedOp = linalg::TransposeOp::create(rewriter, loc, input, empty,
1275 srcPermForTranspose);
1276
1277 // 3. Insert the inner tile into the destination tensor:
1278 // %inserted_tile = tensor.insert_slice(%transposed_tile)
1279
1280 // Compute the sizes attribute:
1281 // [ outer-dims, tile-sizes ]
1282 // Note that the output from the transpose Op excludes the tiled outer dims.
1283 // However, given the assumption that:
1284 // * all tiled outer dims == 1,
1285 // we can just use a rank-expanding tensor.insert_slice.
1286 SmallVector<OpFoldResult> writeSizes;
1287 for (auto size : packOp.getAllOuterDims()) {
1288 writeSizes.push_back(rewriter.getIndexAttr(size));
1289 }
1290
1291 for (auto tileSize : packOp.getMixedTiles()) {
1292 auto [_, tileSizeOfr] =
1293 getSimplifiedOfrAndStaticSizePair(tileSize, rewriter);
1294 writeSizes.push_back(tileSizeOfr);
1295 }
1296
1297 auto insert = tensor::InsertSliceOp::create(
1298 rewriter, loc, transposedOp.getResult()[0], packOp.getDest(), writeSizes);
1299
1300 // 4. Replace tensor.packOp with tensor.insert_slice created above
1301 rewriter.replaceOp(packOp, insert.getResult());
1302
1303 return success();
1304}
1305
1307 linalg::UnPackOp unpackOp, PatternRewriter &rewriter) const {
1308 if (!unpackOp.hasPureTensorSemantics())
1309 return failure();
1310
1311 int64_t destRank = unpackOp.getDestRank();
1312 ArrayRef<int64_t> srcShape = unpackOp.getSourceType().getShape();
1313 ArrayRef<int64_t> innerDimsPos = unpackOp.getInnerDimsPos();
1314 if (llvm::any_of(unpackOp.getTiledOuterDims(),
1315 [](int64_t dim) { return dim != 1; })) {
1316 return rewriter.notifyMatchFailure(
1317 unpackOp,
1318 "require the tiled outer dimensions of the result are all 1s");
1319 }
1320
1321 // 1. Use rank-reduced tensor.extract_slice op to extract the tile:
1322 // %extracted_tile = tensor.extract_slice(%unpack_op_input)
1323 Location loc = unpackOp.getLoc();
1324 Value source = unpackOp.getSource();
1325 DenseMap<int64_t, OpFoldResult> dimAndTileMapping =
1326 unpackOp.getDimAndTileMapping();
1327 Attribute oneIdxAttr = rewriter.getIndexAttr(1);
1328
1329 // The shape for ExtractSliceOp. Note that this will consist of 3 blocks of
1330 // dims:
1331 // [ outer-untiled-dims, outer-tiled-dims, tile-sizes ]
1332 SmallVector<int64_t> readShapeForExtractSlice;
1333 // The sizes attribute for ExtractSliceOp. Due to rank-reducing (and
1334 // outer-tiled-dims being all 1), this will be
1335 // [ outer-untiled-dims, tile-sizes ]
1336 SmallVector<OpFoldResult> extractSliceSizes;
1337
1338 // Shape for EmptyOp that's used as the init value for TransposeOp below.
1339 // This should be:
1340 // [ outer-untiled-dims, tile-sizes ]
1341 // However, skip unit dims - TransposeOp (below) applies rank-reduced
1342 // permutation.
1343 SmallVector<OpFoldResult> shapeForEmptyOp;
1344
1345 for (auto i : llvm::seq<unsigned>(0, destRank)) {
1346 // Compute sizes attribute for ExtractSliceOp - outer-tiled-dims.
1347 //
1348 // As all outer tiled dims are 1, so the corresponding
1349 // slice size to read will also 1. As this will be rank-reducing "extract
1350 // slice" (i.e. the unit dims will be "collapsed"), there's no need to
1351 // update:
1352 // * the output shape for ExtractSliceOp, nor
1353 // * the shape for EmptyOp.
1354 if (dimAndTileMapping.count(i)) {
1355 extractSliceSizes.push_back(oneIdxAttr);
1356 continue;
1357 }
1358
1359 // Compute sizes attribute for ExtractSliceOp + EmptyOp -
1360 // outer-untiled-dims
1361 if (ShapedType::isDynamic(srcShape[i])) {
1362 OpFoldResult dynamicDim =
1363 tensor::DimOp::create(rewriter, loc, source, i).getResult();
1364 extractSliceSizes.push_back(dynamicDim);
1365 shapeForEmptyOp.push_back(dynamicDim);
1366 } else {
1367 extractSliceSizes.push_back(rewriter.getIndexAttr(srcShape[i]));
1368 if (srcShape[i] != 1)
1369 shapeForEmptyOp.push_back(rewriter.getIndexAttr(srcShape[i]));
1370 }
1371 // Compute the output shape for ExtractSliceOp - outer-untiled-dims (take
1372 // into account rank-reducing)
1373 if (srcShape[i] != 1) {
1374 readShapeForExtractSlice.push_back(srcShape[i]);
1375 }
1376 }
1377 // Append the tile sizes to "sizes attribute" for ExtractSliceOp and the
1378 // shape for EmptyOp.
1379 auto mixedTiles = unpackOp.getMixedTiles();
1380 extractSliceSizes.append(mixedTiles.begin(), mixedTiles.end());
1381 shapeForEmptyOp.append(mixedTiles.begin(), mixedTiles.end());
1382
1383 // Explicitly create the type for extract_slice op because the inner tile
1384 // size could be 1. We want to represent the whole inner tile in this case.
1385 auto tileShape = srcShape.drop_front(destRank);
1386 // Append the inner tile shape to the permuted and rank-reduced outer shape.
1387 readShapeForExtractSlice.append(tileShape.begin(), tileShape.end());
1388 Type elemType = unpackOp.getSourceType().getElementType();
1389 auto readType = RankedTensorType::get(readShapeForExtractSlice, elemType);
1390 Value innerTile = tensor::ExtractSliceOp::create(
1391 rewriter, loc, readType, unpackOp.getSource(), extractSliceSizes);
1392
1393 // 2. Transpose the tile to match the outer corresponding tile order.
1395 srcShape.take_front(destRank), innerDimsPos, unpackOp.getOuterDimsPerm());
1396 // Unpack is a transition out of packed space so we invert the permutation.
1397 perm = invertPermutationVector(perm);
1398 applyPermutationToVector<OpFoldResult>(shapeForEmptyOp, perm);
1399
1400 Value empty =
1401 tensor::EmptyOp::create(rewriter, loc, shapeForEmptyOp, elemType);
1402 auto transposedOp =
1403 linalg::TransposeOp::create(rewriter, loc, innerTile, empty, perm);
1404
1405 // 3. Handle in-complete tiles if needed. It truncates trailing data from the
1406 // transposed tile.
1407 SmallVector<OpFoldResult> tileSizes;
1408 ArrayRef<int64_t> destShape = unpackOp.getDestType().getShape();
1409 for (auto i : llvm::seq<unsigned>(0, destRank)) {
1410 if (dimAndTileMapping.count(i) || destShape[i] != 1)
1411 tileSizes.push_back(
1412 tensor::getMixedSize(rewriter, loc, unpackOp.getDest(), i));
1413 }
1414
1415 auto partialTile =
1416 tensor::ExtractSliceOp::create(rewriter, loc, RankedTensorType(),
1417 transposedOp.getResult()[0], tileSizes);
1418
1419 // 4. Insert the result to the destination tensor.
1420 SmallVector<OpFoldResult> writeSizes;
1421 for (int i = 0, idx = 0; i < destRank; ++i) {
1422 if (dimAndTileMapping.count(i) || destShape[i] != 1)
1423 writeSizes.push_back(tileSizes[idx++]);
1424 else
1425 writeSizes.push_back(oneIdxAttr);
1426 }
1427 auto insert = tensor::InsertSliceOp::create(rewriter, loc, partialTile,
1428 unpackOp.getDest(), writeSizes);
1429 rewriter.replaceOp(unpackOp, insert.getResult());
1430
1431 return success();
1432}
1433
1434//===----------------------------------------------------------------------===//
1435// Generic DownscaleSizeOneWindowedConvolution
1436//===----------------------------------------------------------------------===//
1437//
1438/// Returns the indices of affine map results that reference any of the given
1439/// dimensions.
1442 SmallVector<unsigned> resultIndices;
1443 for (unsigned dim : dims) {
1444 for (unsigned i = 0, e = map.getNumResults(); i < e; ++i) {
1445 AffineExpr expr = map.getResult(i);
1446 if (expr.isFunctionOfDim(dim)) {
1447 resultIndices.push_back(i);
1448 break;
1449 }
1450 }
1451 }
1452 return resultIndices;
1453}
1454
1455/// Helper to create a rank-reducing extract_slice that removes specific
1456/// dimensions from a tensor.
1458 Location loc, Value tensor,
1459 ArrayRef<unsigned> dimsToRemove) {
1460 auto tensorType = cast<RankedTensorType>(tensor.getType());
1461 int64_t rank = tensorType.getRank();
1462
1463 // Compute new shape by removing the specified dimensions.
1464 SmallVector<int64_t> newShape;
1465 for (int64_t i = 0; i < rank; ++i) {
1466 if (!llvm::is_contained(dimsToRemove, i))
1467 newShape.push_back(tensorType.getDimSize(i));
1468 }
1469
1470 auto newType = RankedTensorType::get(newShape, tensorType.getElementType());
1472 tensor, newType);
1473}
1474
1475/// Drops specified dimensions from an AffineExpr and compresses remaining
1476/// dimension indices. Returns std::nullopt if the expression only references
1477/// the dropped dimensions.
1478static std::optional<AffineExpr>
1480 unsigned newNumDims, MLIRContext *ctx) {
1481 // Check if expr only references dimensions to be dropped.
1482 bool onlyReferencesDroppedDims = true;
1483 for (unsigned d = 0; d < newNumDims + dimsToDrop.size(); ++d) {
1484 if (expr.isFunctionOfDim(d) && !llvm::is_contained(dimsToDrop, d)) {
1485 onlyReferencesDroppedDims = false;
1486 break;
1487 }
1488 }
1489 if (onlyReferencesDroppedDims && llvm::any_of(dimsToDrop, [&](unsigned d) {
1490 return expr.isFunctionOfDim(d);
1491 }))
1492 return std::nullopt;
1493
1494 // Replace dimensions: compute new index for each old dimension.
1495 // Dropped dimensions get mapped to constant 0, others get compressed.
1496 SmallVector<AffineExpr> dimReplacements;
1497 unsigned newDimIdx = 0;
1498 for (unsigned d = 0; d < newNumDims + dimsToDrop.size(); ++d) {
1499 if (llvm::is_contained(dimsToDrop, d)) {
1500 dimReplacements.push_back(getAffineConstantExpr(0, ctx));
1501 } else {
1502 dimReplacements.push_back(getAffineDimExpr(newDimIdx++, ctx));
1503 }
1504 }
1505
1506 return expr.replaceDims(dimReplacements);
1507}
1508
1509FailureOr<LinalgOp>
1511 LinalgOp op) {
1512 auto maybeDims = inferConvolutionDims(op);
1513 if (failed(maybeDims))
1514 return failure();
1515
1516 // Currently supports only 2D convolutions.
1517 if (maybeDims->outputImage.size() != 2 || maybeDims->filterLoop.size() != 2)
1518 return failure();
1519
1520 if (op.hasPureBufferSemantics())
1521 return failure();
1522
1523 // Get loop domain indices for spatial dimensions.
1524 unsigned outSpatial0 = maybeDims->outputImage[0];
1525 unsigned outSpatial1 = maybeDims->outputImage[1];
1526 unsigned filterSpatial0 = maybeDims->filterLoop[0];
1527 unsigned filterSpatial1 = maybeDims->filterLoop[1];
1528
1529 // Get sizes from loop bounds.
1530 SmallVector<int64_t, 4> loopRanges = op.getStaticLoopRanges();
1531 int64_t outSize0 = loopRanges[outSpatial0];
1532 int64_t outSize1 = loopRanges[outSpatial1];
1533 int64_t filterSize0 = loopRanges[filterSpatial0];
1534 int64_t filterSize1 = loopRanges[filterSpatial1];
1535
1536 // Check if we can downscale by removing a spatial dimension.
1537 bool canRemoveSpatial0 = (filterSize0 == 1 && outSize0 == 1);
1538 bool canRemoveSpatial1 = (filterSize1 == 1 && outSize1 == 1);
1539 if (!canRemoveSpatial0 && !canRemoveSpatial1)
1540 return failure();
1541
1542 // Determine which loop dims to remove (output spatial + corresponding filter)
1543 // and sort for correct index compression when removing dimensions from affine
1544 // maps.
1545 SmallVector<unsigned> loopDimsToRemove;
1546 if (canRemoveSpatial0) {
1547 loopDimsToRemove.push_back(outSpatial0);
1548 loopDimsToRemove.push_back(filterSpatial0);
1549 } else {
1550 loopDimsToRemove.push_back(outSpatial1);
1551 loopDimsToRemove.push_back(filterSpatial1);
1552 }
1553 llvm::sort(loopDimsToRemove);
1554
1555 // Create new indexing maps with dimensions removed.
1556 SmallVector<AffineMap> newMaps;
1557 MLIRContext *ctx = op.getContext();
1558 unsigned numDims = op.getNumLoops();
1559 unsigned newNumDims = numDims - loopDimsToRemove.size();
1560 for (AffineMap map : op.getIndexingMapsArray()) {
1561 SmallVector<AffineExpr> newResults;
1562 for (AffineExpr expr : map.getResults()) {
1563 auto newExpr =
1564 dropDimsAndCompress(expr, loopDimsToRemove, newNumDims, ctx);
1565 if (newExpr)
1566 newResults.push_back(*newExpr);
1567 }
1568 newMaps.push_back(AffineMap::get(newNumDims, 0, newResults, ctx));
1569 }
1570
1571 // Create new iterator types.
1573 auto iterTypes = op.getIteratorTypesArray();
1574 for (unsigned idx = 0; idx < iterTypes.size(); ++idx) {
1575 if (!llvm::is_contained(loopDimsToRemove, idx))
1576 newIterTypes.push_back(iterTypes[idx]);
1577 }
1578
1579 // Rank-reduce operands using extract_slice.
1580 Location loc = op.getLoc();
1581 SmallVector<Value> newInputs;
1582 for (OpOperand *input : op.getDpsInputOperands()) {
1583 AffineMap map = op.getMatchingIndexingMap(input);
1584 SmallVector<unsigned> tensorDimsToRemove =
1585 getResultIndicesReferencingDims(map, loopDimsToRemove);
1586 Value reduced = createRankReducingExtractSlice(rewriter, loc, input->get(),
1587 tensorDimsToRemove);
1588 newInputs.push_back(reduced);
1589 }
1590
1591 OpOperand &output = *op.getDpsInitsMutable().begin();
1592 AffineMap outputMap = op.getMatchingIndexingMap(&output);
1593 SmallVector<unsigned> outputDimsToRemove =
1594 getResultIndicesReferencingDims(outputMap, loopDimsToRemove);
1595 Value newOutput = createRankReducingExtractSlice(rewriter, loc, output.get(),
1596 outputDimsToRemove);
1597
1598 // Create new linalg.generic with reduced dimensions.
1599 auto newOp =
1600 linalg::GenericOp::create(rewriter, loc, TypeRange{newOutput.getType()},
1601 newInputs, newOutput, newMaps, newIterTypes);
1602 rewriter.inlineRegionBefore(op->getRegion(0), newOp.getRegion(),
1603 newOp.getRegion().begin());
1604
1605 // Try to specialize the generic back to a named op only if the input was
1606 // already a specialized (named) op.
1607 LinalgOp resultOp = newOp;
1608 if (!isa<GenericOp>(op)) {
1609 FailureOr<LinalgOp> specializedOp = specializeGenericOp(rewriter, newOp);
1610 if (succeeded(specializedOp))
1611 resultOp = *specializedOp;
1612 }
1613
1614 // Insert result back into original shape.
1616 rewriter, loc, resultOp->getResult(0), output.get());
1617
1618 rewriter.replaceOp(op, result);
1619 return resultOp;
1620}
1621
1622namespace {
1623/// Pattern wrapper around `downscaleSizeOneWindowedConvolution`.
1624struct DownscaleSizeOneWindowedConvolution final
1625 : public OpInterfaceRewritePattern<LinalgOp> {
1626 DownscaleSizeOneWindowedConvolution(MLIRContext *context,
1627 PatternBenefit benefit = 1)
1628 : OpInterfaceRewritePattern<LinalgOp>(context, benefit) {}
1629
1630 LogicalResult matchAndRewrite(LinalgOp op,
1631 PatternRewriter &rewriter) const override {
1633 }
1634};
1635} // namespace
1636
1638 PatternBenefit benefit) {
1639 patterns.add<DownscaleSizeOneWindowedConvolution>(patterns.getContext(),
1640 benefit);
1641}
1642
1647
return success()
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
static RankedTensorType permuteShape(RankedTensorType tensorType, ArrayRef< int64_t > permutationVector)
Return a copy of tensorType after permutation by permutationVector.
static std::optional< AffineExpr > dropDimsAndCompress(AffineExpr expr, ArrayRef< unsigned > dimsToDrop, unsigned newNumDims, MLIRContext *ctx)
Drops specified dimensions from an AffineExpr and compresses remaining dimension indices.
static SmallVector< int64_t > getPackUnpackRankReducedPerm(ArrayRef< int64_t > shape, ArrayRef< int64_t > innerDimsPos, ArrayRef< int64_t > outerDimsPerm)
static Value createRankReducingExtractSlice(RewriterBase &rewriter, Location loc, Value tensor, ArrayRef< unsigned > dimsToRemove)
Helper to create a rank-reducing extract_slice that removes specific dimensions from a tensor.
static FailureOr< SmallVector< std::optional< int64_t > > > packLinalgMetadataOnce(SmallVectorImpl< AffineMap > &indexingMaps, SmallVectorImpl< utils::IteratorType > &iteratorTypes, int64_t dim)
Perform one step of packing of a LinalgOp's metadata along dim into the newDim at iteratorTypes....
static LinalgOp transposeOneLinalgOperandAndReplace(RewriterBase &rewriter, LinalgOp linalgOp, OpOperand &opOperand, ArrayRef< int64_t > permutation, Value transposedValue)
Return a new GenericOp obtained by transposing opOperand by the permutation vector:
static SmallVector< int64_t > getPackUnpackNormalizedPerm(int rank, ArrayRef< int64_t > perm)
static bool hasAtMostOneResultFunctionOfDim(AffineMap map, int64_t dim)
Return true if map has 0 or 1 result function of AffineDimExpr(dim).
static Value getPackOpSourceOrPaddedSource(OpBuilder &builder, linalg::PackOp packOp)
If padding value is set, returns a tensor.pad Op for the source tensor, with the output shape matchin...
static SmallVector< unsigned > getResultIndicesReferencingDims(AffineMap map, ArrayRef< unsigned > dims)
Returns the indices of affine map results that reference any of the given dimensions.
static std::string stringifyReassocIndices(ReassociationIndicesRef ri)
static std::optional< int64_t > getFirstResultIndexFunctionOf(AffineMap map, int64_t dim)
Return the index of the first result of map that is a function of AffineDimExpr(dim),...
Base type for affine expression.
Definition AffineExpr.h:68
bool isFunctionOfDim(unsigned position) const
Return true if the affine expression involves AffineDimExpr position.
AffineExpr replaceDims(ArrayRef< AffineExpr > dimReplacements) const
Dim-only version of replaceDimsAndSymbols.
AffineExpr ceilDiv(uint64_t v) const
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: () -> ().
AffineMap shiftDims(unsigned shift, unsigned offset=0) const
Replace dims[offset ... numDims) by dims[offset + shift ... shift + numDims).
Definition AffineMap.h:267
AffineMap insertResult(AffineExpr expr, unsigned pos) const
Returns a new AffineMap with the same number of dims and symbols and an extra result inserted at pos.
Definition AffineMap.h:315
unsigned getNumDims() const
ArrayRef< AffineExpr > getResults() const
unsigned getNumResults() const
AffineExpr getResult(unsigned idx) const
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.
Attributes are known-constant values of operations.
Definition Attributes.h:25
This class is a general helper class for creating context-global objects like types,...
Definition Builders.h:51
IntegerAttr getIndexAttr(int64_t value)
Definition Builders.cpp:112
TypedAttr getZeroAttr(Type type)
Definition Builders.cpp:329
AffineExpr getAffineDimExpr(unsigned position)
Definition Builders.cpp:369
MLIRContext * getContext() const
Definition Builders.h:56
This is a utility class for mapping one set of IR entities to another.
Definition IRMapping.h:26
IRValueT get() const
Return the current value being used by this operand.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
RAII guard to reset the insertion point of the builder when destroyed.
Definition Builders.h:350
This class helps build Operations.
Definition Builders.h:209
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:400
void createOrFold(SmallVectorImpl< Value > &results, Location location, Args &&...args)
Create an operation of specific op type at the current insertion point, and immediately try to fold i...
Definition Builders.h:528
This class represents a single result from folding an operation.
This class represents an operand of an operation.
Definition Value.h:254
unsigned getOperandNumber() const
Return which operand this is in the OpOperand list of the Operation.
Definition Value.cpp:226
This is a value defined by a result of an operation.
Definition Value.h:454
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
result_range getResults()
Definition Operation.h:440
This class represents the benefit of a pattern match in a unitless scheme that ranges from 0 (very li...
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
This is a builder type that keeps local references to arguments.
Builder & setShape(ArrayRef< int64_t > newShape)
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.
This class coordinates the application of a rewrite on a set of IR, providing a way for clients to tr...
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
void moveOpBefore(Operation *op, Operation *existingOp)
Unlink this operation from its current block and insert it right before existingOp which may be in th...
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 inlineRegionBefore(Region &region, Region &parent, Region::iterator before)
Move the blocks that belong to "region" before the given position in another region "parent".
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
This class provides an abstraction over the various different ranges of value types.
Definition TypeRange.h:40
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
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
Type getType() const
Return the type of this value.
Definition Value.h:105
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:384
Operation * getOwner() const
Return the owner of this operand.
Definition UseDefLists.h:38
OpFoldResult makeComposedFoldedAffineApply(OpBuilder &b, Location loc, AffineMap map, ArrayRef< OpFoldResult > operands, bool composeAffineMin=false)
Constructs an AffineApplyOp that applies map to operands after composing the map with the maps of any...
SmallVector< int64_t > getUnPackInverseSrcPerm(linalg::UnPackOp, PackingMetadata &metadata)
Compute inverse permutation for the source tensor (i.e.
FailureOr< PackTransposeResult > packTranspose(RewriterBase &rewriter, linalg::PackOp packOp, linalg::LinalgOp linalgOp, linalg::UnPackOp maybeUnPackOp, ArrayRef< int64_t > outerPerm, ArrayRef< int64_t > innerPerm)
Transpose a single PackOp -> LinalgOp -> UnPackOp chain and return the transposed PackOp -> LinalgOp ...
FailureOr< LowerUnPackOpResult > lowerUnPack(RewriterBase &rewriter, linalg::UnPackOp unPackOp, bool lowerUnpadLikeWithExtractSlice=true)
Rewrite pack as empty + transpose + reshape + extract_slice + copy.
void peelLoops(RewriterBase &rewriter, ArrayRef< scf::ForOp > loops)
Peel 'loops' and applies affine_min/max bounds simplification on the fly where relevant.
FailureOr< LinalgOp > specializeGenericOp(RewriterBase &rewriter, GenericOp genericOp, const GenericOpSpecializationOptions &options={})
Replace the given GenericOp with a namedOp or categoryOp.
FailureOr< ConvolutionDimensions > inferConvolutionDims(LinalgOp linalgOp)
Find at least 1 parallel (output_image) and reduction (filter_loop) dimension candidates that form a ...
FailureOr< GenericOp > generalizeNamedOp(RewriterBase &rewriter, LinalgOp linalgOp)
Create a GenericOp from the given named operation linalgOp and replace the given linalgOp.
void populateDecomposeConvolutionPatterns(RewritePatternSet &patterns, PatternBenefit benefit=1)
Linalg decompose convolutions patterns.
LogicalResult vectorizeCopy(RewriterBase &builder, memref::CopyOp copyOp)
Emit a suitable vector form for a Copy op with fully static shape.
FailureOr< GenericOp > interchangeGenericOp(RewriterBase &rewriter, GenericOp genericOp, ArrayRef< unsigned > interchangeVector)
Interchange the iterator_types and iterator_maps dimensions and adapts the index accesses of op.
SmallVector< int64_t > getPackInverseDestPerm(linalg::PackOp packOp, PackingMetadata &metadata)
Compute inverse permutation for the destination tensor (i.e.
void populateDecomposePackUnpackPatterns(RewritePatternSet &patterns)
Populates patterns to decompose linalg.pack and linalg.unpack Ops into e.g.
FailureOr< ContractionDimensions > inferContractionDims(LinalgOp linalgOp)
Find at least 2 parallel (m and n) and 1 reduction (k) dimension candidates that form a matmul subcom...
FailureOr< PackResult > packMatmulGreedily(RewriterBase &rewriter, LinalgOp linalgOp, ArrayRef< OpFoldResult > mnkPackedSizes, ArrayRef< int64_t > mnkPaddedSizesNextMultipleOf, ArrayRef< int64_t > mnkOrder)
Pack a LinalgOp by greedily inferring matmul dimensions (m, n, k) where m and n are proper parallel d...
FailureOr< PackResult > pack(RewriterBase &rewriter, linalg::LinalgOp linalgOp, ArrayRef< OpFoldResult > packedSizes)
Implement packing of a single LinalgOp by packedSizes.
SmallVector< Value > peelLoop(RewriterBase &rewriter, Operation *op)
Try to peel and canonicalize loop op and return the new result.
void populateDecomposePadPatterns(RewritePatternSet &patterns)
Populates patterns to decompose tensor.pad into e.g.
FailureOr< LinalgOp > downscaleSizeOneWindowedConvolution(RewriterBase &rewriter, LinalgOp op)
Rewrite convolution/pooling/depthwise ops with size-1 window dimensions into lower-dimensional ops.
FailureOr< LowerPackResult > lowerPack(RewriterBase &rewriter, linalg::PackOp packOp, bool lowerPadLikeWithInsertSlice=true)
Rewrite pack as pad + reshape + transpose.
LogicalResult peelForLoopAndSimplifyBounds(RewriterBase &rewriter, ForOp forOp, scf::ForOp &partialIteration)
Rewrite a for loop with bounds/step that potentially do not divide evenly into a for loop where the s...
FailureOr< TilingResult > bubbleUpPadSlice(OpBuilder &b, tensor::PadOp padOp, ArrayRef< OpFoldResult > offsets, ArrayRef< OpFoldResult > sizes, bool generateZeroSliceGuard=true)
Bubbles up a slice of this pad by taking the slice first and then performing the padding.
PadOp createPadHighOp(RankedTensorType resType, Value source, Value pad, bool nofold, Location loc, OpBuilder &builder, ValueRange dynOutDims={})
Definition Utils.cpp:23
Value createCanonicalRankReducingInsertSliceOp(OpBuilder &b, Location loc, Value tensor, Value dest)
Create a rank-reducing InsertSliceOp @[0 .
Value createCanonicalRankReducingExtractSliceOp(OpBuilder &b, Location loc, Value tensor, RankedTensorType targetType)
Create a rank-reducing ExtractSliceOp @[0 .
OpFoldResult getMixedSize(OpBuilder &builder, Location loc, Value value, int64_t dim)
Return the dimension of the given tensor value.
Definition TensorOps.cpp:60
SmallVector< OpFoldResult > getMixedSizes(OpBuilder &builder, Location loc, Value value)
Return the dimensions of the given tensor value.
Definition TensorOps.cpp:69
Include the generated interface declarations.
SliceVerificationResult
Enum that captures information related to verifier error conditions on slice insert/extract type of o...
ArrayRef< int64_t > ReassociationIndicesRef
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
void bindDims(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to DimExpr at positions: [0 .
Definition AffineExpr.h:311
SmallVector< int64_t > computePermutationVector(int64_t permSize, ArrayRef< int64_t > positions, ArrayRef< int64_t > desiredPositions)
Return a permutation vector of size permSize that would result in moving positions into desiredPositi...
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
void bindSymbols(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to SymbolExpr at positions: [0 .
Definition AffineExpr.h:325
SmallVector< Loops, 8 > tile(ArrayRef< scf::ForOp > forOps, ArrayRef< Value > sizes, ArrayRef< scf::ForOp > targets)
Performs tiling fo imperfectly nested loops (with interchange) by strip-mining the forOps by sizes an...
Definition Utils.cpp:1330
AffineExpr getAffineConstantExpr(int64_t constant, MLIRContext *context)
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
std::pair< int64_t, OpFoldResult > getSimplifiedOfrAndStaticSizePair(OpFoldResult ofr, Builder &b)
Given OpFoldResult representing dim size value (*), generates a pair of sizes:
void applyPermutationToVector(SmallVector< T, N > &inVec, ArrayRef< int64_t > permutation)
Apply the permutation defined by permutation to inVec.
AffineExpr getAffineDimExpr(unsigned position, MLIRContext *context)
These free functions allow clients of the API to not use classes in detail.
SliceVerificationResult isRankReducedType(ShapedType originalType, ShapedType candidateReducedType)
Check if originalType can be rank reduced to candidateReducedType type by dropping some dimensions wi...
bool isPermutationVector(ArrayRef< int64_t > interchange)
Method to check if an interchange vector is a permutation.
SmallVector< int64_t > invertPermutationVector(ArrayRef< int64_t > permutation)
Helper method to apply to inverse a permutation.
OpInterfaceRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting a...
Represents a range (offset, size, and stride) where each element of the triple may be dynamic or stat...
OpFoldResult size
LogicalResult matchAndRewrite(memref::CopyOp copyOp, PatternRewriter &rewriter) const override
Rewrites a linalg::PackOp into a sequence of:
LogicalResult matchAndRewrite(linalg::PackOp packOp, PatternRewriter &rewriter) const override
Rewrites a linalg::UnPackOp into a sequence of:
LogicalResult matchAndRewrite(linalg::UnPackOp unpackOp, PatternRewriter &rewriter) const override
Rewrite a tensor::PadOp into a sequence of EmptyOp, FillOp and InsertSliceOp.
LogicalResult matchAndRewrite(tensor::PadOp padOp, PatternRewriter &rewriter) const override
Value createFillOrGenerateOp(RewriterBase &rewriter, tensor::PadOp padOp, Value dest, const SmallVector< Value > &dynSizes) const
Filling dest using FillOp constant padding value if possible.
LogicalResult matchAndRewrite(tensor::ExtractSliceOp sliceOp, PatternRewriter &rewriter) const override
LinalgTilingOptions & setTileSizes(const SmallVector< Value, 4 > &ts)
Set the tileSizeComputationFunction to return the values ts.
Definition Transforms.h:204
TileSizeComputationFunction tileSizeComputationFunction
Computation function that returns the tile sizes for each operation.
Definition Transforms.h:194
Struct to hold the result of a pack call.
Struct to hold the result of a packTranspose call.