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 // TODO: Support Memref PackOp. Temporarily return failure.
1134 if (!packOp.hasPureTensorSemantics())
1135 return failure();
1136
1137 Location loc = packOp.getLoc();
1138
1139 // The tiling is applied on interchanged dimensions. We have to undo the
1140 // interchange to map sizes and offsets to the original input.
1141 int64_t inputRank = packOp.getSourceRank();
1142 SmallVector<OpFoldResult> origOffsets(offsets);
1143 SmallVector<OpFoldResult> origSizes(sizes);
1144 applyPermToRange(origOffsets, origSizes,
1145 invertPermutationVector(packOp.getOuterDimsPerm()));
1146
1147 DenseMap<int64_t, OpFoldResult> dimAndTileMapping =
1148 packOp.getDimAndTileMapping();
1149 SmallVector<OpFoldResult> srcDimValues =
1150 tensor::getMixedSizes(b, loc, packOp.getSource());
1151 SmallVector<OpFoldResult> inputIndices, inputSizes;
1152 for (auto dim : llvm::seq<int64_t>(0, inputRank)) {
1153 using AV = affine::AffineValueExpr;
1154 affine::AffineBuilder ab(b, loc);
1155 AffineExpr dim0, dim1, sym;
1156 bindDims(b.getContext(), dim0, dim1);
1157 bindSymbols(b.getContext(), sym);
1158 if (dimAndTileMapping.count(dim)) {
1159 // If the data dimension is tiled, the i-th index is the product of
1160 // offset_i and tile_i, and the i-th size is the product of sizes_i and
1161 // tile_i.
1162 auto avOffset = AV(dim0).bind(origOffsets[dim]);
1163 auto avSize = AV(dim0).bind(origSizes[dim]);
1164 auto avTileSize = AV(sym).bind(dimAndTileMapping[dim]);
1165 inputIndices.push_back(ab.mul(avOffset, avTileSize));
1166 inputSizes.push_back(ab.mul(avSize, avTileSize));
1167 } else {
1168 inputIndices.push_back(origOffsets[dim]);
1169 inputSizes.push_back(origSizes[dim]);
1170 }
1171
1172 // Limit the size of the input operand for incomplete tiles.
1173 if (packOp.getPaddingValue()) {
1174 OpFoldResult dimSize = srcDimValues[dim];
1175 auto avDimSize = AV(dim0).bind(dimSize);
1176 auto avInputIdx = AV(dim1).bind(inputIndices.back());
1177 inputSizes.back() =
1178 ab.min({inputSizes.back(), ab.sub(avDimSize, avInputIdx)});
1179 }
1180 }
1181
1182 auto oneAttr = b.getI64IntegerAttr(1);
1183 SmallVector<OpFoldResult> strides(inputRank, oneAttr);
1184
1185 SmallVector<Value> tiledOperands;
1186 auto sourceSlice = tensor::ExtractSliceOp::create(
1187 b, loc, packOp.getSource(), inputIndices, inputSizes, strides);
1188 tiledOperands.push_back(sourceSlice);
1189
1190 SmallVector<OpFoldResult> outputOffsets, outputSizes;
1191 if (failed(getResultTilePosition(op, b, 0, offsets, sizes, outputOffsets,
1192 outputSizes)))
1193 return {};
1194
1195 strides.append(packOp.getDestRank() - inputRank, oneAttr);
1196 auto outSlice = tensor::ExtractSliceOp::create(
1197 b, loc, packOp.getDest(), outputOffsets, outputSizes, strides);
1198 tiledOperands.push_back(outSlice);
1199
1200 if (auto val = packOp.getPaddingValue())
1201 tiledOperands.push_back(val);
1202 for (auto tile : packOp.getInnerTiles())
1203 tiledOperands.push_back(tile);
1204
1205 PackOp tiledPackOp =
1206 PackOp::create(b, loc, TypeRange{outSlice.getType()}, tiledOperands,
1207 packOp.getProperties(),
1208 packOp->getDiscardableAttrDictionary().getValue());
1209
1210 return TilingResult{
1211 {tiledPackOp},
1212 SmallVector<Value>(tiledPackOp->getResults()),
1213 llvm::to_vector(ArrayRef<Operation *>{sourceSlice, outSlice})};
1214 }
1215
1216 LogicalResult
1217 getResultTilePosition(Operation *op, OpBuilder &b, unsigned resultNumber,
1218 ArrayRef<OpFoldResult> offsets,
1219 ArrayRef<OpFoldResult> sizes,
1220 SmallVector<OpFoldResult> &resultOffsets,
1221 SmallVector<OpFoldResult> &resultSizes) const {
1222 // The iteration domain is over outer dimensions of packed layout. In this
1223 // context, the outer dimensions of `resultOffsets` are `offsets`. The
1224 // inner dimensions of `resultOffsets` are zeros because tiling is not
1225 // applied to them.
1226 auto packOp = cast<PackOp>(op);
1227 int64_t inputRank = packOp.getSourceRank();
1228 int64_t outputRank = packOp.getDestRank();
1229 auto zeroAttr = b.getI64IntegerAttr(0);
1230 resultOffsets.assign(offsets.begin(), offsets.end());
1231 resultOffsets.append(outputRank - inputRank, zeroAttr);
1232
1233 ReifiedRankedShapedTypeDims outputShape;
1234 (void)reifyResultShapes(b, packOp, outputShape);
1235 resultSizes.assign(sizes.begin(), sizes.end());
1236 for (auto dataTileDim : llvm::seq<unsigned>(inputRank, outputRank))
1237 resultSizes.push_back(outputShape[0][dataTileDim]);
1238
1239 return success();
1240 }
1241
1242 FailureOr<TilingResult>
1243 generateResultTileValue(Operation *op, OpBuilder &b, unsigned resultNumber,
1244 ArrayRef<OpFoldResult> offsets,
1245 ArrayRef<OpFoldResult> sizes) const {
1246 return generateResultTileValue(op, b, resultNumber, offsets, sizes,
1247 /*innerTileAlignments=*/{});
1248 }
1249
1250 FailureOr<TilingResult> generateResultTileValue(
1251 Operation *op, OpBuilder &b, unsigned resultNumber,
1252 ArrayRef<OpFoldResult> offsets, ArrayRef<OpFoldResult> sizes,
1253 ArrayRef<InnerTileAlignment> innerTileAlignments) const {
1254 auto packOp = cast<PackOp>(op);
1255 int64_t numTiles = packOp.getInnerDimsPos().size();
1256
1257 // linalg.pack op is fusible (as a producer) only if full inner tiles are
1258 // iterated or inner dims are not tiled. Otherwise, it will generate a
1259 // sequence of non-trivial ops (for partial tiles).
1260 for (auto offset : offsets.take_back(numTiles))
1261 if (!isZeroInteger(offset))
1262 return failure();
1263
1264 // Each requested inner-dim size must cover a full inner tile. A caller may
1265 // instead assert this via an `Equal` alignment hint. The hint is indexed by
1266 // source dim, matching the consumer-fusion path.
1267 ArrayRef<int64_t> innerDimsPos = packOp.getInnerDimsPos();
1268 SmallVector<OpFoldResult> mixedTiles = packOp.getMixedTiles();
1269 ArrayRef<OpFoldResult> innerSizes = sizes.take_back(numTiles);
1270 for (auto [i, pos] : llvm::enumerate(innerDimsPos)) {
1271 InnerTileAlignment alignment =
1272 pos < static_cast<int64_t>(innerTileAlignments.size())
1273 ? innerTileAlignments[pos]
1274 : InnerTileAlignment::Unknown;
1275 if (alignment != InnerTileAlignment::Equal &&
1276 !isEqualConstantIntOrValue(mixedTiles[i], innerSizes[i]))
1277 return failure();
1278 }
1279
1280 FailureOr<TilingResult> tilingResult = getTiledImplementation(
1281 op, b, offsets.drop_back(numTiles), sizes.drop_back(numTiles));
1282 if (failed(tilingResult))
1283 return failure();
1284 return tilingResult.value();
1285 }
1286
1287 LogicalResult generateScalarImplementation(Operation *op, OpBuilder &builder,
1288 Location loc,
1289 ValueRange ivs) const {
1290 auto packOp = cast<PackOp>(op);
1291 assert(packOp.hasPureBufferSemantics() &&
1292 "expected operation to have buffer semantics");
1293 OpBuilder::InsertionGuard g(builder);
1294 // The `ivs` already represent the position into the output for the non
1295 // data-tile dimensions.
1296 SmallVector<Value> ivVec(ivs);
1297
1298 // Get output shape - for memrefs, get dimensions from dest directly.
1299 SmallVector<OpFoldResult> outputShape;
1300 Value dest = packOp.getDest();
1301 for (auto dim : llvm::seq<int64_t>(0, packOp.getDestRank()))
1302 outputShape.push_back(createOrFoldDimOp(builder, loc, dest, dim));
1303
1304 // Generate the loops that iterate over the data tile.
1305 Value zero = arith::ConstantIndexOp::create(builder, loc, 0);
1306 Value one = arith::ConstantIndexOp::create(builder, loc, 1);
1307
1308 // All loops except the innermost are simple loops that just iterate
1309 // over the tile dimensions.
1310 for (auto dataTileDim : llvm::seq<unsigned>(packOp.getSourceRank(),
1311 packOp.getDestRank() - 1)) {
1312 Value ub = getValueOrCreateConstantIndexOp(builder, loc,
1313 outputShape[dataTileDim]);
1314 scf::ForOp loop = scf::ForOp::create(builder, loc, zero, ub, one);
1315 builder.setInsertionPointToStart(loop.getBody());
1316 ivVec.push_back(loop.getInductionVar());
1317 }
1318 // The body of the innermost loops does the actual data movement.
1319 scf::ForOp::create(
1320 builder, loc, zero,
1321 getValueOrCreateConstantIndexOp(builder, loc, outputShape.back()), one,
1322 ValueRange{},
1323 [&](OpBuilder &bodyBuilder, Location bodyLoc, Value iv,
1324 ValueRange regionIterArgs) {
1325 ivVec.push_back(iv);
1326 generatePackOpScalarImplementationBody(packOp, bodyBuilder, bodyLoc,
1327 ivVec);
1328 scf::YieldOp::create(bodyBuilder, bodyLoc);
1329 });
1330 return success();
1331 }
1332
1333 LogicalResult getIterationDomainTileFromOperandTiles(
1334 Operation *op, OpBuilder &b, ArrayRef<unsigned> operandNumbers,
1335 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1336 ArrayRef<SmallVector<OpFoldResult>> allSizes,
1337 SmallVectorImpl<OpFoldResult> &resultOffsets,
1338 SmallVectorImpl<OpFoldResult> &resultSizes) const {
1339 return getIterationDomainTileFromOperandTiles(
1340 op, b, operandNumbers, allOffsets, allSizes, resultOffsets, resultSizes,
1341 /*innerTileAlignments=*/{});
1342 }
1343
1344 /// Method to return the position of iteration domain tile computed by the
1345 /// tiled operation. In current `linalg.pack` context, the `resultOffsets` and
1346 /// `resultSizes` only cover outer dimensions.
1347 LogicalResult getIterationDomainTileFromOperandTiles(
1348 Operation *op, OpBuilder &b, ArrayRef<unsigned> operandNumbers,
1349 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1350 ArrayRef<SmallVector<OpFoldResult>> allSizes,
1351 SmallVectorImpl<OpFoldResult> &resultOffsets,
1352 SmallVectorImpl<OpFoldResult> &resultSizes,
1353 ArrayRef<InnerTileAlignment> innerTileAlignments) const {
1354 if (operandNumbers.size() != 1 || operandNumbers[0] != 0) {
1355 LLVM_DEBUG(
1356 { llvm::dbgs() << "unsupported operands for consumer fusion"; });
1357 return failure();
1358 }
1359
1360 ArrayRef<OpFoldResult> offsets(allOffsets[0]);
1361 ArrayRef<OpFoldResult> sizes(allSizes[0]);
1362 auto packOp = cast<PackOp>(op);
1363 Location loc = packOp.getLoc();
1364 SmallVector<OpFoldResult> outerDimOffsets, outerDimSizes;
1365 DenseMap<int64_t, OpFoldResult> dimAndTileMapping =
1366 packOp.getDimAndTileMapping();
1367 SmallVector<int64_t> outerShapeWithoutTranspose(
1368 packOp.getDestType().getShape().take_front(packOp.getSourceRank()));
1369 if (!packOp.getOuterDimsPerm().empty()) {
1371 outerShapeWithoutTranspose,
1372 invertPermutationVector(packOp.getOuterDimsPerm()));
1373 }
1374 for (auto dim : llvm::seq<int64_t>(packOp.getSourceRank())) {
1375 if (dimAndTileMapping.count(dim)) {
1376 FailureOr<int64_t> cstTileSize =
1378 presburger::BoundType::UB, sizes[dim],
1379 /*stopCondition=*/nullptr,
1380 ValueBoundsOptions{/*closedUB=*/true});
1381 std::optional<int64_t> cstInnerSize =
1382 getConstantIntValue(dimAndTileMapping[dim]);
1383
1384 // A caller-supplied alignment hint (see InnerTileAlignment) asserts
1385 // that this packed dimension is tiled and how its loop tile size
1386 // relates to the pack op inner tile size.
1387 InnerTileAlignment innerTileAlignment =
1388 dim < static_cast<int64_t>(innerTileAlignments.size())
1389 ? innerTileAlignments[dim]
1390 : InnerTileAlignment::Unknown;
1391
1392 // If a dimension is not tiled, it is always valid to fuse the pack op,
1393 // even if the op has padding semantics. Because it always generates a
1394 // full slice along the dimension. The tile sizes are for unpacked
1395 // domain, i.e., `srcDimSize`, so `tileSize < srcDimSize` means that the
1396 // dimension is tiled.
1397 // TODO: It could be untiled if the `srcDimSize` is dynamic. It is a
1398 // hard check to determine if a dimension is tiled or not.
1399 // A non-`Unknown` hint also means the caller asserts the dimension is
1400 // tiled: `cstTileSize` is an upper bound, so a scalable/`min`-shaped
1401 // tile (whose bound equals `srcDimSize`) would otherwise be mistaken
1402 // for untiled and bypass the hint below.
1403 int64_t srcDimSize = packOp.getSourceType().getDimSize(dim);
1404 int64_t destDimSize = outerShapeWithoutTranspose[dim];
1405 bool isTiled = innerTileAlignment != InnerTileAlignment::Unknown ||
1406 failed(cstTileSize) ||
1407 ShapedType::isDynamic(srcDimSize) ||
1408 cstTileSize.value() < srcDimSize;
1409 if (!isTiled) {
1410 outerDimOffsets.push_back(offsets[dim]);
1411 if (ShapedType::isStatic(destDimSize)) {
1412 outerDimSizes.push_back(b.getIndexAttr(destDimSize));
1413 } else {
1414 outerDimSizes.push_back(
1415 b.createOrFold<tensor::DimOp>(loc, packOp.getDest(), dim));
1416 }
1417 continue;
1418 }
1419
1420 // Currently fusing `packOp` as consumer only expects perfect tiling
1421 // scenario because even if without padding semantic, the `packOp` may
1422 // also yield incomplete tiles. E.g. tensor<30xf32> -> tensor<5x6xf32>,
1423 // where the `tileSize` from operand of `packOp` is 5, which is not
1424 // exactly divided by `innerTile`(=6) of `packOp`. As the result:
1425 // 1. the first slice is extracted from (0) to (4) and inserted into
1426 // (0,0)~(0,4) at first row.
1427 // 2. the second slice is extracted from (5) to (9) and SHOULD BE
1428 // respectively inserted into two rows with different length, including
1429 // first row: (0,5) and second row (1,0)~(1,3). It is hard to coordinate
1430 // them, thus adding below constraint to bypass them temporarily. In
1431 // another word, we can only support tiling with consumer if the tile
1432 // size for the producer is a multiple of the inner tile size for the
1433 // packed dimensions at this moment.
1434
1435 // The caller may assert how this packed dimension's loop tile size
1436 // relates to the inner tile size via `innerTileAlignments` (see
1437 // InnerTileAlignment). The hint is the source of truth and is honored
1438 // when present. When both sizes are also statically known we assert the
1439 // hint agrees with them (a contradicting hint is a caller bug). When
1440 // the hint is `Unknown`, fall back to requiring a statically-provable
1441 // multiple.
1442 bool assumeInnerTileSizesMatchTiles =
1443 innerTileAlignment == InnerTileAlignment::Equal;
1444 bool staticallyDecidable =
1445 !failed(cstTileSize) && cstInnerSize.has_value();
1446 if (innerTileAlignment == InnerTileAlignment::Unknown) {
1447 if (!staticallyDecidable || *cstTileSize % *cstInnerSize != 0)
1448 return failure();
1449 } else if (staticallyDecidable) {
1450 assert(*cstTileSize % *cstInnerSize == 0 &&
1451 "InnerTileAlignment hint contradicts statically known tile "
1452 "sizes");
1453 assert((innerTileAlignment != InnerTileAlignment::Equal ||
1454 *cstTileSize == *cstInnerSize) &&
1455 "InnerTileAlignment::Equal contradicts statically known tile "
1456 "sizes");
1457 }
1458
1459 using AV = affine::AffineValueExpr;
1460 affine::AffineBuilder ab(b, loc);
1461 AffineExpr dim0, sym;
1462 bindDims(b.getContext(), dim0);
1463 bindSymbols(b.getContext(), sym);
1464 auto avOffset = AV(dim0).bind(offsets[dim]);
1465 auto avSize = AV(dim0).bind(sizes[dim]);
1466 auto avTileSize = AV(sym).bind(dimAndTileMapping[dim]);
1467 outerDimOffsets.push_back(ab.floor(avOffset, avTileSize));
1468 // If the tile size equals the inner tile size, the outer dims are
1469 // always 1.
1470 outerDimSizes.push_back(assumeInnerTileSizesMatchTiles
1471 ? b.getIndexAttr(1)
1472 : ab.ceil(avSize, avTileSize));
1473 } else {
1474 outerDimOffsets.push_back(offsets[dim]);
1475 outerDimSizes.push_back(sizes[dim]);
1476 }
1477 }
1478 applyPermToRange(outerDimOffsets, outerDimSizes, packOp.getOuterDimsPerm());
1479 resultOffsets = outerDimOffsets;
1480 resultSizes = outerDimSizes;
1481 return success();
1482 }
1483
1484 FailureOr<TilingResult> getTiledImplementationFromOperandTiles(
1485 Operation *op, OpBuilder &b, ArrayRef<unsigned> operandNumbers,
1486 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1487 ArrayRef<SmallVector<OpFoldResult>> allSizes) const {
1488 return getTiledImplementationFromOperandTiles(op, b, operandNumbers,
1489 allOffsets, allSizes,
1490 /*innerTileAlignments=*/{});
1491 }
1492
1493 /// Method to return the tiled implementation of linalg.pack as a consumer.
1494 FailureOr<TilingResult> getTiledImplementationFromOperandTiles(
1495 Operation *op, OpBuilder &b, ArrayRef<unsigned> operandNumbers,
1496 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1497 ArrayRef<SmallVector<OpFoldResult>> allSizes,
1498 ArrayRef<InnerTileAlignment> innerTileAlignments) const {
1499 if (operandNumbers.size() != 1 || operandNumbers[0] != 0) {
1500 LLVM_DEBUG({ llvm::dbgs() << "unhandled operands for consumer fusion"; });
1501 return failure();
1502 }
1503
1504 ArrayRef<OpFoldResult> offsets(allOffsets[0]);
1505 ArrayRef<OpFoldResult> sizes(allSizes[0]);
1506
1507 auto packOp = cast<PackOp>(op);
1508 // TODO: Support Memref UnPackOp. Temporarily return failure.
1509 if (!packOp.hasPureTensorSemantics())
1510 return failure();
1511
1512 Location loc = packOp.getLoc();
1513
1514 int64_t inputRank = packOp.getSourceRank();
1515 auto oneAttr = b.getI64IntegerAttr(1);
1516 SmallVector<OpFoldResult> strides(inputRank, oneAttr);
1517
1518 SmallVector<Value> tiledOperands;
1519 auto sourceSlice = tensor::ExtractSliceOp::create(
1520 b, loc, packOp.getSource(), offsets, sizes, strides);
1521 tiledOperands.push_back(sourceSlice);
1522
1523 SmallVector<OpFoldResult> outerDimOffsets, outerDimSizes;
1524 if (failed(getIterationDomainTileFromOperandTiles(
1525 op, b, operandNumbers, allOffsets, allSizes, outerDimOffsets,
1526 outerDimSizes, innerTileAlignments)))
1527 return failure();
1528
1529 SmallVector<OpFoldResult> outputOffsets, outputSizes;
1530 if (failed(getResultTilePosition(op, b, 0, outerDimOffsets, outerDimSizes,
1531 outputOffsets, outputSizes)))
1532 return failure();
1533
1534 strides.append(packOp.getDestRank() - inputRank, oneAttr);
1535 auto outSlice = tensor::ExtractSliceOp::create(
1536 b, loc, packOp.getDest(), outputOffsets, outputSizes, strides);
1537 tiledOperands.push_back(outSlice);
1538
1539 if (auto val = packOp.getPaddingValue())
1540 tiledOperands.push_back(val);
1541 for (auto tile : packOp.getInnerTiles())
1542 tiledOperands.push_back(tile);
1543
1544 PackOp tiledPackOp =
1545 PackOp::create(b, loc, TypeRange{outSlice.getType()}, tiledOperands,
1546 packOp.getProperties(),
1547 packOp->getDiscardableAttrDictionary().getValue());
1548
1549 return TilingResult{
1550 {tiledPackOp},
1551 SmallVector<Value>(tiledPackOp->getResults()),
1552 llvm::to_vector(ArrayRef<Operation *>{sourceSlice, outSlice})};
1553 }
1554};
1555
1556struct UnpackTileDimInfo {
1557 bool isAlignedToInnerTileSize;
1558 OpFoldResult sourceOffset;
1559 OpFoldResult sourceSize;
1560 OpFoldResult resultOffset;
1561 OpFoldResult destExpandedSize;
1562};
1563
1564/// Returns the needed information for tiling unpack op on `tileDim` with given
1565/// `tileOffset` and `tileSize`. For more details, see the comment of the
1566/// `getTiledImplementation`.
1567static UnpackTileDimInfo
1568getUnpackTileDimInfo(OpBuilder &b, UnPackOp unpackOp, int64_t tileDim,
1569 OpFoldResult tileOffset, OpFoldResult tileSize,
1570 InnerTileAlignment innerTileAlignment) {
1571 UnpackTileDimInfo info;
1572 Attribute zeroAttr = b.getIndexAttr(0);
1573 Attribute oneAttr = b.getIndexAttr(1);
1574 DenseMap<int64_t, OpFoldResult> dimAndTileMapping =
1575 unpackOp.getDimAndTileMapping();
1576 // The dimension is not one of packed data dimension.
1577 if (!dimAndTileMapping.count(tileDim)) {
1578 info.isAlignedToInnerTileSize = true;
1579 info.sourceOffset = tileOffset;
1580 info.sourceSize = tileSize;
1581 info.resultOffset = zeroAttr;
1582 info.destExpandedSize = tileSize;
1583 return info;
1584 }
1585
1586 Location loc = unpackOp.getLoc();
1587 using AV = affine::AffineValueExpr;
1588 affine::AffineBuilder ab(b, loc);
1589 AffineExpr dim0, dim1, sym0;
1590 bindDims(b.getContext(), dim0, dim1);
1591 bindSymbols(b.getContext(), sym0);
1592
1593 OpFoldResult innerTileSize = dimAndTileMapping[tileDim];
1594
1595 info.isAlignedToInnerTileSize = false;
1596 FailureOr<int64_t> cstSize = ValueBoundsConstraintSet::computeConstantBound(
1597 presburger::BoundType::UB, tileSize,
1598 /*stopCondition=*/nullptr, ValueBoundsOptions{/*closedUB=*/true});
1599 std::optional<int64_t> cstInnerSize = getConstantIntValue(innerTileSize);
1600 // The caller may assert how this dimension's loop tile size relates to the
1601 // op's inner tile size via `innerTileAlignment` (see InnerTileAlignment). The
1602 // hint is the source of truth and is honored when present: `Equal`/`Multiple`
1603 // both mean the tile is aligned to (a multiple of) the inner tile, and
1604 // `Equal` additionally collapses the source slice to a single inner tile.
1605 // When both sizes are also statically known we assert the hint agrees with
1606 // them (a contradicting hint is a caller bug). When `Unknown`, fall back to
1607 // the static upper-bound path below.
1608 bool assumeInnerTileSizesMatchTiles =
1609 innerTileAlignment == InnerTileAlignment::Equal;
1610 bool staticallyDecidable = !failed(cstSize) && cstInnerSize.has_value();
1611 if (innerTileAlignment != InnerTileAlignment::Unknown) {
1612 info.isAlignedToInnerTileSize = true;
1613 if (staticallyDecidable) {
1614 assert(*cstSize % *cstInnerSize == 0 &&
1615 "InnerTileAlignment hint contradicts statically known tile sizes");
1616 assert((innerTileAlignment != InnerTileAlignment::Equal ||
1617 *cstSize == *cstInnerSize) &&
1618 "InnerTileAlignment::Equal contradicts statically known tile "
1619 "sizes");
1620 }
1621 }
1622 if (info.isAlignedToInnerTileSize || (!failed(cstSize) && cstInnerSize)) {
1623 if (!info.isAlignedToInnerTileSize && *cstSize % *cstInnerSize == 0)
1624 info.isAlignedToInnerTileSize = true;
1625
1626 // If the tiling size equals to the inner tiling size, the outer dims are
1627 // always 1.
1628 if (assumeInnerTileSizesMatchTiles ||
1629 (cstInnerSize && !failed(cstSize) && *cstInnerSize == *cstSize)) {
1630 auto lhs = AV(dim0).bind(tileOffset);
1631 auto rhs = AV(dim1).bind(innerTileSize);
1632 info.sourceOffset = ab.floor(lhs, rhs);
1633 info.sourceSize = oneAttr;
1634 info.resultOffset = zeroAttr;
1635 info.destExpandedSize = tileSize;
1636 return info;
1637 }
1638 }
1639
1640 if (info.isAlignedToInnerTileSize) {
1641 info.sourceOffset =
1642 ab.floor(AV(dim0).bind(tileOffset), AV(dim1).bind(innerTileSize));
1643 info.resultOffset = zeroAttr;
1644 info.destExpandedSize = tileSize;
1645
1646 // The ceilDiv is needed here because there could be incomplete tile even
1647 // it is perfect tiling cases. E.g.,
1648 // %0 = unpack tensor<33x2xf32> into tensor<64xf32>
1649 // If the tiling size is 32, there will be 3 tiles. Two of them have
1650 // size=32; one of them have size=2. The size is represented using
1651 // affine_min op; we need ceilDiv.
1652 info.sourceSize =
1653 ab.ceil(AV(dim0).bind(tileSize), AV(dim1).bind(innerTileSize));
1654 return info;
1655 }
1656
1657 affine::DivModValue firstCoord = affine::getDivMod(
1658 b, loc, getValueOrCreateConstantIndexOp(b, loc, tileOffset),
1659 getValueOrCreateConstantIndexOp(b, loc, innerTileSize));
1660 OpFoldResult tileExclusiveBound =
1661 ab.add(AV(dim0).bind(tileOffset), AV(dim1).bind(tileSize));
1662 affine::DivModValue lastCoord = affine::getDivMod(
1663 b, loc,
1665 b, loc,
1666 ab.sub(AV(dim0).bind(tileExclusiveBound), AV(dim1).bind(oneAttr))),
1667 getValueOrCreateConstantIndexOp(b, loc, innerTileSize));
1668
1669 OpFoldResult lengthMinusOne = ab.sub(AV(dim0).bind(lastCoord.quotient),
1670 AV(dim1).bind(firstCoord.quotient));
1671 info.sourceSize =
1672 ab.add(AV(dim0).bind(lengthMinusOne), AV(dim1).bind(oneAttr));
1673 info.sourceOffset = firstCoord.quotient;
1674 info.resultOffset = firstCoord.remainder;
1675 // Do not create an Affine ops for expanded size because the affine op is too
1676 // complicated which would trigger an issue in affine ops simplification.
1677 info.destExpandedSize = b.createOrFold<arith::MulIOp>(
1678 loc, getValueOrCreateConstantIndexOp(b, loc, info.sourceSize),
1679 getValueOrCreateConstantIndexOp(b, loc, innerTileSize));
1680 return info;
1681}
1682
1683struct UnPackOpTiling
1684 : public TilingInterface::ExternalModel<UnPackOpTiling, linalg::UnPackOp> {
1685 using Base = TilingInterface::ExternalModel<UnPackOpTiling, linalg::UnPackOp>;
1686 using Base::getIterationDomainTileFromOperandTiles;
1687
1688 SmallVector<utils::IteratorType> getLoopIteratorTypes(Operation *op) const {
1689 auto unpackOp = cast<UnPackOp>(op);
1690 SmallVector<utils::IteratorType> iteratorTypes(
1691 unpackOp.getDestRank(), utils::IteratorType::parallel);
1692 return iteratorTypes;
1693 }
1694
1695 SmallVector<Range> getIterationDomain(Operation *op, OpBuilder &b) const {
1696 return getPackUnPackIterationDomain<UnPackOp>(cast<UnPackOp>(op), b);
1697 }
1698
1699 /// There are two cases in tiling unpack ops. If the tiling size is aligned to
1700 /// the inner tile size, the corresponding tiles of source are all complete.
1701 /// Otherwise, there are in-complete tiles. We will need to expand the slice
1702 /// of source for getting complete tiles. The tiled unpack op unpacks more
1703 /// data from source, so We'll need an extract_slice op to shift and truncate
1704 /// the output.
1705 /// Take Nn_to_N as an example. Say that N=32, n=8, and tiling_size=15. The
1706 /// coordinates of second tile (i.e., result[15..31]) are
1707 /// [(1, 7), (2, 0,), (2, 1) ... (3, 6), (3, 7)]. The first row and the last
1708 /// row are incomplete tiles. To represent the unpack op, we have to complete
1709 /// the rows. I.e., the input coordinates would start with (1, 0); end with
1710 /// (3, 7). In this context, the tiled unpack produces a (3 * n) elements
1711 /// because there are 3 rows in total. Follow by a tensor.extract_slice op, we
1712 /// can get the actual result.
1713 FailureOr<TilingResult>
1714 getTiledImplementation(Operation *op, OpBuilder &b,
1715 ArrayRef<OpFoldResult> offsets,
1716 ArrayRef<OpFoldResult> sizes) const {
1717 return getTiledImplementation(op, b, offsets, sizes,
1718 /*innerTileAlignments=*/{});
1719 }
1720
1721 FailureOr<TilingResult> getTiledImplementation(
1722 Operation *op, OpBuilder &b, ArrayRef<OpFoldResult> offsets,
1723 ArrayRef<OpFoldResult> sizes,
1724 ArrayRef<InnerTileAlignment> innerTileAlignments) const {
1725 auto unpackOp = cast<UnPackOp>(op);
1726 // TODO: Support Memref UnPackOp. Temporarily return failure.
1727 if (!unpackOp.hasPureTensorSemantics())
1728 return failure();
1729
1730 int64_t srcRank = unpackOp.getSourceRank();
1731 int64_t destRank = unpackOp.getDestRank();
1732 int64_t numInnerTiles = srcRank - destRank;
1733 Location loc = unpackOp.getLoc();
1734
1735 // The perfect tiling case indicates that the tiling sizes are multiple of
1736 // inner_tile_size. In this context, no extra data is needed when
1737 // representing the tiled unpack op.
1738 bool isPerfectTilingCase = true;
1739 Attribute oneAttr = b.getIndexAttr(1);
1740 SmallVector<OpFoldResult> sliceSrcStrides(destRank, oneAttr);
1741 SmallVector<OpFoldResult> sliceSrcIndices, sliceSrcSizes;
1742 SmallVector<OpFoldResult> destExpandedSizes, resultOffsetsFromDest;
1743 for (auto dim : llvm::seq<int64_t>(0, destRank)) {
1744 UnpackTileDimInfo info = getUnpackTileDimInfo(
1745 b, unpackOp, dim, offsets[dim], sizes[dim],
1746 dim < static_cast<int64_t>(innerTileAlignments.size())
1747 ? innerTileAlignments[dim]
1748 : InnerTileAlignment::Unknown);
1749 if (!info.isAlignedToInnerTileSize)
1750 isPerfectTilingCase = false;
1751 sliceSrcIndices.push_back(info.sourceOffset);
1752 sliceSrcSizes.push_back(info.sourceSize);
1753 destExpandedSizes.push_back(info.destExpandedSize);
1754 resultOffsetsFromDest.push_back(info.resultOffset);
1755 }
1756
1757 // The tiling is applied on destination dimensions. We have to apply the
1758 // interchange on source dimensions if outer_dims_perm is set.
1759 applyPermToRange(sliceSrcIndices, sliceSrcSizes,
1760 unpackOp.getOuterDimsPerm());
1761 Attribute zeroAttr = b.getIndexAttr(0);
1762 sliceSrcIndices.append(numInnerTiles, zeroAttr);
1763 sliceSrcSizes.append(unpackOp.getMixedTiles());
1764 sliceSrcStrides.append(numInnerTiles, oneAttr);
1765 SmallVector<Operation *> generatedSlices;
1766 tensor::ExtractSliceOp sliceSource = tensor::ExtractSliceOp::create(
1767 b, loc, unpackOp.getSource(), sliceSrcIndices, sliceSrcSizes,
1768 sliceSrcStrides);
1769 generatedSlices.push_back(sliceSource);
1770
1771 SmallVector<OpFoldResult> destStrides(destRank, oneAttr);
1772 Value sliceDest;
1773 if (isPerfectTilingCase) {
1774 auto destSliceOp = tensor::ExtractSliceOp::create(
1775 b, loc, unpackOp.getDest(), offsets, sizes, destStrides);
1776 sliceDest = destSliceOp;
1777 generatedSlices.push_back(destSliceOp);
1778 } else {
1779 sliceDest = tensor::EmptyOp::create(
1780 b, loc, destExpandedSizes, unpackOp.getDestType().getElementType());
1781 }
1782
1783 SmallVector<Value> tiledOperands = {sliceSource.getResult(), sliceDest};
1784 for (auto tile : unpackOp.getInnerTiles())
1785 tiledOperands.push_back(tile);
1786
1787 UnPackOp tiledUnpackOp =
1788 UnPackOp::create(b, loc, TypeRange{sliceDest.getType()}, tiledOperands,
1789 unpackOp.getProperties(),
1790 unpackOp->getDiscardableAttrDictionary().getValue());
1791
1792 if (isPerfectTilingCase)
1793 return TilingResult{{tiledUnpackOp},
1794 SmallVector<Value>(tiledUnpackOp->getResults()),
1795 generatedSlices};
1796
1797 auto extractSlice = tensor::ExtractSliceOp::create(
1798 b, loc, tiledUnpackOp->getResult(0), resultOffsetsFromDest, sizes,
1799 destStrides);
1800 return TilingResult{
1801 {tiledUnpackOp}, {extractSlice.getResult()}, generatedSlices};
1802 }
1803
1804 LogicalResult
1805 getResultTilePosition(Operation *op, OpBuilder &b, unsigned resultNumber,
1806 ArrayRef<OpFoldResult> offsets,
1807 ArrayRef<OpFoldResult> sizes,
1808 SmallVector<OpFoldResult> &resultOffsets,
1809 SmallVector<OpFoldResult> &resultSizes) const {
1810 resultOffsets = llvm::to_vector(offsets);
1811 resultSizes = llvm::to_vector(sizes);
1812 return success();
1813 }
1814
1815 FailureOr<TilingResult>
1816 generateResultTileValue(Operation *op, OpBuilder &b, unsigned resultNumber,
1817 ArrayRef<OpFoldResult> offsets,
1818 ArrayRef<OpFoldResult> sizes) const {
1819 return generateResultTileValue(op, b, resultNumber, offsets, sizes,
1820 /*innerTileAlignments=*/{});
1821 }
1822
1823 FailureOr<TilingResult> generateResultTileValue(
1824 Operation *op, OpBuilder &b, unsigned resultNumber,
1825 ArrayRef<OpFoldResult> offsets, ArrayRef<OpFoldResult> sizes,
1826 ArrayRef<InnerTileAlignment> innerTileAlignments) const {
1827 FailureOr<TilingResult> tilingResult =
1828 getTiledImplementation(op, b, offsets, sizes, innerTileAlignments);
1829 if (failed(tilingResult))
1830 return failure();
1831 return tilingResult.value();
1832 }
1833
1834 LogicalResult generateScalarImplementation(Operation *op, OpBuilder &builder,
1835 Location loc,
1836 ValueRange ivs) const {
1837 auto unpackOp = cast<UnPackOp>(op);
1838 assert(unpackOp.hasPureBufferSemantics() &&
1839 "expected operation to have buffer semantics");
1840 assert(ivs.size() == unpackOp.getDestRank() &&
1841 "number of ivs must match the rank of the output tensor");
1842 OpBuilder::InsertionGuard g(builder);
1843
1844 DenseMap<int64_t, OpFoldResult> dimAndTileMapping =
1845 unpackOp.getDimAndTileMapping();
1846 // Untiled loops and tile loops induction variables.
1847 SmallVector<Value> inputIvs;
1848 // Point loops induction variables.
1849 SmallVector<Value> inputIvsPointLoops;
1850 inputIvs.reserve(unpackOp.getDestRank());
1851 inputIvsPointLoops.reserve(dimAndTileMapping.size());
1852 for (auto dim : llvm::seq<int64_t>(0, unpackOp.getDestRank())) {
1853 if (dimAndTileMapping.count(dim)) {
1854 affine::DivModValue divMod =
1855 affine::getDivMod(builder, loc, ivs[dim],
1857 builder, loc, dimAndTileMapping[dim]));
1858 inputIvsPointLoops.push_back(divMod.remainder);
1859 inputIvs.push_back(divMod.quotient);
1860 } else {
1861 inputIvs.push_back(ivs[dim]);
1862 }
1863 }
1864
1865 // TODO: (lorenzo) simplify the logic a bit. There is `ivs`,
1866 // `inputIvsPointLoops` and `inputIvs`.
1867 assert(inputIvsPointLoops.size() + inputIvs.size() ==
1868 unpackOp.getSourceRank() &&
1869 "expect same number of induction variables equals to input rank");
1870 // Interchange the point loops induction variables based on `inner_dim_pos`.
1871 ArrayRef<int64_t> innerDims = unpackOp.getInnerDimsPos();
1872 SmallVector<int64_t> interchangeVector =
1873 computeInterchangeFromDimPos(innerDims, unpackOp.getDestRank());
1874 SmallVector<Value> interchangedInputIvsPointLoops = inputIvsPointLoops;
1875 interchangedInputIvsPointLoops = interchange<Value>(
1876 interchangedInputIvsPointLoops, interchangeVector, /*offset=*/0);
1877 // Interchange the tiled loops induction variables based on
1878 // `outer_dims_perm`.
1879 ArrayRef<int64_t> outerDims = unpackOp.getOuterDimsPerm();
1880 if (!outerDims.empty())
1881 inputIvs = interchange<Value>(inputIvs, outerDims, /*offset=*/0);
1882
1883 llvm::append_range(inputIvs, interchangedInputIvsPointLoops);
1884 Value scalar =
1885 memref::LoadOp::create(builder, loc, unpackOp.getSource(), inputIvs);
1886 memref::StoreOp::create(builder, loc, scalar, unpackOp.getDest(), ivs);
1887 return success();
1888 }
1889
1890 /// Method to return the position of iteration domain tile computed by the
1891 /// tiled operation.
1892 LogicalResult getIterationDomainTileFromOperandTiles(
1893 Operation *op, OpBuilder &b, ArrayRef<unsigned> operandNumbers,
1894 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1895 ArrayRef<SmallVector<OpFoldResult>> allSizes,
1896 SmallVectorImpl<OpFoldResult> &resultOffsets,
1897 SmallVectorImpl<OpFoldResult> &resultSizes) const {
1898 if (operandNumbers.size() != 1) {
1899 LLVM_DEBUG({ llvm::dbgs() << "unable to handle multiple operands"; });
1900 return failure();
1901 }
1902 auto unPackOp = cast<UnPackOp>(op);
1903 unsigned operandNumber = operandNumbers[0];
1904 ArrayRef<OpFoldResult> offsets(allOffsets[0]);
1905 ArrayRef<OpFoldResult> sizes(allSizes[0]);
1906
1907 // If the operand tile is the dest, then no adjustment is needed.
1908 if (operandNumber == unPackOp.getDestMutable().getOperandNumber()) {
1909 resultOffsets = llvm::to_vector(offsets);
1910 resultSizes = llvm::to_vector(sizes);
1911 return success();
1912 }
1913 Location loc = unPackOp.getLoc();
1914
1915 int64_t numTiles = unPackOp.getInnerDimsPos().size();
1916 auto destOffsets = offsets.drop_back(numTiles);
1917 auto destSizes = sizes.drop_back(numTiles);
1918 // The tiling is applied on interchanged dimensions. We have to undo the
1919 // interchange to map sizes and offsets to the original input.
1920 int64_t outputRank = unPackOp.getDestRank();
1921 ReifiedRankedShapedTypeDims reifiedReturnShapes;
1922 if (failed(reifyResultShapes(b, unPackOp, reifiedReturnShapes)))
1923 return failure();
1924 SmallVector<OpFoldResult> outputMixedSizes = reifiedReturnShapes.front();
1925 SmallVector<OpFoldResult> origOffsets(destOffsets);
1926 SmallVector<OpFoldResult> origSizes(destSizes);
1927 applyPermToRange(origOffsets, origSizes,
1928 invertPermutationVector(unPackOp.getOuterDimsPerm()));
1929
1930 DenseMap<int64_t, OpFoldResult> dimAndTileMapping =
1931 unPackOp.getDimAndTileMapping();
1932
1933 for (auto dim : llvm::seq<int64_t>(0, outputRank)) {
1934 using AV = affine::AffineValueExpr;
1935 affine::AffineBuilder ab(b, loc);
1936 AffineExpr dim0, dim1, sym0;
1937 bindDims(b.getContext(), dim0, dim1);
1938 bindSymbols(b.getContext(), sym0);
1939 if (dimAndTileMapping.count(dim)) {
1940 // If the data dimension is tiled, the i-th index is the product of
1941 // offset_i and tile_i, and the i-th size is the product of sizes_i and
1942 // tile_i. The sizes must be clamped to the sizes of the unpack result.
1943 auto avOffset = AV(dim0).bind(origOffsets[dim]);
1944 auto avSize = AV(dim0).bind(origSizes[dim]);
1945 auto avTileSize = AV(sym0).bind(dimAndTileMapping[dim]);
1946 auto avResultSize = AV(dim0).bind(outputMixedSizes[dim]);
1947 resultOffsets.push_back(ab.mul(avOffset, avTileSize));
1948 auto avResultOffset = AV(dim1).bind(resultOffsets.back());
1949 resultSizes.push_back(ab.min({ab.mul(avSize, avTileSize),
1950 ab.sub(avResultSize, avResultOffset)}));
1951 } else {
1952 resultOffsets.push_back(origOffsets[dim]);
1953 resultSizes.push_back(origSizes[dim]);
1954 }
1955 }
1956 return success();
1957 }
1958
1959 FailureOr<TilingResult> getTiledImplementationFromOperandTiles(
1960 Operation *op, OpBuilder &b, ArrayRef<unsigned> operandNumbers,
1961 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1962 ArrayRef<SmallVector<OpFoldResult>> allSizes) const {
1963 return getTiledImplementationFromOperandTiles(op, b, operandNumbers,
1964 allOffsets, allSizes,
1965 /*innerTileAlignments=*/{});
1966 }
1967
1968 /// Method to return the tiled implementation of linalg.unpack as a consumer.
1969 FailureOr<TilingResult> getTiledImplementationFromOperandTiles(
1970 Operation *op, OpBuilder &b, ArrayRef<unsigned> operandNumbers,
1971 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1972 ArrayRef<SmallVector<OpFoldResult>> allSizes,
1973 ArrayRef<InnerTileAlignment> innerTileAlignments) const {
1974 if (operandNumbers.size() != 1 || operandNumbers[0] != 0) {
1975 LLVM_DEBUG({ llvm::dbgs() << "unhandled operands for consumer fusion"; });
1976 return failure();
1977 }
1978 auto unPackOp = cast<UnPackOp>(op);
1979 // TODO: Support Memref UnPackOp. Temporarily return failure.
1980 if (!unPackOp.hasPureTensorSemantics())
1981 return failure();
1982
1983 ArrayRef<OpFoldResult> offsets(allOffsets[0]);
1984 ArrayRef<OpFoldResult> sizes(allSizes[0]);
1985
1986 // linalg.unpack op is fusible (as a consumer) only if the inner dims are
1987 // not tiled, i.e. each inner-dim loop tile size equals the inner tile size.
1988 // The caller may assert this per inner dim via InnerTileAlignment::Equal;
1989 // otherwise we require a statically-provable equality.
1990 int64_t numTiles = unPackOp.getInnerDimsPos().size();
1991 ArrayRef<int64_t> innerDimsPos = unPackOp.getInnerDimsPos();
1992 SmallVector<OpFoldResult> mixedTiles = unPackOp.getMixedTiles();
1993 ArrayRef<OpFoldResult> innerSizes = sizes.take_back(numTiles);
1994 for (int64_t i = 0; i < numTiles; ++i) {
1995 // `innerTileAlignments` is indexed by the unpack iteration domain (the
1996 // dest dims); the i-th inner tile lives on dest dim `innerDimsPos[i]`.
1997 int64_t destDim = innerDimsPos[i];
1998 bool hintedEqual =
1999 destDim < static_cast<int64_t>(innerTileAlignments.size()) &&
2000 innerTileAlignments[destDim] == InnerTileAlignment::Equal;
2001 // The hint is the source of truth: honor a caller `Equal` assertion. When
2002 // both sizes are also statically known, assert the hint agrees with them
2003 // (a contradicting hint is a caller bug) rather than silently ignoring
2004 // it. Without an `Equal` hint, require a statically-provable equality
2005 // (the inner dim must not be tiled).
2006 if (hintedEqual) {
2007 assert((!getConstantIntValue(mixedTiles[i]) ||
2008 !getConstantIntValue(innerSizes[i]) ||
2009 isEqualConstantIntOrValue(mixedTiles[i], innerSizes[i])) &&
2010 "InnerTileAlignment::Equal contradicts statically known tile "
2011 "sizes");
2012 continue;
2013 }
2014 if (isEqualConstantIntOrValue(mixedTiles[i], innerSizes[i]))
2015 continue;
2016 return failure();
2017 }
2018
2019 Location loc = unPackOp.getLoc();
2020
2021 // Fetch offset/size for creating the slice of the dest operand of
2022 // unpack op.
2023 SmallVector<OpFoldResult> outputOffsets, outputSizes;
2024 if (failed(getIterationDomainTileFromOperandTiles(
2025 op, b, operandNumbers, allOffsets, allSizes, outputOffsets,
2026 outputSizes)))
2027 return failure();
2028
2029 auto oneAttr = b.getI64IntegerAttr(1);
2030 int64_t outputRank = unPackOp.getDestRank();
2031 SmallVector<OpFoldResult> strides(outputRank, oneAttr);
2032
2033 SmallVector<Value> tiledOperands;
2034 // Create slice of the dest operand.
2035 auto extractDestSlice = tensor::ExtractSliceOp::create(
2036 b, loc, unPackOp.getDest(), outputOffsets, outputSizes, strides);
2037 tiledOperands.push_back(extractDestSlice);
2038
2039 strides.append(unPackOp.getSourceRank() - outputRank, oneAttr);
2040 // Create slice of the source operand.
2041 auto extractSourceSlice = tensor::ExtractSliceOp::create(
2042 b, loc, unPackOp.getSource(), offsets, sizes, strides);
2043 tiledOperands.insert(tiledOperands.begin(), extractSourceSlice);
2044 for (auto tile : unPackOp.getInnerTiles())
2045 tiledOperands.push_back(tile);
2046
2047 // Create tiled unpack op.
2048 UnPackOp tiledUnPackOp =
2049 UnPackOp::create(b, loc, TypeRange{extractDestSlice.getType()},
2050 tiledOperands, unPackOp.getProperties(),
2051 unPackOp->getDiscardableAttrDictionary().getValue());
2052
2053 return TilingResult{{tiledUnPackOp},
2054 SmallVector<Value>(tiledUnPackOp->getResults()),
2055 llvm::to_vector(ArrayRef<Operation *>{
2056 extractSourceSlice, extractDestSlice})};
2057 }
2058};
2059
2060} // namespace
2061
2062template <typename OpType>
2063static void registerOne(MLIRContext *ctx) {
2064 OpType::template attachInterface<LinalgOpTilingInterfaceModel<OpType>>(*ctx);
2065 OpType::template attachInterface<
2066 LinalgOpPartialReductionInterfaceModel<OpType>>(*ctx);
2067}
2068
2069/// Variadic helper function.
2070template <typename... OpTypes>
2071static void registerAll(MLIRContext *ctx) {
2072 (registerOne<OpTypes>(ctx), ...);
2073}
2074
2075#define GET_OP_LIST
2076
2078 DialectRegistry &registry) {
2079 registry.addExtension(+[](MLIRContext *ctx, linalg::LinalgDialect *dialect) {
2081 linalg::PackOp::attachInterface<PackOpTiling>(*ctx);
2082 linalg::UnPackOp::attachInterface<UnPackOpTiling>(*ctx);
2084#include "mlir/Dialect/Linalg/IR/LinalgStructuredOps.cpp.inc"
2085 >(ctx);
2086 });
2087}
2088
2090 DialectRegistry &registry) {
2091 registry.addExtension(+[](MLIRContext *ctx, LinalgDialect *dialect) {
2092 linalg::PackOp::attachInterface<PackOpTiling>(*ctx);
2093 linalg::UnPackOp::attachInterface<UnPackOpTiling>(*ctx);
2094 });
2095}
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:63
RAII guard to reset the insertion point of the builder when destroyed.
Definition Builders.h:351
This class helps build Operations.
Definition Builders.h:210
void setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
Definition Builders.h:434
This class represents a single result from folding an operation.
This class represents an operand of an operation.
Definition Value.h:254
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Region & getRegion(unsigned index)
Returns the region held by this operation at position 'index'.
Definition Operation.h: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:733
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:125
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
bool isZeroInteger(OpFoldResult v)
Return "true" if v is an integer value/attribute with constant value 0.
void bindSymbols(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to SymbolExpr at positions: [0 .
Definition AffineExpr.h:325
Value getValueOrCreateConstantIndexOp(OpBuilder &b, Location loc, OpFoldResult ofr)
Converts an OpFoldResult to a Value.
Definition Utils.cpp:114
Operation * clone(OpBuilder &b, Operation *op, TypeRange newResultTypes, ValueRange newOperands)
SmallVector< Loops, 8 > tile(ArrayRef< scf::ForOp > forOps, ArrayRef< Value > sizes, ArrayRef< scf::ForOp > targets)
Performs tiling fo imperfectly nested loops (with interchange) by strip-mining the forOps by sizes an...
Definition Utils.cpp:1380
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
void applyPermutationToVector(SmallVector< T, N > &inVec, ArrayRef< int64_t > permutation)
Apply the permutation defined by permutation to inVec.
InnerTileAlignment
Per-dimension alignment of a loop tile size to a linalg.pack / linalg.unpack inner tile size,...
std::pair< SmallVector< int64_t >, SmallVector< Value > > decomposeMixedValues(ArrayRef< OpFoldResult > mixedValues)
Decompose a vector of mixed static or dynamic values into the corresponding pair of arrays.
SmallVector< int64_t > invertPermutationVector(ArrayRef< int64_t > permutation)
Helper method to apply to inverse a permutation.
Helper struct to build simple arithmetic quantities with minimal type inference support.
Definition Utils.h:103
Container for result values of tiling.
Options that control value bound computation.
Helper struct to build simple AffineValueExprs with minimal type inference support.
Definition Utils.h:377
A struct containg offsets-sizes-strides arguments of the tiled shape.
Definition Utils.h:172
SmallVector< OpFoldResult > sizes
Definition Utils.h:174
SmallVector< OpFoldResult > offsets
Definition Utils.h:173
Eliminates variable at the specified position using Fourier-Motzkin variable elimination.