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 ")
208template <
typename LinalgOpTy>
209struct LinalgOpTilingInterface
210 :
public TilingInterface::ExternalModel<LinalgOpTilingInterface<LinalgOpTy>,
213 TilingInterface::ExternalModel<LinalgOpTilingInterface<LinalgOpTy>,
217 using Base::generateResultTileValue;
218 using Base::getIterationDomainTileFromOperandTiles;
219 using Base::getTiledImplementation;
220 using Base::getTiledImplementationFromOperandTiles;
223 SmallVector<utils::IteratorType> getLoopIteratorTypes(Operation *op)
const {
224 LinalgOpTy concreteOp = cast<LinalgOpTy>(op);
225 return concreteOp.getIteratorTypesArray();
229 SmallVector<Range> getIterationDomain(Operation *op, OpBuilder &
b)
const {
230 OpBuilder::InsertionGuard g(
b);
231 b.setInsertionPoint(op);
232 Location loc = op->
getLoc();
233 LinalgOp linalgOp = cast<LinalgOp>(op);
234 SmallVector<OpFoldResult> allShapesSizes =
235 linalgOp.createFlatListOfOperandDims(
b, loc);
236 AffineMap map = linalgOp.getShapesToLoopsMap();
238 return llvm::map_to_vector(map.
getResults(), [&](AffineExpr loopExpr) {
239 OpFoldResult ofr = affine::makeComposedFoldedAffineApply(b, loc, loopExpr,
241 return Range{b.getIndexAttr(0), ofr, b.getIndexAttr(1)};
246 FailureOr<TilingResult>
253 LinalgOp linalgOp = cast<LinalgOp>(op);
264 b, loc, linalgOp, valuesToTile, offsets, sizes, {},
true);
266 llvm::make_filter_range(
268 [](
Value v) ->
bool {
269 return isa_and_nonnull<tensor::ExtractSliceOp, memref::SubViewOp>(
277 Operation *tiledOp =
clone(
b, linalgOp, resultTensorTypes, tiledOperands);
288 getMappedOffsetAndSize(LinalgOp linalgOp,
OpBuilder &
b,
296 for (
auto [indexingMap, offsets, sizes] :
297 llvm::zip_equal(indexingMaps, allOffsets, allSizes)) {
298 for (
auto [resultExpr, offset, size] :
299 llvm::zip_equal(indexingMap.getResults(), offsets, sizes)) {
300 auto dimExpr = dyn_cast<AffineDimExpr>(resultExpr);
303 unsigned position = dimExpr.getPosition();
304 auto it = mappedOffsets.find(position);
305 if (it != mappedOffsets.end()) {
308 if (seenOffset != offset || seenSize != size) {
310 llvm::dbgs() <<
"inconsistent iteration space mapping from "
311 "offsets/sizes of operands/results";
316 mappedOffsets[position] = offset;
317 mappedSizes[position] = size;
325 cast<TilingInterface>(linalgOp.getOperation()).getIterationDomain(
b);
326 mappedOffsetsVec.resize(iterationDomain.size());
327 mappedSizesVec.resize(iterationDomain.size());
328 for (
auto [
index, domain] : llvm::enumerate(iterationDomain)) {
329 auto it = mappedOffsets.find(
index);
330 if (it != mappedOffsets.end()) {
331 mappedOffsetsVec[
index] = it->second;
332 mappedSizesVec[
index] = mappedSizes.lookup(
index);
335 mappedOffsetsVec[
index] = domain.offset;
336 mappedSizesVec[
index] = domain.size;
343 LogicalResult getIterationDomainTileFromOperandTiles(
349 auto linalgOp = cast<LinalgOp>(op);
352 llvm::map_to_vector(operandNumbers, [&](
unsigned operandNumber) {
353 OpOperand &opOperand = linalgOp->getOpOperand(operandNumber);
354 return linalgOp.getMatchingIndexingMap(&opOperand);
356 if (
failed(getMappedOffsetAndSize(linalgOp,
b, indexingMaps, allOffsets,
357 allSizes, iterDomainOffsets,
373 LinalgOp linalgOp = cast<LinalgOp>(op);
382 OpOperand *outOperand = linalgOp.getDpsInitOperand(resultNumber);
384 b, loc, outOperand->get(), sizes,
385 linalgOp.getMatchingIndexingMap(outOperand), offsets,
386 {}, subShapeSizes,
true);
387 resultOffsets = sliceParams.
offsets;
388 resultSizes = sliceParams.
sizes;
392 LogicalResult getIterationDomainTileFromResultTile(
397 auto linalgOp = cast<LinalgOp>(op);
404 linalgOp.getIndexingMapMatchingResult(op->
getResult(resultNumber));
407 "unhandled tiled implementation generation when result is not "
408 "accessed using a permuted projection");
414 getMappedOffsetAndSize(linalgOp,
b, indexingMap, {allOffsets},
415 {allSizes}, iterDomainOffsets, iterDomainSizes);
417 assert(succeeded(status) &&
"unexpected error in offset calculation");
421 FailureOr<TilingResult>
426 if (
failed(getIterationDomainTileFromResultTile(
427 op,
b, resultNumber, offsets, sizes, mappedOffsets, mappedSizes))) {
430 auto tilingInterfaceOp = cast<TilingInterface>(op);
431 FailureOr<TilingResult> tilingResult =
432 tilingInterfaceOp.getTiledImplementation(
b, mappedOffsets, mappedSizes);
437 if (tilingResult->tiledOps.size() != 1)
438 return op->
emitOpError(
"failed to generate tiled implementation");
441 tilingResult->tiledOps,
443 tilingResult->generatedSlices};
448 FailureOr<TilingResult> getTiledImplementationFromOperandTiles(
453 if (
failed(getIterationDomainTileFromOperandTiles(
454 op,
b, operandNumbers, allOffsets, allSizes, mappedOffsets,
464 auto linalgOp = cast<LinalgOp>(op);
465 if (!linalgOp.hasPureBufferSemantics())
466 return op->
emitOpError(
"expected operation to have buffer semantics");
469 indexedValues.reserve(linalgOp->getNumOperands());
473 for (
OpOperand &operand : linalgOp->getOpOperands()) {
474 if (!linalgOp.payloadUsesValueFromOperand(&operand)) {
475 indexedValues.push_back(
nullptr);
478 if (linalgOp.isScalar(&operand)) {
479 indexedValues.push_back(operand.get());
483 builder, linalgOpLoc, linalgOp.getMatchingIndexingMap(&operand), ivs);
485 memref::LoadOp::create(builder, linalgOpLoc, operand.get(),
indices);
486 indexedValues.push_back(
load);
493 bool isOpFusableWithConsumerSlice(
Operation *op,
unsigned resultNumber,
500 bool isOpFusableWithProducerSlices(
505 auto linalgOp = cast<LinalgOp>(op);
507 llvm::map_to_vector(operandNumbers, [&](
unsigned operandNumber) {
508 OpOperand &opOperand = linalgOp->getOpOperand(operandNumber);
509 return linalgOp.getMatchingIndexingMap(&opOperand);
514 return succeeded(getMappedOffsetAndSize(linalgOp,
b, indexingMaps,
515 allOffsets, allSizes, mappedOffsets,
527 for (
auto [
index, reductionDim] : llvm::enumerate(reductionDims)) {
528 if (reductionDim == value) {
540getPartialResultAffineMaps(LinalgOp linalgOp,
542 auto partialReductionMaps = llvm::map_to_vector(
543 linalgOp.getDpsInitsMutable(), [&](
OpOperand &opOperand) {
544 AffineMap map = linalgOp.getMatchingIndexingMap(&opOperand);
545 for (auto redPos : reductionDims) {
547 map.insertResult(getAffineDimExpr(redPos, linalgOp.getContext()),
548 map.getNumResults());
552 return partialReductionMaps;
555struct InitSliceInfo {
556 SmallVector<int64_t> resultShape;
557 SmallVector<OpFoldResult> offsets;
558 SmallVector<OpFoldResult> sizes;
559 SmallVector<OpFoldResult> strides;
565static InitSliceInfo getInitSliceInfoForOuterReduction(
572 Attribute zero = IntegerAttr::get(IndexType::get(context), 0);
573 Attribute one = IntegerAttr::get(IndexType::get(context), 1);
575 for (
auto [resultIdx, dimExpr] :
576 llvm::enumerate(partialReductionMap.
getResults())) {
577 if (isa<AffineConstantExpr>(dimExpr)) {
580 initOffsets.push_back(zero);
581 initSizes.push_back(initOperandShape[resultIdx]);
584 unsigned dim = cast<AffineDimExpr>(dimExpr).getPosition();
585 if (reductionDims.contains(dim)) {
586 initOffsets.push_back(zero);
588 initOffsets.push_back(offsets[dim]);
590 initSizes.push_back(sizes[dim]);
594 return {resultShape, initOffsets, initSizes, initStrides};
600static InitSliceInfo getInitSliceInfoForOuterParallel(
607 Attribute zero = IntegerAttr::get(IndexType::get(context), 0);
608 Attribute one = IntegerAttr::get(IndexType::get(context), 1);
611 for (
auto [resultIdx, dimExpr] :
612 llvm::enumerate(partialReductionMap.
getResults())) {
613 if (isa<AffineConstantExpr>(dimExpr)) {
616 initOffsets.push_back(zero);
617 initSizes.push_back(initOperandShape[resultIdx]);
618 resultShape.push_back(initOperandShape[resultIdx]);
621 unsigned dim = cast<AffineDimExpr>(dimExpr).getPosition();
622 if (std::optional<unsigned> dimPos = getPositionIn(reductionDims, dim)) {
623 initOffsets.push_back(splitReductionIvs[dimPos.value()]);
624 initSizes.push_back(one);
626 initOffsets.push_back(offsets[dim]);
627 initSizes.push_back(sizes[dim]);
628 resultShape.push_back(sizes[dim]);
633 return {staticShapes, initOffsets, initSizes, initStrides};
638static InitSliceInfo getInitSliceInfo(
MLIRContext *context,
647 return getInitSliceInfoForOuterReduction(
648 context, offsets, sizes, reductionDims, splitReductionIvs,
649 partialReductionMap, initOperandShape);
652 "unexpected ReductionTilingStrategy");
653 return getInitSliceInfoForOuterParallel(
654 context, offsets, sizes, reductionDims, splitReductionIvs,
655 partialReductionMap, initOperandShape);
660template <
typename LinalgOpTy>
661struct LinalgOpPartialReductionInterface
662 :
public PartialReductionOpInterface::ExternalModel<
663 LinalgOpPartialReductionInterface<LinalgOpTy>, LinalgOpTy> {
664 FailureOr<SmallVector<Value>> generateInitialTensorForPartialReduction(
665 Operation *op, OpBuilder &
b, Location loc, ArrayRef<OpFoldResult> sizes,
667 auto linalgOp = cast<LinalgOp>(op);
669 OpBuilder::InsertionGuard guard(
b);
670 if (linalgOp.hasPureBufferSemantics())
671 return op->
emitOpError(
"expected operation to have tensor semantics");
673 SmallVector<AffineMap> partialResultMaps =
674 getPartialResultAffineMaps(linalgOp, reductionDims);
676 SmallVector<Value> inits;
677 for (
auto [initIdx,
result, partialMap] :
678 llvm::enumerate(linalgOp->getResults(), partialResultMaps)) {
679 SmallVector<Operation *, 4> combinerOps;
682 combinerOps.size() != 1)
683 return op->
emitOpError(
"Failed to anaysis the reduction operation.");
685 Operation *reductionOp = combinerOps[0];
686 std::optional<TypedAttr> identity = arith::getNeutralElement(reductionOp);
687 if (!identity.has_value())
689 "Failed to get an identity value for the reduction operation.");
692 SmallVector<OpFoldResult> partialResultShape;
693 Value initValue = linalgOp.getDpsInits()[initIdx];
694 SmallVector<OpFoldResult> initShape =
696 for (
auto [resultIdx, dimExpr] :
697 llvm::enumerate(partialMap.getResults())) {
698 if (isa<AffineConstantExpr>(dimExpr)) {
701 partialResultShape.push_back(initShape[resultIdx]);
704 auto dim = cast<AffineDimExpr>(dimExpr);
705 partialResultShape.push_back(sizes[dim.getPosition()]);
710 tensor::EmptyOp::create(
b, loc, partialResultShape, elType);
711 Value constantOp = arith::ConstantOp::create(
b, loc, *identity);
712 auto identityTensor =
713 linalg::FillOp::create(
b, loc, constantOp, emptyTensor);
714 inits.push_back(identityTensor.getResult(0));
720 FailureOr<TilingResult>
721 tileToPartialReduction(Operation *op, OpBuilder &
b, Location loc,
723 ValueRange init, ArrayRef<OpFoldResult> offsets,
724 ArrayRef<OpFoldResult> sizes,
726 ArrayRef<OpFoldResult> splitReductionIvs)
const {
727 OpBuilder::InsertionGuard guard(
b);
728 auto linalgOp = cast<LinalgOp>(op);
730 SmallVector<AffineMap> partialReductionMaps =
731 getPartialResultAffineMaps(linalgOp, reductionDims);
735 SmallVector<AffineMap> newInitMaps;
736 if (tilingStrategy ==
737 ReductionTilingStrategy::PartialReductionOuterReduction) {
738 newInitMaps = llvm::to_vector(partialReductionMaps);
740 newInitMaps = llvm::map_to_vector(
741 linalgOp.getDpsInitsMutable(), [&](OpOperand &opOperand) {
742 return linalgOp.getMatchingIndexingMap(&opOperand);
748 b, loc, linalgOp, linalgOp.getDpsInputs(), offsets, sizes, {},
true);
749 SmallVector<Operation *> generatedSlices = llvm::map_to_vector(
750 llvm::make_filter_range(
751 tiledInputs, [](Value v) ->
bool {
return v.
getDefiningOp(); }),
755 SmallVector<Value, 1> tiledInits;
756 for (
auto [partialReductionMap, valueToTile, initOperandValue] :
757 llvm::zip_equal(partialReductionMaps, init, linalgOp.getDpsInits())) {
760 SmallVector<OpFoldResult> initOperandShape =
762 InitSliceInfo sliceInfo = getInitSliceInfo(
763 b.getContext(), tilingStrategy, offsets, sizes, reductionDims,
764 splitReductionIvs, partialReductionMap, initOperandShape);
765 auto valueToTileType = cast<RankedTensorType>(valueToTile.getType());
767 sliceInfo.resultShape, valueToTileType.getElementType(),
768 valueToTileType.getEncoding());
769 auto sliceOp = tensor::ExtractSliceOp::create(
771 sliceInfo.sizes, sliceInfo.strides);
772 tiledInits.push_back(sliceOp.getResult());
773 generatedSlices.push_back(sliceOp);
777 SmallVector<AffineMap> newMaps = linalgOp.getIndexingMapsArray();
778 for (
auto [initOperand, newInitMap] :
779 llvm::zip_equal(linalgOp.getDpsInitsMutable(), newInitMaps)) {
780 int mapIdx = linalgOp.getIndexingMapIndex(&initOperand);
781 newMaps[mapIdx] = newInitMap;
785 SmallVector<utils::IteratorType> newIteratorTypes =
786 linalgOp.getIteratorTypesArray();
787 if (tilingStrategy ==
788 ReductionTilingStrategy::PartialReductionOuterReduction) {
789 for (
int dim : reductionDims)
790 newIteratorTypes[dim] = utils::IteratorType::parallel;
794 Operation *partialReductionOp;
795 auto resultTypes =
ValueRange(tiledInits).getTypes();
796 if (tilingStrategy ==
797 ReductionTilingStrategy::PartialReductionOuterReduction) {
798 auto genericOp = GenericOp::create(
b, loc, resultTypes, tiledInputs,
799 tiledInits, newMaps, newIteratorTypes);
802 genericOp.getRegion().begin(), mapping);
804 partialReductionOp = genericOp.getOperation();
806 SmallVector<Value> operands = std::move(tiledInputs);
807 llvm::append_range(operands, tiledInits);
808 partialReductionOp =
mlir::clone(
b, op, resultTypes, operands);
812 {partialReductionOp},
813 llvm::map_to_vector(partialReductionOp->
getResults(),
814 [](OpResult r) -> Value { return r; }),
818 FailureOr<MergeResult>
819 mergeReductions(Operation *op, OpBuilder &
b, Location loc,
822 auto linalgOp = cast<LinalgOp>(op);
823 SmallVector<AffineMap> partialReductionMaps =
824 getPartialResultAffineMaps(linalgOp, reductionDims);
827 SmallVector<Operation *> mergeOperations;
828 SmallVector<Value> replacements;
829 for (
auto [idx, init, partialResult, partialMap] : llvm::enumerate(
830 linalgOp.getDpsInits(), partialReduce, partialReductionMaps)) {
831 unsigned initIdx = idx;
836 SmallVector<int64_t> partialReductionDims;
837 for (
auto [resultNum, dimExpr] :
838 llvm::enumerate(partialMap.getResults())) {
839 if (isa<AffineConstantExpr>(dimExpr))
841 unsigned dim = cast<AffineDimExpr>(dimExpr).getPosition();
842 if (llvm::is_contained(reductionDims, dim)) {
843 partialReductionDims.push_back(resultNum);
847 auto reduction = linalg::ReduceOp::create(
848 b, loc, partialResult, init, partialReductionDims,
849 [&linalgOp, &initIdx](OpBuilder &
b, Location loc,
ValueRange inputs) {
851 SmallVector<Operation *, 4> combinerOps;
854 Operation *clonedReductionOp =
b.clone(*combinerOps[0]);
858 linalg::YieldOp::create(
b, loc, clonedReductionOp->
getResult(0));
861 mergeOperations.push_back(reduction);
862 replacements.push_back(reduction->getResult(0));
865 return MergeResult{mergeOperations, replacements};
868 LogicalResult getPartialResultTilePosition(
869 Operation *op, OpBuilder &
b,
unsigned resultNumber,
872 ArrayRef<OpFoldResult> splitReductionIvs,
873 SmallVector<OpFoldResult> &resultOffsets,
874 SmallVector<OpFoldResult> &resultSizes)
const {
875 auto linalgOp = cast<LinalgOp>(op);
876 SmallVector<AffineMap> partialReductionMaps =
877 getPartialResultAffineMaps(linalgOp, reductionDims);
880 Value initOperandValue = linalgOp.getDpsInits()[resultNumber];
881 Location loc = op->
getLoc();
882 SmallVector<OpFoldResult> initOperandShape =
884 InitSliceInfo sliceInfo =
885 getInitSliceInfo(
b.getContext(), tilingStrategy, offsets, sizes,
886 reductionDims, splitReductionIvs,
887 partialReductionMaps[resultNumber], initOperandShape);
888 std::swap(resultOffsets, sliceInfo.offsets);
889 std::swap(resultSizes, sliceInfo.sizes);
895template <
typename OpTy>
898 static_assert(llvm::is_one_of<OpTy, PackOp, UnPackOp>::value,
899 "applies to only pack or unpack operations");
901 int64_t rank = (std::is_same<OpTy, PackOp>::value) ? op.getSourceRank()
906 (
void)op.reifyResultShapes(builder, resultShape);
908 for (
auto dim : llvm::seq<int64_t>(0, rank)) {
909 loopBounds[dim].offset = zero;
910 loopBounds[dim].stride = one;
911 loopBounds[dim].size = resultShape[0][dim];
919 if (permutation.empty())
931 interchangeVector.reserve(dimsPos.size());
940 for (
int64_t dimsIdx = 0, end = dimsPos.size(); dimsIdx < end; dimsIdx++)
941 dimsAndPosMapping[dimsPos[dimsIdx]] = dimsIdx;
945 for (
int64_t dimsIdx = 0; dimsIdx < rank; dimsIdx++) {
946 if (dimsAndPosMapping.count(dimsIdx))
947 interchangeVector.push_back(dimsAndPosMapping[dimsIdx]);
949 return interchangeVector;
969 for (
auto [idx, val] : llvm::enumerate(interchangeVector))
970 vec[idx + offset] = elements[val + offset];
976static void generatePackOpScalarImplementationBody(PackOp packOp,
991 computeInterchangeFromDimPos(dimsToInnerBlock, packOp.getSourceRank());
992 interchangedIvs = interchange<Value>(interchangedIvs, interchangeVector,
993 packOp.getSourceRank());
994 if (!dimsToOuterBlock.empty()) {
996 computeInterchangeFromDimPos(dimsToOuterBlock, packOp.getSourceRank());
998 interchange<Value>(interchangedIvs, interchangeVector, 0);
1001 packOp.getDimAndTileMapping();
1003 size_t pointLoopsOffset = 0;
1004 int64_t sourceRank = packOp.getSourceRank();
1005 for (
auto dim : llvm::seq<int64_t>(0, sourceRank)) {
1006 if (dimAndTileMapping.contains(dim)) {
1011 builder, loc, i *
tile +
j,
1013 interchangedIvs[dim],
1014 interchangedIvs[pointLoopsOffset + packOp.getSourceRank()],
1015 dimAndTileMapping[dim]});
1016 sourceIndices.push_back(sourceIndex);
1019 sourceIndices.push_back(interchangedIvs[dim]);
1023 auto createLoad = [&]() ->
Value {
1024 return memref::LoadOp::create(
1025 builder, loc, packOp.getSource(),
1029 if (
auto paddingValue = packOp.getPaddingValue()) {
1032 for (
auto dim : llvm::seq<int64_t>(0, sourceRank)) {
1035 Value cond = arithBuilder.slt(
1039 scalar = scf::IfOp::create(
1042 scf::YieldOp::create(
b, l, createLoad());
1046 scf::YieldOp::create(
b, l, paddingValue);
1050 scalar = createLoad();
1053 memref::StoreOp::create(builder, loc, scalar, packOp.getDest(), ivs);
1057 :
public TilingInterface::ExternalModel<PackOpTiling, linalg::PackOp> {
1058 using Base = TilingInterface::ExternalModel<PackOpTiling, linalg::PackOp>;
1059 using Base::getTiledImplementation;
1061 SmallVector<utils::IteratorType> getLoopIteratorTypes(Operation *op)
const {
1065 auto packOp = cast<PackOp>(op);
1066 SmallVector<utils::IteratorType> iteratorTypes(
1067 packOp.getSourceRank(), utils::IteratorType::parallel);
1068 return iteratorTypes;
1071 SmallVector<Range> getIterationDomain(Operation *op, OpBuilder &
b)
const {
1072 return getPackUnPackIterationDomain<PackOp>(cast<PackOp>(op),
b);
1075 FailureOr<TilingResult>
1077 ArrayRef<OpFoldResult> offsets,
1078 ArrayRef<OpFoldResult> sizes)
const {
1079 auto packOp = cast<PackOp>(op);
1081 if (!packOp.hasPureTensorSemantics())
1084 Location loc = packOp.getLoc();
1088 int64_t inputRank = packOp.getSourceRank();
1089 SmallVector<OpFoldResult> origOffsets(offsets);
1090 SmallVector<OpFoldResult> origSizes(sizes);
1091 applyPermToRange(origOffsets, origSizes,
1095 packOp.getDimAndTileMapping();
1096 SmallVector<OpFoldResult> srcDimValues =
1098 SmallVector<OpFoldResult> inputIndices, inputSizes;
1099 for (
auto dim : llvm::seq<int64_t>(0, inputRank)) {
1100 using AV = affine::AffineValueExpr;
1101 affine::AffineBuilder ab(
b, loc);
1102 AffineExpr dim0, dim1, sym;
1105 if (dimAndTileMapping.count(dim)) {
1109 auto avOffset = AV(dim0).bind(origOffsets[dim]);
1110 auto avSize = AV(dim0).bind(origSizes[dim]);
1111 auto avTileSize = AV(sym).bind(dimAndTileMapping[dim]);
1112 inputIndices.push_back(ab.mul(avOffset, avTileSize));
1113 inputSizes.push_back(ab.mul(avSize, avTileSize));
1115 inputIndices.push_back(origOffsets[dim]);
1116 inputSizes.push_back(origSizes[dim]);
1120 if (packOp.getPaddingValue()) {
1121 OpFoldResult dimSize = srcDimValues[dim];
1122 auto avDimSize = AV(dim0).bind(dimSize);
1123 auto avInputIdx = AV(dim1).bind(inputIndices.back());
1125 ab.min({inputSizes.back(), ab.sub(avDimSize, avInputIdx)});
1129 auto oneAttr =
b.getI64IntegerAttr(1);
1130 SmallVector<OpFoldResult> strides(inputRank, oneAttr);
1132 SmallVector<Value> tiledOperands;
1133 auto sourceSlice = tensor::ExtractSliceOp::create(
1134 b, loc, packOp.getSource(), inputIndices, inputSizes, strides);
1135 tiledOperands.push_back(sourceSlice);
1137 SmallVector<OpFoldResult> outputOffsets, outputSizes;
1142 strides.append(packOp.getDestRank() - inputRank, oneAttr);
1143 auto outSlice = tensor::ExtractSliceOp::create(
1144 b, loc, packOp.getDest(), outputOffsets, outputSizes, strides);
1145 tiledOperands.push_back(outSlice);
1147 if (
auto val = packOp.getPaddingValue())
1148 tiledOperands.push_back(val);
1149 for (
auto tile : packOp.getInnerTiles())
1150 tiledOperands.push_back(
tile);
1152 PackOp tiledPackOp =
1153 PackOp::create(
b, loc,
TypeRange{outSlice.getType()}, tiledOperands,
1154 packOp.getProperties(),
1155 packOp->getDiscardableAttrDictionary().getValue());
1157 return TilingResult{
1159 SmallVector<Value>(tiledPackOp->getResults()),
1160 llvm::to_vector(ArrayRef<Operation *>{sourceSlice, outSlice})};
1165 ArrayRef<OpFoldResult> offsets,
1166 ArrayRef<OpFoldResult> sizes,
1167 SmallVector<OpFoldResult> &resultOffsets,
1168 SmallVector<OpFoldResult> &resultSizes)
const {
1173 auto packOp = cast<PackOp>(op);
1174 int64_t inputRank = packOp.getSourceRank();
1175 int64_t outputRank = packOp.getDestRank();
1176 auto zeroAttr =
b.getI64IntegerAttr(0);
1177 resultOffsets.assign(offsets.begin(), offsets.end());
1178 resultOffsets.append(outputRank - inputRank, zeroAttr);
1182 resultSizes.assign(sizes.begin(), sizes.end());
1183 for (
auto dataTileDim : llvm::seq<unsigned>(inputRank, outputRank))
1184 resultSizes.push_back(outputShape[0][dataTileDim]);
1189 FailureOr<TilingResult>
1190 generateResultTileValue(Operation *op, OpBuilder &
b,
unsigned resultNumber,
1191 ArrayRef<OpFoldResult> offsets,
1192 ArrayRef<OpFoldResult> sizes)
const {
1193 return generateResultTileValue(op,
b, resultNumber, offsets, sizes,
1197 FailureOr<TilingResult> generateResultTileValue(
1198 Operation *op, OpBuilder &
b,
unsigned resultNumber,
1199 ArrayRef<OpFoldResult> offsets, ArrayRef<OpFoldResult> sizes,
1200 ArrayRef<InnerTileAlignment> innerTileAlignments)
const {
1201 auto packOp = cast<PackOp>(op);
1202 int64_t numTiles = packOp.getInnerDimsPos().size();
1207 for (
auto offset : offsets.take_back(numTiles))
1214 ArrayRef<int64_t> innerDimsPos = packOp.getInnerDimsPos();
1215 SmallVector<OpFoldResult> mixedTiles = packOp.getMixedTiles();
1216 ArrayRef<OpFoldResult> innerSizes = sizes.take_back(numTiles);
1217 for (
auto [i, pos] : llvm::enumerate(innerDimsPos)) {
1219 pos < static_cast<int64_t>(innerTileAlignments.size())
1220 ? innerTileAlignments[pos]
1221 : InnerTileAlignment::Unknown;
1222 if (alignment != InnerTileAlignment::Equal &&
1228 op,
b, offsets.drop_back(numTiles), sizes.drop_back(numTiles));
1229 if (
failed(tilingResult))
1231 return tilingResult.value();
1234 LogicalResult generateScalarImplementation(Operation *op, OpBuilder &builder,
1237 auto packOp = cast<PackOp>(op);
1238 assert(packOp.hasPureBufferSemantics() &&
1239 "expected operation to have buffer semantics");
1240 OpBuilder::InsertionGuard g(builder);
1243 SmallVector<Value> ivVec(ivs);
1246 SmallVector<OpFoldResult> outputShape;
1247 Value dest = packOp.getDest();
1248 for (
auto dim : llvm::seq<int64_t>(0, packOp.getDestRank()))
1257 for (
auto dataTileDim : llvm::seq<unsigned>(packOp.getSourceRank(),
1258 packOp.getDestRank() - 1)) {
1260 outputShape[dataTileDim]);
1261 scf::ForOp loop = scf::ForOp::create(builder, loc, zero, ub, one);
1263 ivVec.push_back(loop.getInductionVar());
1270 [&](OpBuilder &bodyBuilder, Location bodyLoc, Value iv,
1272 ivVec.push_back(iv);
1273 generatePackOpScalarImplementationBody(packOp, bodyBuilder, bodyLoc,
1275 scf::YieldOp::create(bodyBuilder, bodyLoc);
1280 LogicalResult getIterationDomainTileFromOperandTiles(
1281 Operation *op, OpBuilder &
b, ArrayRef<unsigned> operandNumbers,
1282 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1283 ArrayRef<SmallVector<OpFoldResult>> allSizes,
1284 SmallVectorImpl<OpFoldResult> &resultOffsets,
1285 SmallVectorImpl<OpFoldResult> &resultSizes)
const {
1286 return getIterationDomainTileFromOperandTiles(
1287 op,
b, operandNumbers, allOffsets, allSizes, resultOffsets, resultSizes,
1294 LogicalResult getIterationDomainTileFromOperandTiles(
1295 Operation *op, OpBuilder &
b, ArrayRef<unsigned> operandNumbers,
1296 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1297 ArrayRef<SmallVector<OpFoldResult>> allSizes,
1298 SmallVectorImpl<OpFoldResult> &resultOffsets,
1299 SmallVectorImpl<OpFoldResult> &resultSizes,
1300 ArrayRef<InnerTileAlignment> innerTileAlignments)
const {
1301 if (operandNumbers.size() != 1 || operandNumbers[0] != 0) {
1303 { llvm::dbgs() <<
"unsupported operands for consumer fusion"; });
1307 ArrayRef<OpFoldResult> offsets(allOffsets[0]);
1308 ArrayRef<OpFoldResult> sizes(allSizes[0]);
1309 auto packOp = cast<PackOp>(op);
1310 Location loc = packOp.getLoc();
1311 SmallVector<OpFoldResult> outerDimOffsets, outerDimSizes;
1313 packOp.getDimAndTileMapping();
1314 SmallVector<int64_t> outerShapeWithoutTranspose(
1315 packOp.getDestType().getShape().take_front(packOp.getSourceRank()));
1316 if (!packOp.getOuterDimsPerm().empty()) {
1318 outerShapeWithoutTranspose,
1321 for (
auto dim : llvm::seq<int64_t>(packOp.getSourceRank())) {
1322 if (dimAndTileMapping.count(dim)) {
1323 FailureOr<int64_t> cstTileSize =
1325 presburger::BoundType::UB, sizes[dim],
1327 ValueBoundsOptions{
true});
1328 std::optional<int64_t> cstInnerSize =
1335 dim < static_cast<int64_t>(innerTileAlignments.size())
1336 ? innerTileAlignments[dim]
1337 : InnerTileAlignment::Unknown;
1350 int64_t srcDimSize = packOp.getSourceType().getDimSize(dim);
1351 int64_t destDimSize = outerShapeWithoutTranspose[dim];
1352 bool isTiled = innerTileAlignment != InnerTileAlignment::Unknown ||
1354 ShapedType::isDynamic(srcDimSize) ||
1355 cstTileSize.value() < srcDimSize;
1357 outerDimOffsets.push_back(offsets[dim]);
1358 if (ShapedType::isStatic(destDimSize)) {
1359 outerDimSizes.push_back(
b.getIndexAttr(destDimSize));
1361 outerDimSizes.push_back(
1362 b.createOrFold<tensor::DimOp>(loc, packOp.getDest(), dim));
1389 bool assumeInnerTileSizesMatchTiles =
1390 innerTileAlignment == InnerTileAlignment::Equal;
1391 bool staticallyDecidable =
1392 !
failed(cstTileSize) && cstInnerSize.has_value();
1393 if (innerTileAlignment == InnerTileAlignment::Unknown) {
1394 if (!staticallyDecidable || *cstTileSize % *cstInnerSize != 0)
1396 }
else if (staticallyDecidable) {
1397 assert(*cstTileSize % *cstInnerSize == 0 &&
1398 "InnerTileAlignment hint contradicts statically known tile "
1400 assert((innerTileAlignment != InnerTileAlignment::Equal ||
1401 *cstTileSize == *cstInnerSize) &&
1402 "InnerTileAlignment::Equal contradicts statically known tile "
1406 using AV = affine::AffineValueExpr;
1407 affine::AffineBuilder ab(
b, loc);
1408 AffineExpr dim0, sym;
1411 auto avOffset = AV(dim0).bind(offsets[dim]);
1412 auto avSize = AV(dim0).bind(sizes[dim]);
1413 auto avTileSize = AV(sym).bind(dimAndTileMapping[dim]);
1414 outerDimOffsets.push_back(ab.floor(avOffset, avTileSize));
1417 outerDimSizes.push_back(assumeInnerTileSizesMatchTiles
1419 : ab.ceil(avSize, avTileSize));
1421 outerDimOffsets.push_back(offsets[dim]);
1422 outerDimSizes.push_back(sizes[dim]);
1425 applyPermToRange(outerDimOffsets, outerDimSizes, packOp.getOuterDimsPerm());
1426 resultOffsets = outerDimOffsets;
1427 resultSizes = outerDimSizes;
1431 FailureOr<TilingResult> getTiledImplementationFromOperandTiles(
1432 Operation *op, OpBuilder &
b, ArrayRef<unsigned> operandNumbers,
1433 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1434 ArrayRef<SmallVector<OpFoldResult>> allSizes)
const {
1435 return getTiledImplementationFromOperandTiles(op,
b, operandNumbers,
1436 allOffsets, allSizes,
1441 FailureOr<TilingResult> getTiledImplementationFromOperandTiles(
1442 Operation *op, OpBuilder &
b, ArrayRef<unsigned> operandNumbers,
1443 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1444 ArrayRef<SmallVector<OpFoldResult>> allSizes,
1445 ArrayRef<InnerTileAlignment> innerTileAlignments)
const {
1446 if (operandNumbers.size() != 1 || operandNumbers[0] != 0) {
1447 LLVM_DEBUG({ llvm::dbgs() <<
"unhandled operands for consumer fusion"; });
1451 ArrayRef<OpFoldResult> offsets(allOffsets[0]);
1452 ArrayRef<OpFoldResult> sizes(allSizes[0]);
1454 auto packOp = cast<PackOp>(op);
1456 if (!packOp.hasPureTensorSemantics())
1459 Location loc = packOp.getLoc();
1461 int64_t inputRank = packOp.getSourceRank();
1462 auto oneAttr =
b.getI64IntegerAttr(1);
1463 SmallVector<OpFoldResult> strides(inputRank, oneAttr);
1465 SmallVector<Value> tiledOperands;
1466 auto sourceSlice = tensor::ExtractSliceOp::create(
1467 b, loc, packOp.getSource(), offsets, sizes, strides);
1468 tiledOperands.push_back(sourceSlice);
1470 SmallVector<OpFoldResult> outerDimOffsets, outerDimSizes;
1471 if (
failed(getIterationDomainTileFromOperandTiles(
1472 op,
b, operandNumbers, allOffsets, allSizes, outerDimOffsets,
1473 outerDimSizes, innerTileAlignments)))
1476 SmallVector<OpFoldResult> outputOffsets, outputSizes;
1478 outputOffsets, outputSizes)))
1481 strides.append(packOp.getDestRank() - inputRank, oneAttr);
1482 auto outSlice = tensor::ExtractSliceOp::create(
1483 b, loc, packOp.getDest(), outputOffsets, outputSizes, strides);
1484 tiledOperands.push_back(outSlice);
1486 if (
auto val = packOp.getPaddingValue())
1487 tiledOperands.push_back(val);
1488 for (
auto tile : packOp.getInnerTiles())
1489 tiledOperands.push_back(
tile);
1491 PackOp tiledPackOp =
1492 PackOp::create(
b, loc,
TypeRange{outSlice.getType()}, tiledOperands,
1493 packOp.getProperties(),
1494 packOp->getDiscardableAttrDictionary().getValue());
1496 return TilingResult{
1498 SmallVector<Value>(tiledPackOp->getResults()),
1499 llvm::to_vector(ArrayRef<Operation *>{sourceSlice, outSlice})};
1503struct UnpackTileDimInfo {
1504 bool isAlignedToInnerTileSize;
1505 OpFoldResult sourceOffset;
1506 OpFoldResult sourceSize;
1507 OpFoldResult resultOffset;
1508 OpFoldResult destExpandedSize;
1514static UnpackTileDimInfo
1518 UnpackTileDimInfo info;
1522 unpackOp.getDimAndTileMapping();
1524 if (!dimAndTileMapping.count(tileDim)) {
1525 info.isAlignedToInnerTileSize =
true;
1526 info.sourceOffset = tileOffset;
1527 info.sourceSize = tileSize;
1528 info.resultOffset = zeroAttr;
1529 info.destExpandedSize = tileSize;
1540 OpFoldResult innerTileSize = dimAndTileMapping[tileDim];
1542 info.isAlignedToInnerTileSize =
false;
1555 bool assumeInnerTileSizesMatchTiles =
1557 bool staticallyDecidable = !
failed(cstSize) && cstInnerSize.has_value();
1559 info.isAlignedToInnerTileSize =
true;
1560 if (staticallyDecidable) {
1561 assert(*cstSize % *cstInnerSize == 0 &&
1562 "InnerTileAlignment hint contradicts statically known tile sizes");
1564 *cstSize == *cstInnerSize) &&
1565 "InnerTileAlignment::Equal contradicts statically known tile "
1569 if (info.isAlignedToInnerTileSize || (!
failed(cstSize) && cstInnerSize)) {
1570 if (!info.isAlignedToInnerTileSize && *cstSize % *cstInnerSize == 0)
1571 info.isAlignedToInnerTileSize =
true;
1575 if (assumeInnerTileSizesMatchTiles ||
1576 (cstInnerSize && !
failed(cstSize) && *cstInnerSize == *cstSize)) {
1577 auto lhs = AV(dim0).bind(tileOffset);
1578 auto rhs = AV(dim1).bind(innerTileSize);
1579 info.sourceOffset = ab.floor(
lhs,
rhs);
1580 info.sourceSize = oneAttr;
1581 info.resultOffset = zeroAttr;
1582 info.destExpandedSize = tileSize;
1587 if (info.isAlignedToInnerTileSize) {
1589 ab.floor(AV(dim0).bind(tileOffset), AV(dim1).bind(innerTileSize));
1590 info.resultOffset = zeroAttr;
1591 info.destExpandedSize = tileSize;
1600 ab.ceil(AV(dim0).bind(tileSize), AV(dim1).bind(innerTileSize));
1604 affine::DivModValue firstCoord = affine::getDivMod(
1608 ab.add(AV(dim0).bind(tileOffset), AV(dim1).bind(tileSize));
1609 affine::DivModValue lastCoord = affine::getDivMod(
1613 ab.sub(AV(dim0).bind(tileExclusiveBound), AV(dim1).bind(oneAttr))),
1616 OpFoldResult lengthMinusOne = ab.sub(AV(dim0).bind(lastCoord.quotient),
1617 AV(dim1).bind(firstCoord.quotient));
1619 ab.add(AV(dim0).bind(lengthMinusOne), AV(dim1).bind(oneAttr));
1620 info.sourceOffset = firstCoord.quotient;
1621 info.resultOffset = firstCoord.remainder;
1624 info.destExpandedSize =
b.createOrFold<arith::MulIOp>(
1630struct UnPackOpTiling
1631 :
public TilingInterface::ExternalModel<UnPackOpTiling, linalg::UnPackOp> {
1632 using Base = TilingInterface::ExternalModel<UnPackOpTiling, linalg::UnPackOp>;
1633 using Base::getIterationDomainTileFromOperandTiles;
1635 SmallVector<utils::IteratorType> getLoopIteratorTypes(Operation *op)
const {
1636 auto unpackOp = cast<UnPackOp>(op);
1637 SmallVector<utils::IteratorType> iteratorTypes(
1638 unpackOp.getDestRank(), utils::IteratorType::parallel);
1639 return iteratorTypes;
1642 SmallVector<Range> getIterationDomain(Operation *op, OpBuilder &
b)
const {
1643 return getPackUnPackIterationDomain<UnPackOp>(cast<UnPackOp>(op),
b);
1660 FailureOr<TilingResult>
1662 ArrayRef<OpFoldResult> offsets,
1663 ArrayRef<OpFoldResult> sizes)
const {
1669 Operation *op, OpBuilder &
b, ArrayRef<OpFoldResult> offsets,
1670 ArrayRef<OpFoldResult> sizes,
1671 ArrayRef<InnerTileAlignment> innerTileAlignments)
const {
1672 auto unpackOp = cast<UnPackOp>(op);
1674 if (!unpackOp.hasPureTensorSemantics())
1677 int64_t srcRank = unpackOp.getSourceRank();
1678 int64_t destRank = unpackOp.getDestRank();
1679 int64_t numInnerTiles = srcRank - destRank;
1680 Location loc = unpackOp.getLoc();
1685 bool isPerfectTilingCase =
true;
1686 Attribute oneAttr =
b.getIndexAttr(1);
1687 SmallVector<OpFoldResult> sliceSrcStrides(destRank, oneAttr);
1688 SmallVector<OpFoldResult> sliceSrcIndices, sliceSrcSizes;
1689 SmallVector<OpFoldResult> destExpandedSizes, resultOffsetsFromDest;
1690 for (
auto dim : llvm::seq<int64_t>(0, destRank)) {
1691 UnpackTileDimInfo info = getUnpackTileDimInfo(
1692 b, unpackOp, dim, offsets[dim], sizes[dim],
1693 dim <
static_cast<int64_t
>(innerTileAlignments.size())
1694 ? innerTileAlignments[dim]
1695 : InnerTileAlignment::Unknown);
1696 if (!info.isAlignedToInnerTileSize)
1697 isPerfectTilingCase =
false;
1698 sliceSrcIndices.push_back(info.sourceOffset);
1699 sliceSrcSizes.push_back(info.sourceSize);
1700 destExpandedSizes.push_back(info.destExpandedSize);
1701 resultOffsetsFromDest.push_back(info.resultOffset);
1706 applyPermToRange(sliceSrcIndices, sliceSrcSizes,
1707 unpackOp.getOuterDimsPerm());
1708 Attribute zeroAttr =
b.getIndexAttr(0);
1709 sliceSrcIndices.append(numInnerTiles, zeroAttr);
1710 sliceSrcSizes.append(unpackOp.getMixedTiles());
1711 sliceSrcStrides.append(numInnerTiles, oneAttr);
1712 SmallVector<Operation *> generatedSlices;
1713 tensor::ExtractSliceOp sliceSource = tensor::ExtractSliceOp::create(
1714 b, loc, unpackOp.getSource(), sliceSrcIndices, sliceSrcSizes,
1716 generatedSlices.push_back(sliceSource);
1718 SmallVector<OpFoldResult> destStrides(destRank, oneAttr);
1720 if (isPerfectTilingCase) {
1721 auto destSliceOp = tensor::ExtractSliceOp::create(
1722 b, loc, unpackOp.getDest(), offsets, sizes, destStrides);
1723 sliceDest = destSliceOp;
1724 generatedSlices.push_back(destSliceOp);
1726 sliceDest = tensor::EmptyOp::create(
1727 b, loc, destExpandedSizes, unpackOp.getDestType().getElementType());
1730 SmallVector<Value> tiledOperands = {sliceSource.getResult(), sliceDest};
1731 for (
auto tile : unpackOp.getInnerTiles())
1732 tiledOperands.push_back(
tile);
1734 UnPackOp tiledUnpackOp =
1736 unpackOp.getProperties(),
1737 unpackOp->getDiscardableAttrDictionary().getValue());
1739 if (isPerfectTilingCase)
1740 return TilingResult{{tiledUnpackOp},
1741 SmallVector<Value>(tiledUnpackOp->getResults()),
1744 auto extractSlice = tensor::ExtractSliceOp::create(
1745 b, loc, tiledUnpackOp->getResult(0), resultOffsetsFromDest, sizes,
1747 return TilingResult{
1748 {tiledUnpackOp}, {extractSlice.getResult()}, generatedSlices};
1753 ArrayRef<OpFoldResult> offsets,
1754 ArrayRef<OpFoldResult> sizes,
1755 SmallVector<OpFoldResult> &resultOffsets,
1756 SmallVector<OpFoldResult> &resultSizes)
const {
1757 resultOffsets = llvm::to_vector(offsets);
1758 resultSizes = llvm::to_vector(sizes);
1762 FailureOr<TilingResult>
1763 generateResultTileValue(Operation *op, OpBuilder &
b,
unsigned resultNumber,
1764 ArrayRef<OpFoldResult> offsets,
1765 ArrayRef<OpFoldResult> sizes)
const {
1766 return generateResultTileValue(op,
b, resultNumber, offsets, sizes,
1770 FailureOr<TilingResult> generateResultTileValue(
1771 Operation *op, OpBuilder &
b,
unsigned resultNumber,
1772 ArrayRef<OpFoldResult> offsets, ArrayRef<OpFoldResult> sizes,
1773 ArrayRef<InnerTileAlignment> innerTileAlignments)
const {
1774 FailureOr<TilingResult> tilingResult =
1776 if (
failed(tilingResult))
1778 return tilingResult.value();
1781 LogicalResult generateScalarImplementation(Operation *op, OpBuilder &builder,
1784 auto unpackOp = cast<UnPackOp>(op);
1785 assert(unpackOp.hasPureBufferSemantics() &&
1786 "expected operation to have buffer semantics");
1787 assert(ivs.size() == unpackOp.getDestRank() &&
1788 "number of ivs must match the rank of the output tensor");
1789 OpBuilder::InsertionGuard g(builder);
1792 unpackOp.getDimAndTileMapping();
1794 SmallVector<Value> inputIvs;
1796 SmallVector<Value> inputIvsPointLoops;
1797 inputIvs.reserve(unpackOp.getDestRank());
1798 inputIvsPointLoops.reserve(dimAndTileMapping.size());
1799 for (
auto dim : llvm::seq<int64_t>(0, unpackOp.getDestRank())) {
1800 if (dimAndTileMapping.count(dim)) {
1801 affine::DivModValue divMod =
1802 affine::getDivMod(builder, loc, ivs[dim],
1804 builder, loc, dimAndTileMapping[dim]));
1805 inputIvsPointLoops.push_back(divMod.remainder);
1806 inputIvs.push_back(divMod.quotient);
1808 inputIvs.push_back(ivs[dim]);
1814 assert(inputIvsPointLoops.size() + inputIvs.size() ==
1815 unpackOp.getSourceRank() &&
1816 "expect same number of induction variables equals to input rank");
1818 ArrayRef<int64_t> innerDims = unpackOp.getInnerDimsPos();
1819 SmallVector<int64_t> interchangeVector =
1820 computeInterchangeFromDimPos(innerDims, unpackOp.getDestRank());
1821 SmallVector<Value> interchangedInputIvsPointLoops = inputIvsPointLoops;
1822 interchangedInputIvsPointLoops = interchange<Value>(
1823 interchangedInputIvsPointLoops, interchangeVector, 0);
1826 ArrayRef<int64_t> outerDims = unpackOp.getOuterDimsPerm();
1827 if (!outerDims.empty())
1828 inputIvs = interchange<Value>(inputIvs, outerDims, 0);
1830 llvm::append_range(inputIvs, interchangedInputIvsPointLoops);
1832 memref::LoadOp::create(builder, loc, unpackOp.getSource(), inputIvs);
1833 memref::StoreOp::create(builder, loc, scalar, unpackOp.getDest(), ivs);
1839 LogicalResult getIterationDomainTileFromOperandTiles(
1840 Operation *op, OpBuilder &
b, ArrayRef<unsigned> operandNumbers,
1841 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1842 ArrayRef<SmallVector<OpFoldResult>> allSizes,
1843 SmallVectorImpl<OpFoldResult> &resultOffsets,
1844 SmallVectorImpl<OpFoldResult> &resultSizes)
const {
1845 if (operandNumbers.size() != 1) {
1846 LLVM_DEBUG({ llvm::dbgs() <<
"unable to handle multiple operands"; });
1849 auto unPackOp = cast<UnPackOp>(op);
1850 unsigned operandNumber = operandNumbers[0];
1851 ArrayRef<OpFoldResult> offsets(allOffsets[0]);
1852 ArrayRef<OpFoldResult> sizes(allSizes[0]);
1855 if (operandNumber == unPackOp.getDestMutable().getOperandNumber()) {
1856 resultOffsets = llvm::to_vector(offsets);
1857 resultSizes = llvm::to_vector(sizes);
1860 Location loc = unPackOp.getLoc();
1862 int64_t numTiles = unPackOp.getInnerDimsPos().size();
1863 auto destOffsets = offsets.drop_back(numTiles);
1864 auto destSizes = sizes.drop_back(numTiles);
1867 int64_t outputRank = unPackOp.getDestRank();
1871 SmallVector<OpFoldResult> outputMixedSizes = reifiedReturnShapes.front();
1872 SmallVector<OpFoldResult> origOffsets(destOffsets);
1873 SmallVector<OpFoldResult> origSizes(destSizes);
1874 applyPermToRange(origOffsets, origSizes,
1878 unPackOp.getDimAndTileMapping();
1880 for (
auto dim : llvm::seq<int64_t>(0, outputRank)) {
1881 using AV = affine::AffineValueExpr;
1882 affine::AffineBuilder ab(
b, loc);
1883 AffineExpr dim0, dim1, sym0;
1886 if (dimAndTileMapping.count(dim)) {
1890 auto avOffset = AV(dim0).bind(origOffsets[dim]);
1891 auto avSize = AV(dim0).bind(origSizes[dim]);
1892 auto avTileSize = AV(sym0).bind(dimAndTileMapping[dim]);
1893 auto avResultSize = AV(dim0).bind(outputMixedSizes[dim]);
1894 resultOffsets.push_back(ab.mul(avOffset, avTileSize));
1895 auto avResultOffset = AV(dim1).bind(resultOffsets.back());
1896 resultSizes.push_back(ab.min({ab.mul(avSize, avTileSize),
1897 ab.sub(avResultSize, avResultOffset)}));
1899 resultOffsets.push_back(origOffsets[dim]);
1900 resultSizes.push_back(origSizes[dim]);
1906 FailureOr<TilingResult> getTiledImplementationFromOperandTiles(
1907 Operation *op, OpBuilder &
b, ArrayRef<unsigned> operandNumbers,
1908 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1909 ArrayRef<SmallVector<OpFoldResult>> allSizes)
const {
1910 return getTiledImplementationFromOperandTiles(op,
b, operandNumbers,
1911 allOffsets, allSizes,
1916 FailureOr<TilingResult> getTiledImplementationFromOperandTiles(
1917 Operation *op, OpBuilder &
b, ArrayRef<unsigned> operandNumbers,
1918 ArrayRef<SmallVector<OpFoldResult>> allOffsets,
1919 ArrayRef<SmallVector<OpFoldResult>> allSizes,
1920 ArrayRef<InnerTileAlignment> innerTileAlignments)
const {
1921 if (operandNumbers.size() != 1 || operandNumbers[0] != 0) {
1922 LLVM_DEBUG({ llvm::dbgs() <<
"unhandled operands for consumer fusion"; });
1925 auto unPackOp = cast<UnPackOp>(op);
1927 if (!unPackOp.hasPureTensorSemantics())
1930 ArrayRef<OpFoldResult> offsets(allOffsets[0]);
1931 ArrayRef<OpFoldResult> sizes(allSizes[0]);
1937 int64_t numTiles = unPackOp.getInnerDimsPos().size();
1938 ArrayRef<int64_t> innerDimsPos = unPackOp.getInnerDimsPos();
1939 SmallVector<OpFoldResult> mixedTiles = unPackOp.getMixedTiles();
1940 ArrayRef<OpFoldResult> innerSizes = sizes.take_back(numTiles);
1941 for (int64_t i = 0; i < numTiles; ++i) {
1944 int64_t destDim = innerDimsPos[i];
1946 destDim < static_cast<int64_t>(innerTileAlignments.size()) &&
1947 innerTileAlignments[destDim] == InnerTileAlignment::Equal;
1957 "InnerTileAlignment::Equal contradicts statically known tile "
1966 Location loc = unPackOp.getLoc();
1970 SmallVector<OpFoldResult> outputOffsets, outputSizes;
1971 if (
failed(getIterationDomainTileFromOperandTiles(
1972 op,
b, operandNumbers, allOffsets, allSizes, outputOffsets,
1976 auto oneAttr =
b.getI64IntegerAttr(1);
1977 int64_t outputRank = unPackOp.getDestRank();
1978 SmallVector<OpFoldResult> strides(outputRank, oneAttr);
1980 SmallVector<Value> tiledOperands;
1982 auto extractDestSlice = tensor::ExtractSliceOp::create(
1983 b, loc, unPackOp.getDest(), outputOffsets, outputSizes, strides);
1984 tiledOperands.push_back(extractDestSlice);
1986 strides.append(unPackOp.getSourceRank() - outputRank, oneAttr);
1988 auto extractSourceSlice = tensor::ExtractSliceOp::create(
1989 b, loc, unPackOp.getSource(), offsets, sizes, strides);
1990 tiledOperands.insert(tiledOperands.begin(), extractSourceSlice);
1991 for (
auto tile : unPackOp.getInnerTiles())
1992 tiledOperands.push_back(
tile);
1995 UnPackOp tiledUnPackOp =
1996 UnPackOp::create(
b, loc,
TypeRange{extractDestSlice.getType()},
1997 tiledOperands, unPackOp.getProperties(),
1998 unPackOp->getDiscardableAttrDictionary().getValue());
2000 return TilingResult{{tiledUnPackOp},
2001 SmallVector<Value>(tiledUnPackOp->getResults()),
2002 llvm::to_vector(ArrayRef<Operation *>{
2003 extractSourceSlice, extractDestSlice})};
2009template <
typename OpType>
2011 OpType::template attachInterface<LinalgOpTilingInterface<OpType>>(*ctx);
2012 OpType::template attachInterface<LinalgOpPartialReductionInterface<OpType>>(
2017template <
typename... OpTypes>
2028 linalg::PackOp::attachInterface<PackOpTiling>(*ctx);
2029 linalg::UnPackOp::attachInterface<UnPackOpTiling>(*ctx);
2031#include "mlir/Dialect/Linalg/IR/LinalgStructuredOps.cpp.inc"
2039 linalg::PackOp::attachInterface<PackOpTiling>(*ctx);
2040 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.