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