MLIR 24.0.0git
TileUsingInterface.cpp
Go to the documentation of this file.
1//===- Tiling.cpp - Implementation of tiling using TilingInterface -------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements the tiling using TilingInterface.
10//
11//===----------------------------------------------------------------------===//
12
14
23#include "mlir/IR/Dominance.h"
29#include "llvm/ADT/ScopeExit.h"
30#include "llvm/ADT/TypeSwitch.h"
31#include "llvm/Support/Debug.h"
32#include <optional>
33
34#define DEBUG_TYPE "tile-using-interface"
35
36using namespace mlir;
37
38scf::SCFTilingOptions &
39scf::SCFTilingOptions::setTileSizes(ArrayRef<OpFoldResult> ts) {
40 assert(!tileSizeComputationFunction && "tile sizes already set");
41 auto tileSizes = llvm::to_vector(ts);
42 tileSizeComputationFunction = [tileSizes](OpBuilder &b, Operation *op) {
43 return tileSizes;
44 };
45 return *this;
46}
47
48scf::SCFTilingOptions &
49scf::SCFTilingOptions::setNumThreads(ArrayRef<OpFoldResult> nt) {
50 assert(!numThreadsComputationFunction && "num tiles already set");
51 auto numThreads = llvm::to_vector(nt);
52 numThreadsComputationFunction = [numThreads](OpBuilder &b, Operation *op) {
53 return numThreads;
54 };
55 return *this;
56}
57
58/// Helper method to adjust the interchange vector to match the iteration
59/// domain.
62 size_t iterationDomainSize) {
63 SmallVector<int64_t> filledVector = llvm::to_vector(interchangeVector);
64 if (filledVector.size() < iterationDomainSize) {
65 auto range = llvm::seq<int64_t>(filledVector.size(), iterationDomainSize);
66 filledVector.append(range.begin(), range.end());
67 }
68 if (filledVector.size() > iterationDomainSize)
69 filledVector.resize(iterationDomainSize);
70 return filledVector;
71}
72
73//===----------------------------------------------------------------------===//
74// tileUsingSCF implementation.
75//===----------------------------------------------------------------------===//
76
77/// Verify the tile size options are set in a consistent manner.
78static LogicalResult verifyOptions(RewriterBase &rewriter, Location loc,
79 const scf::SCFTilingOptions &options) {
80 // Specifying number of threads is only supported on `scf.forall` op.
81 if (options.numThreadsComputationFunction &&
82 options.loopType != scf::SCFTilingOptions::LoopType::ForallOp) {
83 return rewriter.notifyMatchFailure(
84 loc, "number of threads can only by specified when loop type is "
85 "set to use `scf.forall`");
86 }
87
88 // If specified, check that the interchange vector is a permutation.
89 if (!options.interchangeVector.empty()) {
90 if (!isPermutationVector(options.interchangeVector)) {
91 return rewriter.notifyMatchFailure(
92 loc, "invalid interchange vector, not a permutation of the entire "
93 "iteration space");
94 }
95 }
96 return success();
97}
98
99/// Method to instantiate the tile sizes and/or number of threads specified
100/// by the user.
101static std::tuple<SmallVector<OpFoldResult>, SmallVector<OpFoldResult>>
102getUserTileSizesAndNumThreads(RewriterBase &rewriter, TilingInterface op,
103 ArrayRef<Range> iterationDomain,
104 const scf::SCFTilingOptions &options) {
105 OpFoldResult zero = rewriter.getIndexAttr(0);
106 SmallVector<OpFoldResult> tileSizes, numThreads;
107 size_t numLoops = iterationDomain.size();
108
109 // Check whether the number of tiles to use is specified.
110 if (options.numThreadsComputationFunction) {
111 numThreads = options.numThreadsComputationFunction(rewriter, op);
112 numThreads.resize(numLoops, zero);
113
114 // If the number of tiles is also specified, use that.
115 if (options.tileSizeComputationFunction) {
116 tileSizes = options.tileSizeComputationFunction(rewriter, op);
117 tileSizes.resize(numLoops, zero);
118 return {tileSizes, numThreads};
119 }
120
121 // Compute the tile sizes from the iteration domain and number
122 // of tiles as follows
123 // - niters = ceilDiv(ub - lb, step)
124 // - tileSize = ceilDiv(niters, numThreads)
125 AffineExpr s0, s1, s2;
126 bindSymbols(rewriter.getContext(), s0, s1, s2);
127 // TODO: The step here is assumed to be 1.
128 AffineExpr numItersExpr = (s1 - s0);
129 AffineExpr tileSizeExpr = numItersExpr.ceilDiv(s2);
130 tileSizes.resize(numLoops, zero);
131 for (auto [index, range, nt] :
132 llvm::enumerate(iterationDomain, numThreads)) {
133 if (isZeroInteger(nt))
134 continue;
135
137 rewriter, op.getLoc(), tileSizeExpr, {range.offset, range.size, nt});
138 }
139 tileSizes.resize(numLoops, zero);
140 return {tileSizes, numThreads};
141 }
142
143 // Enforce the convention that "tiling by zero"
144 // skips tiling a particular dimension. This convention is significantly
145 // simpler to handle instead of adjusting affine maps to account for missing
146 // dimensions.
147 assert(options.tileSizeComputationFunction &&
148 "expected tile sizes to be specified");
149 tileSizes = options.tileSizeComputationFunction(rewriter, op);
150 tileSizes.resize(numLoops, zero);
151
152 return {tileSizes, numThreads};
153}
154
155/// Checks if any of the tiled loops are not parallel.
156static LogicalResult checkTileSizes(TilingInterface op,
157 scf::SCFTilingOptions::LoopType loopType,
158 ReductionTilingStrategy reductionStrategy,
159 ArrayRef<OpFoldResult> givenTileSizes,
160 ArrayRef<OpFoldResult> numThreads) {
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");
166
167 bool isParallelTiling = false;
168 for (auto [index, iterator, givenTileSize] :
169 llvm::enumerate(iterators, givenTileSizes)) {
170 if (!isConstantIntValue(givenTileSize, 0)) {
171 isParallelTiling |= iterator == utils::IteratorType::parallel;
172 }
173
174 if (loopType == scf::SCFTilingOptions::LoopType::ForallOp &&
175 reductionStrategy == ReductionTilingStrategy::FullReduction) {
176 // If num threads is specified, check that it is greater than one only for
177 // parallel dimensions.
178 if (!numThreads.empty()) {
179 if (std::optional<int64_t> constNumThreads =
180 getConstantIntValue(numThreads[index])) {
181 if (constNumThreads.value() > 1 &&
182 iterator != utils::IteratorType::parallel) {
183 op.emitWarning() << "tiling is not thread safe at axis #" << index;
184 }
185 }
186 continue;
187 }
188
189 if (std::optional<int64_t> constTileSize =
190 getConstantIntValue(givenTileSize)) {
191 if (constTileSize.value() > 0 &&
192 iterator != utils::IteratorType::parallel) {
193 op.emitWarning() << "tiling is not thread safe at axis #" << index;
194 }
195 }
196 }
197 }
198
199 if (reductionStrategy != ReductionTilingStrategy::FullReduction) {
200 if (isParallelTiling) {
201 return op->emitOpError("tiling parallel dimensions is not supported with "
202 "partial reduction tiling strategies");
203 }
204 }
205 return success();
206}
207
208/// Get the reduction dims that are tiled. This accounts for reduction dims
209/// that are specified as tiled, but the tile size is 0.
212 const scf::SCFTilingOptions &options) {
213 SetVector<unsigned> reductionDims;
214 for (auto dim : options.reductionDims) {
215 if (isConstantIntValue(givenTileSizes[dim], 0))
216 continue;
217 reductionDims.insert(dim);
218 }
219 return reductionDims;
220}
221
222/// Check if `stride` evenly divides the trip count `size - offset`.
223static bool tileDividesIterationDomain(Range loopRange) {
224 std::optional<int64_t> offsetAsInt = getConstantIntValue(loopRange.offset);
225 if (!offsetAsInt)
226 return false;
227 std::optional<int64_t> sizeAsInt = getConstantIntValue(loopRange.size);
228 if (!sizeAsInt)
229 return false;
230 std::optional<int64_t> strideAsInt = getConstantIntValue(loopRange.stride);
231 if (!strideAsInt)
232 return false;
233 return ((sizeAsInt.value() - offsetAsInt.value()) % strideAsInt.value() == 0);
234}
235
236/// Returns the bounded tile size given the current `offset`, `loopRange` and
237/// `tileSize`, i.e., `min(tileSize, range.end() - offset)`.
239 Range loopRange, OpFoldResult offset,
240 OpFoldResult givenTileSize) {
241 std::optional<int64_t> ts = getConstantIntValue(givenTileSize);
242 if (ts && ts.value() == 1)
243 return givenTileSize;
244
246 Range{loopRange.offset, loopRange.size, givenTileSize}))
247 return givenTileSize;
248
249 // The tile size to use (to avoid out of bounds access) is minimum of
250 // `tileSize` and `ub - iv`, where `iv` is the induction variable of the tiled
251 // loop.
252 AffineExpr s0, s1, d0;
253 bindDims(b.getContext(), d0);
254 bindSymbols(b.getContext(), s0, s1);
255 AffineMap minMap = AffineMap::get(1, 2, {s0 - d0, s1}, b.getContext());
256 Value size = getValueOrCreateConstantIndexOp(b, loc, loopRange.size);
258 b, loc, minMap, SmallVector<OpFoldResult>{offset, size, givenTileSize});
259}
260
261/// Returns true if the maximum tile offset `tileSize * numThreads-1` is less
262/// than `iterationSize`.
264 OpFoldResult numThreads,
265 OpFoldResult iterationSize) {
266 std::optional<int64_t> tileSizeConst = getConstantIntValue(givenTileSize);
267 std::optional<int64_t> numThreadsConst = getConstantIntValue(numThreads);
268 std::optional<int64_t> iterSizeConst = getConstantIntValue(iterationSize);
269 if (!tileSizeConst || !numThreadsConst || !iterSizeConst)
270 return false;
271 return *tileSizeConst * (*numThreadsConst - 1) < *iterSizeConst;
272}
273
274/// Compute the `OpFoldResult`s that represents the multi-dimensional
275/// `offset`s and `size`s of the tile of the iteration space that the
276/// innermost loop body of the generated tiled loops corresponds to.
277static std::tuple<SmallVector<OpFoldResult>, SmallVector<OpFoldResult>>
279 ArrayRef<Range> iterationDomain,
280 ArrayRef<OpFoldResult> givenTileSizes) {
281 SmallVector<OpFoldResult> offsets, sizes;
282 int materializedLoopNum = 0;
283 for (auto [givenTileSize, loopRange] :
284 llvm::zip_equal(givenTileSizes, iterationDomain)) {
285
286 // Non-tiled cases, set the offset and size to the
287 // `loopRange.offset/size`.
288 if (isZeroInteger(givenTileSize)) {
289 offsets.push_back(loopRange.offset);
290 sizes.push_back(loopRange.size);
291 continue;
292 }
293
294 Value iv = ivs[materializedLoopNum++];
295 OpFoldResult offset = getAsOpFoldResult(iv);
296 offsets.push_back(offset);
297 OpFoldResult size =
298 getBoundedTileSize(rewriter, loc, loopRange, offset, givenTileSize);
299 sizes.push_back(size);
300 }
301 return {offsets, sizes};
302}
303
304/// Function to return the bounds of the loops to be generated.
305static std::tuple<SmallVector<OpFoldResult>, SmallVector<OpFoldResult>,
308 ArrayRef<OpFoldResult> givenTileSizes) {
309 SmallVector<OpFoldResult> lbs, ubs, steps;
310 for (auto [loopRange, givenTileSize] :
311 llvm::zip_equal(loopRanges, givenTileSizes)) {
312 // No loop if the tile size is 0.
313 if (isZeroInteger(givenTileSize))
314 continue;
315 lbs.push_back(loopRange.offset);
316 ubs.push_back(loopRange.size);
317 steps.push_back(givenTileSize);
318 }
319 return {lbs, ubs, steps};
320}
321
322/// Typedef for function that allows returning additional yielded values during
323/// `yieldTiledValuesAndReplace`.
324/// - `ivs` induction variable for the loop.
325/// - `newBbArgs` basic block arguments corresponding to newly added iter_args.
326/// - `tiledValues` the tiled values to return. Must be of same size as
327/// `newbbArgs`, each element of this array is inserted into the corresponding
328/// element in `newbbArgs`.
329/// - `resultOffsets` is of the same size as `tiledValues` and represents
330/// the offsets to use when inserting corresponding element from `tiledValues`
331/// into the element from `newBbArgs`.
332/// - `resultSizes` is of the same size as `tiledValues` and represents
333/// the size of the corresponding element from `tiledValues` inserted into
334/// the element from `newBbArgs`.
335/// In case the method needs to return `failure()` the method is expected
336/// to clean up any inserted operations.
337using YieldTiledValuesFn = std::function<LogicalResult(
338 RewriterBase &rewriter, Location loc, ValueRange ivs, ValueRange newBbArgs,
339 SmallVector<Value> &tiledValues,
342
343/// Typedef for function that implements the body of a tiled loop.
344/// - `ivs` induction variable for the loop.
345/// - `tileOffsets` represents offsets for the tiled iteration space.
346/// - `tileSizes` represents the sizes for the tiled iteraiton space.
347/// - `outerDestinationTensors` tensor that holds the result. Is same size
348/// as the destination operands of the original operations.
349/// - `tiledResults` results of the tiled computation, corresponds to
350/// tiles of the original operation computed by the loop body.
351/// Should be same size as the `destinationTensors`
352/// - `resultOffsets` is of the same size as `tiledResults` and represents
353/// the offset to use when writing the corresponding element from
354/// `tiledResults` into `destinationTensors`.
355/// - `resultOffsets` is of the same size as `tiledResults` and represents
356/// the size to use when writing the corresponding element from
357/// `tiledResults` into `destinationTensors`.
358/// In case the method needs to return `failure()` the method is expected
359/// to clean up any inserted operations.
360using GenerateTiledBodyFn = std::function<LogicalResult(
361 RewriterBase &rewriter, Location Loc, ValueRange ivs,
362 ArrayRef<OpFoldResult> tileOffsets, ArrayRef<OpFoldResult> tileSizes,
363 ValueRange outerDestinationTensors, SmallVector<Value> &tiledResults,
366
367/// Clones the operation and updates the destination if the operation
368/// implements the `DestinationStyleOpInterface`.
370 Operation *op,
371 ValueRange newDestArgs) {
372 Operation *clonedOp = rewriter.clone(*op);
373 if (newDestArgs.empty())
374 return clonedOp;
375 if (auto destinationStyleOp = dyn_cast<DestinationStyleOpInterface>(clonedOp))
376 destinationStyleOp.getDpsInitsMutable().assign(newDestArgs);
377 return clonedOp;
378}
379
380/// Generate the tile-loop nest using `scf.for` operation.
381/// - `loopRanges` specifies the lb, ub and step of the untiled iteration space.
382/// - `givenTileSizes` is the tile sizes to use. Zero represent untiled loops.
383/// - `outerDestinationTensors` are the init values to use for the outer most
384/// loop.
385/// - `tiledBodyFn` is called to generated the loop body of the inner
386/// most
387/// loop.
388/// Returns the generated `scf.for` loops on success.
389static FailureOr<SmallVector<LoopLikeOpInterface>> generateLoopNestUsingForOp(
390 RewriterBase &rewriter, Location loc, ArrayRef<Range> loopRanges,
391 ArrayRef<OpFoldResult> givenTileSizes, ValueRange outerDestinationTensors,
392 GenerateTiledBodyFn tiledBodyFn) {
393 assert(!loopRanges.empty() && "unexpected empty loop ranges");
394 assert(loopRanges.size() == givenTileSizes.size() &&
395 "expected as many tile sizes as loop ranges");
396 OpBuilder::InsertionGuard guard(rewriter);
397
398 SmallVector<OpFoldResult> lbs, ubs, steps;
399 std::tie(lbs, ubs, steps) =
400 getLoopBounds(rewriter, loc, loopRanges, givenTileSizes);
401 SmallVector<Value> lbVals =
402 getValueOrCreateConstantIndexOp(rewriter, loc, lbs);
403 SmallVector<Value> ubVals =
404 getValueOrCreateConstantIndexOp(rewriter, loc, ubs);
405 SmallVector<Value> stepVals =
406 getValueOrCreateConstantIndexOp(rewriter, loc, steps);
407
410 ValueRange innerDestinationTensors(outerDestinationTensors);
411 for (auto [lb, ub, step] : llvm::zip_equal(lbVals, ubVals, stepVals)) {
412 auto loop =
413 scf::ForOp::create(rewriter, loc, lb, ub, step, innerDestinationTensors,
414 [](OpBuilder &bodyBuilder, Location bodyLoc,
415 Value iv, ValueRange /*iterArgs*/) {});
416 loops.push_back(loop);
417 ivs.push_back(loop.getInductionVar());
418 rewriter.setInsertionPointToEnd(loop.getBody());
419 innerDestinationTensors = loop.getRegionIterArgs();
420 }
421 if (loops.empty())
422 return success();
423
424 // Compute the `offsets` and `sizes` to use for tiling.
425 SmallVector<OpFoldResult> offsets, sizes;
426 std::tie(offsets, sizes) =
427 getTileOffsetAndSizes(rewriter, loc, ivs, loopRanges, givenTileSizes);
428
429 SmallVector<Value> tiledResults;
430 SmallVector<SmallVector<OpFoldResult>> resultOffsets, resultSizes;
431 if (failed(tiledBodyFn(rewriter, loc, ivs, offsets, sizes,
432 innerDestinationTensors, tiledResults, resultOffsets,
433 resultSizes))) {
434 return rewriter.notifyMatchFailure(
435 loc, "failed to generate inner tile loop body");
436 }
437 if (loops.empty())
438 return loops;
439
440 assert(tiledResults.size() == innerDestinationTensors.size() &&
441 "Number of results of body should be equal to number of iter args");
442
443 // 6. Yield all the results of the tiled operation.
444 SmallVector<Value> yieldedValues;
445 for (auto [tiledValue, destinationTensor, resultOffset, resultSize] :
446 llvm::zip_equal(tiledResults, innerDestinationTensors, resultOffsets,
447 resultSizes)) {
448 SmallVector<OpFoldResult> resultStride(resultOffset.size(),
449 rewriter.getIndexAttr(1));
450 auto insertSlice = tensor::InsertSliceOp::create(
451 rewriter, loc, tiledValue, destinationTensor, resultOffset, resultSize,
452 resultStride);
453 yieldedValues.push_back(insertSlice);
454 }
455 scf::YieldOp::create(rewriter, loc, yieldedValues);
456
457 // Add the scf.yield operations for all the outer loops.
458 for (auto [outerLoop, innerLoop] :
459 llvm::zip_equal(MutableArrayRef(loops).drop_back(),
460 MutableArrayRef(loops).drop_front())) {
461 rewriter.setInsertionPointToEnd(
462 cast<scf::ForOp>(outerLoop.getOperation()).getBody());
463 scf::YieldOp::create(rewriter, outerLoop.getLoc(), innerLoop->getResults());
464 }
465 return loops;
466}
467
468/// Compute the `OpFoldResult`s that represents the multi-dimensional
469/// `offset`s and `size`s of the tile of the iteration space that the
470/// innermost loop body of the generated tiled loops corresponds to
471/// when tiling using `forall` op. This is handle separately due to
472/// the special case handling needed for when the tiling is done by
473/// specifying number of threads.
474static std::tuple<SmallVector<OpFoldResult>, SmallVector<OpFoldResult>>
476 ValueRange ivs,
477 ArrayRef<Range> iterationDomain,
478 ArrayRef<OpFoldResult> givenTileSizes,
479 ArrayRef<OpFoldResult> numThreads) {
480 if (numThreads.empty()) {
481 return getTileOffsetAndSizes(rewriter, loc, ivs, iterationDomain,
482 givenTileSizes);
483 }
484
485 SmallVector<OpFoldResult> offsets, sizes;
486 int materializedLoopNum = 0;
487
488 AffineExpr d0, d1, s0, s1;
489 AffineExpr offsetExpr, residualTileSizeExpr;
490 bindDims(rewriter.getContext(), d0, d1);
491 bindSymbols(rewriter.getContext(), s0, s1);
492 offsetExpr = d0 + d1 * s0;
493 residualTileSizeExpr = s1 - (d0 + d1 * s0);
494
495 for (auto [index, nt, givenTileSize, loopRange] :
496 llvm::enumerate(numThreads, givenTileSizes, iterationDomain)) {
497
498 // Non-tiled cases, set the offset and size to the
499 // `loopRange.offset/size`.
500 if (isZeroInteger(nt)) {
501 offsets.push_back(loopRange.offset);
502 sizes.push_back(loopRange.size);
503 continue;
504 }
505
506 Value iv = ivs[materializedLoopNum++];
508 rewriter, loc, offsetExpr,
509 ArrayRef<OpFoldResult>{loopRange.offset, iv, givenTileSize});
511 rewriter, loc, residualTileSizeExpr,
512 {loopRange.offset, nt, givenTileSize, loopRange.size});
513
514 OpFoldResult size = givenTileSize;
515 if (!isZeroInteger(residualTileSize)) {
516 OpFoldResult sizeMinusOffsetPerThread =
517 affine::makeComposedFoldedAffineApply(rewriter, loc, s0 - d0,
518 {offset, loopRange.size});
520 rewriter, loc,
522 {sizeMinusOffsetPerThread, givenTileSize});
523 }
524
525 // Consider the case where the original loop was `[0, 100)`.
526 // If number of threads are `7`, the tile size would be computed as
527 // `ceilDiv(100, 7) = 15`. For the last thread (thread_id = 6)
528 // - `offset = 0 + 6 * 15 = 105`
529 // - `tileSize = min(15, 100 - 105) = -5`
530 // To avoid negative tile sizes, we need to do a further
531 // `nonNegativeTileSize = affine.max(0, tileSize)`.
532 // This `max` can be avoided if
533 // `offset + tileSize * (numThreads - 1) < (ub - lb)`
534 if (!canOmitTileOffsetInBoundsCheck(givenTileSize, nt, loopRange.size)) {
535 AffineMap maxMap =
538 rewriter, loc, maxMap, {rewriter.getIndexAttr(0), size});
539 }
540
541 offsets.push_back(offset);
542 sizes.push_back(size);
543 }
544 return {offsets, sizes};
545}
546
547/// Generate the tile-loop nest using `scf.forall` operation.
548/// - `loopRanges` specifies the lb, ub and step of the untiled iteration space.
549/// - `giventileSizes` is the tile sizes to use. Zero represent untiled loops.
550/// - `outerDestinationTensors` are the init values to use for the loop.
551/// - `mappingVector` is the mapping attributes to use for loop construction.
552/// Can be empty.
553/// - `tiledBodyFn` is called to generated the loop body of the inner
554/// most
555/// loop.
556/// Returns the generated `scf.forall` loop on success.
557static FailureOr<SmallVector<LoopLikeOpInterface>>
559 ArrayRef<Range> loopRanges,
560 ArrayRef<OpFoldResult> givenTileSizes,
561 ArrayRef<OpFoldResult> numThreads,
562 ArrayRef<Attribute> mappingVector,
563 ValueRange outerDestinationTensors,
564 GenerateTiledBodyFn tiledBodyFn) {
565 assert(!loopRanges.empty() && "unexpected empty loop ranges");
566 assert(loopRanges.size() == givenTileSizes.size() &&
567 "expected as many tile sizes as loop ranges");
568 OpBuilder::InsertionGuard guard(rewriter);
569
570 std::optional<ArrayAttr> mappingAttr;
571 if (!mappingVector.empty())
572 mappingAttr = rewriter.getArrayAttr(mappingVector);
573
574 scf::ForallOp forallOp;
575 bool useNumThreads = !numThreads.empty();
576
578 if (useNumThreads) {
579 // Prune the zero numthreads.
580 SmallVector<OpFoldResult> nonZeroNumThreads;
581 for (auto nt : numThreads) {
582 if (isZeroInteger(nt))
583 continue;
584 nonZeroNumThreads.push_back(nt);
585 }
586 forallOp = scf::ForallOp::create(rewriter, loc, nonZeroNumThreads,
587 outerDestinationTensors, mappingAttr);
588 } else {
589 SmallVector<OpFoldResult> lbs, ubs, steps;
590 std::tie(lbs, ubs, steps) =
591 getLoopBounds(rewriter, loc, loopRanges, givenTileSizes);
592 forallOp = scf::ForallOp::create(rewriter, loc, lbs, ubs, steps,
593 outerDestinationTensors, mappingAttr);
594 }
595 loops.push_back(forallOp);
596
597 rewriter.setInsertionPoint(forallOp.getTerminator());
598 ValueRange innerDestinationTensors = forallOp.getRegionOutArgs();
599 SmallVector<Value> ivs = forallOp.getInductionVars();
600
601 // Compute the `offsets` and `sizes` to use for tiling.
602 SmallVector<OpFoldResult> offsets, sizes;
603 std::tie(offsets, sizes) = getTileOffsetAndSizesWithForAllOp(
604 rewriter, loc, ivs, loopRanges, givenTileSizes, numThreads);
605
606 SmallVector<Value> tiledResults;
607 SmallVector<SmallVector<OpFoldResult>> resultOffsets, resultSizes;
608 if (failed(tiledBodyFn(rewriter, loc, ivs, offsets, sizes,
609 innerDestinationTensors, tiledResults, resultOffsets,
610 resultSizes)))
611 return rewriter.notifyMatchFailure(loc, "failed to generate loop body");
612
613 rewriter.setInsertionPointToEnd(forallOp.getTerminator().getBody());
614 for (auto [tiledValue, destinationTensor, resultOffset, resultSize] :
615 llvm::zip_equal(tiledResults, innerDestinationTensors, resultOffsets,
616 resultSizes)) {
617 SmallVector<OpFoldResult> resultStride(resultOffset.size(),
618 rewriter.getIndexAttr(1));
619
620 tensor::ParallelInsertSliceOp::create(rewriter, loc, tiledValue,
621 destinationTensor, resultOffset,
622 resultSize, resultStride);
623 }
624 return loops;
625}
626
627/// Generate the tile-loop nest using custom loop operation.
628/// - `loopRanges` specifies the lb, ub and step of the untiled iteration space.
629/// - `tileSizes` is the tile sizes to use. Zero represent untiled loops.
630/// - `destinationTensors` are the init values to use for the outer most loop.
631/// - `mappingVector` is the mapping attributes to use for loop construction.
632/// Can be empty.
633/// - `tiledBodyFn` is called to generated the loop body of the inner
634/// most
635/// loop.
636/// Returns the generated `scf.forall` loop on success.
637static FailureOr<SmallVector<LoopLikeOpInterface>>
639 RewriterBase &rewriter, Location loc, ArrayRef<Range> loopRanges,
640 ArrayRef<OpFoldResult> givenTileSizes, ValueRange outerDestinationTensors,
641 const scf::SCFTilingOptions::GenerateLoopHeaderFn &generateLoopHeaderFn,
642 const scf::SCFTilingOptions::GenerateLoopTerminatorFn
643 &generateLoopTerminatorFn,
644 GenerateTiledBodyFn tiledBodyFn) {
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");
650 OpBuilder::InsertionGuard guard(rewriter);
651
652 FailureOr<scf::SCFTilingOptions::CustomLoopHeaderInfo> loopHeaderInfo =
653 generateLoopHeaderFn(rewriter, loc, loopRanges, givenTileSizes,
654 outerDestinationTensors);
655 if (failed(loopHeaderInfo)) {
656 return failure();
657 }
658
660 SmallVector<Value> tiledResults;
661 SmallVector<SmallVector<OpFoldResult>> resultOffsets, resultSizes;
662 if (failed(tiledBodyFn(rewriter, loc, ivs, loopHeaderInfo->tileOffset,
663 loopHeaderInfo->tileSizes,
664 loopHeaderInfo->destinationTensors, tiledResults,
665 resultOffsets, resultSizes))) {
666 return failure();
667 }
668
669 if (failed(generateLoopTerminatorFn(rewriter, loc, loopHeaderInfo->loops,
670 tiledResults, resultOffsets, resultSizes,
671 loopHeaderInfo->destinationTensors))) {
672 return failure();
673 }
674
675 return loopHeaderInfo->loops;
676}
677
678/// Generate the tile-loop nest using the loop construct specifed in `options`.
679/// - `options`: Tiling options specified.
680/// - `loopRanges` specifies the lb, ub and step of the untiled iteration space.
681/// - `tileSizes` is the tile sizes to use. Zero represent untiled loops.
682/// - `outerDestinationTensors` are the init values to use for the outer most
683/// loop.
684/// - `yieldTiledValuesFn` is called to generated the loop body of the inner
685/// most
686/// loop.
687/// Returns the generated loops on success.
688static FailureOr<SmallVector<LoopLikeOpInterface>> generateLoopNest(
689 RewriterBase &rewriter, Location loc, const scf::SCFTilingOptions &options,
690 ArrayRef<Range> loopRanges, ArrayRef<OpFoldResult> givenTileSizes,
691 ArrayRef<OpFoldResult> numThreads, ValueRange destinationTensors,
692 GenerateTiledBodyFn tiledBodyFn) {
693 // If the tile sizes are all zero, no loops are generated. Just call the
694 // callback function to handle untiled case.
695 if (llvm::all_of(givenTileSizes, isZeroInteger)) {
696 SmallVector<Value> tiledResults;
697 SmallVector<SmallVector<OpFoldResult>> resultOffsets, resultSizes;
698 auto tileOffsets =
699 llvm::map_to_vector(loopRanges, [](Range r) { return r.offset; });
700 auto tileSizes =
701 llvm::map_to_vector(loopRanges, [](Range r) { return r.size; });
702 if (failed(tiledBodyFn(rewriter, loc, ValueRange{}, tileOffsets, tileSizes,
703 destinationTensors, tiledResults, resultOffsets,
704 resultSizes))) {
705 return failure();
706 }
708 }
709 if (options.loopType == scf::SCFTilingOptions::LoopType::ForOp) {
710 return generateLoopNestUsingForOp(rewriter, loc, loopRanges, givenTileSizes,
711 destinationTensors, tiledBodyFn);
712 }
713 if (options.loopType == scf::SCFTilingOptions::LoopType::ForallOp) {
715 rewriter, loc, loopRanges, givenTileSizes, numThreads,
716 options.mappingVector, destinationTensors, tiledBodyFn);
717 }
718 if (options.loopType == scf::SCFTilingOptions::LoopType::CustomOp) {
720 rewriter, loc, loopRanges, givenTileSizes, destinationTensors,
721 options.generateLoopHeaderFn, options.generateLoopTerminatorFn,
722 tiledBodyFn);
723 }
724 return rewriter.notifyMatchFailure(loc, "unhandled loop type");
725}
726
727static FailureOr<SmallVector<Value>> createInitialTensorsForTiling(
728 RewriterBase &rewriter, TilingInterface op,
729 ReductionTilingStrategy reductionStrategy, ArrayRef<Range> iterationDomain,
730 ArrayRef<OpFoldResult> numThreads, ArrayRef<OpFoldResult> givenTileSizes,
731 const SetVector<unsigned> &reductionDims) {
732 SmallVector<Value> initTensors;
733 Location loc = op->getLoc();
734 if (reductionStrategy == ReductionTilingStrategy::FullReduction) {
735 if (failed(tensor::getOrCreateDestinations(rewriter, loc, op, initTensors)))
736 return failure();
737 return initTensors;
738 }
739
740 auto redOp = dyn_cast<PartialReductionOpInterface>(op.getOperation());
741 if (!redOp) {
742 return op->emitOpError(
743 "PartialReductionOuterReduction tiling strategy is only supported for "
744 "operations implementing PartialReductionOpInterface");
745 }
746 SmallVector<OpFoldResult> sizes(iterationDomain.size());
747 AffineExpr s0, s1, s2;
748 bindSymbols(rewriter.getContext(), s0, s1, s2);
749 AffineExpr sizeExpr = ((s0 - s1).ceilDiv(s2));
750 AffineExpr divExpr = s0.ceilDiv(s1);
751 for (auto [index, domain, tileSize] :
752 llvm::enumerate(iterationDomain, givenTileSizes)) {
753 if (!numThreads.empty()) {
754 // Untiled case.
755 if (isConstantIntValue(numThreads[index], 0)) {
757 rewriter, op.getLoc(), sizeExpr,
758 {domain.size, domain.offset, domain.stride});
759 continue;
760 }
761 sizes[index] = numThreads[index];
762 continue;
763 }
764
765 // Non reduction dimensions/non-tiled dimensions.
766 if (!reductionDims.contains(index) || isConstantIntValue(tileSize, 0)) {
768 rewriter, op.getLoc(), sizeExpr,
769 {domain.size, domain.offset, domain.stride});
770 continue;
771 }
772
773 if (reductionStrategy ==
775 sizes[index] = tileSize;
776 continue;
777 }
778
779 assert(reductionStrategy ==
782 rewriter, op.getLoc(), sizeExpr,
783 {domain.size, domain.offset, domain.stride});
785 rewriter, op.getLoc(), divExpr, {normalizedRange, tileSize});
786 }
787 return redOp.generateInitialTensorForPartialReduction(rewriter, loc, sizes,
788 reductionDims);
789}
790
791/// For the case of `ReductionTilingStrategy::PartialReductionOuterParallel`
792/// the `PartialReductionOpInterface` methods need the index of the parallel
793/// split reduction being executed.
796 ReductionTilingStrategy reductionStrategy, ValueRange ivs,
797 ArrayRef<OpFoldResult> numThreads,
798 ArrayRef<OpFoldResult> givenTileSizes,
799 const SetVector<unsigned> &reductionDims) {
800 SmallVector<OpFoldResult> splitReductionIvs;
801 splitReductionIvs.resize(reductionDims.size(), rewriter.getIndexAttr(0));
802 AffineExpr s0, s1;
803 bindSymbols(rewriter.getContext(), s0, s1);
804 AffineExpr divExpr = s0.floorDiv(s1);
805 int ivIndex = 0;
806 if (reductionStrategy ==
808 for (auto [index, reductionDim] : llvm::enumerate(reductionDims)) {
809 if (!numThreads.empty()) {
810 splitReductionIvs[index] = ivs[ivIndex++];
811 continue;
812 }
813 splitReductionIvs[index] = affine::makeComposedFoldedAffineApply(
814 rewriter, loc, divExpr,
815 ArrayRef<OpFoldResult>{ivs[ivIndex++], givenTileSizes[reductionDim]});
816 }
817 }
818 return splitReductionIvs;
819}
820
821static FailureOr<TilingResult>
822getTiledImplementation(RewriterBase &rewriter, TilingInterface op,
823 ReductionTilingStrategy reductionStrategy,
824 ValueRange regionIterArg, ArrayRef<OpFoldResult> offsets,
826 ArrayRef<OpFoldResult> numThreads,
827 ArrayRef<OpFoldResult> givenTileSizes,
828 ArrayRef<InnerTileAlignment> innerTileAlignments,
829 const SetVector<unsigned> &reductionDims) {
830 if (reductionStrategy == ReductionTilingStrategy::FullReduction) {
831 return op.getTiledImplementation(rewriter, offsets, sizes,
832 innerTileAlignments);
833 }
834
835 auto redOp = dyn_cast<PartialReductionOpInterface>(op.getOperation());
836 if (!redOp) {
837 return rewriter.notifyMatchFailure(
838 op, "PartialReductionOuterReduction tiling strategy is only "
839 "supported for operations "
840 "implementing PartialReductionOpInterface");
841 }
842
843 SmallVector<OpFoldResult> splitReductionIvs =
844 getSplitReductionIvs(rewriter, op.getLoc(), reductionStrategy, ivs,
845 numThreads, givenTileSizes, reductionDims);
846 return redOp.tileToPartialReduction(rewriter, op.getLoc(), reductionStrategy,
847 regionIterArg, offsets, sizes,
848 reductionDims, splitReductionIvs);
849}
850
851static LogicalResult getResultTilePosition(
852 RewriterBase &rewriter, ReductionTilingStrategy reductionStrategy,
853 int64_t index, Value tiledResult, TilingInterface op,
855 ValueRange ivs, ArrayRef<OpFoldResult> numThreads,
856 ArrayRef<OpFoldResult> givenTileSizes,
857 const SetVector<unsigned> &reductionDims,
858 SmallVector<OpFoldResult> &resultOffset,
859 SmallVector<OpFoldResult> &resultSize) {
860
861 if (reductionStrategy == ReductionTilingStrategy::FullReduction) {
862 return op.getResultTilePosition(rewriter, index, offsets, sizes,
863 resultOffset, resultSize);
864 }
865 auto redOp = dyn_cast<PartialReductionOpInterface>(op.getOperation());
866 if (!redOp) {
867 return rewriter.notifyMatchFailure(
868 op, "PartialReductionOuterReduction tiling strategy is only supported"
869 "for operations implementing PartialReductionOpInterface");
870 }
871 SmallVector<OpFoldResult> splitReductionIvs =
872 getSplitReductionIvs(rewriter, op.getLoc(), reductionStrategy, ivs,
873 numThreads, givenTileSizes, reductionDims);
874 return redOp.getPartialResultTilePosition(
875 rewriter, index, reductionStrategy, offsets, sizes, reductionDims,
876 splitReductionIvs, resultOffset, resultSize);
877}
878
879static FailureOr<MergeResult>
880mergeTilingResults(RewriterBase &rewriter, TilingInterface op,
881 ReductionTilingStrategy reductionStrategy,
882 const SetVector<unsigned> &reductionDims,
883 ValueRange partialResults) {
884 assert(reductionStrategy != ReductionTilingStrategy::FullReduction &&
885 "expected merge to be called for only partial reduction cases");
886
887 auto redOp = dyn_cast<PartialReductionOpInterface>(op.getOperation());
888 if (!redOp) {
889 return rewriter.notifyMatchFailure(
890 op, "PartialReductionOuterReduction tiling strategy is only "
891 "supported for operations "
892 "implementing PartialReductionOpInterface");
893 }
894 return redOp.mergeReductions(rewriter, op.getLoc(), partialResults,
895 reductionDims);
896}
897
898/// Append the specified additional `newInitOperands` operands to the
899/// loops existing `init` operands (or similar), and replace `loopOp` with
900/// the new loop that has the additional init operands. The loop body of
901/// this loop is moved over to the new loop. `yieldTiledValuesFn`
902/// is called to get the new tiled values returned, and the offset
903/// and sizes at which the tiled value is inserted into the
904/// new region iter_args that correspond to the newly added init operands.
905template <typename LoopType>
906static FailureOr<LoopLikeOpInterface>
908 ValueRange newInitOperands,
909 YieldTiledValuesFn yieldTiledValuesFn) {
910 return rewriter.notifyMatchFailure(loopOp, "unhandled loop type");
911}
912
913/// Implementation of `yieldTiledValuesAndReplaceLoop` for `scf.for`.
914template <>
915FailureOr<LoopLikeOpInterface> yieldTiledValuesAndReplaceLoop<scf::ForOp>(
916 scf::ForOp loopOp, RewriterBase &rewriter, ValueRange newInitOperands,
917 YieldTiledValuesFn yieldTiledValuesFn) {
918 OpBuilder::InsertionGuard g(rewriter);
919 Location loc = loopOp.getLoc();
920 rewriter.setInsertionPoint(loopOp);
921
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(),
926 loopOp.getStep(), inits, [](OpBuilder &, Location, Value, ValueRange) {},
927 loopOp.getUnsignedCmp());
928
929 // Move the loop body to the new op.
930 Block *loopBody = loopOp.getBody();
931 Block *newLoopBody = newLoop.getBody();
932 rewriter.mergeBlocks(
933 loopBody, newLoopBody,
934 newLoopBody->getArguments().take_front(loopBody->getNumArguments()));
935
936 auto yieldOp = cast<scf::YieldOp>(newLoopBody->getTerminator());
937 rewriter.setInsertionPoint(yieldOp);
938
939 SmallVector<Value> tiledValues;
940 SmallVector<SmallVector<OpFoldResult>> resultOffsets, resultSizes;
941 ValueRange newRegionIterArgs =
942 newLoop.getRegionIterArgs().take_back(newInitOperands.size());
943 if (failed(yieldTiledValuesFn(rewriter, loc, newLoop.getInductionVar(),
944 newRegionIterArgs, tiledValues, resultOffsets,
945 resultSizes))) {
946 rewriter.eraseOp(newLoop);
947 return rewriter.notifyMatchFailure(loopOp, "failed to get tiled values");
948 }
949
950 SmallVector<Value> newYieldValues = llvm::to_vector(yieldOp.getOperands());
951 for (auto [tiledValue, regionIterArg, resultOffset, resultSize] :
952 llvm::zip_equal(tiledValues, newRegionIterArgs, resultOffsets,
953 resultSizes)) {
954 SmallVector<OpFoldResult> resultStride(resultOffset.size(),
955 rewriter.getIndexAttr(1));
956 Value insert = tensor::InsertSliceOp::create(
957 rewriter, yieldOp->getLoc(), tiledValue, regionIterArg, resultOffset,
958 resultSize, resultStride);
959 newYieldValues.push_back(insert);
960 }
961
962 rewriter.replaceOpWithNewOp<scf::YieldOp>(yieldOp, newYieldValues);
963 rewriter.replaceOp(loopOp,
964 newLoop->getResults().take_front(loopOp.getNumResults()));
965 return cast<LoopLikeOpInterface>(newLoop.getOperation());
966}
967
968/// Implementation of `yieldTiledValuesAndReplaceLoop` for `scf.forall`
969template <>
970FailureOr<LoopLikeOpInterface> yieldTiledValuesAndReplaceLoop<scf::ForallOp>(
971 scf::ForallOp loopOp, RewriterBase &rewriter, ValueRange newInitOperands,
972 YieldTiledValuesFn yieldTiledValuesFn) {
973 OpBuilder::InsertionGuard g(rewriter);
974 Location loc = loopOp.getLoc();
975 rewriter.setInsertionPoint(loopOp);
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(),
981 [](OpBuilder &, Location, ValueRange) {});
982
983 // Move the region of the current block to the newly created op.
984 Block *loopBody = loopOp.getBody();
985 Block *newLoopBody = newLoop.getBody();
986 rewriter.mergeBlocks(
987 loopBody, newLoopBody,
988 newLoopBody->getArguments().take_front(loopBody->getNumArguments()));
989
990 auto terminator = cast<scf::InParallelOp>(newLoopBody->getTerminator());
991 rewriter.setInsertionPoint(terminator);
992 SmallVector<Value> tiledValues;
993 SmallVector<SmallVector<OpFoldResult>> resultOffsets, resultSizes;
994 ValueRange regionIterArgs =
995 newLoop.getRegionIterArgs().take_back(newInitOperands.size());
996 if (failed(yieldTiledValuesFn(rewriter, loc, newLoop.getInductionVars(),
997 regionIterArgs, tiledValues, resultOffsets,
998 resultSizes))) {
999 rewriter.eraseOp(newLoop);
1000 return rewriter.notifyMatchFailure(loopOp,
1001 "failed to get yielded tiled values");
1002 }
1003
1004 // Update the terminator.
1005 rewriter.setInsertionPointToEnd(terminator.getBody());
1006
1007 for (auto [tiledValue, iterArg, resultOffset, resultSize] : llvm::zip_equal(
1008 tiledValues, regionIterArgs, resultOffsets, resultSizes)) {
1009 SmallVector<OpFoldResult> resultStride(resultOffset.size(),
1010 rewriter.getIndexAttr(1));
1011 tensor::ParallelInsertSliceOp::create(rewriter, terminator.getLoc(),
1012 tiledValue, iterArg, resultOffset,
1013 resultSize, resultStride);
1014 }
1015
1016 rewriter.replaceOp(loopOp,
1017 newLoop->getResults().take_front(loopOp.getNumResults()));
1018 return cast<LoopLikeOpInterface>(newLoop.getOperation());
1019}
1020
1021/// Implementation of `yieldTiledValuesAndReplaceLoop` for
1022/// `LoopLikeOpInterface`, that just dispatches to the implementation for each
1023/// supported loop type.
1024static FailureOr<LoopLikeOpInterface> yieldTiledValuesAndReplaceLoop(
1025 LoopLikeOpInterface loopLikeOp, RewriterBase &rewriter,
1026 ValueRange newInitOperands, YieldTiledValuesFn yieldTiledValuesFn) {
1028 loopLikeOp.getOperation())
1029 .Case<scf::ForOp, scf::ForallOp>(
1030 [&](auto loopOp) -> FailureOr<LoopLikeOpInterface> {
1032 loopOp, rewriter, newInitOperands, yieldTiledValuesFn);
1033 })
1034 .Default([&](auto loopOp) -> FailureOr<LoopLikeOpInterface> {
1035 return rewriter.notifyMatchFailure(loopOp, "unhandled loop type");
1036 });
1037}
1038
1039/// Method to add new init values to a loop nest. Updates `loops` in-place
1040/// with new loops that use the `newInitValues`. The outer-loops are updated
1041/// to yield the new result values of the inner loop. For the innermost loop,
1042/// the call back `getNewYields` is invoked to get the additional values to
1043/// yield form the innermost loop.
1044static LogicalResult addInitOperandsToLoopNest(
1046 ValueRange newInitValues, YieldTiledValuesFn getNewTiledYieldsFn) {
1047 if (loops.empty())
1048 return success();
1049 OpBuilder::InsertionGuard g(rewriter);
1050 rewriter.setInsertionPoint(loops.front());
1051
1053 for (auto &loop : loops.drop_back()) {
1054 rewriter.setInsertionPoint(loop);
1055
1056 // if loops.size() > 1 we assume that scf.for is used for the loops.
1057 auto forLoop = cast<scf::ForOp>(loop.getOperation());
1058
1059 // Create a new loop with the new init values for this loop.
1060 SmallVector<Value> newInits = llvm::to_vector(forLoop.getInitArgs());
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,
1065 [&](OpBuilder &b, Location loc, Value iv, ValueRange iterArgs) {},
1066 forLoop.getUnsignedCmp());
1067
1068 // Merge the body of the new loop with the body of the old loops.
1069 SmallVector<Value> sourceBlockArgs;
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);
1076 rewriter.replaceOp(
1077 forLoop, newLoop.getResults().take_front(forLoop.getNumResults()));
1078 loop = newLoop;
1079 ivs.push_back(newLoop.getInductionVar());
1080 newInitValues = newLoop.getRegionIterArgs().take_back(newInitValues.size());
1081 }
1082
1083 // Update the loop body of the innermost loop to get new yield values.
1084 LoopLikeOpInterface innerMostLoop = loops.back();
1085 FailureOr<LoopLikeOpInterface> newInnerMostLoop =
1086 yieldTiledValuesAndReplaceLoop(innerMostLoop, rewriter, newInitValues,
1087 getNewTiledYieldsFn);
1088
1089 if (failed(newInnerMostLoop))
1090 return innerMostLoop.emitOpError("failed to return additional yields");
1091 loops.back() = newInnerMostLoop.value();
1092
1093 // Make all other loops except the innermost loops yield the values returned
1094 // by the inner loop.
1095 for (auto [outerLoop, innerLoop] :
1096 llvm::zip_equal(loops.drop_back(), loops.drop_front())) {
1097 // Again assume that all the outer loops are scf.for operations.
1098 auto outerForLoop = cast<scf::ForOp>(outerLoop.getOperation());
1099 auto outerLoopYield =
1100 cast<scf::YieldOp>(outerForLoop.getBody()->getTerminator());
1101 SmallVector<Value> newYields =
1102 llvm::to_vector(outerLoopYield.getOperands());
1103 ValueRange additionalYields =
1104 innerLoop->getResults().take_back(newInitValues.size());
1105 newYields.append(additionalYields.begin(), additionalYields.end());
1106 rewriter.setInsertionPoint(outerLoopYield);
1107 rewriter.replaceOpWithNewOp<scf::YieldOp>(outerLoopYield, newYields);
1108 }
1109 return success();
1110}
1111
1112/// Implementation of tiling transformation of `op` that implements the
1113/// `TilingInterface` using `scf.for` to iterate over the tiles.
1114FailureOr<scf::SCFTilingResult>
1115mlir::scf::tileUsingSCF(RewriterBase &rewriter, TilingInterface op,
1116 const scf::SCFTilingOptions &options) {
1117 if (failed(verifyOptions(rewriter, op.getLoc(), options))) {
1118 return failure();
1119 }
1120
1121 OpBuilder::InsertionGuard guard(rewriter);
1122 rewriter.setInsertionPointAfter(op);
1123
1124 // 1. Get the range of the loops that are represented by the operation.
1125 SmallVector<Range> iterationDomain = op.getIterationDomain(rewriter);
1126
1127 // 2. Materialize the tile sizes and/or number of threads;
1128 SmallVector<OpFoldResult> givenTileSizes, numThreads;
1129 std::tie(givenTileSizes, numThreads) =
1130 getUserTileSizesAndNumThreads(rewriter, op, iterationDomain, options);
1131
1132 // Check if it is safe to tile. This is hold over from previous iterations
1133 // of tile to for-all. Consider dropping it.
1134 if (failed(checkTileSizes(op, options.loopType, options.reductionStrategy,
1135 givenTileSizes, numThreads))) {
1136 return failure();
1137 }
1138
1139 // Get the reduction dims
1140 SetVector<unsigned> reductionDims =
1141 getSanitizedReductionDims(givenTileSizes, options);
1142
1143 // 3. If there is an interchange specified, permute the iteration domain and
1144 // the tile sizes.
1145 SmallVector<int64_t> interchangeVector;
1146 if (!options.interchangeVector.empty()) {
1147 interchangeVector = fillInterchangeVector(options.interchangeVector,
1148 iterationDomain.size());
1149 assert(isPermutationVector(interchangeVector) &&
1150 "expected interchange vector to be a permutation");
1151
1152 applyPermutationToVector(iterationDomain, interchangeVector);
1153 applyPermutationToVector(givenTileSizes, interchangeVector);
1154 if (!numThreads.empty())
1155 applyPermutationToVector(numThreads, interchangeVector);
1156 }
1157
1158 FailureOr<TilingResult> tilingResult;
1159 // 4. Define the lambda function used later to generate the body of the
1160 // innermost tiled loop.
1161 GenerateTiledBodyFn innerYieldTiledValuesFn =
1162 [&](RewriterBase &rewriter, Location loc, ValueRange ivs,
1163 ArrayRef<OpFoldResult> tileOffsets, ArrayRef<OpFoldResult> tileSizes,
1164 ValueRange regionIterArgs, SmallVector<Value> &tiledResults,
1167 -> LogicalResult {
1168 // 4b. If interchange was provided, apply inverse of the interchange
1169 // to get back the offsets/sizes in the order to be specified.
1170 SmallVector<OpFoldResult> tileOffsetsVec = llvm::to_vector(tileOffsets);
1171 SmallVector<OpFoldResult> tileSizesVec = llvm::to_vector(tileSizes);
1172 if (!interchangeVector.empty()) {
1173 auto inversePermutation = invertPermutationVector(interchangeVector);
1176 }
1177
1178 // 5. Generate the tiled implementation within the inner most loop.
1179
1180 // 5a. Clone the operation within the loop body.
1181 auto clonedOp = cast<TilingInterface>(
1182 cloneOpAndUpdateDestinationArgs(rewriter, op, regionIterArgs));
1183
1184 // 5b. Early return cloned op if tiling is not happening. We can not
1185 // return the original op because it could lead to `rewriter.replaceOp(op,
1186 // op->getResults())` and users would get crash.
1187 if (llvm::all_of(givenTileSizes, isZeroInteger)) {
1188 tiledResults.append(clonedOp->result_begin(), clonedOp->result_end());
1189 tilingResult =
1190 TilingResult{/*tiledOps=*/{clonedOp}, clonedOp->getResults(),
1191 /*generatedSlices=*/{}};
1192 return success();
1193 }
1194
1195 // 5c. Tile the cloned operation.
1196 SmallVector<InnerTileAlignment> innerTileAlignments =
1197 options.innerTileAlignmentFn
1198 ? options.innerTileAlignmentFn(clonedOp, givenTileSizes,
1199 /*slices=*/{})
1201 tilingResult = getTiledImplementation(
1202 rewriter, clonedOp, options.reductionStrategy, regionIterArgs,
1203 tileOffsetsVec, tileSizesVec, ivs, numThreads, givenTileSizes,
1204 innerTileAlignments, reductionDims);
1205 if (failed(tilingResult)) {
1206 rewriter.eraseOp(clonedOp);
1207 return op.emitOpError("failed to tile operation");
1208 }
1209
1210 // 5d. Delete the cloned operation.
1211 rewriter.eraseOp(clonedOp);
1212
1213 // 5e. Compute the offsets at which the result values are to be inserted
1214 // back into its destinations.
1215 for (auto [index, tiledValue] :
1216 llvm::enumerate(tilingResult->tiledValues)) {
1217 tiledResults.push_back(tiledValue);
1218 SmallVector<OpFoldResult> resultOffset, resultSize;
1220 rewriter, options.reductionStrategy, index, tiledValue, op,
1221 tileOffsetsVec, tileSizesVec, ivs, numThreads, givenTileSizes,
1222 reductionDims, resultOffset, resultSize))) {
1223 for (auto op : tilingResult->tiledOps) {
1224 rewriter.eraseOp(op);
1225 }
1226 return rewriter.notifyMatchFailure(
1227 op, "failed to get slice of result produced");
1228 }
1229 resultOffsets.emplace_back(std::move(resultOffset));
1230 resultSizes.emplace_back(std::move(resultSize));
1231 }
1232
1233 return success();
1234 };
1235
1236 // 6. Find the destination tensors to use for the operation.
1237 FailureOr<SmallVector<Value>> maybeInits = createInitialTensorsForTiling(
1238 rewriter, op, options.reductionStrategy, iterationDomain, numThreads,
1239 givenTileSizes, reductionDims);
1240 if (failed(maybeInits)) {
1241 return rewriter.notifyMatchFailure(
1242 op, "unable to create initial tensors for tiling");
1243 }
1244 SmallVector<Value> &initTensors = maybeInits.value();
1245
1246 // 7. Generate the tiled loops nest using the callback defined above.
1248 {
1249 FailureOr<SmallVector<LoopLikeOpInterface>> loopsOr = generateLoopNest(
1250 rewriter, op.getLoc(), options, iterationDomain, givenTileSizes,
1251 numThreads, initTensors, innerYieldTiledValuesFn);
1252 if (failed(loopsOr))
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());
1257 }
1258
1259 if (loops.empty()) {
1260 // If loops are empty, the tiled op is used as the replacement for the
1261 // untiled op.
1262 return scf::SCFTilingResult{tilingResult->tiledOps,
1263 initTensors,
1264 loops,
1265 tilingResult->tiledValues,
1266 tilingResult->generatedSlices,
1267 {}};
1268 }
1269
1270 auto loopResults = llvm::map_to_vector(loops.front()->getResults(),
1271 [](OpResult r) -> Value { return r; });
1272
1273 // For the full reduction case, there is nothing more to do.
1274 if (options.reductionStrategy == ReductionTilingStrategy::FullReduction) {
1275 return scf::SCFTilingResult{
1276 tilingResult->tiledOps, initTensors, loops, loopResults,
1277 tilingResult->generatedSlices, {}};
1278 }
1279
1280 // The results of the loop needs to be merged.
1281 FailureOr<MergeResult> mergeResult = mergeTilingResults(
1282 rewriter, op, options.reductionStrategy, reductionDims, loopResults);
1283 if (failed(mergeResult)) {
1284 return rewriter.notifyMatchFailure(
1285 op, "Failed to merge partial results from tiling");
1286 }
1287 return scf::SCFTilingResult{tilingResult->tiledOps,
1288 initTensors,
1289 loops,
1290 mergeResult->replacements,
1291 tilingResult->generatedSlices,
1292 mergeResult->mergeOps};
1293}
1294
1295FailureOr<scf::SCFTilingResult>
1296mlir::scf::tileReductionUsingScf(RewriterBase &b,
1297 PartialReductionOpInterface op,
1298 ArrayRef<OpFoldResult> tileSize) {
1299 scf::SCFTilingOptions options;
1300 options.setLoopType(scf::SCFTilingOptions::LoopType::ForOp);
1301 options.setReductionTilingStrategy(
1303 options.setTileSizes(tileSize);
1304 SmallVector<unsigned> reductionDims;
1305 for (auto [index, iteratorType] : llvm::enumerate(op.getLoopIteratorTypes()))
1306 if (iteratorType == utils::IteratorType::reduction)
1307 reductionDims.push_back(index);
1308 options.setReductionDims(reductionDims);
1309 return tileUsingSCF(b, op, options);
1310}
1311
1312//===----------------------------------------------------------------------===//
1313// tileConsumerAndFuseProducersUsingSCF implementation.
1314//===----------------------------------------------------------------------===//
1315
1316/// Return the untiled producer whose slice is used in a tiled consumer. The
1317/// method traverses the tile loop nest (`loops`) if needed, and returns the
1318/// `iter_args` of the outer most that is encountered. Traversing the
1319/// iter_args indicates that this is a destination operand of the consumer. If
1320/// there was no loop traversal needed, the second value of the returned tuple
1321/// is empty.
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)
1332 break;
1333 source = loop.getTiedLoopInit(iterArg);
1334 loopIt++;
1335 }
1336 if (loopIt == loops.rend())
1337 destinationIterArg = source;
1338
1339 auto result = dyn_cast<OpResult>(source->get());
1340 if (result) {
1341 Operation *producer = result.getOwner();
1342 Operation *innermostLoop = loops.back();
1343 // If the producer is already inside the innermost loop (where the slice
1344 // is), it has already been fused. Skip it to avoid infinite loops.
1345 if (innermostLoop->isProperAncestor(producer))
1346 return {OpResult(), std::nullopt};
1347 }
1348
1349 return {result, destinationIterArg};
1350}
1351
1352/// Implementation of fusing producer of a single slice by computing the
1353/// slice of the producer in-place.
1354std::optional<scf::SCFFuseProducerOfSliceResult>
1355mlir::scf::tileAndFuseProducerOfSlice(
1356 RewriterBase &rewriter, tensor::ExtractSliceOp candidateSliceOp,
1358 const InnerTileAlignmentFnTy &fn) {
1359 // 1. Get the producer of the source (potentially walking through
1360 // `iter_args` of nested `scf.for`)
1361 auto [fusableProducer, destinationInitArg] =
1362 getUntiledProducerFromSliceSource(&candidateSliceOp.getSourceMutable(),
1363 loops);
1364 if (!fusableProducer)
1365 return std::nullopt;
1366 unsigned resultNumber = fusableProducer.getResultNumber();
1367
1368 // Resolve the inner-tile alignment hint for the producer in its own iteration
1369 // domain via the control function (consulted only by pack/unpack).
1370 SmallVector<InnerTileAlignment> innerTileAlignments;
1371 if (fn)
1372 if (auto producer = dyn_cast<TilingInterface>(fusableProducer.getOwner()))
1373 innerTileAlignments =
1374 fn(producer, /*tileSizes=*/{}, {candidateSliceOp.getOperation()});
1375
1376 OpBuilder::InsertionGuard g(rewriter);
1377 rewriter.setInsertionPoint(candidateSliceOp);
1378
1379 // 2. Clone the fused producer
1380 // 2a. Compute the destination operands to use for the cloned operation.
1381 SmallVector<Value> origDestinationTensors, clonedOpDestinationTensors;
1382 Operation *fusableProducerOp = fusableProducer.getOwner();
1383 if (isa<DestinationStyleOpInterface>(fusableProducerOp) &&
1385 rewriter, fusableProducerOp->getLoc(), fusableProducerOp,
1386 origDestinationTensors)))
1387 return std::nullopt;
1388
1389 clonedOpDestinationTensors = origDestinationTensors;
1390 if (destinationInitArg &&
1391 isa<DestinationStyleOpInterface>(fusableProducerOp)) {
1392 // 2b. If the producer is also destination style, then to maintain the
1393 // destination passing style, update the destination of the producer to be
1394 // the source of the slice.
1395 clonedOpDestinationTensors[resultNumber] = candidateSliceOp.getSource();
1396 }
1397 // 2c. Clone the fused producer.
1398 Operation *clonedProducerOp = cloneOpAndUpdateDestinationArgs(
1399 rewriter, fusableProducerOp, clonedOpDestinationTensors);
1400 // 2d. Update the source of the candidateSlice to be the cloned producer.
1401 // Easier to just clone the slice with different source since
1402 // replacements and DCE of cloned ops becomes easier
1403 SmallVector<Value> candidateSliceOpOperands =
1404 llvm::to_vector(candidateSliceOp->getOperands());
1405 candidateSliceOpOperands[0] = clonedProducerOp->getResult(resultNumber);
1406 tensor::ExtractSliceOp clonedCandidateSliceOp =
1407 mlir::clone(rewriter, candidateSliceOp,
1408 candidateSliceOp->getResultTypes(), candidateSliceOpOperands);
1409
1410 // 3. Generate the tiled implementation of the producer of the source
1411 FailureOr<TilingResult> tileAndFuseResult =
1413 rewriter, clonedCandidateSliceOp,
1414 clonedProducerOp->getResult(resultNumber), innerTileAlignments);
1415 if (failed(tileAndFuseResult))
1416 return std::nullopt;
1417 // Note: Do not delete the candidateSliceOp, since its passed in from the
1418 // caller.
1419 rewriter.replaceAllUsesWith(candidateSliceOp,
1420 tileAndFuseResult->tiledValues[0]);
1421 rewriter.eraseOp(clonedCandidateSliceOp);
1422 rewriter.eraseOp(clonedProducerOp);
1423
1424 // 3. If the slice is for a destination operand, for example,
1425 //
1426 // ```mlir
1427 // %0 = linalg.init
1428 // %1 = linalg.fill .. outs(%0 : )
1429 // %2 = scf.for .. iter_args(%arg0 = %1) {
1430 // %3 = scf.for .. iter_args(%arg1 = %arg0) {
1431 // %4 = tensor.extract_slice %arg1 [..]
1432 // .. = linalg.matmul .. outs(%4 : )
1433 // }
1434 // }
1435 // ```
1436 //
1437 // the IR is currently
1438 //
1439 // ```
1440 // %0 = linalg.init
1441 // %1 = linalg.fill
1442 // %2 = scf.for .. iter_args(%arg0 = %1 /* incorrect value */ ) {
1443 // %3 = scf.for .. iter_args(%arg1 = %arg0) {
1444 // %4 = tensor.extract_slice %arg1[..]
1445 // %5 = linalg.fill .. outs(%4 : )
1446 // .. = linalg.matmul .. outs(%5 : )
1447 // }
1448 // }
1449 // ```
1450 //
1451 // The untiled `linalg.fill` is still used as the `init_value` since it
1452 // was originally a destination operand of the untiled `linalg.matmul`.
1453 // When fusing an operand that is a destination operand, the iter_arg of
1454 // the outer most loop should be changed to use the destination of the
1455 // fused operation. With this the IR will be.
1456 //
1457 // ```
1458 // %0 = linalg.init
1459 // %1 = scf.for .. iter_args(%arg0 = %0 /* corrected value */ ) {
1460 // %2 = scf.for .. iter_args(%arg1 = %arg0) {
1461 // %3 = tensor.extract_slice %arg1[..]
1462 // %4 = linalg.fill .. outs(%3 : )
1463 // .. = linalg.matmul .. outs(%4 : )
1464 // }
1465 // }
1466 // ```
1467 if (destinationInitArg &&
1468 isa<DestinationStyleOpInterface>(fusableProducerOp) && !loops.empty()) {
1469 loops.front()
1470 ->getOpOperands()[destinationInitArg.value()->getOperandNumber()]
1471 .set(origDestinationTensors[resultNumber]);
1472 }
1473 return scf::SCFFuseProducerOfSliceResult{
1474 fusableProducer, tileAndFuseResult->tiledValues[0],
1475 tileAndFuseResult->tiledOps, tileAndFuseResult->generatedSlices};
1476}
1477
1478/// Reconstruct the fused producer from within the tiled-and-fused code.
1479FailureOr<SmallVector<Operation *>> mlir::scf::yieldReplacementForFusedProducer(
1480 RewriterBase &rewriter, tensor::ExtractSliceOp sliceOp,
1481 scf::SCFFuseProducerOfSliceResult fusedProducerInfo,
1483 ArrayRef<unsigned> yieldResultNumber) {
1484 if (loops.empty())
1485 return success();
1486
1487 Operation *originalOwner = fusedProducerInfo.origProducer.getOwner(),
1488 *tiledOwner = fusedProducerInfo.tiledOps[0];
1489
1490 Location loc = originalOwner->getLoc();
1491 // a. collect all init Value to be appended
1492 SmallVector<unsigned> initNumberList =
1493 yieldResultNumber.empty() ? llvm::to_vector(llvm::seq<unsigned>(
1494 0, originalOwner->getNumResults()))
1495 : llvm::to_vector(yieldResultNumber);
1496 SmallVector<Value> initValueList;
1497 for (const auto &resultNumber : initNumberList) {
1498 FailureOr<Value> initValue = tensor::getOrCreateDestination(
1499 rewriter, loc, originalOwner->getResult(resultNumber));
1500 if (succeeded(initValue)) {
1501 initValueList.push_back(initValue.value());
1502 } else {
1503 return failure();
1504 }
1505 }
1506
1507 SmallVector<Operation *> generatedSlices;
1508 YieldTiledValuesFn newYieldValuesFn =
1509 [&](RewriterBase &innerRewriter, Location loc, ValueRange /*ivs*/,
1510 ValueRange newRegionIterArgs, SmallVector<Value> &tiledResult,
1512 SmallVector<SmallVector<OpFoldResult>> &tiledSizes) -> LogicalResult {
1513 OpBuilder::InsertionGuard g(innerRewriter);
1514
1515 // get sliceOp tile information
1516 SmallVector<OpFoldResult> sliceOffset = sliceOp.getMixedOffsets(),
1517 sliceSizes = sliceOp.getMixedSizes();
1518
1519 // expect all strides of sliceOp being 1
1520 if (!llvm::all_of(sliceOp.getMixedStrides(), isOneInteger))
1521 return failure();
1522
1523 unsigned sliceResultNumber =
1524 fusedProducerInfo.origProducer.getResultNumber();
1525
1526 auto tilableOp = cast<TilingInterface>(originalOwner);
1527 // b. get iterDomain Offset and Sizes based on sliceOp tile
1528 SmallVector<OpFoldResult> iterDomainOffset, iterDomainSizes;
1529 // Set insertion point before any operations that might create new SSA
1530 // values used in offset/size computations. This ensures all values created
1531 // by getIterationDomainTileFromResultTile and getResultTilePosition
1532 // dominate the extract_slice operations created later.
1533 if (auto tiledDestStyleOp =
1534 dyn_cast<DestinationStyleOpInterface>(tiledOwner)) {
1535 rewriter.setInsertionPoint(tiledDestStyleOp);
1536 }
1537 // skip tensor.pack/unpack/pad, which expects single opResult
1538 if (tilableOp->getNumResults() > 1 &&
1539 failed(tilableOp.getIterationDomainTileFromResultTile(
1540 rewriter, sliceResultNumber, sliceOffset, sliceSizes,
1541 iterDomainOffset, iterDomainSizes))) {
1542 // In theory, it is unnecessary to raise an error here. Actually
1543 // although it fails to reconstruct the result tensor, it should not
1544 // broke current fusion anyway. The reason why we must return failure
1545 // currently is that the callback function `newYieldValuesFn` will be
1546 // called after new init operand(s) has already been appended. It will
1547 // take more refactoring to make sure the init operands are added
1548 // consistently in the future. For more details, please refer to:
1549 // https://github.com/llvm/llvm-project/pull/93144#discussion_r1643760814
1550 return failure();
1551 }
1552
1553 // c. calculate offsets and sizes info of all OpResults respectively based
1554 // on iteration Domain Tile
1555 SmallVector<SmallVector<OpFoldResult>> offsetList, sizesList;
1556 for (const auto &resultNumber : initNumberList) {
1557 if (resultNumber == sliceResultNumber) {
1558 offsetList.push_back(sliceOffset);
1559 sizesList.push_back(sliceSizes);
1560 } else {
1561 assert(!iterDomainOffset.empty() && !iterDomainSizes.empty());
1562 // infer result tile according to the iteration domain tile
1563 SmallVector<OpFoldResult> offset, sizes;
1564 if (failed(tilableOp.getResultTilePosition(
1565 rewriter, resultNumber, iterDomainOffset, iterDomainSizes,
1566 offset, sizes))) {
1567 return failure();
1568 }
1569 offsetList.push_back(offset);
1570 sizesList.push_back(sizes);
1571 }
1572 }
1573
1574 // d. create `extract_slice` for `iter_args` for DPS operation if
1575 // necessary
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],
1582 SmallVector<OpFoldResult>(offsetList[index].size(),
1583 rewriter.getIndexAttr(1)));
1584 generatedSlices.push_back(destSlice);
1585 unsigned resultNumber = initNumberList[index];
1586 rewriter.modifyOpInPlace(tiledDestStyleOp, [&]() {
1587 tiledDestStyleOp.getDpsInitsMutable()[resultNumber].set(destSlice);
1588 });
1589 }
1590 }
1591
1592 // e. prepare tiled offset and sizes for later `insert_slice` creation by
1593 // caller
1594 Block *block = rewriter.getInsertionPoint()->getBlock();
1595 rewriter.setInsertionPoint(block->getTerminator());
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]);
1600 }
1601 return success();
1602 };
1603
1604 if (failed(addInitOperandsToLoopNest(rewriter, loops, initValueList,
1605 newYieldValuesFn))) {
1606 return failure();
1607 }
1608 return generatedSlices;
1609}
1610
1611namespace {
1612
1613//===----------------------------------------------------------------------===//
1614// SliceTrackingListener
1615//===----------------------------------------------------------------------===//
1616
1617/// This class is a listener for tracking the insertion and removal of
1618/// `tensor.extract_slice` ops in a worklist. This can be used in a greedy
1619/// fusion algorithm to apply cleanup patterns in between fusion steps.
1620class SliceTrackingListener : public RewriterBase::Listener {
1621public:
1622 explicit SliceTrackingListener(
1623 std::optional<FrozenRewritePatternSet> patterns);
1624 SliceTrackingListener() = default;
1625
1626 /// Adds the given list of operations to the worklist, and if present,
1627 /// applies the list of `patterns` to the newly added operations. This only
1628 /// processes the given operations and any newly inserted ones by the
1629 /// pattern set.
1630 LogicalResult insertAndApplyPatterns(ArrayRef<Operation *> newOps);
1631
1632 /// Add to the new operation worklist if it is an extract_slice.
1633 void notifyOperationInserted(Operation *op,
1634 OpBuilder::InsertPoint previous) override;
1635
1636 /// Shared helper for operation removal from the worklist.
1637 void removeOp(Operation *op);
1638
1639 /// Remove the operation from the worklist.
1640 void notifyOperationErased(Operation *op) override;
1641
1642 /// Remove the operation from the worklist.
1643 void notifyOperationReplaced(Operation *op, ValueRange replacement) override;
1644
1645 /// The worklist for this transformation keeps track of the slices to visit
1646 /// next for fusion.
1647 std::deque<tensor::ExtractSliceOp> worklist;
1648
1649private:
1650 /// Optional pattern set to apply when adding new operations to the
1651 /// worklist.
1652 std::optional<FrozenRewritePatternSet> patterns = std::nullopt;
1653};
1654
1655SliceTrackingListener::SliceTrackingListener(
1656 std::optional<FrozenRewritePatternSet> p) {
1657 patterns = std::move(p);
1658}
1659
1660LogicalResult
1661SliceTrackingListener::insertAndApplyPatterns(ArrayRef<Operation *> ops) {
1662 for (Operation *op : ops) {
1663 if (auto slice = dyn_cast<tensor::ExtractSliceOp>(op))
1664 worklist.push_back(slice);
1665 }
1666
1667 if (!patterns)
1668 return success();
1669
1671 ops, patterns.value(),
1672 GreedyRewriteConfig().setListener(this).setStrictness(
1673 GreedyRewriteStrictness::ExistingAndNewOps));
1674}
1675
1676void SliceTrackingListener::notifyOperationInserted(
1677 Operation *op, OpBuilder::InsertPoint previous) {
1678 auto slice = dyn_cast<tensor::ExtractSliceOp>(op);
1679 if (!slice)
1680 return;
1681 worklist.push_back(slice);
1682}
1683
1684// Scan the worklist for the given op and remove it if present. The
1685// expectation is for the worklist to be small and for removal to be
1686// relatively rare.
1687void SliceTrackingListener::removeOp(Operation *op) {
1688 if (!isa<tensor::ExtractSliceOp>(op))
1689 return;
1690 auto iter = worklist.begin();
1691 while (iter != worklist.end()) {
1692 if (*iter == op)
1693 break;
1694 iter++;
1695 }
1696 if (iter == worklist.end())
1697 return;
1698
1699 worklist.erase(iter);
1700}
1701
1702void SliceTrackingListener::notifyOperationErased(Operation *op) {
1703 removeOp(op);
1704}
1705
1706void SliceTrackingListener::notifyOperationReplaced(Operation *op,
1708 removeOp(op);
1709}
1710
1711//===----------------------------------------------------------------------===//
1712// ReplacementListener
1713//===----------------------------------------------------------------------===//
1714
1715/// Listener that tracks updates replacements for values which can be mutated.
1716/// This listener runs on top of the existing listener for the rewriter,
1717/// to make sure external users can still run listeners.
1718class ReplacementListener : public RewriterBase::ForwardingListener {
1719public:
1720 ReplacementListener(DenseMap<Value, Value> &replacements,
1721 OpBuilder::Listener *listener)
1722 : ForwardingListener(listener), replacements(replacements) {}
1723
1724 void updateReplacementValues(ValueRange origValues,
1725 ValueRange replaceValues) {
1726 // This can probably be written better, but just iterates over the map
1727 // and the new replacements for now.
1728 for (auto &[key, val] : replacements) {
1729 for (auto [orig, replace] : llvm::zip_equal(origValues, replaceValues)) {
1730 if (val == orig) {
1731 val = replace;
1732 }
1733 }
1734 }
1735 }
1736
1737 void notifyOperationReplaced(Operation *op, Operation *newOp) override {
1738 ForwardingListener::notifyOperationReplaced(op, newOp);
1739 updateReplacementValues(op->getResults(), newOp->getResults());
1740 }
1741
1742 void notifyOperationReplaced(Operation *op, ValueRange values) override {
1743 ForwardingListener::notifyOperationReplaced(op, values);
1744 updateReplacementValues(op->getResults(), values);
1745 }
1746
1747private:
1748 DenseMap<Value, Value> &replacements;
1749};
1750
1751} // namespace
1752
1753/// Implementation of tile consumer and fuse producer greedily.
1754FailureOr<scf::SCFTileAndFuseResult>
1755mlir::scf::tileConsumerAndFuseProducersUsingSCF(
1756 RewriterBase &rewriter, TilingInterface consumer,
1757 const scf::SCFTileAndFuseOptions &options) {
1758 // This transformation is only valid for ops that return values (i.e. not
1759 // valid to use with operations that have memref operands).
1760 if (!consumer->getNumResults()) {
1761 return rewriter.notifyMatchFailure(
1762 consumer, "invalid pattern for op with no results");
1763 }
1764
1765 // 1. First tile the consumer.
1766 SetVector<Operation *> fusedProducers, tiledAndFusedOps;
1767
1768 FailureOr<scf::SCFTilingResult> tilingResult =
1769 tileUsingSCF(rewriter, consumer, options.tilingOptions);
1770
1771 if (failed(tilingResult))
1772 return rewriter.notifyMatchFailure(consumer, "failed to tile consumer");
1773 tiledAndFusedOps.insert_range(tilingResult->tiledOps);
1774
1775 DenseMap<Value, Value> replacements;
1776 for (auto [origVal, replacement] :
1777 llvm::zip_equal(consumer->getResults(), tilingResult->replacements)) {
1778 replacements[origVal] = replacement;
1779 }
1780
1781 // If there are no loops generated, fusion is immaterial.
1782 auto &loops = tilingResult->loops;
1783 if (loops.empty()) {
1784 return scf::SCFTileAndFuseResult{fusedProducers, tiledAndFusedOps, loops,
1785 replacements};
1786 }
1787
1788 // Since the loop gets potentially replaced during fusion, we need to track
1789 // the mutation of replacement values. To do this, we attach a listener to
1790 // update the replacements as they happen.
1791 OpBuilder::Listener *previousListener = rewriter.getListener();
1792 llvm::scope_exit resetListener(
1793 [&]() { rewriter.setListener(previousListener); });
1794 ReplacementListener replaceListener(replacements, previousListener);
1795 rewriter.setListener(&replaceListener);
1796
1797 // 2. Typically, the operands of the tiled operation are slices of the
1798 // operands of the untiled operation. These are expressed in IR using
1799 // `tensor.extract_slice` operations with source being the operands of
1800 // the untiled operation. Create a worklist of these
1801 // `tensor.extract_slice` operations. If the producers of the source of
1802 // the `tensor.extract_slice` can be tiled such that the tiled value is
1803 // generated in-place, that effectively tiles + fuses the operations.
1804 struct WorklistItem {
1805 tensor::ExtractSliceOp candidateSlice;
1806 SCFTileAndFuseOptions::ControlFnResult controlFnResult;
1807 };
1808
1809 SliceTrackingListener sliceTracker =
1810 SliceTrackingListener(options.cleanupPatterns);
1811
1812 if (failed(
1813 sliceTracker.insertAndApplyPatterns(tilingResult->generatedSlices))) {
1814 return rewriter.notifyMatchFailure(consumer, "cleanup patterns failed");
1815 }
1816 OpBuilder::InsertionGuard g(rewriter);
1817 while (!sliceTracker.worklist.empty()) {
1818 auto candidateSlice = sliceTracker.worklist.front();
1819 sliceTracker.worklist.pop_front();
1820
1821 auto [fusableProducer, destinationInitArg] =
1822 getUntiledProducerFromSliceSource(&candidateSlice.getSourceMutable(),
1823 loops);
1824 if (!fusableProducer)
1825 continue;
1826
1827 std::optional<SCFTileAndFuseOptions::ControlFnResult> controlFnResult =
1828 options.fusionControlFn(candidateSlice, fusableProducer,
1829 destinationInitArg.has_value());
1830 if (!controlFnResult)
1831 continue;
1832
1833 WorklistItem worklistItem = {candidateSlice, controlFnResult.value()};
1834
1835 // The operands of the fused producer might themselved be slices of
1836 // values produced by operations that implement the `TilingInterface`.
1837 // Add these operations to the worklist.
1838 std::optional<scf::SCFFuseProducerOfSliceResult> fusedResult =
1839 tileAndFuseProducerOfSlice(rewriter, worklistItem.candidateSlice, loops,
1840 options.tilingOptions.innerTileAlignmentFn);
1841 if (!fusedResult)
1842 continue;
1843
1844 SmallVector<Operation *> worklistCandidates = fusedResult->generatedSlices;
1845
1846 if (worklistItem.controlFnResult.yieldProducerReplacement) {
1847 // Reconstruct and yield all opResult of fusableProducerOp by default.
1848 // The caller can specific which one to yield by designating optional
1849 // argument named `yieldResultNumber` of
1850 // `yieldReplacementForFusedProducer`.
1851 Operation *fusableProducerOp = fusedResult->origProducer.getOwner();
1852 FailureOr<SmallVector<Operation *>> newSlices =
1854 worklistItem.candidateSlice,
1855 fusedResult.value(), loops);
1856 if (failed(newSlices)) {
1857 return rewriter.notifyMatchFailure(
1858 fusableProducerOp, "failed to replacement value for this "
1859 "operation from within the tiled loop");
1860 }
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() -
1866 fusableProducerOp->getNumResults() + index);
1867 }
1868 }
1869 if (Operation *tiledAndFusedOp =
1870 fusedResult->tiledAndFusedProducer.getDefiningOp()) {
1871 fusedProducers.insert(fusedResult->origProducer.getDefiningOp());
1872 tiledAndFusedOps.insert(tiledAndFusedOp);
1873 }
1874
1875 if (failed(sliceTracker.insertAndApplyPatterns(worklistCandidates))) {
1876 return rewriter.notifyMatchFailure(consumer, "cleanup patterns failed");
1877 }
1878 }
1879
1880 return scf::SCFTileAndFuseResult{fusedProducers, tiledAndFusedOps, loops,
1881 replacements};
1882}
1883
1884//===----------------------------------------------------------------------===//
1885// tileAndFuseConsumerUsingSCF implementation.
1886//===----------------------------------------------------------------------===//
1887
1888/// A utility function that checks whether the only use of the result of a
1889/// tensor.insert_slice op is in a scf.yield op.
1890static LogicalResult
1891checkAssumptionForFusingConsumer(tensor::InsertSliceOp candidateSliceOp) {
1892 Value result = candidateSliceOp.getResult();
1893 Value::use_range uses = result.getUses();
1894 if (!llvm::hasSingleElement(uses)) {
1895 LLVM_DEBUG(llvm::dbgs() << "Too many uses of the candidate slice op\n");
1896 return failure();
1897 }
1898 OpOperand &operandUse = (*uses.begin());
1899 Operation *userOp = operandUse.getOwner();
1900 if (!isa<scf::YieldOp>(userOp)) {
1901 LLVM_DEBUG(llvm::dbgs()
1902 << "Expected scf.yield to be the only user, but got -> "
1903 << (*userOp));
1904 return failure();
1905 }
1906 if (result.getDefiningOp()->getBlock() != userOp->getBlock()) {
1907 LLVM_DEBUG(llvm::dbgs() << "Expected tensor.insert_slice and scf.yield to "
1908 "be in the same block\n");
1909 return failure();
1910 }
1911 return success();
1912}
1913
1914/// An utility to get the first user of the given loopOp. If any of user stay
1915/// in different block of loopOp, return failure.
1916static FailureOr<Operation *> getFirstUserOfLoop(Operation *loopOp) {
1917 if (!isa<LoopLikeOpInterface>(loopOp))
1918 return failure();
1919 Operation *firstUserOfLoop = nullptr;
1920 for (Operation *userOp : loopOp->getUsers()) {
1921 // `ParallelInsertSlice` located inside `InParallelOp` has no same parent
1922 // block with any other types of operation. Thus, just redirecting to its
1923 // parent `InParallelOp`. E.g.
1924 //
1925 // ```
1926 // %1 = scf.for {
1927 // ...
1928 // }
1929 // %2 = consumerOp ins(%1, ...)
1930 // scf.forall.in_parallel {
1931 // tensor.parallel_insert_slice %1
1932 // }
1933 // ```
1934 // where `InParallelOp` but not `ParallelInsertSlice` stays in the same
1935 // same block with `consumerOp`.
1936 if (isa<tensor::ParallelInsertSliceOp>(userOp))
1937 userOp = userOp->getParentOfType<scf::InParallelOp>();
1938
1939 if (loopOp->getBlock() != userOp->getBlock())
1940 return failure();
1941
1942 if (!firstUserOfLoop || userOp->isBeforeInBlock(firstUserOfLoop))
1943 firstUserOfLoop = userOp;
1944 }
1945 return firstUserOfLoop;
1946}
1947
1948/// This utility currently checks whether the first userOp of loop is NOT
1949/// before the last defineOp of consumer operand. Because that we need to move
1950/// the whole loop structure right before the `firstUserOfLoop`. This utility
1951/// thus helps ensuring that no invalid IR is formed, i.e. no backward slice
1952/// of consumerOp is dominated by the `firstUserOfLoop`. Saying that:
1953///
1954/// ```
1955/// %0 = scf.for() {
1956/// ...
1957/// }
1958/// ...
1959/// %1 = firstUserOfLoop(%0)
1960/// ...
1961/// %2 = lastDefOfConsumerOperand
1962/// ...
1963/// %3 = consumerOp(%2)
1964/// ```
1965///
1966/// If the `firstUserOfLoop` is before `lastDefOfConsumerOperand`, then it
1967/// would be invalid to move the `loopOp` right before the `firstUserOfLoop`,
1968/// a.k.a. use-def chain violation:
1969///
1970/// ```
1971/// %0:2 = scf.for() {
1972/// // use before define error
1973/// %3 = tiledConsumerOp(%2)
1974/// }
1975/// %1 = firstUserOfLoop(%0)
1976/// ...
1977/// %2 = lastDefOfConsumerOperand
1978/// ```
1979///
1980/// @param loopOp: loop operation
1981/// @param consumerOp: consumer operation
1982/// @param reorderOperations: the flag controls whether to reorder the
1983/// backward slice w.r.t. the defineOp of `consumerOp` operands.
1984/// @return: computed backward slice of consumerOp, but excluding those
1985/// already dominates `firstUserOfLoop`.
1986static FailureOr<llvm::SetVector<Operation *>>
1988 bool reorderOperations) {
1989 FailureOr<Operation *> firstUserOfLoop = getFirstUserOfLoop(loopOp);
1990 if (failed(firstUserOfLoop))
1991 return failure();
1992
1994 DominanceInfo dominanceInfo;
1995 options.inclusive = true;
1996 options.omitBlockArguments = true;
1997 bool includeLoopOp = false;
1998 options.filter = [&](Operation *op) {
1999 if (op == loopOp) {
2000 includeLoopOp = true;
2001 return false;
2002 }
2003 // Cut off the slice to not include any operation that already dominates
2004 // firstUserOfLoop.
2005 return !dominanceInfo.properlyDominates(op, *firstUserOfLoop);
2006 };
2008 for (auto operand : consumerOp->getOperands()) {
2009 LogicalResult result = getBackwardSlice(operand, &slice, options);
2010 assert(result.succeeded() && "expected a backward slice");
2011 (void)result;
2012 }
2013
2014 if (!slice.empty()) {
2015 // If consumerOp has one producer, which is also the user of loopOp.
2016 // E.g.
2017 // ```
2018 // %0 = %loopOp
2019 // %1 = consumerOp1 ins(%0)
2020 // %2 = consumerOp2 ins(%0, %1)
2021 // ```
2022 // We can not fuse consumerOp2 into loopOp due to UD chain, unless
2023 // consumerOp1 has already been fused into loopOp before.
2024 if (includeLoopOp || !reorderOperations)
2025 return failure();
2026 }
2027
2028 return slice;
2029}
2030
2031/// Fetches the OpOperand of the first valid user (and use) of the value `val`
2032/// which implements `TilingInterface` and `DestinationStyleOpInterface`.
2033/// Returns failure otherwise.
2034static FailureOr<OpOperand *> getConsumerFromLoopUses(RewriterBase &rewriter,
2035 Operation *loopOp,
2036 unsigned resultNumber) {
2037 if (!isa<LoopLikeOpInterface>(loopOp))
2038 return failure();
2039 Value val = loopOp->getResult(resultNumber);
2040 Block *loopBlock = loopOp->getBlock();
2041 for (OpOperand &opOperand : val.getUses()) {
2042 Operation *consumerOp = opOperand.getOwner();
2043 // Step 1. Check if the user is tilable.
2044 if (!isa<TilingInterface>(consumerOp) ||
2045 !isa<DestinationStyleOpInterface>(consumerOp)) {
2046 // TODO: We have to init result of consumer before scf.for, use
2047 // DestinationStyleOpInterface to get result shape from init for now.
2048 // Add support for other op such as op has InferTypeOpInterface.
2049 continue;
2050 }
2051 // Step 2. Check if user stay in the same block.
2052 if (loopBlock != consumerOp->getBlock())
2053 continue;
2054 // Step 3. Check if user has succeeding user. Otherwise, it usually
2055 // represents already tiled.
2056 if (consumerOp->use_empty())
2057 continue;
2058 // Step 4. Check assumption for loop with `reorderOperations` enabled.
2059 FailureOr<llvm::SetVector<Operation *>> slice =
2060 checkAssumptionForLoop(loopOp, consumerOp, true);
2061 if (failed(slice))
2062 continue;
2063 // Step 5. If backward sice is not empty, move them before
2064 // firstUserOfLoop.
2065 if (!slice->empty()) {
2066 mlir::topologicalSort(*slice);
2067 FailureOr<Operation *> firstUserOfLoop = getFirstUserOfLoop(loopOp);
2068 assert(succeeded(firstUserOfLoop) && "First user of loop is not found");
2069 for (auto op : *slice) {
2070 rewriter.moveOpBefore(op, *firstUserOfLoop);
2071 }
2072 }
2073 return &opOperand;
2074 }
2075 return failure();
2076}
2077
2078/// Fetch the untiled consumer of the outermost scf.for's result which is
2079/// yielded by a tensor.insert_slice from the innermost scf.for. This function
2080/// makes the following assumptions :
2081/// 1. tensor.insert_slice has scf.yield as its only user.
2082/// 2. scf.for's corresponding result has only one use.
2083/// 3. The `loops` passed in are perfectly nested `scf.for` operations.
2084static FailureOr<OpOperand *>
2086 tensor::InsertSliceOp candidateSliceOp,
2088 assert(!loops.empty() && "unexpected loops to be empty");
2089 // 1. Expect slice to be part of the body of the inner most loop.
2090 Operation *containingOp = candidateSliceOp->getParentOp();
2091 if (containingOp != loops.back()) {
2092 return rewriter.notifyMatchFailure(
2093 candidateSliceOp,
2094 "expected slice to be within body of inner-most loop");
2095 }
2096
2097 // 2. Check that the loop is perfectly nested.
2098 if (!isPerfectlyNestedForLoops(loops)) {
2099 return rewriter.notifyMatchFailure(
2100 candidateSliceOp, "expected passed loops to be perfectly nested.");
2101 }
2102
2103 if (failed(checkAssumptionForFusingConsumer(candidateSliceOp)))
2104 return failure();
2105 Value sliceResult = candidateSliceOp.getResult();
2106
2107 // 3. Fetch the corresponding output.
2108 OpOperand &yieldOpOperand = (*sliceResult.getUses().begin());
2109 unsigned resultNumber = yieldOpOperand.getOperandNumber();
2110
2111 scf::ForOp topLevelForOp = cast<scf::ForOp>(loops.front().getOperation());
2112
2113 return getConsumerFromLoopUses(rewriter, topLevelForOp, resultNumber);
2114}
2115
2116/// Fetch the first untiled consumer of a scf.forall's result which is yielded
2117/// by a tensor.parallel_insert_slice.
2118static FailureOr<OpOperand *>
2120 tensor::ParallelInsertSliceOp candidateSliceOp,
2122 assert(!loops.empty() && "unexpected loops to be empty");
2123 // 1. Check that the surrounding loop is a single scf.forall loop.
2124 if (loops.size() != 1) {
2125 return rewriter.notifyMatchFailure(
2126 candidateSliceOp, "expected single surrounding scf.forall");
2127 }
2128 auto forallOp = dyn_cast<scf::ForallOp>(loops.front().getOperation());
2129 if (!forallOp) {
2130 return rewriter.notifyMatchFailure(
2131 candidateSliceOp, "expected single surrounding scf.forall");
2132 }
2133
2134 // 2. Fetch the corresponding output
2135 Value sliceDest = candidateSliceOp.getDest();
2136 auto iterArg = dyn_cast<BlockArgument>(sliceDest);
2137 if (!iterArg)
2138 return failure();
2139 if (iterArg.getOwner()->getParentOp() != forallOp)
2140 return failure();
2141
2142 unsigned resultNumber =
2143 forallOp.getTiedOpResult(forallOp.getTiedOpOperand(iterArg))
2144 .getResultNumber();
2145
2146 return getConsumerFromLoopUses(rewriter, forallOp, resultNumber);
2147}
2148
2149/// A utility to fetch an untiled consumer of
2150/// tensor.insert_slice/tensor.parallel_insert_slice.
2151static FailureOr<SmallVector<OpOperand *>> getUntiledConsumerOperandsFromSlices(
2152 RewriterBase &rewriter, ArrayRef<Operation *> sliceOps,
2154 assert(!loops.empty() && "unexpected empty loops");
2155 assert(!sliceOps.empty() && "unexpected empty list of candidate slices");
2156 SmallVector<OpOperand *> fusedOperands;
2157 for (auto sliceOp : sliceOps) {
2158 FailureOr<OpOperand *> fusedOperand =
2160 .Case<tensor::InsertSliceOp, tensor::ParallelInsertSliceOp>(
2161 [&](auto op) {
2162 return getUntiledConsumerFromSlice(rewriter, op, loops);
2163 })
2164 .Default([&](Operation *op) {
2165 return rewriter.notifyMatchFailure(op, "unhandled slice type");
2166 });
2167 if (failed(fusedOperand)) {
2168 return failure();
2169 }
2170 if (!fusedOperands.empty() &&
2171 fusedOperand.value()->getOwner() != fusedOperands.front()->getOwner()) {
2172 return rewriter.notifyMatchFailure(
2173 fusedOperand.value()->getOwner(),
2174 "all candidate slices must be to the same consumer");
2175 }
2176 fusedOperands.push_back(fusedOperand.value());
2177 }
2178 return fusedOperands;
2179}
2180
2181template <typename InsertSliceOpTy>
2182static tensor::InsertSliceOp cloneAsInsertSlice(RewriterBase &rewriter,
2183 InsertSliceOpTy sliceOp);
2184
2185template <>
2186tensor::InsertSliceOp
2188 tensor::InsertSliceOp insertSliceOp) {
2189 return cast<tensor::InsertSliceOp>(
2190 rewriter.clone(*insertSliceOp.getOperation()));
2191}
2192
2193template <>
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());
2200}
2201
2202static SmallVector<tensor::InsertSliceOp>
2204 ArrayRef<Operation *> candidateSlices) {
2205 assert(!candidateSlices.empty() &&
2206 "unexpected empty list of slices to clone");
2208 for (auto sliceOp : candidateSlices) {
2210 .Case<tensor::InsertSliceOp, tensor::ParallelInsertSliceOp>(
2211 [&](auto op) {
2212 auto clonedOp = cloneAsInsertSlice(rewriter, op);
2213 clonedSlices.push_back(clonedOp);
2214 })
2215 // Assert here assuming this has already been checked.
2216 .DefaultUnreachable(
2217 "unexpected slice type while cloning as insert slice");
2218 }
2219 return clonedSlices;
2220}
2221
2222static FailureOr<scf::SCFFuseConsumerOfSliceResult>
2224 ArrayRef<OpOperand *> consumerOpOperands,
2225 ArrayRef<Operation *> candidateSlices,
2227 const mlir::scf::InnerTileAlignmentFnTy &fn) {
2228 assert(!loops.empty() && "expected loops to be not empty");
2229
2230 // Resolve the inner-tile alignment hint for the consumer in its own iteration
2231 // domain via the control function (consulted only by pack/unpack).
2232 SmallVector<InnerTileAlignment> innerTileAlignments;
2233 if (fn)
2234 if (auto consumer = dyn_cast<TilingInterface>(consumerOp))
2235 innerTileAlignments = fn(consumer, /*tileSizes=*/{}, candidateSlices);
2236
2237 // 1. Check assumption for loop with `reorderOperations` disabled.
2238 if (failed(checkAssumptionForLoop(loops.front(), consumerOp, false))) {
2239 return rewriter.notifyMatchFailure(
2240 loops.front(), "the first user of loop should not dominate any define "
2241 "of consumer operand(s)");
2242 }
2243
2244 LoopLikeOpInterface outerMostLoop = loops.front();
2245 LoopLikeOpInterface innerMostLoop = loops.back();
2246
2247 OpBuilder::InsertionGuard g(rewriter);
2248 // 2. Check consumer is not using scf loop's output as init.
2249 auto dstOp = dyn_cast<DestinationStyleOpInterface>(consumerOp);
2250 if (!dstOp)
2251 return rewriter.notifyMatchFailure(consumerOp,
2252 "consumer op is not DPS operation");
2253 if (llvm::any_of(consumerOpOperands, [&](OpOperand *opOperand) {
2254 return dstOp.isDpsInit(opOperand);
2255 })) {
2256 return rewriter.notifyMatchFailure(
2257 consumerOp,
2258 "consumer op taking the result of scf.for as init is not supported");
2259 }
2260 SmallVector<Value> newInits = llvm::to_vector(dstOp.getDpsInits());
2261
2262 // 3. Move the whole loop structure right before firstUserOfLoop, the
2263 // dominance should be already ensured by `checkAssumptionForLoop`.
2264 FailureOr<Operation *> firstUserOfLoop = getFirstUserOfLoop(outerMostLoop);
2265 if (failed(firstUserOfLoop)) {
2266 return rewriter.notifyMatchFailure(
2267 outerMostLoop, "could not find the first user of outer most loop");
2268 }
2269 rewriter.moveOpBefore(outerMostLoop, *firstUserOfLoop);
2270
2271 // 4. Set insertion point before terminator op of the loop and create a new
2272 // tensor.insert_slice. In the scf.for case this is a clone of the
2273 // candidateSliceOp whereas in the scf.forall case this is created from the
2274 // operands of tensor.parallel_insert_slice.
2275 if (auto sliceOp =
2276 dyn_cast<tensor::ParallelInsertSliceOp>(candidateSlices.front())) {
2277 auto newForallOp = cast<scf::ForallOp>(innerMostLoop.getOperation());
2278 rewriter.setInsertionPoint(newForallOp.getTerminator());
2279 } else {
2280 rewriter.setInsertionPoint(candidateSlices.front());
2281 }
2282 // 5.a. Clone all the candidate slices as equivalent insert slice ops.
2283 SmallVector<tensor::InsertSliceOp> clonedInsertSlices =
2284 cloneAsInsertSlices(rewriter, candidateSlices);
2285
2286 // 5.b. Clone consumer op.
2287 auto clonedConsumerOp = cast<TilingInterface>(rewriter.clone(*consumerOp));
2288 SmallVector<unsigned> operandNumbers =
2289 llvm::map_to_vector(consumerOpOperands, [](OpOperand *opOperand) {
2290 return opOperand->getOperandNumber();
2291 });
2292 SmallVector<OpOperand *> clonedOpFusedOperandsList =
2293 llvm::map_to_vector(operandNumbers, [&](unsigned operandNum) {
2294 return &clonedConsumerOp->getOpOperand(operandNum);
2295 });
2296
2297 // 5.c. Replace all uses of the loop result with the result of the cloned
2298 // tensor.insert_slice.
2299 rewriter.modifyOpInPlace(clonedConsumerOp, [&]() {
2300 for (auto [operandToReplace, clonedSliceOp] :
2301 llvm::zip_equal(clonedOpFusedOperandsList, clonedInsertSlices)) {
2302 operandToReplace->set(clonedSliceOp.getResult());
2303 }
2304 });
2305
2306 // 6. Perform tiling of the cloned consumer and replace the operand at
2307 // `operandNumber` with the source of the cloned tensor.insert_slice op.
2308 FailureOr<TilingResult> tileAndFuseResult =
2309 tensor::replaceInsertSlicesWithTiledConsumer(rewriter, clonedInsertSlices,
2310 clonedOpFusedOperandsList,
2311 innerTileAlignments);
2312 if (failed(tileAndFuseResult)) {
2313 return failure();
2314 }
2315
2316 auto tiledConsumerOp = cast<TilingInterface>(tileAndFuseResult->tiledOps[0]);
2317 for (auto [operandNum, clonedSliceOp] :
2318 llvm::zip_equal(operandNumbers, clonedInsertSlices)) {
2319 rewriter.replaceAllUsesWith(tiledConsumerOp->getOperand(operandNum),
2320 clonedSliceOp.getSource());
2321 }
2322
2323 // 7. Reconstruct [nested] loop with new inits.
2324 YieldTiledValuesFn newYieldValuesFn =
2325 [&](RewriterBase &innerRewriter, Location loc, ValueRange /*ivs*/,
2326 ValueRange newRegionIterArgs, SmallVector<Value> &tiledResult,
2328 SmallVector<SmallVector<OpFoldResult>> &tiledSizes) -> LogicalResult {
2329 OpBuilder::InsertionGuard g(innerRewriter);
2330 // 8. Set inner insertPoint right before tiled consumer op.
2331 innerRewriter.setInsertionPoint(tiledConsumerOp);
2332
2333 SmallVector<SmallVector<OpFoldResult>> allOffsets, allSizes;
2334 for (auto candidateSliceOp : clonedInsertSlices) {
2335 SmallVector<OpFoldResult> offsets = candidateSliceOp.getMixedOffsets();
2336 SmallVector<OpFoldResult> sizes = candidateSliceOp.getMixedSizes();
2337 SmallVector<OpFoldResult> strides = candidateSliceOp.getMixedStrides();
2338
2339 // 9. Check all insert stride is 1.
2340 if (!llvm::all_of(strides, isOneInteger)) {
2341 return rewriter.notifyMatchFailure(
2342 candidateSliceOp, "containingOp's result yield with stride");
2343 }
2344
2345 allOffsets.emplace_back(std::move(offsets));
2346 allSizes.emplace_back(std::move(sizes));
2347 }
2348
2349 // 10. Try to get iter domain position from input position. Use
2350 // clonedConsumerOp instead of tiledConsumerOp, because the iteration
2351 // domain may require index computation based on the result size. The
2352 // sizes and offsets should be the same either way, but using
2353 // tiledConsumerOp could lead to some chained unnecessary extra index
2354 // computation.
2355 SmallVector<OpFoldResult> iterDomainOffsets, iterDomainSizes;
2356 if (failed(clonedConsumerOp.getIterationDomainTileFromOperandTiles(
2357 rewriter, operandNumbers, allOffsets, allSizes, iterDomainOffsets,
2358 iterDomainSizes, innerTileAlignments))) {
2359 return rewriter.notifyMatchFailure(
2360 clonedConsumerOp,
2361 "can't get iter domain position from input position");
2362 }
2363
2364 // 11. Try to fetch the offset and size for all results of the cloned
2365 // consumer. This would then be used to form the corresponding
2366 // tensor.insert_slice/parallel_insert_slice later.
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]))) {
2376 return rewriter.notifyMatchFailure(
2377 tiledConsumerOp,
2378 "can't get result domain position from iter domain position");
2379 }
2380 }
2381
2382 // 12. Create `extract_slice` for `iter_args` for DPS operation if
2383 // necessary.
2384 if (auto tiledDestStyleOp = dyn_cast<DestinationStyleOpInterface>(
2385 tiledConsumerOp.getOperation())) {
2386 rewriter.setInsertionPoint(tiledDestStyleOp);
2387 for (const auto &&[index, newRegionArg] :
2388 llvm::enumerate(newRegionIterArgs)) {
2389 auto destSlice = tensor::ExtractSliceOp::create(
2390 rewriter, loc, newRegionArg, resultOffsets[index],
2391 resultSizes[index],
2392 SmallVector<OpFoldResult>(resultOffsets[index].size(),
2393 rewriter.getIndexAttr(1)));
2394 // Make a copy of index to avoid a capturing structured binding, which
2395 // is a C++20 extension.
2396 auto dstNumber = index;
2397 rewriter.modifyOpInPlace(tiledDestStyleOp, [&]() {
2398 tiledDestStyleOp.getDpsInitsMutable()[dstNumber].set(destSlice);
2399 });
2400 }
2401 }
2402
2403 // 13. Prepare tiled offset and sizes for later `insert_slice` creation by
2404 // caller.
2405 Block *block = rewriter.getInsertionPoint()->getBlock();
2406 rewriter.setInsertionPoint(block->getTerminator());
2407 for (const auto &&[index, result] :
2408 llvm::enumerate(tiledConsumerOp->getResults())) {
2409 tiledResult.push_back(result);
2410 tiledOffset.emplace_back(resultOffsets[index]);
2411 tiledSizes.emplace_back(resultSizes[index]);
2412 }
2413 return success();
2414 };
2415 // 14. Add new inits to [nested] loops.
2416 if (failed(addInitOperandsToLoopNest(rewriter, loops, newInits,
2417 newYieldValuesFn))) {
2418 return rewriter.notifyMatchFailure(tiledConsumerOp,
2419 "unable to add new inits to nest loop");
2420 }
2421
2422 // 15. Replace the result of scf loop and consumer op with new loop's
2423 // results.
2424
2425 for (auto &&[oldResult, newResult] :
2426 llvm::zip(consumerOp->getResults(),
2427 loops.front()->getResults().take_back(newInits.size()))) {
2428 rewriter.replaceAllUsesWith(oldResult, newResult);
2429 }
2430
2431 // 16. Need to erase the old scf loop and the cloned consumer op.
2432 rewriter.eraseOp(clonedConsumerOp);
2433
2434 SmallVector<OpOperand *> tiledAndFusedOpOperands =
2435 llvm::map_to_vector(operandNumbers, [&](unsigned operandNum) {
2436 return &tileAndFuseResult->tiledOps[0]->getOpOperand(operandNum);
2437 });
2438 auto consumerOpOperandsVec = llvm::to_vector(consumerOpOperands);
2439 return scf::SCFFuseConsumerOfSliceResult{
2440 std::move(consumerOpOperandsVec), std::move(tiledAndFusedOpOperands),
2441 std::move(tileAndFuseResult->tiledOps)};
2442}
2443
2444/// Implementation of fusing consumer of a single slice by computing the
2445/// slice of the consumer in-place for scf loop.
2446FailureOr<scf::SCFFuseConsumerOfSliceResult>
2447mlir::scf::tileAndFuseConsumerOfSlices(
2448 RewriterBase &rewriter, ArrayRef<Operation *> candidateSlices,
2450 const InnerTileAlignmentFnTy &fn) {
2451 if (candidateSlices.empty()) {
2452 return rewriter.notifyMatchFailure(
2453 rewriter.getUnknownLoc(),
2454 "no candidate slices provided for consumer fusion");
2455 }
2456 // Return if `loops` is empty, return an error for now. Caller is expected
2457 // to handle this case.
2458 if (loops.empty()) {
2459 return rewriter.notifyMatchFailure(
2460 candidateSlices.front(),
2461 "cannot call tile and fuse consumer with an empty loop nest");
2462 }
2463
2464 if (!(llvm::all_of(candidateSlices, llvm::IsaPred<tensor::InsertSliceOp>) ||
2465 llvm::all_of(candidateSlices,
2466 llvm::IsaPred<tensor::ParallelInsertSliceOp>))) {
2467 return rewriter.notifyMatchFailure(
2468 candidateSlices.front(),
2469 "candidates slices need to be all `tensor.extract_slice`s or "
2470 "`tensor.parallel_insert_slice`s");
2471 }
2472
2473 // Get the consumer of scf.for for the result yielded by
2474 // tensor.insert_slice/parallel_insert_slice.
2475 FailureOr<SmallVector<OpOperand *>> maybeConsumerOpOperands =
2476 getUntiledConsumerOperandsFromSlices(rewriter, candidateSlices, loops);
2477 if (failed(maybeConsumerOpOperands)) {
2478 return rewriter.notifyMatchFailure(candidateSlices.front(),
2479 "could not fetch consumer to fuse");
2480 }
2481 Operation *consumerOp = maybeConsumerOpOperands->front()->getOwner();
2482
2483 return tileAndFuseConsumerOfSlicesImpl(rewriter, consumerOp,
2484 maybeConsumerOpOperands.value(),
2485 candidateSlices, loops, fn);
2486}
2487
2488/// For a given `result` of a `forallOp` return the
2489/// `tensor.parallel_insert_slice` op (or combining op) that is used to
2490/// construct this result.
2491static std::optional<Operation *>
2493 if (result.getOwner() != forallOp)
2494 return std::nullopt;
2495 BlockArgument bbArg = forallOp.getTiedBlockArgument(result);
2496 SmallVector<Operation *> combiningOps = forallOp.getCombiningOps(bbArg);
2497 // If the number of combining ops is not 1, then this is unexpected. Return
2498 // nullopt.
2499 if (combiningOps.size() != 1)
2500 return std::nullopt;
2501 return combiningOps[0];
2502}
2503
2504/// For a given result of the loop nest that is a tiled loop nest, return the
2505/// insert slice-like op that is used for consumer fusion
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");
2514 return getProducingParallelInsertSlice(forallOp, result);
2515 }
2516 // Assume that the loop nest is a nested `scf.for` that is created through
2517 // tiling and retrieve the `tensor.insert_slice` operation used to construct
2518 // the result.
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());
2524 if (!forOp)
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;
2531 result = innerForResult;
2532 loops = loops.drop_front();
2533 }
2534 LoopLikeOpInterface loop = loops.front();
2535 if (result.getOwner() != loop)
2536 return std::nullopt;
2537 auto forOp = dyn_cast<scf::ForOp>(loop.getOperation());
2538 if (!forOp)
2539 return std::nullopt;
2540 auto yieldOp = cast<scf::YieldOp>(forOp.getBody()->getTerminator());
2541 auto insertSliceOp = yieldOp.getOperand(result.getResultNumber())
2542 .getDefiningOp<tensor::InsertSliceOp>();
2543 if (!insertSliceOp)
2544 return std::nullopt;
2545 return insertSliceOp;
2546}
2547
2548FailureOr<scf::SCFFuseConsumerOfSliceResult>
2549mlir::scf::tileAndFuseConsumer(RewriterBase &rewriter, Operation *consumer,
2551 const InnerTileAlignmentFnTy &fn) {
2552 if (!isa<TilingInterface>(consumer)) {
2553 return rewriter.notifyMatchFailure(
2554 consumer, "unhandled consumer that does not implement TilingInterface");
2555 }
2556
2557 // Return if `loops` is empty, return an error for now. Caller is expected
2558 // to handle this case.
2559 if (loops.empty()) {
2560 return rewriter.notifyMatchFailure(
2561 consumer, "cannot call tile and fuse consumer with an empty loop nest");
2562 }
2563
2564 LoopLikeOpInterface outermostLoop = loops.front();
2565
2566 // Collect the operands of the consumer that come from the outermost loop of
2567 // the loop nest.
2568 SmallVector<OpOperand *> consumerFusableOperands;
2569 for (OpOperand &opOperand : consumer->getOpOperands()) {
2570 if (opOperand.get().getDefiningOp() == outermostLoop) {
2571 consumerFusableOperands.push_back(&opOperand);
2572 }
2573 }
2574
2575 // Nothing to fuse. Just return an empty set.
2576 if (consumerFusableOperands.empty()) {
2577 return mlir::scf::SCFFuseConsumerOfSliceResult{consumerFusableOperands,
2580 }
2581
2582 // Collect the relevant tensor.insert_slice/tensor.parallel_insert_slices
2583 // for fusion.
2584 SmallVector<Operation *> candidateSlices;
2585 candidateSlices.reserve(consumerFusableOperands.size());
2586 for (OpOperand *opOperand : consumerFusableOperands) {
2587 std::optional<Operation *> slice =
2588 getProducingInsertSliceLikeOp(cast<OpResult>(opOperand->get()), loops);
2589 if (!slice) {
2590 return rewriter.notifyMatchFailure(
2591 consumer,
2592 "couldnt find producing insert-slice like operation for operand");
2593 }
2594 candidateSlices.push_back(slice.value());
2595 }
2596
2598 rewriter, consumer, consumerFusableOperands, candidateSlices, loops, fn);
2599}
2600
2601//===----------------------------------------------------------------------===//
2602// lowerToLoopsUsingSCFForOp implementation.
2603//===----------------------------------------------------------------------===//
2604
2605FailureOr<SmallVector<scf::ForOp>>
2606mlir::scf::lowerToLoopsUsingSCFForOp(RewriterBase &rewriter,
2607 TilingInterface op) {
2608 // TODO: Handle cases where the op has results if needed.
2609 if (op->getNumResults() > 0) {
2610 return rewriter.notifyMatchFailure(
2611 op, "unable to lower to loops operations with return values");
2612 }
2613
2614 SmallVector<Range> domain = op.getIterationDomain(rewriter);
2617 Location loc = op.getLoc();
2618 for (auto loopRange : domain) {
2619 Value offsetVal =
2620 getValueOrCreateConstantIndexOp(rewriter, loc, loopRange.offset);
2621 Value sizeVal =
2622 getValueOrCreateConstantIndexOp(rewriter, loc, loopRange.size);
2623 Value strideVal =
2624 getValueOrCreateConstantIndexOp(rewriter, loc, loopRange.stride);
2625 auto loop = scf::ForOp::create(rewriter, op.getLoc(), offsetVal, sizeVal,
2626 strideVal, ValueRange{});
2627 loops.push_back(loop);
2628 ivs.push_back(loop.getInductionVar());
2629 rewriter.setInsertionPoint(loop.getBody()->getTerminator());
2630 }
2631 if (failed(op.generateScalarImplementation(rewriter, op.getLoc(), ivs))) {
2632 return failure();
2633 }
2634 return loops;
2635}
return success()
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
*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.
Definition AffineExpr.h:68
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.
Definition AffineMap.h:46
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.
Definition Value.h:306
Block represents an ordered list of Operations.
Definition Block.h:34
unsigned getNumArguments()
Definition Block.h:153
Operation * getTerminator()
Get the terminator operation of this block.
Definition Block.cpp:249
BlockArgListType getArguments()
Definition Block.h:112
IntegerAttr getIndexAttr(int64_t value)
Definition Builders.cpp:116
Location getUnknownLoc()
Definition Builders.cpp:25
ArrayAttr getArrayAttr(ArrayRef< Attribute > value)
Definition Builders.cpp:275
MLIRContext * getContext() const
Definition Builders.h:56
A class for computing basic dominance information.
Definition Dominance.h:143
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...
Definition Location.h:76
This class represents a saved insertion point.
Definition Builders.h:330
RAII guard to reset the insertion point of the builder when destroyed.
Definition Builders.h:351
This class helps build Operations.
Definition Builders.h:210
Block::iterator getInsertionPoint() const
Returns the current insertion point of the builder.
Definition Builders.h:448
Operation * clone(Operation &op, IRMapping &mapper)
Creates a deep copy of the specified operation, remapping any operands that use values outside of the...
Definition Builders.cpp:581
void setListener(Listener *newListener)
Sets the listener of this builder to the one provided.
Definition Builders.h:319
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:401
Listener * getListener() const
Returns the current listener of this builder, or nullptr if this builder doesn't have a listener.
Definition Builders.h:323
void setInsertionPointToEnd(Block *block)
Sets the insertion point to the end of the specified block.
Definition Builders.h:439
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Definition Builders.h:415
This class represents a single result from folding an operation.
This class represents an operand of an operation.
Definition Value.h:254
unsigned getOperandNumber() const
Return which operand this is in the OpOperand list of the Operation.
Definition Value.cpp:226
This is a value defined by a result of an operation.
Definition Value.h:454
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
bool use_empty()
Returns true if this operation has no uses.
Definition Operation.h:904
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.
Definition Operation.h:230
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:251
MutableArrayRef< OpOperand > getOpOperands()
Definition Operation.h:408
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:403
user_range getUsers()
Returns a range of all users.
Definition Operation.h:925
result_range getResults()
Definition Operation.h:440
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.
Definition Operation.h:429
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.
Definition ValueRange.h:389
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
use_range getUses() const
Returns a range of all uses, which is useful for iterating over all uses.
Definition Value.h:188
iterator_range< use_iterator > use_range
Definition Value.h:182
Operation * getOwner() const
Return the owner of this operand.
Definition UseDefLists.h:38
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,...
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:733
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.
Definition Utils.cpp:1620
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...
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 .
Definition AffineExpr.h:311
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
Definition LLVM.h:125
bool isZeroInteger(OpFoldResult v)
Return "true" if v is an integer value/attribute with constant value 0.
void bindSymbols(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to SymbolExpr at positions: [0 .
Definition AffineExpr.h:325
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
Definition LLVM.h:139
Value getValueOrCreateConstantIndexOp(OpBuilder &b, Location loc, OpFoldResult ofr)
Converts an OpFoldResult to a Value.
Definition Utils.cpp:114
Operation * clone(OpBuilder &b, Operation *op, TypeRange newResultTypes, ValueRange newOperands)
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...
OpFoldResult stride
OpFoldResult size
OpFoldResult offset
Container for result values of tiling.