28#include "llvm/ADT/ScopeExit.h"
29#include "llvm/ADT/TypeSwitch.h"
30#include "llvm/Support/Debug.h"
33#define DEBUG_TYPE "tile-using-interface"
37scf::SCFTilingOptions &
39 assert(!tileSizeComputationFunction &&
"tile sizes already set");
40 auto tileSizes = llvm::to_vector(ts);
47scf::SCFTilingOptions &
49 assert(!numThreadsComputationFunction &&
"num tiles already set");
50 auto numThreads = llvm::to_vector(nt);
61 size_t iterationDomainSize) {
63 if (filledVector.size() < iterationDomainSize) {
64 auto range = llvm::seq<int64_t>(filledVector.size(), iterationDomainSize);
65 filledVector.append(range.begin(), range.end());
67 if (filledVector.size() > iterationDomainSize)
68 filledVector.resize(iterationDomainSize);
78 const scf::SCFTilingOptions &
options) {
80 if (
options.numThreadsComputationFunction &&
81 options.loopType != scf::SCFTilingOptions::LoopType::ForallOp) {
83 loc,
"number of threads can only by specified when loop type is "
84 "set to use `scf.forall`");
88 if (!
options.interchangeVector.empty()) {
91 loc,
"invalid interchange vector, not a permutation of the entire "
103 const scf::SCFTilingOptions &
options) {
106 size_t numLoops = iterationDomain.size();
109 if (
options.numThreadsComputationFunction) {
110 numThreads =
options.numThreadsComputationFunction(rewriter, op);
111 numThreads.resize(numLoops, zero);
114 if (
options.tileSizeComputationFunction) {
115 tileSizes =
options.tileSizeComputationFunction(rewriter, op);
116 tileSizes.resize(numLoops, zero);
117 return {tileSizes, numThreads};
129 tileSizes.resize(numLoops, zero);
130 for (
auto [
index, range, nt] :
131 llvm::enumerate(iterationDomain, numThreads)) {
136 rewriter, op.getLoc(), tileSizeExpr, {range.offset, range.size, nt});
138 tileSizes.resize(numLoops, zero);
139 return {tileSizes, numThreads};
146 assert(
options.tileSizeComputationFunction &&
147 "expected tile sizes to be specified");
148 tileSizes =
options.tileSizeComputationFunction(rewriter, op);
149 tileSizes.resize(numLoops, zero);
151 return {tileSizes, numThreads};
156 scf::SCFTilingOptions::LoopType loopType,
160 auto iterators = op.getLoopIteratorTypes();
161 assert(iterators.size() == givenTileSizes.size() &&
162 "expected as many tile size values as number of loops");
163 assert((numThreads.empty() || (numThreads.size() == iterators.size())) &&
164 "when specified, expected number of threads to use for each loop");
166 bool isParallelTiling =
false;
167 for (
auto [
index, iterator, givenTileSize] :
168 llvm::enumerate(iterators, givenTileSizes)) {
170 isParallelTiling |= iterator == utils::IteratorType::parallel;
173 if (loopType == scf::SCFTilingOptions::LoopType::ForallOp &&
177 if (!numThreads.empty()) {
178 if (std::optional<int64_t> constNumThreads =
180 if (constNumThreads.value() > 1 &&
181 iterator != utils::IteratorType::parallel) {
182 op.emitWarning() <<
"tiling is not thread safe at axis #" <<
index;
188 if (std::optional<int64_t> constTileSize =
190 if (constTileSize.value() > 0 &&
191 iterator != utils::IteratorType::parallel) {
192 op.emitWarning() <<
"tiling is not thread safe at axis #" <<
index;
199 if (isParallelTiling) {
200 return op->emitOpError(
"tiling parallel dimensions is not supported with "
201 "partial reduction tiling strategies");
211 const scf::SCFTilingOptions &
options) {
213 for (
auto dim :
options.reductionDims) {
216 reductionDims.insert(dim);
218 return reductionDims;
232 return ((sizeAsInt.value() - offsetAsInt.value()) % strideAsInt.value() == 0);
241 if (ts && ts.value() == 1)
242 return givenTileSize;
246 return givenTileSize;
268 if (!tileSizeConst || !numThreadsConst || !iterSizeConst)
270 return *tileSizeConst * (*numThreadsConst - 1) < *iterSizeConst;
281 int materializedLoopNum = 0;
282 for (
auto [givenTileSize, loopRange] :
283 llvm::zip_equal(givenTileSizes, iterationDomain)) {
288 offsets.push_back(loopRange.offset);
289 sizes.push_back(loopRange.size);
293 Value iv = ivs[materializedLoopNum++];
295 offsets.push_back(offset);
298 sizes.push_back(size);
300 return {offsets, sizes};
309 for (
auto [loopRange, givenTileSize] :
310 llvm::zip_equal(loopRanges, givenTileSizes)) {
314 lbs.push_back(loopRange.offset);
315 ubs.push_back(loopRange.size);
316 steps.push_back(givenTileSize);
318 return {lbs, ubs, steps};
372 if (newDestArgs.empty())
374 if (
auto destinationStyleOp = dyn_cast<DestinationStyleOpInterface>(clonedOp))
375 destinationStyleOp.getDpsInitsMutable().assign(newDestArgs);
392 assert(!loopRanges.empty() &&
"unexpected empty loop ranges");
393 assert(loopRanges.size() == givenTileSizes.size() &&
394 "expected as many tile sizes as loop ranges");
398 std::tie(lbs, ubs, steps) =
409 ValueRange innerDestinationTensors(outerDestinationTensors);
410 for (
auto [lb,
ub, step] : llvm::zip_equal(lbVals, ubVals, stepVals)) {
412 scf::ForOp::create(rewriter, loc, lb,
ub, step, innerDestinationTensors,
415 loops.push_back(loop);
416 ivs.push_back(loop.getInductionVar());
418 innerDestinationTensors = loop.getRegionIterArgs();
425 std::tie(offsets, sizes) =
430 if (failed(tiledBodyFn(rewriter, loc, ivs, offsets, sizes,
431 innerDestinationTensors, tiledResults, resultOffsets,
434 loc,
"failed to generate inner tile loop body");
439 assert(tiledResults.size() == innerDestinationTensors.size() &&
440 "Number of results of body should be equal to number of iter args");
444 for (
auto [tiledValue, destinationTensor, resultOffset, resultSize] :
445 llvm::zip_equal(tiledResults, innerDestinationTensors, resultOffsets,
449 auto insertSlice = tensor::InsertSliceOp::create(
450 rewriter, loc, tiledValue, destinationTensor, resultOffset, resultSize,
452 yieldedValues.push_back(insertSlice);
454 scf::YieldOp::create(rewriter, loc, yieldedValues);
457 for (
auto [outerLoop, innerLoop] :
461 cast<scf::ForOp>(outerLoop.getOperation()).getBody());
462 scf::YieldOp::create(rewriter, outerLoop.getLoc(), innerLoop->getResults());
479 if (numThreads.empty()) {
485 int materializedLoopNum = 0;
491 offsetExpr = d0 + d1 * s0;
492 residualTileSizeExpr = s1 - (d0 + d1 * s0);
494 for (
auto [
index, nt, givenTileSize, loopRange] :
495 llvm::enumerate(numThreads, givenTileSizes, iterationDomain)) {
500 offsets.push_back(loopRange.offset);
501 sizes.push_back(loopRange.size);
505 Value iv = ivs[materializedLoopNum++];
507 rewriter, loc, offsetExpr,
510 rewriter, loc, residualTileSizeExpr,
511 {loopRange.offset, nt, givenTileSize, loopRange.size});
517 {offset, loopRange.size});
521 {sizeMinusOffsetPerThread, givenTileSize});
537 rewriter, loc, maxMap, {rewriter.
getIndexAttr(0), size});
540 offsets.push_back(offset);
541 sizes.push_back(size);
543 return {offsets, sizes};
556static FailureOr<SmallVector<LoopLikeOpInterface>>
564 assert(!loopRanges.empty() &&
"unexpected empty loop ranges");
565 assert(loopRanges.size() == givenTileSizes.size() &&
566 "expected as many tile sizes as loop ranges");
569 std::optional<ArrayAttr> mappingAttr;
570 if (!mappingVector.empty())
573 scf::ForallOp forallOp;
574 bool useNumThreads = !numThreads.empty();
580 for (
auto nt : numThreads) {
583 nonZeroNumThreads.push_back(nt);
585 forallOp = scf::ForallOp::create(rewriter, loc, nonZeroNumThreads,
586 outerDestinationTensors, mappingAttr);
589 std::tie(lbs, ubs, steps) =
591 forallOp = scf::ForallOp::create(rewriter, loc, lbs, ubs, steps,
592 outerDestinationTensors, mappingAttr);
594 loops.push_back(forallOp);
597 ValueRange innerDestinationTensors = forallOp.getRegionOutArgs();
603 rewriter, loc, ivs, loopRanges, givenTileSizes, numThreads);
607 if (failed(tiledBodyFn(rewriter, loc, ivs, offsets, sizes,
608 innerDestinationTensors, tiledResults, resultOffsets,
613 for (
auto [tiledValue, destinationTensor, resultOffset, resultSize] :
614 llvm::zip_equal(tiledResults, innerDestinationTensors, resultOffsets,
619 tensor::ParallelInsertSliceOp::create(rewriter, loc, tiledValue,
620 destinationTensor, resultOffset,
621 resultSize, resultStride);
636static FailureOr<SmallVector<LoopLikeOpInterface>>
640 const scf::SCFTilingOptions::GenerateLoopHeaderFn &generateLoopHeaderFn,
641 const scf::SCFTilingOptions::GenerateLoopTerminatorFn
642 &generateLoopTerminatorFn,
644 assert(!loopRanges.empty() &&
"unexpected empty loop ranges");
645 assert(loopRanges.size() == givenTileSizes.size() &&
646 "expected as many tile sizes as loop ranges");
647 assert(generateLoopHeaderFn && generateLoopTerminatorFn &&
648 "expected loop header/terminator generation function");
651 FailureOr<scf::SCFTilingOptions::CustomLoopHeaderInfo> loopHeaderInfo =
652 generateLoopHeaderFn(rewriter, loc, loopRanges, givenTileSizes,
653 outerDestinationTensors);
654 if (failed(loopHeaderInfo)) {
661 if (failed(tiledBodyFn(rewriter, loc, ivs, loopHeaderInfo->tileOffset,
662 loopHeaderInfo->tileSizes,
663 loopHeaderInfo->destinationTensors, tiledResults,
664 resultOffsets, resultSizes))) {
668 if (failed(generateLoopTerminatorFn(rewriter, loc, loopHeaderInfo->loops,
669 tiledResults, resultOffsets, resultSizes,
670 loopHeaderInfo->destinationTensors))) {
674 return loopHeaderInfo->loops;
698 llvm::map_to_vector(loopRanges, [](
Range r) {
return r.
offset; });
700 llvm::map_to_vector(loopRanges, [](
Range r) {
return r.
size; });
701 if (failed(tiledBodyFn(rewriter, loc,
ValueRange{}, tileOffsets, tileSizes,
702 destinationTensors, tiledResults, resultOffsets,
708 if (
options.loopType == scf::SCFTilingOptions::LoopType::ForOp) {
710 destinationTensors, tiledBodyFn);
712 if (
options.loopType == scf::SCFTilingOptions::LoopType::ForallOp) {
714 rewriter, loc, loopRanges, givenTileSizes, numThreads,
715 options.mappingVector, destinationTensors, tiledBodyFn);
717 if (
options.loopType == scf::SCFTilingOptions::LoopType::CustomOp) {
719 rewriter, loc, loopRanges, givenTileSizes, destinationTensors,
739 auto redOp = dyn_cast<PartialReductionOpInterface>(op.getOperation());
741 return op->emitOpError(
742 "PartialReductionOuterReduction tiling strategy is only supported for "
743 "operations implementing PartialReductionOpInterface");
748 AffineExpr sizeExpr = ((s0 - s1).ceilDiv(s2));
750 for (
auto [
index, domain, tileSize] :
751 llvm::enumerate(iterationDomain, givenTileSizes)) {
752 if (!numThreads.empty()) {
756 rewriter, op.getLoc(), sizeExpr,
757 {domain.size, domain.offset, domain.stride});
767 rewriter, op.getLoc(), sizeExpr,
768 {domain.size, domain.offset, domain.stride});
772 if (reductionStrategy ==
774 sizes[
index] = tileSize;
778 assert(reductionStrategy ==
781 rewriter, op.getLoc(), sizeExpr,
782 {domain.size, domain.offset, domain.stride});
784 rewriter, op.getLoc(), divExpr, {normalizedRange, tileSize});
786 return redOp.generateInitialTensorForPartialReduction(rewriter, loc, sizes,
800 splitReductionIvs.resize(reductionDims.size(), rewriter.
getIndexAttr(0));
805 if (reductionStrategy ==
807 for (
auto [
index, reductionDim] : llvm::enumerate(reductionDims)) {
808 if (!numThreads.empty()) {
809 splitReductionIvs[
index] = ivs[ivIndex++];
813 rewriter, loc, divExpr,
817 return splitReductionIvs;
820static FailureOr<TilingResult>
830 return op.getTiledImplementation(rewriter, offsets, sizes,
831 innerTileAlignments);
834 auto redOp = dyn_cast<PartialReductionOpInterface>(op.getOperation());
837 op,
"PartialReductionOuterReduction tiling strategy is only "
838 "supported for operations "
839 "implementing PartialReductionOpInterface");
844 numThreads, givenTileSizes, reductionDims);
845 return redOp.tileToPartialReduction(rewriter, op.getLoc(), reductionStrategy,
846 regionIterArg, offsets, sizes,
847 reductionDims, splitReductionIvs);
861 return op.getResultTilePosition(rewriter,
index, offsets, sizes,
862 resultOffset, resultSize);
864 auto redOp = dyn_cast<PartialReductionOpInterface>(op.getOperation());
867 op,
"PartialReductionOuterReduction tiling strategy is only supported"
868 "for operations implementing PartialReductionOpInterface");
872 numThreads, givenTileSizes, reductionDims);
873 return redOp.getPartialResultTilePosition(
874 rewriter,
index, reductionStrategy, offsets, sizes, reductionDims,
875 splitReductionIvs, resultOffset, resultSize);
878static FailureOr<MergeResult>
884 "expected merge to be called for only partial reduction cases");
886 auto redOp = dyn_cast<PartialReductionOpInterface>(op.getOperation());
889 op,
"PartialReductionOuterReduction tiling strategy is only "
890 "supported for operations "
891 "implementing PartialReductionOpInterface");
893 return redOp.mergeReductions(rewriter, op.getLoc(), partialResults,
904template <
typename LoopType>
905static FailureOr<LoopLikeOpInterface>
921 auto inits = llvm::to_vector(loopOp.getInitArgs());
922 inits.append(newInitOperands.begin(), newInitOperands.end());
923 auto newLoop = scf::ForOp::create(
924 rewriter, loc, loopOp.getLowerBound(), loopOp.getUpperBound(),
926 loopOp.getUnsignedCmp());
929 Block *loopBody = loopOp.getBody();
930 Block *newLoopBody = newLoop.getBody();
932 loopBody, newLoopBody,
935 auto yieldOp = cast<scf::YieldOp>(newLoopBody->
getTerminator());
941 newLoop.getRegionIterArgs().take_back(newInitOperands.size());
942 if (
failed(yieldTiledValuesFn(rewriter, loc, newLoop.getInductionVar(),
943 newRegionIterArgs, tiledValues, resultOffsets,
950 for (
auto [tiledValue, regionIterArg, resultOffset, resultSize] :
951 llvm::zip_equal(tiledValues, newRegionIterArgs, resultOffsets,
955 Value insert = tensor::InsertSliceOp::create(
956 rewriter, yieldOp->getLoc(), tiledValue, regionIterArg, resultOffset,
957 resultSize, resultStride);
958 newYieldValues.push_back(insert);
963 newLoop->getResults().take_front(loopOp.getNumResults()));
964 return cast<LoopLikeOpInterface>(newLoop.getOperation());
975 auto inits = llvm::to_vector(loopOp.getOutputs());
976 inits.append(newInitOperands.begin(), newInitOperands.end());
977 auto newLoop = scf::ForallOp::create(
978 rewriter, loc, loopOp.getMixedLowerBound(), loopOp.getMixedUpperBound(),
979 loopOp.getMixedStep(), inits, loopOp.getMapping(),
983 Block *loopBody = loopOp.getBody();
984 Block *newLoopBody = newLoop.getBody();
986 loopBody, newLoopBody,
989 auto terminator = cast<scf::InParallelOp>(newLoopBody->
getTerminator());
994 newLoop.getRegionIterArgs().take_back(newInitOperands.size());
995 if (
failed(yieldTiledValuesFn(rewriter, loc, newLoop.getInductionVars(),
996 regionIterArgs, tiledValues, resultOffsets,
1000 "failed to get yielded tiled values");
1006 for (
auto [tiledValue, iterArg, resultOffset, resultSize] : llvm::zip_equal(
1007 tiledValues, regionIterArgs, resultOffsets, resultSizes)) {
1010 tensor::ParallelInsertSliceOp::create(rewriter, terminator.getLoc(),
1011 tiledValue, iterArg, resultOffset,
1012 resultSize, resultStride);
1016 newLoop->getResults().take_front(loopOp.getNumResults()));
1017 return cast<LoopLikeOpInterface>(newLoop.getOperation());
1024 LoopLikeOpInterface loopLikeOp,
RewriterBase &rewriter,
1027 loopLikeOp.getOperation())
1028 .Case<scf::ForOp, scf::ForallOp>(
1029 [&](
auto loopOp) -> FailureOr<LoopLikeOpInterface> {
1031 loopOp, rewriter, newInitOperands, yieldTiledValuesFn);
1033 .Default([&](
auto loopOp) -> FailureOr<LoopLikeOpInterface> {
1052 for (
auto &loop : loops.drop_back()) {
1056 auto forLoop = cast<scf::ForOp>(loop.getOperation());
1060 newInits.append(newInitValues.begin(), newInitValues.end());
1061 auto newLoop = scf::ForOp::create(
1062 rewriter, forLoop.getLoc(), forLoop.getLowerBound(),
1063 forLoop.getUpperBound(), forLoop.getStep(), newInits,
1065 forLoop.getUnsignedCmp());
1069 sourceBlockArgs.push_back(newLoop.getInductionVar());
1070 auto newRegionIterArgs = newLoop.getRegionIterArgs();
1071 sourceBlockArgs.append(
1072 newRegionIterArgs.begin(),
1073 std::next(newRegionIterArgs.begin(), forLoop.getNumResults()));
1074 rewriter.
mergeBlocks(forLoop.getBody(), newLoop.getBody(), sourceBlockArgs);
1076 forLoop, newLoop.getResults().take_front(forLoop.getNumResults()));
1078 ivs.push_back(newLoop.getInductionVar());
1079 newInitValues = newLoop.getRegionIterArgs().take_back(newInitValues.size());
1083 LoopLikeOpInterface innerMostLoop = loops.back();
1084 FailureOr<LoopLikeOpInterface> newInnerMostLoop =
1086 getNewTiledYieldsFn);
1088 if (failed(newInnerMostLoop))
1089 return innerMostLoop.emitOpError(
"failed to return additional yields");
1090 loops.back() = newInnerMostLoop.value();
1094 for (
auto [outerLoop, innerLoop] :
1095 llvm::zip_equal(loops.drop_back(), loops.drop_front())) {
1097 auto outerForLoop = cast<scf::ForOp>(outerLoop.getOperation());
1098 auto outerLoopYield =
1099 cast<scf::YieldOp>(outerForLoop.getBody()->getTerminator());
1101 llvm::to_vector(outerLoopYield.getOperands());
1103 innerLoop->getResults().take_back(newInitValues.size());
1104 newYields.append(additionalYields.begin(), additionalYields.end());
1113FailureOr<scf::SCFTilingResult>
1114mlir::scf::tileUsingSCF(
RewriterBase &rewriter, TilingInterface op,
1115 const scf::SCFTilingOptions &
options) {
1128 std::tie(givenTileSizes, numThreads) =
1134 givenTileSizes, numThreads))) {
1145 if (!
options.interchangeVector.empty()) {
1147 iterationDomain.size());
1149 "expected interchange vector to be a permutation");
1153 if (!numThreads.empty())
1157 FailureOr<TilingResult> tilingResult;
1171 if (!interchangeVector.empty()) {
1180 auto clonedOp = cast<TilingInterface>(
1187 tiledResults.append(clonedOp->result_begin(), clonedOp->result_end());
1197 ?
options.innerTileAlignmentFn(clonedOp, givenTileSizes,
1201 rewriter, clonedOp,
options.reductionStrategy, regionIterArgs,
1202 tileOffsetsVec, tileSizesVec, ivs, numThreads, givenTileSizes,
1203 innerTileAlignments, reductionDims);
1204 if (
failed(tilingResult)) {
1206 return op.emitOpError(
"failed to tile operation");
1214 for (
auto [
index, tiledValue] :
1215 llvm::enumerate(tilingResult->tiledValues)) {
1216 tiledResults.push_back(tiledValue);
1219 rewriter,
options.reductionStrategy,
index, tiledValue, op,
1220 tileOffsetsVec, tileSizesVec, ivs, numThreads, givenTileSizes,
1221 reductionDims, resultOffset, resultSize))) {
1222 for (
auto op : tilingResult->tiledOps) {
1226 op,
"failed to get slice of result produced");
1228 resultOffsets.emplace_back(std::move(resultOffset));
1229 resultSizes.emplace_back(std::move(resultSize));
1237 rewriter, op,
options.reductionStrategy, iterationDomain, numThreads,
1238 givenTileSizes, reductionDims);
1239 if (
failed(maybeInits)) {
1241 op,
"unable to create initial tensors for tiling");
1249 rewriter, op.getLoc(),
options, iterationDomain, givenTileSizes,
1250 numThreads, initTensors, innerYieldTiledValuesFn);
1252 return op.emitOpError(
"failed to generate tiling loops");
1253 assert(succeeded(tilingResult) &&
1254 "expected tiling result to be computed after loop generation");
1255 std::swap(loops, loopsOr.value());
1258 if (loops.empty()) {
1261 return scf::SCFTilingResult{tilingResult->tiledOps,
1264 tilingResult->tiledValues,
1265 tilingResult->generatedSlices,
1269 auto loopResults = llvm::map_to_vector(loops.front()->getResults(),
1274 return scf::SCFTilingResult{
1275 tilingResult->tiledOps, initTensors, loops, loopResults,
1276 tilingResult->generatedSlices, {}};
1281 rewriter, op,
options.reductionStrategy, reductionDims, loopResults);
1282 if (
failed(mergeResult)) {
1284 op,
"Failed to merge partial results from tiling");
1286 return scf::SCFTilingResult{tilingResult->tiledOps,
1289 mergeResult->replacements,
1290 tilingResult->generatedSlices,
1291 mergeResult->mergeOps};
1294FailureOr<scf::SCFTilingResult>
1296 PartialReductionOpInterface op,
1298 scf::SCFTilingOptions
options;
1299 options.setLoopType(scf::SCFTilingOptions::LoopType::ForOp);
1300 options.setReductionTilingStrategy(
1302 options.setTileSizes(tileSize);
1304 for (
auto [
index, iteratorType] : llvm::enumerate(op.getLoopIteratorTypes()))
1305 if (iteratorType == utils::IteratorType::reduction)
1306 reductionDims.push_back(
index);
1307 options.setReductionDims(reductionDims);
1321static std::tuple<OpResult, std::optional<OpOperand *>>
1324 std::optional<OpOperand *> destinationIterArg;
1325 assert(!loops.empty() &&
"expected non empty loops container");
1326 auto loopIt = loops.rbegin();
1327 while (loopIt != loops.rend() && isa<BlockArgument>(source->
get())) {
1328 auto iterArg = cast<BlockArgument>(source->
get());
1329 auto loop = *loopIt;
1330 if (iterArg.getOwner()->getParentOp() != loop)
1332 source = loop.getTiedLoopInit(iterArg);
1335 if (loopIt == loops.rend())
1336 destinationIterArg = source;
1338 auto result = dyn_cast<OpResult>(source->
get());
1341 Operation *innermostLoop = loops.back();
1348 return {
result, destinationIterArg};
1353std::optional<scf::SCFFuseProducerOfSliceResult>
1354mlir::scf::tileAndFuseProducerOfSlice(
1355 RewriterBase &rewriter, tensor::ExtractSliceOp candidateSliceOp,
1357 const InnerTileAlignmentFnTy &fn) {
1360 auto [fusableProducer, destinationInitArg] =
1363 if (!fusableProducer)
1364 return std::nullopt;
1365 unsigned resultNumber = fusableProducer.getResultNumber();
1371 if (
auto producer = dyn_cast<TilingInterface>(fusableProducer.getOwner()))
1372 innerTileAlignments =
1373 fn(producer, {}, {candidateSliceOp.getOperation()});
1381 Operation *fusableProducerOp = fusableProducer.getOwner();
1382 if (isa<DestinationStyleOpInterface>(fusableProducerOp) &&
1384 rewriter, fusableProducerOp->
getLoc(), fusableProducerOp,
1385 origDestinationTensors)))
1386 return std::nullopt;
1388 clonedOpDestinationTensors = origDestinationTensors;
1389 if (destinationInitArg &&
1390 isa<DestinationStyleOpInterface>(fusableProducerOp)) {
1394 clonedOpDestinationTensors[resultNumber] = candidateSliceOp.getSource();
1398 rewriter, fusableProducerOp, clonedOpDestinationTensors);
1403 llvm::to_vector(candidateSliceOp->getOperands());
1404 candidateSliceOpOperands[0] = clonedProducerOp->
getResult(resultNumber);
1405 tensor::ExtractSliceOp clonedCandidateSliceOp =
1407 candidateSliceOp->getResultTypes(), candidateSliceOpOperands);
1410 FailureOr<TilingResult> tileAndFuseResult =
1412 rewriter, clonedCandidateSliceOp,
1413 clonedProducerOp->
getResult(resultNumber), innerTileAlignments);
1414 if (failed(tileAndFuseResult))
1415 return std::nullopt;
1419 tileAndFuseResult->tiledValues[0]);
1420 rewriter.
eraseOp(clonedCandidateSliceOp);
1421 rewriter.
eraseOp(clonedProducerOp);
1466 if (destinationInitArg &&
1467 isa<DestinationStyleOpInterface>(fusableProducerOp) && !loops.empty()) {
1469 ->getOpOperands()[destinationInitArg.value()->getOperandNumber()]
1470 .set(origDestinationTensors[resultNumber]);
1472 return scf::SCFFuseProducerOfSliceResult{
1473 fusableProducer, tileAndFuseResult->tiledValues[0],
1474 tileAndFuseResult->tiledOps, tileAndFuseResult->generatedSlices};
1478FailureOr<SmallVector<Operation *>> mlir::scf::yieldReplacementForFusedProducer(
1479 RewriterBase &rewriter, tensor::ExtractSliceOp sliceOp,
1480 scf::SCFFuseProducerOfSliceResult fusedProducerInfo,
1486 Operation *originalOwner = fusedProducerInfo.origProducer.getOwner(),
1487 *tiledOwner = fusedProducerInfo.tiledOps[0];
1492 yieldResultNumber.empty() ? llvm::to_vector(llvm::seq<unsigned>(
1494 : llvm::to_vector(yieldResultNumber);
1496 for (
const auto &resultNumber : initNumberList) {
1498 rewriter, loc, originalOwner->
getResult(resultNumber));
1499 if (succeeded(initValue)) {
1500 initValueList.push_back(initValue.value());
1516 sliceSizes = sliceOp.getMixedSizes();
1519 if (!llvm::all_of(sliceOp.getMixedStrides(),
isOneInteger))
1522 unsigned sliceResultNumber =
1523 fusedProducerInfo.origProducer.getResultNumber();
1525 auto tilableOp = cast<TilingInterface>(originalOwner);
1532 if (
auto tiledDestStyleOp =
1533 dyn_cast<DestinationStyleOpInterface>(tiledOwner)) {
1537 if (tilableOp->getNumResults() > 1 &&
1538 failed(tilableOp.getIterationDomainTileFromResultTile(
1539 rewriter, sliceResultNumber, sliceOffset, sliceSizes,
1540 iterDomainOffset, iterDomainSizes))) {
1555 for (
const auto &resultNumber : initNumberList) {
1556 if (resultNumber == sliceResultNumber) {
1557 offsetList.push_back(sliceOffset);
1558 sizesList.push_back(sliceSizes);
1560 assert(!iterDomainOffset.empty() && !iterDomainSizes.empty());
1563 if (failed(tilableOp.getResultTilePosition(
1564 rewriter, resultNumber, iterDomainOffset, iterDomainSizes,
1568 offsetList.push_back(offset);
1569 sizesList.push_back(sizes);
1575 if (
auto tiledDestStyleOp =
1576 dyn_cast<DestinationStyleOpInterface>(tiledOwner)) {
1577 for (
const auto &&[
index, newRegionArg] :
1578 llvm::enumerate(newRegionIterArgs)) {
1579 auto destSlice = tensor::ExtractSliceOp::create(
1580 rewriter, loc, newRegionArg, offsetList[
index], sizesList[
index],
1583 generatedSlices.push_back(destSlice);
1584 unsigned resultNumber = initNumberList[
index];
1586 tiledDestStyleOp.getDpsInitsMutable()[resultNumber].set(destSlice);
1595 for (
const auto &&[
index, resultNumber] : llvm::enumerate(initNumberList)) {
1596 tiledResult.push_back(tiledOwner->getResult(resultNumber));
1597 tiledOffset.emplace_back(offsetList[
index]);
1598 tiledSizes.emplace_back(sizesList[
index]);
1604 newYieldValuesFn))) {
1607 return generatedSlices;
1621 explicit SliceTrackingListener(
1622 std::optional<FrozenRewritePatternSet> patterns);
1623 SliceTrackingListener() =
default;
1632 void notifyOperationInserted(
Operation *op,
1639 void notifyOperationErased(
Operation *op)
override;
1646 std::deque<tensor::ExtractSliceOp> worklist;
1651 std::optional<FrozenRewritePatternSet> patterns = std::nullopt;
1654SliceTrackingListener::SliceTrackingListener(
1655 std::optional<FrozenRewritePatternSet> p) {
1656 patterns = std::move(p);
1660SliceTrackingListener::insertAndApplyPatterns(ArrayRef<Operation *> ops) {
1661 for (Operation *op : ops) {
1662 if (
auto slice = dyn_cast<tensor::ExtractSliceOp>(op))
1663 worklist.push_back(slice);
1670 ops, patterns.value(),
1671 GreedyRewriteConfig().setListener(
this).setStrictness(
1672 GreedyRewriteStrictness::ExistingAndNewOps));
1675void SliceTrackingListener::notifyOperationInserted(
1676 Operation *op, OpBuilder::InsertPoint previous) {
1677 auto slice = dyn_cast<tensor::ExtractSliceOp>(op);
1680 worklist.push_back(slice);
1686void SliceTrackingListener::removeOp(Operation *op) {
1687 if (!isa<tensor::ExtractSliceOp>(op))
1689 auto iter = worklist.begin();
1690 while (iter != worklist.end()) {
1695 if (iter == worklist.end())
1698 worklist.erase(iter);
1701void SliceTrackingListener::notifyOperationErased(Operation *op) {
1705void SliceTrackingListener::notifyOperationReplaced(Operation *op,
1717class ReplacementListener :
public RewriterBase::ForwardingListener {
1719 ReplacementListener(DenseMap<Value, Value> &replacements,
1720 OpBuilder::Listener *listener)
1721 : ForwardingListener(listener), replacements(replacements) {}
1723 void updateReplacementValues(
ValueRange origValues,
1727 for (
auto &[key, val] : replacements) {
1728 for (
auto [orig, replace] : llvm::zip_equal(origValues, replaceValues)) {
1736 void notifyOperationReplaced(Operation *op, Operation *newOp)
override {
1737 ForwardingListener::notifyOperationReplaced(op, newOp);
1741 void notifyOperationReplaced(Operation *op,
ValueRange values)
override {
1742 ForwardingListener::notifyOperationReplaced(op, values);
1743 updateReplacementValues(op->
getResults(), values);
1747 DenseMap<Value, Value> &replacements;
1753FailureOr<scf::SCFTileAndFuseResult>
1754mlir::scf::tileConsumerAndFuseProducersUsingSCF(
1755 RewriterBase &rewriter, TilingInterface consumer,
1756 const scf::SCFTileAndFuseOptions &
options) {
1759 if (!consumer->getNumResults()) {
1761 consumer,
"invalid pattern for op with no results");
1767 FailureOr<scf::SCFTilingResult> tilingResult =
1770 if (
failed(tilingResult))
1772 tiledAndFusedOps.insert_range(tilingResult->tiledOps);
1774 DenseMap<Value, Value> replacements;
1776 llvm::zip_equal(consumer->getResults(), tilingResult->replacements)) {
1781 auto &loops = tilingResult->loops;
1782 if (loops.empty()) {
1783 return scf::SCFTileAndFuseResult{fusedProducers, tiledAndFusedOps, loops,
1790 OpBuilder::Listener *previousListener = rewriter.
getListener();
1791 llvm::scope_exit resetListener(
1792 [&]() { rewriter.
setListener(previousListener); });
1793 ReplacementListener replaceListener(replacements, previousListener);
1803 struct WorklistItem {
1804 tensor::ExtractSliceOp candidateSlice;
1805 SCFTileAndFuseOptions::ControlFnResult controlFnResult;
1808 SliceTrackingListener sliceTracker =
1809 SliceTrackingListener(
options.cleanupPatterns);
1812 sliceTracker.insertAndApplyPatterns(tilingResult->generatedSlices))) {
1815 OpBuilder::InsertionGuard g(rewriter);
1816 while (!sliceTracker.worklist.empty()) {
1817 auto candidateSlice = sliceTracker.worklist.front();
1818 sliceTracker.worklist.pop_front();
1820 auto [fusableProducer, destinationInitArg] =
1823 if (!fusableProducer)
1826 std::optional<SCFTileAndFuseOptions::ControlFnResult> controlFnResult =
1827 options.fusionControlFn(candidateSlice, fusableProducer,
1828 destinationInitArg.has_value());
1829 if (!controlFnResult)
1832 WorklistItem worklistItem = {candidateSlice, controlFnResult.value()};
1837 std::optional<scf::SCFFuseProducerOfSliceResult> fusedResult =
1839 options.tilingOptions.innerTileAlignmentFn);
1843 SmallVector<Operation *> worklistCandidates = fusedResult->generatedSlices;
1845 if (worklistItem.controlFnResult.yieldProducerReplacement) {
1850 Operation *fusableProducerOp = fusedResult->origProducer.getOwner();
1851 FailureOr<SmallVector<Operation *>> newSlices =
1853 worklistItem.candidateSlice,
1854 fusedResult.value(), loops);
1857 fusableProducerOp,
"failed to replacement value for this "
1858 "operation from within the tiled loop");
1860 worklistCandidates.append(newSlices.value());
1861 for (
auto [index,
result] :
1862 llvm::enumerate(fusableProducerOp->
getResults())) {
1863 replacements[
result] = loops.front()->getResult(
1864 loops.front()->getNumResults() -
1868 if (Operation *tiledAndFusedOp =
1869 fusedResult->tiledAndFusedProducer.getDefiningOp()) {
1870 fusedProducers.insert(fusedResult->origProducer.getDefiningOp());
1871 tiledAndFusedOps.insert(tiledAndFusedOp);
1874 if (
failed(sliceTracker.insertAndApplyPatterns(worklistCandidates))) {
1879 return scf::SCFTileAndFuseResult{fusedProducers, tiledAndFusedOps, loops,
1893 if (!llvm::hasSingleElement(uses)) {
1894 LLVM_DEBUG(llvm::dbgs() <<
"Too many uses of the candidate slice op\n");
1897 OpOperand &operandUse = (*uses.begin());
1899 if (!isa<scf::YieldOp>(userOp)) {
1900 LLVM_DEBUG(llvm::dbgs()
1901 <<
"Expected scf.yield to be the only user, but got -> "
1906 LLVM_DEBUG(llvm::dbgs() <<
"Expected tensor.insert_slice and scf.yield to "
1907 "be in the same block\n");
1916 if (!isa<LoopLikeOpInterface>(loopOp))
1935 if (isa<tensor::ParallelInsertSliceOp>(userOp))
1936 userOp = userOp->getParentOfType<scf::InParallelOp>();
1938 if (loopOp->
getBlock() != userOp->getBlock())
1942 firstUserOfLoop = userOp;
1944 return firstUserOfLoop;
1985static FailureOr<llvm::SetVector<Operation *>>
1987 bool reorderOperations) {
1989 if (failed(firstUserOfLoop))
1995 options.omitBlockArguments =
true;
1996 bool includeLoopOp =
false;
1999 includeLoopOp =
true;
2009 assert(
result.succeeded() &&
"expected a backward slice");
2013 if (!slice.empty()) {
2023 if (includeLoopOp || !reorderOperations)
2035 unsigned resultNumber) {
2036 if (!isa<LoopLikeOpInterface>(loopOp))
2041 Operation *consumerOp = opOperand.getOwner();
2043 if (!isa<TilingInterface>(consumerOp) ||
2044 !isa<DestinationStyleOpInterface>(consumerOp)) {
2051 if (loopBlock != consumerOp->
getBlock())
2058 FailureOr<llvm::SetVector<Operation *>> slice =
2064 if (!slice->empty()) {
2067 assert(succeeded(firstUserOfLoop) &&
"First user of loop is not found");
2068 for (
auto op : *slice) {
2083static FailureOr<OpOperand *>
2085 tensor::InsertSliceOp candidateSliceOp,
2087 assert(!loops.empty() &&
"unexpected loops to be empty");
2090 if (containingOp != loops.back()) {
2093 "expected slice to be within body of inner-most loop");
2099 candidateSliceOp,
"expected passed loops to be perfectly nested.");
2104 Value sliceResult = candidateSliceOp.getResult();
2110 scf::ForOp topLevelForOp = cast<scf::ForOp>(loops.front().getOperation());
2117static FailureOr<OpOperand *>
2119 tensor::ParallelInsertSliceOp candidateSliceOp,
2121 assert(!loops.empty() &&
"unexpected loops to be empty");
2123 if (loops.size() != 1) {
2125 candidateSliceOp,
"expected single surrounding scf.forall");
2127 auto forallOp = dyn_cast<scf::ForallOp>(loops.front().getOperation());
2130 candidateSliceOp,
"expected single surrounding scf.forall");
2134 Value sliceDest = candidateSliceOp.getDest();
2135 auto iterArg = dyn_cast<BlockArgument>(sliceDest);
2138 if (iterArg.getOwner()->getParentOp() != forallOp)
2141 unsigned resultNumber =
2142 forallOp.getTiedOpResult(forallOp.getTiedOpOperand(iterArg))
2153 assert(!loops.empty() &&
"unexpected empty loops");
2154 assert(!sliceOps.empty() &&
"unexpected empty list of candidate slices");
2156 for (
auto sliceOp : sliceOps) {
2157 FailureOr<OpOperand *> fusedOperand =
2159 .Case<tensor::InsertSliceOp, tensor::ParallelInsertSliceOp>(
2166 if (failed(fusedOperand)) {
2169 if (!fusedOperands.empty() &&
2170 fusedOperand.value()->getOwner() != fusedOperands.front()->getOwner()) {
2172 fusedOperand.value()->getOwner(),
2173 "all candidate slices must be to the same consumer");
2175 fusedOperands.push_back(fusedOperand.value());
2177 return fusedOperands;
2180template <
typename InsertSliceOpTy>
2182 InsertSliceOpTy sliceOp);
2185tensor::InsertSliceOp
2187 tensor::InsertSliceOp insertSliceOp) {
2188 return cast<tensor::InsertSliceOp>(
2189 rewriter.
clone(*insertSliceOp.getOperation()));
2194 RewriterBase &rewriter, tensor::ParallelInsertSliceOp insertSliceOp) {
2195 return tensor::InsertSliceOp::create(
2196 rewriter, insertSliceOp->getLoc(), insertSliceOp.getSource(),
2197 insertSliceOp.getDest(), insertSliceOp.getMixedOffsets(),
2198 insertSliceOp.getMixedSizes(), insertSliceOp.getMixedStrides());
2201static SmallVector<tensor::InsertSliceOp>
2204 assert(!candidateSlices.empty() &&
2205 "unexpected empty list of slices to clone");
2207 for (
auto sliceOp : candidateSlices) {
2209 .Case<tensor::InsertSliceOp, tensor::ParallelInsertSliceOp>(
2212 clonedSlices.push_back(clonedOp);
2215 .DefaultUnreachable(
2216 "unexpected slice type while cloning as insert slice");
2218 return clonedSlices;
2221static FailureOr<scf::SCFFuseConsumerOfSliceResult>
2226 const mlir::scf::InnerTileAlignmentFnTy &fn) {
2227 assert(!loops.empty() &&
"expected loops to be not empty");
2233 if (
auto consumer = dyn_cast<TilingInterface>(consumerOp))
2234 innerTileAlignments = fn(consumer, {}, candidateSlices);
2239 loops.front(),
"the first user of loop should not dominate any define "
2240 "of consumer operand(s)");
2243 LoopLikeOpInterface outerMostLoop = loops.front();
2244 LoopLikeOpInterface innerMostLoop = loops.back();
2248 auto dstOp = dyn_cast<DestinationStyleOpInterface>(consumerOp);
2251 "consumer op is not DPS operation");
2252 if (llvm::any_of(consumerOpOperands, [&](
OpOperand *opOperand) {
2253 return dstOp.isDpsInit(opOperand);
2257 "consumer op taking the result of scf.for as init is not supported");
2264 if (failed(firstUserOfLoop)) {
2266 outerMostLoop,
"could not find the first user of outer most loop");
2268 rewriter.
moveOpBefore(outerMostLoop, *firstUserOfLoop);
2275 dyn_cast<tensor::ParallelInsertSliceOp>(candidateSlices.front())) {
2276 auto newForallOp = cast<scf::ForallOp>(innerMostLoop.getOperation());
2286 auto clonedConsumerOp = cast<TilingInterface>(rewriter.
clone(*consumerOp));
2288 llvm::map_to_vector(consumerOpOperands, [](
OpOperand *opOperand) {
2292 llvm::map_to_vector(operandNumbers, [&](
unsigned operandNum) {
2293 return &clonedConsumerOp->getOpOperand(operandNum);
2299 for (
auto [operandToReplace, clonedSliceOp] :
2300 llvm::zip_equal(clonedOpFusedOperandsList, clonedInsertSlices)) {
2301 operandToReplace->set(clonedSliceOp.getResult());
2307 FailureOr<TilingResult> tileAndFuseResult =
2309 clonedOpFusedOperandsList,
2310 innerTileAlignments);
2311 if (failed(tileAndFuseResult)) {
2315 auto tiledConsumerOp = cast<TilingInterface>(tileAndFuseResult->tiledOps[0]);
2316 for (
auto [operandNum, clonedSliceOp] :
2317 llvm::zip_equal(operandNumbers, clonedInsertSlices)) {
2319 clonedSliceOp.getSource());
2333 for (
auto candidateSliceOp : clonedInsertSlices) {
2341 candidateSliceOp,
"containingOp's result yield with stride");
2344 allOffsets.emplace_back(std::move(offsets));
2345 allSizes.emplace_back(std::move(sizes));
2355 if (failed(clonedConsumerOp.getIterationDomainTileFromOperandTiles(
2356 rewriter, operandNumbers, allOffsets, allSizes, iterDomainOffsets,
2357 iterDomainSizes, innerTileAlignments))) {
2360 "can't get iter domain position from input position");
2366 unsigned totalNumResultsOfConsumer = tiledConsumerOp->getNumResults();
2368 totalNumResultsOfConsumer);
2370 totalNumResultsOfConsumer);
2371 for (
auto [idx, v] : llvm::enumerate(tiledConsumerOp->getResults())) {
2372 if (failed(tiledConsumerOp.getResultTilePosition(
2373 rewriter, idx, iterDomainOffsets, iterDomainSizes,
2374 resultOffsets[idx], resultSizes[idx]))) {
2377 "can't get result domain position from iter domain position");
2383 if (
auto tiledDestStyleOp = dyn_cast<DestinationStyleOpInterface>(
2384 tiledConsumerOp.getOperation())) {
2386 for (
const auto &&[
index, newRegionArg] :
2387 llvm::enumerate(newRegionIterArgs)) {
2388 auto destSlice = tensor::ExtractSliceOp::create(
2389 rewriter, loc, newRegionArg, resultOffsets[
index],
2395 auto dstNumber =
index;
2397 tiledDestStyleOp.getDpsInitsMutable()[dstNumber].set(destSlice);
2407 llvm::enumerate(tiledConsumerOp->getResults())) {
2408 tiledResult.push_back(
result);
2409 tiledOffset.emplace_back(resultOffsets[
index]);
2410 tiledSizes.emplace_back(resultSizes[
index]);
2416 newYieldValuesFn))) {
2418 "unable to add new inits to nest loop");
2424 for (
auto &&[oldResult, newResult] :
2426 loops.front()->getResults().take_back(newInits.size()))) {
2431 rewriter.
eraseOp(clonedConsumerOp);
2434 llvm::map_to_vector(operandNumbers, [&](
unsigned operandNum) {
2435 return &tileAndFuseResult->tiledOps[0]->getOpOperand(operandNum);
2437 auto consumerOpOperandsVec = llvm::to_vector(consumerOpOperands);
2438 return scf::SCFFuseConsumerOfSliceResult{
2439 std::move(consumerOpOperandsVec), std::move(tiledAndFusedOpOperands),
2440 std::move(tileAndFuseResult->tiledOps)};
2445FailureOr<scf::SCFFuseConsumerOfSliceResult>
2446mlir::scf::tileAndFuseConsumerOfSlices(
2449 const InnerTileAlignmentFnTy &fn) {
2450 if (candidateSlices.empty()) {
2453 "no candidate slices provided for consumer fusion");
2457 if (loops.empty()) {
2459 candidateSlices.front(),
2460 "cannot call tile and fuse consumer with an empty loop nest");
2463 if (!(llvm::all_of(candidateSlices, llvm::IsaPred<tensor::InsertSliceOp>) ||
2464 llvm::all_of(candidateSlices,
2465 llvm::IsaPred<tensor::ParallelInsertSliceOp>))) {
2467 candidateSlices.front(),
2468 "candidates slices need to be all `tensor.extract_slice`s or "
2469 "`tensor.parallel_insert_slice`s");
2474 FailureOr<SmallVector<OpOperand *>> maybeConsumerOpOperands =
2476 if (failed(maybeConsumerOpOperands)) {
2478 "could not fetch consumer to fuse");
2480 Operation *consumerOp = maybeConsumerOpOperands->front()->getOwner();
2483 maybeConsumerOpOperands.value(),
2484 candidateSlices, loops, fn);
2490static std::optional<Operation *>
2492 if (
result.getOwner() != forallOp)
2493 return std::nullopt;
2498 if (combiningOps.size() != 1)
2499 return std::nullopt;
2500 return combiningOps[0];
2505static std::optional<Operation *>
2508 assert(!loops.empty() &&
"Expected loops to be not empty");
2509 LoopLikeOpInterface outerMostLoop = loops.front();
2510 if (
auto forallOp = dyn_cast<scf::ForallOp>(outerMostLoop.getOperation())) {
2511 assert(loops.size() == 1 &&
2512 "expected only a single loop when tiling using scf.forall");
2518 while (loops.size() != 1) {
2519 LoopLikeOpInterface loop = loops.front();
2520 if (
result.getOwner() != loop)
2521 return std::nullopt;
2522 auto forOp = dyn_cast<scf::ForOp>(loop.getOperation());
2524 return std::nullopt;
2525 auto yieldOp = cast<scf::YieldOp>(forOp.getBody()->getTerminator());
2526 auto innerForResult =
2527 dyn_cast<OpResult>(yieldOp.getOperand(
result.getResultNumber()));
2528 if (!innerForResult)
2529 return std::nullopt;
2531 loops = loops.drop_front();
2533 LoopLikeOpInterface loop = loops.front();
2534 if (
result.getOwner() != loop)
2535 return std::nullopt;
2536 auto forOp = dyn_cast<scf::ForOp>(loop.getOperation());
2538 return std::nullopt;
2539 auto yieldOp = cast<scf::YieldOp>(forOp.getBody()->getTerminator());
2540 auto insertSliceOp = yieldOp.getOperand(
result.getResultNumber())
2541 .getDefiningOp<tensor::InsertSliceOp>();
2543 return std::nullopt;
2544 return insertSliceOp;
2547FailureOr<scf::SCFFuseConsumerOfSliceResult>
2550 const InnerTileAlignmentFnTy &fn) {
2551 if (!isa<TilingInterface>(consumer)) {
2553 consumer,
"unhandled consumer that does not implement TilingInterface");
2558 if (loops.empty()) {
2560 consumer,
"cannot call tile and fuse consumer with an empty loop nest");
2563 LoopLikeOpInterface outermostLoop = loops.front();
2569 if (opOperand.get().getDefiningOp() == outermostLoop) {
2570 consumerFusableOperands.push_back(&opOperand);
2575 if (consumerFusableOperands.empty()) {
2576 return mlir::scf::SCFFuseConsumerOfSliceResult{consumerFusableOperands,
2584 candidateSlices.reserve(consumerFusableOperands.size());
2585 for (
OpOperand *opOperand : consumerFusableOperands) {
2586 std::optional<Operation *> slice =
2591 "couldnt find producing insert-slice like operation for operand");
2593 candidateSlices.push_back(slice.value());
2597 rewriter, consumer, consumerFusableOperands, candidateSlices, loops, fn);
2604FailureOr<SmallVector<scf::ForOp>>
2606 TilingInterface op) {
2608 if (op->getNumResults() > 0) {
2610 op,
"unable to lower to loops operations with return values");
2617 for (
auto loopRange : domain) {
2624 auto loop = scf::ForOp::create(rewriter, op.getLoc(), offsetVal, sizeVal,
2626 loops.push_back(loop);
2627 ivs.push_back(loop.getInductionVar());
2630 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.