MLIR 24.0.0git
TilingInterfaceImpl.cpp
Go to the documentation of this file.
1//===- TilingInterfaceImpl.cpp - Implementation of TilingInterface -------===//
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
10
27#include "llvm/ADT/SmallVectorExtras.h"
28#include "llvm/Support/Debug.h"
29#include <optional>
30
31#define DEBUG_TYPE "linalg-tiling-interface-impl"
32
33using namespace mlir;
34using namespace mlir::linalg;
35
36//===----------------------------------------------------------------------===//
37// Utility methods for implementation of Tiling Interface for Linalg ops
38//===----------------------------------------------------------------------===//
39
40/// Return the SSA values that represent the data point accessed using a given
41/// `indexingMap` for a given point in the iteration space represented by `ivs`.
43 AffineMap indexingMap,
44 ValueRange ivs) {
46 indices.reserve(indexingMap.getNumResults());
47 for (auto result : indexingMap.getResults()) {
48 AffineMap m = AffineMap::get(indexingMap.getNumDims(),
49 indexingMap.getNumSymbols(), result);
50 Value v = affine::AffineApplyOp::create(b, loc, m, ivs);
51 indices.push_back(v);
52 }
53 return indices;
54}
55
56/// Method to inline the payload of a `linalgOp` given the iteration space
57/// point and values for the arguments of the payload.
58static LogicalResult inlinePayload(OpBuilder &b, LinalgOp linalgOp,
59 ValueRange ivs, ValueRange argValues) {
60 Block *body = linalgOp.getBlock();
61 IRMapping map;
62 map.map(body->getArguments(), argValues);
63 for (auto &op : body->without_terminator()) {
64 if (auto indexOp = dyn_cast<IndexOp>(&op)) {
65 map.map(indexOp.getResult(), ivs[indexOp.getDim()]);
66 continue;
67 }
68 b.clone(op, map);
69 }
70
71 Operation *terminator = body->getTerminator();
72 Location loc = terminator->getLoc();
73 for (const auto &operand : llvm::enumerate(terminator->getOperands())) {
74 Value toStore = map.lookupOrDefault(operand.value());
75 OpOperand *storeInto = linalgOp.getDpsInitOperand(operand.index());
77 b, loc, linalgOp.getMatchingIndexingMap(storeInto), ivs);
78 memref::StoreOp::create(b, loc, toStore,
79 linalgOp.getDpsInitOperand(operand.index())->get(),
80 indices);
81 }
82 return success();
83}
84
85/// Verify that tiling can be applied in presence of semi-affine maps.
86static LogicalResult
88 // Precompute each dimension's constant tile-size upper bound once.
89 // A failed entry marks a dynamic tile with no static bound.
90 SmallVector<FailureOr<int64_t>> tileSizeBounds =
91 llvm::map_to_vector(sizes, [](OpFoldResult size) {
94 /*stopCondition=*/nullptr, ValueBoundsOptions{/*closedUB=*/true});
95 });
96 SmallVector<int64_t> loopRanges = linalgOp.getStaticLoopRanges();
97
98 // Dynamic tiles or dynamic loop ranges are conservatively treated as tiled.
99 SmallVector<bool> tiledDims(loopRanges.size(), false);
100 for (auto [pos, tileSize] : llvm::enumerate(tileSizeBounds)) {
101 if (failed(tileSize)) {
102 tiledDims[pos] = true;
103 continue;
104 }
105 if (*tileSize == 0)
106 continue;
107 tiledDims[pos] =
108 ShapedType::isDynamic(loopRanges[pos]) || *tileSize < loopRanges[pos];
109 }
110
111 for (AffineMap map : linalgOp.getIndexingMapsArray()) {
112 for (AffineExpr result : map.getResults()) {
113 WalkResult status = result.walk([&](AffineExpr expr) -> WalkResult {
114 auto binExpr = dyn_cast<AffineBinaryOpExpr>(expr);
115 if (!binExpr)
116 return WalkResult::advance();
117 AffineExprKind kind = binExpr.getKind();
118 if (kind != AffineExprKind::Mod && kind != AffineExprKind::FloorDiv &&
120 return WalkResult::advance();
121
122 // Skip if the semi-affine expression does not involve any tiled
123 // dimension: an untiled dimension keeps its full extent in every tile,
124 // so re-applying the map on the slice is exact.
125 bool involvesTiledDim = expr.walk([&](AffineExpr e) -> WalkResult {
126 auto dim = dyn_cast<AffineDimExpr>(e);
127 if (dim && tiledDims[dim.getPosition()])
128 return WalkResult::interrupt();
129 return WalkResult::advance();
130 })
131 .wasInterrupted();
132 if (!involvesTiledDim)
133 return WalkResult::advance();
134
135 // Allow only `d OP C` map where `d` is a dimension and `C` is a
136 // constant. A compound LHS (e.g. `(d0 + d1)`, `(d0 * 2)`, a nested
137 // semi-affine expression) or a non-constant step is not provably safe,
138 // so reject it.
139 auto dimExpr = dyn_cast<AffineDimExpr>(binExpr.getLHS());
140 auto stepExpr = dyn_cast<AffineConstantExpr>(binExpr.getRHS());
141 if (!dimExpr || !stepExpr || stepExpr.getValue() <= 0) {
142 linalgOp.emitOpError()
143 << "tiling is not supported for the semi-affine indexing map: "
144 "only a single iteration dimension divided by a positive "
145 "constant step can be tiled over a tiled dimension";
146 return WalkResult::interrupt();
147 }
148
149 // Tiles are spaced by the full tile size, so tile origins are its
150 // multiples (0, tileSize, 2*tileSize, ...).
151 // A tile's indices are `origin + d'`, with `origin` the tile's start
152 // and `0 <= d' < tileSize`. A trailing partial tile is a full tile
153 // truncated at the same origin, spanning a subset of the same `d'`, so
154 // full-tile validity implies partial-tile validity and validating the
155 // upper-bound tile size suffices.
156 unsigned dimPos = dimExpr.getPosition();
157 FailureOr<int64_t> tileSize = tileSizeBounds[dimPos];
158
159 // Dynamic tile sizes are assumed to be valid.
160 // Unit tile is always valid.
161 if (failed(tileSize) || *tileSize == 1)
162 return WalkResult::advance();
163
164 // Tiled op reuses the same map on a slice whose base offset is
165 // `m(origin) - m(0)`, so it is correct only when
166 // `m(origin + d') == (m(origin) - m(0)) + m(d')` for every `d'`.
167 // Slice origins are tile-size multiples, so this reduces to a relation
168 // between the tile size and the step `C`:
169 // - `floordiv`/`mod` are locally affine within a step window (floordiv
170 // is constant, mod is linear), so they compose when the origin is
171 // step-aligned (`C | tileSize`) or the whole tile fits in one window
172 // (`tileSize | C`);
173 // - `ceildiv` jumps at `k * C + 1` instead of `k * C`, so a
174 // non-step-aligned origin already straddles the jump. It composes
175 // only from a step-aligned origin, i.e. `C | tileSize`.
176 int64_t step = stepExpr.getValue();
177 bool isCeil = kind == AffineExprKind::CeilDiv;
178 bool safe = *tileSize % step == 0 || (!isCeil && step % *tileSize == 0);
179 if (!safe) {
180 linalgOp.emitOpError()
181 << "tiling is not supported for the semi-affine indexing map: "
182 "tile size "
183 << *tileSize << " for dimension d" << dimPos
184 << (isCeil ? " must be a multiple of the step "
185 : " must divide or be divisible by the step ")
186 << step;
187 return WalkResult::interrupt();
188 }
189 return WalkResult::advance();
190 });
191 if (status.wasInterrupted())
192 return failure();
193 }
194 }
195 return success();
196}
197
198//===----------------------------------------------------------------------===//
199// External Model for implementing `TilingInterface` for `LinalgOp`s.
200//===----------------------------------------------------------------------===//
201
202namespace {
203/// External model implementation of TilingInterface for LinalgOps. An external
204/// model implementation is used for now till the use of `TilingInterface` is
205/// on-par with the current Linalg tiling + fusion patterns. Once it is
206/// maybe possible to move this into the op-definition (though there are
207/// advantages to leaving it as an external model)
208template <typename LinalgOpTy>
209struct LinalgOpTilingInterface
210 : public TilingInterface::ExternalModel<LinalgOpTilingInterface<LinalgOpTy>,
211 LinalgOpTy> {
212 using Base =
213 TilingInterface::ExternalModel<LinalgOpTilingInterface<LinalgOpTy>,
214 LinalgOpTy>;
215 // Inherit the defaulted hint-bearing overloads; these ops do not require the
216 // hint (no inner tiles).
217 using Base::generateResultTileValue;
218 using Base::getIterationDomainTileFromOperandTiles;
219 using Base::getTiledImplementation;
220 using Base::getTiledImplementationFromOperandTiles;
221
222 /// Return the loop iterator type.
223 SmallVector<utils::IteratorType> getLoopIteratorTypes(Operation *op) const {
224 LinalgOpTy concreteOp = cast<LinalgOpTy>(op);
225 return concreteOp.getIteratorTypesArray();
226 }
227
228 /// Return the iteration domain range.
229 SmallVector<Range> getIterationDomain(Operation *op, OpBuilder &b) const {
230 OpBuilder::InsertionGuard g(b);
231 b.setInsertionPoint(op);
232 Location loc = op->getLoc();
233 LinalgOp linalgOp = cast<LinalgOp>(op);
234 SmallVector<OpFoldResult> allShapesSizes =
235 linalgOp.createFlatListOfOperandDims(b, loc);
236 AffineMap map = linalgOp.getShapesToLoopsMap();
237
238 return llvm::map_to_vector(map.getResults(), [&](AffineExpr loopExpr) {
239 OpFoldResult ofr = affine::makeComposedFoldedAffineApply(b, loc, loopExpr,
240 allShapesSizes);
241 return Range{b.getIndexAttr(0), ofr, b.getIndexAttr(1)};
242 });
243 }
244
245 /// Instantiate the tiled implementation of the operation.
246 FailureOr<TilingResult>
249 ArrayRef<OpFoldResult> sizes) const {
250 // Leave the `sizeBounds` value empty. That is only needed when the `sizes`
251 // specified could lead to out of bounds accesses.
252 Location loc = op->getLoc();
253 LinalgOp linalgOp = cast<LinalgOp>(op);
254 // In case of a semi-affine expression, generalized tracking of tiles would
255 // require a per-tile-position shift that cannot be expressed by the
256 // symbol-free indexing maps.
257 // Thus, tiling is allowed only when the semi-affine maps can be proven safe
258 // for the current tiling configuration. Otherwise, tiling can end up
259 // producing incorrect results.
260 if (failed(validateTilingSemiAffineMaps(linalgOp, sizes)))
261 return failure();
262 SmallVector<Value> valuesToTile = linalgOp->getOperands();
263 SmallVector<Value> tiledOperands = makeTiledShapes(
264 b, loc, linalgOp, valuesToTile, offsets, sizes, {}, true);
265 SmallVector<Operation *> generatedSlices = llvm::map_to_vector(
266 llvm::make_filter_range(
267 tiledOperands,
268 [](Value v) -> bool {
269 return isa_and_nonnull<tensor::ExtractSliceOp, memref::SubViewOp>(
270 v.getDefiningOp());
271 }),
272 [](Value v) -> Operation * { return v.getDefiningOp(); });
273
274 SmallVector<Type> resultTensorTypes =
275 getTensorOutputTypes(linalgOp, tiledOperands);
276
277 Operation *tiledOp = clone(b, linalgOp, resultTensorTypes, tiledOperands);
278 offsetIndices(b, cast<LinalgOp>(tiledOp), offsets);
279
280 return TilingResult{
281 {tiledOp}, SmallVector<Value>(tiledOp->getResults()), generatedSlices};
282 }
283
284 /// Utility to fetch the offsets and sizes when applied as per the indexing
285 /// map of the linalg op. This helps in fusing the linalg op as a consumer of
286 /// a given slice op.
287 static LogicalResult
288 getMappedOffsetAndSize(LinalgOp linalgOp, OpBuilder &b,
289 ArrayRef<AffineMap> indexingMaps,
292 SmallVectorImpl<OpFoldResult> &mappedOffsetsVec,
293 SmallVectorImpl<OpFoldResult> &mappedSizesVec) {
294 DenseMap<unsigned, OpFoldResult> mappedOffsets, mappedSizes;
295
296 for (auto [indexingMap, offsets, sizes] :
297 llvm::zip_equal(indexingMaps, allOffsets, allSizes)) {
298 for (auto [resultExpr, offset, size] :
299 llvm::zip_equal(indexingMap.getResults(), offsets, sizes)) {
300 auto dimExpr = dyn_cast<AffineDimExpr>(resultExpr);
301 if (!dimExpr)
302 return failure();
303 unsigned position = dimExpr.getPosition();
304 auto it = mappedOffsets.find(position);
305 if (it != mappedOffsets.end()) {
306 OpFoldResult seenOffset = it->second;
307 OpFoldResult seenSize = mappedSizes.lookup(position);
308 if (seenOffset != offset || seenSize != size) {
309 LLVM_DEBUG({
310 llvm::dbgs() << "inconsistent iteration space mapping from "
311 "offsets/sizes of operands/results";
312 });
313 return failure();
314 }
315 } else {
316 mappedOffsets[position] = offset;
317 mappedSizes[position] = size;
318 }
319 }
320 }
321
322 // Aggregate from the given operand offsets and sizes, or default to
323 // iteration space values.
324 SmallVector<Range> iterationDomain =
325 cast<TilingInterface>(linalgOp.getOperation()).getIterationDomain(b);
326 mappedOffsetsVec.resize(iterationDomain.size());
327 mappedSizesVec.resize(iterationDomain.size());
328 for (auto [index, domain] : llvm::enumerate(iterationDomain)) {
329 auto it = mappedOffsets.find(index);
330 if (it != mappedOffsets.end()) {
331 mappedOffsetsVec[index] = it->second;
332 mappedSizesVec[index] = mappedSizes.lookup(index);
333 continue;
334 }
335 mappedOffsetsVec[index] = domain.offset;
336 mappedSizesVec[index] = domain.size;
337 }
338 return success();
339 }
340
341 /// Method to return the position of the result tile computed by the tiled
342 /// operation.
343 LogicalResult getIterationDomainTileFromOperandTiles(
344 Operation *op, OpBuilder &b, ArrayRef<unsigned> operandNumbers,
347 SmallVectorImpl<OpFoldResult> &iterDomainOffsets,
348 SmallVectorImpl<OpFoldResult> &iterDomainSizes) const {
349 auto linalgOp = cast<LinalgOp>(op);
350
351 SmallVector<AffineMap> indexingMaps =
352 llvm::map_to_vector(operandNumbers, [&](unsigned operandNumber) {
353 OpOperand &opOperand = linalgOp->getOpOperand(operandNumber);
354 return linalgOp.getMatchingIndexingMap(&opOperand);
355 });
356 if (failed(getMappedOffsetAndSize(linalgOp, b, indexingMaps, allOffsets,
357 allSizes, iterDomainOffsets,
358 iterDomainSizes))) {
359 return failure();
360 }
361 return success();
362 }
363
364 /// Return the details of the output tile generated by the tiled
365 /// implementation.
366 LogicalResult
367 getResultTilePosition(Operation *op, OpBuilder &b, unsigned resultNumber,
370 SmallVector<OpFoldResult> &resultOffsets,
371 SmallVector<OpFoldResult> &resultSizes) const {
372 Location loc = op->getLoc();
373 LinalgOp linalgOp = cast<LinalgOp>(op);
374
375 AffineExpr d0;
376 bindDims(b.getContext(), d0);
377 SmallVector<OpFoldResult> subShapeSizes =
378 llvm::map_to_vector(sizes, [&](OpFoldResult ofr) {
379 return affine::makeComposedFoldedAffineApply(b, loc, d0 - 1, ofr);
380 });
381
382 OpOperand *outOperand = linalgOp.getDpsInitOperand(resultNumber);
384 b, loc, outOperand->get(), sizes,
385 linalgOp.getMatchingIndexingMap(outOperand), offsets,
386 /*ubs*/ {}, subShapeSizes, true);
387 resultOffsets = sliceParams.offsets;
388 resultSizes = sliceParams.sizes;
389 return success();
390 }
391
392 LogicalResult getIterationDomainTileFromResultTile(
393 Operation *op, OpBuilder &b, unsigned resultNumber,
395 SmallVectorImpl<OpFoldResult> &iterDomainOffsets,
396 SmallVectorImpl<OpFoldResult> &iterDomainSizes) const {
397 auto linalgOp = cast<LinalgOp>(op);
398
399 // Check that the indexing map used for the output is a projected
400 // permutation. This could be relaxed with a more general approach that can
401 // map the offsets and sizes from the result to iteration space tiles
402 // (filling in full extent for dimensions not used to access the result).
403 AffineMap indexingMap =
404 linalgOp.getIndexingMapMatchingResult(op->getResult(resultNumber));
405 if (!indexingMap.isProjectedPermutation()) {
406 return op->emitOpError(
407 "unhandled tiled implementation generation when result is not "
408 "accessed using a permuted projection");
409 }
410
411 SmallVector<OpFoldResult> allOffsets = llvm::to_vector(offsets);
412 SmallVector<OpFoldResult> allSizes = llvm::to_vector(sizes);
413 auto status =
414 getMappedOffsetAndSize(linalgOp, b, indexingMap, {allOffsets},
415 {allSizes}, iterDomainOffsets, iterDomainSizes);
416 (void)status;
417 assert(succeeded(status) && "unexpected error in offset calculation");
418 return success();
419 }
420
421 FailureOr<TilingResult>
422 generateResultTileValue(Operation *op, OpBuilder &b, unsigned resultNumber,
424 ArrayRef<OpFoldResult> sizes) const {
425 SmallVector<OpFoldResult> mappedOffsets, mappedSizes;
426 if (failed(getIterationDomainTileFromResultTile(
427 op, b, resultNumber, offsets, sizes, mappedOffsets, mappedSizes))) {
428 return failure();
429 }
430 auto tilingInterfaceOp = cast<TilingInterface>(op);
431 FailureOr<TilingResult> tilingResult =
432 tilingInterfaceOp.getTiledImplementation(b, mappedOffsets, mappedSizes);
433
434 if (failed(tilingResult))
435 return failure();
436
437 if (tilingResult->tiledOps.size() != 1)
438 return op->emitOpError("failed to generate tiled implementation");
439
440 return TilingResult{
441 tilingResult->tiledOps,
442 SmallVector<Value>{tilingResult->tiledValues[resultNumber]},
443 tilingResult->generatedSlices};
444 }
445
446 /// Method to generate the tiled implementation of an operation from the tile
447 /// of the operand.
448 FailureOr<TilingResult> getTiledImplementationFromOperandTiles(
449 Operation *op, OpBuilder &b, ArrayRef<unsigned> operandNumbers,
451 ArrayRef<SmallVector<OpFoldResult>> allSizes) const {
452 SmallVector<OpFoldResult> mappedOffsets, mappedSizes;
453 if (failed(getIterationDomainTileFromOperandTiles(
454 op, b, operandNumbers, allOffsets, allSizes, mappedOffsets,
455 mappedSizes))) {
456 return failure();
457 }
458 return getTiledImplementation(op, b, mappedOffsets, mappedSizes);
459 }
460
461 LogicalResult generateScalarImplementation(Operation *op, OpBuilder &builder,
462 Location loc,
463 ValueRange ivs) const {
464 auto linalgOp = cast<LinalgOp>(op);
465 if (!linalgOp.hasPureBufferSemantics())
466 return op->emitOpError("expected operation to have buffer semantics");
467
468 SmallVector<Value> indexedValues;
469 indexedValues.reserve(linalgOp->getNumOperands());
470 Location linalgOpLoc = op->getLoc();
471 /// Load the data corresponding to the block arguments that
472 /// represent input operands.
473 for (OpOperand &operand : linalgOp->getOpOperands()) {
474 if (!linalgOp.payloadUsesValueFromOperand(&operand)) {
475 indexedValues.push_back(nullptr);
476 continue;
477 }
478 if (linalgOp.isScalar(&operand)) {
479 indexedValues.push_back(operand.get());
480 continue;
481 }
483 builder, linalgOpLoc, linalgOp.getMatchingIndexingMap(&operand), ivs);
484 Value load =
485 memref::LoadOp::create(builder, linalgOpLoc, operand.get(), indices);
486 indexedValues.push_back(load);
487 }
488
489 /// Inline the op payload and store the result.
490 return inlinePayload(builder, linalgOp, ivs, indexedValues);
491 }
492
493 bool isOpFusableWithConsumerSlice(Operation *op, unsigned resultNumber,
495 ArrayRef<OpFoldResult> sizes) const {
496 // The verifier gives all the necessary requirements for consumer fusion.
497 return true;
498 }
499
500 bool isOpFusableWithProducerSlices(
501 Operation *op, ArrayRef<unsigned> operandNumbers,
503 ArrayRef<SmallVector<OpFoldResult>> allSizes) const {
504
505 auto linalgOp = cast<LinalgOp>(op);
506 SmallVector<AffineMap> indexingMaps =
507 llvm::map_to_vector(operandNumbers, [&](unsigned operandNumber) {
508 OpOperand &opOperand = linalgOp->getOpOperand(operandNumber);
509 return linalgOp.getMatchingIndexingMap(&opOperand);
510 });
511 // Check that offsets/sizes are consistent across all operands.
512 OpBuilder b(op);
513 SmallVector<OpFoldResult> mappedOffsets, mappedSizes;
514 return succeeded(getMappedOffsetAndSize(linalgOp, b, indexingMaps,
515 allOffsets, allSizes, mappedOffsets,
516 mappedSizes));
517 }
518};
519
520//===----------------------------------------------------------------------===//
521// External Model for implementing `PartialReductionInterface` for `LinalgOp`s.
522//===----------------------------------------------------------------------===//
523
524/// In a given set vector, get the position of a particular element.
525std::optional<int> getPositionIn(const llvm::SetVector<unsigned> &reductionDims,
526 unsigned value) {
527 for (auto [index, reductionDim] : llvm::enumerate(reductionDims)) {
528 if (reductionDim == value) {
529 return index;
530 }
531 }
532 return std::nullopt;
533}
534
535/// Return an AffineMaps to use for the `outs` operands of the linalg op
536/// generated for partial results. The new AffineMap is the AffineMap of the
537/// untiled op with reduction dimensions appended at end in order in which they
538/// were specified during tiling.
540getPartialResultAffineMaps(LinalgOp linalgOp,
541 const SetVector<unsigned> &reductionDims) {
542 auto partialReductionMaps = llvm::map_to_vector(
543 linalgOp.getDpsInitsMutable(), [&](OpOperand &opOperand) {
544 AffineMap map = linalgOp.getMatchingIndexingMap(&opOperand);
545 for (auto redPos : reductionDims) {
546 map =
547 map.insertResult(getAffineDimExpr(redPos, linalgOp.getContext()),
548 map.getNumResults());
549 }
550 return map;
551 });
552 return partialReductionMaps;
553}
554
555struct InitSliceInfo {
556 SmallVector<int64_t> resultShape;
557 SmallVector<OpFoldResult> offsets;
558 SmallVector<OpFoldResult> sizes;
559 SmallVector<OpFoldResult> strides;
560};
561
562/// Return the result shape, offsets, sizes and strides of the slice of the
563/// `initValue` to use as the destination of the partial reduction op generated
564/// with outer reduction strategy.
565static InitSliceInfo getInitSliceInfoForOuterReduction(
566 MLIRContext *context, ArrayRef<OpFoldResult> offsets,
567 ArrayRef<OpFoldResult> sizes, const SetVector<unsigned> &reductionDims,
568 ArrayRef<OpFoldResult> splitReductionIvs, AffineMap partialReductionMap,
569 ArrayRef<OpFoldResult> initOperandShape) {
570 int64_t initRank = partialReductionMap.getNumResults();
571 SmallVector<OpFoldResult> initOffsets, initSizes;
572 Attribute zero = IntegerAttr::get(IndexType::get(context), 0);
573 Attribute one = IntegerAttr::get(IndexType::get(context), 1);
574 SmallVector<OpFoldResult> initStrides(initRank, one);
575 for (auto [resultIdx, dimExpr] :
576 llvm::enumerate(partialReductionMap.getResults())) {
577 if (isa<AffineConstantExpr>(dimExpr)) {
578 // A constant index in the output map accesses a fixed position; keep
579 // the full output dimension to match the original output operand shape.
580 initOffsets.push_back(zero);
581 initSizes.push_back(initOperandShape[resultIdx]);
582 continue;
583 }
584 unsigned dim = cast<AffineDimExpr>(dimExpr).getPosition();
585 if (reductionDims.contains(dim)) {
586 initOffsets.push_back(zero);
587 } else {
588 initOffsets.push_back(offsets[dim]);
589 }
590 initSizes.push_back(sizes[dim]);
591 }
592 SmallVector<int64_t> resultShape;
593 std::tie(resultShape, std::ignore) = decomposeMixedValues(initSizes);
594 return {resultShape, initOffsets, initSizes, initStrides};
595}
596
597/// Return the result shape, offsets, sizes and strides of the slice of the
598/// `initValue` to use as destination of the partial reduction op generated with
599/// outer parallel strategy.
600static InitSliceInfo getInitSliceInfoForOuterParallel(
601 MLIRContext *context, ArrayRef<OpFoldResult> offsets,
602 ArrayRef<OpFoldResult> sizes, const SetVector<unsigned> &reductionDims,
603 ArrayRef<OpFoldResult> splitReductionIvs, AffineMap partialReductionMap,
604 ArrayRef<OpFoldResult> initOperandShape) {
605 int64_t initRank = partialReductionMap.getNumResults();
606 SmallVector<OpFoldResult> initOffsets, initSizes;
607 Attribute zero = IntegerAttr::get(IndexType::get(context), 0);
608 Attribute one = IntegerAttr::get(IndexType::get(context), 1);
609 SmallVector<OpFoldResult> initStrides(initRank, one);
610 SmallVector<OpFoldResult> resultShape;
611 for (auto [resultIdx, dimExpr] :
612 llvm::enumerate(partialReductionMap.getResults())) {
613 if (isa<AffineConstantExpr>(dimExpr)) {
614 // A constant index accesses a fixed position; keep the full output
615 // dimension to match the original output operand shape.
616 initOffsets.push_back(zero);
617 initSizes.push_back(initOperandShape[resultIdx]);
618 resultShape.push_back(initOperandShape[resultIdx]);
619 continue;
620 }
621 unsigned dim = cast<AffineDimExpr>(dimExpr).getPosition();
622 if (std::optional<unsigned> dimPos = getPositionIn(reductionDims, dim)) {
623 initOffsets.push_back(splitReductionIvs[dimPos.value()]);
624 initSizes.push_back(one);
625 } else {
626 initOffsets.push_back(offsets[dim]);
627 initSizes.push_back(sizes[dim]);
628 resultShape.push_back(sizes[dim]);
629 }
630 }
631 SmallVector<int64_t> staticShapes;
632 std::tie(staticShapes, std::ignore) = decomposeMixedValues(resultShape);
633 return {staticShapes, initOffsets, initSizes, initStrides};
634}
635
636/// Return the result shape, offsets, sizes and strides of the slice of the
637/// `initValue` to use as destination of the partial reduction op.
638static InitSliceInfo getInitSliceInfo(MLIRContext *context,
642 const SetVector<unsigned> &reductionDims,
643 ArrayRef<OpFoldResult> splitReductionIvs,
644 AffineMap partialReductionMap,
645 ArrayRef<OpFoldResult> initOperandShape) {
647 return getInitSliceInfoForOuterReduction(
648 context, offsets, sizes, reductionDims, splitReductionIvs,
649 partialReductionMap, initOperandShape);
650 }
652 "unexpected ReductionTilingStrategy");
653 return getInitSliceInfoForOuterParallel(
654 context, offsets, sizes, reductionDims, splitReductionIvs,
655 partialReductionMap, initOperandShape);
656}
657
658/// External model implementation of PartialReductionInterface for
659/// LinalgOps.
660template <typename LinalgOpTy>
661struct LinalgOpPartialReductionInterface
662 : public PartialReductionOpInterface::ExternalModel<
663 LinalgOpPartialReductionInterface<LinalgOpTy>, LinalgOpTy> {
664 FailureOr<SmallVector<Value>> generateInitialTensorForPartialReduction(
665 Operation *op, OpBuilder &b, Location loc, ArrayRef<OpFoldResult> sizes,
666 const SetVector<unsigned> &reductionDims) const {
667 auto linalgOp = cast<LinalgOp>(op);
668
669 OpBuilder::InsertionGuard guard(b);
670 if (linalgOp.hasPureBufferSemantics())
671 return op->emitOpError("expected operation to have tensor semantics");
672
673 SmallVector<AffineMap> partialResultMaps =
674 getPartialResultAffineMaps(linalgOp, reductionDims);
675
676 SmallVector<Value> inits;
677 for (auto [initIdx, result, partialMap] :
678 llvm::enumerate(linalgOp->getResults(), partialResultMaps)) {
679 SmallVector<Operation *, 4> combinerOps;
680 if (!matchReduction(linalgOp.getRegionOutputArgs(), initIdx,
681 combinerOps) ||
682 combinerOps.size() != 1)
683 return op->emitOpError("Failed to anaysis the reduction operation.");
684
685 Operation *reductionOp = combinerOps[0];
686 std::optional<TypedAttr> identity = arith::getNeutralElement(reductionOp);
687 if (!identity.has_value())
688 return op->emitOpError(
689 "Failed to get an identity value for the reduction operation.");
690
691 // Append the new partial result dimensions.
692 SmallVector<OpFoldResult> partialResultShape;
693 Value initValue = linalgOp.getDpsInits()[initIdx];
694 SmallVector<OpFoldResult> initShape =
695 tensor::getMixedSizes(b, loc, initValue);
696 for (auto [resultIdx, dimExpr] :
697 llvm::enumerate(partialMap.getResults())) {
698 if (isa<AffineConstantExpr>(dimExpr)) {
699 // A constant index in the output map accesses a fixed position; use
700 // the actual output dimension size (not a hardcoded 1).
701 partialResultShape.push_back(initShape[resultIdx]);
702 continue;
703 }
704 auto dim = cast<AffineDimExpr>(dimExpr);
705 partialResultShape.push_back(sizes[dim.getPosition()]);
706 }
707
708 Type elType = getElementTypeOrSelf(result.getType());
709 Value emptyTensor =
710 tensor::EmptyOp::create(b, loc, partialResultShape, elType);
711 Value constantOp = arith::ConstantOp::create(b, loc, *identity);
712 auto identityTensor =
713 linalg::FillOp::create(b, loc, constantOp, emptyTensor);
714 inits.push_back(identityTensor.getResult(0));
715 }
716
717 return inits;
718 }
719
720 FailureOr<TilingResult>
721 tileToPartialReduction(Operation *op, OpBuilder &b, Location loc,
722 ReductionTilingStrategy tilingStrategy,
723 ValueRange init, ArrayRef<OpFoldResult> offsets,
724 ArrayRef<OpFoldResult> sizes,
725 const SetVector<unsigned> &reductionDims,
726 ArrayRef<OpFoldResult> splitReductionIvs) const {
727 OpBuilder::InsertionGuard guard(b);
728 auto linalgOp = cast<LinalgOp>(op);
729
730 SmallVector<AffineMap> partialReductionMaps =
731 getPartialResultAffineMaps(linalgOp, reductionDims);
732
733 // Step 1. Extend init maps to have reduction dimension dims, since we
734 // are converting them to parallel dimensions.
735 SmallVector<AffineMap> newInitMaps;
736 if (tilingStrategy ==
737 ReductionTilingStrategy::PartialReductionOuterReduction) {
738 newInitMaps = llvm::to_vector(partialReductionMaps);
739 } else {
740 newInitMaps = llvm::map_to_vector(
741 linalgOp.getDpsInitsMutable(), [&](OpOperand &opOperand) {
742 return linalgOp.getMatchingIndexingMap(&opOperand);
743 });
744 }
745
746 // Step 2a: Extract a slice of the input operands.
747 SmallVector<Value> tiledInputs = makeTiledShapes(
748 b, loc, linalgOp, linalgOp.getDpsInputs(), offsets, sizes, {}, true);
749 SmallVector<Operation *> generatedSlices = llvm::map_to_vector(
750 llvm::make_filter_range(
751 tiledInputs, [](Value v) -> bool { return v.getDefiningOp(); }),
752 [](Value v) -> Operation * { return v.getDefiningOp(); });
753
754 // Step 2b: Extract a slice of the init operands.
755 SmallVector<Value, 1> tiledInits;
756 for (auto [partialReductionMap, valueToTile, initOperandValue] :
757 llvm::zip_equal(partialReductionMaps, init, linalgOp.getDpsInits())) {
758 // Compute the actual shape of the original init operand for handling
759 // constant expressions in the partial reduction map.
760 SmallVector<OpFoldResult> initOperandShape =
761 tensor::getMixedSizes(b, loc, initOperandValue);
762 InitSliceInfo sliceInfo = getInitSliceInfo(
763 b.getContext(), tilingStrategy, offsets, sizes, reductionDims,
764 splitReductionIvs, partialReductionMap, initOperandShape);
765 auto valueToTileType = cast<RankedTensorType>(valueToTile.getType());
766 RankedTensorType sliceResultType = RankedTensorType::get(
767 sliceInfo.resultShape, valueToTileType.getElementType(),
768 valueToTileType.getEncoding());
769 auto sliceOp = tensor::ExtractSliceOp::create(
770 b, loc, sliceResultType, valueToTile, sliceInfo.offsets,
771 sliceInfo.sizes, sliceInfo.strides);
772 tiledInits.push_back(sliceOp.getResult());
773 generatedSlices.push_back(sliceOp);
774 }
775
776 // Update the indexing maps.
777 SmallVector<AffineMap> newMaps = linalgOp.getIndexingMapsArray();
778 for (auto [initOperand, newInitMap] :
779 llvm::zip_equal(linalgOp.getDpsInitsMutable(), newInitMaps)) {
780 int mapIdx = linalgOp.getIndexingMapIndex(&initOperand);
781 newMaps[mapIdx] = newInitMap;
782 }
783
784 // Step 3. Change the reduction dim iterator types.
785 SmallVector<utils::IteratorType> newIteratorTypes =
786 linalgOp.getIteratorTypesArray();
787 if (tilingStrategy ==
788 ReductionTilingStrategy::PartialReductionOuterReduction) {
789 for (int dim : reductionDims)
790 newIteratorTypes[dim] = utils::IteratorType::parallel;
791 }
792
793 // Step 4. Create the new generic op.
794 Operation *partialReductionOp;
795 auto resultTypes = ValueRange(tiledInits).getTypes();
796 if (tilingStrategy ==
797 ReductionTilingStrategy::PartialReductionOuterReduction) {
798 auto genericOp = GenericOp::create(b, loc, resultTypes, tiledInputs,
799 tiledInits, newMaps, newIteratorTypes);
800 IRMapping mapping;
801 op->getRegion(0).cloneInto(&genericOp.getRegion(),
802 genericOp.getRegion().begin(), mapping);
803 offsetIndices(b, genericOp, offsets);
804 partialReductionOp = genericOp.getOperation();
805 } else {
806 SmallVector<Value> operands = std::move(tiledInputs);
807 llvm::append_range(operands, tiledInits);
808 partialReductionOp = mlir::clone(b, op, resultTypes, operands);
809 offsetIndices(b, cast<LinalgOp>(partialReductionOp), offsets);
810 }
811 return TilingResult{
812 {partialReductionOp},
813 llvm::map_to_vector(partialReductionOp->getResults(),
814 [](OpResult r) -> Value { return r; }),
815 generatedSlices};
816 }
817
818 FailureOr<MergeResult>
819 mergeReductions(Operation *op, OpBuilder &b, Location loc,
820 ValueRange partialReduce,
821 const SetVector<unsigned> &reductionDims) const {
822 auto linalgOp = cast<LinalgOp>(op);
823 SmallVector<AffineMap> partialReductionMaps =
824 getPartialResultAffineMaps(linalgOp, reductionDims);
825
826 // Permute the reduction dims as permuted by the partial result map.
827 SmallVector<Operation *> mergeOperations;
828 SmallVector<Value> replacements;
829 for (auto [idx, init, partialResult, partialMap] : llvm::enumerate(
830 linalgOp.getDpsInits(), partialReduce, partialReductionMaps)) {
831 unsigned initIdx = idx;
832 // linalg.reduce's iteration space is the tiled result's iteration space
833 // (and not the tiled operation's iteration space). To account for this,
834 // permute the reduction dimensions based on the partial result map of the
835 // tiled result.
836 SmallVector<int64_t> partialReductionDims;
837 for (auto [resultNum, dimExpr] :
838 llvm::enumerate(partialMap.getResults())) {
839 if (isa<AffineConstantExpr>(dimExpr))
840 continue; // Constant dims are never reduction dims.
841 unsigned dim = cast<AffineDimExpr>(dimExpr).getPosition();
842 if (llvm::is_contained(reductionDims, dim)) {
843 partialReductionDims.push_back(resultNum);
844 }
845 }
846
847 auto reduction = linalg::ReduceOp::create(
848 b, loc, partialResult, init, partialReductionDims,
849 [&linalgOp, &initIdx](OpBuilder &b, Location loc, ValueRange inputs) {
850 // Get the combiner op.
851 SmallVector<Operation *, 4> combinerOps;
852 matchReduction(linalgOp.getRegionOutputArgs(), initIdx,
853 combinerOps);
854 Operation *clonedReductionOp = b.clone(*combinerOps[0]);
855 // Combine the input at idx and output at numInits + idx.
856 clonedReductionOp->setOperand(0, inputs[0]);
857 clonedReductionOp->setOperand(1, inputs[1]);
858 linalg::YieldOp::create(b, loc, clonedReductionOp->getResult(0));
859 });
860
861 mergeOperations.push_back(reduction);
862 replacements.push_back(reduction->getResult(0));
863 }
864
865 return MergeResult{mergeOperations, replacements};
866 }
867
868 LogicalResult getPartialResultTilePosition(
869 Operation *op, OpBuilder &b, unsigned resultNumber,
870 ReductionTilingStrategy tilingStrategy, ArrayRef<OpFoldResult> offsets,
871 ArrayRef<OpFoldResult> sizes, const SetVector<unsigned> &reductionDims,
872 ArrayRef<OpFoldResult> splitReductionIvs,
873 SmallVector<OpFoldResult> &resultOffsets,
874 SmallVector<OpFoldResult> &resultSizes) const {
875 auto linalgOp = cast<LinalgOp>(op);
876 SmallVector<AffineMap> partialReductionMaps =
877 getPartialResultAffineMaps(linalgOp, reductionDims);
878 // Compute the actual shape of the init operand for handling constant
879 // expressions in the partial reduction map.
880 Value initOperandValue = linalgOp.getDpsInits()[resultNumber];
881 Location loc = op->getLoc();
882 SmallVector<OpFoldResult> initOperandShape =
883 tensor::getMixedSizes(b, loc, initOperandValue);
884 InitSliceInfo sliceInfo =
885 getInitSliceInfo(b.getContext(), tilingStrategy, offsets, sizes,
886 reductionDims, splitReductionIvs,
887 partialReductionMaps[resultNumber], initOperandShape);
888 std::swap(resultOffsets, sliceInfo.offsets);
889 std::swap(resultSizes, sliceInfo.sizes);
890
891 return success();
892 }
893};
894
895template <typename OpTy>
896static SmallVector<Range> getPackUnPackIterationDomain(OpTy op,
897 OpBuilder &builder) {
898 static_assert(llvm::is_one_of<OpTy, PackOp, UnPackOp>::value,
899 "applies to only pack or unpack operations");
900 OpBuilder::InsertionGuard g(builder);
901 int64_t rank = (std::is_same<OpTy, PackOp>::value) ? op.getSourceRank()
902 : op.getDestRank();
903 OpFoldResult zero = builder.getIndexAttr(0);
904 OpFoldResult one = builder.getIndexAttr(1);
905 ReifiedRankedShapedTypeDims resultShape;
906 (void)op.reifyResultShapes(builder, resultShape);
907 SmallVector<Range> loopBounds(rank);
908 for (auto dim : llvm::seq<int64_t>(0, rank)) {
909 loopBounds[dim].offset = zero;
910 loopBounds[dim].stride = one;
911 loopBounds[dim].size = resultShape[0][dim];
912 }
913 return loopBounds;
914}
915
916static void applyPermToRange(SmallVector<OpFoldResult> &offsets,
918 ArrayRef<int64_t> permutation) {
919 if (permutation.empty())
920 return;
921 applyPermutationToVector<OpFoldResult>(offsets, permutation);
922 applyPermutationToVector<OpFoldResult>(sizes, permutation);
923}
924
925/// Compute the permutation vector to interchange `elements` such that the
926/// elements at positions in `dimsPos` are moved to the positions `[0, ...,
927/// dimsPos.size())` in order.
929computeInterchangeFromDimPos(ArrayRef<int64_t> dimsPos, int64_t rank) {
930 SmallVector<int64_t> interchangeVector;
931 interchangeVector.reserve(dimsPos.size());
932 // First map dims and their position. For example, dims_pos = [2, 0] will map
933 // to:
934 // [
935 // [ key: 2, value: 0]
936 // [ key: 0, value: 1]
937 // ]
938 // where key is the idx in dims_pos while value its position in dims_pos.
939 DenseMap<int64_t, int64_t> dimsAndPosMapping;
940 for (int64_t dimsIdx = 0, end = dimsPos.size(); dimsIdx < end; dimsIdx++)
941 dimsAndPosMapping[dimsPos[dimsIdx]] = dimsIdx;
942
943 // Scan the position in order and insert the value in the map
944 // to compute the interchange vector.
945 for (int64_t dimsIdx = 0; dimsIdx < rank; dimsIdx++) {
946 if (dimsAndPosMapping.count(dimsIdx))
947 interchangeVector.push_back(dimsAndPosMapping[dimsIdx]);
948 }
949 return interchangeVector;
950}
951
952/// Permute the elements of `vec` starting at position `offset` according to
953/// `interchangeVector`. The permutation maps position `i` in the permuted range
954/// to position `interchangeVector[i]` in the original range. Elements before
955/// `offset` are unchanged.
956///
957/// Example: interchange([a, b, c, d, e], [2, 0, 1], offset=2)
958/// returns [a, b, e, c, d] (permutes the suffix [c, d, e])
959///
960/// Note: This is similar to `applyPermutationToVector` but supports an offset
961/// for permuting a suffix of the vector. It is only used for pack/unpack scalar
962/// implementation where we need to permute inner tile dimensions which are
963/// stored at the end of the index vector.
964template <typename T>
965static SmallVector<T> interchange(ArrayRef<T> elements,
966 ArrayRef<int64_t> interchangeVector,
967 int offset = 0) {
968 SmallVector<T> vec = llvm::to_vector(elements);
969 for (auto [idx, val] : llvm::enumerate(interchangeVector))
970 vec[idx + offset] = elements[val + offset];
971 return vec;
972}
973
974/// Generate the body of the innermost loop of the scalar implementation
975/// of `pack` operation.
976static void generatePackOpScalarImplementationBody(PackOp packOp,
977 OpBuilder &builder,
978 Location loc,
979 ValueRange ivs) {
980 // Note: `ivs` are already in the correct order, possibly interchanged based
981 // on `dims_pos`. However, connecting the loops with the access patterns is
982 // difficult - What is the relation between the position of the tile loop and
983 // the point loop? However, if we interchange `ivs` once more to go to the
984 // canonical blocking format: ABCabc, this connection becomes trivial: Each
985 // point loop is pointLoopsOffset + inputRank away from the tiled loop.
986 ArrayRef<int64_t> dimsToInnerBlock = packOp.getInnerDimsPos();
987 ArrayRef<int64_t> dimsToOuterBlock = packOp.getOuterDimsPerm();
988
989 SmallVector<Value> interchangedIvs = ivs;
990 SmallVector<int64_t> interchangeVector =
991 computeInterchangeFromDimPos(dimsToInnerBlock, packOp.getSourceRank());
992 interchangedIvs = interchange<Value>(interchangedIvs, interchangeVector,
993 /*offset=*/packOp.getSourceRank());
994 if (!dimsToOuterBlock.empty()) {
995 interchangeVector =
996 computeInterchangeFromDimPos(dimsToOuterBlock, packOp.getSourceRank());
997 interchangedIvs =
998 interchange<Value>(interchangedIvs, interchangeVector, /*offset=*/0);
999 }
1000 DenseMap<int64_t, OpFoldResult> dimAndTileMapping =
1001 packOp.getDimAndTileMapping();
1002 SmallVector<OpFoldResult> sourceIndices;
1003 size_t pointLoopsOffset = 0;
1004 int64_t sourceRank = packOp.getSourceRank();
1005 for (auto dim : llvm::seq<int64_t>(0, sourceRank)) {
1006 if (dimAndTileMapping.contains(dim)) {
1007 AffineExpr i, j, tile;
1008 bindDims(builder.getContext(), i, j);
1009 bindSymbols(builder.getContext(), tile);
1011 builder, loc, i * tile + j,
1013 interchangedIvs[dim],
1014 interchangedIvs[pointLoopsOffset + packOp.getSourceRank()],
1015 dimAndTileMapping[dim]});
1016 sourceIndices.push_back(sourceIndex);
1017 ++pointLoopsOffset;
1018 } else {
1019 sourceIndices.push_back(interchangedIvs[dim]);
1020 }
1021 }
1022
1023 auto createLoad = [&]() -> Value {
1024 return memref::LoadOp::create(
1025 builder, loc, packOp.getSource(),
1026 getValueOrCreateConstantIndexOp(builder, loc, sourceIndices));
1027 };
1028 Value scalar;
1029 if (auto paddingValue = packOp.getPaddingValue()) {
1030 ArithBuilder arithBuilder(builder, loc);
1032 for (auto dim : llvm::seq<int64_t>(0, sourceRank)) {
1033 Value idx =
1034 getValueOrCreateConstantIndexOp(builder, loc, sourceIndices[dim]);
1035 Value cond = arithBuilder.slt(
1036 idx, createOrFoldDimOp(builder, loc, packOp.getSource(), dim));
1037 isInBounds = dim == 0 ? cond : arithBuilder._and(isInBounds, cond);
1038 }
1039 scalar = scf::IfOp::create(
1040 builder, loc, isInBounds, /*thenBuilder=*/
1041 [&](OpBuilder &b, Location l) {
1042 scf::YieldOp::create(b, l, createLoad());
1043 },
1044 /*elseBuilder=*/
1045 [&](OpBuilder &b, Location l) {
1046 scf::YieldOp::create(b, l, paddingValue);
1047 })
1048 .getResult(0);
1049 } else {
1050 scalar = createLoad();
1051 }
1052
1053 memref::StoreOp::create(builder, loc, scalar, packOp.getDest(), ivs);
1054}
1055
1056struct PackOpTiling
1057 : public TilingInterface::ExternalModel<PackOpTiling, linalg::PackOp> {
1058 using Base = TilingInterface::ExternalModel<PackOpTiling, linalg::PackOp>;
1059 using Base::getTiledImplementation;
1060
1061 SmallVector<utils::IteratorType> getLoopIteratorTypes(Operation *op) const {
1062 // Note that here we only consider untiled dimensions and outer tiled data
1063 // dimensions, the inner tiled data dimensions are materialized when
1064 // building the body of the operation.
1065 auto packOp = cast<PackOp>(op);
1066 SmallVector<utils::IteratorType> iteratorTypes(
1067 packOp.getSourceRank(), utils::IteratorType::parallel);
1068 return iteratorTypes;
1069 }
1070
1071 SmallVector<Range> getIterationDomain(Operation *op, OpBuilder &b) const {
1072 return getPackUnPackIterationDomain<PackOp>(cast<PackOp>(op), b);
1073 }
1074
1075 FailureOr<TilingResult>
1076 getTiledImplementation(Operation *op, OpBuilder &b,
1077 ArrayRef<OpFoldResult> offsets,
1078 ArrayRef<OpFoldResult> sizes) const {
1079 auto packOp = cast<PackOp>(op);
1080 // TODO: Support Memref PackOp. Temporarily return failure.
1081 if (!packOp.hasPureTensorSemantics())
1082 return failure();
1083
1084 Location loc = packOp.getLoc();
1085
1086 // The tiling is applied on interchanged dimensions. We have to undo the
1087 // interchange to map sizes and offsets to the original input.
1088 int64_t inputRank = packOp.getSourceRank();
1089 SmallVector<OpFoldResult> origOffsets(offsets);
1090 SmallVector<OpFoldResult> origSizes(sizes);
1091 applyPermToRange(origOffsets, origSizes,
1092 invertPermutationVector(packOp.getOuterDimsPerm()));
1093
1094 DenseMap<int64_t, OpFoldResult> dimAndTileMapping =
1095 packOp.getDimAndTileMapping();
1096 SmallVector<OpFoldResult> srcDimValues =
1097 tensor::getMixedSizes(b, loc, packOp.getSource());
1098 SmallVector<OpFoldResult> inputIndices, inputSizes;
1099 for (auto dim : llvm::seq<int64_t>(0, inputRank)) {
1100 using AV = affine::AffineValueExpr;
1101 affine::AffineBuilder ab(b, loc);
1102 AffineExpr dim0, dim1, sym;
1103 bindDims(b.getContext(), dim0, dim1);
1104 bindSymbols(b.getContext(), sym);
1105 if (dimAndTileMapping.count(dim)) {
1106 // If the data dimension is tiled, the i-th index is the product of
1107 // offset_i and tile_i, and the i-th size is the product of sizes_i and
1108 // tile_i.
1109 auto avOffset = AV(dim0).bind(origOffsets[dim]);
1110 auto avSize = AV(dim0).bind(origSizes[dim]);
1111 auto avTileSize = AV(sym).bind(dimAndTileMapping[dim]);
1112 inputIndices.push_back(ab.mul(avOffset, avTileSize));
1113 inputSizes.push_back(ab.mul(avSize, avTileSize));
1114 } else {
1115 inputIndices.push_back(origOffsets[dim]);
1116 inputSizes.push_back(origSizes[dim]);
1117 }
1118
1119 // Limit the size of the input operand for incomplete tiles.
1120 if (packOp.getPaddingValue()) {
1121 OpFoldResult dimSize = srcDimValues[dim];
1122 auto avDimSize = AV(dim0).bind(dimSize);
1123 auto avInputIdx = AV(dim1).bind(inputIndices.back());
1124 inputSizes.back() =
1125 ab.min({inputSizes.back(), ab.sub(avDimSize, avInputIdx)});
1126 }
1127 }
1128
1129 auto oneAttr = b.getI64IntegerAttr(1);
1130 SmallVector<OpFoldResult> strides(inputRank, oneAttr);
1131
1132 SmallVector<Value> tiledOperands;
1133 auto sourceSlice = tensor::ExtractSliceOp::create(
1134 b, loc, packOp.getSource(), inputIndices, inputSizes, strides);
1135 tiledOperands.push_back(sourceSlice);
1136
1137 SmallVector<OpFoldResult> outputOffsets, outputSizes;
1138 if (failed(getResultTilePosition(op, b, 0, offsets, sizes, outputOffsets,
1139 outputSizes)))
1140 return {};
1141
1142 strides.append(packOp.getDestRank() - inputRank, oneAttr);
1143 auto outSlice = tensor::ExtractSliceOp::create(
1144 b, loc, packOp.getDest(), outputOffsets, outputSizes, strides);
1145 tiledOperands.push_back(outSlice);
1146
1147 if (auto val = packOp.getPaddingValue())
1148 tiledOperands.push_back(val);
1149 for (auto tile : packOp.getInnerTiles())
1150 tiledOperands.push_back(tile);
1151
1152 Operation *tiledPackOp = PackOp::create(
1153 b, loc, TypeRange{outSlice.getType()}, tiledOperands, op->getAttrs());
1154
1155 return TilingResult{
1156 {tiledPackOp},
1157 SmallVector<Value>(tiledPackOp->getResults()),
1158 llvm::to_vector(ArrayRef<Operation *>{sourceSlice, outSlice})};
1159 }
1160
1161 LogicalResult
1162 getResultTilePosition(Operation *op, OpBuilder &b, unsigned resultNumber,
1163 ArrayRef<OpFoldResult> offsets,
1164 ArrayRef<OpFoldResult> sizes,
1165 SmallVector<OpFoldResult> &resultOffsets,
1166 SmallVector<OpFoldResult> &resultSizes) const {
1167 // The iteration domain is over outer dimensions of packed layout. In this
1168 // context, the outer dimensions of `resultOffsets` are `offsets`. The
1169 // inner dimensions of `resultOffsets` are zeros because tiling is not
1170 // applied to them.
1171 auto packOp = cast<PackOp>(op);
1172 int64_t inputRank = packOp.getSourceRank();
1173 int64_t outputRank = packOp.getDestRank();
1174 auto zeroAttr = b.getI64IntegerAttr(0);
1175 resultOffsets.assign(offsets.begin(), offsets.end());
1176 resultOffsets.append(outputRank - inputRank, zeroAttr);
1177
1178 ReifiedRankedShapedTypeDims outputShape;
1179 (void)reifyResultShapes(b, packOp, outputShape);
1180 resultSizes.assign(sizes.begin(), sizes.end());
1181 for (auto dataTileDim : llvm::seq<unsigned>(inputRank, outputRank))
1182 resultSizes.push_back(outputShape[0][dataTileDim]);
1183
1184 return success();
1185 }
1186
1187 FailureOr<TilingResult>
1188 generateResultTileValue(Operation *op, OpBuilder &b, unsigned resultNumber,
1189 ArrayRef<OpFoldResult> offsets,
1190 ArrayRef<OpFoldResult> sizes) const {
1191 return generateResultTileValue(op, b, resultNumber, offsets, sizes,
1192 /*innerTileAlignments=*/{});
1193 }
1194
1195 FailureOr<TilingResult> generateResultTileValue(
1196 Operation *op, OpBuilder &b, unsigned resultNumber,
1197 ArrayRef<OpFoldResult> offsets, ArrayRef<OpFoldResult> sizes,
1198 ArrayRef<InnerTileAlignment> innerTileAlignments) const {
1199 auto packOp = cast<PackOp>(op);
1200 int64_t numTiles = packOp.getInnerDimsPos().size();
1201
1202 // linalg.pack op is fusible (as a producer) only if full inner tiles are
1203 // iterated or inner dims are not tiled. Otherwise, it will generate a
1204 // sequence of non-trivial ops (for partial tiles).
1205 for (auto offset : offsets.take_back(numTiles))
1206 if (!isZeroInteger(offset))
1207 return failure();
1208
1209 // Each requested inner-dim size must cover a full inner tile. A caller may
1210 // instead assert this via an `Equal` alignment hint. The hint is indexed by
1211 // source dim, matching the consumer-fusion path.
1212 ArrayRef<int64_t> innerDimsPos = packOp.getInnerDimsPos();
1213 SmallVector<OpFoldResult> mixedTiles = packOp.getMixedTiles();
1214 ArrayRef<OpFoldResult> innerSizes = sizes.take_back(numTiles);
1215 for (auto [i, pos] : llvm::enumerate(innerDimsPos)) {
1216 InnerTileAlignment alignment =
1217 pos < static_cast<int64_t>(innerTileAlignments.size())
1218 ? innerTileAlignments[pos]
1219 : InnerTileAlignment::Unknown;
1220 if (alignment != InnerTileAlignment::Equal &&
1221 !isEqualConstantIntOrValue(mixedTiles[i], innerSizes[i]))
1222 return failure();
1223 }
1224
1225 FailureOr<TilingResult> tilingResult = getTiledImplementation(
1226 op, b, offsets.drop_back(numTiles), sizes.drop_back(numTiles));
1227 if (failed(tilingResult))
1228 return failure();
1229 return tilingResult.value();
1230 }
1231
1232 LogicalResult generateScalarImplementation(Operation *op, OpBuilder &builder,
1233 Location loc,
1234 ValueRange ivs) const {
1235 auto packOp = cast<PackOp>(op);
1236 assert(packOp.hasPureBufferSemantics() &&
1237 "expected operation to have buffer semantics");
1238 OpBuilder::InsertionGuard g(builder);
1239 // The `ivs` already represent the position into the output for the non
1240 // data-tile dimensions.
1241 SmallVector<Value> ivVec(ivs);
1242
1243 // Get output shape - for memrefs, get dimensions from dest directly.
1244 SmallVector<OpFoldResult> outputShape;
1245 Value dest = packOp.getDest();
1246 for (auto dim : llvm::seq<int64_t>(0, packOp.getDestRank()))
1247 outputShape.push_back(createOrFoldDimOp(builder, loc, dest, dim));
1248
1249 // Generate the loops that iterate over the data tile.
1250 Value zero = arith::ConstantIndexOp::create(builder, loc, 0);
1251 Value one = arith::ConstantIndexOp::create(builder, loc, 1);
1252
1253 // All loops except the innermost are simple loops that just iterate
1254 // over the tile dimensions.
1255 for (auto dataTileDim : llvm::seq<unsigned>(packOp.getSourceRank(),
1256 packOp.getDestRank() - 1)) {
1257 Value ub = getValueOrCreateConstantIndexOp(builder, loc,
1258 outputShape[dataTileDim]);
1259 scf::ForOp loop = scf::ForOp::create(builder, loc, zero, ub, one);
1260 builder.setInsertionPointToStart(loop.getBody());
1261 ivVec.push_back(loop.getInductionVar());
1262 }
1263 // The body of the innermost loops does the actual data movement.
1264 scf::ForOp::create(
1265 builder, loc, zero,
1266 getValueOrCreateConstantIndexOp(builder, loc, outputShape.back()), one,
1267 ValueRange{},
1268 [&](OpBuilder &bodyBuilder, Location bodyLoc, Value iv,
1269 ValueRange regionIterArgs) {
1270 ivVec.push_back(iv);
1271 generatePackOpScalarImplementationBody(packOp, bodyBuilder, bodyLoc,
1272 ivVec);
1273 scf::YieldOp::create(bodyBuilder, bodyLoc);
1274 });
1275 return success();
1276 }
1277
1278 LogicalResult getIterationDomainTileFromOperandTiles(
1279 Operation *op, OpBuilder &b, ArrayRef<unsigned> operandNumbers,
1280 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1281 ArrayRef<SmallVector<OpFoldResult>> allSizes,
1282 SmallVectorImpl<OpFoldResult> &resultOffsets,
1283 SmallVectorImpl<OpFoldResult> &resultSizes) const {
1284 return getIterationDomainTileFromOperandTiles(
1285 op, b, operandNumbers, allOffsets, allSizes, resultOffsets, resultSizes,
1286 /*innerTileAlignments=*/{});
1287 }
1288
1289 /// Method to return the position of iteration domain tile computed by the
1290 /// tiled operation. In current `linalg.pack` context, the `resultOffsets` and
1291 /// `resultSizes` only cover outer dimensions.
1292 LogicalResult getIterationDomainTileFromOperandTiles(
1293 Operation *op, OpBuilder &b, ArrayRef<unsigned> operandNumbers,
1294 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1295 ArrayRef<SmallVector<OpFoldResult>> allSizes,
1296 SmallVectorImpl<OpFoldResult> &resultOffsets,
1297 SmallVectorImpl<OpFoldResult> &resultSizes,
1298 ArrayRef<InnerTileAlignment> innerTileAlignments) const {
1299 if (operandNumbers.size() != 1 || operandNumbers[0] != 0) {
1300 LLVM_DEBUG(
1301 { llvm::dbgs() << "unsupported operands for consumer fusion"; });
1302 return failure();
1303 }
1304
1305 ArrayRef<OpFoldResult> offsets(allOffsets[0]);
1306 ArrayRef<OpFoldResult> sizes(allSizes[0]);
1307 auto packOp = cast<PackOp>(op);
1308 Location loc = packOp.getLoc();
1309 SmallVector<OpFoldResult> outerDimOffsets, outerDimSizes;
1310 DenseMap<int64_t, OpFoldResult> dimAndTileMapping =
1311 packOp.getDimAndTileMapping();
1312 SmallVector<int64_t> outerShapeWithoutTranspose(
1313 packOp.getDestType().getShape().take_front(packOp.getSourceRank()));
1314 if (!packOp.getOuterDimsPerm().empty()) {
1316 outerShapeWithoutTranspose,
1317 invertPermutationVector(packOp.getOuterDimsPerm()));
1318 }
1319 for (auto dim : llvm::seq<int64_t>(packOp.getSourceRank())) {
1320 if (dimAndTileMapping.count(dim)) {
1321 FailureOr<int64_t> cstTileSize =
1323 presburger::BoundType::UB, sizes[dim],
1324 /*stopCondition=*/nullptr,
1325 ValueBoundsOptions{/*closedUB=*/true});
1326 std::optional<int64_t> cstInnerSize =
1327 getConstantIntValue(dimAndTileMapping[dim]);
1328
1329 // A caller-supplied alignment hint (see InnerTileAlignment) asserts
1330 // that this packed dimension is tiled and how its loop tile size
1331 // relates to the pack op inner tile size.
1332 InnerTileAlignment innerTileAlignment =
1333 dim < static_cast<int64_t>(innerTileAlignments.size())
1334 ? innerTileAlignments[dim]
1335 : InnerTileAlignment::Unknown;
1336
1337 // If a dimension is not tiled, it is always valid to fuse the pack op,
1338 // even if the op has padding semantics. Because it always generates a
1339 // full slice along the dimension. The tile sizes are for unpacked
1340 // domain, i.e., `srcDimSize`, so `tileSize < srcDimSize` means that the
1341 // dimension is tiled.
1342 // TODO: It could be untiled if the `srcDimSize` is dynamic. It is a
1343 // hard check to determine if a dimension is tiled or not.
1344 // A non-`Unknown` hint also means the caller asserts the dimension is
1345 // tiled: `cstTileSize` is an upper bound, so a scalable/`min`-shaped
1346 // tile (whose bound equals `srcDimSize`) would otherwise be mistaken
1347 // for untiled and bypass the hint below.
1348 int64_t srcDimSize = packOp.getSourceType().getDimSize(dim);
1349 int64_t destDimSize = outerShapeWithoutTranspose[dim];
1350 bool isTiled = innerTileAlignment != InnerTileAlignment::Unknown ||
1351 failed(cstTileSize) ||
1352 ShapedType::isDynamic(srcDimSize) ||
1353 cstTileSize.value() < srcDimSize;
1354 if (!isTiled) {
1355 outerDimOffsets.push_back(offsets[dim]);
1356 if (ShapedType::isStatic(destDimSize)) {
1357 outerDimSizes.push_back(b.getIndexAttr(destDimSize));
1358 } else {
1359 outerDimSizes.push_back(
1360 b.createOrFold<tensor::DimOp>(loc, packOp.getDest(), dim));
1361 }
1362 continue;
1363 }
1364
1365 // Currently fusing `packOp` as consumer only expects perfect tiling
1366 // scenario because even if without padding semantic, the `packOp` may
1367 // also yield incomplete tiles. E.g. tensor<30xf32> -> tensor<5x6xf32>,
1368 // where the `tileSize` from operand of `packOp` is 5, which is not
1369 // exactly divided by `innerTile`(=6) of `packOp`. As the result:
1370 // 1. the first slice is extracted from (0) to (4) and inserted into
1371 // (0,0)~(0,4) at first row.
1372 // 2. the second slice is extracted from (5) to (9) and SHOULD BE
1373 // respectively inserted into two rows with different length, including
1374 // first row: (0,5) and second row (1,0)~(1,3). It is hard to coordinate
1375 // them, thus adding below constraint to bypass them temporarily. In
1376 // another word, we can only support tiling with consumer if the tile
1377 // size for the producer is a multiple of the inner tile size for the
1378 // packed dimensions at this moment.
1379
1380 // The caller may assert how this packed dimension's loop tile size
1381 // relates to the inner tile size via `innerTileAlignments` (see
1382 // InnerTileAlignment). The hint is the source of truth and is honored
1383 // when present. When both sizes are also statically known we assert the
1384 // hint agrees with them (a contradicting hint is a caller bug). When
1385 // the hint is `Unknown`, fall back to requiring a statically-provable
1386 // multiple.
1387 bool assumeInnerTileSizesMatchTiles =
1388 innerTileAlignment == InnerTileAlignment::Equal;
1389 bool staticallyDecidable =
1390 !failed(cstTileSize) && cstInnerSize.has_value();
1391 if (innerTileAlignment == InnerTileAlignment::Unknown) {
1392 if (!staticallyDecidable || *cstTileSize % *cstInnerSize != 0)
1393 return failure();
1394 } else if (staticallyDecidable) {
1395 assert(*cstTileSize % *cstInnerSize == 0 &&
1396 "InnerTileAlignment hint contradicts statically known tile "
1397 "sizes");
1398 assert((innerTileAlignment != InnerTileAlignment::Equal ||
1399 *cstTileSize == *cstInnerSize) &&
1400 "InnerTileAlignment::Equal contradicts statically known tile "
1401 "sizes");
1402 }
1403
1404 using AV = affine::AffineValueExpr;
1405 affine::AffineBuilder ab(b, loc);
1406 AffineExpr dim0, sym;
1407 bindDims(b.getContext(), dim0);
1408 bindSymbols(b.getContext(), sym);
1409 auto avOffset = AV(dim0).bind(offsets[dim]);
1410 auto avSize = AV(dim0).bind(sizes[dim]);
1411 auto avTileSize = AV(sym).bind(dimAndTileMapping[dim]);
1412 outerDimOffsets.push_back(ab.floor(avOffset, avTileSize));
1413 // If the tile size equals the inner tile size, the outer dims are
1414 // always 1.
1415 outerDimSizes.push_back(assumeInnerTileSizesMatchTiles
1416 ? b.getIndexAttr(1)
1417 : ab.ceil(avSize, avTileSize));
1418 } else {
1419 outerDimOffsets.push_back(offsets[dim]);
1420 outerDimSizes.push_back(sizes[dim]);
1421 }
1422 }
1423 applyPermToRange(outerDimOffsets, outerDimSizes, packOp.getOuterDimsPerm());
1424 resultOffsets = outerDimOffsets;
1425 resultSizes = outerDimSizes;
1426 return success();
1427 }
1428
1429 FailureOr<TilingResult> getTiledImplementationFromOperandTiles(
1430 Operation *op, OpBuilder &b, ArrayRef<unsigned> operandNumbers,
1431 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1432 ArrayRef<SmallVector<OpFoldResult>> allSizes) const {
1433 return getTiledImplementationFromOperandTiles(op, b, operandNumbers,
1434 allOffsets, allSizes,
1435 /*innerTileAlignments=*/{});
1436 }
1437
1438 /// Method to return the tiled implementation of linalg.pack as a consumer.
1439 FailureOr<TilingResult> getTiledImplementationFromOperandTiles(
1440 Operation *op, OpBuilder &b, ArrayRef<unsigned> operandNumbers,
1441 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1442 ArrayRef<SmallVector<OpFoldResult>> allSizes,
1443 ArrayRef<InnerTileAlignment> innerTileAlignments) const {
1444 if (operandNumbers.size() != 1 || operandNumbers[0] != 0) {
1445 LLVM_DEBUG({ llvm::dbgs() << "unhandled operands for consumer fusion"; });
1446 return failure();
1447 }
1448
1449 ArrayRef<OpFoldResult> offsets(allOffsets[0]);
1450 ArrayRef<OpFoldResult> sizes(allSizes[0]);
1451
1452 auto packOp = cast<PackOp>(op);
1453 // TODO: Support Memref UnPackOp. Temporarily return failure.
1454 if (!packOp.hasPureTensorSemantics())
1455 return failure();
1456
1457 Location loc = packOp.getLoc();
1458
1459 int64_t inputRank = packOp.getSourceRank();
1460 auto oneAttr = b.getI64IntegerAttr(1);
1461 SmallVector<OpFoldResult> strides(inputRank, oneAttr);
1462
1463 SmallVector<Value> tiledOperands;
1464 auto sourceSlice = tensor::ExtractSliceOp::create(
1465 b, loc, packOp.getSource(), offsets, sizes, strides);
1466 tiledOperands.push_back(sourceSlice);
1467
1468 SmallVector<OpFoldResult> outerDimOffsets, outerDimSizes;
1469 if (failed(getIterationDomainTileFromOperandTiles(
1470 op, b, operandNumbers, allOffsets, allSizes, outerDimOffsets,
1471 outerDimSizes, innerTileAlignments)))
1472 return failure();
1473
1474 SmallVector<OpFoldResult> outputOffsets, outputSizes;
1475 if (failed(getResultTilePosition(op, b, 0, outerDimOffsets, outerDimSizes,
1476 outputOffsets, outputSizes)))
1477 return failure();
1478
1479 strides.append(packOp.getDestRank() - inputRank, oneAttr);
1480 auto outSlice = tensor::ExtractSliceOp::create(
1481 b, loc, packOp.getDest(), outputOffsets, outputSizes, strides);
1482 tiledOperands.push_back(outSlice);
1483
1484 if (auto val = packOp.getPaddingValue())
1485 tiledOperands.push_back(val);
1486 for (auto tile : packOp.getInnerTiles())
1487 tiledOperands.push_back(tile);
1488
1489 Operation *tiledPackOp = PackOp::create(
1490 b, loc, TypeRange{outSlice.getType()}, tiledOperands, op->getAttrs());
1491
1492 return TilingResult{
1493 {tiledPackOp},
1494 SmallVector<Value>(tiledPackOp->getResults()),
1495 llvm::to_vector(ArrayRef<Operation *>{sourceSlice, outSlice})};
1496 }
1497};
1498
1499struct UnpackTileDimInfo {
1500 bool isAlignedToInnerTileSize;
1501 OpFoldResult sourceOffset;
1502 OpFoldResult sourceSize;
1503 OpFoldResult resultOffset;
1504 OpFoldResult destExpandedSize;
1505};
1506
1507/// Returns the needed information for tiling unpack op on `tileDim` with given
1508/// `tileOffset` and `tileSize`. For more details, see the comment of the
1509/// `getTiledImplementation`.
1510static UnpackTileDimInfo
1511getUnpackTileDimInfo(OpBuilder &b, UnPackOp unpackOp, int64_t tileDim,
1512 OpFoldResult tileOffset, OpFoldResult tileSize,
1513 InnerTileAlignment innerTileAlignment) {
1514 UnpackTileDimInfo info;
1515 Attribute zeroAttr = b.getIndexAttr(0);
1516 Attribute oneAttr = b.getIndexAttr(1);
1517 DenseMap<int64_t, OpFoldResult> dimAndTileMapping =
1518 unpackOp.getDimAndTileMapping();
1519 // The dimension is not one of packed data dimension.
1520 if (!dimAndTileMapping.count(tileDim)) {
1521 info.isAlignedToInnerTileSize = true;
1522 info.sourceOffset = tileOffset;
1523 info.sourceSize = tileSize;
1524 info.resultOffset = zeroAttr;
1525 info.destExpandedSize = tileSize;
1526 return info;
1527 }
1528
1529 Location loc = unpackOp.getLoc();
1530 using AV = affine::AffineValueExpr;
1531 affine::AffineBuilder ab(b, loc);
1532 AffineExpr dim0, dim1, sym0;
1533 bindDims(b.getContext(), dim0, dim1);
1534 bindSymbols(b.getContext(), sym0);
1535
1536 OpFoldResult innerTileSize = dimAndTileMapping[tileDim];
1537
1538 info.isAlignedToInnerTileSize = false;
1539 FailureOr<int64_t> cstSize = ValueBoundsConstraintSet::computeConstantBound(
1540 presburger::BoundType::UB, tileSize,
1541 /*stopCondition=*/nullptr, ValueBoundsOptions{/*closedUB=*/true});
1542 std::optional<int64_t> cstInnerSize = getConstantIntValue(innerTileSize);
1543 // The caller may assert how this dimension's loop tile size relates to the
1544 // op's inner tile size via `innerTileAlignment` (see InnerTileAlignment). The
1545 // hint is the source of truth and is honored when present: `Equal`/`Multiple`
1546 // both mean the tile is aligned to (a multiple of) the inner tile, and
1547 // `Equal` additionally collapses the source slice to a single inner tile.
1548 // When both sizes are also statically known we assert the hint agrees with
1549 // them (a contradicting hint is a caller bug). When `Unknown`, fall back to
1550 // the static upper-bound path below.
1551 bool assumeInnerTileSizesMatchTiles =
1552 innerTileAlignment == InnerTileAlignment::Equal;
1553 bool staticallyDecidable = !failed(cstSize) && cstInnerSize.has_value();
1554 if (innerTileAlignment != InnerTileAlignment::Unknown) {
1555 info.isAlignedToInnerTileSize = true;
1556 if (staticallyDecidable) {
1557 assert(*cstSize % *cstInnerSize == 0 &&
1558 "InnerTileAlignment hint contradicts statically known tile sizes");
1559 assert((innerTileAlignment != InnerTileAlignment::Equal ||
1560 *cstSize == *cstInnerSize) &&
1561 "InnerTileAlignment::Equal contradicts statically known tile "
1562 "sizes");
1563 }
1564 }
1565 if (info.isAlignedToInnerTileSize || (!failed(cstSize) && cstInnerSize)) {
1566 if (!info.isAlignedToInnerTileSize && *cstSize % *cstInnerSize == 0)
1567 info.isAlignedToInnerTileSize = true;
1568
1569 // If the tiling size equals to the inner tiling size, the outer dims are
1570 // always 1.
1571 if (assumeInnerTileSizesMatchTiles ||
1572 (cstInnerSize && !failed(cstSize) && *cstInnerSize == *cstSize)) {
1573 auto lhs = AV(dim0).bind(tileOffset);
1574 auto rhs = AV(dim1).bind(innerTileSize);
1575 info.sourceOffset = ab.floor(lhs, rhs);
1576 info.sourceSize = oneAttr;
1577 info.resultOffset = zeroAttr;
1578 info.destExpandedSize = tileSize;
1579 return info;
1580 }
1581 }
1582
1583 if (info.isAlignedToInnerTileSize) {
1584 info.sourceOffset =
1585 ab.floor(AV(dim0).bind(tileOffset), AV(dim1).bind(innerTileSize));
1586 info.resultOffset = zeroAttr;
1587 info.destExpandedSize = tileSize;
1588
1589 // The ceilDiv is needed here because there could be incomplete tile even
1590 // it is perfect tiling cases. E.g.,
1591 // %0 = unpack tensor<33x2xf32> into tensor<64xf32>
1592 // If the tiling size is 32, there will be 3 tiles. Two of them have
1593 // size=32; one of them have size=2. The size is represented using
1594 // affine_min op; we need ceilDiv.
1595 info.sourceSize =
1596 ab.ceil(AV(dim0).bind(tileSize), AV(dim1).bind(innerTileSize));
1597 return info;
1598 }
1599
1600 affine::DivModValue firstCoord = affine::getDivMod(
1601 b, loc, getValueOrCreateConstantIndexOp(b, loc, tileOffset),
1602 getValueOrCreateConstantIndexOp(b, loc, innerTileSize));
1603 OpFoldResult tileExclusiveBound =
1604 ab.add(AV(dim0).bind(tileOffset), AV(dim1).bind(tileSize));
1605 affine::DivModValue lastCoord = affine::getDivMod(
1606 b, loc,
1608 b, loc,
1609 ab.sub(AV(dim0).bind(tileExclusiveBound), AV(dim1).bind(oneAttr))),
1610 getValueOrCreateConstantIndexOp(b, loc, innerTileSize));
1611
1612 OpFoldResult lengthMinusOne = ab.sub(AV(dim0).bind(lastCoord.quotient),
1613 AV(dim1).bind(firstCoord.quotient));
1614 info.sourceSize =
1615 ab.add(AV(dim0).bind(lengthMinusOne), AV(dim1).bind(oneAttr));
1616 info.sourceOffset = firstCoord.quotient;
1617 info.resultOffset = firstCoord.remainder;
1618 // Do not create an Affine ops for expanded size because the affine op is too
1619 // complicated which would trigger an issue in affine ops simplification.
1620 info.destExpandedSize = b.createOrFold<arith::MulIOp>(
1621 loc, getValueOrCreateConstantIndexOp(b, loc, info.sourceSize),
1622 getValueOrCreateConstantIndexOp(b, loc, innerTileSize));
1623 return info;
1624}
1625
1626struct UnPackOpTiling
1627 : public TilingInterface::ExternalModel<UnPackOpTiling, linalg::UnPackOp> {
1628 using Base = TilingInterface::ExternalModel<UnPackOpTiling, linalg::UnPackOp>;
1629 using Base::getIterationDomainTileFromOperandTiles;
1630
1631 SmallVector<utils::IteratorType> getLoopIteratorTypes(Operation *op) const {
1632 auto unpackOp = cast<UnPackOp>(op);
1633 SmallVector<utils::IteratorType> iteratorTypes(
1634 unpackOp.getDestRank(), utils::IteratorType::parallel);
1635 return iteratorTypes;
1636 }
1637
1638 SmallVector<Range> getIterationDomain(Operation *op, OpBuilder &b) const {
1639 return getPackUnPackIterationDomain<UnPackOp>(cast<UnPackOp>(op), b);
1640 }
1641
1642 /// There are two cases in tiling unpack ops. If the tiling size is aligned to
1643 /// the inner tile size, the corresponding tiles of source are all complete.
1644 /// Otherwise, there are in-complete tiles. We will need to expand the slice
1645 /// of source for getting complete tiles. The tiled unpack op unpacks more
1646 /// data from source, so We'll need an extract_slice op to shift and truncate
1647 /// the output.
1648 /// Take Nn_to_N as an example. Say that N=32, n=8, and tiling_size=15. The
1649 /// coordinates of second tile (i.e., result[15..31]) are
1650 /// [(1, 7), (2, 0,), (2, 1) ... (3, 6), (3, 7)]. The first row and the last
1651 /// row are incomplete tiles. To represent the unpack op, we have to complete
1652 /// the rows. I.e., the input coordinates would start with (1, 0); end with
1653 /// (3, 7). In this context, the tiled unpack produces a (3 * n) elements
1654 /// because there are 3 rows in total. Follow by a tensor.extract_slice op, we
1655 /// can get the actual result.
1656 FailureOr<TilingResult>
1657 getTiledImplementation(Operation *op, OpBuilder &b,
1658 ArrayRef<OpFoldResult> offsets,
1659 ArrayRef<OpFoldResult> sizes) const {
1660 return getTiledImplementation(op, b, offsets, sizes,
1661 /*innerTileAlignments=*/{});
1662 }
1663
1664 FailureOr<TilingResult> getTiledImplementation(
1665 Operation *op, OpBuilder &b, ArrayRef<OpFoldResult> offsets,
1666 ArrayRef<OpFoldResult> sizes,
1667 ArrayRef<InnerTileAlignment> innerTileAlignments) const {
1668 auto unpackOp = cast<UnPackOp>(op);
1669 // TODO: Support Memref UnPackOp. Temporarily return failure.
1670 if (!unpackOp.hasPureTensorSemantics())
1671 return failure();
1672
1673 int64_t srcRank = unpackOp.getSourceRank();
1674 int64_t destRank = unpackOp.getDestRank();
1675 int64_t numInnerTiles = srcRank - destRank;
1676 Location loc = unpackOp.getLoc();
1677
1678 // The perfect tiling case indicates that the tiling sizes are multiple of
1679 // inner_tile_size. In this context, no extra data is needed when
1680 // representing the tiled unpack op.
1681 bool isPerfectTilingCase = true;
1682 Attribute oneAttr = b.getIndexAttr(1);
1683 SmallVector<OpFoldResult> sliceSrcStrides(destRank, oneAttr);
1684 SmallVector<OpFoldResult> sliceSrcIndices, sliceSrcSizes;
1685 SmallVector<OpFoldResult> destExpandedSizes, resultOffsetsFromDest;
1686 for (auto dim : llvm::seq<int64_t>(0, destRank)) {
1687 UnpackTileDimInfo info = getUnpackTileDimInfo(
1688 b, unpackOp, dim, offsets[dim], sizes[dim],
1689 dim < static_cast<int64_t>(innerTileAlignments.size())
1690 ? innerTileAlignments[dim]
1691 : InnerTileAlignment::Unknown);
1692 if (!info.isAlignedToInnerTileSize)
1693 isPerfectTilingCase = false;
1694 sliceSrcIndices.push_back(info.sourceOffset);
1695 sliceSrcSizes.push_back(info.sourceSize);
1696 destExpandedSizes.push_back(info.destExpandedSize);
1697 resultOffsetsFromDest.push_back(info.resultOffset);
1698 }
1699
1700 // The tiling is applied on destination dimensions. We have to apply the
1701 // interchange on source dimensions if outer_dims_perm is set.
1702 applyPermToRange(sliceSrcIndices, sliceSrcSizes,
1703 unpackOp.getOuterDimsPerm());
1704 Attribute zeroAttr = b.getIndexAttr(0);
1705 sliceSrcIndices.append(numInnerTiles, zeroAttr);
1706 sliceSrcSizes.append(unpackOp.getMixedTiles());
1707 sliceSrcStrides.append(numInnerTiles, oneAttr);
1708 SmallVector<Operation *> generatedSlices;
1709 tensor::ExtractSliceOp sliceSource = tensor::ExtractSliceOp::create(
1710 b, loc, unpackOp.getSource(), sliceSrcIndices, sliceSrcSizes,
1711 sliceSrcStrides);
1712 generatedSlices.push_back(sliceSource);
1713
1714 SmallVector<OpFoldResult> destStrides(destRank, oneAttr);
1715 Value sliceDest;
1716 if (isPerfectTilingCase) {
1717 auto destSliceOp = tensor::ExtractSliceOp::create(
1718 b, loc, unpackOp.getDest(), offsets, sizes, destStrides);
1719 sliceDest = destSliceOp;
1720 generatedSlices.push_back(destSliceOp);
1721 } else {
1722 sliceDest = tensor::EmptyOp::create(
1723 b, loc, destExpandedSizes, unpackOp.getDestType().getElementType());
1724 }
1725
1726 SmallVector<Value> tiledOperands = {sliceSource.getResult(), sliceDest};
1727 for (auto tile : unpackOp.getInnerTiles())
1728 tiledOperands.push_back(tile);
1729
1730 Operation *tiledUnpackOp = UnPackOp::create(
1731 b, loc, TypeRange{sliceDest.getType()}, tiledOperands, op->getAttrs());
1732
1733 if (isPerfectTilingCase)
1734 return TilingResult{{tiledUnpackOp},
1735 SmallVector<Value>(tiledUnpackOp->getResults()),
1736 generatedSlices};
1737
1738 auto extractSlice = tensor::ExtractSliceOp::create(
1739 b, loc, tiledUnpackOp->getResult(0), resultOffsetsFromDest, sizes,
1740 destStrides);
1741 return TilingResult{
1742 {tiledUnpackOp}, {extractSlice.getResult()}, generatedSlices};
1743 }
1744
1745 LogicalResult
1746 getResultTilePosition(Operation *op, OpBuilder &b, unsigned resultNumber,
1747 ArrayRef<OpFoldResult> offsets,
1748 ArrayRef<OpFoldResult> sizes,
1749 SmallVector<OpFoldResult> &resultOffsets,
1750 SmallVector<OpFoldResult> &resultSizes) const {
1751 resultOffsets = llvm::to_vector(offsets);
1752 resultSizes = llvm::to_vector(sizes);
1753 return success();
1754 }
1755
1756 FailureOr<TilingResult>
1757 generateResultTileValue(Operation *op, OpBuilder &b, unsigned resultNumber,
1758 ArrayRef<OpFoldResult> offsets,
1759 ArrayRef<OpFoldResult> sizes) const {
1760 return generateResultTileValue(op, b, resultNumber, offsets, sizes,
1761 /*innerTileAlignments=*/{});
1762 }
1763
1764 FailureOr<TilingResult> generateResultTileValue(
1765 Operation *op, OpBuilder &b, unsigned resultNumber,
1766 ArrayRef<OpFoldResult> offsets, ArrayRef<OpFoldResult> sizes,
1767 ArrayRef<InnerTileAlignment> innerTileAlignments) const {
1768 FailureOr<TilingResult> tilingResult =
1769 getTiledImplementation(op, b, offsets, sizes, innerTileAlignments);
1770 if (failed(tilingResult))
1771 return failure();
1772 return tilingResult.value();
1773 }
1774
1775 LogicalResult generateScalarImplementation(Operation *op, OpBuilder &builder,
1776 Location loc,
1777 ValueRange ivs) const {
1778 auto unpackOp = cast<UnPackOp>(op);
1779 assert(unpackOp.hasPureBufferSemantics() &&
1780 "expected operation to have buffer semantics");
1781 assert(ivs.size() == unpackOp.getDestRank() &&
1782 "number of ivs must match the rank of the output tensor");
1783 OpBuilder::InsertionGuard g(builder);
1784
1785 DenseMap<int64_t, OpFoldResult> dimAndTileMapping =
1786 unpackOp.getDimAndTileMapping();
1787 // Untiled loops and tile loops induction variables.
1788 SmallVector<Value> inputIvs;
1789 // Point loops induction variables.
1790 SmallVector<Value> inputIvsPointLoops;
1791 inputIvs.reserve(unpackOp.getDestRank());
1792 inputIvsPointLoops.reserve(dimAndTileMapping.size());
1793 for (auto dim : llvm::seq<int64_t>(0, unpackOp.getDestRank())) {
1794 if (dimAndTileMapping.count(dim)) {
1795 affine::DivModValue divMod =
1796 affine::getDivMod(builder, loc, ivs[dim],
1798 builder, loc, dimAndTileMapping[dim]));
1799 inputIvsPointLoops.push_back(divMod.remainder);
1800 inputIvs.push_back(divMod.quotient);
1801 } else {
1802 inputIvs.push_back(ivs[dim]);
1803 }
1804 }
1805
1806 // TODO: (lorenzo) simplify the logic a bit. There is `ivs`,
1807 // `inputIvsPointLoops` and `inputIvs`.
1808 assert(inputIvsPointLoops.size() + inputIvs.size() ==
1809 unpackOp.getSourceRank() &&
1810 "expect same number of induction variables equals to input rank");
1811 // Interchange the point loops induction variables based on `inner_dim_pos`.
1812 ArrayRef<int64_t> innerDims = unpackOp.getInnerDimsPos();
1813 SmallVector<int64_t> interchangeVector =
1814 computeInterchangeFromDimPos(innerDims, unpackOp.getDestRank());
1815 SmallVector<Value> interchangedInputIvsPointLoops = inputIvsPointLoops;
1816 interchangedInputIvsPointLoops = interchange<Value>(
1817 interchangedInputIvsPointLoops, interchangeVector, /*offset=*/0);
1818 // Interchange the tiled loops induction variables based on
1819 // `outer_dims_perm`.
1820 ArrayRef<int64_t> outerDims = unpackOp.getOuterDimsPerm();
1821 if (!outerDims.empty())
1822 inputIvs = interchange<Value>(inputIvs, outerDims, /*offset=*/0);
1823
1824 llvm::append_range(inputIvs, interchangedInputIvsPointLoops);
1825 Value scalar =
1826 memref::LoadOp::create(builder, loc, unpackOp.getSource(), inputIvs);
1827 memref::StoreOp::create(builder, loc, scalar, unpackOp.getDest(), ivs);
1828 return success();
1829 }
1830
1831 /// Method to return the position of iteration domain tile computed by the
1832 /// tiled operation.
1833 LogicalResult getIterationDomainTileFromOperandTiles(
1834 Operation *op, OpBuilder &b, ArrayRef<unsigned> operandNumbers,
1835 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1836 ArrayRef<SmallVector<OpFoldResult>> allSizes,
1837 SmallVectorImpl<OpFoldResult> &resultOffsets,
1838 SmallVectorImpl<OpFoldResult> &resultSizes) const {
1839 if (operandNumbers.size() != 1) {
1840 LLVM_DEBUG({ llvm::dbgs() << "unable to handle multiple operands"; });
1841 return failure();
1842 }
1843 auto unPackOp = cast<UnPackOp>(op);
1844 unsigned operandNumber = operandNumbers[0];
1845 ArrayRef<OpFoldResult> offsets(allOffsets[0]);
1846 ArrayRef<OpFoldResult> sizes(allSizes[0]);
1847
1848 // If the operand tile is the dest, then no adjustment is needed.
1849 if (operandNumber == unPackOp.getDestMutable().getOperandNumber()) {
1850 resultOffsets = llvm::to_vector(offsets);
1851 resultSizes = llvm::to_vector(sizes);
1852 return success();
1853 }
1854 Location loc = unPackOp.getLoc();
1855
1856 int64_t numTiles = unPackOp.getInnerDimsPos().size();
1857 auto destOffsets = offsets.drop_back(numTiles);
1858 auto destSizes = sizes.drop_back(numTiles);
1859 // The tiling is applied on interchanged dimensions. We have to undo the
1860 // interchange to map sizes and offsets to the original input.
1861 int64_t outputRank = unPackOp.getDestRank();
1862 ReifiedRankedShapedTypeDims reifiedReturnShapes;
1863 if (failed(reifyResultShapes(b, unPackOp, reifiedReturnShapes)))
1864 return failure();
1865 SmallVector<OpFoldResult> outputMixedSizes = reifiedReturnShapes.front();
1866 SmallVector<OpFoldResult> origOffsets(destOffsets);
1867 SmallVector<OpFoldResult> origSizes(destSizes);
1868 applyPermToRange(origOffsets, origSizes,
1869 invertPermutationVector(unPackOp.getOuterDimsPerm()));
1870
1871 DenseMap<int64_t, OpFoldResult> dimAndTileMapping =
1872 unPackOp.getDimAndTileMapping();
1873
1874 for (auto dim : llvm::seq<int64_t>(0, outputRank)) {
1875 using AV = affine::AffineValueExpr;
1876 affine::AffineBuilder ab(b, loc);
1877 AffineExpr dim0, dim1, sym0;
1878 bindDims(b.getContext(), dim0, dim1);
1879 bindSymbols(b.getContext(), sym0);
1880 if (dimAndTileMapping.count(dim)) {
1881 // If the data dimension is tiled, the i-th index is the product of
1882 // offset_i and tile_i, and the i-th size is the product of sizes_i and
1883 // tile_i. The sizes must be clamped to the sizes of the unpack result.
1884 auto avOffset = AV(dim0).bind(origOffsets[dim]);
1885 auto avSize = AV(dim0).bind(origSizes[dim]);
1886 auto avTileSize = AV(sym0).bind(dimAndTileMapping[dim]);
1887 auto avResultSize = AV(dim0).bind(outputMixedSizes[dim]);
1888 resultOffsets.push_back(ab.mul(avOffset, avTileSize));
1889 auto avResultOffset = AV(dim1).bind(resultOffsets.back());
1890 resultSizes.push_back(ab.min({ab.mul(avSize, avTileSize),
1891 ab.sub(avResultSize, avResultOffset)}));
1892 } else {
1893 resultOffsets.push_back(origOffsets[dim]);
1894 resultSizes.push_back(origSizes[dim]);
1895 }
1896 }
1897 return success();
1898 }
1899
1900 FailureOr<TilingResult> getTiledImplementationFromOperandTiles(
1901 Operation *op, OpBuilder &b, ArrayRef<unsigned> operandNumbers,
1902 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1903 ArrayRef<SmallVector<OpFoldResult>> allSizes) const {
1904 return getTiledImplementationFromOperandTiles(op, b, operandNumbers,
1905 allOffsets, allSizes,
1906 /*innerTileAlignments=*/{});
1907 }
1908
1909 /// Method to return the tiled implementation of linalg.unpack as a consumer.
1910 FailureOr<TilingResult> getTiledImplementationFromOperandTiles(
1911 Operation *op, OpBuilder &b, ArrayRef<unsigned> operandNumbers,
1912 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1913 ArrayRef<SmallVector<OpFoldResult>> allSizes,
1914 ArrayRef<InnerTileAlignment> innerTileAlignments) const {
1915 if (operandNumbers.size() != 1 || operandNumbers[0] != 0) {
1916 LLVM_DEBUG({ llvm::dbgs() << "unhandled operands for consumer fusion"; });
1917 return failure();
1918 }
1919 auto unPackOp = cast<UnPackOp>(op);
1920 // TODO: Support Memref UnPackOp. Temporarily return failure.
1921 if (!unPackOp.hasPureTensorSemantics())
1922 return failure();
1923
1924 ArrayRef<OpFoldResult> offsets(allOffsets[0]);
1925 ArrayRef<OpFoldResult> sizes(allSizes[0]);
1926
1927 // linalg.unpack op is fusible (as a consumer) only if the inner dims are
1928 // not tiled, i.e. each inner-dim loop tile size equals the inner tile size.
1929 // The caller may assert this per inner dim via InnerTileAlignment::Equal;
1930 // otherwise we require a statically-provable equality.
1931 int64_t numTiles = unPackOp.getInnerDimsPos().size();
1932 ArrayRef<int64_t> innerDimsPos = unPackOp.getInnerDimsPos();
1933 SmallVector<OpFoldResult> mixedTiles = unPackOp.getMixedTiles();
1934 ArrayRef<OpFoldResult> innerSizes = sizes.take_back(numTiles);
1935 for (int64_t i = 0; i < numTiles; ++i) {
1936 // `innerTileAlignments` is indexed by the unpack iteration domain (the
1937 // dest dims); the i-th inner tile lives on dest dim `innerDimsPos[i]`.
1938 int64_t destDim = innerDimsPos[i];
1939 bool hintedEqual =
1940 destDim < static_cast<int64_t>(innerTileAlignments.size()) &&
1941 innerTileAlignments[destDim] == InnerTileAlignment::Equal;
1942 // The hint is the source of truth: honor a caller `Equal` assertion. When
1943 // both sizes are also statically known, assert the hint agrees with them
1944 // (a contradicting hint is a caller bug) rather than silently ignoring
1945 // it. Without an `Equal` hint, require a statically-provable equality
1946 // (the inner dim must not be tiled).
1947 if (hintedEqual) {
1948 assert((!getConstantIntValue(mixedTiles[i]) ||
1949 !getConstantIntValue(innerSizes[i]) ||
1950 isEqualConstantIntOrValue(mixedTiles[i], innerSizes[i])) &&
1951 "InnerTileAlignment::Equal contradicts statically known tile "
1952 "sizes");
1953 continue;
1954 }
1955 if (isEqualConstantIntOrValue(mixedTiles[i], innerSizes[i]))
1956 continue;
1957 return failure();
1958 }
1959
1960 Location loc = unPackOp.getLoc();
1961
1962 // Fetch offset/size for creating the slice of the dest operand of
1963 // unpack op.
1964 SmallVector<OpFoldResult> outputOffsets, outputSizes;
1965 if (failed(getIterationDomainTileFromOperandTiles(
1966 op, b, operandNumbers, allOffsets, allSizes, outputOffsets,
1967 outputSizes)))
1968 return failure();
1969
1970 auto oneAttr = b.getI64IntegerAttr(1);
1971 int64_t outputRank = unPackOp.getDestRank();
1972 SmallVector<OpFoldResult> strides(outputRank, oneAttr);
1973
1974 SmallVector<Value> tiledOperands;
1975 // Create slice of the dest operand.
1976 auto extractDestSlice = tensor::ExtractSliceOp::create(
1977 b, loc, unPackOp.getDest(), outputOffsets, outputSizes, strides);
1978 tiledOperands.push_back(extractDestSlice);
1979
1980 strides.append(unPackOp.getSourceRank() - outputRank, oneAttr);
1981 // Create slice of the source operand.
1982 auto extractSourceSlice = tensor::ExtractSliceOp::create(
1983 b, loc, unPackOp.getSource(), offsets, sizes, strides);
1984 tiledOperands.insert(tiledOperands.begin(), extractSourceSlice);
1985 for (auto tile : unPackOp.getInnerTiles())
1986 tiledOperands.push_back(tile);
1987
1988 // Create tiled unpack op.
1989 Operation *tiledUnPackOp =
1990 UnPackOp::create(b, loc, TypeRange{extractDestSlice.getType()},
1991 tiledOperands, op->getAttrs());
1992
1993 return TilingResult{{tiledUnPackOp},
1994 SmallVector<Value>(tiledUnPackOp->getResults()),
1995 llvm::to_vector(ArrayRef<Operation *>{
1996 extractSourceSlice, extractDestSlice})};
1997 }
1998};
1999
2000} // namespace
2001
2002template <typename OpType>
2003static void registerOne(MLIRContext *ctx) {
2004 OpType::template attachInterface<LinalgOpTilingInterface<OpType>>(*ctx);
2005 OpType::template attachInterface<LinalgOpPartialReductionInterface<OpType>>(
2006 *ctx);
2007}
2008
2009/// Variadic helper function.
2010template <typename... OpTypes>
2011static void registerAll(MLIRContext *ctx) {
2012 (registerOne<OpTypes>(ctx), ...);
2013}
2014
2015#define GET_OP_LIST
2016
2018 DialectRegistry &registry) {
2019 registry.addExtension(+[](MLIRContext *ctx, linalg::LinalgDialect *dialect) {
2021 linalg::PackOp::attachInterface<PackOpTiling>(*ctx);
2022 linalg::UnPackOp::attachInterface<UnPackOpTiling>(*ctx);
2024#include "mlir/Dialect/Linalg/IR/LinalgStructuredOps.cpp.inc"
2025 >(ctx);
2026 });
2027}
2028
2030 DialectRegistry &registry) {
2031 registry.addExtension(+[](MLIRContext *ctx, LinalgDialect *dialect) {
2032 linalg::PackOp::attachInterface<PackOpTiling>(*ctx);
2033 linalg::UnPackOp::attachInterface<UnPackOpTiling>(*ctx);
2034 });
2035}
return success()
static bool isTiled(AffineExpr expr, ArrayRef< OpFoldResult > tileSizes)
Definition Utils.cpp:76
lhs
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
auto load
static RankedTensorType sliceResultType(Type operandType, GridOp grid, ArrayRef< GridAxis > gridAxes, int64_t sliceAxis)
static LogicalResult getResultTilePosition(RewriterBase &rewriter, ReductionTilingStrategy reductionStrategy, int64_t index, Value tiledResult, TilingInterface op, ArrayRef< OpFoldResult > offsets, ArrayRef< OpFoldResult > sizes, ValueRange ivs, ArrayRef< OpFoldResult > numThreads, ArrayRef< OpFoldResult > givenTileSizes, const SetVector< unsigned > &reductionDims, SmallVector< OpFoldResult > &resultOffset, SmallVector< OpFoldResult > &resultSize)
static FailureOr< TilingResult > getTiledImplementation(RewriterBase &rewriter, TilingInterface op, ReductionTilingStrategy reductionStrategy, ValueRange regionIterArg, ArrayRef< OpFoldResult > offsets, ArrayRef< OpFoldResult > sizes, ValueRange ivs, ArrayRef< OpFoldResult > numThreads, ArrayRef< OpFoldResult > givenTileSizes, ArrayRef< InnerTileAlignment > innerTileAlignments, const SetVector< unsigned > &reductionDims)
static LogicalResult inlinePayload(OpBuilder &b, LinalgOp linalgOp, ValueRange ivs, ValueRange argValues)
Method to inline the payload of a linalgOp given the iteration space point and values for the argumen...
static SmallVector< Value > getIndicesForAccess(OpBuilder &b, Location loc, AffineMap indexingMap, ValueRange ivs)
Return the SSA values that represent the data point accessed using a given indexingMap for a given po...
static LogicalResult validateTilingSemiAffineMaps(LinalgOp linalgOp, ArrayRef< OpFoldResult > sizes)
Verify that tiling can be applied in presence of semi-affine maps.
static bool isInBounds(TransferOp op, int64_t resultIdx, int64_t indicesIdx)
Base type for affine expression.
Definition AffineExpr.h:68
RetT walk(FnT &&callback) const
Walk all of the AffineExpr's in this expression in postorder.
Definition AffineExpr.h:117
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition AffineMap.h:46
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
bool isProjectedPermutation(bool allowZeroInResults=false) const
Returns true if the AffineMap represents a subset (i.e.
unsigned getNumSymbols() const
unsigned getNumDims() const
ArrayRef< AffineExpr > getResults() const
unsigned getNumResults() const
Attributes are known-constant values of operations.
Definition Attributes.h:25
Block represents an ordered list of Operations.
Definition Block.h:33
Operation * getTerminator()
Get the terminator operation of this block.
Definition Block.cpp:249
BlockArgListType getArguments()
Definition Block.h:111
iterator_range< iterator > without_terminator()
Return an iterator range over the operation within this block excluding the terminator operation at t...
Definition Block.h:236
IntegerAttr getIndexAttr(int64_t value)
Definition Builders.cpp:116
MLIRContext * getContext() const
Definition Builders.h:56
The DialectRegistry maps a dialect namespace to a constructor for the matching dialect.
bool addExtension(TypeID extensionID, std::unique_ptr< DialectExtensionBase > extension)
Add the given extension to the registry.
This is a utility class for mapping one set of IR entities to another.
Definition IRMapping.h:26
auto lookupOrDefault(T from) const
Lookup a mapped value within the map.
Definition IRMapping.h:65
void map(Value from, Value to)
Inserts a new mapping for 'from' to 'to'.
Definition IRMapping.h:30
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:351
This class helps build Operations.
Definition Builders.h:210
void setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
Definition Builders.h:434
This class represents a single result from folding an operation.
This class represents an operand of an operation.
Definition Value.h:254
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Region & getRegion(unsigned index)
Returns the region held by this operation at position 'index'.
Definition Operation.h:711
void setOperand(unsigned idx, Value value)
Definition Operation.h:376
ArrayRef< NamedAttribute > getAttrs()
Return all of the attributes on this operation.
Definition Operation.h:537
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:403
result_range getResults()
Definition Operation.h:440
InFlightDiagnostic emitOpError(const Twine &message={})
Emit an error with the op name prefixed, like "'dim' op " which is convenient for verifiers.
void cloneInto(Region *dest, IRMapping &mapper)
Clone the internal blocks from this region into dest.
Definition Region.cpp:70
static FailureOr< int64_t > computeConstantBound(presburger::BoundType type, const Variable &var, const StopConditionFn &stopCondition=nullptr, ValueBoundsOptions options={})
Compute a constant bound for the given variable.
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
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
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
A utility result that is used to signal how to proceed with an ongoing walk:
Definition WalkResult.h:29
static WalkResult advance()
Definition WalkResult.h:47
bool wasInterrupted() const
Returns true if the walk was interrupted.
Definition WalkResult.h:51
static WalkResult interrupt()
Definition WalkResult.h:46
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:398
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< Value > makeTiledShapes(OpBuilder &builder, Location loc, LinalgOp linalgOp, ValueRange valuesToTile, ArrayRef< OpFoldResult > ivs, ArrayRef< OpFoldResult > tileSizes, ArrayRef< OpFoldResult > sizeBounds, bool omitPartialTileCheck)
Creates extract_slice/subview ops for all valuesToTile of the given linalgOp with builder,...
Definition Utils.cpp:2850
void registerTilingInterfaceExternalModelsForPackUnPackOps(DialectRegistry &registry)
Similar to the above registeration, but it is only for tensor.pack and tensor.unpack ops.
static void registerOne(MLIRContext *ctx)
static void registerAll(MLIRContext *ctx)
Variadic helper function.
void offsetIndices(OpBuilder &b, LinalgOp linalgOp, ArrayRef< OpFoldResult > offests)
Add the specified offsets to any linalg.index ops contained in the given linalgOp.
Definition Utils.cpp:2872
Value createOrFoldDimOp(OpBuilder &b, Location loc, Value val, int64_t dim)
Create one memref::DimOp or tensor::DimOp depending on the type of val.
void registerTilingInterfaceExternalModels(DialectRegistry &registry)
SmallVector< Type > getTensorOutputTypes(LinalgOp op, ValueRange operands)
Returns the list of tensor output types produced when the given structured operation op is applied to...
Definition Utils.cpp:2761
SliceParameters computeSliceParameters(OpBuilder &builder, Location loc, Value valueToTile, ArrayRef< OpFoldResult > tileSizes, AffineMap map, ArrayRef< OpFoldResult > lbs, ArrayRef< OpFoldResult > ubs, ArrayRef< OpFoldResult > subShapeSizes, bool omitPartialTileCheck)
Computes SliceParameters for a single valueToTile assuming that its user is being tiled with the give...
Definition Utils.cpp:2614
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
SmallVector< OpFoldResult > getMixedSizes(OpBuilder &builder, Location loc, Value value)
Return the dimensions of the given tensor value.
Definition TensorOps.cpp:90
Include the generated interface declarations.
ReductionTilingStrategy
Tiling can be thought of as splitting a dimension into 2 and materializing the outer dimension as a l...
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.
void bindDims(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to DimExpr at positions: [0 .
Definition AffineExpr.h:311
SmallVector< SmallVector< OpFoldResult > > ReifiedRankedShapedTypeDims
Value matchReduction(ArrayRef< BlockArgument > iterCarriedArgs, unsigned redPos, SmallVectorImpl< Operation * > &combinerOps)
Utility to match a generic reduction given a list of iteration-carried arguments, iterCarriedArgs and...
AffineExprKind
Definition AffineExpr.h:40
@ CeilDiv
RHS of ceildiv is always a constant or a symbolic expression.
Definition AffineExpr.h:50
@ Mod
RHS of mod is always a constant or a symbolic expression with a positive value.
Definition AffineExpr.h:46
@ FloorDiv
RHS of floordiv is always a constant or a symbolic expression.
Definition AffineExpr.h:48
llvm::SetVector< T, Vector, Set, N > SetVector
Definition LLVM.h:125
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
bool isZeroInteger(OpFoldResult v)
Return "true" if v is an integer value/attribute with constant value 0.
void bindSymbols(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to SymbolExpr at positions: [0 .
Definition AffineExpr.h:325
Value getValueOrCreateConstantIndexOp(OpBuilder &b, Location loc, OpFoldResult ofr)
Converts an OpFoldResult to a Value.
Definition Utils.cpp:114
Operation * clone(OpBuilder &b, Operation *op, TypeRange newResultTypes, ValueRange newOperands)
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
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
void applyPermutationToVector(SmallVector< T, N > &inVec, ArrayRef< int64_t > permutation)
Apply the permutation defined by permutation to inVec.
InnerTileAlignment
Per-dimension alignment of a loop tile size to a linalg.pack / linalg.unpack inner tile size,...
std::pair< SmallVector< int64_t >, SmallVector< Value > > decomposeMixedValues(ArrayRef< OpFoldResult > mixedValues)
Decompose a vector of mixed static or dynamic values into the corresponding pair of arrays.
SmallVector< int64_t > invertPermutationVector(ArrayRef< int64_t > permutation)
Helper method to apply to inverse a permutation.
Helper struct to build simple arithmetic quantities with minimal type inference support.
Definition Utils.h:103
Container for result values of tiling.
Options that control value bound computation.
Helper struct to build simple AffineValueExprs with minimal type inference support.
Definition Utils.h:377
A struct containg offsets-sizes-strides arguments of the tiled shape.
Definition Utils.h:172
SmallVector< OpFoldResult > sizes
Definition Utils.h:174
SmallVector< OpFoldResult > offsets
Definition Utils.h:173
Eliminates variable at the specified position using Fourier-Motzkin variable elimination.