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