MLIR 24.0.0git
LoopUtils.cpp
Go to the documentation of this file.
1//===- LoopUtils.cpp ---- Misc utilities for loop transformation ----------===//
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 miscellaneous loop transformation routines.
10//
11//===----------------------------------------------------------------------===//
12
22#include "mlir/IR/IRMapping.h"
23#include "mlir/IR/IntegerSet.h"
26#include "llvm/ADT/MapVector.h"
27#include "llvm/Support/Debug.h"
28#include "llvm/Support/DebugLog.h"
29#include "llvm/Support/raw_ostream.h"
30#include <optional>
31
32#define DEBUG_TYPE "loop-utils"
33
34using namespace mlir;
35using namespace affine;
36using namespace presburger;
37using llvm::SmallMapVector;
38
39/// Computes the cleanup loop lower bound of the loop being unrolled with
40/// the specified unroll factor; this bound will also be upper bound of the main
41/// part of the unrolled loop. Computes the bound as an AffineMap with its
42/// operands or a null map when the trip count can't be expressed as an affine
43/// expression.
44static void
45getCleanupLoopLowerBound(AffineForOp forOp, unsigned unrollFactor,
46 AffineMap &cleanupLbMap,
47 SmallVectorImpl<Value> &cleanupLbOperands) {
48 AffineMap tripCountMap;
49 SmallVector<Value, 4> tripCountOperands;
50 getTripCountMapAndOperands(forOp, &tripCountMap, &tripCountOperands);
51 // Trip count can't be computed.
52 if (!tripCountMap) {
53 cleanupLbMap = AffineMap();
54 return;
55 }
56
57 OpBuilder b(forOp);
58 auto lbMap = forOp.getLowerBoundMap();
59 auto lb = AffineApplyOp::create(b, forOp.getLoc(), lbMap,
60 forOp.getLowerBoundOperands());
61
62 // For each upper bound expr, get the range.
63 // Eg: affine.for %i = lb to min (ub1, ub2),
64 // where tripCountExprs yield (tr1, tr2), we create affine.apply's:
65 // lb + tr1 - tr1 % ufactor, lb + tr2 - tr2 % ufactor; the results of all
66 // these affine.apply's make up the cleanup loop lower bound.
67 SmallVector<AffineExpr, 4> bumpExprs(tripCountMap.getNumResults());
68 SmallVector<Value, 4> bumpValues(tripCountMap.getNumResults());
69 int64_t step = forOp.getStepAsInt();
70 for (unsigned i = 0, e = tripCountMap.getNumResults(); i < e; i++) {
71 auto tripCountExpr = tripCountMap.getResult(i);
72 bumpExprs[i] = (tripCountExpr - tripCountExpr % unrollFactor) * step;
73 auto bumpMap = AffineMap::get(tripCountMap.getNumDims(),
74 tripCountMap.getNumSymbols(), bumpExprs[i]);
75 bumpValues[i] =
76 AffineApplyOp::create(b, forOp.getLoc(), bumpMap, tripCountOperands);
77 }
78
79 SmallVector<AffineExpr, 4> newUbExprs(tripCountMap.getNumResults());
80 for (unsigned i = 0, e = bumpExprs.size(); i < e; i++)
81 newUbExprs[i] = b.getAffineDimExpr(0) + b.getAffineDimExpr(i + 1);
82
83 cleanupLbOperands.clear();
84 cleanupLbOperands.push_back(lb);
85 cleanupLbOperands.append(bumpValues.begin(), bumpValues.end());
86 cleanupLbMap = AffineMap::get(1 + tripCountMap.getNumResults(), 0, newUbExprs,
87 b.getContext());
88 // Simplify the cleanupLbMap + cleanupLbOperands.
89 fullyComposeAffineMapAndOperands(&cleanupLbMap, &cleanupLbOperands);
90 cleanupLbMap = simplifyAffineMap(cleanupLbMap);
91 canonicalizeMapAndOperands(&cleanupLbMap, &cleanupLbOperands);
92 // Remove any affine.apply's that became dead from the simplification above.
93 for (auto v : bumpValues)
94 if (v.use_empty())
95 v.getDefiningOp()->erase();
96
97 if (lb.use_empty())
98 lb.erase();
99}
100
101/// Returns the max of the bound given by `map` applied to `operands` and the
102/// lower bound of `forOp`, as a map over the updated `operands`. Returns `map`
103/// unchanged if it is known not to be below the lower bound. Both bounds must
104/// have a single result.
105static AffineMap getMaxWithLowerBound(AffineForOp forOp, AffineMap map,
106 SmallVectorImpl<Value> &operands) {
107 AffineMap lbMap = forOp.getLowerBoundMap();
108 assert(map.getNumResults() == 1 && lbMap.getNumResults() == 1 &&
109 "expected single-result bounds");
110 // Place the dims and symbols of the lower bound after those of `map`.
111 unsigned numDims = map.getNumDims();
112 unsigned numLbDims = lbMap.getNumDims();
113 AffineMap shiftedLbMap =
114 lbMap.shiftDims(numDims).shiftSymbols(map.getNumSymbols());
115 AffineMap maxMap = AffineMap::get(
116 shiftedLbMap.getNumDims(), shiftedLbMap.getNumSymbols(),
117 {map.getResult(0), shiftedLbMap.getResult(0)}, forOp.getContext());
118 ValueRange lbOperands = forOp.getLowerBoundOperands();
119 SmallVector<Value, 4> maxOperands(operands.begin(),
120 operands.begin() + numDims);
121 llvm::append_range(maxOperands, lbOperands.take_front(numLbDims));
122 llvm::append_range(maxOperands,
123 ArrayRef<Value>(operands).drop_front(numDims));
124 llvm::append_range(maxOperands, lbOperands.drop_front(numLbDims));
125 fullyComposeAffineMapAndOperands(&maxMap, &maxOperands);
126 canonicalizeMapAndOperands(&maxMap, &maxOperands);
127 maxMap = simplifyAffineMap(maxMap);
128
129 AffineExpr diff =
130 simplifyAffineExpr(maxMap.getResult(0) - maxMap.getResult(1),
131 maxMap.getNumDims(), maxMap.getNumSymbols());
132 if (auto cst = dyn_cast<AffineConstantExpr>(diff); cst && cst.getValue() >= 0)
133 return map;
134 operands.assign(maxOperands.begin(), maxOperands.end());
135 return maxMap;
136}
137
138/// Helper to replace uses of loop carried values (iter_args) and loop
139/// yield values while promoting single iteration affine.for ops.
140static void replaceIterArgsAndYieldResults(AffineForOp forOp) {
141 // Replace uses of iter arguments with iter operands (initial values).
142 auto iterOperands = forOp.getInits();
143 auto iterArgs = forOp.getRegionIterArgs();
144 for (auto e : llvm::zip(iterOperands, iterArgs))
145 std::get<1>(e).replaceAllUsesWith(std::get<0>(e));
146
147 // Replace uses of loop results with the values yielded by the loop.
148 auto outerResults = forOp.getResults();
149 auto innerResults = forOp.getBody()->getTerminator()->getOperands();
150 for (auto e : llvm::zip(outerResults, innerResults))
151 std::get<0>(e).replaceAllUsesWith(std::get<1>(e));
152}
153
154/// Promotes the loop body of a forOp to its containing block if the forOp
155/// was known to have a single iteration.
156LogicalResult mlir::affine::promoteIfSingleIteration(AffineForOp forOp) {
157 std::optional<APInt> tripCount = forOp.getStaticTripCount();
158 if (!tripCount || *tripCount != 1)
159 return failure();
160
161 // TODO: extend this for arbitrary affine bounds.
162 if (forOp.getLowerBoundMap().getNumResults() != 1)
163 return failure();
164
165 // Replaces all IV uses to its single iteration value.
166 auto iv = forOp.getInductionVar();
167 auto *parentBlock = forOp->getBlock();
168 if (!iv.use_empty()) {
169 if (forOp.hasConstantLowerBound()) {
170 auto func = forOp->getParentOfType<FunctionOpInterface>();
171 OpBuilder builder(forOp->getContext());
172 if (func)
173 builder.setInsertionPointToStart(&func.getFunctionBody().front());
174 else
175 builder.setInsertionPoint(forOp);
176 auto constOp = arith::ConstantIndexOp::create(
177 builder, forOp.getLoc(), forOp.getConstantLowerBound());
178 iv.replaceAllUsesWith(constOp);
179 } else {
180 auto lbOperands = forOp.getLowerBoundOperands();
181 auto lbMap = forOp.getLowerBoundMap();
182 OpBuilder builder(forOp);
183 if (lbMap == builder.getDimIdentityMap()) {
184 // No need of generating an affine.apply.
185 iv.replaceAllUsesWith(lbOperands[0]);
186 } else {
187 auto affineApplyOp =
188 AffineApplyOp::create(builder, forOp.getLoc(), lbMap, lbOperands);
189 iv.replaceAllUsesWith(affineApplyOp);
190 }
191 }
192 }
193
195
196 // Move the loop body operations, except for its terminator, to the loop's
197 // containing block.
198 forOp.getBody()->back().erase();
199 parentBlock->getOperations().splice(Block::iterator(forOp),
200 forOp.getBody()->getOperations());
201 forOp.erase();
202 return success();
203}
204
205/// Generates an affine.for op with the specified lower and upper bounds
206/// while generating the right IV remappings to realize shifts for operations in
207/// its body. The operations that go into the loop body are specified in
208/// opGroupQueue starting from the specified offset, and in that order. The
209/// first element of the pair specifies the shift applied to that group of
210/// operations; the shift is multiplied by the loop step before being applied.
211/// Returns nullptr if the generated loop simplifies to a single iteration one.
212static AffineForOp generateShiftedLoop(
213 AffineMap lbMap, AffineMap ubMap,
214 const std::vector<std::pair<uint64_t, ArrayRef<Operation *>>> &opGroupQueue,
215 unsigned offset, AffineForOp srcForOp, OpBuilder b) {
216 auto lbOperands = srcForOp.getLowerBoundOperands();
217 auto ubOperands = srcForOp.getUpperBoundOperands();
218
219 assert(lbMap.getNumInputs() == lbOperands.size());
220 assert(ubMap.getNumInputs() == ubOperands.size());
221
222 auto loopChunk =
223 AffineForOp::create(b, srcForOp.getLoc(), lbOperands, lbMap, ubOperands,
224 ubMap, srcForOp.getStepAsInt());
225 auto loopChunkIV = loopChunk.getInductionVar();
226 auto srcIV = srcForOp.getInductionVar();
227
228 IRMapping operandMap;
229
230 auto bodyBuilder = OpBuilder::atBlockTerminator(loopChunk.getBody());
231 for (const auto &it : llvm::drop_begin(opGroupQueue, offset)) {
232 uint64_t shift = it.first;
233 auto ops = it.second;
234 // All 'same shift' operations get added with their operands being
235 // remapped to results of cloned operations, and their IV used remapped.
236 // Generate the remapping if the shift is not zero: remappedIV = newIV -
237 // shift.
238 if (!srcIV.use_empty() && shift != 0) {
239 auto ivRemap = AffineApplyOp::create(
240 bodyBuilder, srcForOp.getLoc(),
241 bodyBuilder.getSingleDimShiftAffineMap(
242 -static_cast<int64_t>(srcForOp.getStepAsInt() * shift)),
243 loopChunkIV);
244 operandMap.map(srcIV, ivRemap);
245 } else {
246 operandMap.map(srcIV, loopChunkIV);
247 }
248 for (auto *op : ops)
249 bodyBuilder.clone(*op, operandMap);
250 };
251 if (succeeded(promoteIfSingleIteration(loopChunk)))
252 return AffineForOp();
253 return loopChunk;
254}
255
256// The skewing of operations with respect to one another can be used for
257// example to allow overlap of asynchronous operations (such as DMA
258// communication) with computation, or just relative shifting of operations
259// for better register reuse, locality or parallelism. As such, the shifts are
260// typically expected to be at most of the order of the number of operations.
261// This method should not be used as a substitute for loop distribution/fission.
262// This method uses an algorithm// in time linear in the number of operations
263// in the body of the for loop - (using the 'sweep line' paradigm). This method
264// asserts preservation of SSA dominance. A check for that as well as that for
265// memory-based dependence preservation check rests with the users of this
266// method.
267LogicalResult mlir::affine::affineForOpBodySkew(AffineForOp forOp,
268 ArrayRef<uint64_t> shifts,
269 bool unrollPrologueEpilogue) {
270 assert(forOp.getBody()->getOperations().size() == shifts.size() &&
271 "too few/many shifts");
272 if (forOp.getBody()->begin() == std::prev(forOp.getBody()->end()))
273 return success();
274
275 // If the trip counts aren't constant, we would need versioning and
276 // conditional guards (or context information to prevent such versioning). The
277 // better way to pipeline for such loops is to first tile them and extract
278 // constant trip count "full tiles" before applying this.
279 auto mayBeConstTripCount = forOp.getStaticTripCount();
280 if (!mayBeConstTripCount) {
281 LLVM_DEBUG(forOp.emitRemark("non-constant trip count loop not handled"));
282 return success();
283 }
284 uint64_t tripCount = mayBeConstTripCount->getZExtValue();
285
286 assert(isOpwiseShiftValid(forOp, shifts) &&
287 "shifts will lead to an invalid transformation\n");
288
289 int64_t step = forOp.getStepAsInt();
290
291 unsigned numChildOps = shifts.size();
292
293 // Do a linear time (counting) sort for the shifts.
294 uint64_t maxShift = *llvm::max_element(shifts);
295 if (maxShift >= numChildOps) {
296 // Large shifts are not the typical use case.
297 forOp.emitWarning("not shifting because shifts are unrealistically large");
298 return success();
299 }
300
301 // An array of operation groups sorted by shift amount; each group has all
302 // operations with the same shift in the order in which they appear in the
303 // body of the 'affine.for' op.
304 std::vector<std::vector<Operation *>> sortedOpGroups(maxShift + 1);
305 unsigned pos = 0;
306 for (auto &op : forOp.getBody()->without_terminator()) {
307 auto shift = shifts[pos++];
308 sortedOpGroups[shift].push_back(&op);
309 }
310
311 // Unless the shifts have a specific pattern (which actually would be the
312 // common use case), prologue and epilogue are not meaningfully defined.
313 // Nevertheless, if 'unrollPrologueEpilogue' is set, we will treat the first
314 // loop generated as the prologue and the last as epilogue and unroll these
315 // fully.
316 AffineForOp prologue, epilogue;
317
318 // Do a sweep over the sorted shifts while storing open groups in a
319 // vector, and generating loop portions as necessary during the sweep. A block
320 // of operations is paired with its shift.
321 std::vector<std::pair<uint64_t, ArrayRef<Operation *>>> opGroupQueue;
322
323 auto origLbMap = forOp.getLowerBoundMap();
324 uint64_t lbShift = 0;
325 OpBuilder b(forOp);
326 for (uint64_t d = 0, e = sortedOpGroups.size(); d < e; ++d) {
327 // If nothing is shifted by d, continue.
328 if (sortedOpGroups[d].empty())
329 continue;
330 if (!opGroupQueue.empty()) {
331 assert(d > 0 &&
332 "Queue expected to be empty when the first block is found");
333 // The interval for which the loop needs to be generated here is:
334 // [lbShift, min(lbShift + tripCount, d)) and the body of the
335 // loop needs to have all operations in opQueue in that order.
336 AffineForOp res;
337 if (lbShift + tripCount * step < d * step) {
339 b.getShiftedAffineMap(origLbMap, lbShift),
340 b.getShiftedAffineMap(origLbMap, lbShift + tripCount * step),
341 opGroupQueue, /*offset=*/0, forOp, b);
342 // Entire loop for the queued op groups generated, empty it.
343 opGroupQueue.clear();
344 lbShift += tripCount * step;
345 } else {
346 res = generateShiftedLoop(b.getShiftedAffineMap(origLbMap, lbShift),
347 b.getShiftedAffineMap(origLbMap, d),
348 opGroupQueue, /*offset=*/0, forOp, b);
349 lbShift = d * step;
350 }
351
352 if (res) {
353 // Simplify/canonicalize the affine.for.
354 RewritePatternSet patterns(res.getContext());
355 AffineForOp::getCanonicalizationPatterns(patterns, res.getContext());
356 bool erased;
358 res.getOperation(), std::move(patterns),
359 GreedyRewriteConfig().setStrictness(
361 /*changed=*/nullptr, &erased);
362 if (!erased && !prologue)
363 prologue = res;
364 if (!erased)
365 epilogue = res;
366 }
367 } else {
368 // Start of first interval.
369 lbShift = d * step;
370 }
371 // Augment the list of operations that get into the current open interval.
372 opGroupQueue.emplace_back(d, sortedOpGroups[d]);
373 }
374
375 // Those operations groups left in the queue now need to be processed (FIFO)
376 // and their loops completed.
377 for (unsigned i = 0, e = opGroupQueue.size(); i < e; ++i) {
378 uint64_t ubShift = (opGroupQueue[i].first + tripCount) * step;
379 epilogue = generateShiftedLoop(b.getShiftedAffineMap(origLbMap, lbShift),
380 b.getShiftedAffineMap(origLbMap, ubShift),
381 opGroupQueue, /*offset=*/i, forOp, b);
382 lbShift = ubShift;
383 if (!prologue)
384 prologue = epilogue;
385 }
386
387 // Erase the original for op.
388 forOp.erase();
389
390 if (unrollPrologueEpilogue && prologue)
391 (void)loopUnrollFull(prologue);
392 if (unrollPrologueEpilogue && !epilogue && epilogue != prologue)
393 (void)loopUnrollFull(epilogue);
394
395 return success();
396}
397
398/// Checks whether a loop nest is hyper-rectangular or not.
399static LogicalResult
402 SmallVector<Operation *, 8> ops(input.begin(), input.end());
403 // 0-d or 1-d is trivially hyper-rectangular.
404 if (input.size() <= 1)
405 return success();
406 if (failed(getIndexSet(ops, &cst))) {
407 LDBG() << "Index set computation failed!";
408 return failure();
409 }
410 if (!cst.isHyperRectangular(0, input.size())) {
411 LDBG() << "Non-hyperrectangular nests not supported for tiling!";
412 return failure();
413 }
414 return success();
415}
416
417/// Check if the input nest is supported for tiling and whether tiling would be
418/// legal or not.
419template <typename t>
421 ArrayRef<t> tileSizes) {
422 assert(input.size() == tileSizes.size() && "Too few/many tile sizes");
423
424 if (llvm::any_of(input,
425 [](AffineForOp op) { return op.getNumResults() > 0; })) {
426 LDBG() << "Cannot tile nest where a loop has yield values";
427 return failure();
428 }
429
430 // Check if the supplied `for` ops are all successively nested.
431 if (!isPerfectlyNested(input)) {
432 LDBG() << "input loops not perfectly nested";
433 return failure();
434 }
435
436 // TODO: handle non hyper-rectangular spaces.
437 if (failed(checkIfHyperRectangular(input)))
438 return failure();
439
440 return success();
441}
442
443/// Move the loop body of AffineForOp 'src' from 'src' into the specified
444/// location in destination's body, ignoring the terminator.
445static void moveLoopBodyImpl(AffineForOp src, AffineForOp dest,
446 Block::iterator loc) {
447 auto &ops = src.getBody()->getOperations();
448 dest.getBody()->getOperations().splice(loc, ops, ops.begin(),
449 std::prev(ops.end()));
450}
451
452/// Move the loop body of AffineForOp 'src' from 'src' to the start of dest
453/// body.
454static void moveLoopBody(AffineForOp src, AffineForOp dest) {
455 moveLoopBodyImpl(src, dest, dest.getBody()->begin());
456}
457
458/// Constructs tiled loop nest, without setting the loop bounds and move the
459/// body of the original loop nest to the tiled loop nest.
461 AffineForOp rootAffineForOp, unsigned width,
462 MutableArrayRef<AffineForOp> tiledLoops) {
463 Location loc = rootAffineForOp.getLoc();
464
465 // The outermost among the loops as we add more..
466 Operation *topLoop = rootAffineForOp.getOperation();
467 AffineForOp innermostPointLoop;
468
469 // Add intra-tile (or point) loops.
470 for (unsigned i = 0; i < width; i++) {
471 OpBuilder b(topLoop);
472 // Loop bounds will be set later.
473 AffineForOp pointLoop = AffineForOp::create(b, loc, 0, 0);
474 pointLoop.getBody()->getOperations().splice(
475 pointLoop.getBody()->begin(), topLoop->getBlock()->getOperations(),
476 topLoop);
477 tiledLoops[2 * width - 1 - i] = pointLoop;
478 topLoop = pointLoop.getOperation();
479 if (i == 0)
480 innermostPointLoop = pointLoop;
481 }
482
483 // Add tile space loops;
484 for (unsigned i = width; i < 2 * width; i++) {
485 OpBuilder b(topLoop);
486 // Loop bounds will be set later.
487 AffineForOp tileSpaceLoop = AffineForOp::create(b, loc, 0, 0);
488 tileSpaceLoop.getBody()->getOperations().splice(
489 tileSpaceLoop.getBody()->begin(), topLoop->getBlock()->getOperations(),
490 topLoop);
491 tiledLoops[2 * width - i - 1] = tileSpaceLoop;
492 topLoop = tileSpaceLoop.getOperation();
493 }
494
495 // Move the loop body of the original nest to the new one.
496 moveLoopBody(origLoops.back(), innermostPointLoop);
497}
498
499/// Set lower and upper bounds of intra-tile loops for parametric tiling.
500// TODO: Handle non-constant lower bounds.
501static void setIntraTileBoundsParametric(OpBuilder &b, AffineForOp origLoop,
502 AffineForOp newInterTileLoop,
503 AffineForOp newIntraTileLoop,
504 Value tileSize) {
505 // The lower bound for the intra-tile loop is represented by an affine map
506 // as (%i, %t0)->((%i - %origlb) * %t0 + %origlb). Similarly, the upper bound
507 // for the intra-tile loop is represented by an affine map as (%i, %t0)->((%i
508 // - %origlb) * %t0) + (%t0 * %origLoopStep) + %origlb), where %i is loop IV
509 // of the corresponding inter-tile loop, %t0 is the corresponding tiling
510 // parameter, %origlb is lower bound and %origLoopStep is the loop step of the
511 // corresponding inter-tile loop.
512
513 assert(origLoop.hasConstantLowerBound() &&
514 "expected input loops to have constant lower bound.");
515
516 // Get lower bound of original loop as an affine expression.
517 AffineExpr origLowerBoundExpr;
518 origLowerBoundExpr =
519 b.getAffineConstantExpr(origLoop.getConstantLowerBound());
520
521 // Add dim operands from original lower/upper bound.
522 SmallVector<Value, 4> lbOperands, ubOperands;
523 AffineBound lb = origLoop.getLowerBound();
524 AffineBound ub = origLoop.getUpperBound();
525 lbOperands.reserve(lb.getNumOperands() + 2);
526 ubOperands.reserve(ub.getNumOperands() + 2);
527 AffineMap origLbMap = lb.getMap();
528 AffineMap origUbMap = ub.getMap();
529 for (unsigned j = 0, e = origLbMap.getNumDims(); j < e; ++j)
530 lbOperands.push_back(lb.getOperand(j));
531 for (unsigned j = 0, e = origUbMap.getNumDims(); j < e; ++j)
532 ubOperands.push_back(ub.getOperand(j));
533
534 // Add a new dim operand in lb/ubOperands corresponding to the origLoop
535 // IV.
536 lbOperands.push_back(newInterTileLoop.getInductionVar());
537 ubOperands.push_back(newInterTileLoop.getInductionVar());
538
539 // Get loop IV as an affine expression for lower/upper bound. Size of
540 // lb/ubOperands is guaranteed to be atleast one.
541 AffineExpr lbLoopIvExpr = b.getAffineDimExpr(lbOperands.size() - 1);
542 AffineExpr ubLoopIvExpr = b.getAffineDimExpr(ubOperands.size() - 1);
543
544 // Add symbol operands from original lower/upper bound.
545 for (unsigned j = 0, e = origLbMap.getNumSymbols(); j < e; ++j)
546 lbOperands.push_back(lb.getOperand(origLbMap.getNumDims() + j));
547 for (unsigned j = 0, e = origUbMap.getNumSymbols(); j < e; ++j)
548 ubOperands.push_back(ub.getOperand(origUbMap.getNumDims() + j));
549
550 // Add a new symbol operand which is the tile size for this loop.
551 lbOperands.push_back(tileSize);
552 ubOperands.push_back(tileSize);
553
554 SmallVector<AffineExpr, 4> lbBoundExprs;
555 SmallVector<AffineExpr, 4> ubBoundExprs;
556 lbBoundExprs.reserve(origLbMap.getNumResults());
557 ubBoundExprs.reserve(origUbMap.getNumResults());
558
559 // Get tiling parameter as an affine expression for lb/ub.
560 AffineExpr lbTileParameter = b.getAffineSymbolExpr(origLbMap.getNumSymbols());
561 AffineExpr ubTileParameter = b.getAffineSymbolExpr(origUbMap.getNumSymbols());
562
563 // Insert lb as inter-tile ((loop IV - origlb) * tilingParameter) + origlb.
564 lbBoundExprs.push_back(
565 ((lbLoopIvExpr - origLowerBoundExpr) * lbTileParameter) +
566 origLowerBoundExpr);
567
568 // Get the origLoopStep as an affine expression.
569 AffineExpr origLoopStep = b.getAffineConstantExpr(origLoop.getStepAsInt());
570
571 // Insert ub as inter-tile ((loop IV - origlb) * tilingParameter) +
572 // (tilingParameter * origLoopStep) + origlb.
573 ubBoundExprs.push_back(
574 ((ubLoopIvExpr - origLowerBoundExpr) * ubTileParameter) +
575 (ubTileParameter * origLoopStep) + origLowerBoundExpr);
576
577 ubBoundExprs.append(origUbMap.getResults().begin(),
578 origUbMap.getResults().end());
579
580 AffineMap lbMap =
581 AffineMap::get(origLbMap.getNumDims() + 1, origLbMap.getNumSymbols() + 1,
582 lbBoundExprs, b.getContext());
583 newIntraTileLoop.setLowerBound(lbOperands, lbMap);
584
585 AffineMap ubMap =
586 AffineMap::get(origUbMap.getNumDims() + 1, origUbMap.getNumSymbols() + 1,
587 ubBoundExprs, b.getContext());
588 newIntraTileLoop.setUpperBound(ubOperands, ubMap);
589
590 // Original loop step must be preserved.
591 newIntraTileLoop.setStep(origLoop.getStepAsInt());
592}
593
594/// Set lower and upper bounds of inter-tile loops for parametric tiling.
595// TODO: Handle non-constant lower bounds.
596static void setInterTileBoundsParametric(OpBuilder &b, AffineForOp origLoop,
597 AffineForOp newLoop, Value tileSize) {
598 OperandRange newLbOperands = origLoop.getLowerBoundOperands();
599
600 // The lower bounds for inter-tile loops are same as the corresponding lower
601 // bounds of original loops.
602 newLoop.setLowerBound(newLbOperands, origLoop.getLowerBoundMap());
603
604 // The new upper bound map for inter-tile loops, assuming constant lower
605 // bounds, are now originalLowerBound + ceildiv((originalUpperBound -
606 // originalLowerBound), tiling parameter); where tiling parameter is the
607 // respective tile size for that loop. For e.g. if the original ubmap was
608 // ()->(1024), the new map will be
609 // ()[s0]->(ceildiv((1024 -lb) % s0)), where s0 is the tiling parameter.
610 // Therefore a new symbol operand is inserted in the map and the result
611 // expression is overwritten.
612
613 assert(origLoop.hasConstantLowerBound() &&
614 "expected input loops to have constant lower bound.");
615
616 // Get lower bound of original loop as an affine expression.
617 AffineExpr origLowerBoundExpr;
618 origLowerBoundExpr =
619 b.getAffineConstantExpr(origLoop.getConstantLowerBound());
620
621 // Add dim operands from original upper bound.
622 SmallVector<Value, 4> ubOperands;
623 AffineBound ub = origLoop.getUpperBound();
624 ubOperands.reserve(ub.getNumOperands() + 1);
625 AffineMap origUbMap = ub.getMap();
626 for (unsigned j = 0, e = origUbMap.getNumDims(); j < e; ++j)
627 ubOperands.push_back(ub.getOperand(j));
628
629 // Add symbol operands from original upper bound.
630 for (unsigned j = 0, e = origUbMap.getNumSymbols(); j < e; ++j)
631 ubOperands.push_back(ub.getOperand(origUbMap.getNumDims() + j));
632
633 // Add a new symbol operand which is the tile size for this loop.
634 ubOperands.push_back(tileSize);
635
636 // Get tiling parameter as an affine expression.
637 AffineExpr tileParameter = b.getAffineSymbolExpr(origUbMap.getNumSymbols());
638
640 boundExprs.reserve(origUbMap.getNumResults());
641 int64_t origUpperBound;
642 AffineExpr origUpperBoundExpr;
643
644 // If upper bound for the original loop is constant, then the constant can
645 // be obtained as an affine expression straight away.
646 if (origLoop.hasConstantUpperBound()) {
647 origUpperBound = origLoop.getConstantUpperBound();
648
649 // Get original constant upper bound as an affine expression.
650 origUpperBoundExpr = b.getAffineConstantExpr(origUpperBound);
651
652 // Insert the bound as originalLowerBoundceildiv((originalUpperBound -
653 // originalLowerBound), tilingParameter).
654 boundExprs.push_back(
655 origLowerBoundExpr +
656 (origUpperBoundExpr - origLowerBoundExpr).ceilDiv(tileParameter));
657 } else {
658 // If upper bound for the original loop is not constant then two cases
659 // are possible, although there handeling is the same, 1.) The result of
660 // ubmap has only one result expression. For e.g.
661 // affine.for %i = 5 to %ub
662 //
663 // A symbol operand is added which represents the tiling parameter. The
664 // new loop bounds here will be like ()[s0, s1] -> ((s0 - 5) ceildiv s1 + 5)
665 // where 's0' is the original upper bound and 's1' is the tiling
666 // parameter. 2.) When ubMap has more than one result expression. For e.g.
667 // #map0 = affine_map<()[s0, s1] -> (s0, s1)
668 // affine.for %i = 5 to min #map0()[%s0, %s1]
669 //
670 // A symbol operand is added which represents the tiling parameter. The
671 // new loop bounds will be like ()[s0, s1, s2] -> ((s0 - 5) ceildiv s2 + 5,
672 // (s1 -5) ceildiv s2 + 5), where s2 is the tiling parameter.
673
674 // Insert the bounds as originalLowerBound + ceildiv((originalUpperBound -
675 // originalLowerBound), tilingParameter).
676 for (AffineExpr origUpperBoundExpr : origUbMap.getResults())
677 boundExprs.push_back(
678 origLowerBoundExpr +
679 (origUpperBoundExpr - origLowerBoundExpr).ceilDiv(tileParameter));
680 }
681
682 AffineMap ubMap =
683 AffineMap::get(origUbMap.getNumDims(), origUbMap.getNumSymbols() + 1,
684 boundExprs, b.getContext());
685 newLoop.setUpperBound(ubOperands, ubMap);
686
687 // Original loop step must be preserved.
688 newLoop.setStep(origLoop.getStepAsInt());
689}
690
691/// Constructs and sets new loop bounds after tiling for the case of
692/// hyper-rectangular index sets, where the bounds of one dimension do not
693/// depend on other dimensions and tiling parameters are captured from SSA
694/// values. Bounds of each dimension can thus be treated independently,
695/// and deriving the new bounds is much simpler and faster than for the case of
696/// tiling arbitrary polyhedral shapes.
699 MutableArrayRef<AffineForOp> newLoops, ArrayRef<Value> tileSizes) {
700 assert(!origLoops.empty() && "expected atleast one loop in band");
701 assert(origLoops.size() == tileSizes.size() &&
702 "expected tiling parameter for each loop in band.");
703
704 OpBuilder b(origLoops[0].getOperation());
705 unsigned width = origLoops.size();
706
707 // Set bounds for tile space loops.
708 for (unsigned i = 0; i < width; ++i) {
709 setInterTileBoundsParametric(b, origLoops[i], newLoops[i], tileSizes[i]);
710 }
711
712 // Set bounds for intra-tile loops.
713 for (unsigned i = 0; i < width; ++i) {
714 setIntraTileBoundsParametric(b, origLoops[i], newLoops[i],
715 newLoops[i + width], tileSizes[i]);
716 }
717}
718
719/// Constructs and sets new loop bounds after tiling for the case of
720/// hyper-rectangular index sets, where the bounds of one dimension do not
721/// depend on other dimensions. Bounds of each dimension can thus be treated
722/// independently, and deriving the new bounds is much simpler and faster
723/// than for the case of tiling arbitrary polyhedral shapes.
724static void
727 ArrayRef<unsigned> tileSizes) {
728 assert(!origLoops.empty());
729 assert(origLoops.size() == tileSizes.size());
730
731 OpBuilder b(origLoops[0].getOperation());
732 unsigned width = origLoops.size();
733
734 // Bounds for tile space loops.
735 for (unsigned i = 0; i < width; i++) {
736 OperandRange newLbOperands = origLoops[i].getLowerBoundOperands();
737 OperandRange newUbOperands = origLoops[i].getUpperBoundOperands();
738 newLoops[i].setLowerBound(newLbOperands, origLoops[i].getLowerBoundMap());
739 newLoops[i].setUpperBound(newUbOperands, origLoops[i].getUpperBoundMap());
740 // If the step size of original loop is x and tileSize is y then after
741 // tiling the tile space loops' step size becomes x*y.
742 newLoops[i].setStep(tileSizes[i] * origLoops[i].getStepAsInt());
743 }
744 // Bounds for intra-tile loops.
745 for (unsigned i = 0; i < width; i++) {
746 int64_t largestDiv = getLargestDivisorOfTripCount(origLoops[i]);
747 AffineForOp forOp = origLoops[i];
748 std::optional<uint64_t> mayBeConstantCount = std::nullopt;
749 if (auto staticTripCount = forOp.getStaticTripCount())
750 mayBeConstantCount = staticTripCount->getZExtValue();
751 // The lower bound is just the tile-space loop.
752 AffineMap lbMap = b.getDimIdentityMap();
753 newLoops[width + i].setLowerBound(
754 /*operands=*/newLoops[i].getInductionVar(), lbMap);
755 // The step sizes of intra-tile loops is just the original loops' step size.
756 newLoops[width + i].setStep(origLoops[i].getStepAsInt());
757
758 // Set the upper bound.
759 if (mayBeConstantCount && *mayBeConstantCount < tileSizes[i]) {
760 // Trip count is less than the tile size: upper bound is lower bound +
761 // trip count * stepSize.
762 AffineMap ubMap = b.getSingleDimShiftAffineMap(
763 *mayBeConstantCount * origLoops[i].getStepAsInt());
764 newLoops[width + i].setUpperBound(
765 /*operands=*/newLoops[i].getInductionVar(), ubMap);
766 } else if (largestDiv % tileSizes[i] != 0) {
767 // Intra-tile loop ii goes from i to min(i + tileSize * stepSize, ub_i).
768 // Construct the upper bound map; the operands are the original operands
769 // with 'i' (tile-space loop) appended to it. The new upper bound map is
770 // the original one with an additional expression i + tileSize * stepSize
771 // appended.
772
773 // Add dim operands from original upper bound.
774 SmallVector<Value, 4> ubOperands;
775 AffineBound ub = origLoops[i].getUpperBound();
776 ubOperands.reserve(ub.getNumOperands() + 1);
777 AffineMap origUbMap = ub.getMap();
778 for (unsigned j = 0, e = origUbMap.getNumDims(); j < e; ++j)
779 ubOperands.push_back(ub.getOperand(j));
780
781 // Add dim operand for new loop upper bound.
782 ubOperands.push_back(newLoops[i].getInductionVar());
783
784 // Add symbol operands from original upper bound.
785 for (unsigned j = 0, e = origUbMap.getNumSymbols(); j < e; ++j)
786 ubOperands.push_back(ub.getOperand(origUbMap.getNumDims() + j));
787
789 boundExprs.reserve(1 + origUbMap.getNumResults());
790 AffineExpr dim = b.getAffineDimExpr(origUbMap.getNumDims());
791 // The new upper bound map is the original one with an additional
792 // expression i + tileSize * stepSize (of original loop) appended.
793 boundExprs.push_back(dim + tileSizes[i] * origLoops[i].getStepAsInt());
794 boundExprs.append(origUbMap.getResults().begin(),
795 origUbMap.getResults().end());
796 AffineMap ubMap =
797 AffineMap::get(origUbMap.getNumDims() + 1, origUbMap.getNumSymbols(),
798 boundExprs, b.getContext());
799 newLoops[width + i].setUpperBound(/*operands=*/ubOperands, ubMap);
800 } else {
801 // No need of the min expression.
802 AffineExpr dim = b.getAffineDimExpr(0);
804 1, 0, dim + tileSizes[i] * origLoops[i].getStepAsInt());
805 newLoops[width + i].setUpperBound(newLoops[i].getInductionVar(), ubMap);
806 }
807 }
808}
809
810LogicalResult
812 ArrayRef<unsigned> tileSizes,
813 SmallVectorImpl<AffineForOp> *tiledNest) {
814 if (input.empty())
815 return success();
816
817 if (failed(performPreTilingChecks(input, tileSizes)))
818 return failure();
819
820 MutableArrayRef<AffineForOp> origLoops = input;
821 AffineForOp rootAffineForOp = origLoops[0];
822
823 // Note that width is at least one since the band isn't empty.
824 unsigned width = input.size();
825 SmallVector<AffineForOp, 6> tiledLoops(2 * width);
826
827 // Construct a tiled loop nest without setting their bounds. Bounds are
828 // set later.
829 constructTiledLoopNest(origLoops, rootAffineForOp, width, tiledLoops);
830
831 SmallVector<Value, 8> origLoopIVs;
832 extractForInductionVars(input, &origLoopIVs);
833
834 // Set loop bounds for the tiled loop nest.
835 constructTiledIndexSetHyperRect(origLoops, tiledLoops, tileSizes);
836
837 // Replace original IVs with intra-tile loop IVs.
838 for (unsigned i = 0; i < width; i++)
839 origLoopIVs[i].replaceAllUsesWith(tiledLoops[i + width].getInductionVar());
840
841 // Erase the old loop nest.
842 rootAffineForOp.erase();
843
844 if (tiledNest)
845 *tiledNest = std::move(tiledLoops);
846
847 return success();
848}
849
850/// Tiles the specified band of perfectly nested loops creating tile-space
851/// loops and intra-tile loops, using SSA values as tiling parameters. A band
852/// is a contiguous set of loops.
855 SmallVectorImpl<AffineForOp> *tiledNest) {
856 if (input.empty())
857 return success();
858
859 if (failed(performPreTilingChecks(input, tileSizes)))
860 return failure();
861
862 MutableArrayRef<AffineForOp> origLoops = input;
863 AffineForOp rootAffineForOp = origLoops[0];
864 unsigned width = input.size();
865 SmallVector<AffineForOp, 6> tiledLoops(2 * width);
866
867 // Construct a tiled loop nest without setting their bounds. Bounds are
868 // set later.
869 constructTiledLoopNest(origLoops, rootAffineForOp, width, tiledLoops);
870
871 SmallVector<Value, 8> origLoopIVs;
872 extractForInductionVars(input, &origLoopIVs);
873
874 // Set loop bounds for the tiled loop nest.
876 tileSizes);
877
878 // Replace original IVs with intra-tile loop IVs.
879 for (unsigned i = 0; i < width; i++)
880 origLoopIVs[i].replaceAllUsesWith(tiledLoops[i + width].getInductionVar());
881
882 // Erase the old loop nest.
883 rootAffineForOp.erase();
884
885 if (tiledNest)
886 *tiledNest = std::move(tiledLoops);
887
888 return success();
889}
890
891/// Get perfectly nested sequence of loops starting at root of loop nest
892/// (the first op being another AffineFor, and the second op - a terminator).
893/// A loop is perfectly nested iff: the first op in the loop's body is another
894/// AffineForOp, and the second op is a terminator).
896 SmallVectorImpl<AffineForOp> &nestedLoops, AffineForOp root) {
897 for (unsigned i = 0; i < std::numeric_limits<unsigned>::max(); ++i) {
898 nestedLoops.push_back(root);
899 Block &body = root.getRegion().front();
900 if (body.begin() != std::prev(body.end(), 2))
901 return;
902
903 root = dyn_cast<AffineForOp>(&body.front());
904 if (!root)
905 return;
906 }
907}
908
909/// Unrolls this loop completely.
910LogicalResult mlir::affine::loopUnrollFull(AffineForOp forOp) {
911 std::optional<APInt> mayBeConstantTripCount = forOp.getStaticTripCount();
912 if (mayBeConstantTripCount.has_value()) {
913 uint64_t tripCount = mayBeConstantTripCount->getZExtValue();
914 if (tripCount == 0)
915 return success();
916 if (tripCount == 1)
917 return promoteIfSingleIteration(forOp);
918 return loopUnrollByFactor(forOp, tripCount);
919 }
920 return failure();
921}
922
923/// Unrolls this loop by the specified factor or by the trip count (if constant)
924/// whichever is lower.
925LogicalResult mlir::affine::loopUnrollUpToFactor(AffineForOp forOp,
926 uint64_t unrollFactor) {
927 std::optional<APInt> mayBeConstantTripCount = forOp.getStaticTripCount();
928 if (mayBeConstantTripCount.has_value() &&
929 mayBeConstantTripCount->ult(unrollFactor))
930 return loopUnrollByFactor(forOp, mayBeConstantTripCount->getZExtValue());
931 return loopUnrollByFactor(forOp, unrollFactor);
932}
933
934/// Generates unrolled copies of AffineForOp 'loopBodyBlock', with associated
935/// 'forOpIV' by 'unrollFactor', calling 'ivRemapFn' to remap 'forOpIV' for each
936/// unrolled body. If specified, annotates the Ops in each unrolled iteration
937/// using annotateFn.
939 Block *loopBodyBlock, Value forOpIV, uint64_t unrollFactor,
940 function_ref<Value(unsigned, Value, OpBuilder)> ivRemapFn,
941 function_ref<void(unsigned, Operation *, OpBuilder)> annotateFn,
942 ValueRange iterArgs, ValueRange yieldedValues) {
943 // Builder to insert unrolled bodies just before the terminator of the body of
944 // 'forOp'.
945 auto builder = OpBuilder::atBlockTerminator(loopBodyBlock);
946
947 constexpr auto defaultAnnotateFn = [](unsigned, Operation *, OpBuilder) {};
948 if (!annotateFn)
949 annotateFn = defaultAnnotateFn;
950
951 // Keep a pointer to the last non-terminator operation in the original block
952 // so that we know what to clone (since we are doing this in-place).
953 Block::iterator srcBlockEnd = std::prev(loopBodyBlock->end(), 2);
954
955 // Unroll the contents of 'forOp' (append unrollFactor - 1 additional copies).
956 SmallVector<Value, 4> lastYielded(yieldedValues);
957
958 for (unsigned i = 1; i < unrollFactor; i++) {
959 IRMapping operandMap;
960
961 // Prepare operand map.
962 operandMap.map(iterArgs, lastYielded);
963
964 // If the induction variable is used, create a remapping to the value for
965 // this unrolled instance.
966 if (!forOpIV.use_empty()) {
967 Value ivUnroll = ivRemapFn(i, forOpIV, builder);
968 operandMap.map(forOpIV, ivUnroll);
969 }
970
971 // Clone the original body of 'forOp'.
972 for (auto it = loopBodyBlock->begin(); it != std::next(srcBlockEnd); it++) {
973 Operation *clonedOp = builder.clone(*it, operandMap);
974 annotateFn(i, clonedOp, builder);
975 }
976
977 // Update yielded values. If the yielded value is defined outside the
978 // `loopBodyBlock` or if it is a BlockArgument then it won't be cloned, thus
979 // the `lastYielded` value remains unchanged. Else, update the `lastYielded`
980 // value with the clone corresponding to the yielded value.
981 for (unsigned i = 0, e = lastYielded.size(); i < e; i++) {
982 Operation *defOp = yieldedValues[i].getDefiningOp();
983 if (defOp && defOp->getBlock() == loopBodyBlock)
984 lastYielded[i] = operandMap.lookup(yieldedValues[i]);
985 }
986 }
987
988 // Make sure we annotate the Ops in the original body. We do this last so that
989 // any annotations are not copied into the cloned Ops above.
990 for (auto it = loopBodyBlock->begin(); it != std::next(srcBlockEnd); it++)
991 annotateFn(0, &*it, builder);
992
993 // Update operands of the yield statement.
994 loopBodyBlock->getTerminator()->setOperands(lastYielded);
995}
996
997/// Helper to generate cleanup loop for unroll or unroll-and-jam when the trip
998/// count is not a multiple of `unrollFactor`.
999static LogicalResult generateCleanupLoopForUnroll(AffineForOp forOp,
1000 uint64_t unrollFactor) {
1001 // Insert the cleanup loop right after 'forOp'.
1002 OpBuilder builder(forOp->getBlock(), std::next(Block::iterator(forOp)));
1003 auto cleanupForOp = cast<AffineForOp>(builder.clone(*forOp));
1004
1005 // Update uses of `forOp` results. `cleanupForOp` should use `forOp` result
1006 // and produce results for the original users of `forOp` results.
1007 auto results = forOp.getResults();
1008 auto cleanupResults = cleanupForOp.getResults();
1009 auto cleanupIterOperands = cleanupForOp.getInits();
1010
1011 for (auto e : llvm::zip(results, cleanupResults, cleanupIterOperands)) {
1012 std::get<0>(e).replaceAllUsesWith(std::get<1>(e));
1013 cleanupForOp->replaceUsesOfWith(std::get<2>(e), std::get<0>(e));
1014 }
1015
1016 AffineMap cleanupMap;
1017 SmallVector<Value, 4> cleanupOperands;
1018 getCleanupLoopLowerBound(forOp, unrollFactor, cleanupMap, cleanupOperands);
1019 if (!cleanupMap)
1020 return failure();
1021
1022 // The cleanup loop must not execute if `forOp` does not, but `cleanupMap` is
1023 // below the lower bound of `forOp` if the trip count is negative.
1024 SmallVector<Value, 4> cleanupLbOperands(cleanupOperands);
1025 AffineMap cleanupLbMap =
1026 getMaxWithLowerBound(forOp, cleanupMap, cleanupLbOperands);
1027 cleanupForOp.setLowerBound(cleanupLbOperands, cleanupLbMap);
1028 // Promote the loop body up if this has turned into a single iteration loop.
1029 (void)promoteIfSingleIteration(cleanupForOp);
1030
1031 // Adjust upper bound of the original loop; this is the same as the lower
1032 // bound of the cleanup loop.
1033 forOp.setUpperBound(cleanupOperands, cleanupMap);
1034 return success();
1035}
1036
1037/// Unrolls this loop by the specified factor. Returns success if the loop
1038/// is successfully unrolled.
1040 AffineForOp forOp, uint64_t unrollFactor,
1041 function_ref<void(unsigned, Operation *, OpBuilder)> annotateFn,
1042 bool cleanUpUnroll) {
1043 assert(unrollFactor > 0 && "unroll factor should be positive");
1044
1045 std::optional<uint64_t> mayBeConstantTripCount = std::nullopt;
1046 if (auto staticTripCount = forOp.getStaticTripCount())
1047 mayBeConstantTripCount = staticTripCount->getZExtValue();
1048 if (unrollFactor == 1) {
1049 if (mayBeConstantTripCount == 1 && failed(promoteIfSingleIteration(forOp)))
1050 return failure();
1051 return success();
1052 }
1053
1054 // Nothing in the loop body other than the terminator.
1055 if (llvm::hasSingleElement(forOp.getBody()->getOperations()))
1056 return success();
1057
1058 // If the trip count is lower than the unroll factor, no unrolled body.
1059 if (mayBeConstantTripCount && *mayBeConstantTripCount < unrollFactor) {
1060 if (cleanUpUnroll) {
1061 // Unroll the cleanup loop if cleanUpUnroll is specified.
1062 return loopUnrollFull(forOp);
1063 }
1064
1065 return failure();
1066 }
1067
1068 // Generate the cleanup loop if trip count isn't a multiple of unrollFactor.
1069 if (getLargestDivisorOfTripCount(forOp) % unrollFactor != 0) {
1070 // Loops where the lower bound is a max expression or the upper bound is
1071 // a min expression and the trip count doesn't divide the unroll factor
1072 // can't be unrolled since the lower bound of the cleanup loop in such cases
1073 // cannot be expressed as an affine function or a max over affine functions.
1074 if (forOp.getLowerBoundMap().getNumResults() != 1 ||
1075 forOp.getUpperBoundMap().getNumResults() != 1)
1076 return failure();
1077 if (cleanUpUnroll)
1078 // Force unroll including cleanup loop
1079 return loopUnrollFull(forOp);
1080 if (failed(generateCleanupLoopForUnroll(forOp, unrollFactor)))
1081 assert(false && "cleanup loop lower bound map for single result lower "
1082 "and upper bound maps can always be determined");
1083 }
1084
1085 ValueRange iterArgs(forOp.getRegionIterArgs());
1086 auto yieldedValues = forOp.getBody()->getTerminator()->getOperands();
1087
1088 // Scale the step of loop being unrolled by unroll factor.
1089 int64_t step = forOp.getStepAsInt();
1090 forOp.setStep(step * unrollFactor);
1092 forOp.getBody(), forOp.getInductionVar(), unrollFactor,
1093 [&](unsigned i, Value iv, OpBuilder b) {
1094 // iv' = iv + i * step
1095 auto d0 = b.getAffineDimExpr(0);
1096 auto bumpMap = AffineMap::get(1, 0, d0 + i * step);
1097 return AffineApplyOp::create(b, forOp.getLoc(), bumpMap, iv);
1098 },
1099 /*annotateFn=*/annotateFn,
1100 /*iterArgs=*/iterArgs, /*yieldedValues=*/yieldedValues);
1101
1102 // Promote the loop body up if this has turned into a single iteration loop.
1104 return success();
1105}
1106
1107LogicalResult mlir::affine::loopUnrollJamUpToFactor(AffineForOp forOp,
1108 uint64_t unrollJamFactor) {
1109 std::optional<APInt> mayBeConstantTripCount = forOp.getStaticTripCount();
1110 if (mayBeConstantTripCount.has_value() &&
1111 mayBeConstantTripCount->getZExtValue() < unrollJamFactor)
1112 return loopUnrollJamByFactor(forOp, mayBeConstantTripCount->getZExtValue());
1113 return loopUnrollJamByFactor(forOp, unrollJamFactor);
1114}
1115
1116/// Check if all control operands of all loops are defined outside of `forOp`
1117/// and return false if not.
1118static bool areInnerBoundsInvariant(AffineForOp forOp) {
1119 auto walkResult = forOp.walk([&](AffineForOp aForOp) {
1120 for (auto controlOperand : aForOp.getControlOperands()) {
1121 if (!forOp.isDefinedOutsideOfLoop(controlOperand))
1122 return WalkResult::interrupt();
1123 }
1124 return WalkResult::advance();
1125 });
1126 return !walkResult.wasInterrupted();
1127}
1128
1129/// Unrolls and jams this loop by the specified factor.
1130LogicalResult mlir::affine::loopUnrollJamByFactor(AffineForOp forOp,
1131 uint64_t unrollJamFactor) {
1132 assert(unrollJamFactor > 0 && "unroll jam factor should be positive");
1133
1134 std::optional<uint64_t> mayBeConstantTripCount = std::nullopt;
1135 if (auto staticTripCount = forOp.getStaticTripCount())
1136 mayBeConstantTripCount = staticTripCount->getZExtValue();
1137 if (unrollJamFactor == 1) {
1138 if (mayBeConstantTripCount == 1 && failed(promoteIfSingleIteration(forOp)))
1139 return failure();
1140 return success();
1141 }
1142
1143 // Nothing in the loop body other than the terminator.
1144 if (llvm::hasSingleElement(forOp.getBody()->getOperations()))
1145 return success();
1146
1147 // If the trip count is lower than the unroll jam factor, no unroll jam.
1148 if (mayBeConstantTripCount && *mayBeConstantTripCount < unrollJamFactor) {
1149 LDBG() << "[failed] trip count < unroll-jam factor";
1150 return failure();
1151 }
1152
1153 // If any control operand of any inner loop of `forOp` is defined within
1154 // `forOp`, no unroll jam.
1155 if (!areInnerBoundsInvariant(forOp))
1156 return failure();
1157
1158 // Gather all sub-blocks to jam upon the loop being unrolled.
1160 jbg.walk(forOp);
1161 auto &subBlocks = jbg.subBlocks;
1162
1163 // Collect loops with iter_args.
1164 SmallVector<AffineForOp, 4> loopsWithIterArgs;
1165 forOp.walk([&](AffineForOp aForOp) {
1166 if (aForOp.getNumIterOperands() > 0)
1167 loopsWithIterArgs.push_back(aForOp);
1168 });
1169
1170 // Get supported reductions to be used for creating reduction ops at the end.
1171 SmallVector<LoopReduction> reductions;
1172 if (forOp.getNumIterOperands() > 0)
1173 getSupportedReductions(forOp, reductions);
1174
1175 // Generate the cleanup loop if trip count isn't a multiple of
1176 // unrollJamFactor.
1177 if (getLargestDivisorOfTripCount(forOp) % unrollJamFactor != 0) {
1178 // Loops where the lower bound is a max expression or the upper bound is
1179 // a min expression and the trip count doesn't divide the unroll factor
1180 // can't be unrolled since the lower bound of the cleanup loop in such cases
1181 // cannot be expressed as an affine function or a max over affine functions.
1182 if (forOp.getLowerBoundMap().getNumResults() != 1 ||
1183 forOp.getUpperBoundMap().getNumResults() != 1)
1184 return failure();
1185 if (failed(generateCleanupLoopForUnroll(forOp, unrollJamFactor)))
1186 assert(false && "cleanup loop lower bound map for single result lower "
1187 "and upper bound maps can always be determined");
1188 }
1189
1190 // `operandMaps[i - 1]` carries old->new operand mapping for the ith unrolled
1191 // iteration. There are (`unrollJamFactor` - 1) iterations.
1192 SmallVector<IRMapping, 4> operandMaps(unrollJamFactor - 1);
1193
1194 // For any loop with iter_args, replace it with a new loop that has
1195 // `unrollJamFactor` copies of its iterOperands, iter_args and yield
1196 // operands.
1197 SmallVector<AffineForOp, 4> newLoopsWithIterArgs;
1198 IRRewriter rewriter(forOp.getContext());
1199 for (AffineForOp oldForOp : loopsWithIterArgs) {
1200 SmallVector<Value> dupIterOperands, dupYieldOperands;
1201 ValueRange oldIterOperands = oldForOp.getInits();
1202 ValueRange oldIterArgs = oldForOp.getRegionIterArgs();
1203 ValueRange oldYieldOperands =
1204 cast<AffineYieldOp>(oldForOp.getBody()->getTerminator()).getOperands();
1205 // Get additional iterOperands, iterArgs, and yield operands. We will
1206 // fix iterOperands and yield operands after cloning of sub-blocks.
1207 for (unsigned i = unrollJamFactor - 1; i >= 1; --i) {
1208 dupIterOperands.append(oldIterOperands.begin(), oldIterOperands.end());
1209 dupYieldOperands.append(oldYieldOperands.begin(), oldYieldOperands.end());
1210 }
1211 // Create a new loop with additional iterOperands, iter_args and yield
1212 // operands. This new loop will take the loop body of the original loop.
1213 bool forOpReplaced = oldForOp == forOp;
1214 AffineForOp newForOp =
1215 cast<AffineForOp>(*oldForOp.replaceWithAdditionalYields(
1216 rewriter, dupIterOperands, /*replaceInitOperandUsesInLoop=*/false,
1217 [&](OpBuilder &b, Location loc, ArrayRef<BlockArgument> newBbArgs) {
1218 return dupYieldOperands;
1219 }));
1220 newLoopsWithIterArgs.push_back(newForOp);
1221 // `forOp` has been replaced with a new loop.
1222 if (forOpReplaced)
1223 forOp = newForOp;
1224 // Update `operandMaps` for `newForOp` iterArgs and results.
1225 ValueRange newIterArgs = newForOp.getRegionIterArgs();
1226 unsigned oldNumIterArgs = oldIterArgs.size();
1227 ValueRange newResults = newForOp.getResults();
1228 unsigned oldNumResults = newResults.size() / unrollJamFactor;
1229 assert(oldNumIterArgs == oldNumResults &&
1230 "oldNumIterArgs must be the same as oldNumResults");
1231 for (unsigned i = unrollJamFactor - 1; i >= 1; --i) {
1232 for (unsigned j = 0; j < oldNumIterArgs; ++j) {
1233 // `newForOp` has `unrollJamFactor` - 1 new sets of iterArgs and
1234 // results. Update `operandMaps[i - 1]` to map old iterArgs and results
1235 // to those in the `i`th new set.
1236 operandMaps[i - 1].map(newIterArgs[j],
1237 newIterArgs[i * oldNumIterArgs + j]);
1238 operandMaps[i - 1].map(newResults[j],
1239 newResults[i * oldNumResults + j]);
1240 }
1241 }
1242 }
1243
1244 // Scale the step of loop being unroll-jammed by the unroll-jam factor.
1245 int64_t step = forOp.getStepAsInt();
1246 forOp.setStep(step * unrollJamFactor);
1247
1248 auto forOpIV = forOp.getInductionVar();
1249 // Unroll and jam (appends unrollJamFactor - 1 additional copies).
1250 for (unsigned i = unrollJamFactor - 1; i >= 1; --i) {
1251 for (auto &subBlock : subBlocks) {
1252 // Builder to insert unroll-jammed bodies. Insert right at the end of
1253 // sub-block.
1254 OpBuilder builder(subBlock.first->getBlock(), std::next(subBlock.second));
1255
1256 // If the induction variable is used, create a remapping to the value for
1257 // this unrolled instance.
1258 if (!forOpIV.use_empty()) {
1259 // iv' = iv + i * step, i = 1 to unrollJamFactor-1.
1260 auto d0 = builder.getAffineDimExpr(0);
1261 auto bumpMap = AffineMap::get(1, 0, d0 + i * step);
1262 auto ivUnroll =
1263 AffineApplyOp::create(builder, forOp.getLoc(), bumpMap, forOpIV);
1264 operandMaps[i - 1].map(forOpIV, ivUnroll);
1265 }
1266 // Clone the sub-block being unroll-jammed.
1267 for (auto it = subBlock.first; it != std::next(subBlock.second); ++it)
1268 builder.clone(*it, operandMaps[i - 1]);
1269 }
1270 // Fix iterOperands and yield op operands of newly created loops.
1271 for (auto newForOp : newLoopsWithIterArgs) {
1272 unsigned oldNumIterOperands =
1273 newForOp.getNumIterOperands() / unrollJamFactor;
1274 unsigned numControlOperands = newForOp.getNumControlOperands();
1275 auto yieldOp = cast<AffineYieldOp>(newForOp.getBody()->getTerminator());
1276 unsigned oldNumYieldOperands = yieldOp.getNumOperands() / unrollJamFactor;
1277 assert(oldNumIterOperands == oldNumYieldOperands &&
1278 "oldNumIterOperands must be the same as oldNumYieldOperands");
1279 for (unsigned j = 0; j < oldNumIterOperands; ++j) {
1280 // The `i`th duplication of an old iterOperand or yield op operand
1281 // needs to be replaced with a mapped value from `operandMaps[i - 1]`
1282 // if such mapped value exists.
1283 newForOp.setOperand(numControlOperands + i * oldNumIterOperands + j,
1284 operandMaps[i - 1].lookupOrDefault(
1285 newForOp.getOperand(numControlOperands + j)));
1286 yieldOp.setOperand(
1287 i * oldNumYieldOperands + j,
1288 operandMaps[i - 1].lookupOrDefault(yieldOp.getOperand(j)));
1289 }
1290 }
1291 }
1292 if (forOp.getNumResults() > 0) {
1293 // Create reduction ops to combine every `unrollJamFactor` related results
1294 // into one value. For example, for %0:2 = affine.for ... and addf, we add
1295 // %1 = arith.addf %0#0, %0#1, and replace the following uses of %0#0 with
1296 // %1.
1297 rewriter.setInsertionPointAfter(forOp);
1298 auto loc = forOp.getLoc();
1299 unsigned oldNumResults = forOp.getNumResults() / unrollJamFactor;
1300 for (LoopReduction &reduction : reductions) {
1301 unsigned pos = reduction.iterArgPosition;
1302 Value lhs = forOp.getResult(pos);
1303 Value rhs;
1305 for (unsigned i = unrollJamFactor - 1; i >= 1; --i) {
1306 rhs = forOp.getResult(i * oldNumResults + pos);
1307 // Create ops based on reduction type.
1308 lhs = arith::getReductionOp(reduction.kind, rewriter, loc, lhs, rhs);
1309 if (!lhs)
1310 return failure();
1311 Operation *op = lhs.getDefiningOp();
1312 assert(op && "Reduction op should have been created");
1313 newOps.insert(op);
1314 }
1315 // Replace all uses except those in newly created reduction ops.
1316 forOp.getResult(pos).replaceAllUsesExcept(lhs, newOps);
1317 }
1318 }
1319
1320 // Promote the loop body up if this has turned into a single iteration loop.
1322 return success();
1323}
1324
1325/// Performs loop interchange on 'forOpA' and 'forOpB', where 'forOpB' is
1326/// nested within 'forOpA' as the only non-terminator operation in its block.
1327void mlir::affine::interchangeLoops(AffineForOp forOpA, AffineForOp forOpB) {
1328 assert(&*forOpA.getBody()->begin() == forOpB.getOperation());
1329 auto &forOpABody = forOpA.getBody()->getOperations();
1330 auto &forOpBBody = forOpB.getBody()->getOperations();
1331
1332 // 1) Splice forOpA's non-terminator operations (which is just forOpB) just
1333 // before forOpA (in ForOpA's parent's block) this should leave 'forOpA's
1334 // body containing only the terminator.
1335 forOpA->getBlock()->getOperations().splice(Block::iterator(forOpA),
1336 forOpABody, forOpABody.begin(),
1337 std::prev(forOpABody.end()));
1338 // 2) Splice forOpB's non-terminator operations into the beginning of forOpA's
1339 // body (this leaves forOpB's body containing only the terminator).
1340 forOpABody.splice(forOpABody.begin(), forOpBBody, forOpBBody.begin(),
1341 std::prev(forOpBBody.end()));
1342 // 3) Splice forOpA into the beginning of forOpB's body.
1343 forOpBBody.splice(forOpBBody.begin(), forOpA->getBlock()->getOperations(),
1344 Block::iterator(forOpA));
1345}
1346
1347// Checks each dependence component against the permutation to see if the
1348// desired loop interchange would violate dependences by making the
1349// dependence component lexicographically negative.
1351 const std::vector<SmallVector<DependenceComponent, 2>> &depCompsVec,
1352 ArrayRef<AffineForOp> loops, ArrayRef<unsigned> loopPermMap) {
1353 // Invert permutation map.
1354 unsigned maxLoopDepth = loops.size();
1355 SmallVector<unsigned, 4> loopPermMapInv;
1356 loopPermMapInv.resize(maxLoopDepth);
1357 for (unsigned i = 0; i < maxLoopDepth; ++i)
1358 loopPermMapInv[loopPermMap[i]] = i;
1359
1360 // Check each dependence component against the permutation to see if the
1361 // desired loop interchange permutation would make the dependence vectors
1362 // lexicographically negative.
1363 // Example 1: [-1, 1][0, 0]
1364 // Example 2: [0, 0][-1, 1]
1365 for (const auto &depComps : depCompsVec) {
1366 assert(depComps.size() >= maxLoopDepth);
1367 // Check if the first non-zero dependence component is positive.
1368 // This iterates through loops in the desired order.
1369 for (unsigned j = 0; j < maxLoopDepth; ++j) {
1370 unsigned permIndex = loopPermMapInv[j];
1371 assert(depComps[permIndex].lb);
1372 int64_t depCompLb = *depComps[permIndex].lb;
1373 if (depCompLb > 0)
1374 break;
1375 if (depCompLb < 0)
1376 return false;
1377 }
1378 }
1379 return true;
1380}
1381
1382/// Checks if the loop interchange permutation 'loopPermMap' of the perfectly
1383/// nested sequence of loops in 'loops' would violate dependences.
1385 ArrayRef<AffineForOp> loops, ArrayRef<unsigned> loopPermMap) {
1386 assert(loopPermMap.size() == loops.size() && "invalid loop perm map");
1387 unsigned maxLoopDepth = loops.size();
1388 if (maxLoopDepth == 1)
1389 return true;
1390
1391 // We cannot guarantee the validity of the interchange if the loops have
1392 // iter_args, since the dependence analysis does not take them into account.
1393 // Conservatively return false in such cases.
1394 if (llvm::any_of(loops, [](AffineForOp loop) {
1395 return loop.getNumIterOperands() > 0;
1396 }))
1397 return false;
1398
1399 // Gather dependence components for dependences between all ops in loop nest
1400 // rooted at 'loops[0]', at loop depths in range [1, maxLoopDepth].
1401 std::vector<SmallVector<DependenceComponent, 2>> depCompsVec;
1402 getDependenceComponents(loops[0], maxLoopDepth, &depCompsVec);
1403 return checkLoopInterchangeDependences(depCompsVec, loops, loopPermMap);
1404}
1405
1406/// Returns true if `loops` is a perfectly nested loop nest, where loops appear
1407/// in it from outermost to innermost.
1408[[maybe_unused]] bool
1410 assert(!loops.empty() && "no loops provided");
1411
1412 // We already know that the block can't be empty.
1413 auto hasTwoElements = [](Block *block) {
1414 auto secondOpIt = std::next(block->begin());
1415 return secondOpIt != block->end() && &*secondOpIt == &block->back();
1416 };
1417
1418 auto enclosingLoop = loops.front();
1419 for (auto loop : loops.drop_front()) {
1420 auto parentForOp = dyn_cast<AffineForOp>(loop->getParentOp());
1421 // parentForOp's body should be just this loop and the terminator.
1422 if (parentForOp != enclosingLoop || !hasTwoElements(parentForOp.getBody()))
1423 return false;
1424 enclosingLoop = loop;
1425 }
1426 return true;
1427}
1428
1429// input[i] should move from position i -> permMap[i]. Returns the position in
1430// `input` that becomes the new outermost loop.
1432 ArrayRef<unsigned> permMap) {
1433 assert(input.size() == permMap.size() && "invalid permutation map size");
1434 // Check whether the permutation spec is valid. This is a small vector - we'll
1435 // just sort and check if it's iota.
1436 SmallVector<unsigned, 4> checkPermMap(permMap);
1437 llvm::sort(checkPermMap);
1438 if (llvm::any_of(llvm::enumerate(checkPermMap),
1439 [](const auto &en) { return en.value() != en.index(); }))
1440 assert(false && "invalid permutation map");
1441
1442 // Nothing to do.
1443 if (input.size() < 2)
1444 return 0;
1445
1446 assert(isPerfectlyNested(input) && "input not perfectly nested");
1447
1448 // Compute the inverse mapping, invPermMap: since input[i] goes to position
1449 // permMap[i], position i of the permuted nest is at input[invPermMap[i]].
1451 for (unsigned i = 0, e = input.size(); i < e; ++i)
1452 invPermMap.push_back({permMap[i], i});
1453 llvm::sort(invPermMap);
1454
1455 // Move the innermost loop body to the loop that would be the innermost in the
1456 // permuted nest (only if the innermost loop is going to change).
1457 if (permMap.back() != input.size() - 1) {
1458 Block *destBody = ((AffineForOp)input[invPermMap.back().second]).getBody();
1459 Block *srcBody = ((AffineForOp)input.back()).getBody();
1460 destBody->getOperations().splice(destBody->begin(),
1461 srcBody->getOperations(), srcBody->begin(),
1462 std::prev(srcBody->end()));
1463 }
1464
1465 // We'll move each loop in `input` in the reverse order so that its body is
1466 // empty when we are moving it; this incurs zero copies and no erasing.
1467 for (int i = input.size() - 1; i >= 0; --i) {
1468 // If this has to become the outermost loop after permutation, add it to the
1469 // parent block of the original root.
1470 if (permMap[i] == 0) {
1471 // If the root remains the same, nothing to do.
1472 if (i == 0)
1473 continue;
1474 // Make input[i] the new outermost loop moving it into parentBlock.
1475 auto *parentBlock = input[0]->getBlock();
1476 parentBlock->getOperations().splice(Block::iterator(input[0]),
1477 input[i]->getBlock()->getOperations(),
1478 Block::iterator(input[i]));
1479 continue;
1480 }
1481
1482 // If the parent in the permuted order is the same as in the original,
1483 // nothing to do.
1484 unsigned parentPosInInput = invPermMap[permMap[i] - 1].second;
1485 if (i > 0 && static_cast<unsigned>(i - 1) == parentPosInInput)
1486 continue;
1487
1488 // Move input[i] to its surrounding loop in the transformed nest.
1489 auto *destBody = ((AffineForOp)input[parentPosInInput]).getBody();
1490 destBody->getOperations().splice(destBody->begin(),
1491 input[i]->getBlock()->getOperations(),
1492 Block::iterator(input[i]));
1493 }
1494
1495 return invPermMap[0].second;
1496}
1497
1498// Sinks all sequential loops to the innermost levels (while preserving
1499// relative order among them) and moves all parallel loops to the
1500// outermost (while again preserving relative order among them).
1501AffineForOp mlir::affine::sinkSequentialLoops(AffineForOp forOp) {
1503 getPerfectlyNestedLoops(loops, forOp);
1504 if (loops.size() < 2)
1505 return forOp;
1506
1507 // Gather dependence components for dependences between all ops in loop nest
1508 // rooted at 'loops[0]', at loop depths in range [1, maxLoopDepth].
1509 unsigned maxLoopDepth = loops.size();
1510 std::vector<SmallVector<DependenceComponent, 2>> depCompsVec;
1511 getDependenceComponents(loops[0], maxLoopDepth, &depCompsVec);
1512
1513 // Mark loops as either parallel or sequential.
1514 SmallVector<bool, 8> isParallelLoop(maxLoopDepth, true);
1515 for (auto &depComps : depCompsVec) {
1516 assert(depComps.size() >= maxLoopDepth);
1517 for (unsigned j = 0; j < maxLoopDepth; ++j) {
1518 DependenceComponent &depComp = depComps[j];
1519 assert(depComp.lb.has_value() && depComp.ub.has_value());
1520 if (*depComp.lb != 0 || *depComp.ub != 0)
1521 isParallelLoop[j] = false;
1522 }
1523 }
1524
1525 unsigned numParallelLoops = llvm::count(isParallelLoop, true);
1526
1527 // Compute permutation of loops that sinks sequential loops (and thus raises
1528 // parallel loops) while preserving relative order.
1529 SmallVector<unsigned, 4> loopPermMap(maxLoopDepth);
1530 unsigned nextSequentialLoop = numParallelLoops;
1531 unsigned nextParallelLoop = 0;
1532 for (unsigned i = 0; i < maxLoopDepth; ++i) {
1533 if (isParallelLoop[i]) {
1534 loopPermMap[i] = nextParallelLoop++;
1535 } else {
1536 loopPermMap[i] = nextSequentialLoop++;
1537 }
1538 }
1539
1540 // Check if permutation 'loopPermMap' would violate dependences.
1541 if (!checkLoopInterchangeDependences(depCompsVec, loops, loopPermMap))
1542 return forOp;
1543 // Perform loop interchange according to permutation 'loopPermMap'.
1544 unsigned loopNestRootIndex = permuteLoops(loops, loopPermMap);
1545 return loops[loopNestRootIndex];
1546}
1547
1548// Factors out common behavior to add a new `iv` (resp. `iv` + `offset`) to the
1549// lower (resp. upper) loop bound. When called for both the lower and upper
1550// bounds, the resulting IR resembles:
1551//
1552// ```mlir
1553// affine.for %i = max (`iv, ...) to min (`iv` + `offset`) {
1554// ...
1555// }
1556// ```
1558 SmallVector<Value, 4> *operands,
1559 int64_t offset = 0) {
1560 auto bounds = llvm::to_vector<4>(map->getResults());
1561 bounds.push_back(b.getAffineDimExpr(map->getNumDims()) + offset);
1562 operands->insert(operands->begin() + map->getNumDims(), iv);
1563 *map = AffineMap::get(map->getNumDims() + 1, map->getNumSymbols(), bounds,
1564 b.getContext());
1565 canonicalizeMapAndOperands(map, operands);
1566}
1567
1568// Stripmines `forOp` by `factor` and sinks it under each of the `targets`.
1569// Stripmine-sink is a primitive building block for generalized tiling of
1570// imperfectly nested loops.
1571// This transformation is purely mechanical and does not check legality,
1572// profitability or even structural correctness. It is the user's
1573// responsibility to specify `targets` that are dominated by `forOp`.
1574// Returns the new AffineForOps, one per `targets`, nested immediately under
1575// each of the `targets`.
1577stripmineSink(AffineForOp forOp, uint64_t factor,
1578 ArrayRef<AffineForOp> targets) {
1579 auto originalStep = forOp.getStepAsInt();
1580 auto scaledStep = originalStep * factor;
1581 forOp.setStep(scaledStep);
1582
1583 OpBuilder b(forOp->getBlock(), std::next(Block::iterator(forOp)));
1584
1585 // Lower-bound map creation.
1586 auto lbMap = forOp.getLowerBoundMap();
1587 SmallVector<Value, 4> lbOperands(forOp.getLowerBoundOperands());
1588 augmentMapAndBounds(b, forOp.getInductionVar(), &lbMap, &lbOperands);
1589
1590 // Upper-bound map creation.
1591 auto ubMap = forOp.getUpperBoundMap();
1592 SmallVector<Value, 4> ubOperands(forOp.getUpperBoundOperands());
1593 augmentMapAndBounds(b, forOp.getInductionVar(), &ubMap, &ubOperands,
1594 /*offset=*/scaledStep);
1595
1596 auto iv = forOp.getInductionVar();
1597 SmallVector<AffineForOp, 8> innerLoops;
1598 for (auto t : targets) {
1599 // Insert newForOp before the terminator of `t`.
1600 auto b = OpBuilder::atBlockTerminator(t.getBody());
1601 auto newForOp = AffineForOp::create(b, t.getLoc(), lbOperands, lbMap,
1602 ubOperands, ubMap, originalStep);
1603 auto begin = t.getBody()->begin();
1604 // Skip terminator and `newForOp` which is just before the terminator.
1605 auto nOps = t.getBody()->getOperations().size() - 2;
1606 newForOp.getBody()->getOperations().splice(
1607 newForOp.getBody()->getOperations().begin(),
1608 t.getBody()->getOperations(), begin, std::next(begin, nOps));
1609 replaceAllUsesInRegionWith(iv, newForOp.getInductionVar(),
1610 newForOp.getRegion());
1611 innerLoops.push_back(newForOp);
1612 }
1613
1614 return innerLoops;
1615}
1616
1619 ArrayRef<AffineForOp> targets) {
1621 SmallVector<AffineForOp, 8> currentTargets(targets);
1622 for (auto it : llvm::zip(forOps, sizes)) {
1623 auto step = stripmineSink(std::get<0>(it), std::get<1>(it), currentTargets);
1624 res.push_back(step);
1625 currentTargets = step;
1626 }
1627 return res;
1628}
1629
1631 ArrayRef<uint64_t> sizes,
1632 AffineForOp target) {
1634 for (auto loops : tile(forOps, sizes, ArrayRef<AffineForOp>(target)))
1635 res.push_back(llvm::getSingleElement(loops));
1636 return res;
1637}
1638
1639LogicalResult mlir::affine::coalesceLoops(MutableArrayRef<AffineForOp> loops) {
1640 if (loops.size() < 2)
1641 return success();
1642
1643 AffineForOp innermost = loops.back();
1644 AffineForOp outermost = loops.front();
1645 AffineBound ub = outermost.getUpperBound();
1646 AffineMap origUbMap = ub.getMap();
1647 Location loc = outermost.getLoc();
1648 OpBuilder builder(outermost);
1649 for (AffineForOp loop : loops) {
1650 // We only work on normalized loops.
1651 if (loop.getStepAsInt() != 1 || !loop.hasConstantLowerBound() ||
1652 loop.getConstantLowerBound() != 0)
1653 return failure();
1654 }
1655 SmallVector<Value, 4> upperBoundSymbols;
1656 SmallVector<Value, 4> ubOperands(ub.getOperands().begin(),
1657 ub.getOperands().end());
1658
1659 // 1. Store the upper bound of the outermost loop in a variable.
1660 Value prev;
1661 if (!llvm::hasSingleElement(origUbMap.getResults()))
1662 prev = AffineMinOp::create(builder, loc, origUbMap, ubOperands);
1663 else
1664 prev = AffineApplyOp::create(builder, loc, origUbMap, ubOperands);
1665 upperBoundSymbols.push_back(prev);
1666
1667 // 2. Emit code computing the upper bound of the coalesced loop as product of
1668 // the number of iterations of all loops.
1669 for (AffineForOp loop : loops.drop_front()) {
1670 ub = loop.getUpperBound();
1671 origUbMap = ub.getMap();
1672 ubOperands = ub.getOperands();
1673 Value upperBound;
1674 // If upper bound map has more than one result, take their minimum.
1675 if (!llvm::hasSingleElement(origUbMap.getResults()))
1676 upperBound = AffineMinOp::create(builder, loc, origUbMap, ubOperands);
1677 else
1678 upperBound = AffineApplyOp::create(builder, loc, origUbMap, ubOperands);
1679 upperBoundSymbols.push_back(upperBound);
1680 SmallVector<Value, 4> operands;
1681 operands.push_back(prev);
1682 operands.push_back(upperBound);
1683 // Maintain running product of loop upper bounds.
1684 prev = AffineApplyOp::create(
1685 builder, loc,
1686 AffineMap::get(/*dimCount=*/1,
1687 /*symbolCount=*/1,
1688 builder.getAffineDimExpr(0) *
1689 builder.getAffineSymbolExpr(0)),
1690 operands);
1691 }
1692 // Set upper bound of the coalesced loop.
1693 AffineMap newUbMap = AffineMap::get(
1694 /*dimCount=*/0,
1695 /*symbolCount=*/1, builder.getAffineSymbolExpr(0), builder.getContext());
1696 outermost.setUpperBound(prev, newUbMap);
1697
1698 builder.setInsertionPointToStart(outermost.getBody());
1699
1700 // 3. Remap induction variables. For each original loop, the value of the
1701 // induction variable can be obtained by dividing the induction variable of
1702 // the linearized loop by the total number of iterations of the loops nested
1703 // in it modulo the number of iterations in this loop (remove the values
1704 // related to the outer loops):
1705 // iv_i = floordiv(iv_linear, product-of-loop-ranges-until-i) mod range_i.
1706 // Compute these iteratively from the innermost loop by creating a "running
1707 // quotient" of division by the range.
1708 Value previous = outermost.getInductionVar();
1709 for (unsigned idx = loops.size(); idx > 0; --idx) {
1710 if (idx != loops.size()) {
1711 SmallVector<Value, 4> operands;
1712 operands.push_back(previous);
1713 operands.push_back(upperBoundSymbols[idx]);
1714 previous = AffineApplyOp::create(builder, loc,
1716 /*dimCount=*/1, /*symbolCount=*/1,
1717 builder.getAffineDimExpr(0).floorDiv(
1718 builder.getAffineSymbolExpr(0))),
1719 operands);
1720 }
1721 // Modified value of the induction variables of the nested loops after
1722 // coalescing.
1723 Value inductionVariable;
1724 if (idx == 1) {
1725 inductionVariable = previous;
1726 } else {
1727 SmallVector<Value, 4> applyOperands;
1728 applyOperands.push_back(previous);
1729 applyOperands.push_back(upperBoundSymbols[idx - 1]);
1730 inductionVariable = AffineApplyOp::create(
1731 builder, loc,
1733 /*dimCount=*/1, /*symbolCount=*/1,
1734 builder.getAffineDimExpr(0) % builder.getAffineSymbolExpr(0)),
1735 applyOperands);
1736 }
1737 replaceAllUsesInRegionWith(loops[idx - 1].getInductionVar(),
1738 inductionVariable, loops.back().getRegion());
1739 }
1740
1741 // 4. Move the operations from the innermost just above the second-outermost
1742 // loop, delete the extra terminator and the second-outermost loop.
1743 AffineForOp secondOutermostLoop = loops[1];
1744 innermost.getBody()->back().erase();
1745 outermost.getBody()->getOperations().splice(
1746 Block::iterator(secondOutermostLoop.getOperation()),
1747 innermost.getBody()->getOperations());
1748 for (auto [iter, init] :
1749 llvm::zip_equal(secondOutermostLoop.getRegionIterArgs(),
1750 secondOutermostLoop.getInits())) {
1751 iter.replaceAllUsesWith(init);
1752 iter.dropAllUses();
1753 }
1754 secondOutermostLoop.erase();
1755 return success();
1756}
1757
1758void mlir::affine::mapLoopToProcessorIds(scf::ForOp forOp,
1759 ArrayRef<Value> processorId,
1760 ArrayRef<Value> numProcessors) {
1761 assert(processorId.size() == numProcessors.size());
1762 if (processorId.empty())
1763 return;
1764
1765 OpBuilder b(forOp);
1766 Location loc(forOp.getLoc());
1767 AffineExpr lhs, rhs;
1768 bindSymbols(forOp.getContext(), lhs, rhs);
1769 auto mulMap = AffineMap::get(0, 2, lhs * rhs);
1770 auto addMap = AffineMap::get(0, 2, lhs + rhs);
1771
1772 Value linearIndex = processorId.front();
1773 for (unsigned i = 1, e = processorId.size(); i < e; ++i) {
1774 auto mulApplyOp = AffineApplyOp::create(
1775 b, loc, mulMap, ValueRange{linearIndex, numProcessors[i]});
1776 linearIndex = AffineApplyOp::create(b, loc, addMap,
1777 ValueRange{mulApplyOp, processorId[i]});
1778 }
1779
1780 auto mulApplyOp = AffineApplyOp::create(
1781 b, loc, mulMap, ValueRange{linearIndex, forOp.getStep()});
1782 Value lb = AffineApplyOp::create(
1783 b, loc, addMap, ValueRange{mulApplyOp, forOp.getLowerBound()});
1784 forOp.setLowerBound(lb);
1785
1786 Value step = forOp.getStep();
1787 for (auto numProcs : numProcessors)
1788 step = AffineApplyOp::create(b, loc, mulMap, ValueRange{numProcs, step});
1789 forOp.setStep(step);
1790}
1791
1792/// Given a memref region, determine the lowest depth at which transfers can be
1793/// placed for it, and return the corresponding block, start and end positions
1794/// in the block for placing incoming (read) and outgoing (write) copies
1795/// respectively. The lowest depth depends on whether the region being accessed
1796/// is hoistable with respect to one or more immediately surrounding loops.
1797static void
1799 Block::iterator &begin, Block::iterator &end,
1800 Block **copyPlacementBlock,
1801 Block::iterator *copyInPlacementStart,
1802 Block::iterator *copyOutPlacementStart) {
1803 const auto *cst = region.getConstraints();
1804 SmallVector<Value, 4> symbols;
1805 cst->getValues(cst->getNumDimVars(), cst->getNumDimAndSymbolVars(), &symbols);
1806
1807 SmallVector<Operation *, 4> enclosingAffineOps;
1808 getEnclosingAffineOps(*block.begin(), &enclosingAffineOps);
1809 // Walk up loop parents till we find an IV on which this region is
1810 // symbolic/variant or we hit `hoistGuard`.
1811 auto it = enclosingAffineOps.rbegin();
1812 AffineForOp lastInvariantFor;
1813 for (auto e = enclosingAffineOps.rend(); it != e; ++it) {
1814 Operation *enclosingOp = *it;
1815 // We can't hoist past the definition of the memref being copied.
1816 Value memref = region.memref;
1817 if (!memref.getParentRegion()->isAncestor(enclosingOp->getParentRegion())) {
1818 LDBG() << "memref definition will end up not dominating hoist location";
1819 break;
1820 }
1821
1822 auto affineFor = dyn_cast<AffineForOp>(enclosingOp);
1823 if (!affineFor)
1824 break;
1825 // TODO: also need to be checking this for regions symbols that
1826 // aren't loop IVs, whether we are within their resp. defs' dominance scope.
1827 if (llvm::is_contained(symbols, affineFor.getInductionVar()))
1828 break;
1829 lastInvariantFor = affineFor;
1830 }
1831
1832 if (it != enclosingAffineOps.rbegin()) {
1833 *copyInPlacementStart = Block::iterator(lastInvariantFor);
1834 *copyOutPlacementStart = std::next(*copyInPlacementStart);
1835 *copyPlacementBlock = lastInvariantFor->getBlock();
1836 } else {
1837 *copyInPlacementStart = begin;
1838 *copyOutPlacementStart = end;
1839 *copyPlacementBlock = &block;
1840 }
1841}
1842
1843// Info comprising stride and number of elements transferred every stride.
1848
1849/// Returns striding information for a copy/transfer of this region with
1850/// potentially multiple striding levels from outermost to innermost. For an
1851/// n-dimensional region, there can be at most n-1 levels of striding
1852/// successively nested.
1853// TODO: make this work with non-identity layout maps.
1854static void getMultiLevelStrides(const MemRefRegion &region,
1855 ArrayRef<int64_t> bufferShape,
1856 SmallVectorImpl<StrideInfo> *strideInfos) {
1857 if (bufferShape.size() <= 1)
1858 return;
1859
1860 int64_t numEltPerStride = 1;
1861 int64_t stride = 1;
1862 for (int d = bufferShape.size() - 1; d >= 1; d--) {
1863 int64_t dimSize = cast<MemRefType>(region.memref.getType()).getDimSize(d);
1864 stride *= dimSize;
1865 numEltPerStride *= bufferShape[d];
1866 // A stride is needed only if the region has a shorter extent than the
1867 // memref along the dimension *and* has an extent greater than one along the
1868 // next major dimension.
1869 if (bufferShape[d] < dimSize && bufferShape[d - 1] > 1) {
1870 strideInfos->push_back({stride, numEltPerStride});
1871 }
1872 }
1873}
1874
1875/// Generates a point-wise copy from/to a non-zero ranked `memref' to/from
1876/// `fastMemRef' and returns the outermost AffineForOp of the copy loop nest.
1877/// `lbMaps` and `ubMaps` along with `lbOperands` and `ubOperands` hold the
1878/// lower and upper bound information for the copy loop nest. `fastBufOffsets`
1879/// contain the expressions to be subtracted out from the respective copy loop
1880/// iterators in order to index the fast buffer. If `copyOut' is true, generates
1881/// a copy-out; otherwise a copy-in. Builder `b` should be set to the point the
1882/// copy nest is inserted.
1883//
1884/// The copy-in nest is generated as follows as an example for a 2-d region:
1885/// for x = ...
1886/// for y = ...
1887/// fast_buf[x - offset_x][y - offset_y] = memref[x][y]
1888///
1889static AffineForOp
1890generatePointWiseCopy(Location loc, Value memref, Value fastMemRef,
1891 ArrayRef<AffineMap> lbMaps, ArrayRef<Value> lbOperands,
1892 ArrayRef<AffineMap> ubMaps, ArrayRef<Value> ubOperands,
1893 ArrayRef<AffineExpr> fastBufOffsets, bool isCopyOut,
1894 OpBuilder b) {
1895 assert(llvm::all_of(lbMaps, [&](AffineMap lbMap) {
1896 return lbMap.getNumInputs() == lbOperands.size();
1897 }));
1898 assert(llvm::all_of(ubMaps, [&](AffineMap ubMap) {
1899 return ubMap.getNumInputs() == ubOperands.size();
1900 }));
1901
1902 unsigned rank = cast<MemRefType>(memref.getType()).getRank();
1903 // A copy nest can't be generated for 0-ranked memrefs.
1904 assert(rank != 0 && "non-zero rank memref expected");
1905 assert(lbMaps.size() == rank && "wrong number of lb maps");
1906 assert(ubMaps.size() == rank && "wrong number of ub maps");
1907
1908 SmallVector<Value, 4> memIndices;
1910 SmallVector<Value, 4> fastBufMapOperands;
1911 AffineForOp copyNestRoot;
1912 SmallVector<AffineApplyOp, 4> mayBeDeadApplys;
1913 for (unsigned d = 0; d < rank; ++d) {
1914 auto forOp = createCanonicalizedAffineForOp(b, loc, lbOperands, lbMaps[d],
1915 ubOperands, ubMaps[d]);
1916 if (d == 0)
1917 copyNestRoot = forOp;
1918
1919 b = OpBuilder::atBlockTerminator(forOp.getBody());
1920
1921 auto fastBufOffsetMap =
1922 AffineMap::get(lbOperands.size(), 0, fastBufOffsets[d]);
1923 auto offset = AffineApplyOp::create(b, loc, fastBufOffsetMap, lbOperands);
1924
1925 // Construct the subscript for the fast memref being copied into/from:
1926 // x - offset_x.
1927 fastBufExprs.push_back(b.getAffineDimExpr(2 * d + 1) -
1928 b.getAffineDimExpr(2 * d));
1929 fastBufMapOperands.push_back(offset);
1930 fastBufMapOperands.push_back(forOp.getInductionVar());
1931 mayBeDeadApplys.push_back(offset);
1932
1933 // Subscript for the slow memref being copied.
1934 memIndices.push_back(forOp.getInductionVar());
1935 }
1936
1937 auto fastBufMap =
1938 AffineMap::get(2 * rank, /*symbolCount=*/0, fastBufExprs, b.getContext());
1941 canonicalizeMapAndOperands(&fastBufMap, &fastBufMapOperands);
1942
1943 // Drop any dead affine.applys.
1944 for (auto applyOp : mayBeDeadApplys)
1945 if (applyOp.use_empty())
1946 applyOp.erase();
1947
1948 if (!isCopyOut) {
1949 // Copy in.
1950 auto load = AffineLoadOp::create(b, loc, memref, memIndices);
1951 AffineStoreOp::create(b, loc, load, fastMemRef, fastBufMap,
1952 fastBufMapOperands);
1953 return copyNestRoot;
1954 }
1955
1956 // Copy out.
1957 auto load =
1958 AffineLoadOp::create(b, loc, fastMemRef, fastBufMap, fastBufMapOperands);
1959 AffineStoreOp::create(b, loc, load, memref, memIndices);
1961}
1962
1963[[maybe_unused]] static InFlightDiagnostic emitRemarkForBlock(Block &block) {
1964 return block.getParentOp()->emitRemark();
1965}
1966
1967/// Creates a buffer in the faster memory space for the specified memref region
1968/// (memref has to be non-zero ranked); generates a copy from the lower memory
1969/// space to this one, and replaces all loads/stores in the block range
1970/// [`begin', `end') of `block' to load/store from that buffer. Returns failure
1971/// if copies could not be generated due to yet unimplemented cases.
1972/// `copyInPlacementStart` and `copyOutPlacementStart` in copyPlacementBlock
1973/// specify the insertion points where the incoming copies and outgoing copies,
1974/// respectively, should be inserted (the insertion happens right before the
1975/// insertion point). Since `begin` can itself be invalidated due to the memref
1976/// rewriting done from this method, the output argument `nBegin` is set to its
1977/// replacement (set to `begin` if no invalidation happens). Since outgoing
1978/// copies could have been inserted at `end`, the output argument `nEnd` is set
1979/// to the new end. `sizeInBytes` is set to the size of the fast buffer
1980/// allocated.
1981static LogicalResult generateCopy(
1982 const MemRefRegion &region, Block *block, Block::iterator begin,
1983 Block::iterator end, Block *copyPlacementBlock,
1984 Block::iterator copyInPlacementStart, Block::iterator copyOutPlacementStart,
1985 const AffineCopyOptions &copyOptions, DenseMap<Value, Value> &fastBufferMap,
1986 DenseSet<Operation *> &copyNests, uint64_t *sizeInBytes,
1987 Block::iterator *nBegin, Block::iterator *nEnd) {
1988 *nBegin = begin;
1989 *nEnd = end;
1990
1991 auto f = begin->getParentOfType<FunctionOpInterface>();
1992 OpBuilder topBuilder(f.getFunctionBody());
1993 Value zeroIndex = arith::ConstantIndexOp::create(topBuilder, f.getLoc(), 0);
1994
1995 *sizeInBytes = 0;
1996
1997 if (begin == end)
1998 return success();
1999
2000 // Record the last op in the block for which we are performing copy
2001 // generation. We later do the memref replacement only in [begin, lastCopyOp]
2002 // so that the original memref's used in the data movement code themselves
2003 // don't get replaced.
2004 Operation *lastCopyOp = end->getPrevNode();
2005
2006 // Is the copy out point at the end of the block where we are doing
2007 // explicit copying.
2008 bool isCopyOutAtEndOfBlock = (end == copyOutPlacementStart);
2009
2010 // Copies for read regions are going to be inserted at 'begin'.
2011 OpBuilder prologue(copyPlacementBlock, copyInPlacementStart);
2012 // Copies for write regions are going to be inserted at 'end'.
2013 OpBuilder epilogue(copyPlacementBlock, copyOutPlacementStart);
2014 OpBuilder &b = region.isWrite() ? epilogue : prologue;
2015
2016 // Builder to create constants at the top level.
2017 auto func =
2018 copyPlacementBlock->getParent()->getParentOfType<FunctionOpInterface>();
2019 OpBuilder top(func.getFunctionBody());
2020
2021 auto loc = region.loc;
2022 auto memref = region.memref;
2023 auto memRefType = cast<MemRefType>(memref.getType());
2024
2025 if (!memRefType.getLayout().isIdentity()) {
2026 LDBG() << "Non-identity layout map not yet supported";
2027 return failure();
2028 }
2029
2030 // Indices to use for the copying.
2031 // Indices for the original memref being copied from/to.
2032 SmallVector<Value, 4> memIndices;
2033 // Indices for the faster buffer being copied into/from.
2034 SmallVector<Value, 4> bufIndices;
2035
2036 unsigned rank = memRefType.getRank();
2037 if (rank == 0) {
2038 LDBG() << "Non-zero ranked memrefs supported";
2039 return failure();
2040 }
2041
2042 SmallVector<int64_t, 4> fastBufferShape;
2043
2044 // Compute the extents of the buffer.
2046 lbs.reserve(rank);
2047 std::optional<int64_t> numElements =
2048 region.getConstantBoundingSizeAndShape(&fastBufferShape, &lbs);
2049 if (!numElements) {
2050 LDBG() << "Non-constant region size not supported";
2051 return failure();
2052 }
2053
2054 if (llvm::any_of(lbs, [](AffineMap lb) { return lb.getNumResults() > 1; })) {
2055 // This can be supported in the future if needed.
2056 LDBG() << "Max lower bound for memref region start not supported";
2057 return failure();
2058 }
2059
2060 if (*numElements == 0) {
2061 LDBG() << "Nothing to copy";
2062 return success();
2063 }
2064
2065 SmallVector<AffineMap, 4> lbMaps(rank), ubMaps(rank);
2066 for (unsigned i = 0; i < rank; ++i) {
2067 region.getLowerAndUpperBound(i, lbMaps[i], ubMaps[i]);
2068 if (lbMaps[i].getNumResults() == 0 || ubMaps[i].getNumResults() == 0) {
2069 LDBG() << "Missing lower or upper bound for region along dimension: "
2070 << i;
2071 return failure();
2072 }
2073 }
2074
2075 const FlatAffineValueConstraints *cst = region.getConstraints();
2076 // 'regionSymbols' hold values that this memory region is symbolic/parametric
2077 // on; these typically include loop IVs surrounding the level at which the
2078 // copy generation is being done or other valid symbols in MLIR.
2079 SmallVector<Value, 8> regionSymbols;
2080 cst->getValues(rank, cst->getNumVars(), &regionSymbols);
2081
2082 // Construct the access expression for the fast memory buffer. The access
2083 // expression for a particular dimension of the fast buffer is obtained by
2084 // subtracting out the lower bound on the original memref's data region
2085 // along the corresponding dimension.
2086
2087 // Index start offsets for faster memory buffer relative to the original.
2088 SmallVector<AffineExpr, 4> fastBufOffsets;
2089 fastBufOffsets.reserve(rank);
2090 for (unsigned d = 0; d < rank; d++) {
2091 assert(lbs[d].getNumSymbols() == cst->getNumCols() - rank - 1 &&
2092 "incorrect bound size");
2093
2094 // Set copy start location for this dimension in the lower memory space
2095 // memref.
2096 if (lbs[d].isSingleConstant()) {
2097 auto indexVal = lbs[d].getSingleConstantResult();
2098 if (indexVal == 0) {
2099 memIndices.push_back(zeroIndex);
2100 } else {
2101 memIndices.push_back(
2102 arith::ConstantIndexOp::create(top, loc, indexVal).getResult());
2103 }
2104 } else {
2105 // The coordinate for the start location is just the lower bound along the
2106 // corresponding dimension on the memory region (stored in 'offset').
2107 // Remap all inputs of the map to dimensions uniformly since in the
2108 // generate IR we need valid affine symbols as opposed to "symbols" for
2109 // the purpose of the memref region.
2110 SmallVector<AffineExpr> symReplacements(lbs[d].getNumSymbols());
2111 for (unsigned i = 0, e = lbs[d].getNumSymbols(); i < e; ++i)
2112 symReplacements[i] = top.getAffineDimExpr(i);
2113 lbs[d] = lbs[d].replaceDimsAndSymbols(
2114 /*dimReplacements=*/{}, symReplacements, lbs[d].getNumSymbols(),
2115 /*numResultSyms=*/0);
2116 memIndices.push_back(
2117 AffineApplyOp::create(b, loc, lbs[d], regionSymbols));
2118 }
2119 // The fast buffer is copied into at location zero; addressing is relative.
2120 bufIndices.push_back(zeroIndex);
2121
2122 // Record the offsets since they are needed to remap the memory accesses of
2123 // the original memref further below.
2124 fastBufOffsets.push_back(lbs[d].getResult(0));
2125 }
2126
2127 // The faster memory space buffer.
2128 Value fastMemRef;
2129
2130 // Check if a buffer was already created.
2131 bool existingBuf = fastBufferMap.count(memref) > 0;
2132 if (!existingBuf) {
2133 AffineMap fastBufferLayout = b.getMultiDimIdentityMap(rank);
2134 auto fastMemRefType =
2135 MemRefType::get(fastBufferShape, memRefType.getElementType(),
2136 fastBufferLayout, copyOptions.fastMemorySpace);
2137
2138 // Create the fast memory space buffer just before the 'affine.for'
2139 // operation.
2140 fastMemRef =
2141 memref::AllocOp::create(prologue, loc, fastMemRefType).getResult();
2142 // Record it.
2143 fastBufferMap[memref] = fastMemRef;
2144 // fastMemRefType is a constant shaped memref.
2145 auto maySizeInBytes = getIntOrFloatMemRefSizeInBytes(fastMemRefType);
2146 // We don't account for things of unknown size.
2147 *sizeInBytes = maySizeInBytes.value_or(0);
2148
2149 LLVM_DEBUG(emitRemarkForBlock(*block)
2150 << "Creating fast buffer of type " << fastMemRefType
2151 << " and size " << llvm::divideCeil(*sizeInBytes, 1024)
2152 << " KiB\n");
2153 } else {
2154 // Reuse the one already created.
2155 fastMemRef = fastBufferMap[memref];
2156 }
2157
2158 auto numElementsSSA = arith::ConstantIndexOp::create(top, loc, *numElements);
2159
2160 Value dmaStride;
2161 Value numEltPerDmaStride;
2162 if (copyOptions.generateDma) {
2163 SmallVector<StrideInfo, 4> dmaStrideInfos;
2164 getMultiLevelStrides(region, fastBufferShape, &dmaStrideInfos);
2165
2166 // TODO: use all stride levels once DmaStartOp is extended for
2167 // multi-level strides.
2168 if (dmaStrideInfos.size() > 1) {
2169 LDBG() << "Only up to one level of stride supported";
2170 return failure();
2171 }
2172
2173 if (!dmaStrideInfos.empty()) {
2174 dmaStride =
2175 arith::ConstantIndexOp::create(top, loc, dmaStrideInfos[0].stride);
2176 numEltPerDmaStride = arith::ConstantIndexOp::create(
2177 top, loc, dmaStrideInfos[0].numEltPerStride);
2178 }
2179 }
2180
2181 // Create fully composed affine maps for each memref.
2182 auto memAffineMap = b.getMultiDimIdentityMap(memIndices.size());
2183 fullyComposeAffineMapAndOperands(&memAffineMap, &memIndices);
2184 auto bufAffineMap = b.getMultiDimIdentityMap(bufIndices.size());
2185 fullyComposeAffineMapAndOperands(&bufAffineMap, &bufIndices);
2186
2187 if (!copyOptions.generateDma) {
2188 // Point-wise copy generation.
2189 auto copyNest =
2190 generatePointWiseCopy(loc, memref, fastMemRef, lbMaps,
2191 /*lbOperands=*/regionSymbols, ubMaps,
2192 /*ubOperands=*/regionSymbols, fastBufOffsets,
2193 /*isCopyOut=*/region.isWrite(), b);
2194
2195 // Record this so that we can skip it from yet another copy.
2196 copyNests.insert(copyNest);
2197
2198 // Since new ops are being appended (for copy out's), adjust the end to
2199 // mark end of block range being processed if necessary.
2200 if (region.isWrite() && isCopyOutAtEndOfBlock)
2201 *nEnd = Block::iterator(copyNest.getOperation());
2202 } else {
2203 // DMA generation.
2204 // Create a tag (single element 1-d memref) for the DMA.
2205 auto tagMemRefType = MemRefType::get({1}, top.getIntegerType(32), {},
2206 copyOptions.tagMemorySpace);
2207 auto tagMemRef = memref::AllocOp::create(prologue, loc, tagMemRefType);
2208
2209 SmallVector<Value, 4> tagIndices({zeroIndex});
2210 auto tagAffineMap = b.getMultiDimIdentityMap(tagIndices.size());
2211 fullyComposeAffineMapAndOperands(&tagAffineMap, &tagIndices);
2212 if (!region.isWrite()) {
2213 // DMA non-blocking read from original buffer to fast buffer.
2214 AffineDmaStartOp::create(b, loc, memref, memAffineMap, memIndices,
2215 fastMemRef, bufAffineMap, bufIndices, tagMemRef,
2216 tagAffineMap, tagIndices, numElementsSSA,
2217 dmaStride, numEltPerDmaStride);
2218 } else {
2219 // DMA non-blocking write from fast buffer to the original memref.
2220 auto op = AffineDmaStartOp::create(
2221 b, loc, fastMemRef, bufAffineMap, bufIndices, memref, memAffineMap,
2222 memIndices, tagMemRef, tagAffineMap, tagIndices, numElementsSSA,
2223 dmaStride, numEltPerDmaStride);
2224 // Since new ops may be appended at 'end' (for outgoing DMAs), adjust the
2225 // end to mark end of block range being processed.
2226 if (isCopyOutAtEndOfBlock)
2227 *nEnd = Block::iterator(op.getOperation());
2228 }
2229
2230 // Matching DMA wait to block on completion; tag always has a 0 index.
2231 AffineDmaWaitOp::create(b, loc, tagMemRef, tagAffineMap, zeroIndex,
2232 numElementsSSA);
2233
2234 // Generate dealloc for the tag.
2235 auto tagDeallocOp = memref::DeallocOp::create(epilogue, loc, tagMemRef);
2236 if (*nEnd == end && isCopyOutAtEndOfBlock)
2237 // Since new ops are being appended (for outgoing DMAs), adjust the end to
2238 // mark end of range of the original.
2239 *nEnd = Block::iterator(tagDeallocOp.getOperation());
2240 }
2241
2242 // Generate dealloc for the buffer.
2243 if (!existingBuf) {
2244 auto bufDeallocOp = memref::DeallocOp::create(epilogue, loc, fastMemRef);
2245 // When generating pointwise copies, `nEnd' has to be set to deallocOp on
2246 // the fast buffer (since it marks the new end insertion point).
2247 if (!copyOptions.generateDma && *nEnd == end && isCopyOutAtEndOfBlock)
2248 *nEnd = Block::iterator(bufDeallocOp.getOperation());
2249 }
2250
2251 // Replace all uses of the old memref with the faster one while remapping
2252 // access indices (subtracting out lower bound offsets for each dimension).
2253 // Ex: to replace load %A[%i, %j] with load %Abuf[%i - %iT, %j - %jT],
2254 // index remap will be (%i, %j) -> (%i - %iT, %j - %jT),
2255 // i.e., affine.apply (d0, d1, d2, d3) -> (d2-d0, d3-d1) (%iT, %jT, %i, %j),
2256 // and (%iT, %jT) will be the 'extraOperands' for 'rep all memref uses with'.
2257 // d2, d3 correspond to the original indices (%i, %j).
2258 SmallVector<AffineExpr, 4> remapExprs;
2259 remapExprs.reserve(rank);
2260 for (unsigned i = 0; i < rank; i++) {
2261 // The starting operands of indexRemap will be regionSymbols (the symbols on
2262 // which the memref region is parametric); then those corresponding to
2263 // the memref's original indices follow.
2264 auto dimExpr = b.getAffineDimExpr(regionSymbols.size() + i);
2265 remapExprs.push_back(dimExpr - fastBufOffsets[i]);
2266 }
2267 auto indexRemap = AffineMap::get(regionSymbols.size() + rank, 0, remapExprs,
2268 b.getContext());
2269
2270 // Record the begin since it may be invalidated by memref replacement.
2271 Block::iterator prevOfBegin;
2272 bool isBeginAtStartOfBlock = (begin == block->begin());
2273 if (!isBeginAtStartOfBlock)
2274 prevOfBegin = std::prev(begin);
2275
2276 auto userFilterFn = [&](Operation *user) {
2277 auto *ancestorUser = block->findAncestorOpInBlock(*user);
2278 return ancestorUser && !ancestorUser->isBeforeInBlock(&*begin) &&
2279 !lastCopyOp->isBeforeInBlock(ancestorUser);
2280 };
2281
2282 // *Only* those uses within the range [begin, end) of 'block' are replaced.
2283 (void)replaceAllMemRefUsesWith(memref, fastMemRef,
2284 /*extraIndices=*/{}, indexRemap,
2285 /*extraOperands=*/regionSymbols,
2286 /*symbolOperands=*/{}, userFilterFn);
2287
2288 *nBegin = isBeginAtStartOfBlock ? block->begin() : std::next(prevOfBegin);
2289
2290 return success();
2291}
2292
2293/// Construct the memref region to just include the entire memref. Returns false
2294/// dynamic shaped memref's for now. `numParamLoopIVs` is the number of
2295/// enclosing loop IVs of `op` (starting from the outermost) that the region
2296/// is parametric on.
2297static bool getFullMemRefAsRegion(Operation *op, unsigned numParamLoopIVs,
2298 MemRefRegion *region) {
2299 unsigned rank;
2300 if (auto loadOp = dyn_cast<AffineLoadOp>(op)) {
2301 rank = loadOp.getMemRefType().getRank();
2302 region->memref = loadOp.getMemRef();
2303 region->setWrite(false);
2304 } else if (auto storeOp = dyn_cast<AffineStoreOp>(op)) {
2305 rank = storeOp.getMemRefType().getRank();
2306 region->memref = storeOp.getMemRef();
2307 region->setWrite(true);
2308 } else {
2309 assert(false && "expected load or store op");
2310 return false;
2311 }
2312 auto memRefType = cast<MemRefType>(region->memref.getType());
2313 if (!memRefType.hasStaticShape())
2314 return false;
2315
2316 auto *regionCst = region->getConstraints();
2317
2318 // Just get the first numSymbols IVs, which the memref region is parametric
2319 // on.
2321 getAffineForIVs(*op, &ivs);
2322 ivs.resize(numParamLoopIVs);
2323 SmallVector<Value, 4> symbols;
2324 extractForInductionVars(ivs, &symbols);
2325 *regionCst = FlatAffineValueConstraints(rank, numParamLoopIVs, 0);
2326 regionCst->setValues(rank, rank + numParamLoopIVs, symbols);
2327
2328 // Memref dim sizes provide the bounds.
2329 for (unsigned d = 0; d < rank; d++) {
2330 auto dimSize = memRefType.getDimSize(d);
2331 assert(dimSize > 0 && "filtered dynamic shapes above");
2332 regionCst->addBound(BoundType::LB, d, 0);
2333 regionCst->addBound(BoundType::UB, d, dimSize - 1);
2334 }
2335 return true;
2336}
2337
2338LogicalResult
2339mlir::affine::affineDataCopyGenerate(Block::iterator begin, Block::iterator end,
2340 const AffineCopyOptions &copyOptions,
2341 std::optional<Value> filterMemRef,
2342 DenseSet<Operation *> &copyNests) {
2343 if (begin == end)
2344 return success();
2345
2346 assert(begin->getBlock() == std::prev(end)->getBlock() &&
2347 "Inconsistent block begin/end args");
2348 assert(end != end->getBlock()->end() && "end can't be the block terminator");
2349
2350 Block *block = begin->getBlock();
2351
2352 // Copies will be generated for this depth, i.e., symbolic in all loops
2353 // surrounding the this block range.
2354 unsigned copyDepth = getNestingDepth(&*begin);
2355
2356 LDBG() << "Generating copies at depth " << copyDepth;
2357 LDBG() << "from begin: "
2358 << OpWithFlags(&*begin, OpPrintingFlags().skipRegions());
2359 LDBG() << "to inclusive end: "
2360 << OpWithFlags(&*std::prev(end), OpPrintingFlags().skipRegions());
2361
2362 // List of memory regions to copy for. We need a map vector to have a
2363 // guaranteed iteration order to write test cases. CHECK-DAG doesn't help here
2364 // since the alloc's for example are identical except for the SSA id.
2365 SmallMapVector<Value, std::unique_ptr<MemRefRegion>, 4> readRegions;
2366 SmallMapVector<Value, std::unique_ptr<MemRefRegion>, 4> writeRegions;
2367
2368 // Map from original memref's to the fast buffers that their accesses are
2369 // replaced with.
2370 DenseMap<Value, Value> fastBufferMap;
2371
2372 // To check for errors when walking the block.
2373 bool error = false;
2374
2375 // Walk this range of operations to gather all memory regions.
2376 block->walk(begin, end, [&](Operation *opInst) {
2377 Value memref;
2378 MemRefType memrefType;
2379 // Gather regions to allocate to buffers in faster memory space.
2380 if (auto loadOp = dyn_cast<AffineLoadOp>(opInst)) {
2381 memref = loadOp.getMemRef();
2382 memrefType = loadOp.getMemRefType();
2383 } else if (auto storeOp = dyn_cast<AffineStoreOp>(opInst)) {
2384 memref = storeOp.getMemRef();
2385 memrefType = storeOp.getMemRefType();
2386 }
2387 // Not an affine.load/store op.
2388 if (!memref)
2389 return;
2390
2391 if ((filterMemRef.has_value() && filterMemRef != memref) ||
2392 (isa_and_nonnull<IntegerAttr>(memrefType.getMemorySpace()) &&
2393 memrefType.getMemorySpaceAsInt() != copyOptions.slowMemorySpace))
2394 return;
2395
2396 if (!memref.getParentRegion()->isAncestor(block->getParent())) {
2397 LDBG() << "memref definition is inside of the depth at "
2398 << "which copy-in/copy-out would happen";
2399 return;
2400 }
2401
2402 // Compute the MemRefRegion accessed.
2403 auto region = std::make_unique<MemRefRegion>(opInst->getLoc());
2404 if (failed(region->compute(opInst, copyDepth, /*sliceState=*/nullptr,
2405 /*addMemRefDimBounds=*/false))) {
2406 LDBG() << "Error obtaining memory region: semi-affine maps?";
2407 LDBG() << "over-approximating to the entire memref";
2408 if (!getFullMemRefAsRegion(opInst, copyDepth, region.get())) {
2409 LDBG() << "non-constant memref sizes not yet supported";
2410 error = true;
2411 return;
2412 }
2413 }
2414
2415 // Each memref has a single buffer associated with it irrespective of how
2416 // many load's and store's happen on it.
2417 // TODO: in the future, when regions don't intersect and satisfy
2418 // other properties (based on load/store regions), we could consider
2419 // multiple buffers per memref.
2420
2421 // Add to the appropriate region if it's not already in it, or take a
2422 // bounding box union with the existing one if it's already in there.
2423 // Note that a memref may have both read and write regions - so update the
2424 // region in the other list if one exists (write in case of read and vice
2425 // versa) since there is a single bounding box for a memref across all reads
2426 // and writes that happen on it.
2427
2428 // Attempts to update; returns true if 'region' exists in targetRegions.
2429 auto updateRegion =
2430 [&](const SmallMapVector<Value, std::unique_ptr<MemRefRegion>, 4>
2431 &targetRegions) {
2432 const auto *const it = targetRegions.find(region->memref);
2433 if (it == targetRegions.end())
2434 return false;
2435
2436 // Perform a union with the existing region.
2437 if (failed(it->second->unionBoundingBox(*region))) {
2438 LDBG() << "Memory region bounding box failed; "
2439 << "over-approximating to the entire memref";
2440 // If the union fails, we will overapproximate.
2441 if (!getFullMemRefAsRegion(opInst, copyDepth, region.get())) {
2442 LDBG() << "non-constant memref sizes not yet supported";
2443 error = true;
2444 return true;
2445 }
2446 it->second->getConstraints()->clearAndCopyFrom(
2447 *region->getConstraints());
2448 } else {
2449 // Union was computed and stored in 'it->second': copy to 'region'.
2450 region->getConstraints()->clearAndCopyFrom(
2451 *it->second->getConstraints());
2452 }
2453 return true;
2454 };
2455
2456 bool existsInRead = updateRegion(readRegions);
2457 if (error)
2458 return;
2459 bool existsInWrite = updateRegion(writeRegions);
2460 if (error)
2461 return;
2462
2463 // Finally add it to the region list.
2464 if (region->isWrite() && !existsInWrite) {
2465 writeRegions[region->memref] = std::move(region);
2466 } else if (!region->isWrite() && !existsInRead) {
2467 readRegions[region->memref] = std::move(region);
2468 }
2469 });
2470
2471 if (error) {
2472 LDBG() << "copy generation failed for one or more memref's in this block";
2473 return failure();
2474 }
2475
2476 uint64_t totalCopyBuffersSizeInBytes = 0;
2477 bool ret = true;
2478 auto processRegions =
2479 [&](const SmallMapVector<Value, std::unique_ptr<MemRefRegion>, 4>
2480 &regions) {
2481 for (const auto &regionEntry : regions) {
2482 // For each region, hoist copy in/out past all hoistable
2483 // 'affine.for's.
2484 Block::iterator copyInPlacementStart, copyOutPlacementStart;
2485 Block *copyPlacementBlock;
2487 *regionEntry.second, *block, begin, end, &copyPlacementBlock,
2488 &copyInPlacementStart, &copyOutPlacementStart);
2489
2490 uint64_t sizeInBytes;
2491 Block::iterator nBegin, nEnd;
2492 LogicalResult iRet = generateCopy(
2493 *regionEntry.second, block, begin, end, copyPlacementBlock,
2494 copyInPlacementStart, copyOutPlacementStart, copyOptions,
2495 fastBufferMap, copyNests, &sizeInBytes, &nBegin, &nEnd);
2496 if (succeeded(iRet)) {
2497 // begin/end could have been invalidated, and need update.
2498 begin = nBegin;
2499 end = nEnd;
2500 totalCopyBuffersSizeInBytes += sizeInBytes;
2501 }
2502 ret = ret & succeeded(iRet);
2503 }
2504 };
2505 processRegions(readRegions);
2506 processRegions(writeRegions);
2507
2508 if (!ret) {
2509 LDBG() << "copy generation failed for one or more memref's in this block";
2510 return failure();
2511 }
2512
2513 // For a range of operations, a note will be emitted at the caller.
2514 AffineForOp forOp;
2515 if (llvm::DebugFlag && (forOp = dyn_cast<AffineForOp>(&*begin))) {
2516 LLVM_DEBUG(forOp.emitRemark()
2517 << llvm::divideCeil(totalCopyBuffersSizeInBytes, 1024)
2518 << " KiB of copy buffers in fast memory space for this block");
2519 }
2520
2521 if (totalCopyBuffersSizeInBytes > copyOptions.fastMemCapacityBytes) {
2522 block->getParentOp()->emitWarning(
2523 "total size of all copy buffers' for this block exceeds fast memory "
2524 "capacity");
2525 }
2526
2527 return success();
2528}
2529
2530// A convenience version of affineDataCopyGenerate for all ops in the body of
2531// an AffineForOp.
2532LogicalResult mlir::affine::affineDataCopyGenerate(
2533 AffineForOp forOp, const AffineCopyOptions &copyOptions,
2534 std::optional<Value> filterMemRef, DenseSet<Operation *> &copyNests) {
2535 return affineDataCopyGenerate(forOp.getBody()->begin(),
2536 std::prev(forOp.getBody()->end()), copyOptions,
2537 filterMemRef, copyNests);
2538}
2539
2540LogicalResult mlir::affine::generateCopyForMemRegion(
2541 const MemRefRegion &memrefRegion, Operation *analyzedOp,
2542 const AffineCopyOptions &copyOptions, CopyGenerateResult &result) {
2543 Block *block = analyzedOp->getBlock();
2544 auto begin = analyzedOp->getIterator();
2545 auto end = std::next(begin);
2546 DenseMap<Value, Value> fastBufferMap;
2547 DenseSet<Operation *> copyNests;
2548
2549 auto err = generateCopy(memrefRegion, block, begin, end, block, begin, end,
2550 copyOptions, fastBufferMap, copyNests,
2551 &result.sizeInBytes, &begin, &end);
2552 if (failed(err))
2553 return err;
2554
2555 const auto &en = fastBufferMap.find(memrefRegion.memref);
2556 // In some cases (empty loops), no copy generation would have happened.
2557 if (en == fastBufferMap.end())
2558 return failure();
2559 result.alloc = en->second.getDefiningOp();
2560 assert(result.alloc && "fast buffer expected to be locally allocated");
2561 assert(copyNests.size() <= 1 && "At most one copy nest is expected.");
2562 result.copyNest = copyNests.empty() ? nullptr : *copyNests.begin();
2563 return success();
2564}
2565
2566/// Gathers all AffineForOps in 'block' at 'currLoopDepth' in 'depthToLoops'.
2567static void
2568gatherLoopsInBlock(Block *block, unsigned currLoopDepth,
2569 std::vector<SmallVector<AffineForOp, 2>> &depthToLoops) {
2570 // Add a new empty level to output if it doesn't exist level already.
2571 assert(currLoopDepth <= depthToLoops.size() && "Unexpected currLoopDepth");
2572 if (currLoopDepth == depthToLoops.size())
2573 depthToLoops.emplace_back();
2574
2575 for (auto &op : *block) {
2576 if (auto forOp = dyn_cast<AffineForOp>(op)) {
2577 depthToLoops[currLoopDepth].push_back(forOp);
2578 gatherLoopsInBlock(forOp.getBody(), currLoopDepth + 1, depthToLoops);
2579 }
2580 }
2581}
2582
2583/// Gathers all AffineForOps in 'func.func' grouped by loop depth.
2584void mlir::affine::gatherLoops(
2585 func::FuncOp func, std::vector<SmallVector<AffineForOp, 2>> &depthToLoops) {
2586 for (auto &block : func)
2587 gatherLoopsInBlock(&block, /*currLoopDepth=*/0, depthToLoops);
2588
2589 // Remove last loop level from output since it's empty.
2590 if (!depthToLoops.empty()) {
2591 assert(depthToLoops.back().empty() && "Last loop level is not empty?");
2592 depthToLoops.pop_back();
2593 }
2594}
2595
2596AffineForOp mlir::affine::createCanonicalizedAffineForOp(
2597 OpBuilder b, Location loc, ValueRange lbOperands, AffineMap lbMap,
2598 ValueRange ubOperands, AffineMap ubMap, int64_t step) {
2599 SmallVector<Value, 4> lowerOperands(lbOperands);
2600 SmallVector<Value, 4> upperOperands(ubOperands);
2601
2602 fullyComposeAffineMapAndOperands(&lbMap, &lowerOperands);
2603 canonicalizeMapAndOperands(&lbMap, &lowerOperands);
2604 lbMap = removeDuplicateExprs(lbMap);
2605 fullyComposeAffineMapAndOperands(&ubMap, &upperOperands);
2606 canonicalizeMapAndOperands(&ubMap, &upperOperands);
2607 ubMap = removeDuplicateExprs(ubMap);
2608
2609 return AffineForOp::create(b, loc, lowerOperands, lbMap, upperOperands, ubMap,
2610 step);
2611}
2612
2613/// Creates an AffineIfOp that encodes the conditional to choose between
2614/// the constant trip count version and an unknown trip count version of this
2615/// nest of loops. This is used to separate partial and full tiles if `loops`
2616/// has the intra-tile loops. The affine.if op is inserted at the builder
2617/// insertion point of `b`.
2619 OpBuilder b) {
2620 if (loops.empty())
2621 return nullptr;
2622
2623 auto *context = loops[0].getContext();
2624
2627 llvm::append_range(ops, loops);
2628 (void)getIndexSet(ops, &cst);
2629
2630 // Remove constraints that are independent of these loop IVs.
2631 cst.removeIndependentConstraints(/*pos=*/0, /*num=*/loops.size());
2632
2633 // Construct the constraint set representing the guard for full tiles. The
2634 // lower bound (and upper bound) corresponding to the full tile should be
2635 // larger (and resp. smaller) than any other lower (or upper bound).
2636 SmallVector<int64_t, 8> fullTileLb, fullTileUb;
2637 for (auto loop : loops) {
2638 (void)loop;
2639 // TODO: Non-unit stride is not an issue to generalize to.
2640 assert(loop.getStepAsInt() == 1 && "point loop step expected to be one");
2641 // Mark everything symbols for the purpose of finding a constant diff pair.
2642 cst.setDimSymbolSeparation(/*newSymbolCount=*/cst.getNumDimAndSymbolVars() -
2643 1);
2644 unsigned fullTileLbPos, fullTileUbPos;
2645 if (!((IntegerRelation)cst)
2646 .getConstantBoundOnDimSize(0, /*lb=*/nullptr,
2647 /*boundFloorDivisor=*/nullptr,
2648 /*ub=*/nullptr, &fullTileLbPos,
2649 &fullTileUbPos)) {
2650 LDBG() << "Can't get constant diff pair for a loop";
2651 return nullptr;
2652 }
2653
2654 SmallVector<unsigned, 4> lbIndices, ubIndices;
2655 cst.getLowerAndUpperBoundIndices(/*pos=*/0, &lbIndices, &ubIndices);
2656
2657 auto fLb = cst.getInequality(fullTileLbPos);
2658 auto fUb = cst.getInequality(fullTileUbPos);
2659 fullTileLb.assign(fLb.begin(), fLb.end());
2660 fullTileUb.assign(fUb.begin(), fUb.end());
2661
2662 // Full tile lower bound should be >= than any other lower bound.
2663 for (auto lbIndex : lbIndices)
2664 for (unsigned i = 0, e = cst.getNumCols(); i < e; ++i)
2665 cst.atIneq(lbIndex, i) = fullTileLb[i] - cst.atIneq(lbIndex, i);
2666
2667 // Full tile upper bound should be <= any other upper bound.
2668 for (auto ubIndex : ubIndices)
2669 for (unsigned i = 0, e = cst.getNumCols(); i < e; ++i)
2670 cst.atIneq(ubIndex, i) -= fullTileUb[i];
2671
2672 cst.removeVar(0);
2673 }
2674
2675 // The previous step leads to all zeros for the full tile lb and ub position
2676 // itself; remove those and any other duplicates / trivial redundancies.
2678
2679 // Turn everything into dims conservatively since we earlier turned all
2680 // trailing ids past point loop IV into symbols. Some of these could be outer
2681 // loop IVs; we'll canonicalize anyway.
2683
2684 IntegerSet ifCondSet = cst.getAsIntegerSet(context);
2685 // ifCondSet can be null if cst was empty -- this can happen if all loops
2686 // in the nest have constant trip counts.
2687 if (!ifCondSet)
2688 return nullptr;
2689
2690 SmallVector<Value, 4> setOperands;
2691 cst.getValues(0, cst.getNumDimAndSymbolVars(), &setOperands);
2692 canonicalizeSetAndOperands(&ifCondSet, &setOperands);
2693 return AffineIfOp::create(b, loops[0].getLoc(), ifCondSet, setOperands,
2694 /*withElseRegion=*/true);
2695}
2696
2697/// Create the full tile loop nest (along with its body).
2698static LogicalResult
2700 SmallVectorImpl<AffineForOp> &fullTileLoops, OpBuilder b) {
2701 fullTileLoops.reserve(inputNest.size());
2702
2703 // For each loop in the original nest identify a lower/upper bound pair such
2704 // that their difference is a constant.
2706 for (auto loop : inputNest) {
2707 // TODO: straightforward to generalize to a non-unit stride.
2708 if (loop.getStepAsInt() != 1) {
2709 LDBG() << "[tile separation] non-unit stride not implemented";
2710 return failure();
2711 }
2712 SmallVector<Operation *, 1> loopOp{loop.getOperation()};
2713 (void)getIndexSet(loopOp, &cst);
2714 // We will mark everything other than this loop IV as symbol for getting a
2715 // pair of <lb, ub> with a constant difference.
2717 unsigned lbPos, ubPos;
2718 if (!((IntegerRelation)cst)
2719 .getConstantBoundOnDimSize(/*pos=*/0, /*lb=*/nullptr,
2720 /*boundFloorDivisor=*/nullptr,
2721 /*ub=*/nullptr, &lbPos, &ubPos) ||
2722 lbPos == ubPos) {
2723 LDBG() << "[tile separation] Can't get constant diff / "
2724 << "equalities not yet handled";
2725 return failure();
2726 }
2727
2728 // Set all variables as dimensions uniformly since some of those marked as
2729 // symbols above could be outer loop IVs (corresponding tile space IVs).
2730 cst.setDimSymbolSeparation(/*newSymbolCount=*/0);
2731
2732 AffineValueMap lbVmap, ubVmap;
2733 cst.getIneqAsAffineValueMap(/*pos=*/0, lbPos, lbVmap, b.getContext());
2734 cst.getIneqAsAffineValueMap(/*pos=*/0, ubPos, ubVmap, b.getContext());
2735 AffineForOp fullTileLoop = createCanonicalizedAffineForOp(
2736 b, loop.getLoc(), lbVmap.getOperands(), lbVmap.getAffineMap(),
2737 ubVmap.getOperands(), ubVmap.getAffineMap());
2738 b = OpBuilder::atBlockTerminator(fullTileLoop.getBody());
2739 fullTileLoops.push_back(fullTileLoop);
2740 }
2741
2742 // Add the body for the full tile loop nest.
2743 IRMapping operandMap;
2744 for (const auto &loopEn : llvm::enumerate(inputNest))
2745 operandMap.map(loopEn.value().getInductionVar(),
2746 fullTileLoops[loopEn.index()].getInductionVar());
2747 b = OpBuilder::atBlockTerminator(fullTileLoops.back().getBody());
2748 for (auto &op : inputNest.back().getBody()->without_terminator())
2749 b.clone(op, operandMap);
2750 return success();
2751}
2752
2753LogicalResult
2754mlir::affine::separateFullTiles(MutableArrayRef<AffineForOp> inputNest,
2755 SmallVectorImpl<AffineForOp> *fullTileNest) {
2756 if (inputNest.empty())
2757 return success();
2758
2759 auto firstLoop = inputNest[0];
2760
2761 // Each successive for op has to be nested in the other.
2762 auto prevLoop = firstLoop;
2763 for (auto loop : inputNest.drop_front(1)) {
2764 assert(loop->getParentOp() == prevLoop && "input not contiguously nested");
2765 prevLoop = loop;
2766 }
2767
2768 // Create the full tile loop nest.
2769 SmallVector<AffineForOp, 4> fullTileLoops;
2770 OpBuilder b(firstLoop);
2771 if (failed(createFullTiles(inputNest, fullTileLoops, b))) {
2772 if (!fullTileLoops.empty())
2773 fullTileLoops.front().erase();
2774 return failure();
2775 }
2776
2777 // Create and insert the version select right before the root of the nest.
2778 b = OpBuilder(firstLoop);
2779 AffineIfOp ifOp = createSeparationCondition(inputNest, b);
2780 if (!ifOp) {
2781 fullTileLoops.front().erase();
2782 LDBG() << "All tiles are full tiles, or failure creating "
2783 << "separation condition";
2784 return failure();
2785 }
2786
2787 // Move the full tile into the then block.
2788 Block *thenBlock = ifOp.getThenBlock();
2789 AffineForOp outermostFullTileLoop = fullTileLoops[0];
2790 thenBlock->getOperations().splice(
2791 std::prev(thenBlock->end()),
2792 outermostFullTileLoop->getBlock()->getOperations(),
2793 Block::iterator(outermostFullTileLoop));
2794
2795 // Move the partial tile into the else block. The partial tile is the same as
2796 // the original loop nest.
2797 Block *elseBlock = ifOp.getElseBlock();
2798 elseBlock->getOperations().splice(std::prev(elseBlock->end()),
2799 firstLoop->getBlock()->getOperations(),
2800 Block::iterator(firstLoop));
2801
2802 if (fullTileNest)
2803 *fullTileNest = std::move(fullTileLoops);
2804
2805 return success();
2806}
2807
2808LogicalResult affine::coalescePerfectlyNestedAffineLoops(AffineForOp op) {
2809 LogicalResult result(failure());
2811 getPerfectlyNestedLoops(loops, op);
2812 if (loops.size() <= 1)
2813 return success();
2814
2815 // Look for a band of loops that can be coalesced, i.e. perfectly nested
2816 // loops with bounds defined above some loop.
2817 // 1. For each loop, find above which parent loop its operands are
2818 // defined.
2819 SmallVector<unsigned> operandsDefinedAbove(loops.size());
2820 for (unsigned i = 0, e = loops.size(); i < e; ++i) {
2821 operandsDefinedAbove[i] = i;
2822 for (unsigned j = 0; j < i; ++j) {
2823 if (areValuesDefinedAbove(loops[i].getOperands(), loops[j].getRegion())) {
2824 operandsDefinedAbove[i] = j;
2825 break;
2826 }
2827 }
2828 }
2829
2830 // 2. Identify bands of loops such that the operands of all of them are
2831 // defined above the first loop in the band. Traverse the nest bottom-up
2832 // so that modifications don't invalidate the inner loops.
2833 for (unsigned end = loops.size(); end > 0; --end) {
2834 unsigned start = 0;
2835 for (; start < end - 1; ++start) {
2836 auto maxPos =
2837 *std::max_element(std::next(operandsDefinedAbove.begin(), start),
2838 std::next(operandsDefinedAbove.begin(), end));
2839 if (maxPos > start)
2840 continue;
2841 assert(maxPos == start &&
2842 "expected loop bounds to be known at the start of the band");
2843 auto band = llvm::MutableArrayRef(loops.data() + start, end - start);
2844 if (succeeded(coalesceLoops(band)))
2845 result = success();
2846 break;
2847 }
2848 // If a band was found and transformed, keep looking at the loops above
2849 // the outermost transformed loop.
2850 if (start != end - 1)
2851 end = start + 1;
2852 }
2853 return result;
2854}
2855
2857 int64_t count = 0;
2858 Operation *currentOp = operand.getOwner();
2859 while (auto loopOp = currentOp->getParentOfType<LoopLikeOpInterface>()) {
2860 if (!loopOp.isDefinedOutsideOfLoop(operand.get()))
2861 break;
2862 currentOp = loopOp;
2863 count++;
2864 }
2865 return count;
2866}
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 the output argument nEnd is set *to the new end sizeInBytes is set to the size of the fast buffer *allocated *static LogicalResult generateCopy(const MemRefRegion &region, Block *block, Block::iterator begin, Block::iterator end, Block *copyPlacementBlock, Block::iterator copyInPlacementStart, Block::iterator copyOutPlacementStart, const AffineCopyOptions &copyOptions, DenseMap< Value, Value > &fastBufferMap, DenseSet< Operation * > &copyNests, uint64_t *sizeInBytes, Block::iterator *nBegin, Block::iterator *nEnd)
static LogicalResult performPreTilingChecks(MutableArrayRef< AffineForOp > input, ArrayRef< t > tileSizes)
Check if the input nest is supported for tiling and whether tiling would be legal or not.
static void constructParametricallyTiledIndexSetHyperRect(MutableArrayRef< AffineForOp > origLoops, MutableArrayRef< AffineForOp > newLoops, ArrayRef< Value > tileSizes)
Constructs and sets new loop bounds after tiling for the case of hyper-rectangular index sets,...
fullyComposeAffineMapAndOperands & fastBufMap
static InFlightDiagnostic emitRemarkForBlock(Block &block)
static void constructTiledLoopNest(MutableArrayRef< AffineForOp > origLoops, AffineForOp rootAffineForOp, unsigned width, MutableArrayRef< AffineForOp > tiledLoops)
Constructs tiled loop nest, without setting the loop bounds and move the body of the original loop ne...
static bool getFullMemRefAsRegion(Operation *op, unsigned numParamLoopIVs, MemRefRegion *region)
Construct the memref region to just include the entire memref.
static bool checkLoopInterchangeDependences(const std::vector< SmallVector< DependenceComponent, 2 > > &depCompsVec, ArrayRef< AffineForOp > loops, ArrayRef< unsigned > loopPermMap)
static LogicalResult checkIfHyperRectangular(MutableArrayRef< AffineForOp > input)
Checks whether a loop nest is hyper-rectangular or not.
static void findHighestBlockForPlacement(const MemRefRegion &region, Block &block, Block::iterator &begin, Block::iterator &end, Block **copyPlacementBlock, Block::iterator *copyInPlacementStart, Block::iterator *copyOutPlacementStart)
Given a memref region, determine the lowest depth at which transfers can be placed for it,...
static void moveLoopBodyImpl(AffineForOp src, AffineForOp dest, Block::iterator loc)
Move the loop body of AffineForOp 'src' from 'src' into the specified location in destination's body,...
static void setInterTileBoundsParametric(OpBuilder &b, AffineForOp origLoop, AffineForOp newLoop, Value tileSize)
Set lower and upper bounds of inter-tile loops for parametric tiling.
static void setIntraTileBoundsParametric(OpBuilder &b, AffineForOp origLoop, AffineForOp newInterTileLoop, AffineForOp newIntraTileLoop, Value tileSize)
Set lower and upper bounds of intra-tile loops for parametric tiling.
static AffineMap getMaxWithLowerBound(AffineForOp forOp, AffineMap map, SmallVectorImpl< Value > &operands)
Returns the max of the bound given by map applied to operands and the lower bound of forOp,...
static LogicalResult createFullTiles(MutableArrayRef< AffineForOp > inputNest, SmallVectorImpl< AffineForOp > &fullTileLoops, OpBuilder b)
Create the full tile loop nest (along with its body).
static AffineForOp generateShiftedLoop(AffineMap lbMap, AffineMap ubMap, const std::vector< std::pair< uint64_t, ArrayRef< Operation * > > > &opGroupQueue, unsigned offset, AffineForOp srcForOp, OpBuilder b)
Generates an affine.for op with the specified lower and upper bounds while generating the right IV re...
static void getMultiLevelStrides(const MemRefRegion &region, ArrayRef< int64_t > bufferShape, SmallVectorImpl< StrideInfo > *strideInfos)
Returns striding information for a copy/transfer of this region with potentially multiple striding le...
static void constructTiledIndexSetHyperRect(MutableArrayRef< AffineForOp > origLoops, MutableArrayRef< AffineForOp > newLoops, ArrayRef< unsigned > tileSizes)
Constructs and sets new loop bounds after tiling for the case of hyper-rectangular index sets,...
static LogicalResult generateCleanupLoopForUnroll(AffineForOp forOp, uint64_t unrollFactor)
Helper to generate cleanup loop for unroll or unroll-and-jam when the trip count is not a multiple of...
static bool areInnerBoundsInvariant(AffineForOp forOp)
Check if all control operands of all loops are defined outside of forOp and return false if not.
static void moveLoopBody(AffineForOp src, AffineForOp dest)
Move the loop body of AffineForOp 'src' from 'src' to the start of dest body.
static AffineIfOp createSeparationCondition(MutableArrayRef< AffineForOp > loops, OpBuilder b)
Creates an AffineIfOp that encodes the conditional to choose between the constant trip count version ...
auto load
return copyNestRoot
static void gatherLoopsInBlock(Block *block, unsigned currLoopDepth, std::vector< SmallVector< AffineForOp, 2 > > &depthToLoops)
Gathers all AffineForOps in 'block' at 'currLoopDepth' in 'depthToLoops'.
static void getCleanupLoopLowerBound(AffineForOp forOp, unsigned unrollFactor, AffineMap &cleanupLbMap, SmallVectorImpl< Value > &cleanupLbOperands)
Computes the cleanup loop lower bound of the loop being unrolled with the specified unroll factor; th...
Definition LoopUtils.cpp:45
static void augmentMapAndBounds(OpBuilder &b, Value iv, AffineMap *map, SmallVector< Value, 4 > *operands, int64_t offset=0)
static SmallVector< AffineForOp, 8 > stripmineSink(AffineForOp forOp, uint64_t factor, ArrayRef< AffineForOp > targets)
static void replaceIterArgsAndYieldResults(AffineForOp forOp)
Helper to replace uses of loop carried values (iter_args) and loop yield values while promoting singl...
fastBufExprs
Base type for affine expression.
Definition AffineExpr.h:68
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition AffineMap.h:46
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
AffineMap shiftDims(unsigned shift, unsigned offset=0) const
Replace dims[offset ... numDims) by dims[offset + shift ... shift + numDims).
Definition AffineMap.h:267
unsigned getNumSymbols() const
unsigned getNumDims() const
ArrayRef< AffineExpr > getResults() const
unsigned getNumResults() const
unsigned getNumInputs() const
AffineMap shiftSymbols(unsigned shift, unsigned offset=0) const
Replace symbols[offset ... numSymbols) by symbols[offset + shift ... shift + numSymbols).
Definition AffineMap.h:280
AffineExpr getResult(unsigned idx) const
Block represents an ordered list of Operations.
Definition Block.h:34
OpListType::iterator iterator
Definition Block.h:165
Operation * findAncestorOpInBlock(Operation &op)
Returns 'op' if 'op' lies in this block, or otherwise finds the ancestor operation of 'op' that lies ...
Definition Block.cpp:74
Region * getParent() const
Provide a 'getParent' method for ilist_node_with_parent methods.
Definition Block.cpp:27
OpListType & getOperations()
Definition Block.h:162
Operation & front()
Definition Block.h:178
RetT walk(FnT &&callback)
Walk all nested operations, blocks (including this block) or regions, depending on the type of callba...
Definition Block.h:318
Operation * getTerminator()
Get the terminator operation of this block.
Definition Block.cpp:249
iterator end()
Definition Block.h:169
iterator begin()
Definition Block.h:168
Operation * getParentOp()
Returns the closest surrounding operation that contains this block.
Definition Block.cpp:31
AffineMap getDimIdentityMap()
Definition Builders.cpp:392
AffineExpr getAffineDimExpr(unsigned position)
Definition Builders.cpp:373
IntegerSet getAsIntegerSet(MLIRContext *context) const
Returns the constraint system as an integer set.
void getValues(unsigned start, unsigned end, SmallVectorImpl< Value > *values) const
Returns the Values associated with variables in range [start, end).
This class allows control over how the GreedyPatternRewriteDriver works.
This is a utility class for mapping one set of IR entities to another.
Definition IRMapping.h:26
auto lookup(T from) const
Lookup a mapped value within the map.
Definition IRMapping.h:72
void map(Value from, Value to)
Inserts a new mapping for 'from' to 'to'.
Definition IRMapping.h:30
IRValueT get() const
Return the current value being used by this operand.
This class coordinates rewriting a piece of IR outside of a pattern rewrite, providing a way to keep ...
This class represents a diagnostic that is inflight and set to be reported.
An integer set representing a conjunction of one or more affine equalities and inequalities.
Definition IntegerSet.h:44
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
This class helps build Operations.
Definition Builders.h:210
Operation * clone(Operation &op, IRMapping &mapper)
Creates a deep copy of the specified operation, remapping any operands that use values outside of the...
Definition Builders.cpp:581
static OpBuilder atBlockTerminator(Block *block, Listener *listener=nullptr)
Create a builder and set the insertion point to before the block terminator.
Definition Builders.h:255
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Definition Builders.h:415
This class represents an operand of an operation.
Definition Value.h:254
Set of flags used to control the behavior of the various IR print methods (e.g.
A wrapper class that allows for printing an operation with a set of flags, useful to act as a "stream...
Definition Operation.h:1169
This class implements the operand iterators for the Operation class.
Definition ValueRange.h:44
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
bool isBeforeInBlock(Operation *other)
Given an operation 'other' that is within the same parent block, return whether the current operation...
InFlightDiagnostic emitWarning(const Twine &message={})
Emit a warning about this operation, reporting up to any diagnostic handlers that may be listening.
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:230
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
OpTy getParentOfType()
Return the closest surrounding parent operation that is of type 'OpTy'.
Definition Operation.h:255
void setOperands(ValueRange operands)
Replace the current operands of this operation with the ones provided in 'operands'.
Operation * clone(IRMapping &mapper, const CloneOptions &options=CloneOptions::all())
Create a deep copy of this operation, remapping any operands that use values outside of the operation...
Region * getParentRegion()
Returns the region to which the instruction belongs.
Definition Operation.h:247
InFlightDiagnostic emitRemark(const Twine &message={})
Emit a remark about this operation, reporting up to any diagnostic handlers that may be listening.
ParentT getParentOfType()
Find the first parent operation of the given type, or nullptr if there is no ancestor operation.
Definition Region.h:206
MLIRContext * getContext() const
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
bool use_empty() const
Returns true if this value has no uses.
Definition Value.h:208
Type getType() const
Return the type of this value.
Definition Value.h:105
static WalkResult advance()
Definition WalkResult.h:47
static WalkResult interrupt()
Definition WalkResult.h:46
AffineBound represents a lower or upper bound in the for operation.
Definition AffineOps.h:223
Value getOperand(unsigned idx)
Definition AffineOps.h:229
An AffineValueMap is an affine map plus its ML value operands and results for analysis purposes.
ArrayRef< Value > getOperands() const
FlatAffineValueConstraints is an extension of FlatLinearValueConstraints with helper functions for Af...
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:434
Operation * getOwner() const
Return the owner of this operand.
Definition UseDefLists.h:38
An IntegerRelation represents the set of points from a PresburgerSpace that satisfy a list of affine ...
void removeIndependentConstraints(unsigned pos, unsigned num)
Removes constraints that are independent of (i.e., do not have a coefficient) variables in the range ...
void removeTrivialRedundancy()
Removes duplicate constraints, trivially true constraints, and constraints that can be detected as re...
ArrayRef< DynamicAPInt > getInequality(unsigned idx) const
DynamicAPInt atIneq(unsigned i, unsigned j) const
Returns the value at the specified inequality row and column.
bool isHyperRectangular(unsigned pos, unsigned num) const
Returns true if the set can be trivially detected as being hyper-rectangular on the specified contigu...
void setDimSymbolSeparation(unsigned newSymbolCount)
Changes the partition between dimensions and symbols.
unsigned getNumCols() const
Returns the number of columns in the constraint system.
void getLowerAndUpperBoundIndices(unsigned pos, SmallVectorImpl< unsigned > *lbIndices, SmallVectorImpl< unsigned > *ubIndices, SmallVectorImpl< unsigned > *eqIndices=nullptr, unsigned offset=0, unsigned num=0) const
Gather positions of all lower and upper bounds of the variable at pos, and optionally any equalities ...
void removeVar(VarKind kind, unsigned pos)
Removes variables of the specified kind with the specified pos (or within the specified range) from t...
LogicalResult loopUnrollFull(AffineForOp forOp)
Unrolls this for operation completely if the trip count is known to be constant.
LogicalResult promoteIfSingleIteration(AffineForOp forOp)
Promotes the loop body of a AffineForOp to its containing block if the loop was known to have a singl...
LogicalResult loopUnrollJamUpToFactor(AffineForOp forOp, uint64_t unrollJamFactor)
Unrolls and jams this loop by the specified factor or by the trip count (if constant),...
void extractForInductionVars(ArrayRef< AffineForOp > forInsts, SmallVectorImpl< Value > *ivs)
Extracts the induction variables from a list of AffineForOps and places them in the output argument i...
LogicalResult loopUnrollByFactor(AffineForOp forOp, uint64_t unrollFactor, function_ref< void(unsigned, Operation *, OpBuilder)> annotateFn=nullptr, bool cleanUpUnroll=false)
Unrolls this for operation by the specified unroll factor.
void getEnclosingAffineOps(Operation &op, SmallVectorImpl< Operation * > *ops)
Populates 'ops' with affine operations enclosing op ordered from outermost to innermost while stoppin...
Definition Utils.cpp:876
LogicalResult getIndexSet(MutableArrayRef< Operation * > ops, FlatAffineValueConstraints *domain)
Builds a system of constraints with dimensional variables corresponding to the loop IVs of the forOps...
void getPerfectlyNestedLoops(SmallVectorImpl< AffineForOp > &nestedLoops, AffineForOp root)
Get perfectly nested sequence of loops starting at root of loop nest (the first op being another Affi...
LogicalResult affineForOpBodySkew(AffineForOp forOp, ArrayRef< uint64_t > shifts, bool unrollPrologueEpilogue=false)
Skew the operations in an affine.for's body with the specified operation-wise shifts.
void getTripCountMapAndOperands(AffineForOp forOp, AffineMap *map, SmallVectorImpl< Value > *operands)
Returns the trip count of the loop as an affine map with its corresponding operands if the latter is ...
bool isValidLoopInterchangePermutation(ArrayRef< AffineForOp > loops, ArrayRef< unsigned > loopPermMap)
Checks if the loop interchange permutation 'loopPermMap', of the perfectly nested sequence of loops i...
void getSupportedReductions(AffineForOp forOp, SmallVectorImpl< LoopReduction > &supportedReductions)
Populate supportedReductions with descriptors of the supported reductions.
void canonicalizeMapAndOperands(AffineMap *map, SmallVectorImpl< Value > *operands)
Modifies both map and operands in-place so as to:
LogicalResult loopUnrollUpToFactor(AffineForOp forOp, uint64_t unrollFactor)
Unrolls this loop by the specified unroll factor or its trip count, whichever is lower.
void getDependenceComponents(AffineForOp forOp, unsigned maxLoopDepth, std::vector< SmallVector< DependenceComponent, 2 > > *depCompsVec)
Returns in 'depCompsVec', dependence components for dependences between all load and store ops in loo...
unsigned permuteLoops(ArrayRef< AffineForOp > inputNest, ArrayRef< unsigned > permMap)
Performs a loop permutation on a perfectly nested loop nest inputNest (where the contained loops appe...
LogicalResult loopUnrollJamByFactor(AffineForOp forOp, uint64_t unrollJamFactor)
Unrolls and jams this loop by the specified factor.
LogicalResult tilePerfectlyNestedParametric(MutableArrayRef< AffineForOp > input, ArrayRef< Value > tileSizes, SmallVectorImpl< AffineForOp > *tiledNest=nullptr)
Tiles the specified band of perfectly nested loops creating tile-space loops and intra-tile loops,...
void fullyComposeAffineMapAndOperands(AffineMap *map, SmallVectorImpl< Value > *operands, bool composeAffineMin=false)
Given an affine map map and its input operands, this method composes into map, maps of AffineApplyOps...
void canonicalizeSetAndOperands(IntegerSet *set, SmallVectorImpl< Value > *operands)
Canonicalizes an integer set the same way canonicalizeMapAndOperands does for affine maps.
bool isPerfectlyNested(ArrayRef< AffineForOp > loops)
Returns true if loops is a perfectly nested loop nest, where loops appear in it from outermost to inn...
void getAffineForIVs(Operation &op, SmallVectorImpl< AffineForOp > *loops)
Populates 'loops' with IVs of the affine.for ops surrounding 'op' ordered from the outermost 'affine....
Definition Utils.cpp:862
uint64_t getLargestDivisorOfTripCount(AffineForOp forOp)
Returns the greatest known integral divisor of the trip count.
std::optional< uint64_t > getIntOrFloatMemRefSizeInBytes(MemRefType memRefType)
Returns the size of a memref with element type int or float in bytes if it's statically shaped,...
Definition Utils.cpp:1470
int64_t numEnclosingInvariantLoops(OpOperand &operand)
Performs explicit copying for the contiguous sequence of operations in the block iterator range [‘beg...
unsigned getNestingDepth(Operation *op)
Returns the nesting depth of this operation, i.e., the number of loops surrounding this operation.
Definition Utils.cpp:2087
bool isOpwiseShiftValid(AffineForOp forOp, ArrayRef< uint64_t > shifts)
Checks where SSA dominance would be violated if a for op's body operations are shifted by the specifi...
SmallVector< SmallVector< AffineForOp, 8 >, 8 > tile(ArrayRef< AffineForOp > forOps, ArrayRef< uint64_t > sizes, ArrayRef< AffineForOp > targets)
Performs tiling fo imperfectly nested loops (with interchange) by strip-mining the forOps by sizes an...
AffineForOp sinkSequentialLoops(AffineForOp forOp)
LogicalResult tilePerfectlyNested(MutableArrayRef< AffineForOp > input, ArrayRef< unsigned > tileSizes, SmallVectorImpl< AffineForOp > *tiledNest=nullptr)
Tiles the specified band of perfectly nested loops creating tile-space loops and intra-tile loops.
void interchangeLoops(AffineForOp forOpA, AffineForOp forOpB)
Performs loop interchange on 'forOpA' and 'forOpB'.
Value getReductionOp(AtomicRMWKind op, OpBuilder &builder, Location loc, Value lhs, Value rhs)
Returns the value obtained by applying the reduction operation kind associated with a binary AtomicRM...
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:734
Include the generated interface declarations.
AffineMap simplifyAffineMap(AffineMap map)
Simplifies an affine map by simplifying its underlying AffineExpr results.
void replaceAllUsesInRegionWith(Value orig, Value replacement, Region &region)
Replace all uses of orig within the given region with replacement.
AffineMap removeDuplicateExprs(AffineMap map)
Returns a map with the same dimension and symbol count as map, but whose results are the unique affin...
void generateUnrolledLoop(Block *loopBodyBlock, Value iv, uint64_t unrollFactor, function_ref< Value(unsigned, Value, OpBuilder)> ivRemapFn, function_ref< void(unsigned, Operation *, OpBuilder)> annotateFn, ValueRange iterArgs, ValueRange yieldedValues, IRMapping *clonedToSrcOpsMap=nullptr)
Generate unrolled copies of an scf loop's 'loopBodyBlock', with 'iterArgs' and 'yieldedValues' as the...
Definition Utils.cpp:295
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
Definition LLVM.h:125
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...
void bindSymbols(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to SymbolExpr at positions: [0 .
Definition AffineExpr.h:325
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:123
bool areValuesDefinedAbove(Range values, Region &limit)
Check if all values in the provided range are defined above the limit region.
Definition RegionUtils.h:26
@ ExistingAndNewOps
Only pre-existing and newly created ops are processed.
AffineExpr simplifyAffineExpr(AffineExpr expr, unsigned numDims, unsigned numSymbols)
Simplify an affine expression by flattening and some amount of simple analysis.
LogicalResult coalesceLoops(MutableArrayRef< scf::ForOp > loops)
Replace a perfect nest of "for" loops with a single linearized loop.
Definition Utils.cpp:1087
llvm::function_ref< Fn > function_ref
Definition LLVM.h:155
int64_t stride
int64_t numEltPerStride
void walk(Operation *op)
SmallVector< std::pair< Block::iterator, Block::iterator > > subBlocks
Explicit copy / DMA generation options for mlir::affineDataCopyGenerate.
Definition LoopUtils.h:155
std::optional< int64_t > ub
std::optional< int64_t > lb
A description of a (parallelizable) reduction in an affine loop.
A region of a memref's data space; this is typically constructed by analyzing load/store op's on this...
Definition Utils.h:489
std::optional< int64_t > getConstantBoundingSizeAndShape(SmallVectorImpl< int64_t > *shape=nullptr, SmallVectorImpl< AffineMap > *lbs=nullptr) const
Returns a constant upper bound on the number of elements in this region if bounded by a known constan...
Definition Utils.cpp:1167
bool isWrite() const
Definition Utils.h:537
void getLowerAndUpperBound(unsigned pos, AffineMap &lbMap, AffineMap &ubMap) const
Gets the lower and upper bound map for the dimensional variable at pos.
Definition Utils.cpp:1223
void setWrite(bool flag)
Definition Utils.h:538
FlatAffineValueConstraints * getConstraints()
Definition Utils.h:535
Value memref
Memref that this region corresponds to.
Definition Utils.h:570
Location loc
If there is more than one load/store op associated with the region, the location information would co...
Definition Utils.h:577
Eliminates variable at the specified position using Fourier-Motzkin variable elimination.