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