29#include "llvm/ADT/ScopeExit.h"
30#include "llvm/ADT/TypeSwitch.h"
31#include "llvm/Support/Debug.h"
34#define DEBUG_TYPE "tile-using-interface"
38scf::SCFTilingOptions &
40 assert(!tileSizeComputationFunction &&
"tile sizes already set");
41 auto tileSizes = llvm::to_vector(ts);
48scf::SCFTilingOptions &
50 assert(!numThreadsComputationFunction &&
"num tiles already set");
51 auto numThreads = llvm::to_vector(nt);
62 size_t iterationDomainSize) {
64 if (filledVector.size() < iterationDomainSize) {
65 auto range = llvm::seq<int64_t>(filledVector.size(), iterationDomainSize);
66 filledVector.append(range.begin(), range.end());
68 if (filledVector.size() > iterationDomainSize)
69 filledVector.resize(iterationDomainSize);
79 const scf::SCFTilingOptions &
options) {
81 if (
options.numThreadsComputationFunction &&
82 options.loopType != scf::SCFTilingOptions::LoopType::ForallOp) {
84 loc,
"number of threads can only by specified when loop type is "
85 "set to use `scf.forall`");
89 if (!
options.interchangeVector.empty()) {
92 loc,
"invalid interchange vector, not a permutation of the entire "
104 const scf::SCFTilingOptions &
options) {
107 size_t numLoops = iterationDomain.size();
110 if (
options.numThreadsComputationFunction) {
111 numThreads =
options.numThreadsComputationFunction(rewriter, op);
112 numThreads.resize(numLoops, zero);
115 if (
options.tileSizeComputationFunction) {
116 tileSizes =
options.tileSizeComputationFunction(rewriter, op);
117 tileSizes.resize(numLoops, zero);
118 return {tileSizes, numThreads};
130 tileSizes.resize(numLoops, zero);
131 for (
auto [
index, range, nt] :
132 llvm::enumerate(iterationDomain, numThreads)) {
137 rewriter, op.getLoc(), tileSizeExpr, {range.offset, range.size, nt});
139 tileSizes.resize(numLoops, zero);
140 return {tileSizes, numThreads};
147 assert(
options.tileSizeComputationFunction &&
148 "expected tile sizes to be specified");
149 tileSizes =
options.tileSizeComputationFunction(rewriter, op);
150 tileSizes.resize(numLoops, zero);
152 return {tileSizes, numThreads};
157 scf::SCFTilingOptions::LoopType loopType,
161 auto iterators = op.getLoopIteratorTypes();
162 assert(iterators.size() == givenTileSizes.size() &&
163 "expected as many tile size values as number of loops");
164 assert((numThreads.empty() || (numThreads.size() == iterators.size())) &&
165 "when specified, expected number of threads to use for each loop");
167 bool isParallelTiling =
false;
168 for (
auto [
index, iterator, givenTileSize] :
169 llvm::enumerate(iterators, givenTileSizes)) {
171 isParallelTiling |= iterator == utils::IteratorType::parallel;
174 if (loopType == scf::SCFTilingOptions::LoopType::ForallOp &&
178 if (!numThreads.empty()) {
179 if (std::optional<int64_t> constNumThreads =
181 if (constNumThreads.value() > 1 &&
182 iterator != utils::IteratorType::parallel) {
183 op.emitWarning() <<
"tiling is not thread safe at axis #" <<
index;
189 if (std::optional<int64_t> constTileSize =
191 if (constTileSize.value() > 0 &&
192 iterator != utils::IteratorType::parallel) {
193 op.emitWarning() <<
"tiling is not thread safe at axis #" <<
index;
200 if (isParallelTiling) {
201 return op->emitOpError(
"tiling parallel dimensions is not supported with "
202 "partial reduction tiling strategies");
212 const scf::SCFTilingOptions &
options) {
214 for (
auto dim :
options.reductionDims) {
217 reductionDims.insert(dim);
219 return reductionDims;
233 return ((sizeAsInt.value() - offsetAsInt.value()) % strideAsInt.value() == 0);
242 if (ts && ts.value() == 1)
243 return givenTileSize;
247 return givenTileSize;
269 if (!tileSizeConst || !numThreadsConst || !iterSizeConst)
271 return *tileSizeConst * (*numThreadsConst - 1) < *iterSizeConst;
282 int materializedLoopNum = 0;
283 for (
auto [givenTileSize, loopRange] :
284 llvm::zip_equal(givenTileSizes, iterationDomain)) {
289 offsets.push_back(loopRange.offset);
290 sizes.push_back(loopRange.size);
294 Value iv = ivs[materializedLoopNum++];
296 offsets.push_back(offset);
299 sizes.push_back(size);
301 return {offsets, sizes};
310 for (
auto [loopRange, givenTileSize] :
311 llvm::zip_equal(loopRanges, givenTileSizes)) {
315 lbs.push_back(loopRange.offset);
316 ubs.push_back(loopRange.size);
317 steps.push_back(givenTileSize);
319 return {lbs, ubs, steps};
373 if (newDestArgs.empty())
375 if (
auto destinationStyleOp = dyn_cast<DestinationStyleOpInterface>(clonedOp))
376 destinationStyleOp.getDpsInitsMutable().assign(newDestArgs);
393 assert(!loopRanges.empty() &&
"unexpected empty loop ranges");
394 assert(loopRanges.size() == givenTileSizes.size() &&
395 "expected as many tile sizes as loop ranges");
399 std::tie(lbs, ubs, steps) =
410 ValueRange innerDestinationTensors(outerDestinationTensors);
411 for (
auto [lb,
ub, step] : llvm::zip_equal(lbVals, ubVals, stepVals)) {
413 scf::ForOp::create(rewriter, loc, lb,
ub, step, innerDestinationTensors,
416 loops.push_back(loop);
417 ivs.push_back(loop.getInductionVar());
419 innerDestinationTensors = loop.getRegionIterArgs();
426 std::tie(offsets, sizes) =
431 if (failed(tiledBodyFn(rewriter, loc, ivs, offsets, sizes,
432 innerDestinationTensors, tiledResults, resultOffsets,
435 loc,
"failed to generate inner tile loop body");
440 assert(tiledResults.size() == innerDestinationTensors.size() &&
441 "Number of results of body should be equal to number of iter args");
445 for (
auto [tiledValue, destinationTensor, resultOffset, resultSize] :
446 llvm::zip_equal(tiledResults, innerDestinationTensors, resultOffsets,
450 auto insertSlice = tensor::InsertSliceOp::create(
451 rewriter, loc, tiledValue, destinationTensor, resultOffset, resultSize,
453 yieldedValues.push_back(insertSlice);
455 scf::YieldOp::create(rewriter, loc, yieldedValues);
458 for (
auto [outerLoop, innerLoop] :
462 cast<scf::ForOp>(outerLoop.getOperation()).getBody());
463 scf::YieldOp::create(rewriter, outerLoop.getLoc(), innerLoop->getResults());
480 if (numThreads.empty()) {
486 int materializedLoopNum = 0;
492 offsetExpr = d0 + d1 * s0;
493 residualTileSizeExpr = s1 - (d0 + d1 * s0);
495 for (
auto [
index, nt, givenTileSize, loopRange] :
496 llvm::enumerate(numThreads, givenTileSizes, iterationDomain)) {
501 offsets.push_back(loopRange.offset);
502 sizes.push_back(loopRange.size);
506 Value iv = ivs[materializedLoopNum++];
508 rewriter, loc, offsetExpr,
511 rewriter, loc, residualTileSizeExpr,
512 {loopRange.offset, nt, givenTileSize, loopRange.size});
518 {offset, loopRange.size});
522 {sizeMinusOffsetPerThread, givenTileSize});
538 rewriter, loc, maxMap, {rewriter.
getIndexAttr(0), size});
541 offsets.push_back(offset);
542 sizes.push_back(size);
544 return {offsets, sizes};
557static FailureOr<SmallVector<LoopLikeOpInterface>>
565 assert(!loopRanges.empty() &&
"unexpected empty loop ranges");
566 assert(loopRanges.size() == givenTileSizes.size() &&
567 "expected as many tile sizes as loop ranges");
570 std::optional<ArrayAttr> mappingAttr;
571 if (!mappingVector.empty())
574 scf::ForallOp forallOp;
575 bool useNumThreads = !numThreads.empty();
581 for (
auto nt : numThreads) {
584 nonZeroNumThreads.push_back(nt);
586 forallOp = scf::ForallOp::create(rewriter, loc, nonZeroNumThreads,
587 outerDestinationTensors, mappingAttr);
590 std::tie(lbs, ubs, steps) =
592 forallOp = scf::ForallOp::create(rewriter, loc, lbs, ubs, steps,
593 outerDestinationTensors, mappingAttr);
595 loops.push_back(forallOp);
598 ValueRange innerDestinationTensors = forallOp.getRegionOutArgs();
604 rewriter, loc, ivs, loopRanges, givenTileSizes, numThreads);
608 if (failed(tiledBodyFn(rewriter, loc, ivs, offsets, sizes,
609 innerDestinationTensors, tiledResults, resultOffsets,
614 for (
auto [tiledValue, destinationTensor, resultOffset, resultSize] :
615 llvm::zip_equal(tiledResults, innerDestinationTensors, resultOffsets,
620 tensor::ParallelInsertSliceOp::create(rewriter, loc, tiledValue,
621 destinationTensor, resultOffset,
622 resultSize, resultStride);
637static FailureOr<SmallVector<LoopLikeOpInterface>>
641 const scf::SCFTilingOptions::GenerateLoopHeaderFn &generateLoopHeaderFn,
642 const scf::SCFTilingOptions::GenerateLoopTerminatorFn
643 &generateLoopTerminatorFn,
645 assert(!loopRanges.empty() &&
"unexpected empty loop ranges");
646 assert(loopRanges.size() == givenTileSizes.size() &&
647 "expected as many tile sizes as loop ranges");
648 assert(generateLoopHeaderFn && generateLoopTerminatorFn &&
649 "expected loop header/terminator generation function");
652 FailureOr<scf::SCFTilingOptions::CustomLoopHeaderInfo> loopHeaderInfo =
653 generateLoopHeaderFn(rewriter, loc, loopRanges, givenTileSizes,
654 outerDestinationTensors);
655 if (failed(loopHeaderInfo)) {
662 if (failed(tiledBodyFn(rewriter, loc, ivs, loopHeaderInfo->tileOffset,
663 loopHeaderInfo->tileSizes,
664 loopHeaderInfo->destinationTensors, tiledResults,
665 resultOffsets, resultSizes))) {
669 if (failed(generateLoopTerminatorFn(rewriter, loc, loopHeaderInfo->loops,
670 tiledResults, resultOffsets, resultSizes,
671 loopHeaderInfo->destinationTensors))) {
675 return loopHeaderInfo->loops;
699 llvm::map_to_vector(loopRanges, [](
Range r) {
return r.
offset; });
701 llvm::map_to_vector(loopRanges, [](
Range r) {
return r.
size; });
702 if (failed(tiledBodyFn(rewriter, loc,
ValueRange{}, tileOffsets, tileSizes,
703 destinationTensors, tiledResults, resultOffsets,
709 if (
options.loopType == scf::SCFTilingOptions::LoopType::ForOp) {
711 destinationTensors, tiledBodyFn);
713 if (
options.loopType == scf::SCFTilingOptions::LoopType::ForallOp) {
715 rewriter, loc, loopRanges, givenTileSizes, numThreads,
716 options.mappingVector, destinationTensors, tiledBodyFn);
718 if (
options.loopType == scf::SCFTilingOptions::LoopType::CustomOp) {
720 rewriter, loc, loopRanges, givenTileSizes, destinationTensors,
740 auto redOp = dyn_cast<PartialReductionOpInterface>(op.getOperation());
742 return op->emitOpError(
743 "PartialReductionOuterReduction tiling strategy is only supported for "
744 "operations implementing PartialReductionOpInterface");
749 AffineExpr sizeExpr = ((s0 - s1).ceilDiv(s2));
751 for (
auto [
index, domain, tileSize] :
752 llvm::enumerate(iterationDomain, givenTileSizes)) {
753 if (!numThreads.empty()) {
757 rewriter, op.getLoc(), sizeExpr,
758 {domain.size, domain.offset, domain.stride});
768 rewriter, op.getLoc(), sizeExpr,
769 {domain.size, domain.offset, domain.stride});
773 if (reductionStrategy ==
775 sizes[
index] = tileSize;
779 assert(reductionStrategy ==
782 rewriter, op.getLoc(), sizeExpr,
783 {domain.size, domain.offset, domain.stride});
785 rewriter, op.getLoc(), divExpr, {normalizedRange, tileSize});
787 return redOp.generateInitialTensorForPartialReduction(rewriter, loc, sizes,
801 splitReductionIvs.resize(reductionDims.size(), rewriter.
getIndexAttr(0));
806 if (reductionStrategy ==
808 for (
auto [
index, reductionDim] : llvm::enumerate(reductionDims)) {
809 if (!numThreads.empty()) {
810 splitReductionIvs[
index] = ivs[ivIndex++];
814 rewriter, loc, divExpr,
818 return splitReductionIvs;
821static FailureOr<TilingResult>
831 return op.getTiledImplementation(rewriter, offsets, sizes,
832 innerTileAlignments);
835 auto redOp = dyn_cast<PartialReductionOpInterface>(op.getOperation());
838 op,
"PartialReductionOuterReduction tiling strategy is only "
839 "supported for operations "
840 "implementing PartialReductionOpInterface");
845 numThreads, givenTileSizes, reductionDims);
846 return redOp.tileToPartialReduction(rewriter, op.getLoc(), reductionStrategy,
847 regionIterArg, offsets, sizes,
848 reductionDims, splitReductionIvs);
862 return op.getResultTilePosition(rewriter,
index, offsets, sizes,
863 resultOffset, resultSize);
865 auto redOp = dyn_cast<PartialReductionOpInterface>(op.getOperation());
868 op,
"PartialReductionOuterReduction tiling strategy is only supported"
869 "for operations implementing PartialReductionOpInterface");
873 numThreads, givenTileSizes, reductionDims);
874 return redOp.getPartialResultTilePosition(
875 rewriter,
index, reductionStrategy, offsets, sizes, reductionDims,
876 splitReductionIvs, resultOffset, resultSize);
879static FailureOr<MergeResult>
885 "expected merge to be called for only partial reduction cases");
887 auto redOp = dyn_cast<PartialReductionOpInterface>(op.getOperation());
890 op,
"PartialReductionOuterReduction tiling strategy is only "
891 "supported for operations "
892 "implementing PartialReductionOpInterface");
894 return redOp.mergeReductions(rewriter, op.getLoc(), partialResults,
905template <
typename LoopType>
906static FailureOr<LoopLikeOpInterface>
922 auto inits = llvm::to_vector(loopOp.getInitArgs());
923 inits.append(newInitOperands.begin(), newInitOperands.end());
924 auto newLoop = scf::ForOp::create(
925 rewriter, loc, loopOp.getLowerBound(), loopOp.getUpperBound(),
927 loopOp.getUnsignedCmp());
930 Block *loopBody = loopOp.getBody();
931 Block *newLoopBody = newLoop.getBody();
933 loopBody, newLoopBody,
936 auto yieldOp = cast<scf::YieldOp>(newLoopBody->
getTerminator());
942 newLoop.getRegionIterArgs().take_back(newInitOperands.size());
943 if (
failed(yieldTiledValuesFn(rewriter, loc, newLoop.getInductionVar(),
944 newRegionIterArgs, tiledValues, resultOffsets,
951 for (
auto [tiledValue, regionIterArg, resultOffset, resultSize] :
952 llvm::zip_equal(tiledValues, newRegionIterArgs, resultOffsets,
956 Value insert = tensor::InsertSliceOp::create(
957 rewriter, yieldOp->getLoc(), tiledValue, regionIterArg, resultOffset,
958 resultSize, resultStride);
959 newYieldValues.push_back(insert);
964 newLoop->getResults().take_front(loopOp.getNumResults()));
965 return cast<LoopLikeOpInterface>(newLoop.getOperation());
976 auto inits = llvm::to_vector(loopOp.getOutputs());
977 inits.append(newInitOperands.begin(), newInitOperands.end());
978 auto newLoop = scf::ForallOp::create(
979 rewriter, loc, loopOp.getMixedLowerBound(), loopOp.getMixedUpperBound(),
980 loopOp.getMixedStep(), inits, loopOp.getMapping(),
984 Block *loopBody = loopOp.getBody();
985 Block *newLoopBody = newLoop.getBody();
987 loopBody, newLoopBody,
990 auto terminator = cast<scf::InParallelOp>(newLoopBody->
getTerminator());
995 newLoop.getRegionIterArgs().take_back(newInitOperands.size());
996 if (
failed(yieldTiledValuesFn(rewriter, loc, newLoop.getInductionVars(),
997 regionIterArgs, tiledValues, resultOffsets,
1001 "failed to get yielded tiled values");
1007 for (
auto [tiledValue, iterArg, resultOffset, resultSize] : llvm::zip_equal(
1008 tiledValues, regionIterArgs, resultOffsets, resultSizes)) {
1011 tensor::ParallelInsertSliceOp::create(rewriter, terminator.getLoc(),
1012 tiledValue, iterArg, resultOffset,
1013 resultSize, resultStride);
1017 newLoop->getResults().take_front(loopOp.getNumResults()));
1018 return cast<LoopLikeOpInterface>(newLoop.getOperation());
1025 LoopLikeOpInterface loopLikeOp,
RewriterBase &rewriter,
1028 loopLikeOp.getOperation())
1029 .Case<scf::ForOp, scf::ForallOp>(
1030 [&](
auto loopOp) -> FailureOr<LoopLikeOpInterface> {
1032 loopOp, rewriter, newInitOperands, yieldTiledValuesFn);
1034 .Default([&](
auto loopOp) -> FailureOr<LoopLikeOpInterface> {
1053 for (
auto &loop : loops.drop_back()) {
1057 auto forLoop = cast<scf::ForOp>(loop.getOperation());
1061 newInits.append(newInitValues.begin(), newInitValues.end());
1062 auto newLoop = scf::ForOp::create(
1063 rewriter, forLoop.getLoc(), forLoop.getLowerBound(),
1064 forLoop.getUpperBound(), forLoop.getStep(), newInits,
1066 forLoop.getUnsignedCmp());
1070 sourceBlockArgs.push_back(newLoop.getInductionVar());
1071 auto newRegionIterArgs = newLoop.getRegionIterArgs();
1072 sourceBlockArgs.append(
1073 newRegionIterArgs.begin(),
1074 std::next(newRegionIterArgs.begin(), forLoop.getNumResults()));
1075 rewriter.
mergeBlocks(forLoop.getBody(), newLoop.getBody(), sourceBlockArgs);
1077 forLoop, newLoop.getResults().take_front(forLoop.getNumResults()));
1079 ivs.push_back(newLoop.getInductionVar());
1080 newInitValues = newLoop.getRegionIterArgs().take_back(newInitValues.size());
1084 LoopLikeOpInterface innerMostLoop = loops.back();
1085 FailureOr<LoopLikeOpInterface> newInnerMostLoop =
1087 getNewTiledYieldsFn);
1089 if (failed(newInnerMostLoop))
1090 return innerMostLoop.emitOpError(
"failed to return additional yields");
1091 loops.back() = newInnerMostLoop.value();
1095 for (
auto [outerLoop, innerLoop] :
1096 llvm::zip_equal(loops.drop_back(), loops.drop_front())) {
1098 auto outerForLoop = cast<scf::ForOp>(outerLoop.getOperation());
1099 auto outerLoopYield =
1100 cast<scf::YieldOp>(outerForLoop.getBody()->getTerminator());
1102 llvm::to_vector(outerLoopYield.getOperands());
1104 innerLoop->getResults().take_back(newInitValues.size());
1105 newYields.append(additionalYields.begin(), additionalYields.end());
1114FailureOr<scf::SCFTilingResult>
1115mlir::scf::tileUsingSCF(
RewriterBase &rewriter, TilingInterface op,
1116 const scf::SCFTilingOptions &
options) {
1129 std::tie(givenTileSizes, numThreads) =
1135 givenTileSizes, numThreads))) {
1146 if (!
options.interchangeVector.empty()) {
1148 iterationDomain.size());
1150 "expected interchange vector to be a permutation");
1154 if (!numThreads.empty())
1158 FailureOr<TilingResult> tilingResult;
1172 if (!interchangeVector.empty()) {
1181 auto clonedOp = cast<TilingInterface>(
1188 tiledResults.append(clonedOp->result_begin(), clonedOp->result_end());
1198 ?
options.innerTileAlignmentFn(clonedOp, givenTileSizes,
1202 rewriter, clonedOp,
options.reductionStrategy, regionIterArgs,
1203 tileOffsetsVec, tileSizesVec, ivs, numThreads, givenTileSizes,
1204 innerTileAlignments, reductionDims);
1205 if (
failed(tilingResult)) {
1207 return op.emitOpError(
"failed to tile operation");
1215 for (
auto [
index, tiledValue] :
1216 llvm::enumerate(tilingResult->tiledValues)) {
1217 tiledResults.push_back(tiledValue);
1220 rewriter,
options.reductionStrategy,
index, tiledValue, op,
1221 tileOffsetsVec, tileSizesVec, ivs, numThreads, givenTileSizes,
1222 reductionDims, resultOffset, resultSize))) {
1223 for (
auto op : tilingResult->tiledOps) {
1227 op,
"failed to get slice of result produced");
1229 resultOffsets.emplace_back(std::move(resultOffset));
1230 resultSizes.emplace_back(std::move(resultSize));
1238 rewriter, op,
options.reductionStrategy, iterationDomain, numThreads,
1239 givenTileSizes, reductionDims);
1240 if (
failed(maybeInits)) {
1242 op,
"unable to create initial tensors for tiling");
1250 rewriter, op.getLoc(),
options, iterationDomain, givenTileSizes,
1251 numThreads, initTensors, innerYieldTiledValuesFn);
1253 return op.emitOpError(
"failed to generate tiling loops");
1254 assert(succeeded(tilingResult) &&
1255 "expected tiling result to be computed after loop generation");
1256 std::swap(loops, loopsOr.value());
1259 if (loops.empty()) {
1262 return scf::SCFTilingResult{tilingResult->tiledOps,
1265 tilingResult->tiledValues,
1266 tilingResult->generatedSlices,
1270 auto loopResults = llvm::map_to_vector(loops.front()->getResults(),
1275 return scf::SCFTilingResult{
1276 tilingResult->tiledOps, initTensors, loops, loopResults,
1277 tilingResult->generatedSlices, {}};
1282 rewriter, op,
options.reductionStrategy, reductionDims, loopResults);
1283 if (
failed(mergeResult)) {
1285 op,
"Failed to merge partial results from tiling");
1287 return scf::SCFTilingResult{tilingResult->tiledOps,
1290 mergeResult->replacements,
1291 tilingResult->generatedSlices,
1292 mergeResult->mergeOps};
1295FailureOr<scf::SCFTilingResult>
1297 PartialReductionOpInterface op,
1299 scf::SCFTilingOptions
options;
1300 options.setLoopType(scf::SCFTilingOptions::LoopType::ForOp);
1301 options.setReductionTilingStrategy(
1303 options.setTileSizes(tileSize);
1305 for (
auto [
index, iteratorType] : llvm::enumerate(op.getLoopIteratorTypes()))
1306 if (iteratorType == utils::IteratorType::reduction)
1307 reductionDims.push_back(
index);
1308 options.setReductionDims(reductionDims);
1322static std::tuple<OpResult, std::optional<OpOperand *>>
1325 std::optional<OpOperand *> destinationIterArg;
1326 assert(!loops.empty() &&
"expected non empty loops container");
1327 auto loopIt = loops.rbegin();
1328 while (loopIt != loops.rend() && isa<BlockArgument>(source->
get())) {
1329 auto iterArg = cast<BlockArgument>(source->
get());
1330 auto loop = *loopIt;
1331 if (iterArg.getOwner()->getParentOp() != loop)
1333 source = loop.getTiedLoopInit(iterArg);
1336 if (loopIt == loops.rend())
1337 destinationIterArg = source;
1339 auto result = dyn_cast<OpResult>(source->
get());
1342 Operation *innermostLoop = loops.back();
1349 return {
result, destinationIterArg};
1354std::optional<scf::SCFFuseProducerOfSliceResult>
1355mlir::scf::tileAndFuseProducerOfSlice(
1356 RewriterBase &rewriter, tensor::ExtractSliceOp candidateSliceOp,
1358 const InnerTileAlignmentFnTy &fn) {
1361 auto [fusableProducer, destinationInitArg] =
1364 if (!fusableProducer)
1365 return std::nullopt;
1366 unsigned resultNumber = fusableProducer.getResultNumber();
1372 if (
auto producer = dyn_cast<TilingInterface>(fusableProducer.getOwner()))
1373 innerTileAlignments =
1374 fn(producer, {}, {candidateSliceOp.getOperation()});
1382 Operation *fusableProducerOp = fusableProducer.getOwner();
1383 if (isa<DestinationStyleOpInterface>(fusableProducerOp) &&
1385 rewriter, fusableProducerOp->
getLoc(), fusableProducerOp,
1386 origDestinationTensors)))
1387 return std::nullopt;
1389 clonedOpDestinationTensors = origDestinationTensors;
1390 if (destinationInitArg &&
1391 isa<DestinationStyleOpInterface>(fusableProducerOp)) {
1395 clonedOpDestinationTensors[resultNumber] = candidateSliceOp.getSource();
1399 rewriter, fusableProducerOp, clonedOpDestinationTensors);
1404 llvm::to_vector(candidateSliceOp->getOperands());
1405 candidateSliceOpOperands[0] = clonedProducerOp->
getResult(resultNumber);
1406 tensor::ExtractSliceOp clonedCandidateSliceOp =
1408 candidateSliceOp->getResultTypes(), candidateSliceOpOperands);
1411 FailureOr<TilingResult> tileAndFuseResult =
1413 rewriter, clonedCandidateSliceOp,
1414 clonedProducerOp->
getResult(resultNumber), innerTileAlignments);
1415 if (failed(tileAndFuseResult))
1416 return std::nullopt;
1420 tileAndFuseResult->tiledValues[0]);
1421 rewriter.
eraseOp(clonedCandidateSliceOp);
1422 rewriter.
eraseOp(clonedProducerOp);
1467 if (destinationInitArg &&
1468 isa<DestinationStyleOpInterface>(fusableProducerOp) && !loops.empty()) {
1470 ->getOpOperands()[destinationInitArg.value()->getOperandNumber()]
1471 .set(origDestinationTensors[resultNumber]);
1473 return scf::SCFFuseProducerOfSliceResult{
1474 fusableProducer, tileAndFuseResult->tiledValues[0],
1475 tileAndFuseResult->tiledOps, tileAndFuseResult->generatedSlices};
1479FailureOr<SmallVector<Operation *>> mlir::scf::yieldReplacementForFusedProducer(
1480 RewriterBase &rewriter, tensor::ExtractSliceOp sliceOp,
1481 scf::SCFFuseProducerOfSliceResult fusedProducerInfo,
1487 Operation *originalOwner = fusedProducerInfo.origProducer.getOwner(),
1488 *tiledOwner = fusedProducerInfo.tiledOps[0];
1493 yieldResultNumber.empty() ? llvm::to_vector(llvm::seq<unsigned>(
1495 : llvm::to_vector(yieldResultNumber);
1497 for (
const auto &resultNumber : initNumberList) {
1499 rewriter, loc, originalOwner->
getResult(resultNumber));
1500 if (succeeded(initValue)) {
1501 initValueList.push_back(initValue.value());
1517 sliceSizes = sliceOp.getMixedSizes();
1520 if (!llvm::all_of(sliceOp.getMixedStrides(),
isOneInteger))
1523 unsigned sliceResultNumber =
1524 fusedProducerInfo.origProducer.getResultNumber();
1526 auto tilableOp = cast<TilingInterface>(originalOwner);
1533 if (
auto tiledDestStyleOp =
1534 dyn_cast<DestinationStyleOpInterface>(tiledOwner)) {
1538 if (tilableOp->getNumResults() > 1 &&
1539 failed(tilableOp.getIterationDomainTileFromResultTile(
1540 rewriter, sliceResultNumber, sliceOffset, sliceSizes,
1541 iterDomainOffset, iterDomainSizes))) {
1556 for (
const auto &resultNumber : initNumberList) {
1557 if (resultNumber == sliceResultNumber) {
1558 offsetList.push_back(sliceOffset);
1559 sizesList.push_back(sliceSizes);
1561 assert(!iterDomainOffset.empty() && !iterDomainSizes.empty());
1564 if (failed(tilableOp.getResultTilePosition(
1565 rewriter, resultNumber, iterDomainOffset, iterDomainSizes,
1569 offsetList.push_back(offset);
1570 sizesList.push_back(sizes);
1576 if (
auto tiledDestStyleOp =
1577 dyn_cast<DestinationStyleOpInterface>(tiledOwner)) {
1578 for (
const auto &&[
index, newRegionArg] :
1579 llvm::enumerate(newRegionIterArgs)) {
1580 auto destSlice = tensor::ExtractSliceOp::create(
1581 rewriter, loc, newRegionArg, offsetList[
index], sizesList[
index],
1584 generatedSlices.push_back(destSlice);
1585 unsigned resultNumber = initNumberList[
index];
1587 tiledDestStyleOp.getDpsInitsMutable()[resultNumber].set(destSlice);
1596 for (
const auto &&[
index, resultNumber] : llvm::enumerate(initNumberList)) {
1597 tiledResult.push_back(tiledOwner->getResult(resultNumber));
1598 tiledOffset.emplace_back(offsetList[
index]);
1599 tiledSizes.emplace_back(sizesList[
index]);
1605 newYieldValuesFn))) {
1608 return generatedSlices;
1622 explicit SliceTrackingListener(
1623 std::optional<FrozenRewritePatternSet> patterns);
1624 SliceTrackingListener() =
default;
1633 void notifyOperationInserted(
Operation *op,
1640 void notifyOperationErased(
Operation *op)
override;
1647 std::deque<tensor::ExtractSliceOp> worklist;
1652 std::optional<FrozenRewritePatternSet> patterns = std::nullopt;
1655SliceTrackingListener::SliceTrackingListener(
1656 std::optional<FrozenRewritePatternSet> p) {
1657 patterns = std::move(p);
1661SliceTrackingListener::insertAndApplyPatterns(ArrayRef<Operation *> ops) {
1662 for (Operation *op : ops) {
1663 if (
auto slice = dyn_cast<tensor::ExtractSliceOp>(op))
1664 worklist.push_back(slice);
1671 ops, patterns.value(),
1672 GreedyRewriteConfig().setListener(
this).setStrictness(
1673 GreedyRewriteStrictness::ExistingAndNewOps));
1676void SliceTrackingListener::notifyOperationInserted(
1677 Operation *op, OpBuilder::InsertPoint previous) {
1678 auto slice = dyn_cast<tensor::ExtractSliceOp>(op);
1681 worklist.push_back(slice);
1687void SliceTrackingListener::removeOp(Operation *op) {
1688 if (!isa<tensor::ExtractSliceOp>(op))
1690 auto iter = worklist.begin();
1691 while (iter != worklist.end()) {
1696 if (iter == worklist.end())
1699 worklist.erase(iter);
1702void SliceTrackingListener::notifyOperationErased(Operation *op) {
1706void SliceTrackingListener::notifyOperationReplaced(Operation *op,
1718class ReplacementListener :
public RewriterBase::ForwardingListener {
1720 ReplacementListener(DenseMap<Value, Value> &replacements,
1721 OpBuilder::Listener *listener)
1722 : ForwardingListener(listener), replacements(replacements) {}
1724 void updateReplacementValues(
ValueRange origValues,
1728 for (
auto &[key, val] : replacements) {
1729 for (
auto [orig, replace] : llvm::zip_equal(origValues, replaceValues)) {
1737 void notifyOperationReplaced(Operation *op, Operation *newOp)
override {
1738 ForwardingListener::notifyOperationReplaced(op, newOp);
1742 void notifyOperationReplaced(Operation *op,
ValueRange values)
override {
1743 ForwardingListener::notifyOperationReplaced(op, values);
1744 updateReplacementValues(op->
getResults(), values);
1748 DenseMap<Value, Value> &replacements;
1754FailureOr<scf::SCFTileAndFuseResult>
1755mlir::scf::tileConsumerAndFuseProducersUsingSCF(
1756 RewriterBase &rewriter, TilingInterface consumer,
1757 const scf::SCFTileAndFuseOptions &
options) {
1760 if (!consumer->getNumResults()) {
1762 consumer,
"invalid pattern for op with no results");
1768 FailureOr<scf::SCFTilingResult> tilingResult =
1771 if (
failed(tilingResult))
1773 tiledAndFusedOps.insert_range(tilingResult->tiledOps);
1775 DenseMap<Value, Value> replacements;
1777 llvm::zip_equal(consumer->getResults(), tilingResult->replacements)) {
1782 auto &loops = tilingResult->loops;
1783 if (loops.empty()) {
1784 return scf::SCFTileAndFuseResult{fusedProducers, tiledAndFusedOps, loops,
1791 OpBuilder::Listener *previousListener = rewriter.
getListener();
1792 llvm::scope_exit resetListener(
1793 [&]() { rewriter.
setListener(previousListener); });
1794 ReplacementListener replaceListener(replacements, previousListener);
1804 struct WorklistItem {
1805 tensor::ExtractSliceOp candidateSlice;
1806 SCFTileAndFuseOptions::ControlFnResult controlFnResult;
1809 SliceTrackingListener sliceTracker =
1810 SliceTrackingListener(
options.cleanupPatterns);
1813 sliceTracker.insertAndApplyPatterns(tilingResult->generatedSlices))) {
1816 OpBuilder::InsertionGuard g(rewriter);
1817 while (!sliceTracker.worklist.empty()) {
1818 auto candidateSlice = sliceTracker.worklist.front();
1819 sliceTracker.worklist.pop_front();
1821 auto [fusableProducer, destinationInitArg] =
1824 if (!fusableProducer)
1827 std::optional<SCFTileAndFuseOptions::ControlFnResult> controlFnResult =
1828 options.fusionControlFn(candidateSlice, fusableProducer,
1829 destinationInitArg.has_value());
1830 if (!controlFnResult)
1833 WorklistItem worklistItem = {candidateSlice, controlFnResult.value()};
1838 std::optional<scf::SCFFuseProducerOfSliceResult> fusedResult =
1840 options.tilingOptions.innerTileAlignmentFn);
1844 SmallVector<Operation *> worklistCandidates = fusedResult->generatedSlices;
1846 if (worklistItem.controlFnResult.yieldProducerReplacement) {
1851 Operation *fusableProducerOp = fusedResult->origProducer.getOwner();
1852 FailureOr<SmallVector<Operation *>> newSlices =
1854 worklistItem.candidateSlice,
1855 fusedResult.value(), loops);
1858 fusableProducerOp,
"failed to replacement value for this "
1859 "operation from within the tiled loop");
1861 worklistCandidates.append(newSlices.value());
1862 for (
auto [index,
result] :
1863 llvm::enumerate(fusableProducerOp->
getResults())) {
1864 replacements[
result] = loops.front()->getResult(
1865 loops.front()->getNumResults() -
1869 if (Operation *tiledAndFusedOp =
1870 fusedResult->tiledAndFusedProducer.getDefiningOp()) {
1871 fusedProducers.insert(fusedResult->origProducer.getDefiningOp());
1872 tiledAndFusedOps.insert(tiledAndFusedOp);
1875 if (
failed(sliceTracker.insertAndApplyPatterns(worklistCandidates))) {
1880 return scf::SCFTileAndFuseResult{fusedProducers, tiledAndFusedOps, loops,
1894 if (!llvm::hasSingleElement(uses)) {
1895 LLVM_DEBUG(llvm::dbgs() <<
"Too many uses of the candidate slice op\n");
1898 OpOperand &operandUse = (*uses.begin());
1900 if (!isa<scf::YieldOp>(userOp)) {
1901 LLVM_DEBUG(llvm::dbgs()
1902 <<
"Expected scf.yield to be the only user, but got -> "
1907 LLVM_DEBUG(llvm::dbgs() <<
"Expected tensor.insert_slice and scf.yield to "
1908 "be in the same block\n");
1917 if (!isa<LoopLikeOpInterface>(loopOp))
1936 if (isa<tensor::ParallelInsertSliceOp>(userOp))
1937 userOp = userOp->getParentOfType<scf::InParallelOp>();
1939 if (loopOp->
getBlock() != userOp->getBlock())
1943 firstUserOfLoop = userOp;
1945 return firstUserOfLoop;
1986static FailureOr<llvm::SetVector<Operation *>>
1988 bool reorderOperations) {
1990 if (failed(firstUserOfLoop))
1996 options.omitBlockArguments =
true;
1997 bool includeLoopOp =
false;
2000 includeLoopOp =
true;
2010 assert(
result.succeeded() &&
"expected a backward slice");
2014 if (!slice.empty()) {
2024 if (includeLoopOp || !reorderOperations)
2036 unsigned resultNumber) {
2037 if (!isa<LoopLikeOpInterface>(loopOp))
2042 Operation *consumerOp = opOperand.getOwner();
2044 if (!isa<TilingInterface>(consumerOp) ||
2045 !isa<DestinationStyleOpInterface>(consumerOp)) {
2052 if (loopBlock != consumerOp->
getBlock())
2059 FailureOr<llvm::SetVector<Operation *>> slice =
2065 if (!slice->empty()) {
2068 assert(succeeded(firstUserOfLoop) &&
"First user of loop is not found");
2069 for (
auto op : *slice) {
2084static FailureOr<OpOperand *>
2086 tensor::InsertSliceOp candidateSliceOp,
2088 assert(!loops.empty() &&
"unexpected loops to be empty");
2091 if (containingOp != loops.back()) {
2094 "expected slice to be within body of inner-most loop");
2100 candidateSliceOp,
"expected passed loops to be perfectly nested.");
2105 Value sliceResult = candidateSliceOp.getResult();
2111 scf::ForOp topLevelForOp = cast<scf::ForOp>(loops.front().getOperation());
2118static FailureOr<OpOperand *>
2120 tensor::ParallelInsertSliceOp candidateSliceOp,
2122 assert(!loops.empty() &&
"unexpected loops to be empty");
2124 if (loops.size() != 1) {
2126 candidateSliceOp,
"expected single surrounding scf.forall");
2128 auto forallOp = dyn_cast<scf::ForallOp>(loops.front().getOperation());
2131 candidateSliceOp,
"expected single surrounding scf.forall");
2135 Value sliceDest = candidateSliceOp.getDest();
2136 auto iterArg = dyn_cast<BlockArgument>(sliceDest);
2139 if (iterArg.getOwner()->getParentOp() != forallOp)
2142 unsigned resultNumber =
2143 forallOp.getTiedOpResult(forallOp.getTiedOpOperand(iterArg))
2154 assert(!loops.empty() &&
"unexpected empty loops");
2155 assert(!sliceOps.empty() &&
"unexpected empty list of candidate slices");
2157 for (
auto sliceOp : sliceOps) {
2158 FailureOr<OpOperand *> fusedOperand =
2160 .Case<tensor::InsertSliceOp, tensor::ParallelInsertSliceOp>(
2167 if (failed(fusedOperand)) {
2170 if (!fusedOperands.empty() &&
2171 fusedOperand.value()->getOwner() != fusedOperands.front()->getOwner()) {
2173 fusedOperand.value()->getOwner(),
2174 "all candidate slices must be to the same consumer");
2176 fusedOperands.push_back(fusedOperand.value());
2178 return fusedOperands;
2181template <
typename InsertSliceOpTy>
2183 InsertSliceOpTy sliceOp);
2186tensor::InsertSliceOp
2188 tensor::InsertSliceOp insertSliceOp) {
2189 return cast<tensor::InsertSliceOp>(
2190 rewriter.
clone(*insertSliceOp.getOperation()));
2195 RewriterBase &rewriter, tensor::ParallelInsertSliceOp insertSliceOp) {
2196 return tensor::InsertSliceOp::create(
2197 rewriter, insertSliceOp->getLoc(), insertSliceOp.getSource(),
2198 insertSliceOp.getDest(), insertSliceOp.getMixedOffsets(),
2199 insertSliceOp.getMixedSizes(), insertSliceOp.getMixedStrides());
2202static SmallVector<tensor::InsertSliceOp>
2205 assert(!candidateSlices.empty() &&
2206 "unexpected empty list of slices to clone");
2208 for (
auto sliceOp : candidateSlices) {
2210 .Case<tensor::InsertSliceOp, tensor::ParallelInsertSliceOp>(
2213 clonedSlices.push_back(clonedOp);
2216 .DefaultUnreachable(
2217 "unexpected slice type while cloning as insert slice");
2219 return clonedSlices;
2222static FailureOr<scf::SCFFuseConsumerOfSliceResult>
2227 const mlir::scf::InnerTileAlignmentFnTy &fn) {
2228 assert(!loops.empty() &&
"expected loops to be not empty");
2234 if (
auto consumer = dyn_cast<TilingInterface>(consumerOp))
2235 innerTileAlignments = fn(consumer, {}, candidateSlices);
2240 loops.front(),
"the first user of loop should not dominate any define "
2241 "of consumer operand(s)");
2244 LoopLikeOpInterface outerMostLoop = loops.front();
2245 LoopLikeOpInterface innerMostLoop = loops.back();
2249 auto dstOp = dyn_cast<DestinationStyleOpInterface>(consumerOp);
2252 "consumer op is not DPS operation");
2253 if (llvm::any_of(consumerOpOperands, [&](
OpOperand *opOperand) {
2254 return dstOp.isDpsInit(opOperand);
2258 "consumer op taking the result of scf.for as init is not supported");
2265 if (failed(firstUserOfLoop)) {
2267 outerMostLoop,
"could not find the first user of outer most loop");
2269 rewriter.
moveOpBefore(outerMostLoop, *firstUserOfLoop);
2276 dyn_cast<tensor::ParallelInsertSliceOp>(candidateSlices.front())) {
2277 auto newForallOp = cast<scf::ForallOp>(innerMostLoop.getOperation());
2287 auto clonedConsumerOp = cast<TilingInterface>(rewriter.
clone(*consumerOp));
2289 llvm::map_to_vector(consumerOpOperands, [](
OpOperand *opOperand) {
2293 llvm::map_to_vector(operandNumbers, [&](
unsigned operandNum) {
2294 return &clonedConsumerOp->getOpOperand(operandNum);
2300 for (
auto [operandToReplace, clonedSliceOp] :
2301 llvm::zip_equal(clonedOpFusedOperandsList, clonedInsertSlices)) {
2302 operandToReplace->set(clonedSliceOp.getResult());
2308 FailureOr<TilingResult> tileAndFuseResult =
2310 clonedOpFusedOperandsList,
2311 innerTileAlignments);
2312 if (failed(tileAndFuseResult)) {
2316 auto tiledConsumerOp = cast<TilingInterface>(tileAndFuseResult->tiledOps[0]);
2317 for (
auto [operandNum, clonedSliceOp] :
2318 llvm::zip_equal(operandNumbers, clonedInsertSlices)) {
2320 clonedSliceOp.getSource());
2334 for (
auto candidateSliceOp : clonedInsertSlices) {
2342 candidateSliceOp,
"containingOp's result yield with stride");
2345 allOffsets.emplace_back(std::move(offsets));
2346 allSizes.emplace_back(std::move(sizes));
2356 if (failed(clonedConsumerOp.getIterationDomainTileFromOperandTiles(
2357 rewriter, operandNumbers, allOffsets, allSizes, iterDomainOffsets,
2358 iterDomainSizes, innerTileAlignments))) {
2361 "can't get iter domain position from input position");
2367 unsigned totalNumResultsOfConsumer = tiledConsumerOp->getNumResults();
2369 totalNumResultsOfConsumer);
2371 totalNumResultsOfConsumer);
2372 for (
auto [idx, v] : llvm::enumerate(tiledConsumerOp->getResults())) {
2373 if (failed(tiledConsumerOp.getResultTilePosition(
2374 rewriter, idx, iterDomainOffsets, iterDomainSizes,
2375 resultOffsets[idx], resultSizes[idx]))) {
2378 "can't get result domain position from iter domain position");
2384 if (
auto tiledDestStyleOp = dyn_cast<DestinationStyleOpInterface>(
2385 tiledConsumerOp.getOperation())) {
2387 for (
const auto &&[
index, newRegionArg] :
2388 llvm::enumerate(newRegionIterArgs)) {
2389 auto destSlice = tensor::ExtractSliceOp::create(
2390 rewriter, loc, newRegionArg, resultOffsets[
index],
2396 auto dstNumber =
index;
2398 tiledDestStyleOp.getDpsInitsMutable()[dstNumber].set(destSlice);
2408 llvm::enumerate(tiledConsumerOp->getResults())) {
2409 tiledResult.push_back(
result);
2410 tiledOffset.emplace_back(resultOffsets[
index]);
2411 tiledSizes.emplace_back(resultSizes[
index]);
2417 newYieldValuesFn))) {
2419 "unable to add new inits to nest loop");
2425 for (
auto &&[oldResult, newResult] :
2427 loops.front()->getResults().take_back(newInits.size()))) {
2432 rewriter.
eraseOp(clonedConsumerOp);
2435 llvm::map_to_vector(operandNumbers, [&](
unsigned operandNum) {
2436 return &tileAndFuseResult->tiledOps[0]->getOpOperand(operandNum);
2438 auto consumerOpOperandsVec = llvm::to_vector(consumerOpOperands);
2439 return scf::SCFFuseConsumerOfSliceResult{
2440 std::move(consumerOpOperandsVec), std::move(tiledAndFusedOpOperands),
2441 std::move(tileAndFuseResult->tiledOps)};
2446FailureOr<scf::SCFFuseConsumerOfSliceResult>
2447mlir::scf::tileAndFuseConsumerOfSlices(
2450 const InnerTileAlignmentFnTy &fn) {
2451 if (candidateSlices.empty()) {
2454 "no candidate slices provided for consumer fusion");
2458 if (loops.empty()) {
2460 candidateSlices.front(),
2461 "cannot call tile and fuse consumer with an empty loop nest");
2464 if (!(llvm::all_of(candidateSlices, llvm::IsaPred<tensor::InsertSliceOp>) ||
2465 llvm::all_of(candidateSlices,
2466 llvm::IsaPred<tensor::ParallelInsertSliceOp>))) {
2468 candidateSlices.front(),
2469 "candidates slices need to be all `tensor.extract_slice`s or "
2470 "`tensor.parallel_insert_slice`s");
2475 FailureOr<SmallVector<OpOperand *>> maybeConsumerOpOperands =
2477 if (failed(maybeConsumerOpOperands)) {
2479 "could not fetch consumer to fuse");
2481 Operation *consumerOp = maybeConsumerOpOperands->front()->getOwner();
2484 maybeConsumerOpOperands.value(),
2485 candidateSlices, loops, fn);
2491static std::optional<Operation *>
2493 if (
result.getOwner() != forallOp)
2494 return std::nullopt;
2499 if (combiningOps.size() != 1)
2500 return std::nullopt;
2501 return combiningOps[0];
2506static std::optional<Operation *>
2509 assert(!loops.empty() &&
"Expected loops to be not empty");
2510 LoopLikeOpInterface outerMostLoop = loops.front();
2511 if (
auto forallOp = dyn_cast<scf::ForallOp>(outerMostLoop.getOperation())) {
2512 assert(loops.size() == 1 &&
2513 "expected only a single loop when tiling using scf.forall");
2519 while (loops.size() != 1) {
2520 LoopLikeOpInterface loop = loops.front();
2521 if (
result.getOwner() != loop)
2522 return std::nullopt;
2523 auto forOp = dyn_cast<scf::ForOp>(loop.getOperation());
2525 return std::nullopt;
2526 auto yieldOp = cast<scf::YieldOp>(forOp.getBody()->getTerminator());
2527 auto innerForResult =
2528 dyn_cast<OpResult>(yieldOp.getOperand(
result.getResultNumber()));
2529 if (!innerForResult)
2530 return std::nullopt;
2532 loops = loops.drop_front();
2534 LoopLikeOpInterface loop = loops.front();
2535 if (
result.getOwner() != loop)
2536 return std::nullopt;
2537 auto forOp = dyn_cast<scf::ForOp>(loop.getOperation());
2539 return std::nullopt;
2540 auto yieldOp = cast<scf::YieldOp>(forOp.getBody()->getTerminator());
2541 auto insertSliceOp = yieldOp.getOperand(
result.getResultNumber())
2542 .getDefiningOp<tensor::InsertSliceOp>();
2544 return std::nullopt;
2545 return insertSliceOp;
2548FailureOr<scf::SCFFuseConsumerOfSliceResult>
2551 const InnerTileAlignmentFnTy &fn) {
2552 if (!isa<TilingInterface>(consumer)) {
2554 consumer,
"unhandled consumer that does not implement TilingInterface");
2559 if (loops.empty()) {
2561 consumer,
"cannot call tile and fuse consumer with an empty loop nest");
2564 LoopLikeOpInterface outermostLoop = loops.front();
2570 if (opOperand.get().getDefiningOp() == outermostLoop) {
2571 consumerFusableOperands.push_back(&opOperand);
2576 if (consumerFusableOperands.empty()) {
2577 return mlir::scf::SCFFuseConsumerOfSliceResult{consumerFusableOperands,
2585 candidateSlices.reserve(consumerFusableOperands.size());
2586 for (
OpOperand *opOperand : consumerFusableOperands) {
2587 std::optional<Operation *> slice =
2592 "couldnt find producing insert-slice like operation for operand");
2594 candidateSlices.push_back(slice.value());
2598 rewriter, consumer, consumerFusableOperands, candidateSlices, loops, fn);
2605FailureOr<SmallVector<scf::ForOp>>
2607 TilingInterface op) {
2609 if (op->getNumResults() > 0) {
2611 op,
"unable to lower to loops operations with return values");
2618 for (
auto loopRange : domain) {
2625 auto loop = scf::ForOp::create(rewriter, op.getLoc(), offsetVal, sizeVal,
2627 loops.push_back(loop);
2628 ivs.push_back(loop.getInductionVar());
2631 if (failed(op.generateScalarImplementation(rewriter, op.getLoc(), ivs))) {
*if copies could not be generated due to yet unimplemented cases *copyInPlacementStart and copyOutPlacementStart in copyPlacementBlock *specify the insertion points where the incoming copies and outgoing should be the output argument nBegin is set to its * replacement(set to `begin` if no invalidation happens). Since outgoing *copies could have been inserted at `end`
static llvm::ManagedStatic< PassManagerOptions > options
static bool canOmitTileOffsetInBoundsCheck(OpFoldResult givenTileSize, OpFoldResult numThreads, OpFoldResult iterationSize)
Returns true if the maximum tile offset tileSize * numThreads-1 is less than iterationSize.
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< MergeResult > mergeTilingResults(RewriterBase &rewriter, TilingInterface op, ReductionTilingStrategy reductionStrategy, const SetVector< unsigned > &reductionDims, ValueRange partialResults)
static std::optional< Operation * > getProducingInsertSliceLikeOp(OpResult result, ArrayRef< LoopLikeOpInterface > loops)
For a given result of the loop nest that is a tiled loop nest, return the insert slice-like op that i...
static std::tuple< OpResult, std::optional< OpOperand * > > getUntiledProducerFromSliceSource(OpOperand *source, ArrayRef< LoopLikeOpInterface > loops)
Return the untiled producer whose slice is used in a tiled consumer.
FailureOr< LoopLikeOpInterface > yieldTiledValuesAndReplaceLoop< scf::ForallOp >(scf::ForallOp loopOp, RewriterBase &rewriter, ValueRange newInitOperands, YieldTiledValuesFn yieldTiledValuesFn)
Implementation of yieldTiledValuesAndReplaceLoop for scf.forall
static FailureOr< OpOperand * > getConsumerFromLoopUses(RewriterBase &rewriter, Operation *loopOp, unsigned resultNumber)
Fetches the OpOperand of the first valid user (and use) of the value val which implements TilingInter...
static LogicalResult checkAssumptionForFusingConsumer(tensor::InsertSliceOp candidateSliceOp)
A utility function that checks whether the only use of the result of a tensor.insert_slice op is in a...
std::function< LogicalResult( RewriterBase &rewriter, Location Loc, ValueRange ivs, ArrayRef< OpFoldResult > tileOffsets, ArrayRef< OpFoldResult > tileSizes, ValueRange outerDestinationTensors, SmallVector< Value > &tiledResults, SmallVector< SmallVector< OpFoldResult > > &resultOffsets, SmallVector< SmallVector< OpFoldResult > > &resultSizes)> GenerateTiledBodyFn
Typedef for function that implements the body of a tiled loop.
static LogicalResult checkTileSizes(TilingInterface op, scf::SCFTilingOptions::LoopType loopType, ReductionTilingStrategy reductionStrategy, ArrayRef< OpFoldResult > givenTileSizes, ArrayRef< OpFoldResult > numThreads)
Checks if any of the tiled loops are not parallel.
static FailureOr< SmallVector< LoopLikeOpInterface > > generateLoopNestUsingCustomOp(RewriterBase &rewriter, Location loc, ArrayRef< Range > loopRanges, ArrayRef< OpFoldResult > givenTileSizes, ValueRange outerDestinationTensors, const scf::SCFTilingOptions::GenerateLoopHeaderFn &generateLoopHeaderFn, const scf::SCFTilingOptions::GenerateLoopTerminatorFn &generateLoopTerminatorFn, GenerateTiledBodyFn tiledBodyFn)
Generate the tile-loop nest using custom loop operation.
static FailureOr< SmallVector< LoopLikeOpInterface > > generateLoopNest(RewriterBase &rewriter, Location loc, const scf::SCFTilingOptions &options, ArrayRef< Range > loopRanges, ArrayRef< OpFoldResult > givenTileSizes, ArrayRef< OpFoldResult > numThreads, ValueRange destinationTensors, GenerateTiledBodyFn tiledBodyFn)
Generate the tile-loop nest using the loop construct specifed in options.
static FailureOr< SmallVector< LoopLikeOpInterface > > generateLoopNestUsingForOp(RewriterBase &rewriter, Location loc, ArrayRef< Range > loopRanges, ArrayRef< OpFoldResult > givenTileSizes, ValueRange outerDestinationTensors, GenerateTiledBodyFn tiledBodyFn)
Generate the tile-loop nest using scf.for operation.
static SmallVector< int64_t > fillInterchangeVector(ArrayRef< int64_t > interchangeVector, size_t iterationDomainSize)
Helper method to adjust the interchange vector to match the iteration domain.
static FailureOr< OpOperand * > getUntiledConsumerFromSlice(RewriterBase &rewriter, tensor::InsertSliceOp candidateSliceOp, MutableArrayRef< LoopLikeOpInterface > loops)
Fetch the untiled consumer of the outermost scf.for's result which is yielded by a tensor....
static SmallVector< tensor::InsertSliceOp > cloneAsInsertSlices(RewriterBase &rewriter, ArrayRef< Operation * > candidateSlices)
static FailureOr< SmallVector< OpOperand * > > getUntiledConsumerOperandsFromSlices(RewriterBase &rewriter, ArrayRef< Operation * > sliceOps, MutableArrayRef< LoopLikeOpInterface > loops)
A utility to fetch an untiled consumer of tensor.insert_slice/tensor.parallel_insert_slice.
tensor::InsertSliceOp cloneAsInsertSlice< tensor::ParallelInsertSliceOp >(RewriterBase &rewriter, tensor::ParallelInsertSliceOp insertSliceOp)
static FailureOr< Operation * > getFirstUserOfLoop(Operation *loopOp)
An utility to get the first user of the given loopOp.
static Operation * cloneOpAndUpdateDestinationArgs(RewriterBase &rewriter, Operation *op, ValueRange newDestArgs)
Clones the operation and updates the destination if the operation implements the DestinationStyleOpIn...
static FailureOr< llvm::SetVector< Operation * > > checkAssumptionForLoop(Operation *loopOp, Operation *consumerOp, bool reorderOperations)
This utility currently checks whether the first userOp of loop is NOT before the last defineOp of con...
std::function< LogicalResult( RewriterBase &rewriter, Location loc, ValueRange ivs, ValueRange newBbArgs, SmallVector< Value > &tiledValues, SmallVector< SmallVector< OpFoldResult > > &resultOffsets, SmallVector< SmallVector< OpFoldResult > > &resultSizes)> YieldTiledValuesFn
Typedef for function that allows returning additional yielded values during yieldTiledValuesAndReplac...
tensor::InsertSliceOp cloneAsInsertSlice< tensor::InsertSliceOp >(RewriterBase &rewriter, tensor::InsertSliceOp insertSliceOp)
static std::tuple< SmallVector< OpFoldResult >, SmallVector< OpFoldResult > > getTileOffsetAndSizes(RewriterBase &rewriter, Location loc, ValueRange ivs, ArrayRef< Range > iterationDomain, ArrayRef< OpFoldResult > givenTileSizes)
Compute the OpFoldResults that represents the multi-dimensional offsets and sizes of the tile of the ...
static std::tuple< SmallVector< OpFoldResult >, SmallVector< OpFoldResult >, SmallVector< OpFoldResult > > getLoopBounds(RewriterBase &rewriter, Location loc, ArrayRef< Range > loopRanges, ArrayRef< OpFoldResult > givenTileSizes)
Function to return the bounds of the loops to be generated.
static std::tuple< SmallVector< OpFoldResult >, SmallVector< OpFoldResult > > getTileOffsetAndSizesWithForAllOp(RewriterBase &rewriter, Location loc, ValueRange ivs, ArrayRef< Range > iterationDomain, ArrayRef< OpFoldResult > givenTileSizes, ArrayRef< OpFoldResult > numThreads)
Compute the OpFoldResults that represents the multi-dimensional offsets and sizes of the tile of the ...
static OpFoldResult getBoundedTileSize(OpBuilder &b, Location loc, Range loopRange, OpFoldResult offset, OpFoldResult givenTileSize)
Returns the bounded tile size given the current offset, loopRange and tileSize, i....
static FailureOr< scf::SCFFuseConsumerOfSliceResult > tileAndFuseConsumerOfSlicesImpl(RewriterBase &rewriter, Operation *consumerOp, ArrayRef< OpOperand * > consumerOpOperands, ArrayRef< Operation * > candidateSlices, MutableArrayRef< LoopLikeOpInterface > loops, const mlir::scf::InnerTileAlignmentFnTy &fn)
static FailureOr< LoopLikeOpInterface > yieldTiledValuesAndReplaceLoop(LoopType loopOp, RewriterBase &rewriter, ValueRange newInitOperands, YieldTiledValuesFn yieldTiledValuesFn)
Append the specified additional newInitOperands operands to the loops existing init operands (or simi...
FailureOr< LoopLikeOpInterface > yieldTiledValuesAndReplaceLoop< scf::ForOp >(scf::ForOp loopOp, RewriterBase &rewriter, ValueRange newInitOperands, YieldTiledValuesFn yieldTiledValuesFn)
Implementation of yieldTiledValuesAndReplaceLoop for scf.for.
static bool tileDividesIterationDomain(Range loopRange)
Check if stride evenly divides the trip count size - offset.
static SetVector< unsigned > getSanitizedReductionDims(ArrayRef< OpFoldResult > givenTileSizes, const scf::SCFTilingOptions &options)
Get the reduction dims that are tiled.
static LogicalResult addInitOperandsToLoopNest(RewriterBase &rewriter, MutableArrayRef< LoopLikeOpInterface > loops, ValueRange newInitValues, YieldTiledValuesFn getNewTiledYieldsFn)
Method to add new init values to a loop nest.
static FailureOr< SmallVector< LoopLikeOpInterface > > generateLoopNestUsingForallOp(RewriterBase &rewriter, Location loc, ArrayRef< Range > loopRanges, ArrayRef< OpFoldResult > givenTileSizes, ArrayRef< OpFoldResult > numThreads, ArrayRef< Attribute > mappingVector, ValueRange outerDestinationTensors, GenerateTiledBodyFn tiledBodyFn)
Generate the tile-loop nest using scf.forall operation.
static std::optional< Operation * > getProducingParallelInsertSlice(scf::ForallOp forallOp, OpResult result)
For a given result of a forallOp return the tensor.parallel_insert_slice op (or combining op) that is...
static tensor::InsertSliceOp cloneAsInsertSlice(RewriterBase &rewriter, InsertSliceOpTy sliceOp)
static FailureOr< SmallVector< Value > > createInitialTensorsForTiling(RewriterBase &rewriter, TilingInterface op, ReductionTilingStrategy reductionStrategy, ArrayRef< Range > iterationDomain, ArrayRef< OpFoldResult > numThreads, ArrayRef< OpFoldResult > givenTileSizes, const SetVector< unsigned > &reductionDims)
static SmallVector< OpFoldResult > getSplitReductionIvs(RewriterBase &rewriter, Location loc, ReductionTilingStrategy reductionStrategy, ValueRange ivs, ArrayRef< OpFoldResult > numThreads, ArrayRef< OpFoldResult > givenTileSizes, const SetVector< unsigned > &reductionDims)
For the case of ReductionTilingStrategy::PartialReductionOuterParallel the PartialReductionOpInterfac...
static std::tuple< SmallVector< OpFoldResult >, SmallVector< OpFoldResult > > getUserTileSizesAndNumThreads(RewriterBase &rewriter, TilingInterface op, ArrayRef< Range > iterationDomain, const scf::SCFTilingOptions &options)
Method to instantiate the tile sizes and/or number of threads specified by the user.
static LogicalResult verifyOptions(RewriterBase &rewriter, Location loc, const scf::SCFTilingOptions &options)
Verify the tile size options are set in a consistent manner.
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)
Base type for affine expression.
AffineExpr floorDiv(uint64_t v) const
AffineExpr ceilDiv(uint64_t v) const
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
static AffineMap getMultiDimIdentityMap(unsigned numDims, MLIRContext *context)
Returns an AffineMap with 'numDims' identity result dim exprs.
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
This class represents an argument of a Block.
Block represents an ordered list of Operations.
unsigned getNumArguments()
Operation * getTerminator()
Get the terminator operation of this block.
BlockArgListType getArguments()
IntegerAttr getIndexAttr(int64_t value)
ArrayAttr getArrayAttr(ArrayRef< Attribute > value)
MLIRContext * getContext() const
A class for computing basic dominance information.
bool properlyDominates(Operation *a, Operation *b, bool enclosingOpOk=true) const
Return true if operation A properly dominates operation B, i.e.
IRValueT get() const
Return the current value being used by this operand.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
This class represents a saved insertion point.
RAII guard to reset the insertion point of the builder when destroyed.
This class helps build Operations.
Block::iterator getInsertionPoint() const
Returns the current insertion point of the builder.
Operation * clone(Operation &op, IRMapping &mapper)
Creates a deep copy of the specified operation, remapping any operands that use values outside of the...
void setListener(Listener *newListener)
Sets the listener of this builder to the one provided.
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Listener * getListener() const
Returns the current listener of this builder, or nullptr if this builder doesn't have a listener.
void setInsertionPointToEnd(Block *block)
Sets the insertion point to the end of the specified block.
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
This class represents a single result from folding an operation.
This class represents an operand of an operation.
unsigned getOperandNumber() const
Return which operand this is in the OpOperand list of the Operation.
This is a value defined by a result of an operation.
Operation is the basic unit of execution within MLIR.
bool use_empty()
Returns true if this operation has no uses.
bool isBeforeInBlock(Operation *other)
Given an operation 'other' that is within the same parent block, return whether the current operation...
Block * getBlock()
Returns the operation block that contains this operation.
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Location getLoc()
The source location the operation was defined or derived from.
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
MutableArrayRef< OpOperand > getOpOperands()
operand_range getOperands()
Returns an iterator on the underlying Value's.
user_range getUsers()
Returns a range of all users.
result_range getResults()
bool isProperAncestor(Operation *other)
Return true if this operation is a proper ancestor of the other operation.
unsigned getNumResults()
Return the number of results held by this operation.
This class coordinates the application of a rewrite on a set of IR, providing a way for clients to tr...
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
void moveOpBefore(Operation *op, Operation *existingOp)
Unlink this operation from its current block and insert it right before existingOp which may be in th...
void mergeBlocks(Block *source, Block *dest, ValueRange argValues={})
Inline the operations of block 'source' into the end of block 'dest'.
std::enable_if_t<!std::is_convertible< CallbackT, Twine >::value, LogicalResult > notifyMatchFailure(Location loc, CallbackT &&reasonCallback)
Used to notify the listener that the IR failed to be rewritten because of a match failure,...
void modifyOpInPlace(Operation *root, CallableT &&callable)
This method is a utility wrapper around an in-place modification of an operation.
virtual void replaceAllUsesWith(Value from, Value to)
Find uses of from and replace them with to.
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
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...
use_range getUses() const
Returns a range of all uses, which is useful for iterating over all uses.
iterator_range< use_iterator > use_range
Operation * getOwner() const
Return the owner of this operand.
OpFoldResult makeComposedFoldedAffineMax(OpBuilder &b, Location loc, AffineMap map, ArrayRef< OpFoldResult > operands)
Constructs an AffineMinOp that computes a maximum across the results of applying map to operands,...
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...
OpFoldResult makeComposedFoldedAffineMin(OpBuilder &b, Location loc, AffineMap map, ArrayRef< OpFoldResult > operands)
Constructs an AffineMinOp that computes a minimum across the results of applying map to operands,...
FailureOr< TilingResult > replaceExtractSliceWithTiledProducer(OpBuilder &builder, tensor::ExtractSliceOp sliceOp, OpResult producerOp, ArrayRef< InnerTileAlignment > innerTileAlignments={})
Method to swap an tensor.extract_slice with its producer when the producer implements the TilingInter...
FailureOr< TilingResult > replaceInsertSlicesWithTiledConsumer(OpBuilder &builder, ArrayRef< tensor::InsertSliceOp > sliceOps, ArrayRef< OpOperand * > consumerOperands, ArrayRef< InnerTileAlignment > innerTileAlignments={})
Method to swap tensor.insert_slices with their consumers when the consumer implements the TilingInter...
FailureOr< Value > getOrCreateDestination(OpBuilder &b, Location loc, OpResult opResult)
This is a helper function for DestinationStyleOpInterface.
LogicalResult getOrCreateDestinations(OpBuilder &b, Location loc, Operation *op, SmallVector< Value > &result)
This is a helper function for DestinationStyleOpInterface.
Include the generated interface declarations.
bool isPerfectlyNestedForLoops(MutableArrayRef< LoopLikeOpInterface > loops)
Check if the provided loops are perfectly nested for-loops.
bool isConstantIntValue(OpFoldResult ofr, int64_t value)
Return true if ofr is constant integer equal to value.
ReductionTilingStrategy
Tiling can be thought of as splitting a dimension into 2 and materializing the outer dimension as a l...
@ PartialReductionOuterReduction
@ PartialReductionOuterParallel
LogicalResult getBackwardSlice(Operation *op, SetVector< Operation * > *backwardSlice, const BackwardSliceOptions &options={})
Fills backwardSlice with the computed backward slice (i.e.
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
void bindDims(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to DimExpr at positions: [0 .
AffineMap inversePermutation(AffineMap map)
Returns a map of codomain to domain dimensions such that the first codomain dimension for a particula...
LogicalResult applyOpPatternsGreedily(ArrayRef< Operation * > ops, const FrozenRewritePatternSet &patterns, GreedyRewriteConfig config=GreedyRewriteConfig(), bool *changed=nullptr, bool *allErased=nullptr)
Rewrite the specified ops by repeatedly applying the highest benefit patterns in a greedy worklist dr...
llvm::SetVector< T, Vector, Set, N > SetVector
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 .
FailureOr< SmallVector< Operation * > > yieldReplacementForFusedProducer(RewriterBase &rewriter, tensor::ExtractSliceOp sliceOp, scf::SCFFuseProducerOfSliceResult fusedProducerInfo, MutableArrayRef< LoopLikeOpInterface > loops, ArrayRef< unsigned > yieldResultNumber=ArrayRef< unsigned >{})
Reconstruct the fused producer from within the tiled-and-fused code.
llvm::TypeSwitch< T, ResultT > TypeSwitch
Value getValueOrCreateConstantIndexOp(OpBuilder &b, Location loc, OpFoldResult ofr)
Converts an OpFoldResult to a Value.
Operation * clone(OpBuilder &b, Operation *op, TypeRange newResultTypes, ValueRange newOperands)
std::optional< SCFFuseProducerOfSliceResult > tileAndFuseProducerOfSlice(RewriterBase &rewriter, tensor::ExtractSliceOp candidateSliceOp, MutableArrayRef< LoopLikeOpInterface > loops, const InnerTileAlignmentFnTy &fn=nullptr)
Fuse the producer of the source of candidateSliceOp by computing the required slice of the producer i...
OpFoldResult getAsOpFoldResult(Value val)
Given a value, try to extract a constant Attribute.
void applyPermutationToVector(SmallVector< T, N > &inVec, ArrayRef< int64_t > permutation)
Apply the permutation defined by permutation to inVec.
bool isPermutationVector(ArrayRef< int64_t > interchange)
Method to check if an interchange vector is a permutation.
InnerTileAlignment
Per-dimension alignment of a loop tile size to a linalg.pack / linalg.unpack inner tile size,...
bool isOneInteger(OpFoldResult v)
Return true if v is an IntegerAttr with value 1.
FailureOr< SCFTilingResult > tileUsingSCF(RewriterBase &rewriter, TilingInterface op, const SCFTilingOptions &options)
Method to tile an op that implements the TilingInterface using scf.for for iterating over the tiles.
SetVector< Operation * > topologicalSort(const SetVector< Operation * > &toSort)
Sorts all operations in toSort topologically while also considering region semantics.
SmallVector< int64_t > invertPermutationVector(ArrayRef< int64_t > permutation)
Helper method to apply to inverse a permutation.
Represents a range (offset, size, and stride) where each element of the triple may be dynamic or stat...
Container for result values of tiling.