MLIR 24.0.0git
Utils.cpp
Go to the documentation of this file.
1//===- Utils.cpp ---- Utilities for affine dialect 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 transformation utilities for the Affine
10// dialect.
11//
12//===----------------------------------------------------------------------===//
13
15
25#include "mlir/IR/Dominance.h"
26#include "mlir/IR/IRMapping.h"
27#include "mlir/IR/IntegerSet.h"
29#include "llvm/ADT/SmallVectorExtras.h"
30#include <optional>
31
32#define DEBUG_TYPE "affine-utils"
33
34using namespace mlir;
35using namespace affine;
36using namespace presburger;
37
38namespace {
39/// Visit affine expressions recursively and build the sequence of operations
40/// that correspond to it. Visitation functions return an Value of the
41/// expression subtree they visited or `nullptr` on error.
42class AffineApplyExpander
43 : public AffineExprVisitor<AffineApplyExpander, Value> {
44public:
45 /// This internal class expects arguments to be non-null, checks must be
46 /// performed at the call site.
47 AffineApplyExpander(OpBuilder &builder, ValueRange dimValues,
48 ValueRange symbolValues, Location loc)
49 : builder(builder), dimValues(dimValues), symbolValues(symbolValues),
50 loc(loc) {}
51
52 template <typename OpTy>
53 Value buildBinaryExpr(AffineBinaryOpExpr expr,
54 arith::IntegerOverflowFlags overflowFlags =
55 arith::IntegerOverflowFlags::none) {
56 auto lhs = visit(expr.getLHS());
57 auto rhs = visit(expr.getRHS());
58 if (!lhs || !rhs)
59 return nullptr;
60 auto op = OpTy::create(builder, loc, lhs, rhs, overflowFlags);
61 return op.getResult();
62 }
63
64 Value visitAddExpr(AffineBinaryOpExpr expr) {
65 return buildBinaryExpr<arith::AddIOp>(expr);
66 }
67
68 Value visitMulExpr(AffineBinaryOpExpr expr) {
69 return buildBinaryExpr<arith::MulIOp>(expr,
70 arith::IntegerOverflowFlags::nsw);
71 }
72
73 /// Euclidean modulo operation: negative RHS is not allowed.
74 /// Remainder of the euclidean integer division is always non-negative.
75 ///
76 /// Implemented as
77 ///
78 /// a mod b =
79 /// let remainder = srem a, b;
80 /// negative = a < 0 in
81 /// select negative, remainder + b, remainder.
82 Value visitModExpr(AffineBinaryOpExpr expr) {
83 if (auto rhsConst = dyn_cast<AffineConstantExpr>(expr.getRHS())) {
84 if (rhsConst.getValue() <= 0) {
85 emitError(loc, "modulo by non-positive value is not supported");
86 return nullptr;
87 }
88 }
89
90 auto lhs = visit(expr.getLHS());
91 auto rhs = visit(expr.getRHS());
92 assert(lhs && rhs && "unexpected affine expr lowering failure");
93
94 Value remainder = arith::RemSIOp::create(builder, loc, lhs, rhs);
95 Value zeroCst = arith::ConstantIndexOp::create(builder, loc, 0);
96 Value isRemainderNegative = arith::CmpIOp::create(
97 builder, loc, arith::CmpIPredicate::slt, remainder, zeroCst);
98 Value correctedRemainder =
99 arith::AddIOp::create(builder, loc, remainder, rhs);
100 Value result = arith::SelectOp::create(builder, loc, isRemainderNegative,
101 correctedRemainder, remainder);
102 return result;
103 }
104
105 /// Floor division operation (rounds towards negative infinity).
106 ///
107 /// For positive divisors, it can be implemented without branching and with a
108 /// single division operation as
109 ///
110 /// a floordiv b =
111 /// let negative = a < 0 in
112 /// let absolute = negative ? -a - 1 : a in
113 /// let quotient = absolute / b in
114 /// negative ? -quotient - 1 : quotient
115 ///
116 /// Note: this lowering does not use arith.floordivsi because the lowering of
117 /// that to arith.divsi (see populateCeilFloorDivExpandOpsPatterns) generates
118 /// not one but two arith.divsi. That could be changed to one divsi, but one
119 /// way or another, going through arith.floordivsi will result in more complex
120 /// IR because arith.floordivsi is more general than affine floordiv in that
121 /// it supports negative RHS.
122 Value visitFloorDivExpr(AffineBinaryOpExpr expr) {
123 if (auto rhsConst = dyn_cast<AffineConstantExpr>(expr.getRHS())) {
124 if (rhsConst.getValue() <= 0) {
125 emitError(loc, "division by non-positive value is not supported");
126 return nullptr;
127 }
128 }
129 auto lhs = visit(expr.getLHS());
130 auto rhs = visit(expr.getRHS());
131 assert(lhs && rhs && "unexpected affine expr lowering failure");
132
133 Value zeroCst = arith::ConstantIndexOp::create(builder, loc, 0);
134 Value noneCst = arith::ConstantIndexOp::create(builder, loc, -1);
135 Value negative = arith::CmpIOp::create(
136 builder, loc, arith::CmpIPredicate::slt, lhs, zeroCst);
137 Value negatedDecremented =
138 arith::SubIOp::create(builder, loc, noneCst, lhs);
139 Value dividend = arith::SelectOp::create(builder, loc, negative,
140 negatedDecremented, lhs);
141 Value quotient = arith::DivSIOp::create(builder, loc, dividend, rhs);
142 Value correctedQuotient =
143 arith::SubIOp::create(builder, loc, noneCst, quotient);
144 Value result = arith::SelectOp::create(builder, loc, negative,
145 correctedQuotient, quotient);
146 return result;
147 }
148
149 /// Ceiling division operation (rounds towards positive infinity).
150 ///
151 /// For positive divisors, it can be implemented without branching and with a
152 /// single division operation as
153 ///
154 /// a ceildiv b =
155 /// let negative = a <= 0 in
156 /// let absolute = negative ? -a : a - 1 in
157 /// let quotient = absolute / b in
158 /// negative ? -quotient : quotient + 1
159 ///
160 /// Note: not using arith.ceildivsi for the same reason as explained in the
161 /// visitFloorDivExpr comment.
162 Value visitCeilDivExpr(AffineBinaryOpExpr expr) {
163 if (auto rhsConst = dyn_cast<AffineConstantExpr>(expr.getRHS())) {
164 if (rhsConst.getValue() <= 0) {
165 emitError(loc, "division by non-positive value is not supported");
166 return nullptr;
167 }
168 }
169 auto lhs = visit(expr.getLHS());
170 auto rhs = visit(expr.getRHS());
171 assert(lhs && rhs && "unexpected affine expr lowering failure");
172
173 Value zeroCst = arith::ConstantIndexOp::create(builder, loc, 0);
174 Value oneCst = arith::ConstantIndexOp::create(builder, loc, 1);
175 Value nonPositive = arith::CmpIOp::create(
176 builder, loc, arith::CmpIPredicate::sle, lhs, zeroCst);
177 Value negated = arith::SubIOp::create(builder, loc, zeroCst, lhs);
178 Value decremented = arith::SubIOp::create(builder, loc, lhs, oneCst);
179 Value dividend = arith::SelectOp::create(builder, loc, nonPositive, negated,
180 decremented);
181 Value quotient = arith::DivSIOp::create(builder, loc, dividend, rhs);
182 Value negatedQuotient =
183 arith::SubIOp::create(builder, loc, zeroCst, quotient);
184 Value incrementedQuotient =
185 arith::AddIOp::create(builder, loc, quotient, oneCst);
186 Value result = arith::SelectOp::create(
187 builder, loc, nonPositive, negatedQuotient, incrementedQuotient);
188 return result;
189 }
190
191 Value visitConstantExpr(AffineConstantExpr expr) {
192 auto op = arith::ConstantIndexOp::create(builder, loc, expr.getValue());
193 return op.getResult();
194 }
195
196 Value visitDimExpr(AffineDimExpr expr) {
197 assert(expr.getPosition() < dimValues.size() &&
198 "affine dim position out of range");
199 return dimValues[expr.getPosition()];
200 }
201
202 Value visitSymbolExpr(AffineSymbolExpr expr) {
203 assert(expr.getPosition() < symbolValues.size() &&
204 "symbol dim position out of range");
205 return symbolValues[expr.getPosition()];
206 }
207
208private:
209 OpBuilder &builder;
210 ValueRange dimValues;
211 ValueRange symbolValues;
212
213 Location loc;
214};
215} // namespace
216
217/// Create a sequence of operations that implement the `expr` applied to the
218/// given dimension and symbol values.
219mlir::Value mlir::affine::expandAffineExpr(OpBuilder &builder, Location loc,
220 AffineExpr expr,
221 ValueRange dimValues,
222 ValueRange symbolValues) {
223 return AffineApplyExpander(builder, dimValues, symbolValues, loc).visit(expr);
224}
225
226/// Create a sequence of operations that implement the `affineMap` applied to
227/// the given `operands` (as it it were an AffineApplyOp).
228std::optional<SmallVector<Value, 8>>
229mlir::affine::expandAffineMap(OpBuilder &builder, Location loc,
230 AffineMap affineMap, ValueRange operands) {
231 auto numDims = affineMap.getNumDims();
232 auto expanded = llvm::map_to_vector<8>(
233 affineMap.getResults(),
234 [numDims, &builder, loc, operands](AffineExpr expr) {
235 return expandAffineExpr(builder, loc, expr,
236 operands.take_front(numDims),
237 operands.drop_front(numDims));
238 });
239 if (llvm::all_of(expanded, [](Value v) { return v; }))
240 return expanded;
241 return std::nullopt;
242}
243
244/// Promotes the `then` or the `else` block of `ifOp` (depending on whether
245/// `elseBlock` is false or true) into `ifOp`'s containing block, and discards
246/// the rest of the op.
247static void promoteIfBlock(AffineIfOp ifOp, bool elseBlock) {
248 if (elseBlock)
249 assert(ifOp.hasElse() && "else block expected");
250
251 Block *destBlock = ifOp->getBlock();
252 Block *srcBlock = elseBlock ? ifOp.getElseBlock() : ifOp.getThenBlock();
253 destBlock->getOperations().splice(
254 Block::iterator(ifOp), srcBlock->getOperations(), srcBlock->begin(),
255 std::prev(srcBlock->end()));
256 ifOp.erase();
257}
258
259/// Returns the outermost affine.for/parallel op that the `ifOp` is invariant
260/// on. The `ifOp` could be hoisted and placed right before such an operation.
261/// This method assumes that the ifOp has been canonicalized (to be correct and
262/// effective).
263static Operation *getOutermostInvariantForOp(AffineIfOp ifOp) {
264 // Walk up the parents past all for op that this conditional is invariant on.
265 auto ifOperands = ifOp.getOperands();
266 Operation *res = ifOp;
267 while (!res->getParentOp()->hasTrait<OpTrait::IsIsolatedFromAbove>()) {
268 auto *parentOp = res->getParentOp();
269 if (auto forOp = dyn_cast<AffineForOp>(parentOp)) {
270 if (llvm::is_contained(ifOperands, forOp.getInductionVar()))
271 break;
272 } else if (auto parallelOp = dyn_cast<AffineParallelOp>(parentOp)) {
273 if (llvm::any_of(parallelOp.getIVs(), [&](Value iv) {
274 return llvm::is_contained(ifOperands, iv);
275 }))
276 break;
277 } else if (!isa<AffineIfOp>(parentOp)) {
278 // Won't walk up past anything other than affine.for/if ops.
279 break;
280 }
281 // You can always hoist up past any affine.if ops.
282 res = parentOp;
283 }
284 return res;
285}
286
287/// A helper for the mechanics of mlir::hoistAffineIfOp. Hoists `ifOp` just over
288/// `hoistOverOp`. Returns the new hoisted op if any hoisting happened,
289/// otherwise the same `ifOp`.
290static AffineIfOp hoistAffineIfOp(AffineIfOp ifOp, Operation *hoistOverOp) {
291 // No hoisting to do.
292 if (hoistOverOp == ifOp)
293 return ifOp;
294
295 // Create the hoisted 'if' first. Then, clone the op we are hoisting over for
296 // the else block. Then drop the else block of the original 'if' in the 'then'
297 // branch while promoting its then block, and analogously drop the 'then'
298 // block of the original 'if' from the 'else' branch while promoting its else
299 // block.
300 IRMapping operandMap;
301 OpBuilder b(hoistOverOp);
302 auto hoistedIfOp = AffineIfOp::create(b, ifOp.getLoc(), ifOp.getIntegerSet(),
303 ifOp.getOperands(),
304 /*elseBlock=*/true);
305
306 // Create a clone of hoistOverOp to use for the else branch of the hoisted
307 // conditional. The else block may get optimized away if empty.
308 Operation *hoistOverOpClone = nullptr;
309 // We use this unique name to identify/find `ifOp`'s clone in the else
310 // version.
311 StringAttr idForIfOp = b.getStringAttr("__mlir_if_hoisting");
312 operandMap.clear();
313 b.setInsertionPointAfter(hoistOverOp);
314 // We'll set an attribute to identify this op in a clone of this sub-tree.
315 ifOp->setDiscardableAttr(idForIfOp, b.getBoolAttr(true));
316 hoistOverOpClone = b.clone(*hoistOverOp, operandMap);
317
318 // Promote the 'then' block of the original affine.if in the then version.
319 promoteIfBlock(ifOp, /*elseBlock=*/false);
320
321 // Move the then version to the hoisted if op's 'then' block.
322 auto *thenBlock = hoistedIfOp.getThenBlock();
323 thenBlock->getOperations().splice(thenBlock->begin(),
324 hoistOverOp->getBlock()->getOperations(),
325 Block::iterator(hoistOverOp));
326
327 // Find the clone of the original affine.if op in the else version.
328 AffineIfOp ifCloneInElse;
329 hoistOverOpClone->walk([&](AffineIfOp ifClone) {
330 if (!ifClone->getDiscardableAttr(idForIfOp))
331 return WalkResult::advance();
332 ifCloneInElse = ifClone;
333 return WalkResult::interrupt();
334 });
335 assert(ifCloneInElse && "if op clone should exist");
336 // For the else block, promote the else block of the original 'if' if it had
337 // one; otherwise, the op itself is to be erased.
338 if (!ifCloneInElse.hasElse())
339 ifCloneInElse.erase();
340 else
341 promoteIfBlock(ifCloneInElse, /*elseBlock=*/true);
342
343 // Move the else version into the else block of the hoisted if op.
344 auto *elseBlock = hoistedIfOp.getElseBlock();
345 elseBlock->getOperations().splice(
346 elseBlock->begin(), hoistOverOpClone->getBlock()->getOperations(),
347 Block::iterator(hoistOverOpClone));
348
349 return hoistedIfOp;
350}
351
352LogicalResult
354 ArrayRef<LoopReduction> parallelReductions,
355 AffineParallelOp *resOp) {
356 // Fail early if there are iter arguments that are not reductions.
357 unsigned numReductions = parallelReductions.size();
358 if (numReductions != forOp.getNumIterOperands())
359 return failure();
360
361 Location loc = forOp.getLoc();
362 OpBuilder outsideBuilder(forOp);
363 AffineMap lowerBoundMap = forOp.getLowerBoundMap();
364 ValueRange lowerBoundOperands = forOp.getLowerBoundOperands();
365 AffineMap upperBoundMap = forOp.getUpperBoundMap();
366 ValueRange upperBoundOperands = forOp.getUpperBoundOperands();
367
368 // Creating empty 1-D affine.parallel op.
369 auto reducedValues = llvm::map_to_vector<4>(
370 parallelReductions, [](const LoopReduction &red) { return red.value; });
371 auto reductionKinds = llvm::map_to_vector<4>(
372 parallelReductions, [](const LoopReduction &red) { return red.kind; });
373 AffineParallelOp newPloop = AffineParallelOp::create(
374 outsideBuilder, loc, ValueRange(reducedValues).getTypes(), reductionKinds,
375 llvm::ArrayRef(lowerBoundMap), lowerBoundOperands,
376 llvm::ArrayRef(upperBoundMap), upperBoundOperands,
377 llvm::ArrayRef(forOp.getStepAsInt()));
378 // Steal the body of the old affine for op.
379 newPloop.getRegion().takeBody(forOp.getRegion());
380 Operation *yieldOp = &newPloop.getBody()->back();
381
382 // Handle the initial values of reductions because the parallel loop always
383 // starts from the neutral value.
384 SmallVector<Value> newResults;
385 newResults.reserve(numReductions);
386 for (unsigned i = 0; i < numReductions; ++i) {
387 Value init = forOp.getInits()[i];
388 // This works because we are only handling single-op reductions at the
389 // moment. A switch on reduction kind or a mechanism to collect operations
390 // participating in the reduction will be necessary for multi-op reductions.
391 Operation *reductionOp = yieldOp->getOperand(i).getDefiningOp();
392 assert(reductionOp && "yielded value is expected to be produced by an op");
393 outsideBuilder.getInsertionBlock()->getOperations().splice(
394 outsideBuilder.getInsertionPoint(), newPloop.getBody()->getOperations(),
395 reductionOp);
396 reductionOp->setOperands({init, newPloop->getResult(i)});
397 forOp->getResult(i).replaceAllUsesWith(reductionOp->getResult(0));
398 }
399
400 // Update the loop terminator to yield reduced values bypassing the reduction
401 // operation itself (now moved outside of the loop) and erase the block
402 // arguments that correspond to reductions. Note that the loop always has one
403 // "main" induction variable whenc coming from a non-parallel for.
404 unsigned numIVs = 1;
405 yieldOp->setOperands(reducedValues);
406 newPloop.getBody()->eraseArguments(numIVs, numReductions);
407
408 forOp.erase();
409 if (resOp)
410 *resOp = newPloop;
411 return success();
412}
413
414// Returns success if any hoisting happened.
415LogicalResult mlir::affine::hoistAffineIfOp(AffineIfOp ifOp, bool *folded) {
416 // Bail out early if the ifOp returns a result. TODO: Consider how to
417 // properly support this case.
418 if (ifOp.getNumResults() != 0)
419 return failure();
420
421 // Apply canonicalization patterns and folding - this is necessary for the
422 // hoisting check to be correct (operands should be composed), and to be more
423 // effective (no unused operands). Since the pattern rewriter's folding is
424 // entangled with application of patterns, we may fold/end up erasing the op,
425 // in which case we return with `folded` being set.
426 RewritePatternSet patterns(ifOp.getContext());
427 AffineIfOp::getCanonicalizationPatterns(patterns, ifOp.getContext());
428 FrozenRewritePatternSet frozenPatterns(std::move(patterns));
429 bool erased;
431 ifOp.getOperation(), frozenPatterns,
433 /*changed=*/nullptr, &erased);
434 if (erased) {
435 if (folded)
436 *folded = true;
437 return failure();
438 }
439 if (folded)
440 *folded = false;
441
442 // The folding above should have ensured this.
443 assert(llvm::all_of(ifOp.getOperands(),
444 [](Value v) {
445 return isTopLevelValue(v) || isAffineInductionVar(v);
446 }) &&
447 "operands not composed");
448
449 // We are going hoist as high as possible.
450 // TODO: this could be customized in the future.
451 auto *hoistOverOp = getOutermostInvariantForOp(ifOp);
452
453 AffineIfOp hoistedIfOp = ::hoistAffineIfOp(ifOp, hoistOverOp);
454 // Nothing to hoist over.
455 if (hoistedIfOp == ifOp)
456 return failure();
457
458 // Canonicalize to remove dead else blocks (happens whenever an 'if' moves up
459 // a sequence of affine.fors that are all perfectly nested).
461 hoistedIfOp->getParentWithTrait<OpTrait::IsIsolatedFromAbove>(),
462 frozenPatterns);
463
464 return success();
465}
466
467// Return the min expr after replacing the given dim.
468AffineExpr mlir::affine::substWithMin(AffineExpr e, AffineExpr dim,
470 bool positivePath) {
471 if (e == dim)
472 return positivePath ? min : max;
473 if (auto bin = dyn_cast<AffineBinaryOpExpr>(e)) {
474 AffineExpr lhs = bin.getLHS();
475 AffineExpr rhs = bin.getRHS();
476 if (bin.getKind() == mlir::AffineExprKind::Add)
477 return substWithMin(lhs, dim, min, max, positivePath) +
478 substWithMin(rhs, dim, min, max, positivePath);
479
480 auto c1 = dyn_cast<AffineConstantExpr>(bin.getLHS());
481 auto c2 = dyn_cast<AffineConstantExpr>(bin.getRHS());
482 if (c1 && c1.getValue() < 0)
484 bin.getKind(), c1, substWithMin(rhs, dim, min, max, !positivePath));
485 if (c2 && c2.getValue() < 0)
487 bin.getKind(), substWithMin(lhs, dim, min, max, !positivePath), c2);
489 bin.getKind(), substWithMin(lhs, dim, min, max, positivePath),
490 substWithMin(rhs, dim, min, max, positivePath));
491 }
492 return e;
493}
494
495void mlir::affine::normalizeAffineParallel(AffineParallelOp op) {
496 // Loops with min/max in bounds are not normalized at the moment.
497 if (op.hasMinMaxBounds())
498 return;
499
500 AffineMap lbMap = op.getLowerBoundsMap();
501 SmallVector<int64_t, 8> steps = op.getSteps();
502 // No need to do any work if the parallel op is already normalized.
503 bool isAlreadyNormalized =
504 llvm::all_of(llvm::zip(steps, lbMap.getResults()), [](auto tuple) {
505 int64_t step = std::get<0>(tuple);
506 auto lbExpr = dyn_cast<AffineConstantExpr>(std::get<1>(tuple));
507 return lbExpr && lbExpr.getValue() == 0 && step == 1;
508 });
509 if (isAlreadyNormalized)
510 return;
511
512 AffineValueMap ranges;
513 AffineValueMap::difference(op.getUpperBoundsValueMap(),
514 op.getLowerBoundsValueMap(), &ranges);
515 auto builder = OpBuilder::atBlockBegin(op.getBody());
516 auto zeroExpr = builder.getAffineConstantExpr(0);
519 for (unsigned i = 0, e = steps.size(); i < e; ++i) {
520 int64_t step = steps[i];
521
522 // Adjust the lower bound to be 0.
523 lbExprs.push_back(zeroExpr);
524
525 // Adjust the upper bound expression: 'range / step'.
526 AffineExpr ubExpr = ranges.getResult(i).ceilDiv(step);
527 ubExprs.push_back(ubExpr);
528
529 // Adjust the corresponding IV: 'lb + i * step'.
530 BlockArgument iv = op.getBody()->getArgument(i);
531 AffineExpr lbExpr = lbMap.getResult(i);
532 unsigned nDims = lbMap.getNumDims();
533 auto expr = lbExpr + builder.getAffineDimExpr(nDims) * step;
534 auto map = AffineMap::get(/*dimCount=*/nDims + 1,
535 /*symbolCount=*/lbMap.getNumSymbols(), expr);
536
537 // Use an 'affine.apply' op that will be simplified later in subsequent
538 // canonicalizations.
539 OperandRange lbOperands = op.getLowerBoundsOperands();
540 OperandRange dimOperands = lbOperands.take_front(nDims);
541 OperandRange symbolOperands = lbOperands.drop_front(nDims);
542 SmallVector<Value, 8> applyOperands{dimOperands};
543 applyOperands.push_back(iv);
544 applyOperands.append(symbolOperands.begin(), symbolOperands.end());
545 auto apply =
546 AffineApplyOp::create(builder, op.getLoc(), map, applyOperands);
547 iv.replaceAllUsesExcept(apply, apply);
548 }
549
550 SmallVector<int64_t, 8> newSteps(op.getNumDims(), 1);
551 op.setSteps(newSteps);
552 auto newLowerMap = AffineMap::get(
553 /*dimCount=*/0, /*symbolCount=*/0, lbExprs, op.getContext());
554 op.setLowerBounds({}, newLowerMap);
555 auto newUpperMap = AffineMap::get(ranges.getNumDims(), ranges.getNumSymbols(),
556 ubExprs, op.getContext());
557 op.setUpperBounds(ranges.getOperands(), newUpperMap);
558}
559
560LogicalResult mlir::affine::normalizeAffineFor(AffineForOp op,
561 bool promoteSingleIter) {
562 if (promoteSingleIter && succeeded(promoteIfSingleIteration(op)))
563 return success();
564
565 // Check if the forop is already normalized.
566 if (op.hasConstantLowerBound() && (op.getConstantLowerBound() == 0) &&
567 (op.getStep() == 1))
568 return success();
569
570 // Check if the lower bound has a single result only. Loops with a max lower
571 // bound can't be normalized without additional support like
572 // affine.execute_region's. If the lower bound does not have a single result
573 // then skip this op.
574 if (op.getLowerBoundMap().getNumResults() != 1)
575 return failure();
576
577 Location loc = op.getLoc();
578 OpBuilder opBuilder(op);
579 int64_t origLoopStep = op.getStepAsInt();
580
581 // Construct the new upper bound value map.
582 AffineMap oldLbMap = op.getLowerBoundMap();
583 // The upper bound can have multiple results. To use
584 // AffineValueMap::difference, we need to have the same number of results in
585 // both lower and upper bound maps. So, we just create a value map for the
586 // lower bound with the only available lower bound result repeated to pad up
587 // to the number of upper bound results.
588 SmallVector<AffineExpr> lbExprs(op.getUpperBoundMap().getNumResults(),
589 op.getLowerBoundMap().getResult(0));
590 AffineValueMap lbMap(oldLbMap, op.getLowerBoundOperands());
591 AffineMap paddedLbMap =
592 AffineMap::get(oldLbMap.getNumDims(), oldLbMap.getNumSymbols(), lbExprs,
593 op.getContext());
594 AffineValueMap paddedLbValueMap(paddedLbMap, op.getLowerBoundOperands());
595 AffineValueMap ubValueMap(op.getUpperBoundMap(), op.getUpperBoundOperands());
596 AffineValueMap newUbValueMap;
597 // Compute the `upper bound - lower bound`.
598 AffineValueMap::difference(ubValueMap, paddedLbValueMap, &newUbValueMap);
599 (void)newUbValueMap.canonicalize();
600
601 // Scale down the upper bound value map by the loop step.
602 unsigned numResult = newUbValueMap.getNumResults();
603 SmallVector<AffineExpr> scaleDownExprs(numResult);
604 for (unsigned i = 0; i < numResult; ++i)
605 scaleDownExprs[i] = opBuilder.getAffineDimExpr(i).ceilDiv(origLoopStep);
606 // `scaleDownMap` is (d0, d1, ..., d_n) -> (d0 / step, d1 / step, ..., d_n /
607 // step). Where `n` is the number of results in the upper bound map.
608 AffineMap scaleDownMap =
609 AffineMap::get(numResult, 0, scaleDownExprs, op.getContext());
610 AffineMap newUbMap = scaleDownMap.compose(newUbValueMap.getAffineMap());
611
612 // Set the newly create upper bound map and operands.
613 op.setUpperBound(newUbValueMap.getOperands(), newUbMap);
614 op.setLowerBound({}, opBuilder.getConstantAffineMap(0));
615 op.setStep(1);
616
617 // Calculate the Value of new loopIV. Create affine.apply for the value of
618 // the loopIV in normalized loop.
619 opBuilder.setInsertionPointToStart(op.getBody());
620 // Construct an affine.apply op mapping the new IV to the old IV.
621 AffineMap scaleIvMap =
622 AffineMap::get(1, 0, -opBuilder.getAffineDimExpr(0) * origLoopStep);
623 AffineValueMap scaleIvValueMap(scaleIvMap, ValueRange{op.getInductionVar()});
624 AffineValueMap newIvToOldIvMap;
625 AffineValueMap::difference(lbMap, scaleIvValueMap, &newIvToOldIvMap);
626 (void)newIvToOldIvMap.canonicalize();
627 auto newIV =
628 AffineApplyOp::create(opBuilder, loc, newIvToOldIvMap.getAffineMap(),
629 newIvToOldIvMap.getOperands());
630 op.getInductionVar().replaceAllUsesExcept(newIV->getResult(0), newIV);
631 return success();
632}
633
634/// Returns true if the memory operation of `destAccess` depends on `srcAccess`
635/// inside of the innermost common surrounding affine loop between the two
636/// accesses.
637static bool mustReachAtInnermost(const MemRefAccess &srcAccess,
638 const MemRefAccess &destAccess) {
639 // Affine dependence analysis is possible only if both ops in the same
640 // AffineScope.
641 if (getAffineAnalysisScope(srcAccess.opInst) !=
642 getAffineAnalysisScope(destAccess.opInst))
643 return false;
644
645 unsigned nsLoops =
646 getNumCommonSurroundingLoops(*srcAccess.opInst, *destAccess.opInst);
648 checkMemrefAccessDependence(srcAccess, destAccess, nsLoops + 1);
649 return hasDependence(result);
650}
651
652/// Returns true if `srcMemOp` may have an effect on `destMemOp` within the
653/// scope of the outermost `minSurroundingLoops` loops that surround them.
654/// `srcMemOp` and `destMemOp` are expected to be affine read/write ops.
655static bool mayHaveEffect(Operation *srcMemOp, Operation *destMemOp,
656 unsigned minSurroundingLoops) {
657 MemRefAccess srcAccess(srcMemOp);
658 MemRefAccess destAccess(destMemOp);
659
660 // Affine dependence analysis here is applicable only if both ops operate on
661 // the same memref and if `srcMemOp` and `destMemOp` are in the same
662 // AffineScope. Also, we can only check if our affine scope is isolated from
663 // above; otherwise, values can from outside of the affine scope that the
664 // check below cannot analyze.
665 Region *srcScope = getAffineAnalysisScope(srcMemOp);
666 if (srcAccess.memref == destAccess.memref &&
667 srcScope == getAffineAnalysisScope(destMemOp)) {
668 unsigned nsLoops = getNumCommonSurroundingLoops(*srcMemOp, *destMemOp);
669 FlatAffineValueConstraints dependenceConstraints;
670 for (unsigned d = nsLoops + 1; d > minSurroundingLoops; d--) {
672 srcAccess, destAccess, d, &dependenceConstraints,
673 /*dependenceComponents=*/nullptr);
674 // A dependence failure or the presence of a dependence implies a
675 // side effect.
676 if (!noDependence(result))
677 return true;
678 }
679 // No side effect was seen.
680 return false;
681 }
682 // TODO: Check here if the memrefs alias: there is no side effect if
683 // `srcAccess.memref` and `destAccess.memref` don't alias.
684 return true;
685}
686
687template <typename EffectType, typename T>
689 Operation *start, T memOp,
691 // A boolean representing whether an intervening operation could have impacted
692 // memOp.
693 bool hasSideEffect = false;
694
695 // Check whether the effect on memOp can be caused by a given operation op.
696 Value memref = memOp.getMemRef();
697 std::function<void(Operation *)> checkOperation = [&](Operation *op) {
698 // If the effect has alreay been found, early exit,
699 if (hasSideEffect)
700 return;
701
702 if (auto memEffect = dyn_cast<MemoryEffectOpInterface>(op)) {
704 memEffect.getEffects(effects);
705
706 bool opMayHaveEffect = false;
707 for (auto effect : effects) {
708 // If op causes EffectType on a potentially aliasing location for
709 // memOp, mark as having the effect.
710 if (isa<EffectType>(effect.getEffect())) {
711 if (effect.getValue() && effect.getValue() != memref &&
712 !mayAlias(effect.getValue(), memref))
713 continue;
714 opMayHaveEffect = true;
715 break;
716 }
717 }
718
719 if (!opMayHaveEffect)
720 return;
721
722 // If the side effect comes from an affine read or write, try to
723 // prove the side effecting `op` cannot reach `memOp`.
724 if (isa<AffineReadOpInterface, AffineWriteOpInterface>(op)) {
725 // For ease, let's consider the case that `op` is a store and
726 // we're looking for other potential stores that overwrite memory after
727 // `start`, and before being read in `memOp`. In this case, we only
728 // need to consider other potential stores with depth >
729 // minSurroundingLoops since `start` would overwrite any store with a
730 // smaller number of surrounding loops before.
731 unsigned minSurroundingLoops =
732 getNumCommonSurroundingLoops(*start, *memOp);
733 if (mayHaveEffect(op, memOp, minSurroundingLoops))
734 hasSideEffect = true;
735 return;
736 }
737
738 // We have an op with a memory effect and we cannot prove if it
739 // intervenes.
740 hasSideEffect = true;
741 return;
742 }
743
744 if (op->hasTrait<OpTrait::HasRecursiveMemoryEffects>()) {
745 // Recurse into the regions for this op and check whether the internal
746 // operations may have the side effect `EffectType` on memOp.
747 for (Region &region : op->getRegions())
748 for (Block &block : region)
749 for (Operation &op : block)
750 checkOperation(&op);
751 return;
752 }
753
754 // Otherwise, conservatively assume generic operations have the effect
755 // on the operation
756 hasSideEffect = true;
757 };
758
759 // Check all paths from ancestor op `parent` to the operation `to` for the
760 // effect. It is known that `to` must be contained within `parent`.
761 auto until = [&](Operation *parent, Operation *to) {
762 // TODO check only the paths from `parent` to `to`.
763 // Currently we fallback and check the entire parent op, rather than
764 // just the paths from the parent path, stopping after reaching `to`.
765 // This is conservatively correct, but could be made more aggressive.
766 assert(parent->isAncestor(to));
767 checkOperation(parent);
768 };
769
770 // Check for all paths from operation `from` to operation `untilOp` for the
771 // given memory effect.
772 std::function<void(Operation *, Operation *)> recur =
773 [&](Operation *from, Operation *untilOp) {
774 assert(
775 from->getParentRegion()->isAncestor(untilOp->getParentRegion()) &&
776 "Checking for side effect between two operations without a common "
777 "ancestor");
778
779 // If the operations are in different regions, recursively consider all
780 // path from `from` to the parent of `to` and all paths from the parent
781 // of `to` to `to`.
782 if (from->getParentRegion() != untilOp->getParentRegion()) {
783 recur(from, untilOp->getParentOp());
784 until(untilOp->getParentOp(), untilOp);
785 return;
786 }
787
788 // Now, assuming that `from` and `to` exist in the same region, perform
789 // a CFG traversal to check all the relevant operations.
790
791 // Additional blocks to consider.
792 SmallVector<Block *, 2> todoBlocks;
793 {
794 // First consider the parent block of `from` an check all operations
795 // after `from`.
796 for (auto iter = ++from->getIterator(), end = from->getBlock()->end();
797 iter != end && &*iter != untilOp; ++iter) {
798 checkOperation(&*iter);
799 }
800
801 // If the parent of `from` doesn't contain `to`, add the successors
802 // to the list of blocks to check.
803 if (untilOp->getBlock() != from->getBlock())
804 for (Block *succ : from->getBlock()->getSuccessors())
805 todoBlocks.push_back(succ);
806 }
807
809 // Traverse the CFG until hitting `to`.
810 while (!todoBlocks.empty()) {
811 Block *blk = todoBlocks.pop_back_val();
812 if (done.count(blk))
813 continue;
814 done.insert(blk);
815 for (auto &op : *blk) {
816 if (&op == untilOp)
817 break;
818 checkOperation(&op);
819 if (&op == blk->getTerminator())
820 for (Block *succ : blk->getSuccessors())
821 todoBlocks.push_back(succ);
822 }
823 }
824 };
825 recur(start, memOp);
826 return !hasSideEffect;
827}
828
829/// Attempt to eliminate loadOp by replacing it with a value stored into memory
830/// which the load is guaranteed to retrieve. This check involves three
831/// components: 1) The store and load must be on the same location 2) The store
832/// must dominate (and therefore must always occur prior to) the load 3) No
833/// other operations will overwrite the memory loaded between the given load
834/// and store. If such a value exists, the replaced `loadOp` will be added to
835/// `loadOpsToErase` and its memref will be added to `memrefsToErase`.
837 AffineReadOpInterface loadOp, SmallVectorImpl<Operation *> &loadOpsToErase,
838 SmallPtrSetImpl<Value> &memrefsToErase, DominanceInfo &domInfo,
840
841 // The store op candidate for forwarding that satisfies all conditions
842 // to replace the load, if any.
843 Operation *lastWriteStoreOp = nullptr;
844
845 for (auto storeOp : llvm::make_isa_range<AffineWriteOpInterface>(
846 loadOp.getMemRef().getUsers())) {
847 MemRefAccess srcAccess(storeOp);
848 MemRefAccess destAccess(loadOp);
849
850 // 1. Check if the store and the load have mathematically equivalent
851 // affine access functions; this implies that they statically refer to the
852 // same single memref element. As an example this filters out cases like:
853 // store %A[%i0 + 1]
854 // load %A[%i0]
855 // store %A[%M]
856 // load %A[%N]
857 // Use the AffineValueMap difference based memref access equality checking.
858 if (srcAccess != destAccess)
859 continue;
860
861 // 2. The store has to dominate the load op to be candidate.
862 if (!domInfo.dominates(storeOp, loadOp))
863 continue;
864
865 // 3. The store must reach the load. Access function equivalence only
866 // guarantees this for accesses in the same block. The load could be in a
867 // nested block that is unreachable.
868 if (!mustReachAtInnermost(srcAccess, destAccess))
869 continue;
870
871 // 4. Ensure there is no intermediate operation which could replace the
872 // value in memory.
874 mayAlias))
875 continue;
876
877 // We now have a candidate for forwarding.
878 assert(lastWriteStoreOp == nullptr &&
879 "multiple simultaneous replacement stores");
880 lastWriteStoreOp = storeOp;
881 }
882
883 if (!lastWriteStoreOp)
884 return;
885
886 // Perform the actual store to load forwarding.
887 Value storeVal =
888 cast<AffineWriteOpInterface>(lastWriteStoreOp).getValueToStore();
889 // Check if 2 values have the same shape. This is needed for affine vector
890 // loads and stores.
891 if (storeVal.getType() != loadOp.getValue().getType())
892 return;
893 loadOp.getValue().replaceAllUsesWith(storeVal);
894 // Record the memref for a later sweep to optimize away.
895 memrefsToErase.insert(loadOp.getMemRef());
896 // Record this to erase later.
897 loadOpsToErase.push_back(loadOp);
898}
899
900template bool
902 affine::AffineReadOpInterface>(
903 mlir::Operation *, affine::AffineReadOpInterface,
905
906// This attempts to find stores which have no impact on the final result.
907// A writing op writeA will be eliminated if there exists an op writeB if
908// 1) writeA and writeB have mathematically equivalent affine access functions.
909// 2) writeB writes the same type as writeA (so it fully covers writeA's bytes).
910// 3) writeB postdominates writeA.
911// 4) There is no potential read between writeA and writeB.
912static void findUnusedStore(AffineWriteOpInterface writeA,
914 PostDominanceInfo &postDominanceInfo,
916
917 // Only consider writing operations.
918 for (auto writeB : llvm::make_isa_range<AffineWriteOpInterface>(
919 writeA.getMemRef().getUsers())) {
920 // The operations must be distinct.
921 if (writeB == writeA)
922 continue;
923
924 // Both operations must lie in the same region.
925 if (writeB->getParentRegion() != writeA->getParentRegion())
926 continue;
927
928 // Both operations must write to the same memory.
929 MemRefAccess srcAccess(writeB);
930 MemRefAccess destAccess(writeA);
931
932 if (srcAccess != destAccess)
933 continue;
934
935 // Check that the store types match. If types differ, writeB may not cover
936 // all bytes written by writeA (e.g. a narrower vector type), so
937 // conservatively assume writeA is not dead.
938 // One could be tempted whether writeA type is smaller than writeB, however
939 // it can become tricky with cases like vector<4xi6> vs vector<3xi8> due to
940 // padding that can be datalayout dependent.
941 if (writeA.getValueToStore().getType() !=
942 writeB.getValueToStore().getType())
943 continue;
944
945 // writeB must postdominate writeA.
946 if (!postDominanceInfo.postDominates(writeB, writeA))
947 continue;
948
949 // There cannot be an operation which reads from memory between
950 // the two writes.
952 mayAlias))
953 continue;
954
955 opsToErase.push_back(writeA);
956 break;
957 }
958}
959
960// The load to load forwarding / redundant load elimination is similar to the
961// store to load forwarding.
962// loadA will be be replaced with loadB if:
963// 1) loadA and loadB have mathematically equivalent affine access functions.
964// 2) loadB dominates loadA.
965// 3) There is no write between loadA and loadB.
966static void loadCSE(AffineReadOpInterface loadA,
967 SmallVectorImpl<Operation *> &loadOpsToErase,
968 DominanceInfo &domInfo,
971 for (auto *user : loadA.getMemRef().getUsers()) {
972 auto loadB = dyn_cast<AffineReadOpInterface>(user);
973 if (!loadB || loadB == loadA)
974 continue;
975
976 MemRefAccess srcAccess(loadB);
977 MemRefAccess destAccess(loadA);
978
979 // 1. The accesses should be to be to the same location.
980 if (srcAccess != destAccess) {
981 continue;
982 }
983
984 // 2. loadB should dominate loadA.
985 if (!domInfo.dominates(loadB, loadA))
986 continue;
987
988 // 3. There should not be a write between loadA and loadB.
990 loadB.getOperation(), loadA, mayAlias))
991 continue;
992
993 // Check if two values have the same shape. This is needed for affine vector
994 // loads.
995 if (loadB.getValue().getType() != loadA.getValue().getType())
996 continue;
997
998 loadCandidates.push_back(loadB);
999 }
1000
1001 // Of the legal load candidates, use the one that dominates all others
1002 // to minimize the subsequent need to loadCSE
1003 Value loadB;
1004 for (AffineReadOpInterface option : loadCandidates) {
1005 if (llvm::all_of(loadCandidates, [&](AffineReadOpInterface depStore) {
1006 return depStore == option ||
1007 domInfo.dominates(option.getOperation(),
1008 depStore.getOperation());
1009 })) {
1010 loadB = option.getValue();
1011 break;
1012 }
1013 }
1014
1015 if (loadB) {
1016 loadA.getValue().replaceAllUsesWith(loadB);
1017 // Record this to erase later.
1018 loadOpsToErase.push_back(loadA);
1019 }
1020}
1021
1022// The store to load forwarding and load CSE rely on three conditions:
1023//
1024// 1) store/load providing a replacement value and load being replaced need to
1025// have mathematically equivalent affine access functions (checked after full
1026// composition of load/store operands); this implies that they access the same
1027// single memref element for all iterations of the common surrounding loop,
1028//
1029// 2) the store/load op should dominate the load op,
1030//
1031// 3) no operation that may write to memory read by the load being replaced can
1032// occur after executing the instruction (load or store) providing the
1033// replacement value and before the load being replaced (thus potentially
1034// allowing overwriting the memory read by the load).
1035//
1036// The above conditions are simple to check, sufficient, and powerful for most
1037// cases in practice - they are sufficient, but not necessary --- since they
1038// don't reason about loops that are guaranteed to execute at least once or
1039// multiple sources to forward from.
1040//
1041// TODO: more forwarding can be done when support for
1042// loop/conditional live-out SSA values is available.
1043// TODO: do general dead store elimination for memref's. This pass
1044// currently only eliminates the stores only if no other loads/uses (other
1045// than dealloc) remain.
1046//
1047void mlir::affine::affineScalarReplace(func::FuncOp f, DominanceInfo &domInfo,
1048 PostDominanceInfo &postDomInfo,
1049 AliasAnalysis &aliasAnalysis) {
1050 // Load op's whose results were replaced by those forwarded from stores.
1051 SmallVector<Operation *, 8> opsToErase;
1052
1053 // A list of memref's that are potentially dead / could be eliminated.
1054 SmallPtrSet<Value, 4> memrefsToErase;
1055
1056 auto mayAlias = [&](Value val1, Value val2) -> bool {
1057 return !aliasAnalysis.alias(val1, val2).isNo();
1058 };
1059
1060 // Walk all load's and perform store to load forwarding.
1061 f.walk([&](AffineReadOpInterface loadOp) {
1062 forwardStoreToLoad(loadOp, opsToErase, memrefsToErase, domInfo, mayAlias);
1063 });
1064 for (auto *op : opsToErase)
1065 op->erase();
1066 opsToErase.clear();
1067
1068 // Walk all store's and perform unused store elimination
1069 f.walk([&](AffineWriteOpInterface storeOp) {
1070 findUnusedStore(storeOp, opsToErase, postDomInfo, mayAlias);
1071 });
1072 for (auto *op : opsToErase)
1073 op->erase();
1074 opsToErase.clear();
1075
1076 // Check if the store fwd'ed memrefs are now left with only stores and
1077 // deallocs and can thus be completely deleted. Note: the canonicalize pass
1078 // should be able to do this as well, but we'll do it here since we collected
1079 // these anyway.
1080 for (auto memref : memrefsToErase) {
1081 // If the memref hasn't been locally alloc'ed, skip.
1082 Operation *defOp = memref.getDefiningOp();
1083 if (!defOp || !hasSingleEffect<MemoryEffects::Allocate>(defOp, memref))
1084 // TODO: if the memref was returned by a 'call' operation, we
1085 // could still erase it if the call had no side-effects.
1086 continue;
1087 if (llvm::any_of(memref.getUsers(), [&](Operation *ownerOp) {
1088 return !isa<AffineWriteOpInterface>(ownerOp) &&
1089 !hasSingleEffect<MemoryEffects::Free>(ownerOp, memref);
1090 }))
1091 continue;
1092
1093 // Erase all stores, the dealloc, and the alloc on the memref.
1094 for (auto *user : llvm::make_early_inc_range(memref.getUsers()))
1095 user->erase();
1096 defOp->erase();
1097 }
1098
1099 // To eliminate as many loads as possible, run load CSE after eliminating
1100 // stores. Otherwise, some stores are wrongly seen as having an intervening
1101 // effect.
1102 f.walk([&](AffineReadOpInterface loadOp) {
1103 loadCSE(loadOp, opsToErase, domInfo, mayAlias);
1104 });
1105 for (auto *op : opsToErase)
1106 op->erase();
1107}
1108
1109// Checks if `op` is non dereferencing.
1110// TODO: This hardcoded check will be removed once the right interface is added.
1112 return isa<AffineMapAccessInterface, memref::LoadOp, memref::StoreOp>(op);
1113}
1114
1115// Perform the replacement in `op`.
1116LogicalResult mlir::affine::replaceAllMemRefUsesWith(
1117 Value oldMemRef, Value newMemRef, Operation *op,
1118 ArrayRef<Value> extraIndices, AffineMap indexRemap,
1119 ArrayRef<Value> extraOperands, ArrayRef<Value> symbolOperands,
1120 bool allowNonDereferencingOps) {
1121 unsigned newMemRefRank = cast<MemRefType>(newMemRef.getType()).getRank();
1122 (void)newMemRefRank; // unused in opt mode
1123 unsigned oldMemRefRank = cast<MemRefType>(oldMemRef.getType()).getRank();
1124 (void)oldMemRefRank; // unused in opt mode
1125 if (indexRemap) {
1126 assert(indexRemap.getNumSymbols() == symbolOperands.size() &&
1127 "symbolic operand count mismatch");
1128 assert(indexRemap.getNumInputs() ==
1129 extraOperands.size() + oldMemRefRank + symbolOperands.size());
1130 assert(indexRemap.getNumResults() + extraIndices.size() == newMemRefRank);
1131 } else {
1132 assert(oldMemRefRank + extraIndices.size() == newMemRefRank);
1133 }
1134
1135 // Assert same elemental type.
1136 assert(cast<MemRefType>(oldMemRef.getType()).getElementType() ==
1137 cast<MemRefType>(newMemRef.getType()).getElementType());
1138
1139 SmallVector<unsigned, 2> usePositions;
1140 for (const auto &opEntry : llvm::enumerate(op->getOperands())) {
1141 if (opEntry.value() == oldMemRef)
1142 usePositions.push_back(opEntry.index());
1143 }
1144
1145 // If memref doesn't appear, nothing to do.
1146 if (usePositions.empty())
1147 return success();
1148
1149 unsigned memRefOperandPos = usePositions.front();
1150
1151 OpBuilder builder(op);
1152 // The following checks if op is dereferencing memref and performs the access
1153 // index rewrites.
1154 if (!isDereferencingOp(op)) {
1155 if (!allowNonDereferencingOps) {
1156 // Failure: memref used in a non-dereferencing context (potentially
1157 // escapes); no replacement in these cases unless allowNonDereferencingOps
1158 // is set.
1159 return failure();
1160 }
1161 for (unsigned pos : usePositions)
1162 op->setOperand(pos, newMemRef);
1163 return success();
1164 }
1165
1166 if (usePositions.size() > 1) {
1167 // TODO: extend it for this case when needed (rare).
1168 LLVM_DEBUG(llvm::dbgs()
1169 << "multiple dereferencing uses in a single op not supported");
1170 return failure();
1171 }
1172
1173 // Perform index rewrites for the dereferencing op and then replace the op.
1174 SmallVector<Value, 4> oldMapOperands;
1175 AffineMap oldMap;
1176 unsigned oldMemRefNumIndices = oldMemRefRank;
1177 auto startIdx = op->operand_begin() + memRefOperandPos + 1;
1178 auto affMapAccInterface = dyn_cast<AffineMapAccessInterface>(op);
1179 if (affMapAccInterface) {
1180 // If `op` implements AffineMapAccessInterface, we can get the indices by
1181 // quering the number of map operands from the operand list from a certain
1182 // offset (`memRefOperandPos` in this case).
1183 NamedAttribute oldMapAttrPair =
1184 affMapAccInterface.getAffineMapAttrForMemRef(oldMemRef);
1185 oldMap = cast<AffineMapAttr>(oldMapAttrPair.getValue()).getValue();
1186 oldMemRefNumIndices = oldMap.getNumInputs();
1187 }
1188 oldMapOperands.assign(startIdx, startIdx + oldMemRefNumIndices);
1189
1190 // Apply 'oldMemRefOperands = oldMap(oldMapOperands)'.
1191 SmallVector<Value, 4> oldMemRefOperands;
1192 SmallVector<Value, 4> affineApplyOps;
1193 oldMemRefOperands.reserve(oldMemRefRank);
1194 if (affMapAccInterface &&
1195 oldMap != builder.getMultiDimIdentityMap(oldMap.getNumDims())) {
1196 for (auto resultExpr : oldMap.getResults()) {
1197 auto singleResMap = AffineMap::get(oldMap.getNumDims(),
1198 oldMap.getNumSymbols(), resultExpr);
1199 auto afOp = AffineApplyOp::create(builder, op->getLoc(), singleResMap,
1200 oldMapOperands);
1201 oldMemRefOperands.push_back(afOp);
1202 affineApplyOps.push_back(afOp);
1203 }
1204 } else {
1205 oldMemRefOperands.assign(oldMapOperands.begin(), oldMapOperands.end());
1206 }
1207
1208 // Construct new indices as a remap of the old ones if a remapping has been
1209 // provided. The indices of a memref come right after it, i.e.,
1210 // at position memRefOperandPos + 1.
1211 SmallVector<Value, 4> remapOperands;
1212 remapOperands.reserve(extraOperands.size() + oldMemRefRank +
1213 symbolOperands.size());
1214 remapOperands.append(extraOperands.begin(), extraOperands.end());
1215 remapOperands.append(oldMemRefOperands.begin(), oldMemRefOperands.end());
1216 remapOperands.append(symbolOperands.begin(), symbolOperands.end());
1217
1218 SmallVector<Value, 4> remapOutputs;
1219 remapOutputs.reserve(oldMemRefRank);
1220 if (indexRemap &&
1221 indexRemap != builder.getMultiDimIdentityMap(indexRemap.getNumDims())) {
1222 // Remapped indices.
1223 for (auto resultExpr : indexRemap.getResults()) {
1224 auto singleResMap = AffineMap::get(
1225 indexRemap.getNumDims(), indexRemap.getNumSymbols(), resultExpr);
1226 auto afOp = AffineApplyOp::create(builder, op->getLoc(), singleResMap,
1227 remapOperands);
1228 remapOutputs.push_back(afOp);
1229 affineApplyOps.push_back(afOp);
1230 }
1231 } else {
1232 // No remapping specified.
1233 remapOutputs.assign(remapOperands.begin(), remapOperands.end());
1234 }
1235 SmallVector<Value, 4> newMapOperands;
1236 newMapOperands.reserve(newMemRefRank);
1237
1238 // Prepend 'extraIndices' in 'newMapOperands'.
1239 for (Value extraIndex : extraIndices) {
1240 assert((isValidDim(extraIndex) || isValidSymbol(extraIndex)) &&
1241 "invalid memory op index");
1242 newMapOperands.push_back(extraIndex);
1243 }
1244
1245 // Append 'remapOutputs' to 'newMapOperands'.
1246 newMapOperands.append(remapOutputs.begin(), remapOutputs.end());
1247
1248 // Create new fully composed AffineMap for new op to be created.
1249 assert(newMapOperands.size() == newMemRefRank);
1250 auto newMap = builder.getMultiDimIdentityMap(newMemRefRank);
1251 fullyComposeAffineMapAndOperands(&newMap, &newMapOperands);
1252 newMap = simplifyAffineMap(newMap);
1253 canonicalizeMapAndOperands(&newMap, &newMapOperands);
1254 // Remove any affine.apply's that became dead as a result of composition.
1255 for (Value value : affineApplyOps)
1256 if (value.use_empty())
1257 value.getDefiningOp()->erase();
1258
1259 OperationState state(op->getLoc(), op->getName());
1260 // Construct the new operation using this memref.
1261 state.operands.reserve(op->getNumOperands() + extraIndices.size());
1262 // Insert the non-memref operands.
1263 state.operands.append(op->operand_begin(),
1264 op->operand_begin() + memRefOperandPos);
1265 // Insert the new memref value.
1266 state.operands.push_back(newMemRef);
1267
1268 // Insert the new memref map operands.
1269 if (affMapAccInterface) {
1270 state.operands.append(newMapOperands.begin(), newMapOperands.end());
1271 } else {
1272 // In the case of dereferencing ops not implementing
1273 // AffineMapAccessInterface, we need to apply the values of `newMapOperands`
1274 // to the `newMap` to get the correct indices.
1275 for (unsigned i = 0; i < newMemRefRank; i++) {
1276 state.operands.push_back(AffineApplyOp::create(
1277 builder, op->getLoc(),
1278 AffineMap::get(newMap.getNumDims(), newMap.getNumSymbols(),
1279 newMap.getResult(i)),
1280 newMapOperands));
1281 }
1282 }
1283
1284 // Insert the remaining operands unmodified.
1285 unsigned oldMapNumInputs = oldMapOperands.size();
1286 state.operands.append(op->operand_begin() + memRefOperandPos + 1 +
1287 oldMapNumInputs,
1288 op->operand_end());
1289 // Result types don't change. Both memref's are of the same elemental type.
1290 state.types.reserve(op->getNumResults());
1291 for (auto result : op->getResults())
1292 state.types.push_back(result.getType());
1293
1294 // Add attribute for 'newMap', other Attributes do not change.
1295 auto newMapAttr = AffineMapAttr::get(newMap);
1296 state.addAttributes(op->getDiscardableAttrDictionary().getValue());
1297 state.propertiesAttr = op->getPropertiesAsAttribute();
1298
1299 // Create the new operation.
1300 auto *repOp = builder.create(state);
1301 if (affMapAccInterface) {
1302 StringAttr mapAttrName =
1303 affMapAccInterface.getAffineMapAttrForMemRef(oldMemRef).getName();
1304 repOp->setInherentAttr(mapAttrName, newMapAttr);
1305 }
1306 op->replaceAllUsesWith(repOp);
1307 op->erase();
1308
1309 return success();
1310}
1311
1312LogicalResult mlir::affine::replaceAllMemRefUsesWith(
1313 Value oldMemRef, Value newMemRef, ArrayRef<Value> extraIndices,
1314 AffineMap indexRemap, ArrayRef<Value> extraOperands,
1315 ArrayRef<Value> symbolOperands,
1316 llvm::function_ref<bool(Operation *)> userFilterFn,
1317 bool allowNonDereferencingOps, bool replaceInDeallocOp) {
1318 unsigned newMemRefRank = cast<MemRefType>(newMemRef.getType()).getRank();
1319 (void)newMemRefRank; // unused in opt mode
1320 unsigned oldMemRefRank = cast<MemRefType>(oldMemRef.getType()).getRank();
1321 (void)oldMemRefRank;
1322 if (indexRemap) {
1323 assert(indexRemap.getNumSymbols() == symbolOperands.size() &&
1324 "symbol operand count mismatch");
1325 assert(indexRemap.getNumInputs() ==
1326 extraOperands.size() + oldMemRefRank + symbolOperands.size());
1327 assert(indexRemap.getNumResults() + extraIndices.size() == newMemRefRank);
1328 } else {
1329 assert(oldMemRefRank + extraIndices.size() == newMemRefRank);
1330 }
1331
1332 // Assert same elemental type.
1333 assert(cast<MemRefType>(oldMemRef.getType()).getElementType() ==
1334 cast<MemRefType>(newMemRef.getType()).getElementType());
1335
1336 // Walk all uses of old memref; collect ops to perform replacement. We use a
1337 // DenseSet since an operation could potentially have multiple uses of a
1338 // memref (although rare), and the replacement later is going to erase ops.
1339 DenseSet<Operation *> opsToReplace;
1340 for (auto *user : oldMemRef.getUsers()) {
1341 // Check if this user doesn't pass the filter.
1342 if (userFilterFn && !userFilterFn(user))
1343 continue;
1344
1345 // Skip dealloc's - no replacement is necessary, and a memref replacement
1346 // at other uses doesn't hurt these dealloc's.
1347 if (hasSingleEffect<MemoryEffects::Free>(user, oldMemRef) &&
1348 !replaceInDeallocOp)
1349 continue;
1350
1351 // Check if the memref was used in a non-dereferencing context. It is fine
1352 // for the memref to be used in a non-dereferencing way outside of the
1353 // region where this replacement is happening.
1354 if (!isa<AffineMapAccessInterface>(*user)) {
1355 if (!allowNonDereferencingOps) {
1356 LLVM_DEBUG(
1357 llvm::dbgs()
1358 << "Memref replacement failed: non-deferencing memref user: \n"
1359 << *user << '\n');
1360 return failure();
1361 }
1362 // Non-dereferencing ops with the MemRefsNormalizable trait are
1363 // supported for replacement.
1364 if (!user->hasTrait<OpTrait::MemRefsNormalizable>()) {
1365 LLVM_DEBUG(llvm::dbgs() << "Memref replacement failed: use without a "
1366 "memrefs normalizable trait: \n"
1367 << *user << '\n');
1368 return failure();
1369 }
1370 }
1371
1372 // We'll first collect and then replace --- since replacement erases the
1373 // user that has the use, and that user could be postDomFilter or domFilter
1374 // itself!
1375 opsToReplace.insert(user);
1376 }
1377
1378 for (auto *user : opsToReplace) {
1379 if (failed(replaceAllMemRefUsesWith(
1380 oldMemRef, newMemRef, user, extraIndices, indexRemap, extraOperands,
1381 symbolOperands, allowNonDereferencingOps)))
1382 return failure();
1383 }
1384
1385 return success();
1386}
1387
1388/// Given an operation, inserts one or more single result affine
1389/// apply operations, results of which are exclusively used by this operation
1390/// operation. The operands of these newly created affine apply ops are
1391/// guaranteed to be loop iterators or terminal symbols of a function.
1392///
1393/// Before
1394///
1395/// affine.for %i = 0 to #map(%N)
1396/// %idx = affine.apply (d0) -> (d0 mod 2) (%i)
1397/// "send"(%idx, %A, ...)
1398/// "compute"(%idx)
1399///
1400/// After
1401///
1402/// affine.for %i = 0 to #map(%N)
1403/// %idx = affine.apply (d0) -> (d0 mod 2) (%i)
1404/// "send"(%idx, %A, ...)
1405/// %idx_ = affine.apply (d0) -> (d0 mod 2) (%i)
1406/// "compute"(%idx_)
1407///
1408/// This allows applying different transformations on send and compute (for eg.
1409/// different shifts/delays).
1410///
1411/// Returns nullptr either if none of opInst's operands were the result of an
1412/// affine.apply and thus there was no affine computation slice to create, or if
1413/// all the affine.apply op's supplying operands to this opInst did not have any
1414/// uses besides this opInst; otherwise returns the list of affine.apply
1415/// operations created in output argument `sliceOps`.
1416void mlir::affine::createAffineComputationSlice(
1417 Operation *opInst, SmallVectorImpl<AffineApplyOp> *sliceOps) {
1418 // Collect all operands that are results of affine apply ops.
1419 SmallVector<Value, 4> subOperands;
1420 subOperands.reserve(opInst->getNumOperands());
1421 for (auto operand : opInst->getOperands())
1422 if (isa_and_nonnull<AffineApplyOp>(operand.getDefiningOp()))
1423 subOperands.push_back(operand);
1424
1425 // Gather sequence of AffineApplyOps reachable from 'subOperands'.
1426 SmallVector<Operation *, 4> affineApplyOps;
1427 getReachableAffineApplyOps(subOperands, affineApplyOps);
1428 // Skip transforming if there are no affine maps to compose.
1429 if (affineApplyOps.empty())
1430 return;
1431
1432 // Check if all uses of the affine apply op's lie only in this op op, in
1433 // which case there would be nothing to do.
1434 bool localized = true;
1435 for (auto *op : affineApplyOps) {
1436 for (auto result : op->getResults()) {
1437 for (auto *user : result.getUsers()) {
1438 if (user != opInst) {
1439 localized = false;
1440 break;
1441 }
1442 }
1443 }
1444 }
1445 if (localized)
1446 return;
1447
1448 OpBuilder builder(opInst);
1449 SmallVector<Value, 4> composedOpOperands(subOperands);
1450 auto composedMap = builder.getMultiDimIdentityMap(composedOpOperands.size());
1451 fullyComposeAffineMapAndOperands(&composedMap, &composedOpOperands);
1452
1453 // Create an affine.apply for each of the map results.
1454 sliceOps->reserve(composedMap.getNumResults());
1455 for (auto resultExpr : composedMap.getResults()) {
1456 auto singleResMap = AffineMap::get(composedMap.getNumDims(),
1457 composedMap.getNumSymbols(), resultExpr);
1458 sliceOps->push_back(AffineApplyOp::create(
1459 builder, opInst->getLoc(), singleResMap, composedOpOperands));
1460 }
1461
1462 // Construct the new operands that include the results from the composed
1463 // affine apply op above instead of existing ones (subOperands). So, they
1464 // differ from opInst's operands only for those operands in 'subOperands', for
1465 // which they will be replaced by the corresponding one from 'sliceOps'.
1466 SmallVector<Value, 4> newOperands(opInst->getOperands());
1467 for (Value &operand : newOperands) {
1468 // Replace the subOperands from among the new operands.
1469 unsigned j, f;
1470 for (j = 0, f = subOperands.size(); j < f; j++) {
1471 if (operand == subOperands[j])
1472 break;
1473 }
1474 if (j < subOperands.size())
1475 operand = (*sliceOps)[j];
1476 }
1477 for (unsigned idx = 0, e = newOperands.size(); idx < e; idx++)
1478 opInst->setOperand(idx, newOperands[idx]);
1479}
1480
1481/// Enum to set patterns of affine expr in tiled-layout map.
1482/// TileFloorDiv: <dim expr> div <tile size>
1483/// TileMod: <dim expr> mod <tile size>
1484/// TileNone: None of the above
1485/// Example:
1486/// #tiled_2d_128x256 = affine_map<(d0, d1)
1487/// -> (d0 div 128, d1 div 256, d0 mod 128, d1 mod 256)>
1488/// "d0 div 128" and "d1 div 256" ==> TileFloorDiv
1489/// "d0 mod 128" and "d1 mod 256" ==> TileMod
1491
1492/// Check if `map` is a tiled layout. In the tiled layout, specific k dimensions
1493/// being floordiv'ed by respective tile sizes appeare in a mod with the same
1494/// tile sizes, and no other expression involves those k dimensions. This
1495/// function stores a vector of tuples (`tileSizePos`) including AffineExpr for
1496/// tile size, positions of corresponding `floordiv` and `mod`. If it is not a
1497/// tiled layout, an empty vector is returned.
1498static LogicalResult getTileSizePos(
1499 AffineMap map,
1500 SmallVectorImpl<std::tuple<AffineExpr, unsigned, unsigned>> &tileSizePos) {
1501 // Create `floordivExprs` which is a vector of tuples including LHS and RHS of
1502 // `floordiv` and its position in `map` output.
1503 // Example: #tiled_2d_128x256 = affine_map<(d0, d1)
1504 // -> (d0 div 128, d1 div 256, d0 mod 128, d1 mod 256)>
1505 // In this example, `floordivExprs` includes {d0, 128, 0} and {d1, 256, 1}.
1507 unsigned pos = 0;
1508 for (AffineExpr expr : map.getResults()) {
1509 if (expr.getKind() == AffineExprKind::FloorDiv) {
1510 AffineBinaryOpExpr binaryExpr = cast<AffineBinaryOpExpr>(expr);
1511 if (isa<AffineConstantExpr>(binaryExpr.getRHS()))
1512 floordivExprs.emplace_back(
1513 std::make_tuple(binaryExpr.getLHS(), binaryExpr.getRHS(), pos));
1514 }
1515 pos++;
1516 }
1517 // Not tiled layout if `floordivExprs` is empty.
1518 if (floordivExprs.empty()) {
1520 return success();
1521 }
1522
1523 // Check if LHS of `floordiv` is used in LHS of `mod`. If not used, `map` is
1524 // not tiled layout.
1525 for (std::tuple<AffineExpr, AffineExpr, unsigned> fexpr : floordivExprs) {
1526 AffineExpr floordivExprLHS = std::get<0>(fexpr);
1527 AffineExpr floordivExprRHS = std::get<1>(fexpr);
1528 unsigned floordivPos = std::get<2>(fexpr);
1529
1530 // Walk affinexpr of `map` output except `fexpr`, and check if LHS and RHS
1531 // of `fexpr` are used in LHS and RHS of `mod`. If LHS of `fexpr` is used
1532 // other expr, the map is not tiled layout. Example of non tiled layout:
1533 // affine_map<(d0, d1, d2) -> (d0, d1, d2 floordiv 256, d2 floordiv 256)>
1534 // affine_map<(d0, d1, d2) -> (d0, d1, d2 floordiv 256, d2 mod 128)>
1535 // affine_map<(d0, d1, d2) -> (d0, d1, d2 floordiv 256, d2 mod 256, d2 mod
1536 // 256)>
1537 bool found = false;
1538 pos = 0;
1539 for (AffineExpr expr : map.getResults()) {
1540 bool notTiled = false;
1541 if (pos != floordivPos) {
1542 expr.walk([&](AffineExpr e) {
1543 if (e == floordivExprLHS) {
1544 if (expr.getKind() == AffineExprKind::Mod) {
1545 AffineBinaryOpExpr binaryExpr = cast<AffineBinaryOpExpr>(expr);
1546 // If LHS and RHS of `mod` are the same with those of floordiv.
1547 if (floordivExprLHS == binaryExpr.getLHS() &&
1548 floordivExprRHS == binaryExpr.getRHS()) {
1549 // Save tile size (RHS of `mod`), and position of `floordiv` and
1550 // `mod` if same expr with `mod` is not found yet.
1551 if (!found) {
1552 tileSizePos.emplace_back(
1553 std::make_tuple(binaryExpr.getRHS(), floordivPos, pos));
1554 found = true;
1555 } else {
1556 // Non tiled layout: Have multilpe `mod` with the same LHS.
1557 // eg. affine_map<(d0, d1, d2) -> (d0, d1, d2 floordiv 256, d2
1558 // mod 256, d2 mod 256)>
1559 notTiled = true;
1560 }
1561 } else {
1562 // Non tiled layout: RHS of `mod` is different from `floordiv`.
1563 // eg. affine_map<(d0, d1, d2) -> (d0, d1, d2 floordiv 256, d2
1564 // mod 128)>
1565 notTiled = true;
1566 }
1567 } else {
1568 // Non tiled layout: LHS is the same, but not `mod`.
1569 // eg. affine_map<(d0, d1, d2) -> (d0, d1, d2 floordiv 256, d2
1570 // floordiv 256)>
1571 notTiled = true;
1572 }
1573 }
1574 });
1575 }
1576 if (notTiled) {
1578 return success();
1579 }
1580 pos++;
1581 }
1582 }
1583 return success();
1584}
1585
1586/// Check if `dim` dimension of memrefType with `layoutMap` becomes dynamic
1587/// after normalization. Dimensions that include dynamic dimensions in the map
1588/// output will become dynamic dimensions. Return true if `dim` is dynamic
1589/// dimension.
1590///
1591/// Example:
1592/// #map0 = affine_map<(d0, d1) -> (d0, d1 floordiv 32, d1 mod 32)>
1593///
1594/// If d1 is dynamic dimension, 2nd and 3rd dimension of map output are dynamic.
1595/// memref<4x?xf32, #map0> ==> memref<4x?x?xf32>
1596static bool
1598 SmallVectorImpl<unsigned> &inMemrefTypeDynDims) {
1599 AffineExpr expr = layoutMap.getResults()[dim];
1600 // Check if affine expr of the dimension includes dynamic dimension of input
1601 // memrefType.
1602 MLIRContext *context = layoutMap.getContext();
1603 return expr
1604 .walk([&](AffineExpr e) {
1605 if (isa<AffineDimExpr>(e) &&
1606 llvm::any_of(inMemrefTypeDynDims, [&](unsigned dim) {
1607 return e == getAffineDimExpr(dim, context);
1608 }))
1609 return WalkResult::interrupt();
1610 return WalkResult::advance();
1611 })
1612 .wasInterrupted();
1613}
1614
1615/// Create affine expr to calculate dimension size for a tiled-layout map.
1617 TileExprPattern pat) {
1618 // Create map output for the patterns.
1619 // "floordiv <tile size>" ==> "ceildiv <tile size>"
1620 // "mod <tile size>" ==> "<tile size>"
1621 AffineExpr newMapOutput;
1622 AffineBinaryOpExpr binaryExpr = nullptr;
1623 switch (pat) {
1625 binaryExpr = cast<AffineBinaryOpExpr>(oldMapOutput);
1626 newMapOutput = binaryExpr.getRHS();
1627 break;
1629 binaryExpr = cast<AffineBinaryOpExpr>(oldMapOutput);
1630 newMapOutput = getAffineBinaryOpExpr(
1631 AffineExprKind::CeilDiv, binaryExpr.getLHS(), binaryExpr.getRHS());
1632 break;
1633 default:
1634 newMapOutput = oldMapOutput;
1635 }
1636 return newMapOutput;
1637}
1638
1639/// Create new maps to calculate each dimension size of `newMemRefType`, and
1640/// create `newDynamicSizes` from them by using AffineApplyOp.
1641///
1642/// Steps for normalizing dynamic memrefs for a tiled layout map
1643/// Example:
1644/// #map0 = affine_map<(d0, d1) -> (d0, d1 floordiv 32, d1 mod 32)>
1645/// %0 = dim %arg0, %c1 :memref<4x?xf32>
1646/// %1 = alloc(%0) : memref<4x?xf32, #map0>
1647///
1648/// (Before this function)
1649/// 1. Check if `map`(#map0) is a tiled layout using `getTileSizePos()`. Only
1650/// single layout map is supported.
1651///
1652/// 2. Create normalized memrefType using `isNormalizedMemRefDynamicDim()`. It
1653/// is memref<4x?x?xf32> in the above example.
1654///
1655/// (In this function)
1656/// 3. Create new maps to calculate each dimension of the normalized memrefType
1657/// using `createDimSizeExprForTiledLayout()`. In the tiled layout, the
1658/// dimension size can be calculated by replacing "floordiv <tile size>" with
1659/// "ceildiv <tile size>" and "mod <tile size>" with "<tile size>".
1660/// - New map in the above example
1661/// #map0 = affine_map<(d0, d1) -> (d0)>
1662/// #map1 = affine_map<(d0, d1) -> (d1 ceildiv 32)>
1663/// #map2 = affine_map<(d0, d1) -> (32)>
1664///
1665/// 4. Create AffineApplyOp to apply the new maps. The output of AffineApplyOp
1666/// is used in dynamicSizes of new AllocOp.
1667/// %0 = dim %arg0, %c1 : memref<4x?xf32>
1668/// %c4 = arith.constant 4 : index
1669/// %1 = affine.apply #map1(%c4, %0)
1670/// %2 = affine.apply #map2(%c4, %0)
1671template <typename AllocLikeOp>
1672static void createNewDynamicSizes(MemRefType oldMemRefType,
1673 MemRefType newMemRefType, AffineMap map,
1674 AllocLikeOp allocOp, OpBuilder b,
1675 SmallVectorImpl<Value> &newDynamicSizes) {
1676 // Create new input for AffineApplyOp.
1677 SmallVector<Value, 4> inAffineApply;
1678 ArrayRef<int64_t> oldMemRefShape = oldMemRefType.getShape();
1679 unsigned dynIdx = 0;
1680 for (unsigned d = 0; d < oldMemRefType.getRank(); ++d) {
1681 if (oldMemRefShape[d] < 0) {
1682 // Use dynamicSizes of allocOp for dynamic dimension.
1683 inAffineApply.emplace_back(allocOp.getDynamicSizes()[dynIdx]);
1684 dynIdx++;
1685 } else {
1686 // Create ConstantOp for static dimension.
1687 auto constantAttr = b.getIntegerAttr(b.getIndexType(), oldMemRefShape[d]);
1688 inAffineApply.emplace_back(
1689 arith::ConstantOp::create(b, allocOp.getLoc(), constantAttr));
1690 }
1691 }
1692
1693 // Create new map to calculate each dimension size of new memref for each
1694 // original map output. Only for dynamic dimesion of `newMemRefType`.
1695 unsigned newDimIdx = 0;
1696 ArrayRef<int64_t> newMemRefShape = newMemRefType.getShape();
1698 (void)getTileSizePos(map, tileSizePos);
1699 for (AffineExpr expr : map.getResults()) {
1700 if (newMemRefShape[newDimIdx] < 0) {
1701 // Create new maps to calculate each dimension size of new memref.
1703 for (auto pos : tileSizePos) {
1704 if (newDimIdx == std::get<1>(pos))
1706 else if (newDimIdx == std::get<2>(pos))
1708 }
1709 AffineExpr newMapOutput = createDimSizeExprForTiledLayout(expr, pat);
1710 AffineMap newMap =
1711 AffineMap::get(map.getNumInputs(), map.getNumSymbols(), newMapOutput);
1712 Value affineApp =
1713 AffineApplyOp::create(b, allocOp.getLoc(), newMap, inAffineApply);
1714 newDynamicSizes.emplace_back(affineApp);
1715 }
1716 newDimIdx++;
1717 }
1718}
1719
1720template <typename AllocLikeOp>
1721LogicalResult mlir::affine::normalizeMemRef(AllocLikeOp allocOp) {
1722 MemRefType memrefType = allocOp.getType();
1723 OpBuilder b(allocOp);
1724
1725 // Fetch a new memref type after normalizing the old memref to have an
1726 // identity map layout.
1727 MemRefType newMemRefType = normalizeMemRefType(memrefType);
1728 if (newMemRefType == memrefType)
1729 // Either memrefType already had an identity map or the map couldn't be
1730 // transformed to an identity map.
1731 return failure();
1732
1733 Value oldMemRef = allocOp.getResult();
1734
1735 SmallVector<Value, 4> symbolOperands(allocOp.getSymbolOperands());
1736 AffineMap layoutMap = memrefType.getLayout().getAffineMap();
1737 AllocLikeOp newAlloc;
1738 // Check if `layoutMap` is a tiled layout. Only single layout map is
1739 // supported for normalizing dynamic memrefs.
1741 (void)getTileSizePos(layoutMap, tileSizePos);
1742 if (newMemRefType.getNumDynamicDims() > 0 && !tileSizePos.empty()) {
1743 auto oldMemRefType = cast<MemRefType>(oldMemRef.getType());
1744 SmallVector<Value, 4> newDynamicSizes;
1745 createNewDynamicSizes(oldMemRefType, newMemRefType, layoutMap, allocOp, b,
1746 newDynamicSizes);
1747 // Add the new dynamic sizes in new AllocOp.
1748 newAlloc = AllocLikeOp::create(b, allocOp.getLoc(), newMemRefType,
1749 newDynamicSizes, allocOp.getAlignmentAttr());
1750 } else {
1751 newAlloc = AllocLikeOp::create(b, allocOp.getLoc(), newMemRefType,
1752 allocOp.getAlignmentAttr());
1753 }
1754 // Replace all uses of the old memref.
1755 if (failed(replaceAllMemRefUsesWith(oldMemRef, /*newMemRef=*/newAlloc,
1756 /*extraIndices=*/{},
1757 /*indexRemap=*/layoutMap,
1758 /*extraOperands=*/{},
1759 /*symbolOperands=*/symbolOperands,
1760 /*userFilterFn=*/nullptr,
1761 /*allowNonDereferencingOps=*/true))) {
1762 // If it failed (due to escapes for example), bail out.
1763 newAlloc.erase();
1764 return failure();
1765 }
1766 // Replace any uses of the original alloc op and erase it. All remaining uses
1767 // have to be dealloc's; RAMUW above would've failed otherwise.
1768 assert(llvm::all_of(oldMemRef.getUsers(), [&](Operation *op) {
1769 return hasSingleEffect<MemoryEffects::Free>(op, oldMemRef);
1770 }));
1771 oldMemRef.replaceAllUsesWith(newAlloc);
1772 allocOp.erase();
1773 return success();
1774}
1775
1776LogicalResult
1777mlir::affine::normalizeMemRef(memref::ReinterpretCastOp reinterpretCastOp) {
1778 MemRefType memrefType = reinterpretCastOp.getType();
1779 AffineMap oldLayoutMap = memrefType.getLayout().getAffineMap();
1780 Value oldMemRef = reinterpretCastOp.getResult();
1781
1782 // If `oldLayoutMap` is identity, `memrefType` is already normalized.
1783 if (oldLayoutMap.isIdentity())
1784 return success();
1785
1786 // Fetch a new memref type after normalizing the old memref to have an
1787 // identity map layout.
1788 MemRefType newMemRefType = normalizeMemRefType(memrefType);
1789 if (newMemRefType == memrefType)
1790 // `oldLayoutMap` couldn't be transformed to an identity map.
1791 return failure();
1792
1793 uint64_t newRank = newMemRefType.getRank();
1794 SmallVector<Value> mapOperands(oldLayoutMap.getNumDims() +
1795 oldLayoutMap.getNumSymbols());
1796 SmallVector<Value> oldStrides = reinterpretCastOp.getStrides();
1797 Location loc = reinterpretCastOp.getLoc();
1798 // As `newMemRefType` is normalized, it is unit strided.
1799 SmallVector<int64_t> newStaticStrides(newRank, 1);
1800 SmallVector<int64_t> newStaticOffsets(newRank, 0);
1801 ArrayRef<int64_t> oldShape = memrefType.getShape();
1802 ValueRange oldSizes = reinterpretCastOp.getSizes();
1803 unsigned idx = 0;
1804 OpBuilder b(reinterpretCastOp);
1805 // Collect the map operands which will be used to compute the new normalized
1806 // memref shape.
1807 for (unsigned i = 0, e = memrefType.getRank(); i < e; i++) {
1808 if (memrefType.isDynamicDim(i))
1809 mapOperands[i] =
1810 arith::SubIOp::create(b, loc, oldSizes[0].getType(), oldSizes[idx++],
1812 else
1813 mapOperands[i] = arith::ConstantIndexOp::create(b, loc, oldShape[i] - 1);
1814 }
1815 for (unsigned i = 0, e = oldStrides.size(); i < e; i++)
1816 mapOperands[memrefType.getRank() + i] = oldStrides[i];
1817 SmallVector<Value> newSizes;
1818 ArrayRef<int64_t> newShape = newMemRefType.getShape();
1819 // Compute size along all the dimensions of the new normalized memref.
1820 for (unsigned i = 0; i < newRank; i++) {
1821 if (!newMemRefType.isDynamicDim(i))
1822 continue;
1823 newSizes.push_back(AffineApplyOp::create(
1824 b, loc,
1825 AffineMap::get(oldLayoutMap.getNumDims(), oldLayoutMap.getNumSymbols(),
1826 oldLayoutMap.getResult(i)),
1827 mapOperands));
1828 }
1829 for (auto &newSize : newSizes) {
1830 newSize = arith::AddIOp::create(b, loc, newSize.getType(), newSize,
1832 }
1833 // Create the new reinterpret_cast op.
1834 auto newReinterpretCast = memref::ReinterpretCastOp::create(
1835 b, loc, newMemRefType, reinterpretCastOp.getSource(),
1836 /*offsets=*/ValueRange(), newSizes,
1837 /*strides=*/ValueRange(),
1838 /*static_offsets=*/newStaticOffsets,
1839 /*static_sizes=*/newShape,
1840 /*static_strides=*/newStaticStrides);
1841
1842 // Replace all uses of the old memref.
1843 if (failed(replaceAllMemRefUsesWith(oldMemRef,
1844 /*newMemRef=*/newReinterpretCast,
1845 /*extraIndices=*/{},
1846 /*indexRemap=*/oldLayoutMap,
1847 /*extraOperands=*/{},
1848 /*symbolOperands=*/oldStrides,
1849 /*userFilterFn=*/nullptr,
1850 /*allowNonDereferencingOps=*/true))) {
1851 // If it failed (due to escapes for example), bail out.
1852 newReinterpretCast.erase();
1853 return failure();
1854 }
1855
1856 oldMemRef.replaceAllUsesWith(newReinterpretCast);
1857 reinterpretCastOp.erase();
1858 return success();
1859}
1860
1861template LogicalResult
1862mlir::affine::normalizeMemRef<memref::AllocaOp>(memref::AllocaOp op);
1863template LogicalResult
1864mlir::affine::normalizeMemRef<memref::AllocOp>(memref::AllocOp op);
1865
1866MemRefType mlir::affine::normalizeMemRefType(MemRefType memrefType) {
1867 unsigned rank = memrefType.getRank();
1868 if (rank == 0)
1869 return memrefType;
1870
1871 if (memrefType.getLayout().isIdentity()) {
1872 // Either no maps is associated with this memref or this memref has
1873 // a trivial (identity) map.
1874 return memrefType;
1875 }
1876 AffineMap layoutMap = memrefType.getLayout().getAffineMap();
1877 unsigned numSymbolicOperands = layoutMap.getNumSymbols();
1878
1879 // We don't do any checks for one-to-one'ness; we assume that it is
1880 // one-to-one.
1881
1882 // Normalize only static memrefs and dynamic memrefs with a tiled-layout map
1883 // for now.
1884 // TODO: Normalize the other types of dynamic memrefs.
1886 (void)getTileSizePos(layoutMap, tileSizePos);
1887 if (memrefType.getNumDynamicDims() > 0 && tileSizePos.empty())
1888 return memrefType;
1889
1890 // We have a single map that is not an identity map. Create a new memref
1891 // with the right shape and an identity layout map.
1892 ArrayRef<int64_t> shape = memrefType.getShape();
1893 // FlatAffineValueConstraint may later on use symbolicOperands.
1894 FlatAffineValueConstraints fac(rank, numSymbolicOperands);
1895 SmallVector<unsigned, 4> memrefTypeDynDims;
1896 for (unsigned d = 0; d < rank; ++d) {
1897 // Use constraint system only in static dimensions.
1898 if (shape[d] > 0) {
1899 fac.addBound(BoundType::LB, d, 0);
1900 fac.addBound(BoundType::UB, d, shape[d] - 1);
1901 } else {
1902 memrefTypeDynDims.emplace_back(d);
1903 }
1904 }
1905 // We compose this map with the original index (logical) space to derive
1906 // the upper bounds for the new index space.
1907 unsigned newRank = layoutMap.getNumResults();
1908 if (failed(fac.composeMatchingMap(layoutMap)))
1909 return memrefType;
1910 // TODO: Handle semi-affine maps.
1911 // Project out the old data dimensions.
1912 fac.projectOut(newRank, fac.getNumVars() - newRank - fac.getNumLocalVars());
1913 SmallVector<int64_t, 4> newShape(newRank);
1914 MLIRContext *context = memrefType.getContext();
1915 for (unsigned d = 0; d < newRank; ++d) {
1916 // Check if this dimension is dynamic.
1917 if (isNormalizedMemRefDynamicDim(d, layoutMap, memrefTypeDynDims)) {
1918 newShape[d] = ShapedType::kDynamic;
1919 continue;
1920 }
1921 // The lower bound for the shape is always zero.
1922 std::optional<int64_t> ubConst = fac.getConstantBound64(BoundType::UB, d);
1923 // For a static memref and an affine map with no symbols, this is
1924 // always bounded. However, when we have symbols, we may not be able to
1925 // obtain a constant upper bound. Also, mapping to a negative space is
1926 // invalid for normalization.
1927 if (!ubConst.has_value() || *ubConst < 0) {
1928 LLVM_DEBUG(llvm::dbgs()
1929 << "can't normalize map due to unknown/invalid upper bound");
1930 return memrefType;
1931 }
1932 // If dimension of new memrefType is dynamic, the value is -1.
1933 newShape[d] = *ubConst + 1;
1934 }
1935
1936 // Create the new memref type after trivializing the old layout map.
1937 auto newMemRefType =
1938 MemRefType::Builder(memrefType)
1939 .setShape(newShape)
1940 .setLayout(AffineMapAttr::get(
1941 AffineMap::getMultiDimIdentityMap(newRank, context)));
1942 return newMemRefType;
1943}
1944
1945DivModValue mlir::affine::getDivMod(OpBuilder &b, Location loc, Value lhs,
1946 Value rhs) {
1947 DivModValue result;
1948 AffineExpr d0, d1;
1949 bindDims(b.getContext(), d0, d1);
1950 result.quotient =
1951 affine::makeComposedAffineApply(b, loc, d0.floorDiv(d1), {lhs, rhs});
1952 result.remainder =
1953 affine::makeComposedAffineApply(b, loc, d0 % d1, {lhs, rhs});
1954 return result;
1955}
1956
1957/// Create an affine map that computes `lhs` * `rhs`, composing in any other
1958/// affine maps.
1959static FailureOr<OpFoldResult> composedAffineMultiply(OpBuilder &b,
1960 Location loc,
1961 OpFoldResult lhs,
1962 OpFoldResult rhs) {
1963 AffineExpr s0, s1;
1964 bindSymbols(b.getContext(), s0, s1);
1965 return makeComposedFoldedAffineApply(b, loc, s0 * s1, {lhs, rhs});
1966}
1967
1968FailureOr<SmallVector<Value>>
1969mlir::affine::delinearizeIndex(OpBuilder &b, Location loc, Value linearIndex,
1970 ArrayRef<Value> basis, bool hasOuterBound) {
1971 if (hasOuterBound)
1972 basis = basis.drop_front();
1973
1974 // Note: the divisors are backwards due to the scan.
1975 SmallVector<Value> divisors;
1976 OpFoldResult basisProd = b.getIndexAttr(1);
1977 for (OpFoldResult basisElem : llvm::reverse(basis)) {
1978 FailureOr<OpFoldResult> nextProd =
1979 composedAffineMultiply(b, loc, basisElem, basisProd);
1980 if (failed(nextProd))
1981 return failure();
1982 basisProd = *nextProd;
1983 divisors.push_back(getValueOrCreateConstantIndexOp(b, loc, basisProd));
1984 }
1985
1986 SmallVector<Value> results;
1987 results.reserve(divisors.size() + 1);
1988 Value residual = linearIndex;
1989 for (Value divisor : llvm::reverse(divisors)) {
1990 DivModValue divMod = getDivMod(b, loc, residual, divisor);
1991 results.push_back(divMod.quotient);
1992 residual = divMod.remainder;
1993 }
1994 results.push_back(residual);
1995 return results;
1996}
1997
1998FailureOr<SmallVector<Value>>
1999mlir::affine::delinearizeIndex(OpBuilder &b, Location loc, Value linearIndex,
2001 bool hasOuterBound) {
2002 if (hasOuterBound)
2003 basis = basis.drop_front();
2004
2005 // Note: the divisors are backwards due to the scan.
2006 SmallVector<Value> divisors;
2007 OpFoldResult basisProd = b.getIndexAttr(1);
2008 for (OpFoldResult basisElem : llvm::reverse(basis)) {
2009 FailureOr<OpFoldResult> nextProd =
2010 composedAffineMultiply(b, loc, basisElem, basisProd);
2011 if (failed(nextProd))
2012 return failure();
2013 basisProd = *nextProd;
2014 divisors.push_back(getValueOrCreateConstantIndexOp(b, loc, basisProd));
2015 }
2016
2017 SmallVector<Value> results;
2018 results.reserve(divisors.size() + 1);
2019 Value residual = linearIndex;
2020 for (Value divisor : llvm::reverse(divisors)) {
2021 DivModValue divMod = getDivMod(b, loc, residual, divisor);
2022 results.push_back(divMod.quotient);
2023 residual = divMod.remainder;
2024 }
2025 results.push_back(residual);
2026 return results;
2027}
2028
2029OpFoldResult mlir::affine::linearizeIndex(ArrayRef<OpFoldResult> multiIndex,
2031 ImplicitLocOpBuilder &builder) {
2032 return linearizeIndex(builder, builder.getLoc(), multiIndex, basis);
2033}
2034
2035OpFoldResult mlir::affine::linearizeIndex(OpBuilder &builder, Location loc,
2036 ArrayRef<OpFoldResult> multiIndex,
2037 ArrayRef<OpFoldResult> basis) {
2038 assert(multiIndex.size() == basis.size() ||
2039 multiIndex.size() == basis.size() + 1);
2040 SmallVector<AffineExpr> basisAffine;
2041
2042 // Add a fake initial size in order to make the later index linearization
2043 // computations line up if an outer bound is not provided.
2044 if (multiIndex.size() == basis.size() + 1)
2045 basisAffine.push_back(getAffineConstantExpr(1, builder.getContext()));
2046
2047 for (size_t i = 0; i < basis.size(); ++i) {
2048 basisAffine.push_back(getAffineSymbolExpr(i, builder.getContext()));
2049 }
2050
2051 SmallVector<AffineExpr> stridesAffine = computeStrides(basisAffine);
2053 strides.reserve(stridesAffine.size());
2054 llvm::transform(stridesAffine, std::back_inserter(strides),
2055 [&builder, &basis, loc](AffineExpr strideExpr) {
2057 builder, loc, strideExpr, basis);
2058 });
2059
2060 auto &&[linearIndexExpr, multiIndexAndStrides] = computeLinearIndex(
2061 OpFoldResult(builder.getIndexAttr(0)), strides, multiIndex);
2062 return affine::makeComposedFoldedAffineApply(builder, loc, linearIndexExpr,
2063 multiIndexAndStrides);
2064}
return success()
static bool mayHaveEffect(Operation *srcMemOp, Operation *destMemOp, unsigned minSurroundingLoops)
Returns true if srcMemOp may have an effect on destMemOp within the scope of the outermost minSurroun...
Definition Utils.cpp:655
static AffineIfOp hoistAffineIfOp(AffineIfOp ifOp, Operation *hoistOverOp)
A helper for the mechanics of mlir::hoistAffineIfOp.
Definition Utils.cpp:290
static void createNewDynamicSizes(MemRefType oldMemRefType, MemRefType newMemRefType, AffineMap map, AllocLikeOp allocOp, OpBuilder b, SmallVectorImpl< Value > &newDynamicSizes)
Create new maps to calculate each dimension size of newMemRefType, and create newDynamicSizes from th...
Definition Utils.cpp:1672
static bool isDereferencingOp(Operation *op)
Definition Utils.cpp:1111
static LogicalResult getTileSizePos(AffineMap map, SmallVectorImpl< std::tuple< AffineExpr, unsigned, unsigned > > &tileSizePos)
Check if map is a tiled layout.
Definition Utils.cpp:1498
TileExprPattern
Enum to set patterns of affine expr in tiled-layout map.
Definition Utils.cpp:1490
@ TileFloorDiv
Definition Utils.cpp:1490
@ TileNone
Definition Utils.cpp:1490
@ TileMod
Definition Utils.cpp:1490
static void promoteIfBlock(AffineIfOp ifOp, bool elseBlock)
Promotes the then or the else block of ifOp (depending on whether elseBlock is false or true) into if...
Definition Utils.cpp:247
static bool isNormalizedMemRefDynamicDim(unsigned dim, AffineMap layoutMap, SmallVectorImpl< unsigned > &inMemrefTypeDynDims)
Check if dim dimension of memrefType with layoutMap becomes dynamic after normalization.
Definition Utils.cpp:1597
static FailureOr< OpFoldResult > composedAffineMultiply(OpBuilder &b, Location loc, OpFoldResult lhs, OpFoldResult rhs)
Create an affine map that computes lhs * rhs, composing in any other affine maps.
Definition Utils.cpp:1959
static void loadCSE(AffineReadOpInterface loadA, SmallVectorImpl< Operation * > &loadOpsToErase, DominanceInfo &domInfo, llvm::function_ref< bool(Value, Value)> mayAlias)
Definition Utils.cpp:966
static AffineExpr createDimSizeExprForTiledLayout(AffineExpr oldMapOutput, TileExprPattern pat)
Create affine expr to calculate dimension size for a tiled-layout map.
Definition Utils.cpp:1616
static void findUnusedStore(AffineWriteOpInterface writeA, SmallVectorImpl< Operation * > &opsToErase, PostDominanceInfo &postDominanceInfo, llvm::function_ref< bool(Value, Value)> mayAlias)
Definition Utils.cpp:912
static bool mustReachAtInnermost(const MemRefAccess &srcAccess, const MemRefAccess &destAccess)
Returns true if the memory operation of destAccess depends on srcAccess inside of the innermost commo...
Definition Utils.cpp:637
static void forwardStoreToLoad(AffineReadOpInterface loadOp, SmallVectorImpl< Operation * > &loadOpsToErase, SmallPtrSetImpl< Value > &memrefsToErase, DominanceInfo &domInfo, llvm::function_ref< bool(Value, Value)> mayAlias)
Attempt to eliminate loadOp by replacing it with a value stored into memory which the load is guarant...
Definition Utils.cpp:836
static Operation * getOutermostInvariantForOp(AffineIfOp ifOp)
Returns the outermost affine.for/parallel op that the ifOp is invariant on.
Definition Utils.cpp:263
static void visit(Operation *op, DenseSet< Operation * > &visited)
Visits all the pdl.operand(s), pdl.result(s), and pdl.operation(s) connected to the given operation.
Definition PDL.cpp:62
static bool mayAlias(Value first, Value second)
Returns true if two values may be referencing aliasing memory.
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
static Value max(ImplicitLocOpBuilder &builder, Value value, Value bound)
static Value min(ImplicitLocOpBuilder &builder, Value value, Value bound)
Affine binary operation expression.
Definition AffineExpr.h:214
AffineExpr getLHS() const
AffineExpr getRHS() const
An integer constant appearing in affine expression.
Definition AffineExpr.h:239
int64_t getValue() const
A dimensional identifier appearing in an affine expression.
Definition AffineExpr.h:223
unsigned getPosition() const
See documentation for AffineExprVisitorBase.
Base type for affine expression.
Definition AffineExpr.h:68
AffineExpr floorDiv(uint64_t v) const
RetT walk(FnT &&callback) const
Walk all of the AffineExpr's in this expression in postorder.
Definition AffineExpr.h:117
AffineExprKind getKind() const
Return the classification for this type.
AffineExpr ceilDiv(uint64_t v) const
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition AffineMap.h:46
MLIRContext * getContext() const
static AffineMap getMultiDimIdentityMap(unsigned numDims, MLIRContext *context)
Returns an AffineMap with 'numDims' identity result dim exprs.
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
unsigned getNumSymbols() const
unsigned getNumDims() const
ArrayRef< AffineExpr > getResults() const
unsigned getNumResults() const
unsigned getNumInputs() const
AffineExpr getResult(unsigned idx) const
AffineMap compose(AffineMap map) const
Returns the AffineMap resulting from composing this with map.
bool isIdentity() const
Returns true if this affine map is an identity affine map.
A symbolic identifier appearing in an affine expression.
Definition AffineExpr.h:231
unsigned getPosition() const
This class represents the main alias analysis interface in MLIR.
AliasResult alias(Value lhs, Value rhs)
Given two values, return their aliasing behavior.
bool isNo() const
Returns if this result indicates no possibility of aliasing.
This class represents an argument of a Block.
Definition Value.h:306
Block represents an ordered list of Operations.
Definition Block.h:34
OpListType::iterator iterator
Definition Block.h:165
OpListType & getOperations()
Definition Block.h:162
SuccessorRange getSuccessors()
Definition Block.h:280
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
IntegerAttr getIndexAttr(int64_t value)
Definition Builders.cpp:116
AffineMap getMultiDimIdentityMap(unsigned rank)
Definition Builders.cpp:396
AffineExpr getAffineConstantExpr(int64_t constant)
Definition Builders.cpp:381
AffineExpr getAffineDimExpr(unsigned position)
Definition Builders.cpp:373
MLIRContext * getContext() const
Definition Builders.h:56
A class for computing basic dominance information.
Definition Dominance.h:143
bool dominates(Operation *a, Operation *b) const
Return true if operation A dominates operation B, i.e.
Definition Dominance.h:161
This class represents a frozen set of patterns that can be processed by a pattern applicator.
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
void clear()
Clears all mappings held by the mapper.
Definition IRMapping.h:79
ImplicitLocOpBuilder maintains a 'current location', allowing use of the create<> method without spec...
Definition Builders.h:632
Location getLoc() const
Accessors for the implied location.
Definition Builders.h:665
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
This is a builder type that keeps local references to arguments.
Builder & setShape(ArrayRef< int64_t > newShape)
Builder & setLayout(MemRefLayoutAttrInterface newLayout)
NamedAttribute represents a combination of a name and an Attribute value.
Definition Attributes.h:164
Attribute getValue() const
Return the value of the attribute.
Definition Attributes.h:179
This class helps build Operations.
Definition Builders.h:210
static OpBuilder atBlockBegin(Block *block, Listener *listener=nullptr)
Create a builder and set the insertion point to before the first operation in the block but still ins...
Definition Builders.h:243
Block::iterator getInsertionPoint() const
Returns the current insertion point of the builder.
Definition Builders.h:448
Block * getInsertionBlock() const
Return the block the current insertion point belongs to.
Definition Builders.h:445
Operation * create(const OperationState &state)
Creates an operation given the fields represented as an OperationState.
Definition Builders.cpp:466
This class represents a single result from folding an operation.
This trait indicates that the memory effects of an operation includes the effects of operations neste...
This class provides the API for ops that are known to be isolated from above.
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
Value getOperand(unsigned idx)
Definition Operation.h:375
void setOperand(unsigned idx, Value value)
Definition Operation.h:376
operand_iterator operand_begin()
Definition Operation.h:399
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:230
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:251
unsigned getNumOperands()
Definition Operation.h:371
Attribute getPropertiesAsAttribute()
Return the properties converted to an attribute.
operand_iterator operand_end()
Definition Operation.h:400
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:115
DictionaryAttr getDiscardableAttrDictionary()
Return all of the discardable attributes on this operation as a DictionaryAttr.
Definition Operation.h:553
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:403
bool isAncestor(Operation *other)
Return true if this operation is an ancestor of the other operation.
Definition Operation.h:288
void replaceAllUsesWith(ValuesT &&values)
Replace all uses of results of this operation with the provided 'values'.
Definition Operation.h:297
void setOperands(ValueRange operands)
Replace the current operands of this operation with the ones provided in 'operands'.
std::enable_if_t< llvm::function_traits< std::decay_t< FnT > >::num_args==1, RetT > walk(FnT &&callback)
Walk the operation by calling the callback for each nested operation (including this one),...
Definition Operation.h:849
result_range getResults()
Definition Operation.h:440
Region * getParentRegion()
Returns the region to which the instruction belongs.
Definition Operation.h:247
void erase()
Remove this operation from its parent block and delete it.
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:429
A class for computing basic postdominance information.
Definition Dominance.h:207
bool postDominates(Operation *a, Operation *b) const
Return true if operation A postdominates operation B.
Definition Dominance.h:216
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
Region * getParentRegion()
Return the region containing this region or nullptr if the region is attached to a top-level operatio...
Definition Region.cpp:45
bool isAncestor(Region *other)
Return true if this region is ancestor of the other region.
Definition Region.h:234
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
Type getType() const
Return the type of this value.
Definition Value.h:105
void replaceAllUsesExcept(Value newValue, const SmallPtrSetImpl< Operation * > &exceptions)
Replace all uses of 'this' value with 'newValue', updating anything in the IR that uses 'this' to use...
Definition Value.cpp:71
void replaceAllUsesWith(Value newValue)
Replace all uses of 'this' value with the new value, updating anything in the IR that uses 'this' to ...
Definition Value.h:149
user_range getUsers() const
Definition Value.h:218
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
static WalkResult advance()
Definition WalkResult.h:47
static WalkResult interrupt()
Definition WalkResult.h:46
An AffineValueMap is an affine map plus its ML value operands and results for analysis purposes.
LogicalResult canonicalize()
Attempts to canonicalize the map and operands.
ArrayRef< Value > getOperands() const
AffineExpr getResult(unsigned i)
static void difference(const AffineValueMap &a, const AffineValueMap &b, AffineValueMap *res)
Return the value map that is the difference of value maps 'a' and 'b', represented as an affine map a...
FlatAffineValueConstraints is an extension of FlatLinearValueConstraints with helper functions for Af...
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:398
AffineApplyOp makeComposedAffineApply(OpBuilder &b, Location loc, AffineMap map, ArrayRef< OpFoldResult > operands, bool composeAffineMin=false)
Returns a composed AffineApplyOp by composing map and operands with other AffineApplyOps supplying th...
LogicalResult promoteIfSingleIteration(AffineForOp forOp)
Promotes the loop body of a AffineForOp to its containing block if the loop was known to have a singl...
bool isValidDim(Value value)
Returns true if the given Value can be used as a dimension id in the region of the closest surroundin...
unsigned getNumCommonSurroundingLoops(Operation &a, Operation &b)
Returns the number of surrounding loops common to both A and B.
Definition Utils.cpp:2122
DependenceResult checkMemrefAccessDependence(const MemRefAccess &srcAccess, const MemRefAccess &dstAccess, unsigned loopDepth, FlatAffineValueConstraints *dependenceConstraints=nullptr, SmallVector< DependenceComponent, 2 > *dependenceComponents=nullptr, bool allowRAR=false)
LogicalResult affineParallelize(AffineForOp forOp, ArrayRef< LoopReduction > parallelReductions={}, AffineParallelOp *resOp=nullptr)
Replaces a parallel affine.for op with a 1-d affine.parallel op.
Definition Utils.cpp:353
void canonicalizeMapAndOperands(AffineMap *map, SmallVectorImpl< Value > *operands)
Modifies both map and operands in-place so as to:
void getReachableAffineApplyOps(ArrayRef< Value > operands, SmallVectorImpl< Operation * > &affineApplyOps)
Returns in affineApplyOps, the sequence of those AffineApplyOp Operations that are reachable via a se...
OpFoldResult makeComposedFoldedAffineApply(OpBuilder &b, Location loc, AffineMap map, ArrayRef< OpFoldResult > operands, bool composeAffineMin=false)
Constructs an AffineApplyOp that applies map to operands after composing the map with the maps of any...
Region * getAffineAnalysisScope(Operation *op)
Returns the closest region enclosing op that is held by a non-affine operation; nullptr if there is n...
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...
bool isValidSymbol(Value value)
Returns true if the given value can be used as a symbol in the region of the closest surrounding op t...
bool hasDependence(DependenceResult result)
Utility function that returns true if the provided DependenceResult corresponds to a dependence resul...
bool hasNoInterveningEffect(Operation *start, T memOp, llvm::function_ref< bool(Value, Value)> mayAlias)
Hoists out affine.if/else to as high as possible, i.e., past all invariant affine....
Definition Utils.cpp:688
bool noDependence(DependenceResult result)
Returns true if the provided DependenceResult corresponds to the absence of a dependence.
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:733
Value linearizeIndex(ValueRange indices, ArrayRef< int64_t > strides, int64_t offset, Type integerType, Location loc, OpBuilder &builder, LinearizedIndexNoWrapFlags noWrapFlags={})
Generates IR to perform index linearization with the given indices and their corresponding strides,...
Include the generated interface declarations.
AffineMap simplifyAffineMap(AffineMap map)
Simplifies an affine map by simplifying its underlying AffineExpr results.
SmallVector< int64_t > computeStrides(ArrayRef< int64_t > sizes)
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:310
void bindDims(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to DimExpr at positions: [0 .
Definition AffineExpr.h:311
std::pair< AffineExpr, SmallVector< OpFoldResult > > computeLinearIndex(OpFoldResult sourceOffset, ArrayRef< OpFoldResult > strides, ArrayRef< OpFoldResult > indices)
Compute linear index from provided strides and indices, assuming strided layout.
LogicalResult applyPatternsGreedily(Region &region, const FrozenRewritePatternSet &patterns, GreedyRewriteConfig config=GreedyRewriteConfig(), bool *changed=nullptr)
Rewrite ops in the given region, which must be isolated from above, by repeatedly applying the highes...
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
Definition LLVM.h:122
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
bool hasSingleEffect(Operation *op)
Returns "true" if op has only an effect of type EffectTy.
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...
@ CeilDiv
RHS of ceildiv is always a constant or a symbolic expression.
Definition AffineExpr.h:50
@ Mod
RHS of mod is always a constant or a symbolic expression with a positive value.
Definition AffineExpr.h:46
@ FloorDiv
RHS of floordiv is always a constant or a symbolic expression.
Definition AffineExpr.h:48
AffineExpr getAffineBinaryOpExpr(AffineExprKind kind, AffineExpr lhs, AffineExpr rhs)
void bindSymbols(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to SymbolExpr at positions: [0 .
Definition AffineExpr.h:325
Value getValueOrCreateConstantIndexOp(OpBuilder &b, Location loc, OpFoldResult ofr)
Converts an OpFoldResult to a Value.
Definition Utils.cpp:114
AffineExpr getAffineConstantExpr(int64_t constant, MLIRContext *context)
@ ExistingOps
Only pre-existing ops are processed.
AffineExpr getAffineDimExpr(unsigned position, MLIRContext *context)
These free functions allow clients of the API to not use classes in detail.
AffineExpr getAffineSymbolExpr(unsigned position, MLIRContext *context)
The following effect indicates that the operation reads from some resource.
This represents an operation in an abstracted form, suitable for use with the builder APIs.
Checks whether two accesses to the same memref access the same element.
A description of a (parallelizable) reduction in an affine loop.
arith::AtomicRMWKind kind
Reduction kind.
Value value
The value being reduced.
Encapsulates a memref load or store access information.
Eliminates variable at the specified position using Fourier-Motzkin variable elimination.