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