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