27#include "llvm/ADT/SmallVectorExtras.h"
28#include "llvm/Support/Debug.h"
31#define DEBUG_TYPE "linalg-tiling-interface-impl"
50 Value v = affine::AffineApplyOp::create(
b, loc, m, ivs);
60 Block *body = linalgOp.getBlock();
64 if (
auto indexOp = dyn_cast<IndexOp>(&op)) {
65 map.
map(indexOp.getResult(), ivs[indexOp.getDim()]);
73 for (
const auto &operand : llvm::enumerate(terminator->
getOperands())) {
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(),
100 for (
auto [pos, tileSize] : llvm::enumerate(tileSizeBounds)) {
101 if (failed(tileSize)) {
102 tiledDims[pos] =
true;
108 ShapedType::isDynamic(loopRanges[pos]) || *tileSize < loopRanges[pos];
111 for (
AffineMap map : linalgOp.getIndexingMapsArray()) {
114 auto binExpr = dyn_cast<AffineBinaryOpExpr>(expr);
126 auto dim = dyn_cast<AffineDimExpr>(e);
127 if (dim && tiledDims[dim.getPosition()])
132 if (!involvesTiledDim)
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";
156 unsigned dimPos = dimExpr.getPosition();
157 FailureOr<int64_t> tileSize = tileSizeBounds[dimPos];
161 if (failed(tileSize) || *tileSize == 1)
176 int64_t step = stepExpr.getValue();
178 bool safe = *tileSize % step == 0 || (!isCeil && step % *tileSize == 0);
180 linalgOp.emitOpError()
181 <<
"tiling is not supported for the semi-affine indexing map: "
183 << *tileSize <<
" for dimension d" << dimPos
184 << (isCeil ?
" must be a multiple of the step "
185 :
" must divide or be divisible by the step ")
208struct LinalgOpTilingInterfaceImpl {
210 SmallVector<Range> getIterationDomain(Operation *op, OpBuilder &
b)
const {
211 OpBuilder::InsertionGuard g(
b);
212 b.setInsertionPoint(op);
213 Location loc = op->
getLoc();
214 LinalgOp linalgOp = cast<LinalgOp>(op);
215 SmallVector<OpFoldResult> allShapesSizes =
216 linalgOp.createFlatListOfOperandDims(
b, loc);
217 AffineMap map = linalgOp.getShapesToLoopsMap();
219 return llvm::map_to_vector(map.
getResults(), [&](AffineExpr loopExpr) {
220 OpFoldResult ofr = affine::makeComposedFoldedAffineApply(b, loc, loopExpr,
222 return Range{b.getIndexAttr(0), ofr, b.getIndexAttr(1)};
227 FailureOr<TilingResult>
234 LinalgOp linalgOp = cast<LinalgOp>(op);
245 b, loc, linalgOp, valuesToTile, offsets, sizes, {},
true);
247 llvm::make_filter_range(
249 [](
Value v) ->
bool {
250 return isa_and_nonnull<tensor::ExtractSliceOp, memref::SubViewOp>(
258 Operation *tiledOp =
clone(
b, linalgOp, resultTensorTypes, tiledOperands);
269 getMappedOffsetAndSize(LinalgOp linalgOp,
OpBuilder &
b,
277 for (
auto [indexingMap, offsets, sizes] :
278 llvm::zip_equal(indexingMaps, allOffsets, allSizes)) {
279 for (
auto [resultExpr, offset, size] :
280 llvm::zip_equal(indexingMap.getResults(), offsets, sizes)) {
281 auto dimExpr = dyn_cast<AffineDimExpr>(resultExpr);
284 unsigned position = dimExpr.getPosition();
285 auto it = mappedOffsets.find(position);
286 if (it != mappedOffsets.end()) {
289 if (seenOffset != offset || seenSize != size) {
291 llvm::dbgs() <<
"inconsistent iteration space mapping from "
292 "offsets/sizes of operands/results";
297 mappedOffsets[position] = offset;
298 mappedSizes[position] = size;
306 cast<TilingInterface>(linalgOp.getOperation()).getIterationDomain(
b);
307 mappedOffsetsVec.resize(iterationDomain.size());
308 mappedSizesVec.resize(iterationDomain.size());
309 for (
auto [
index, domain] : llvm::enumerate(iterationDomain)) {
310 auto it = mappedOffsets.find(
index);
311 if (it != mappedOffsets.end()) {
312 mappedOffsetsVec[
index] = it->second;
313 mappedSizesVec[
index] = mappedSizes.lookup(
index);
316 mappedOffsetsVec[
index] = domain.offset;
317 mappedSizesVec[
index] = domain.size;
324 LogicalResult getIterationDomainTileFromOperandTiles(
330 auto linalgOp = cast<LinalgOp>(op);
333 llvm::map_to_vector(operandNumbers, [&](
unsigned operandNumber) {
334 OpOperand &opOperand = linalgOp->getOpOperand(operandNumber);
335 return linalgOp.getMatchingIndexingMap(&opOperand);
337 if (
failed(getMappedOffsetAndSize(linalgOp,
b, indexingMaps, allOffsets,
338 allSizes, iterDomainOffsets,
354 LinalgOp linalgOp = cast<LinalgOp>(op);
363 OpOperand *outOperand = linalgOp.getDpsInitOperand(resultNumber);
365 b, loc, outOperand->get(), sizes,
366 linalgOp.getMatchingIndexingMap(outOperand), offsets,
367 {}, subShapeSizes,
true);
368 resultOffsets = sliceParams.
offsets;
369 resultSizes = sliceParams.
sizes;
373 LogicalResult getIterationDomainTileFromResultTile(
378 auto linalgOp = cast<LinalgOp>(op);
385 linalgOp.getIndexingMapMatchingResult(op->
getResult(resultNumber));
388 "unhandled tiled implementation generation when result is not "
389 "accessed using a permuted projection");
395 getMappedOffsetAndSize(linalgOp,
b, indexingMap, {allOffsets},
396 {allSizes}, iterDomainOffsets, iterDomainSizes);
398 assert(succeeded(status) &&
"unexpected error in offset calculation");
402 FailureOr<TilingResult>
407 if (
failed(getIterationDomainTileFromResultTile(
408 op,
b, resultNumber, offsets, sizes, mappedOffsets, mappedSizes))) {
411 auto tilingInterfaceOp = cast<TilingInterface>(op);
412 FailureOr<TilingResult> tilingResult =
413 tilingInterfaceOp.getTiledImplementation(
b, mappedOffsets, mappedSizes);
418 if (tilingResult->tiledOps.size() != 1)
419 return op->
emitOpError(
"failed to generate tiled implementation");
422 tilingResult->tiledOps,
424 tilingResult->generatedSlices};
429 FailureOr<TilingResult> getTiledImplementationFromOperandTiles(
434 if (
failed(getIterationDomainTileFromOperandTiles(
435 op,
b, operandNumbers, allOffsets, allSizes, mappedOffsets,
445 auto linalgOp = cast<LinalgOp>(op);
446 if (!linalgOp.hasPureBufferSemantics())
447 return op->
emitOpError(
"expected operation to have buffer semantics");
450 indexedValues.reserve(linalgOp->getNumOperands());
454 for (
OpOperand &operand : linalgOp->getOpOperands()) {
455 if (!linalgOp.payloadUsesValueFromOperand(&operand)) {
456 indexedValues.push_back(
nullptr);
459 if (linalgOp.isScalar(&operand)) {
460 indexedValues.push_back(operand.get());
464 builder, linalgOpLoc, linalgOp.getMatchingIndexingMap(&operand), ivs);
466 memref::LoadOp::create(builder, linalgOpLoc, operand.get(),
indices);
467 indexedValues.push_back(
load);
474 bool isOpFusableWithConsumerSlice(
Operation *op,
unsigned resultNumber,
481 bool isOpFusableWithProducerSlices(
486 auto linalgOp = cast<LinalgOp>(op);
488 llvm::map_to_vector(operandNumbers, [&](
unsigned operandNumber) {
489 OpOperand &opOperand = linalgOp->getOpOperand(operandNumber);
490 return linalgOp.getMatchingIndexingMap(&opOperand);
495 return succeeded(getMappedOffsetAndSize(linalgOp,
b, indexingMaps,
496 allOffsets, allSizes, mappedOffsets,
501template <
typename LinalgOpTy>
502struct LinalgOpTilingInterfaceModel
503 :
public TilingInterface::ExternalModel<
504 LinalgOpTilingInterfaceModel<LinalgOpTy>, LinalgOpTy>,
505 public LinalgOpTilingInterfaceImpl {
506 using ExternalModel =
507 TilingInterface::ExternalModel<LinalgOpTilingInterfaceModel<LinalgOpTy>,
510 using LinalgOpTilingInterfaceImpl::generateScalarImplementation;
511 using LinalgOpTilingInterfaceImpl::getIterationDomain;
512 using LinalgOpTilingInterfaceImpl::getIterationDomainTileFromResultTile;
513 using LinalgOpTilingInterfaceImpl::getResultTilePosition;
514 using LinalgOpTilingInterfaceImpl::isOpFusableWithConsumerSlice;
515 using LinalgOpTilingInterfaceImpl::isOpFusableWithProducerSlices;
518 SmallVector<utils::IteratorType> getLoopIteratorTypes(Operation *op)
const {
519 return cast<LinalgOpTy>(op).getIteratorTypesArray();
524 using ExternalModel::generateResultTileValue;
525 FailureOr<TilingResult>
526 generateResultTileValue(Operation *op, OpBuilder &
b,
unsigned resultNumber,
527 ArrayRef<OpFoldResult> offsets,
528 ArrayRef<OpFoldResult> sizes)
const {
529 return LinalgOpTilingInterfaceImpl::generateResultTileValue(
530 op,
b, resultNumber, offsets, sizes);
533 using ExternalModel::getIterationDomainTileFromOperandTiles;
534 LogicalResult getIterationDomainTileFromOperandTiles(
535 Operation *op, OpBuilder &
b, ArrayRef<unsigned> operandNumbers,
536 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
537 ArrayRef<SmallVector<OpFoldResult>> allSizes,
538 SmallVectorImpl<OpFoldResult> &iterDomainOffsets,
539 SmallVectorImpl<OpFoldResult> &iterDomainSizes)
const {
540 return LinalgOpTilingInterfaceImpl::getIterationDomainTileFromOperandTiles(
541 op,
b, operandNumbers, allOffsets, allSizes, iterDomainOffsets,
545 using ExternalModel::getTiledImplementation;
546 FailureOr<TilingResult>
548 ArrayRef<OpFoldResult> offsets,
549 ArrayRef<OpFoldResult> sizes)
const {
550 return LinalgOpTilingInterfaceImpl::getTiledImplementation(op,
b, offsets,
554 using ExternalModel::getTiledImplementationFromOperandTiles;
555 FailureOr<TilingResult> getTiledImplementationFromOperandTiles(
556 Operation *op, OpBuilder &
b, ArrayRef<unsigned> operandNumbers,
557 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
558 ArrayRef<SmallVector<OpFoldResult>> allSizes)
const {
559 return LinalgOpTilingInterfaceImpl::getTiledImplementationFromOperandTiles(
560 op,
b, operandNumbers, allOffsets, allSizes);
571 for (
auto [
index, reductionDim] : llvm::enumerate(reductionDims)) {
572 if (reductionDim == value) {
584getPartialResultAffineMaps(LinalgOp linalgOp,
586 auto partialReductionMaps = llvm::map_to_vector(
587 linalgOp.getDpsInitsMutable(), [&](
OpOperand &opOperand) {
588 AffineMap map = linalgOp.getMatchingIndexingMap(&opOperand);
589 for (auto redPos : reductionDims) {
591 map.insertResult(getAffineDimExpr(redPos, linalgOp.getContext()),
592 map.getNumResults());
596 return partialReductionMaps;
599struct InitSliceInfo {
600 SmallVector<int64_t> resultShape;
601 SmallVector<OpFoldResult> offsets;
602 SmallVector<OpFoldResult> sizes;
603 SmallVector<OpFoldResult> strides;
609static InitSliceInfo getInitSliceInfoForOuterReduction(
616 Attribute zero = IntegerAttr::get(IndexType::get(context), 0);
617 Attribute one = IntegerAttr::get(IndexType::get(context), 1);
619 for (
auto [resultIdx, dimExpr] :
620 llvm::enumerate(partialReductionMap.
getResults())) {
621 if (isa<AffineConstantExpr>(dimExpr)) {
624 initOffsets.push_back(zero);
625 initSizes.push_back(initOperandShape[resultIdx]);
628 unsigned dim = cast<AffineDimExpr>(dimExpr).getPosition();
629 if (reductionDims.contains(dim)) {
630 initOffsets.push_back(zero);
632 initOffsets.push_back(offsets[dim]);
634 initSizes.push_back(sizes[dim]);
638 return {resultShape, initOffsets, initSizes, initStrides};
644static InitSliceInfo getInitSliceInfoForOuterParallel(
651 Attribute zero = IntegerAttr::get(IndexType::get(context), 0);
652 Attribute one = IntegerAttr::get(IndexType::get(context), 1);
655 for (
auto [resultIdx, dimExpr] :
656 llvm::enumerate(partialReductionMap.
getResults())) {
657 if (isa<AffineConstantExpr>(dimExpr)) {
660 initOffsets.push_back(zero);
661 initSizes.push_back(initOperandShape[resultIdx]);
662 resultShape.push_back(initOperandShape[resultIdx]);
665 unsigned dim = cast<AffineDimExpr>(dimExpr).getPosition();
666 if (std::optional<unsigned> dimPos = getPositionIn(reductionDims, dim)) {
667 initOffsets.push_back(splitReductionIvs[dimPos.value()]);
668 initSizes.push_back(one);
670 initOffsets.push_back(offsets[dim]);
671 initSizes.push_back(sizes[dim]);
672 resultShape.push_back(sizes[dim]);
677 return {staticShapes, initOffsets, initSizes, initStrides};
682static InitSliceInfo getInitSliceInfo(
MLIRContext *context,
691 return getInitSliceInfoForOuterReduction(
692 context, offsets, sizes, reductionDims, splitReductionIvs,
693 partialReductionMap, initOperandShape);
696 "unexpected ReductionTilingStrategy");
697 return getInitSliceInfoForOuterParallel(
698 context, offsets, sizes, reductionDims, splitReductionIvs,
699 partialReductionMap, initOperandShape);
704struct LinalgOpPartialReductionInterfaceImpl {
705 FailureOr<SmallVector<Value>> generateInitialTensorForPartialReduction(
706 Operation *op, OpBuilder &
b, Location loc, ArrayRef<OpFoldResult> sizes,
708 auto linalgOp = cast<LinalgOp>(op);
710 OpBuilder::InsertionGuard guard(
b);
711 if (linalgOp.hasPureBufferSemantics())
712 return op->
emitOpError(
"expected operation to have tensor semantics");
714 SmallVector<AffineMap> partialResultMaps =
715 getPartialResultAffineMaps(linalgOp, reductionDims);
717 SmallVector<Value> inits;
718 for (
auto [initIdx,
result, partialMap] :
719 llvm::enumerate(linalgOp->getResults(), partialResultMaps)) {
720 SmallVector<Operation *, 4> combinerOps;
723 combinerOps.size() != 1)
724 return op->
emitOpError(
"Failed to anaysis the reduction operation.");
726 Operation *reductionOp = combinerOps[0];
727 std::optional<TypedAttr> identity = arith::getNeutralElement(reductionOp);
728 if (!identity.has_value())
730 "Failed to get an identity value for the reduction operation.");
733 SmallVector<OpFoldResult> partialResultShape;
734 Value initValue = linalgOp.getDpsInits()[initIdx];
735 SmallVector<OpFoldResult> initShape =
737 for (
auto [resultIdx, dimExpr] :
738 llvm::enumerate(partialMap.getResults())) {
739 if (isa<AffineConstantExpr>(dimExpr)) {
742 partialResultShape.push_back(initShape[resultIdx]);
745 auto dim = cast<AffineDimExpr>(dimExpr);
746 partialResultShape.push_back(sizes[dim.getPosition()]);
751 tensor::EmptyOp::create(
b, loc, partialResultShape, elType);
752 Value constantOp = arith::ConstantOp::create(
b, loc, *identity);
753 auto identityTensor =
754 linalg::FillOp::create(
b, loc, constantOp, emptyTensor);
755 inits.push_back(identityTensor.getResult(0));
761 FailureOr<TilingResult>
762 tileToPartialReduction(Operation *op, OpBuilder &
b, Location loc,
764 ValueRange init, ArrayRef<OpFoldResult> offsets,
765 ArrayRef<OpFoldResult> sizes,
767 ArrayRef<OpFoldResult> splitReductionIvs)
const {
768 OpBuilder::InsertionGuard guard(
b);
769 auto linalgOp = cast<LinalgOp>(op);
771 SmallVector<AffineMap> partialReductionMaps =
772 getPartialResultAffineMaps(linalgOp, reductionDims);
776 SmallVector<AffineMap> newInitMaps;
777 if (tilingStrategy ==
778 ReductionTilingStrategy::PartialReductionOuterReduction) {
779 newInitMaps = llvm::to_vector(partialReductionMaps);
781 newInitMaps = llvm::map_to_vector(
782 linalgOp.getDpsInitsMutable(), [&](OpOperand &opOperand) {
783 return linalgOp.getMatchingIndexingMap(&opOperand);
789 b, loc, linalgOp, linalgOp.getDpsInputs(), offsets, sizes, {},
true);
790 SmallVector<Operation *> generatedSlices = llvm::map_to_vector(
791 llvm::make_filter_range(
792 tiledInputs, [](Value v) ->
bool {
return v.
getDefiningOp(); }),
796 SmallVector<Value, 1> tiledInits;
797 for (
auto [partialReductionMap, valueToTile, initOperandValue] :
798 llvm::zip_equal(partialReductionMaps, init, linalgOp.getDpsInits())) {
801 SmallVector<OpFoldResult> initOperandShape =
803 InitSliceInfo sliceInfo = getInitSliceInfo(
804 b.getContext(), tilingStrategy, offsets, sizes, reductionDims,
805 splitReductionIvs, partialReductionMap, initOperandShape);
806 auto valueToTileType = cast<RankedTensorType>(valueToTile.getType());
808 sliceInfo.resultShape, valueToTileType.getElementType(),
809 valueToTileType.getEncoding());
810 auto sliceOp = tensor::ExtractSliceOp::create(
812 sliceInfo.sizes, sliceInfo.strides);
813 tiledInits.push_back(sliceOp.getResult());
814 generatedSlices.push_back(sliceOp);
818 SmallVector<AffineMap> newMaps = linalgOp.getIndexingMapsArray();
819 for (
auto [initOperand, newInitMap] :
820 llvm::zip_equal(linalgOp.getDpsInitsMutable(), newInitMaps)) {
821 int mapIdx = linalgOp.getIndexingMapIndex(&initOperand);
822 newMaps[mapIdx] = newInitMap;
826 SmallVector<utils::IteratorType> newIteratorTypes =
827 linalgOp.getIteratorTypesArray();
828 if (tilingStrategy ==
829 ReductionTilingStrategy::PartialReductionOuterReduction) {
830 for (
int dim : reductionDims)
831 newIteratorTypes[dim] = utils::IteratorType::parallel;
835 Operation *partialReductionOp;
836 auto resultTypes =
ValueRange(tiledInits).getTypes();
837 if (tilingStrategy ==
838 ReductionTilingStrategy::PartialReductionOuterReduction) {
839 auto genericOp = GenericOp::create(
b, loc, resultTypes, tiledInputs,
840 tiledInits, newMaps, newIteratorTypes);
843 genericOp.getRegion().begin(), mapping);
845 partialReductionOp = genericOp.getOperation();
847 SmallVector<Value> operands = std::move(tiledInputs);
848 llvm::append_range(operands, tiledInits);
849 partialReductionOp =
mlir::clone(
b, op, resultTypes, operands);
853 {partialReductionOp},
854 llvm::map_to_vector(partialReductionOp->
getResults(),
855 [](OpResult r) -> Value { return r; }),
859 FailureOr<MergeResult>
860 mergeReductions(Operation *op, OpBuilder &
b, Location loc,
863 auto linalgOp = cast<LinalgOp>(op);
864 SmallVector<AffineMap> partialReductionMaps =
865 getPartialResultAffineMaps(linalgOp, reductionDims);
868 SmallVector<Operation *> mergeOperations;
869 SmallVector<Value> replacements;
870 for (
auto [idx, init, partialResult, partialMap] : llvm::enumerate(
871 linalgOp.getDpsInits(), partialReduce, partialReductionMaps)) {
872 unsigned initIdx = idx;
877 SmallVector<int64_t> partialReductionDims;
878 for (
auto [resultNum, dimExpr] :
879 llvm::enumerate(partialMap.getResults())) {
880 if (isa<AffineConstantExpr>(dimExpr))
882 unsigned dim = cast<AffineDimExpr>(dimExpr).getPosition();
883 if (llvm::is_contained(reductionDims, dim)) {
884 partialReductionDims.push_back(resultNum);
888 auto reduction = linalg::ReduceOp::create(
889 b, loc, partialResult, init, partialReductionDims,
890 [&linalgOp, &initIdx](OpBuilder &
b, Location loc,
ValueRange inputs) {
892 SmallVector<Operation *, 4> combinerOps;
895 Operation *clonedReductionOp =
b.clone(*combinerOps[0]);
899 linalg::YieldOp::create(
b, loc, clonedReductionOp->
getResult(0));
902 mergeOperations.push_back(reduction);
903 replacements.push_back(reduction->getResult(0));
906 return MergeResult{mergeOperations, replacements};
909 LogicalResult getPartialResultTilePosition(
910 Operation *op, OpBuilder &
b,
unsigned resultNumber,
913 ArrayRef<OpFoldResult> splitReductionIvs,
914 SmallVector<OpFoldResult> &resultOffsets,
915 SmallVector<OpFoldResult> &resultSizes)
const {
916 auto linalgOp = cast<LinalgOp>(op);
917 SmallVector<AffineMap> partialReductionMaps =
918 getPartialResultAffineMaps(linalgOp, reductionDims);
921 Value initOperandValue = linalgOp.getDpsInits()[resultNumber];
922 Location loc = op->
getLoc();
923 SmallVector<OpFoldResult> initOperandShape =
925 InitSliceInfo sliceInfo =
926 getInitSliceInfo(
b.getContext(), tilingStrategy, offsets, sizes,
927 reductionDims, splitReductionIvs,
928 partialReductionMaps[resultNumber], initOperandShape);
929 std::swap(resultOffsets, sliceInfo.offsets);
930 std::swap(resultSizes, sliceInfo.sizes);
936template <
typename LinalgOpTy>
937struct LinalgOpPartialReductionInterfaceModel
938 :
public PartialReductionOpInterface::ExternalModel<
939 LinalgOpPartialReductionInterfaceModel<LinalgOpTy>, LinalgOpTy>,
940 public LinalgOpPartialReductionInterfaceImpl {
941 using LinalgOpPartialReductionInterfaceImpl::
942 generateInitialTensorForPartialReduction;
943 using LinalgOpPartialReductionInterfaceImpl::getPartialResultTilePosition;
944 using LinalgOpPartialReductionInterfaceImpl::mergeReductions;
945 using LinalgOpPartialReductionInterfaceImpl::tileToPartialReduction;
948template <
typename OpTy>
951 static_assert(llvm::is_one_of<OpTy, PackOp, UnPackOp>::value,
952 "applies to only pack or unpack operations");
954 int64_t rank = (std::is_same<OpTy, PackOp>::value) ? op.getSourceRank()
959 (
void)op.reifyResultShapes(builder, resultShape);
961 for (
auto dim : llvm::seq<int64_t>(0, rank)) {
962 loopBounds[dim].offset = zero;
963 loopBounds[dim].stride = one;
964 loopBounds[dim].size = resultShape[0][dim];
972 if (permutation.empty())
984 interchangeVector.reserve(dimsPos.size());
993 for (
int64_t dimsIdx = 0, end = dimsPos.size(); dimsIdx < end; dimsIdx++)
994 dimsAndPosMapping[dimsPos[dimsIdx]] = dimsIdx;
998 for (
int64_t dimsIdx = 0; dimsIdx < rank; dimsIdx++) {
999 if (dimsAndPosMapping.count(dimsIdx))
1000 interchangeVector.push_back(dimsAndPosMapping[dimsIdx]);
1002 return interchangeVector;
1017template <
typename T>
1022 for (
auto [idx, val] : llvm::enumerate(interchangeVector))
1023 vec[idx + offset] = elements[val + offset];
1029static void generatePackOpScalarImplementationBody(PackOp packOp,
1044 computeInterchangeFromDimPos(dimsToInnerBlock, packOp.getSourceRank());
1045 interchangedIvs = interchange<Value>(interchangedIvs, interchangeVector,
1046 packOp.getSourceRank());
1047 if (!dimsToOuterBlock.empty()) {
1049 computeInterchangeFromDimPos(dimsToOuterBlock, packOp.getSourceRank());
1051 interchange<Value>(interchangedIvs, interchangeVector, 0);
1054 packOp.getDimAndTileMapping();
1056 size_t pointLoopsOffset = 0;
1057 int64_t sourceRank = packOp.getSourceRank();
1058 for (
auto dim : llvm::seq<int64_t>(0, sourceRank)) {
1059 if (dimAndTileMapping.contains(dim)) {
1064 builder, loc, i *
tile +
j,
1066 interchangedIvs[dim],
1067 interchangedIvs[pointLoopsOffset + packOp.getSourceRank()],
1068 dimAndTileMapping[dim]});
1069 sourceIndices.push_back(sourceIndex);
1072 sourceIndices.push_back(interchangedIvs[dim]);
1076 auto createLoad = [&]() ->
Value {
1077 return memref::LoadOp::create(
1078 builder, loc, packOp.getSource(),
1082 if (
auto paddingValue = packOp.getPaddingValue()) {
1085 for (
auto dim : llvm::seq<int64_t>(0, sourceRank)) {
1088 Value cond = arithBuilder.slt(
1092 scalar = scf::IfOp::create(
1095 scf::YieldOp::create(
b, l, createLoad());
1099 scf::YieldOp::create(
b, l, paddingValue);
1103 scalar = createLoad();
1106 memref::StoreOp::create(builder, loc, scalar, packOp.getDest(), ivs);
1110 :
public TilingInterface::ExternalModel<PackOpTiling, linalg::PackOp> {
1111 using Base = TilingInterface::ExternalModel<PackOpTiling, linalg::PackOp>;
1112 using Base::getTiledImplementation;
1114 SmallVector<utils::IteratorType> getLoopIteratorTypes(Operation *op)
const {
1118 auto packOp = cast<PackOp>(op);
1119 SmallVector<utils::IteratorType> iteratorTypes(
1120 packOp.getSourceRank(), utils::IteratorType::parallel);
1121 return iteratorTypes;
1124 SmallVector<Range> getIterationDomain(Operation *op, OpBuilder &
b)
const {
1125 return getPackUnPackIterationDomain<PackOp>(cast<PackOp>(op),
b);
1128 FailureOr<TilingResult>
1130 ArrayRef<OpFoldResult> offsets,
1131 ArrayRef<OpFoldResult> sizes)
const {
1132 auto packOp = cast<PackOp>(op);
1137 if (!packOp.hasPureTensorSemantics())
1140 Location loc = packOp.getLoc();
1144 int64_t inputRank = packOp.getSourceRank();
1145 SmallVector<OpFoldResult> origOffsets(offsets);
1146 SmallVector<OpFoldResult> origSizes(sizes);
1147 applyPermToRange(origOffsets, origSizes,
1151 packOp.getDimAndTileMapping();
1152 SmallVector<OpFoldResult> srcDimValues =
1154 SmallVector<OpFoldResult> inputIndices, inputSizes;
1155 for (
auto dim : llvm::seq<int64_t>(0, inputRank)) {
1156 using AV = affine::AffineValueExpr;
1157 affine::AffineBuilder ab(
b, loc);
1158 AffineExpr dim0, dim1, sym;
1161 if (dimAndTileMapping.count(dim)) {
1165 auto avOffset = AV(dim0).bind(origOffsets[dim]);
1166 auto avSize = AV(dim0).bind(origSizes[dim]);
1167 auto avTileSize = AV(sym).bind(dimAndTileMapping[dim]);
1168 inputIndices.push_back(ab.mul(avOffset, avTileSize));
1169 inputSizes.push_back(ab.mul(avSize, avTileSize));
1171 inputIndices.push_back(origOffsets[dim]);
1172 inputSizes.push_back(origSizes[dim]);
1176 if (packOp.getPaddingValue()) {
1177 OpFoldResult dimSize = srcDimValues[dim];
1178 auto avDimSize = AV(dim0).bind(dimSize);
1179 auto avInputIdx = AV(dim1).bind(inputIndices.back());
1181 ab.min({inputSizes.back(), ab.sub(avDimSize, avInputIdx)});
1185 auto oneAttr =
b.getI64IntegerAttr(1);
1186 SmallVector<OpFoldResult> strides(inputRank, oneAttr);
1188 SmallVector<Value> tiledOperands;
1189 auto sourceSlice = tensor::ExtractSliceOp::create(
1190 b, loc, packOp.getSource(), inputIndices, inputSizes, strides);
1191 tiledOperands.push_back(sourceSlice);
1193 SmallVector<OpFoldResult> outputOffsets, outputSizes;
1198 strides.append(packOp.getDestRank() - inputRank, oneAttr);
1199 auto outSlice = tensor::ExtractSliceOp::create(
1200 b, loc, packOp.getDest(), outputOffsets, outputSizes, strides);
1201 tiledOperands.push_back(outSlice);
1203 if (
auto val = packOp.getPaddingValue())
1204 tiledOperands.push_back(val);
1205 for (
auto tile : packOp.getInnerTiles())
1206 tiledOperands.push_back(
tile);
1208 PackOp tiledPackOp =
1209 PackOp::create(
b, loc,
TypeRange{outSlice.getType()}, tiledOperands,
1210 packOp.getProperties(),
1211 packOp->getDiscardableAttrDictionary().getValue());
1213 return TilingResult{
1215 SmallVector<Value>(tiledPackOp->getResults()),
1216 llvm::to_vector(ArrayRef<Operation *>{sourceSlice, outSlice})};
1221 ArrayRef<OpFoldResult> offsets,
1222 ArrayRef<OpFoldResult> sizes,
1223 SmallVector<OpFoldResult> &resultOffsets,
1224 SmallVector<OpFoldResult> &resultSizes)
const {
1229 auto packOp = cast<PackOp>(op);
1230 int64_t inputRank = packOp.getSourceRank();
1231 int64_t outputRank = packOp.getDestRank();
1232 auto zeroAttr =
b.getI64IntegerAttr(0);
1233 resultOffsets.assign(offsets.begin(), offsets.end());
1234 resultOffsets.append(outputRank - inputRank, zeroAttr);
1238 resultSizes.assign(sizes.begin(), sizes.end());
1239 for (
auto dataTileDim : llvm::seq<unsigned>(inputRank, outputRank))
1240 resultSizes.push_back(outputShape[0][dataTileDim]);
1245 FailureOr<TilingResult>
1246 generateResultTileValue(Operation *op, OpBuilder &
b,
unsigned resultNumber,
1247 ArrayRef<OpFoldResult> offsets,
1248 ArrayRef<OpFoldResult> sizes)
const {
1249 return generateResultTileValue(op,
b, resultNumber, offsets, sizes,
1253 FailureOr<TilingResult> generateResultTileValue(
1254 Operation *op, OpBuilder &
b,
unsigned resultNumber,
1255 ArrayRef<OpFoldResult> offsets, ArrayRef<OpFoldResult> sizes,
1256 ArrayRef<InnerTileAlignment> innerTileAlignments)
const {
1257 auto packOp = cast<PackOp>(op);
1258 int64_t numTiles = packOp.getInnerDimsPos().size();
1263 for (
auto offset : offsets.take_back(numTiles))
1270 ArrayRef<int64_t> innerDimsPos = packOp.getInnerDimsPos();
1271 SmallVector<OpFoldResult> mixedTiles = packOp.getMixedTiles();
1272 ArrayRef<OpFoldResult> innerSizes = sizes.take_back(numTiles);
1273 for (
auto [i, pos] : llvm::enumerate(innerDimsPos)) {
1275 pos < static_cast<int64_t>(innerTileAlignments.size())
1276 ? innerTileAlignments[pos]
1277 : InnerTileAlignment::Unknown;
1278 if (alignment != InnerTileAlignment::Equal &&
1284 op,
b, offsets.drop_back(numTiles), sizes.drop_back(numTiles));
1285 if (
failed(tilingResult))
1287 return tilingResult.value();
1290 LogicalResult generateScalarImplementation(Operation *op, OpBuilder &builder,
1293 auto packOp = cast<PackOp>(op);
1294 assert(packOp.hasPureBufferSemantics() &&
1295 "expected operation to have buffer semantics");
1296 OpBuilder::InsertionGuard g(builder);
1299 SmallVector<Value> ivVec(ivs);
1302 SmallVector<OpFoldResult> outputShape;
1303 Value dest = packOp.getDest();
1304 for (
auto dim : llvm::seq<int64_t>(0, packOp.getDestRank()))
1313 for (
auto dataTileDim : llvm::seq<unsigned>(packOp.getSourceRank(),
1314 packOp.getDestRank() - 1)) {
1316 outputShape[dataTileDim]);
1317 scf::ForOp loop = scf::ForOp::create(builder, loc, zero, ub, one);
1319 ivVec.push_back(loop.getInductionVar());
1326 [&](OpBuilder &bodyBuilder, Location bodyLoc, Value iv,
1328 ivVec.push_back(iv);
1329 generatePackOpScalarImplementationBody(packOp, bodyBuilder, bodyLoc,
1331 scf::YieldOp::create(bodyBuilder, bodyLoc);
1336 LogicalResult getIterationDomainTileFromOperandTiles(
1337 Operation *op, OpBuilder &
b, ArrayRef<unsigned> operandNumbers,
1338 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1339 ArrayRef<SmallVector<OpFoldResult>> allSizes,
1340 SmallVectorImpl<OpFoldResult> &resultOffsets,
1341 SmallVectorImpl<OpFoldResult> &resultSizes)
const {
1342 return getIterationDomainTileFromOperandTiles(
1343 op,
b, operandNumbers, allOffsets, allSizes, resultOffsets, resultSizes,
1350 LogicalResult getIterationDomainTileFromOperandTiles(
1351 Operation *op, OpBuilder &
b, ArrayRef<unsigned> operandNumbers,
1352 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1353 ArrayRef<SmallVector<OpFoldResult>> allSizes,
1354 SmallVectorImpl<OpFoldResult> &resultOffsets,
1355 SmallVectorImpl<OpFoldResult> &resultSizes,
1356 ArrayRef<InnerTileAlignment> innerTileAlignments)
const {
1357 if (operandNumbers.size() != 1 || operandNumbers[0] != 0) {
1359 { llvm::dbgs() <<
"unsupported operands for consumer fusion"; });
1363 ArrayRef<OpFoldResult> offsets(allOffsets[0]);
1364 ArrayRef<OpFoldResult> sizes(allSizes[0]);
1365 auto packOp = cast<PackOp>(op);
1366 Location loc = packOp.getLoc();
1367 SmallVector<OpFoldResult> outerDimOffsets, outerDimSizes;
1369 packOp.getDimAndTileMapping();
1370 SmallVector<int64_t> outerShapeWithoutTranspose(
1371 packOp.getDestType().getShape().take_front(packOp.getSourceRank()));
1372 if (!packOp.getOuterDimsPerm().empty()) {
1374 outerShapeWithoutTranspose,
1377 for (
auto dim : llvm::seq<int64_t>(packOp.getSourceRank())) {
1378 if (dimAndTileMapping.count(dim)) {
1379 FailureOr<int64_t> cstTileSize =
1381 presburger::BoundType::UB, sizes[dim],
1383 ValueBoundsOptions{
true});
1384 std::optional<int64_t> cstInnerSize =
1391 dim < static_cast<int64_t>(innerTileAlignments.size())
1392 ? innerTileAlignments[dim]
1393 : InnerTileAlignment::Unknown;
1406 int64_t srcDimSize = packOp.getSourceType().getDimSize(dim);
1407 int64_t destDimSize = outerShapeWithoutTranspose[dim];
1408 bool isTiled = innerTileAlignment != InnerTileAlignment::Unknown ||
1410 ShapedType::isDynamic(srcDimSize) ||
1411 cstTileSize.value() < srcDimSize;
1413 outerDimOffsets.push_back(offsets[dim]);
1414 if (ShapedType::isStatic(destDimSize)) {
1415 outerDimSizes.push_back(
b.getIndexAttr(destDimSize));
1417 outerDimSizes.push_back(
1418 b.createOrFold<tensor::DimOp>(loc, packOp.getDest(), dim));
1445 bool assumeInnerTileSizesMatchTiles =
1446 innerTileAlignment == InnerTileAlignment::Equal;
1447 bool staticallyDecidable =
1448 !
failed(cstTileSize) && cstInnerSize.has_value();
1449 if (innerTileAlignment == InnerTileAlignment::Unknown) {
1450 if (!staticallyDecidable || *cstTileSize % *cstInnerSize != 0)
1452 }
else if (staticallyDecidable) {
1453 assert(*cstTileSize % *cstInnerSize == 0 &&
1454 "InnerTileAlignment hint contradicts statically known tile "
1456 assert((innerTileAlignment != InnerTileAlignment::Equal ||
1457 *cstTileSize == *cstInnerSize) &&
1458 "InnerTileAlignment::Equal contradicts statically known tile "
1462 using AV = affine::AffineValueExpr;
1463 affine::AffineBuilder ab(
b, loc);
1464 AffineExpr dim0, sym;
1467 auto avOffset = AV(dim0).bind(offsets[dim]);
1468 auto avSize = AV(dim0).bind(sizes[dim]);
1469 auto avTileSize = AV(sym).bind(dimAndTileMapping[dim]);
1470 outerDimOffsets.push_back(ab.floor(avOffset, avTileSize));
1473 outerDimSizes.push_back(assumeInnerTileSizesMatchTiles
1475 : ab.ceil(avSize, avTileSize));
1477 outerDimOffsets.push_back(offsets[dim]);
1478 outerDimSizes.push_back(sizes[dim]);
1481 applyPermToRange(outerDimOffsets, outerDimSizes, packOp.getOuterDimsPerm());
1482 resultOffsets = outerDimOffsets;
1483 resultSizes = outerDimSizes;
1487 FailureOr<TilingResult> getTiledImplementationFromOperandTiles(
1488 Operation *op, OpBuilder &
b, ArrayRef<unsigned> operandNumbers,
1489 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1490 ArrayRef<SmallVector<OpFoldResult>> allSizes)
const {
1491 return getTiledImplementationFromOperandTiles(op,
b, operandNumbers,
1492 allOffsets, allSizes,
1497 FailureOr<TilingResult> getTiledImplementationFromOperandTiles(
1498 Operation *op, OpBuilder &
b, ArrayRef<unsigned> operandNumbers,
1499 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1500 ArrayRef<SmallVector<OpFoldResult>> allSizes,
1501 ArrayRef<InnerTileAlignment> innerTileAlignments)
const {
1502 if (operandNumbers.size() != 1 || operandNumbers[0] != 0) {
1503 LLVM_DEBUG({ llvm::dbgs() <<
"unhandled operands for consumer fusion"; });
1507 ArrayRef<OpFoldResult> offsets(allOffsets[0]);
1508 ArrayRef<OpFoldResult> sizes(allSizes[0]);
1510 auto packOp = cast<PackOp>(op);
1515 if (!packOp.hasPureTensorSemantics())
1518 Location loc = packOp.getLoc();
1520 int64_t inputRank = packOp.getSourceRank();
1521 auto oneAttr =
b.getI64IntegerAttr(1);
1522 SmallVector<OpFoldResult> strides(inputRank, oneAttr);
1524 SmallVector<Value> tiledOperands;
1525 auto sourceSlice = tensor::ExtractSliceOp::create(
1526 b, loc, packOp.getSource(), offsets, sizes, strides);
1527 tiledOperands.push_back(sourceSlice);
1529 SmallVector<OpFoldResult> outerDimOffsets, outerDimSizes;
1530 if (
failed(getIterationDomainTileFromOperandTiles(
1531 op,
b, operandNumbers, allOffsets, allSizes, outerDimOffsets,
1532 outerDimSizes, innerTileAlignments)))
1535 SmallVector<OpFoldResult> outputOffsets, outputSizes;
1537 outputOffsets, outputSizes)))
1540 strides.append(packOp.getDestRank() - inputRank, oneAttr);
1541 auto outSlice = tensor::ExtractSliceOp::create(
1542 b, loc, packOp.getDest(), outputOffsets, outputSizes, strides);
1543 tiledOperands.push_back(outSlice);
1545 if (
auto val = packOp.getPaddingValue())
1546 tiledOperands.push_back(val);
1547 for (
auto tile : packOp.getInnerTiles())
1548 tiledOperands.push_back(
tile);
1550 PackOp tiledPackOp =
1551 PackOp::create(
b, loc,
TypeRange{outSlice.getType()}, tiledOperands,
1552 packOp.getProperties(),
1553 packOp->getDiscardableAttrDictionary().getValue());
1555 return TilingResult{
1557 SmallVector<Value>(tiledPackOp->getResults()),
1558 llvm::to_vector(ArrayRef<Operation *>{sourceSlice, outSlice})};
1562struct UnpackTileDimInfo {
1563 bool isAlignedToInnerTileSize;
1564 OpFoldResult sourceOffset;
1565 OpFoldResult sourceSize;
1566 OpFoldResult resultOffset;
1567 OpFoldResult destExpandedSize;
1573static UnpackTileDimInfo
1577 UnpackTileDimInfo info;
1581 unpackOp.getDimAndTileMapping();
1583 if (!dimAndTileMapping.count(tileDim)) {
1584 info.isAlignedToInnerTileSize =
true;
1585 info.sourceOffset = tileOffset;
1586 info.sourceSize = tileSize;
1587 info.resultOffset = zeroAttr;
1588 info.destExpandedSize = tileSize;
1599 OpFoldResult innerTileSize = dimAndTileMapping[tileDim];
1601 info.isAlignedToInnerTileSize =
false;
1614 bool assumeInnerTileSizesMatchTiles =
1616 bool staticallyDecidable = !
failed(cstSize) && cstInnerSize.has_value();
1618 info.isAlignedToInnerTileSize =
true;
1619 if (staticallyDecidable) {
1620 assert(*cstSize % *cstInnerSize == 0 &&
1621 "InnerTileAlignment hint contradicts statically known tile sizes");
1623 *cstSize == *cstInnerSize) &&
1624 "InnerTileAlignment::Equal contradicts statically known tile "
1628 if (info.isAlignedToInnerTileSize || (!
failed(cstSize) && cstInnerSize)) {
1629 if (!info.isAlignedToInnerTileSize && *cstSize % *cstInnerSize == 0)
1630 info.isAlignedToInnerTileSize =
true;
1634 if (assumeInnerTileSizesMatchTiles ||
1635 (cstInnerSize && !
failed(cstSize) && *cstInnerSize == *cstSize)) {
1636 auto lhs = AV(dim0).bind(tileOffset);
1637 auto rhs = AV(dim1).bind(innerTileSize);
1638 info.sourceOffset = ab.floor(lhs, rhs);
1639 info.sourceSize = oneAttr;
1640 info.resultOffset = zeroAttr;
1641 info.destExpandedSize = tileSize;
1646 if (info.isAlignedToInnerTileSize) {
1648 ab.floor(AV(dim0).bind(tileOffset), AV(dim1).bind(innerTileSize));
1649 info.resultOffset = zeroAttr;
1650 info.destExpandedSize = tileSize;
1659 ab.ceil(AV(dim0).bind(tileSize), AV(dim1).bind(innerTileSize));
1663 affine::DivModValue firstCoord = affine::getDivMod(
1667 ab.add(AV(dim0).bind(tileOffset), AV(dim1).bind(tileSize));
1668 affine::DivModValue lastCoord = affine::getDivMod(
1672 ab.sub(AV(dim0).bind(tileExclusiveBound), AV(dim1).bind(oneAttr))),
1675 OpFoldResult lengthMinusOne = ab.sub(AV(dim0).bind(lastCoord.quotient),
1676 AV(dim1).bind(firstCoord.quotient));
1678 ab.add(AV(dim0).bind(lengthMinusOne), AV(dim1).bind(oneAttr));
1679 info.sourceOffset = firstCoord.quotient;
1680 info.resultOffset = firstCoord.remainder;
1683 info.destExpandedSize =
b.createOrFold<arith::MulIOp>(
1689struct UnPackOpTiling
1690 :
public TilingInterface::ExternalModel<UnPackOpTiling, linalg::UnPackOp> {
1691 using Base = TilingInterface::ExternalModel<UnPackOpTiling, linalg::UnPackOp>;
1692 using Base::getIterationDomainTileFromOperandTiles;
1694 SmallVector<utils::IteratorType> getLoopIteratorTypes(Operation *op)
const {
1695 auto unpackOp = cast<UnPackOp>(op);
1696 SmallVector<utils::IteratorType> iteratorTypes(
1697 unpackOp.getDestRank(), utils::IteratorType::parallel);
1698 return iteratorTypes;
1701 SmallVector<Range> getIterationDomain(Operation *op, OpBuilder &
b)
const {
1702 return getPackUnPackIterationDomain<UnPackOp>(cast<UnPackOp>(op),
b);
1719 FailureOr<TilingResult>
1721 ArrayRef<OpFoldResult> offsets,
1722 ArrayRef<OpFoldResult> sizes)
const {
1728 Operation *op, OpBuilder &
b, ArrayRef<OpFoldResult> offsets,
1729 ArrayRef<OpFoldResult> sizes,
1730 ArrayRef<InnerTileAlignment> innerTileAlignments)
const {
1731 auto unpackOp = cast<UnPackOp>(op);
1736 if (!unpackOp.hasPureTensorSemantics())
1739 int64_t srcRank = unpackOp.getSourceRank();
1740 int64_t destRank = unpackOp.getDestRank();
1741 int64_t numInnerTiles = srcRank - destRank;
1742 Location loc = unpackOp.getLoc();
1747 bool isPerfectTilingCase =
true;
1748 Attribute oneAttr =
b.getIndexAttr(1);
1749 SmallVector<OpFoldResult> sliceSrcStrides(destRank, oneAttr);
1750 SmallVector<OpFoldResult> sliceSrcIndices, sliceSrcSizes;
1751 SmallVector<OpFoldResult> destExpandedSizes, resultOffsetsFromDest;
1752 for (
auto dim : llvm::seq<int64_t>(0, destRank)) {
1753 UnpackTileDimInfo info = getUnpackTileDimInfo(
1754 b, unpackOp, dim, offsets[dim], sizes[dim],
1755 dim <
static_cast<int64_t
>(innerTileAlignments.size())
1756 ? innerTileAlignments[dim]
1757 : InnerTileAlignment::Unknown);
1758 if (!info.isAlignedToInnerTileSize)
1759 isPerfectTilingCase =
false;
1760 sliceSrcIndices.push_back(info.sourceOffset);
1761 sliceSrcSizes.push_back(info.sourceSize);
1762 destExpandedSizes.push_back(info.destExpandedSize);
1763 resultOffsetsFromDest.push_back(info.resultOffset);
1768 applyPermToRange(sliceSrcIndices, sliceSrcSizes,
1769 unpackOp.getOuterDimsPerm());
1770 Attribute zeroAttr =
b.getIndexAttr(0);
1771 sliceSrcIndices.append(numInnerTiles, zeroAttr);
1772 sliceSrcSizes.append(unpackOp.getMixedTiles());
1773 sliceSrcStrides.append(numInnerTiles, oneAttr);
1774 SmallVector<Operation *> generatedSlices;
1775 tensor::ExtractSliceOp sliceSource = tensor::ExtractSliceOp::create(
1776 b, loc, unpackOp.getSource(), sliceSrcIndices, sliceSrcSizes,
1778 generatedSlices.push_back(sliceSource);
1780 SmallVector<OpFoldResult> destStrides(destRank, oneAttr);
1782 if (isPerfectTilingCase) {
1783 auto destSliceOp = tensor::ExtractSliceOp::create(
1784 b, loc, unpackOp.getDest(), offsets, sizes, destStrides);
1785 sliceDest = destSliceOp;
1786 generatedSlices.push_back(destSliceOp);
1788 sliceDest = tensor::EmptyOp::create(
1789 b, loc, destExpandedSizes, unpackOp.getDestType().getElementType());
1792 SmallVector<Value> tiledOperands = {sliceSource.getResult(), sliceDest};
1793 for (
auto tile : unpackOp.getInnerTiles())
1794 tiledOperands.push_back(
tile);
1796 UnPackOp tiledUnpackOp =
1798 unpackOp.getProperties(),
1799 unpackOp->getDiscardableAttrDictionary().getValue());
1801 if (isPerfectTilingCase)
1802 return TilingResult{{tiledUnpackOp},
1803 SmallVector<Value>(tiledUnpackOp->getResults()),
1806 auto extractSlice = tensor::ExtractSliceOp::create(
1807 b, loc, tiledUnpackOp->getResult(0), resultOffsetsFromDest, sizes,
1809 return TilingResult{
1810 {tiledUnpackOp}, {extractSlice.getResult()}, generatedSlices};
1815 ArrayRef<OpFoldResult> offsets,
1816 ArrayRef<OpFoldResult> sizes,
1817 SmallVector<OpFoldResult> &resultOffsets,
1818 SmallVector<OpFoldResult> &resultSizes)
const {
1819 resultOffsets = llvm::to_vector(offsets);
1820 resultSizes = llvm::to_vector(sizes);
1824 FailureOr<TilingResult>
1825 generateResultTileValue(Operation *op, OpBuilder &
b,
unsigned resultNumber,
1826 ArrayRef<OpFoldResult> offsets,
1827 ArrayRef<OpFoldResult> sizes)
const {
1828 return generateResultTileValue(op,
b, resultNumber, offsets, sizes,
1832 FailureOr<TilingResult> generateResultTileValue(
1833 Operation *op, OpBuilder &
b,
unsigned resultNumber,
1834 ArrayRef<OpFoldResult> offsets, ArrayRef<OpFoldResult> sizes,
1835 ArrayRef<InnerTileAlignment> innerTileAlignments)
const {
1836 FailureOr<TilingResult> tilingResult =
1838 if (
failed(tilingResult))
1840 return tilingResult.value();
1843 LogicalResult generateScalarImplementation(Operation *op, OpBuilder &builder,
1846 auto unpackOp = cast<UnPackOp>(op);
1847 assert(unpackOp.hasPureBufferSemantics() &&
1848 "expected operation to have buffer semantics");
1849 assert(ivs.size() == unpackOp.getDestRank() &&
1850 "number of ivs must match the rank of the output tensor");
1851 OpBuilder::InsertionGuard g(builder);
1854 unpackOp.getDimAndTileMapping();
1856 SmallVector<Value> inputIvs;
1858 SmallVector<Value> inputIvsPointLoops;
1859 inputIvs.reserve(unpackOp.getDestRank());
1860 inputIvsPointLoops.reserve(dimAndTileMapping.size());
1861 for (
auto dim : llvm::seq<int64_t>(0, unpackOp.getDestRank())) {
1862 if (dimAndTileMapping.count(dim)) {
1863 affine::DivModValue divMod =
1864 affine::getDivMod(builder, loc, ivs[dim],
1866 builder, loc, dimAndTileMapping[dim]));
1867 inputIvsPointLoops.push_back(divMod.remainder);
1868 inputIvs.push_back(divMod.quotient);
1870 inputIvs.push_back(ivs[dim]);
1876 assert(inputIvsPointLoops.size() + inputIvs.size() ==
1877 unpackOp.getSourceRank() &&
1878 "expect same number of induction variables equals to input rank");
1880 ArrayRef<int64_t> innerDims = unpackOp.getInnerDimsPos();
1881 SmallVector<int64_t> interchangeVector =
1882 computeInterchangeFromDimPos(innerDims, unpackOp.getDestRank());
1883 SmallVector<Value> interchangedInputIvsPointLoops = inputIvsPointLoops;
1884 interchangedInputIvsPointLoops = interchange<Value>(
1885 interchangedInputIvsPointLoops, interchangeVector, 0);
1888 ArrayRef<int64_t> outerDims = unpackOp.getOuterDimsPerm();
1889 if (!outerDims.empty())
1890 inputIvs = interchange<Value>(inputIvs, outerDims, 0);
1892 llvm::append_range(inputIvs, interchangedInputIvsPointLoops);
1894 memref::LoadOp::create(builder, loc, unpackOp.getSource(), inputIvs);
1895 memref::StoreOp::create(builder, loc, scalar, unpackOp.getDest(), ivs);
1901 LogicalResult getIterationDomainTileFromOperandTiles(
1902 Operation *op, OpBuilder &
b, ArrayRef<unsigned> operandNumbers,
1903 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1904 ArrayRef<SmallVector<OpFoldResult>> allSizes,
1905 SmallVectorImpl<OpFoldResult> &resultOffsets,
1906 SmallVectorImpl<OpFoldResult> &resultSizes)
const {
1907 if (operandNumbers.size() != 1) {
1908 LLVM_DEBUG({ llvm::dbgs() <<
"unable to handle multiple operands"; });
1911 auto unPackOp = cast<UnPackOp>(op);
1912 unsigned operandNumber = operandNumbers[0];
1913 ArrayRef<OpFoldResult> offsets(allOffsets[0]);
1914 ArrayRef<OpFoldResult> sizes(allSizes[0]);
1917 if (operandNumber == unPackOp.getDestMutable().getOperandNumber()) {
1918 resultOffsets = llvm::to_vector(offsets);
1919 resultSizes = llvm::to_vector(sizes);
1922 Location loc = unPackOp.getLoc();
1924 int64_t numTiles = unPackOp.getInnerDimsPos().size();
1925 auto destOffsets = offsets.drop_back(numTiles);
1926 auto destSizes = sizes.drop_back(numTiles);
1929 int64_t outputRank = unPackOp.getDestRank();
1933 SmallVector<OpFoldResult> outputMixedSizes = reifiedReturnShapes.front();
1934 SmallVector<OpFoldResult> origOffsets(destOffsets);
1935 SmallVector<OpFoldResult> origSizes(destSizes);
1936 applyPermToRange(origOffsets, origSizes,
1940 unPackOp.getDimAndTileMapping();
1942 for (
auto dim : llvm::seq<int64_t>(0, outputRank)) {
1943 using AV = affine::AffineValueExpr;
1944 affine::AffineBuilder ab(
b, loc);
1945 AffineExpr dim0, dim1, sym0;
1948 if (dimAndTileMapping.count(dim)) {
1952 auto avOffset = AV(dim0).bind(origOffsets[dim]);
1953 auto avSize = AV(dim0).bind(origSizes[dim]);
1954 auto avTileSize = AV(sym0).bind(dimAndTileMapping[dim]);
1955 auto avResultSize = AV(dim0).bind(outputMixedSizes[dim]);
1956 resultOffsets.push_back(ab.mul(avOffset, avTileSize));
1957 auto avResultOffset = AV(dim1).bind(resultOffsets.back());
1958 resultSizes.push_back(ab.min({ab.mul(avSize, avTileSize),
1959 ab.sub(avResultSize, avResultOffset)}));
1961 resultOffsets.push_back(origOffsets[dim]);
1962 resultSizes.push_back(origSizes[dim]);
1968 FailureOr<TilingResult> getTiledImplementationFromOperandTiles(
1969 Operation *op, OpBuilder &
b, ArrayRef<unsigned> operandNumbers,
1970 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1971 ArrayRef<SmallVector<OpFoldResult>> allSizes)
const {
1972 return getTiledImplementationFromOperandTiles(op,
b, operandNumbers,
1973 allOffsets, allSizes,
1978 FailureOr<TilingResult> getTiledImplementationFromOperandTiles(
1979 Operation *op, OpBuilder &
b, ArrayRef<unsigned> operandNumbers,
1980 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1981 ArrayRef<SmallVector<OpFoldResult>> allSizes,
1982 ArrayRef<InnerTileAlignment> innerTileAlignments)
const {
1983 if (operandNumbers.size() != 1 || operandNumbers[0] != 0) {
1984 LLVM_DEBUG({ llvm::dbgs() <<
"unhandled operands for consumer fusion"; });
1987 auto unPackOp = cast<UnPackOp>(op);
1992 if (!unPackOp.hasPureTensorSemantics())
1995 ArrayRef<OpFoldResult> offsets(allOffsets[0]);
1996 ArrayRef<OpFoldResult> sizes(allSizes[0]);
2002 int64_t numTiles = unPackOp.getInnerDimsPos().size();
2003 ArrayRef<int64_t> innerDimsPos = unPackOp.getInnerDimsPos();
2004 SmallVector<OpFoldResult> mixedTiles = unPackOp.getMixedTiles();
2005 ArrayRef<OpFoldResult> innerSizes = sizes.take_back(numTiles);
2006 for (int64_t i = 0; i < numTiles; ++i) {
2009 int64_t destDim = innerDimsPos[i];
2011 destDim < static_cast<int64_t>(innerTileAlignments.size()) &&
2012 innerTileAlignments[destDim] == InnerTileAlignment::Equal;
2022 "InnerTileAlignment::Equal contradicts statically known tile "
2031 Location loc = unPackOp.getLoc();
2035 SmallVector<OpFoldResult> outputOffsets, outputSizes;
2036 if (
failed(getIterationDomainTileFromOperandTiles(
2037 op,
b, operandNumbers, allOffsets, allSizes, outputOffsets,
2041 auto oneAttr =
b.getI64IntegerAttr(1);
2042 int64_t outputRank = unPackOp.getDestRank();
2043 SmallVector<OpFoldResult> strides(outputRank, oneAttr);
2045 SmallVector<Value> tiledOperands;
2047 auto extractDestSlice = tensor::ExtractSliceOp::create(
2048 b, loc, unPackOp.getDest(), outputOffsets, outputSizes, strides);
2049 tiledOperands.push_back(extractDestSlice);
2051 strides.append(unPackOp.getSourceRank() - outputRank, oneAttr);
2053 auto extractSourceSlice = tensor::ExtractSliceOp::create(
2054 b, loc, unPackOp.getSource(), offsets, sizes, strides);
2055 tiledOperands.insert(tiledOperands.begin(), extractSourceSlice);
2056 for (
auto tile : unPackOp.getInnerTiles())
2057 tiledOperands.push_back(
tile);
2060 UnPackOp tiledUnPackOp =
2061 UnPackOp::create(
b, loc,
TypeRange{extractDestSlice.getType()},
2062 tiledOperands, unPackOp.getProperties(),
2063 unPackOp->getDiscardableAttrDictionary().getValue());
2065 return TilingResult{{tiledUnPackOp},
2066 SmallVector<Value>(tiledUnPackOp->getResults()),
2067 llvm::to_vector(ArrayRef<Operation *>{
2068 extractSourceSlice, extractDestSlice})};
2074template <
typename OpType>
2076 OpType::template attachInterface<LinalgOpTilingInterfaceModel<OpType>>(*ctx);
2077 OpType::template attachInterface<
2078 LinalgOpPartialReductionInterfaceModel<OpType>>(*ctx);
2082template <
typename... OpTypes>
2093 linalg::PackOp::attachInterface<PackOpTiling>(*ctx);
2094 linalg::UnPackOp::attachInterface<UnPackOpTiling>(*ctx);
2096#include "mlir/Dialect/Linalg/IR/LinalgStructuredOps.cpp.inc"
2104 linalg::PackOp::attachInterface<PackOpTiling>(*ctx);
2105 linalg::UnPackOp::attachInterface<UnPackOpTiling>(*ctx);
static bool isTiled(AffineExpr expr, ArrayRef< OpFoldResult > tileSizes)
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.
RetT walk(FnT &&callback) const
Walk all of the AffineExpr's in this expression in postorder.
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
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.
Block represents an ordered list of Operations.
Operation * getTerminator()
Get the terminator operation of this block.
BlockArgListType getArguments()
iterator_range< iterator > without_terminator()
Return an iterator range over the operation within this block excluding the terminator operation at t...
IntegerAttr getIndexAttr(int64_t value)
MLIRContext * getContext() const
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.
auto lookupOrDefault(T from) const
Lookup a mapped value within the map.
void map(Value from, Value to)
Inserts a new mapping for 'from' to 'to'.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
MLIRContext is the top-level object for a collection of MLIR operations.
RAII guard to reset the insertion point of the builder when destroyed.
This class helps build Operations.
void setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
This class represents a single result from folding an operation.
This class represents an operand of an operation.
Operation is the basic unit of execution within MLIR.
Region & getRegion(unsigned index)
Returns the region held by this operation at position 'index'.
void setOperand(unsigned idx, Value value)
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Location getLoc()
The source location the operation was defined or derived from.
operand_range getOperands()
Returns an iterator on the underlying Value's.
result_range getResults()
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.
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.
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Type getType() const
Return the type of this value.
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
A utility result that is used to signal how to proceed with an ongoing walk:
static WalkResult advance()
bool wasInterrupted() const
Returns true if the walk was interrupted.
static WalkResult interrupt()
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
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,...
void registerTilingInterfaceExternalModelsForPackUnPackOps(DialectRegistry ®istry)
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.
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 ®istry)
SmallVector< Type > getTensorOutputTypes(LinalgOp op, ValueRange operands)
Returns the list of tensor output types produced when the given structured operation op is applied to...
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...
SmallVector< OpFoldResult > getMixedSizes(OpBuilder &builder, Location loc, Value value)
Return the dimensions of the given tensor value.
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...
@ PartialReductionOuterReduction
@ PartialReductionOuterParallel
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 .
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...
@ CeilDiv
RHS of ceildiv is always a constant or a symbolic expression.
@ Mod
RHS of mod is always a constant or a symbolic expression with a positive value.
@ FloorDiv
RHS of floordiv is always a constant or a symbolic expression.
llvm::SetVector< T, Vector, Set, N > SetVector
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 .
Value getValueOrCreateConstantIndexOp(OpBuilder &b, Location loc, OpFoldResult ofr)
Converts an OpFoldResult to a Value.
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...
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
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.
Container for result values of tiling.
Options that control value bound computation.
Helper struct to build simple AffineValueExprs with minimal type inference support.
A struct containg offsets-sizes-strides arguments of the tiled shape.
SmallVector< OpFoldResult > sizes
SmallVector< OpFoldResult > offsets
Eliminates variable at the specified position using Fourier-Motzkin variable elimination.