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 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/Func/IR/FuncOps.h" 21 #include "mlir/Dialect/MemRef/IR/MemRef.h" 22 #include "mlir/IR/AffineExprVisitor.h" 23 #include "mlir/IR/BlockAndValueMapping.h" 24 #include "mlir/IR/Dominance.h" 25 #include "mlir/IR/IntegerSet.h" 26 #include "mlir/Transforms/GreedyPatternRewriteDriver.h" 27 28 #define DEBUG_TYPE "affine-utils" 29 30 using namespace mlir; 31 32 namespace { 33 /// Visit affine expressions recursively and build the sequence of operations 34 /// that correspond to it. Visitation functions return an Value of the 35 /// expression subtree they visited or `nullptr` on error. 36 class AffineApplyExpander 37 : public AffineExprVisitor<AffineApplyExpander, Value> { 38 public: 39 /// This internal class expects arguments to be non-null, checks must be 40 /// performed at the call site. 41 AffineApplyExpander(OpBuilder &builder, ValueRange dimValues, 42 ValueRange symbolValues, Location loc) 43 : builder(builder), dimValues(dimValues), symbolValues(symbolValues), 44 loc(loc) {} 45 46 template <typename OpTy> 47 Value buildBinaryExpr(AffineBinaryOpExpr expr) { 48 auto lhs = visit(expr.getLHS()); 49 auto rhs = visit(expr.getRHS()); 50 if (!lhs || !rhs) 51 return nullptr; 52 auto op = builder.create<OpTy>(loc, lhs, rhs); 53 return op.getResult(); 54 } 55 56 Value visitAddExpr(AffineBinaryOpExpr expr) { 57 return buildBinaryExpr<arith::AddIOp>(expr); 58 } 59 60 Value visitMulExpr(AffineBinaryOpExpr expr) { 61 return buildBinaryExpr<arith::MulIOp>(expr); 62 } 63 64 /// Euclidean modulo operation: negative RHS is not allowed. 65 /// Remainder of the euclidean integer division is always non-negative. 66 /// 67 /// Implemented as 68 /// 69 /// a mod b = 70 /// let remainder = srem a, b; 71 /// negative = a < 0 in 72 /// select negative, remainder + b, remainder. 73 Value visitModExpr(AffineBinaryOpExpr expr) { 74 auto rhsConst = expr.getRHS().dyn_cast<AffineConstantExpr>(); 75 if (!rhsConst) { 76 emitError( 77 loc, 78 "semi-affine expressions (modulo by non-const) are not supported"); 79 return nullptr; 80 } 81 if (rhsConst.getValue() <= 0) { 82 emitError(loc, "modulo by non-positive value is not supported"); 83 return nullptr; 84 } 85 86 auto lhs = visit(expr.getLHS()); 87 auto rhs = visit(expr.getRHS()); 88 assert(lhs && rhs && "unexpected affine expr lowering failure"); 89 90 Value remainder = builder.create<arith::RemSIOp>(loc, lhs, rhs); 91 Value zeroCst = builder.create<arith::ConstantIndexOp>(loc, 0); 92 Value isRemainderNegative = builder.create<arith::CmpIOp>( 93 loc, arith::CmpIPredicate::slt, remainder, zeroCst); 94 Value correctedRemainder = 95 builder.create<arith::AddIOp>(loc, remainder, rhs); 96 Value result = builder.create<arith::SelectOp>( 97 loc, isRemainderNegative, correctedRemainder, remainder); 98 return result; 99 } 100 101 /// Floor division operation (rounds towards negative infinity). 102 /// 103 /// For positive divisors, it can be implemented without branching and with a 104 /// single division operation as 105 /// 106 /// a floordiv b = 107 /// let negative = a < 0 in 108 /// let absolute = negative ? -a - 1 : a in 109 /// let quotient = absolute / b in 110 /// negative ? -quotient - 1 : quotient 111 Value visitFloorDivExpr(AffineBinaryOpExpr expr) { 112 auto rhsConst = expr.getRHS().dyn_cast<AffineConstantExpr>(); 113 if (!rhsConst) { 114 emitError( 115 loc, 116 "semi-affine expressions (division by non-const) are not supported"); 117 return nullptr; 118 } 119 if (rhsConst.getValue() <= 0) { 120 emitError(loc, "division by non-positive value is not supported"); 121 return nullptr; 122 } 123 124 auto lhs = visit(expr.getLHS()); 125 auto rhs = visit(expr.getRHS()); 126 assert(lhs && rhs && "unexpected affine expr lowering failure"); 127 128 Value zeroCst = builder.create<arith::ConstantIndexOp>(loc, 0); 129 Value noneCst = builder.create<arith::ConstantIndexOp>(loc, -1); 130 Value negative = builder.create<arith::CmpIOp>( 131 loc, arith::CmpIPredicate::slt, lhs, zeroCst); 132 Value negatedDecremented = builder.create<arith::SubIOp>(loc, noneCst, lhs); 133 Value dividend = 134 builder.create<arith::SelectOp>(loc, negative, negatedDecremented, lhs); 135 Value quotient = builder.create<arith::DivSIOp>(loc, dividend, rhs); 136 Value correctedQuotient = 137 builder.create<arith::SubIOp>(loc, noneCst, quotient); 138 Value result = builder.create<arith::SelectOp>(loc, negative, 139 correctedQuotient, quotient); 140 return result; 141 } 142 143 /// Ceiling division operation (rounds towards positive infinity). 144 /// 145 /// For positive divisors, it can be implemented without branching and with a 146 /// single division operation as 147 /// 148 /// a ceildiv b = 149 /// let negative = a <= 0 in 150 /// let absolute = negative ? -a : a - 1 in 151 /// let quotient = absolute / b in 152 /// negative ? -quotient : quotient + 1 153 Value visitCeilDivExpr(AffineBinaryOpExpr expr) { 154 auto rhsConst = expr.getRHS().dyn_cast<AffineConstantExpr>(); 155 if (!rhsConst) { 156 emitError(loc) << "semi-affine expressions (division by non-const) are " 157 "not supported"; 158 return nullptr; 159 } 160 if (rhsConst.getValue() <= 0) { 161 emitError(loc, "division by non-positive value is not supported"); 162 return nullptr; 163 } 164 auto lhs = visit(expr.getLHS()); 165 auto rhs = visit(expr.getRHS()); 166 assert(lhs && rhs && "unexpected affine expr lowering failure"); 167 168 Value zeroCst = builder.create<arith::ConstantIndexOp>(loc, 0); 169 Value oneCst = builder.create<arith::ConstantIndexOp>(loc, 1); 170 Value nonPositive = builder.create<arith::CmpIOp>( 171 loc, arith::CmpIPredicate::sle, lhs, zeroCst); 172 Value negated = builder.create<arith::SubIOp>(loc, zeroCst, lhs); 173 Value decremented = builder.create<arith::SubIOp>(loc, lhs, oneCst); 174 Value dividend = 175 builder.create<arith::SelectOp>(loc, nonPositive, negated, decremented); 176 Value quotient = builder.create<arith::DivSIOp>(loc, dividend, rhs); 177 Value negatedQuotient = 178 builder.create<arith::SubIOp>(loc, zeroCst, quotient); 179 Value incrementedQuotient = 180 builder.create<arith::AddIOp>(loc, quotient, oneCst); 181 Value result = builder.create<arith::SelectOp>( 182 loc, nonPositive, negatedQuotient, incrementedQuotient); 183 return result; 184 } 185 186 Value visitConstantExpr(AffineConstantExpr expr) { 187 auto op = builder.create<arith::ConstantIndexOp>(loc, expr.getValue()); 188 return op.getResult(); 189 } 190 191 Value visitDimExpr(AffineDimExpr expr) { 192 assert(expr.getPosition() < dimValues.size() && 193 "affine dim position out of range"); 194 return dimValues[expr.getPosition()]; 195 } 196 197 Value visitSymbolExpr(AffineSymbolExpr expr) { 198 assert(expr.getPosition() < symbolValues.size() && 199 "symbol dim position out of range"); 200 return symbolValues[expr.getPosition()]; 201 } 202 203 private: 204 OpBuilder &builder; 205 ValueRange dimValues; 206 ValueRange symbolValues; 207 208 Location loc; 209 }; 210 } // namespace 211 212 /// Create a sequence of operations that implement the `expr` applied to the 213 /// given dimension and symbol values. 214 mlir::Value mlir::expandAffineExpr(OpBuilder &builder, Location loc, 215 AffineExpr expr, ValueRange dimValues, 216 ValueRange symbolValues) { 217 return AffineApplyExpander(builder, dimValues, symbolValues, loc).visit(expr); 218 } 219 220 /// Create a sequence of operations that implement the `affineMap` applied to 221 /// the given `operands` (as it it were an AffineApplyOp). 222 Optional<SmallVector<Value, 8>> mlir::expandAffineMap(OpBuilder &builder, 223 Location loc, 224 AffineMap affineMap, 225 ValueRange operands) { 226 auto numDims = affineMap.getNumDims(); 227 auto expanded = llvm::to_vector<8>( 228 llvm::map_range(affineMap.getResults(), 229 [numDims, &builder, loc, operands](AffineExpr expr) { 230 return expandAffineExpr(builder, loc, expr, 231 operands.take_front(numDims), 232 operands.drop_front(numDims)); 233 })); 234 if (llvm::all_of(expanded, [](Value v) { return v; })) 235 return expanded; 236 return None; 237 } 238 239 /// Promotes the `then` or the `else` block of `ifOp` (depending on whether 240 /// `elseBlock` is false or true) into `ifOp`'s containing block, and discards 241 /// the rest of the op. 242 static void promoteIfBlock(AffineIfOp ifOp, bool elseBlock) { 243 if (elseBlock) 244 assert(ifOp.hasElse() && "else block expected"); 245 246 Block *destBlock = ifOp->getBlock(); 247 Block *srcBlock = elseBlock ? ifOp.getElseBlock() : ifOp.getThenBlock(); 248 destBlock->getOperations().splice( 249 Block::iterator(ifOp), srcBlock->getOperations(), srcBlock->begin(), 250 std::prev(srcBlock->end())); 251 ifOp.erase(); 252 } 253 254 /// Returns the outermost affine.for/parallel op that the `ifOp` is invariant 255 /// on. The `ifOp` could be hoisted and placed right before such an operation. 256 /// This method assumes that the ifOp has been canonicalized (to be correct and 257 /// effective). 258 static Operation *getOutermostInvariantForOp(AffineIfOp ifOp) { 259 // Walk up the parents past all for op that this conditional is invariant on. 260 auto ifOperands = ifOp.getOperands(); 261 auto *res = ifOp.getOperation(); 262 while (!isa<FuncOp>(res->getParentOp())) { 263 auto *parentOp = res->getParentOp(); 264 if (auto forOp = dyn_cast<AffineForOp>(parentOp)) { 265 if (llvm::is_contained(ifOperands, forOp.getInductionVar())) 266 break; 267 } else if (auto parallelOp = dyn_cast<AffineParallelOp>(parentOp)) { 268 for (auto iv : parallelOp.getIVs()) 269 if (llvm::is_contained(ifOperands, iv)) 270 break; 271 } else if (!isa<AffineIfOp>(parentOp)) { 272 // Won't walk up past anything other than affine.for/if ops. 273 break; 274 } 275 // You can always hoist up past any affine.if ops. 276 res = parentOp; 277 } 278 return res; 279 } 280 281 /// A helper for the mechanics of mlir::hoistAffineIfOp. Hoists `ifOp` just over 282 /// `hoistOverOp`. Returns the new hoisted op if any hoisting happened, 283 /// otherwise the same `ifOp`. 284 static AffineIfOp hoistAffineIfOp(AffineIfOp ifOp, Operation *hoistOverOp) { 285 // No hoisting to do. 286 if (hoistOverOp == ifOp) 287 return ifOp; 288 289 // Create the hoisted 'if' first. Then, clone the op we are hoisting over for 290 // the else block. Then drop the else block of the original 'if' in the 'then' 291 // branch while promoting its then block, and analogously drop the 'then' 292 // block of the original 'if' from the 'else' branch while promoting its else 293 // block. 294 BlockAndValueMapping operandMap; 295 OpBuilder b(hoistOverOp); 296 auto hoistedIfOp = b.create<AffineIfOp>(ifOp.getLoc(), ifOp.getIntegerSet(), 297 ifOp.getOperands(), 298 /*elseBlock=*/true); 299 300 // Create a clone of hoistOverOp to use for the else branch of the hoisted 301 // conditional. The else block may get optimized away if empty. 302 Operation *hoistOverOpClone = nullptr; 303 // We use this unique name to identify/find `ifOp`'s clone in the else 304 // version. 305 StringAttr idForIfOp = b.getStringAttr("__mlir_if_hoisting"); 306 operandMap.clear(); 307 b.setInsertionPointAfter(hoistOverOp); 308 // We'll set an attribute to identify this op in a clone of this sub-tree. 309 ifOp->setAttr(idForIfOp, b.getBoolAttr(true)); 310 hoistOverOpClone = b.clone(*hoistOverOp, operandMap); 311 312 // Promote the 'then' block of the original affine.if in the then version. 313 promoteIfBlock(ifOp, /*elseBlock=*/false); 314 315 // Move the then version to the hoisted if op's 'then' block. 316 auto *thenBlock = hoistedIfOp.getThenBlock(); 317 thenBlock->getOperations().splice(thenBlock->begin(), 318 hoistOverOp->getBlock()->getOperations(), 319 Block::iterator(hoistOverOp)); 320 321 // Find the clone of the original affine.if op in the else version. 322 AffineIfOp ifCloneInElse; 323 hoistOverOpClone->walk([&](AffineIfOp ifClone) { 324 if (!ifClone->getAttr(idForIfOp)) 325 return WalkResult::advance(); 326 ifCloneInElse = ifClone; 327 return WalkResult::interrupt(); 328 }); 329 assert(ifCloneInElse && "if op clone should exist"); 330 // For the else block, promote the else block of the original 'if' if it had 331 // one; otherwise, the op itself is to be erased. 332 if (!ifCloneInElse.hasElse()) 333 ifCloneInElse.erase(); 334 else 335 promoteIfBlock(ifCloneInElse, /*elseBlock=*/true); 336 337 // Move the else version into the else block of the hoisted if op. 338 auto *elseBlock = hoistedIfOp.getElseBlock(); 339 elseBlock->getOperations().splice( 340 elseBlock->begin(), hoistOverOpClone->getBlock()->getOperations(), 341 Block::iterator(hoistOverOpClone)); 342 343 return hoistedIfOp; 344 } 345 346 LogicalResult 347 mlir::affineParallelize(AffineForOp forOp, 348 ArrayRef<LoopReduction> parallelReductions) { 349 // Fail early if there are iter arguments that are not reductions. 350 unsigned numReductions = parallelReductions.size(); 351 if (numReductions != forOp.getNumIterOperands()) 352 return failure(); 353 354 Location loc = forOp.getLoc(); 355 OpBuilder outsideBuilder(forOp); 356 AffineMap lowerBoundMap = forOp.getLowerBoundMap(); 357 ValueRange lowerBoundOperands = forOp.getLowerBoundOperands(); 358 AffineMap upperBoundMap = forOp.getUpperBoundMap(); 359 ValueRange upperBoundOperands = forOp.getUpperBoundOperands(); 360 361 // Creating empty 1-D affine.parallel op. 362 auto reducedValues = llvm::to_vector<4>(llvm::map_range( 363 parallelReductions, [](const LoopReduction &red) { return red.value; })); 364 auto reductionKinds = llvm::to_vector<4>(llvm::map_range( 365 parallelReductions, [](const LoopReduction &red) { return red.kind; })); 366 AffineParallelOp newPloop = outsideBuilder.create<AffineParallelOp>( 367 loc, ValueRange(reducedValues).getTypes(), reductionKinds, 368 llvm::makeArrayRef(lowerBoundMap), lowerBoundOperands, 369 llvm::makeArrayRef(upperBoundMap), upperBoundOperands, 370 llvm::makeArrayRef(forOp.getStep())); 371 // Steal the body of the old affine for op. 372 newPloop.region().takeBody(forOp.region()); 373 Operation *yieldOp = &newPloop.getBody()->back(); 374 375 // Handle the initial values of reductions because the parallel loop always 376 // starts from the neutral value. 377 SmallVector<Value> newResults; 378 newResults.reserve(numReductions); 379 for (unsigned i = 0; i < numReductions; ++i) { 380 Value init = forOp.getIterOperands()[i]; 381 // This works because we are only handling single-op reductions at the 382 // moment. A switch on reduction kind or a mechanism to collect operations 383 // participating in the reduction will be necessary for multi-op reductions. 384 Operation *reductionOp = yieldOp->getOperand(i).getDefiningOp(); 385 assert(reductionOp && "yielded value is expected to be produced by an op"); 386 outsideBuilder.getInsertionBlock()->getOperations().splice( 387 outsideBuilder.getInsertionPoint(), newPloop.getBody()->getOperations(), 388 reductionOp); 389 reductionOp->setOperands({init, newPloop->getResult(i)}); 390 forOp->getResult(i).replaceAllUsesWith(reductionOp->getResult(0)); 391 } 392 393 // Update the loop terminator to yield reduced values bypassing the reduction 394 // operation itself (now moved outside of the loop) and erase the block 395 // arguments that correspond to reductions. Note that the loop always has one 396 // "main" induction variable whenc coming from a non-parallel for. 397 unsigned numIVs = 1; 398 yieldOp->setOperands(reducedValues); 399 newPloop.getBody()->eraseArguments( 400 llvm::to_vector<4>(llvm::seq<unsigned>(numIVs, numReductions + numIVs))); 401 402 forOp.erase(); 403 return success(); 404 } 405 406 // Returns success if any hoisting happened. 407 LogicalResult mlir::hoistAffineIfOp(AffineIfOp ifOp, bool *folded) { 408 // Bail out early if the ifOp returns a result. TODO: Consider how to 409 // properly support this case. 410 if (ifOp.getNumResults() != 0) 411 return failure(); 412 413 // Apply canonicalization patterns and folding - this is necessary for the 414 // hoisting check to be correct (operands should be composed), and to be more 415 // effective (no unused operands). Since the pattern rewriter's folding is 416 // entangled with application of patterns, we may fold/end up erasing the op, 417 // in which case we return with `folded` being set. 418 RewritePatternSet patterns(ifOp.getContext()); 419 AffineIfOp::getCanonicalizationPatterns(patterns, ifOp.getContext()); 420 bool erased; 421 FrozenRewritePatternSet frozenPatterns(std::move(patterns)); 422 (void)applyOpPatternsAndFold(ifOp, frozenPatterns, &erased); 423 if (erased) { 424 if (folded) 425 *folded = true; 426 return failure(); 427 } 428 if (folded) 429 *folded = false; 430 431 // The folding above should have ensured this, but the affine.if's 432 // canonicalization is missing composition of affine.applys into it. 433 assert(llvm::all_of(ifOp.getOperands(), 434 [](Value v) { 435 return isTopLevelValue(v) || isForInductionVar(v); 436 }) && 437 "operands not composed"); 438 439 // We are going hoist as high as possible. 440 // TODO: this could be customized in the future. 441 auto *hoistOverOp = getOutermostInvariantForOp(ifOp); 442 443 AffineIfOp hoistedIfOp = ::hoistAffineIfOp(ifOp, hoistOverOp); 444 // Nothing to hoist over. 445 if (hoistedIfOp == ifOp) 446 return failure(); 447 448 // Canonicalize to remove dead else blocks (happens whenever an 'if' moves up 449 // a sequence of affine.fors that are all perfectly nested). 450 (void)applyPatternsAndFoldGreedily( 451 hoistedIfOp->getParentWithTrait<OpTrait::IsIsolatedFromAbove>(), 452 frozenPatterns); 453 454 return success(); 455 } 456 457 // Return the min expr after replacing the given dim. 458 AffineExpr mlir::substWithMin(AffineExpr e, AffineExpr dim, AffineExpr min, 459 AffineExpr max, bool positivePath) { 460 if (e == dim) 461 return positivePath ? min : max; 462 if (auto bin = e.dyn_cast<AffineBinaryOpExpr>()) { 463 AffineExpr lhs = bin.getLHS(); 464 AffineExpr rhs = bin.getRHS(); 465 if (bin.getKind() == mlir::AffineExprKind::Add) 466 return substWithMin(lhs, dim, min, max, positivePath) + 467 substWithMin(rhs, dim, min, max, positivePath); 468 469 auto c1 = bin.getLHS().dyn_cast<AffineConstantExpr>(); 470 auto c2 = bin.getRHS().dyn_cast<AffineConstantExpr>(); 471 if (c1 && c1.getValue() < 0) 472 return getAffineBinaryOpExpr( 473 bin.getKind(), c1, substWithMin(rhs, dim, min, max, !positivePath)); 474 if (c2 && c2.getValue() < 0) 475 return getAffineBinaryOpExpr( 476 bin.getKind(), substWithMin(lhs, dim, min, max, !positivePath), c2); 477 return getAffineBinaryOpExpr( 478 bin.getKind(), substWithMin(lhs, dim, min, max, positivePath), 479 substWithMin(rhs, dim, min, max, positivePath)); 480 } 481 return e; 482 } 483 484 void mlir::normalizeAffineParallel(AffineParallelOp op) { 485 // Loops with min/max in bounds are not normalized at the moment. 486 if (op.hasMinMaxBounds()) 487 return; 488 489 AffineMap lbMap = op.lowerBoundsMap(); 490 SmallVector<int64_t, 8> steps = op.getSteps(); 491 // No need to do any work if the parallel op is already normalized. 492 bool isAlreadyNormalized = 493 llvm::all_of(llvm::zip(steps, lbMap.getResults()), [](auto tuple) { 494 int64_t step = std::get<0>(tuple); 495 auto lbExpr = 496 std::get<1>(tuple).template dyn_cast<AffineConstantExpr>(); 497 return lbExpr && lbExpr.getValue() == 0 && step == 1; 498 }); 499 if (isAlreadyNormalized) 500 return; 501 502 AffineValueMap ranges; 503 AffineValueMap::difference(op.getUpperBoundsValueMap(), 504 op.getLowerBoundsValueMap(), &ranges); 505 auto builder = OpBuilder::atBlockBegin(op.getBody()); 506 auto zeroExpr = builder.getAffineConstantExpr(0); 507 SmallVector<AffineExpr, 8> lbExprs; 508 SmallVector<AffineExpr, 8> ubExprs; 509 for (unsigned i = 0, e = steps.size(); i < e; ++i) { 510 int64_t step = steps[i]; 511 512 // Adjust the lower bound to be 0. 513 lbExprs.push_back(zeroExpr); 514 515 // Adjust the upper bound expression: 'range / step'. 516 AffineExpr ubExpr = ranges.getResult(i).ceilDiv(step); 517 ubExprs.push_back(ubExpr); 518 519 // Adjust the corresponding IV: 'lb + i * step'. 520 BlockArgument iv = op.getBody()->getArgument(i); 521 AffineExpr lbExpr = lbMap.getResult(i); 522 unsigned nDims = lbMap.getNumDims(); 523 auto expr = lbExpr + builder.getAffineDimExpr(nDims) * step; 524 auto map = AffineMap::get(/*dimCount=*/nDims + 1, 525 /*symbolCount=*/lbMap.getNumSymbols(), expr); 526 527 // Use an 'affine.apply' op that will be simplified later in subsequent 528 // canonicalizations. 529 OperandRange lbOperands = op.getLowerBoundsOperands(); 530 OperandRange dimOperands = lbOperands.take_front(nDims); 531 OperandRange symbolOperands = lbOperands.drop_front(nDims); 532 SmallVector<Value, 8> applyOperands{dimOperands}; 533 applyOperands.push_back(iv); 534 applyOperands.append(symbolOperands.begin(), symbolOperands.end()); 535 auto apply = builder.create<AffineApplyOp>(op.getLoc(), map, applyOperands); 536 iv.replaceAllUsesExcept(apply, apply); 537 } 538 539 SmallVector<int64_t, 8> newSteps(op.getNumDims(), 1); 540 op.setSteps(newSteps); 541 auto newLowerMap = AffineMap::get( 542 /*dimCount=*/0, /*symbolCount=*/0, lbExprs, op.getContext()); 543 op.setLowerBounds({}, newLowerMap); 544 auto newUpperMap = AffineMap::get(ranges.getNumDims(), ranges.getNumSymbols(), 545 ubExprs, op.getContext()); 546 op.setUpperBounds(ranges.getOperands(), newUpperMap); 547 } 548 549 /// Normalizes affine.for ops. If the affine.for op has only a single iteration 550 /// only then it is simply promoted, else it is normalized in the traditional 551 /// way, by converting the lower bound to zero and loop step to one. The upper 552 /// bound is set to the trip count of the loop. For now, original loops must 553 /// have lower bound with a single result only. There is no such restriction on 554 /// upper bounds. 555 LogicalResult mlir::normalizeAffineFor(AffineForOp op) { 556 if (succeeded(promoteIfSingleIteration(op))) 557 return success(); 558 559 // Check if the forop is already normalized. 560 if (op.hasConstantLowerBound() && (op.getConstantLowerBound() == 0) && 561 (op.getStep() == 1)) 562 return success(); 563 564 // Check if the lower bound has a single result only. Loops with a max lower 565 // bound can't be normalized without additional support like 566 // affine.execute_region's. If the lower bound does not have a single result 567 // then skip this op. 568 if (op.getLowerBoundMap().getNumResults() != 1) 569 return failure(); 570 571 Location loc = op.getLoc(); 572 OpBuilder opBuilder(op); 573 int64_t origLoopStep = op.getStep(); 574 575 // Calculate upperBound for normalized loop. 576 SmallVector<Value, 4> ubOperands; 577 AffineBound lb = op.getLowerBound(); 578 AffineBound ub = op.getUpperBound(); 579 ubOperands.reserve(ub.getNumOperands() + lb.getNumOperands()); 580 AffineMap origLbMap = lb.getMap(); 581 AffineMap origUbMap = ub.getMap(); 582 583 // Add dimension operands from upper/lower bound. 584 for (unsigned j = 0, e = origUbMap.getNumDims(); j < e; ++j) 585 ubOperands.push_back(ub.getOperand(j)); 586 for (unsigned j = 0, e = origLbMap.getNumDims(); j < e; ++j) 587 ubOperands.push_back(lb.getOperand(j)); 588 589 // Add symbol operands from upper/lower bound. 590 for (unsigned j = 0, e = origUbMap.getNumSymbols(); j < e; ++j) 591 ubOperands.push_back(ub.getOperand(origUbMap.getNumDims() + j)); 592 for (unsigned j = 0, e = origLbMap.getNumSymbols(); j < e; ++j) 593 ubOperands.push_back(lb.getOperand(origLbMap.getNumDims() + j)); 594 595 // Add original result expressions from lower/upper bound map. 596 SmallVector<AffineExpr, 1> origLbExprs(origLbMap.getResults().begin(), 597 origLbMap.getResults().end()); 598 SmallVector<AffineExpr, 2> origUbExprs(origUbMap.getResults().begin(), 599 origUbMap.getResults().end()); 600 SmallVector<AffineExpr, 4> newUbExprs; 601 602 // The original upperBound can have more than one result. For the new 603 // upperBound of this loop, take difference of all possible combinations of 604 // the ub results and lb result and ceildiv with the loop step. For e.g., 605 // 606 // affine.for %i1 = 0 to min affine_map<(d0)[] -> (d0 + 32, 1024)>(%i0) 607 // will have an upperBound map as, 608 // affine_map<(d0)[] -> (((d0 + 32) - 0) ceildiv 1, (1024 - 0) ceildiv 609 // 1)>(%i0) 610 // 611 // Insert all combinations of upper/lower bound results. 612 for (unsigned i = 0, e = origUbExprs.size(); i < e; ++i) { 613 newUbExprs.push_back( 614 (origUbExprs[i] - origLbExprs[0]).ceilDiv(origLoopStep)); 615 } 616 617 // Construct newUbMap. 618 AffineMap newUbMap = 619 AffineMap::get(origLbMap.getNumDims() + origUbMap.getNumDims(), 620 origLbMap.getNumSymbols() + origUbMap.getNumSymbols(), 621 newUbExprs, opBuilder.getContext()); 622 canonicalizeMapAndOperands(&newUbMap, &ubOperands); 623 624 // Normalize the loop. 625 op.setUpperBound(ubOperands, newUbMap); 626 op.setLowerBound({}, opBuilder.getConstantAffineMap(0)); 627 op.setStep(1); 628 629 // Calculate the Value of new loopIV. Create affine.apply for the value of 630 // the loopIV in normalized loop. 631 opBuilder.setInsertionPointToStart(op.getBody()); 632 SmallVector<Value, 4> lbOperands(lb.getOperands().begin(), 633 lb.getOperands().begin() + 634 lb.getMap().getNumDims()); 635 // Add an extra dim operand for loopIV. 636 lbOperands.push_back(op.getInductionVar()); 637 // Add symbol operands from lower bound. 638 for (unsigned j = 0, e = origLbMap.getNumSymbols(); j < e; ++j) 639 lbOperands.push_back(lb.getOperand(origLbMap.getNumDims() + j)); 640 641 AffineExpr origIVExpr = opBuilder.getAffineDimExpr(lb.getMap().getNumDims()); 642 AffineExpr newIVExpr = origIVExpr * origLoopStep + origLbMap.getResult(0); 643 AffineMap ivMap = AffineMap::get(origLbMap.getNumDims() + 1, 644 origLbMap.getNumSymbols(), newIVExpr); 645 canonicalizeMapAndOperands(&ivMap, &lbOperands); 646 Operation *newIV = opBuilder.create<AffineApplyOp>(loc, ivMap, lbOperands); 647 op.getInductionVar().replaceAllUsesExcept(newIV->getResult(0), newIV); 648 return success(); 649 } 650 651 /// Ensure that all operations that could be executed after `start` 652 /// (noninclusive) and prior to `memOp` (e.g. on a control flow/op path 653 /// between the operations) do not have the potential memory effect 654 /// `EffectType` on `memOp`. `memOp` is an operation that reads or writes to 655 /// a memref. For example, if `EffectType` is MemoryEffects::Write, this method 656 /// will check if there is no write to the memory between `start` and `memOp` 657 /// that would change the read within `memOp`. 658 template <typename EffectType, typename T> 659 static bool hasNoInterveningEffect(Operation *start, T memOp) { 660 Value memref = memOp.getMemRef(); 661 bool isOriginalAllocation = memref.getDefiningOp<memref::AllocaOp>() || 662 memref.getDefiningOp<memref::AllocOp>(); 663 664 // A boolean representing whether an intervening operation could have impacted 665 // memOp. 666 bool hasSideEffect = false; 667 668 // Check whether the effect on memOp can be caused by a given operation op. 669 std::function<void(Operation *)> checkOperation = [&](Operation *op) { 670 // If the effect has alreay been found, early exit, 671 if (hasSideEffect) 672 return; 673 674 if (auto memEffect = dyn_cast<MemoryEffectOpInterface>(op)) { 675 SmallVector<MemoryEffects::EffectInstance, 1> effects; 676 memEffect.getEffects(effects); 677 678 bool opMayHaveEffect = false; 679 for (auto effect : effects) { 680 // If op causes EffectType on a potentially aliasing location for 681 // memOp, mark as having the effect. 682 if (isa<EffectType>(effect.getEffect())) { 683 if (isOriginalAllocation && effect.getValue() && 684 (effect.getValue().getDefiningOp<memref::AllocaOp>() || 685 effect.getValue().getDefiningOp<memref::AllocOp>())) { 686 if (effect.getValue() != memref) 687 continue; 688 } 689 opMayHaveEffect = true; 690 break; 691 } 692 } 693 694 if (!opMayHaveEffect) 695 return; 696 697 // If the side effect comes from an affine read or write, try to 698 // prove the side effecting `op` cannot reach `memOp`. 699 if (isa<AffineReadOpInterface, AffineWriteOpInterface>(op)) { 700 MemRefAccess srcAccess(op); 701 MemRefAccess destAccess(memOp); 702 // Dependence analysis is only correct if both ops operate on the same 703 // memref. 704 if (srcAccess.memref == destAccess.memref) { 705 FlatAffineValueConstraints dependenceConstraints; 706 707 // Number of loops containing the start op and the ending operation. 708 unsigned minSurroundingLoops = 709 getNumCommonSurroundingLoops(*start, *memOp); 710 711 // Number of loops containing the operation `op` which has the 712 // potential memory side effect and can occur on a path between 713 // `start` and `memOp`. 714 unsigned nsLoops = getNumCommonSurroundingLoops(*op, *memOp); 715 716 // For ease, let's consider the case that `op` is a store and we're 717 // looking for other potential stores (e.g `op`) that overwrite memory 718 // after `start`, and before being read in `memOp`. In this case, we 719 // only need to consider other potential stores with depth > 720 // minSurrounding loops since `start` would overwrite any store with a 721 // smaller number of surrounding loops before. 722 unsigned d; 723 for (d = nsLoops + 1; d > minSurroundingLoops; d--) { 724 DependenceResult result = checkMemrefAccessDependence( 725 srcAccess, destAccess, d, &dependenceConstraints, 726 /*dependenceComponents=*/nullptr); 727 if (hasDependence(result)) { 728 hasSideEffect = true; 729 return; 730 } 731 } 732 733 // No side effect was seen, simply return. 734 return; 735 } 736 } 737 hasSideEffect = true; 738 return; 739 } 740 741 if (op->hasTrait<OpTrait::HasRecursiveSideEffects>()) { 742 // Recurse into the regions for this op and check whether the internal 743 // operations may have the side effect `EffectType` on memOp. 744 for (Region ®ion : op->getRegions()) 745 for (Block &block : region) 746 for (Operation &op : block) 747 checkOperation(&op); 748 return; 749 } 750 751 // Otherwise, conservatively assume generic operations have the effect 752 // on the operation 753 hasSideEffect = true; 754 }; 755 756 // Check all paths from ancestor op `parent` to the operation `to` for the 757 // effect. It is known that `to` must be contained within `parent`. 758 auto until = [&](Operation *parent, Operation *to) { 759 // TODO check only the paths from `parent` to `to`. 760 // Currently we fallback and check the entire parent op, rather than 761 // just the paths from the parent path, stopping after reaching `to`. 762 // This is conservatively correct, but could be made more aggressive. 763 assert(parent->isAncestor(to)); 764 checkOperation(parent); 765 }; 766 767 // Check for all paths from operation `from` to operation `untilOp` for the 768 // given memory effect. 769 std::function<void(Operation *, Operation *)> recur = 770 [&](Operation *from, Operation *untilOp) { 771 assert( 772 from->getParentRegion()->isAncestor(untilOp->getParentRegion()) && 773 "Checking for side effect between two operations without a common " 774 "ancestor"); 775 776 // If the operations are in different regions, recursively consider all 777 // path from `from` to the parent of `to` and all paths from the parent 778 // of `to` to `to`. 779 if (from->getParentRegion() != untilOp->getParentRegion()) { 780 recur(from, untilOp->getParentOp()); 781 until(untilOp->getParentOp(), untilOp); 782 return; 783 } 784 785 // Now, assuming that `from` and `to` exist in the same region, perform 786 // a CFG traversal to check all the relevant operations. 787 788 // Additional blocks to consider. 789 SmallVector<Block *, 2> todoBlocks; 790 { 791 // First consider the parent block of `from` an check all operations 792 // after `from`. 793 for (auto iter = ++from->getIterator(), end = from->getBlock()->end(); 794 iter != end && &*iter != untilOp; ++iter) { 795 checkOperation(&*iter); 796 } 797 798 // If the parent of `from` doesn't contain `to`, add the successors 799 // to the list of blocks to check. 800 if (untilOp->getBlock() != from->getBlock()) 801 for (Block *succ : from->getBlock()->getSuccessors()) 802 todoBlocks.push_back(succ); 803 } 804 805 SmallPtrSet<Block *, 4> done; 806 // Traverse the CFG until hitting `to`. 807 while (!todoBlocks.empty()) { 808 Block *blk = todoBlocks.pop_back_val(); 809 if (done.count(blk)) 810 continue; 811 done.insert(blk); 812 for (auto &op : *blk) { 813 if (&op == untilOp) 814 break; 815 checkOperation(&op); 816 if (&op == blk->getTerminator()) 817 for (Block *succ : blk->getSuccessors()) 818 todoBlocks.push_back(succ); 819 } 820 } 821 }; 822 recur(start, memOp); 823 return !hasSideEffect; 824 } 825 826 /// Attempt to eliminate loadOp by replacing it with a value stored into memory 827 /// which the load is guaranteed to retrieve. This check involves three 828 /// components: 1) The store and load must be on the same location 2) The store 829 /// must dominate (and therefore must always occur prior to) the load 3) No 830 /// other operations will overwrite the memory loaded between the given load 831 /// and store. If such a value exists, the replaced `loadOp` will be added to 832 /// `loadOpsToErase` and its memref will be added to `memrefsToErase`. 833 static LogicalResult forwardStoreToLoad( 834 AffineReadOpInterface loadOp, SmallVectorImpl<Operation *> &loadOpsToErase, 835 SmallPtrSetImpl<Value> &memrefsToErase, DominanceInfo &domInfo) { 836 837 // The store op candidate for forwarding that satisfies all conditions 838 // to replace the load, if any. 839 Operation *lastWriteStoreOp = nullptr; 840 841 for (auto *user : loadOp.getMemRef().getUsers()) { 842 auto storeOp = dyn_cast<AffineWriteOpInterface>(user); 843 if (!storeOp) 844 continue; 845 MemRefAccess srcAccess(storeOp); 846 MemRefAccess destAccess(loadOp); 847 848 // 1. Check if the store and the load have mathematically equivalent 849 // affine access functions; this implies that they statically refer to the 850 // same single memref element. As an example this filters out cases like: 851 // store %A[%i0 + 1] 852 // load %A[%i0] 853 // store %A[%M] 854 // load %A[%N] 855 // Use the AffineValueMap difference based memref access equality checking. 856 if (srcAccess != destAccess) 857 continue; 858 859 // 2. The store has to dominate the load op to be candidate. 860 if (!domInfo.dominates(storeOp, loadOp)) 861 continue; 862 863 // 3. Ensure there is no intermediate operation which could replace the 864 // value in memory. 865 if (!hasNoInterveningEffect<MemoryEffects::Write>(storeOp, loadOp)) 866 continue; 867 868 // We now have a candidate for forwarding. 869 assert(lastWriteStoreOp == nullptr && 870 "multiple simulataneous replacement stores"); 871 lastWriteStoreOp = storeOp; 872 } 873 874 if (!lastWriteStoreOp) 875 return failure(); 876 877 // Perform the actual store to load forwarding. 878 Value storeVal = 879 cast<AffineWriteOpInterface>(lastWriteStoreOp).getValueToStore(); 880 // Check if 2 values have the same shape. This is needed for affine vector 881 // loads and stores. 882 if (storeVal.getType() != loadOp.getValue().getType()) 883 return failure(); 884 loadOp.getValue().replaceAllUsesWith(storeVal); 885 // Record the memref for a later sweep to optimize away. 886 memrefsToErase.insert(loadOp.getMemRef()); 887 // Record this to erase later. 888 loadOpsToErase.push_back(loadOp); 889 return success(); 890 } 891 892 // This attempts to find stores which have no impact on the final result. 893 // A writing op writeA will be eliminated if there exists an op writeB if 894 // 1) writeA and writeB have mathematically equivalent affine access functions. 895 // 2) writeB postdominates writeA. 896 // 3) There is no potential read between writeA and writeB. 897 static void findUnusedStore(AffineWriteOpInterface writeA, 898 SmallVectorImpl<Operation *> &opsToErase, 899 PostDominanceInfo &postDominanceInfo) { 900 901 for (Operation *user : writeA.getMemRef().getUsers()) { 902 // Only consider writing operations. 903 auto writeB = dyn_cast<AffineWriteOpInterface>(user); 904 if (!writeB) 905 continue; 906 907 // The operations must be distinct. 908 if (writeB == writeA) 909 continue; 910 911 // Both operations must lie in the same region. 912 if (writeB->getParentRegion() != writeA->getParentRegion()) 913 continue; 914 915 // Both operations must write to the same memory. 916 MemRefAccess srcAccess(writeB); 917 MemRefAccess destAccess(writeA); 918 919 if (srcAccess != destAccess) 920 continue; 921 922 // writeB must postdominate writeA. 923 if (!postDominanceInfo.postDominates(writeB, writeA)) 924 continue; 925 926 // There cannot be an operation which reads from memory between 927 // the two writes. 928 if (!hasNoInterveningEffect<MemoryEffects::Read>(writeA, writeB)) 929 continue; 930 931 opsToErase.push_back(writeA); 932 break; 933 } 934 } 935 936 // The load to load forwarding / redundant load elimination is similar to the 937 // store to load forwarding. 938 // loadA will be be replaced with loadB if: 939 // 1) loadA and loadB have mathematically equivalent affine access functions. 940 // 2) loadB dominates loadA. 941 // 3) There is no write between loadA and loadB. 942 static void loadCSE(AffineReadOpInterface loadA, 943 SmallVectorImpl<Operation *> &loadOpsToErase, 944 DominanceInfo &domInfo) { 945 SmallVector<AffineReadOpInterface, 4> loadCandidates; 946 for (auto *user : loadA.getMemRef().getUsers()) { 947 auto loadB = dyn_cast<AffineReadOpInterface>(user); 948 if (!loadB || loadB == loadA) 949 continue; 950 951 MemRefAccess srcAccess(loadB); 952 MemRefAccess destAccess(loadA); 953 954 // 1. The accesses have to be to the same location. 955 if (srcAccess != destAccess) { 956 continue; 957 } 958 959 // 2. The store has to dominate the load op to be candidate. 960 if (!domInfo.dominates(loadB, loadA)) 961 continue; 962 963 // 3. There is no write between loadA and loadB. 964 if (!hasNoInterveningEffect<MemoryEffects::Write>(loadB.getOperation(), 965 loadA)) 966 continue; 967 968 // Check if two values have the same shape. This is needed for affine vector 969 // loads. 970 if (loadB.getValue().getType() != loadA.getValue().getType()) 971 continue; 972 973 loadCandidates.push_back(loadB); 974 } 975 976 // Of the legal load candidates, use the one that dominates all others 977 // to minimize the subsequent need to loadCSE 978 Value loadB; 979 for (AffineReadOpInterface option : loadCandidates) { 980 if (llvm::all_of(loadCandidates, [&](AffineReadOpInterface depStore) { 981 return depStore == option || 982 domInfo.dominates(option.getOperation(), 983 depStore.getOperation()); 984 })) { 985 loadB = option.getValue(); 986 break; 987 } 988 } 989 990 if (loadB) { 991 loadA.getValue().replaceAllUsesWith(loadB); 992 // Record this to erase later. 993 loadOpsToErase.push_back(loadA); 994 } 995 } 996 997 // The store to load forwarding and load CSE rely on three conditions: 998 // 999 // 1) store/load providing a replacement value and load being replaced need to 1000 // have mathematically equivalent affine access functions (checked after full 1001 // composition of load/store operands); this implies that they access the same 1002 // single memref element for all iterations of the common surrounding loop, 1003 // 1004 // 2) the store/load op should dominate the load op, 1005 // 1006 // 3) no operation that may write to memory read by the load being replaced can 1007 // occur after executing the instruction (load or store) providing the 1008 // replacement value and before the load being replaced (thus potentially 1009 // allowing overwriting the memory read by the load). 1010 // 1011 // The above conditions are simple to check, sufficient, and powerful for most 1012 // cases in practice - they are sufficient, but not necessary --- since they 1013 // don't reason about loops that are guaranteed to execute at least once or 1014 // multiple sources to forward from. 1015 // 1016 // TODO: more forwarding can be done when support for 1017 // loop/conditional live-out SSA values is available. 1018 // TODO: do general dead store elimination for memref's. This pass 1019 // currently only eliminates the stores only if no other loads/uses (other 1020 // than dealloc) remain. 1021 // 1022 void mlir::affineScalarReplace(FuncOp f, DominanceInfo &domInfo, 1023 PostDominanceInfo &postDomInfo) { 1024 // Load op's whose results were replaced by those forwarded from stores. 1025 SmallVector<Operation *, 8> opsToErase; 1026 1027 // A list of memref's that are potentially dead / could be eliminated. 1028 SmallPtrSet<Value, 4> memrefsToErase; 1029 1030 // Walk all load's and perform store to load forwarding. 1031 f.walk([&](AffineReadOpInterface loadOp) { 1032 if (failed( 1033 forwardStoreToLoad(loadOp, opsToErase, memrefsToErase, domInfo))) { 1034 loadCSE(loadOp, opsToErase, domInfo); 1035 } 1036 }); 1037 1038 // Erase all load op's whose results were replaced with store fwd'ed ones. 1039 for (auto *op : opsToErase) 1040 op->erase(); 1041 opsToErase.clear(); 1042 1043 // Walk all store's and perform unused store elimination 1044 f.walk([&](AffineWriteOpInterface storeOp) { 1045 findUnusedStore(storeOp, opsToErase, postDomInfo); 1046 }); 1047 // Erase all store op's which don't impact the program 1048 for (auto *op : opsToErase) 1049 op->erase(); 1050 1051 // Check if the store fwd'ed memrefs are now left with only stores and can 1052 // thus be completely deleted. Note: the canonicalize pass should be able 1053 // to do this as well, but we'll do it here since we collected these anyway. 1054 for (auto memref : memrefsToErase) { 1055 // If the memref hasn't been alloc'ed in this function, skip. 1056 Operation *defOp = memref.getDefiningOp(); 1057 if (!defOp || !isa<memref::AllocOp>(defOp)) 1058 // TODO: if the memref was returned by a 'call' operation, we 1059 // could still erase it if the call had no side-effects. 1060 continue; 1061 if (llvm::any_of(memref.getUsers(), [&](Operation *ownerOp) { 1062 return !isa<AffineWriteOpInterface, memref::DeallocOp>(ownerOp); 1063 })) 1064 continue; 1065 1066 // Erase all stores, the dealloc, and the alloc on the memref. 1067 for (auto *user : llvm::make_early_inc_range(memref.getUsers())) 1068 user->erase(); 1069 defOp->erase(); 1070 } 1071 } 1072 1073 // Perform the replacement in `op`. 1074 LogicalResult mlir::replaceAllMemRefUsesWith(Value oldMemRef, Value newMemRef, 1075 Operation *op, 1076 ArrayRef<Value> extraIndices, 1077 AffineMap indexRemap, 1078 ArrayRef<Value> extraOperands, 1079 ArrayRef<Value> symbolOperands, 1080 bool allowNonDereferencingOps) { 1081 unsigned newMemRefRank = newMemRef.getType().cast<MemRefType>().getRank(); 1082 (void)newMemRefRank; // unused in opt mode 1083 unsigned oldMemRefRank = oldMemRef.getType().cast<MemRefType>().getRank(); 1084 (void)oldMemRefRank; // unused in opt mode 1085 if (indexRemap) { 1086 assert(indexRemap.getNumSymbols() == symbolOperands.size() && 1087 "symbolic operand count mismatch"); 1088 assert(indexRemap.getNumInputs() == 1089 extraOperands.size() + oldMemRefRank + symbolOperands.size()); 1090 assert(indexRemap.getNumResults() + extraIndices.size() == newMemRefRank); 1091 } else { 1092 assert(oldMemRefRank + extraIndices.size() == newMemRefRank); 1093 } 1094 1095 // Assert same elemental type. 1096 assert(oldMemRef.getType().cast<MemRefType>().getElementType() == 1097 newMemRef.getType().cast<MemRefType>().getElementType()); 1098 1099 SmallVector<unsigned, 2> usePositions; 1100 for (const auto &opEntry : llvm::enumerate(op->getOperands())) { 1101 if (opEntry.value() == oldMemRef) 1102 usePositions.push_back(opEntry.index()); 1103 } 1104 1105 // If memref doesn't appear, nothing to do. 1106 if (usePositions.empty()) 1107 return success(); 1108 1109 if (usePositions.size() > 1) { 1110 // TODO: extend it for this case when needed (rare). 1111 assert(false && "multiple dereferencing uses in a single op not supported"); 1112 return failure(); 1113 } 1114 1115 unsigned memRefOperandPos = usePositions.front(); 1116 1117 OpBuilder builder(op); 1118 // The following checks if op is dereferencing memref and performs the access 1119 // index rewrites. 1120 auto affMapAccInterface = dyn_cast<AffineMapAccessInterface>(op); 1121 if (!affMapAccInterface) { 1122 if (!allowNonDereferencingOps) { 1123 // Failure: memref used in a non-dereferencing context (potentially 1124 // escapes); no replacement in these cases unless allowNonDereferencingOps 1125 // is set. 1126 return failure(); 1127 } 1128 op->setOperand(memRefOperandPos, newMemRef); 1129 return success(); 1130 } 1131 // Perform index rewrites for the dereferencing op and then replace the op 1132 NamedAttribute oldMapAttrPair = 1133 affMapAccInterface.getAffineMapAttrForMemRef(oldMemRef); 1134 AffineMap oldMap = oldMapAttrPair.getValue().cast<AffineMapAttr>().getValue(); 1135 unsigned oldMapNumInputs = oldMap.getNumInputs(); 1136 SmallVector<Value, 4> oldMapOperands( 1137 op->operand_begin() + memRefOperandPos + 1, 1138 op->operand_begin() + memRefOperandPos + 1 + oldMapNumInputs); 1139 1140 // Apply 'oldMemRefOperands = oldMap(oldMapOperands)'. 1141 SmallVector<Value, 4> oldMemRefOperands; 1142 SmallVector<Value, 4> affineApplyOps; 1143 oldMemRefOperands.reserve(oldMemRefRank); 1144 if (oldMap != builder.getMultiDimIdentityMap(oldMap.getNumDims())) { 1145 for (auto resultExpr : oldMap.getResults()) { 1146 auto singleResMap = AffineMap::get(oldMap.getNumDims(), 1147 oldMap.getNumSymbols(), resultExpr); 1148 auto afOp = builder.create<AffineApplyOp>(op->getLoc(), singleResMap, 1149 oldMapOperands); 1150 oldMemRefOperands.push_back(afOp); 1151 affineApplyOps.push_back(afOp); 1152 } 1153 } else { 1154 oldMemRefOperands.assign(oldMapOperands.begin(), oldMapOperands.end()); 1155 } 1156 1157 // Construct new indices as a remap of the old ones if a remapping has been 1158 // provided. The indices of a memref come right after it, i.e., 1159 // at position memRefOperandPos + 1. 1160 SmallVector<Value, 4> remapOperands; 1161 remapOperands.reserve(extraOperands.size() + oldMemRefRank + 1162 symbolOperands.size()); 1163 remapOperands.append(extraOperands.begin(), extraOperands.end()); 1164 remapOperands.append(oldMemRefOperands.begin(), oldMemRefOperands.end()); 1165 remapOperands.append(symbolOperands.begin(), symbolOperands.end()); 1166 1167 SmallVector<Value, 4> remapOutputs; 1168 remapOutputs.reserve(oldMemRefRank); 1169 1170 if (indexRemap && 1171 indexRemap != builder.getMultiDimIdentityMap(indexRemap.getNumDims())) { 1172 // Remapped indices. 1173 for (auto resultExpr : indexRemap.getResults()) { 1174 auto singleResMap = AffineMap::get( 1175 indexRemap.getNumDims(), indexRemap.getNumSymbols(), resultExpr); 1176 auto afOp = builder.create<AffineApplyOp>(op->getLoc(), singleResMap, 1177 remapOperands); 1178 remapOutputs.push_back(afOp); 1179 affineApplyOps.push_back(afOp); 1180 } 1181 } else { 1182 // No remapping specified. 1183 remapOutputs.assign(remapOperands.begin(), remapOperands.end()); 1184 } 1185 1186 SmallVector<Value, 4> newMapOperands; 1187 newMapOperands.reserve(newMemRefRank); 1188 1189 // Prepend 'extraIndices' in 'newMapOperands'. 1190 for (Value extraIndex : extraIndices) { 1191 assert(extraIndex.getDefiningOp()->getNumResults() == 1 && 1192 "single result op's expected to generate these indices"); 1193 assert((isValidDim(extraIndex) || isValidSymbol(extraIndex)) && 1194 "invalid memory op index"); 1195 newMapOperands.push_back(extraIndex); 1196 } 1197 1198 // Append 'remapOutputs' to 'newMapOperands'. 1199 newMapOperands.append(remapOutputs.begin(), remapOutputs.end()); 1200 1201 // Create new fully composed AffineMap for new op to be created. 1202 assert(newMapOperands.size() == newMemRefRank); 1203 auto newMap = builder.getMultiDimIdentityMap(newMemRefRank); 1204 // TODO: Avoid creating/deleting temporary AffineApplyOps here. 1205 fullyComposeAffineMapAndOperands(&newMap, &newMapOperands); 1206 newMap = simplifyAffineMap(newMap); 1207 canonicalizeMapAndOperands(&newMap, &newMapOperands); 1208 // Remove any affine.apply's that became dead as a result of composition. 1209 for (Value value : affineApplyOps) 1210 if (value.use_empty()) 1211 value.getDefiningOp()->erase(); 1212 1213 OperationState state(op->getLoc(), op->getName()); 1214 // Construct the new operation using this memref. 1215 state.operands.reserve(op->getNumOperands() + extraIndices.size()); 1216 // Insert the non-memref operands. 1217 state.operands.append(op->operand_begin(), 1218 op->operand_begin() + memRefOperandPos); 1219 // Insert the new memref value. 1220 state.operands.push_back(newMemRef); 1221 1222 // Insert the new memref map operands. 1223 state.operands.append(newMapOperands.begin(), newMapOperands.end()); 1224 1225 // Insert the remaining operands unmodified. 1226 state.operands.append(op->operand_begin() + memRefOperandPos + 1 + 1227 oldMapNumInputs, 1228 op->operand_end()); 1229 1230 // Result types don't change. Both memref's are of the same elemental type. 1231 state.types.reserve(op->getNumResults()); 1232 for (auto result : op->getResults()) 1233 state.types.push_back(result.getType()); 1234 1235 // Add attribute for 'newMap', other Attributes do not change. 1236 auto newMapAttr = AffineMapAttr::get(newMap); 1237 for (auto namedAttr : op->getAttrs()) { 1238 if (namedAttr.getName() == oldMapAttrPair.getName()) 1239 state.attributes.push_back({namedAttr.getName(), newMapAttr}); 1240 else 1241 state.attributes.push_back(namedAttr); 1242 } 1243 1244 // Create the new operation. 1245 auto *repOp = builder.create(state); 1246 op->replaceAllUsesWith(repOp); 1247 op->erase(); 1248 1249 return success(); 1250 } 1251 1252 LogicalResult mlir::replaceAllMemRefUsesWith( 1253 Value oldMemRef, Value newMemRef, ArrayRef<Value> extraIndices, 1254 AffineMap indexRemap, ArrayRef<Value> extraOperands, 1255 ArrayRef<Value> symbolOperands, Operation *domOpFilter, 1256 Operation *postDomOpFilter, bool allowNonDereferencingOps, 1257 bool replaceInDeallocOp) { 1258 unsigned newMemRefRank = newMemRef.getType().cast<MemRefType>().getRank(); 1259 (void)newMemRefRank; // unused in opt mode 1260 unsigned oldMemRefRank = oldMemRef.getType().cast<MemRefType>().getRank(); 1261 (void)oldMemRefRank; 1262 if (indexRemap) { 1263 assert(indexRemap.getNumSymbols() == symbolOperands.size() && 1264 "symbol operand count mismatch"); 1265 assert(indexRemap.getNumInputs() == 1266 extraOperands.size() + oldMemRefRank + symbolOperands.size()); 1267 assert(indexRemap.getNumResults() + extraIndices.size() == newMemRefRank); 1268 } else { 1269 assert(oldMemRefRank + extraIndices.size() == newMemRefRank); 1270 } 1271 1272 // Assert same elemental type. 1273 assert(oldMemRef.getType().cast<MemRefType>().getElementType() == 1274 newMemRef.getType().cast<MemRefType>().getElementType()); 1275 1276 std::unique_ptr<DominanceInfo> domInfo; 1277 std::unique_ptr<PostDominanceInfo> postDomInfo; 1278 if (domOpFilter) 1279 domInfo = 1280 std::make_unique<DominanceInfo>(domOpFilter->getParentOfType<FuncOp>()); 1281 1282 if (postDomOpFilter) 1283 postDomInfo = std::make_unique<PostDominanceInfo>( 1284 postDomOpFilter->getParentOfType<FuncOp>()); 1285 1286 // Walk all uses of old memref; collect ops to perform replacement. We use a 1287 // DenseSet since an operation could potentially have multiple uses of a 1288 // memref (although rare), and the replacement later is going to erase ops. 1289 DenseSet<Operation *> opsToReplace; 1290 for (auto *op : oldMemRef.getUsers()) { 1291 // Skip this use if it's not dominated by domOpFilter. 1292 if (domOpFilter && !domInfo->dominates(domOpFilter, op)) 1293 continue; 1294 1295 // Skip this use if it's not post-dominated by postDomOpFilter. 1296 if (postDomOpFilter && !postDomInfo->postDominates(postDomOpFilter, op)) 1297 continue; 1298 1299 // Skip dealloc's - no replacement is necessary, and a memref replacement 1300 // at other uses doesn't hurt these dealloc's. 1301 if (isa<memref::DeallocOp>(op) && !replaceInDeallocOp) 1302 continue; 1303 1304 // Check if the memref was used in a non-dereferencing context. It is fine 1305 // for the memref to be used in a non-dereferencing way outside of the 1306 // region where this replacement is happening. 1307 if (!isa<AffineMapAccessInterface>(*op)) { 1308 if (!allowNonDereferencingOps) { 1309 LLVM_DEBUG(llvm::dbgs() 1310 << "Memref replacement failed: non-deferencing memref op: \n" 1311 << *op << '\n'); 1312 return failure(); 1313 } 1314 // Non-dereferencing ops with the MemRefsNormalizable trait are 1315 // supported for replacement. 1316 if (!op->hasTrait<OpTrait::MemRefsNormalizable>()) { 1317 LLVM_DEBUG(llvm::dbgs() << "Memref replacement failed: use without a " 1318 "memrefs normalizable trait: \n" 1319 << *op << '\n'); 1320 return failure(); 1321 } 1322 } 1323 1324 // We'll first collect and then replace --- since replacement erases the op 1325 // that has the use, and that op could be postDomFilter or domFilter itself! 1326 opsToReplace.insert(op); 1327 } 1328 1329 for (auto *op : opsToReplace) { 1330 if (failed(replaceAllMemRefUsesWith( 1331 oldMemRef, newMemRef, op, extraIndices, indexRemap, extraOperands, 1332 symbolOperands, allowNonDereferencingOps))) 1333 llvm_unreachable("memref replacement guaranteed to succeed here"); 1334 } 1335 1336 return success(); 1337 } 1338 1339 /// Given an operation, inserts one or more single result affine 1340 /// apply operations, results of which are exclusively used by this operation 1341 /// operation. The operands of these newly created affine apply ops are 1342 /// guaranteed to be loop iterators or terminal symbols of a function. 1343 /// 1344 /// Before 1345 /// 1346 /// affine.for %i = 0 to #map(%N) 1347 /// %idx = affine.apply (d0) -> (d0 mod 2) (%i) 1348 /// "send"(%idx, %A, ...) 1349 /// "compute"(%idx) 1350 /// 1351 /// After 1352 /// 1353 /// affine.for %i = 0 to #map(%N) 1354 /// %idx = affine.apply (d0) -> (d0 mod 2) (%i) 1355 /// "send"(%idx, %A, ...) 1356 /// %idx_ = affine.apply (d0) -> (d0 mod 2) (%i) 1357 /// "compute"(%idx_) 1358 /// 1359 /// This allows applying different transformations on send and compute (for eg. 1360 /// different shifts/delays). 1361 /// 1362 /// Returns nullptr either if none of opInst's operands were the result of an 1363 /// affine.apply and thus there was no affine computation slice to create, or if 1364 /// all the affine.apply op's supplying operands to this opInst did not have any 1365 /// uses besides this opInst; otherwise returns the list of affine.apply 1366 /// operations created in output argument `sliceOps`. 1367 void mlir::createAffineComputationSlice( 1368 Operation *opInst, SmallVectorImpl<AffineApplyOp> *sliceOps) { 1369 // Collect all operands that are results of affine apply ops. 1370 SmallVector<Value, 4> subOperands; 1371 subOperands.reserve(opInst->getNumOperands()); 1372 for (auto operand : opInst->getOperands()) 1373 if (isa_and_nonnull<AffineApplyOp>(operand.getDefiningOp())) 1374 subOperands.push_back(operand); 1375 1376 // Gather sequence of AffineApplyOps reachable from 'subOperands'. 1377 SmallVector<Operation *, 4> affineApplyOps; 1378 getReachableAffineApplyOps(subOperands, affineApplyOps); 1379 // Skip transforming if there are no affine maps to compose. 1380 if (affineApplyOps.empty()) 1381 return; 1382 1383 // Check if all uses of the affine apply op's lie only in this op op, in 1384 // which case there would be nothing to do. 1385 bool localized = true; 1386 for (auto *op : affineApplyOps) { 1387 for (auto result : op->getResults()) { 1388 for (auto *user : result.getUsers()) { 1389 if (user != opInst) { 1390 localized = false; 1391 break; 1392 } 1393 } 1394 } 1395 } 1396 if (localized) 1397 return; 1398 1399 OpBuilder builder(opInst); 1400 SmallVector<Value, 4> composedOpOperands(subOperands); 1401 auto composedMap = builder.getMultiDimIdentityMap(composedOpOperands.size()); 1402 fullyComposeAffineMapAndOperands(&composedMap, &composedOpOperands); 1403 1404 // Create an affine.apply for each of the map results. 1405 sliceOps->reserve(composedMap.getNumResults()); 1406 for (auto resultExpr : composedMap.getResults()) { 1407 auto singleResMap = AffineMap::get(composedMap.getNumDims(), 1408 composedMap.getNumSymbols(), resultExpr); 1409 sliceOps->push_back(builder.create<AffineApplyOp>( 1410 opInst->getLoc(), singleResMap, composedOpOperands)); 1411 } 1412 1413 // Construct the new operands that include the results from the composed 1414 // affine apply op above instead of existing ones (subOperands). So, they 1415 // differ from opInst's operands only for those operands in 'subOperands', for 1416 // which they will be replaced by the corresponding one from 'sliceOps'. 1417 SmallVector<Value, 4> newOperands(opInst->getOperands()); 1418 for (unsigned i = 0, e = newOperands.size(); i < e; i++) { 1419 // Replace the subOperands from among the new operands. 1420 unsigned j, f; 1421 for (j = 0, f = subOperands.size(); j < f; j++) { 1422 if (newOperands[i] == subOperands[j]) 1423 break; 1424 } 1425 if (j < subOperands.size()) { 1426 newOperands[i] = (*sliceOps)[j]; 1427 } 1428 } 1429 for (unsigned idx = 0, e = newOperands.size(); idx < e; idx++) { 1430 opInst->setOperand(idx, newOperands[idx]); 1431 } 1432 } 1433 1434 /// Enum to set patterns of affine expr in tiled-layout map. 1435 /// TileFloorDiv: <dim expr> div <tile size> 1436 /// TileMod: <dim expr> mod <tile size> 1437 /// TileNone: None of the above 1438 /// Example: 1439 /// #tiled_2d_128x256 = affine_map<(d0, d1) 1440 /// -> (d0 div 128, d1 div 256, d0 mod 128, d1 mod 256)> 1441 /// "d0 div 128" and "d1 div 256" ==> TileFloorDiv 1442 /// "d0 mod 128" and "d1 mod 256" ==> TileMod 1443 enum TileExprPattern { TileFloorDiv, TileMod, TileNone }; 1444 1445 /// Check if `map` is a tiled layout. In the tiled layout, specific k dimensions 1446 /// being floordiv'ed by respective tile sizes appeare in a mod with the same 1447 /// tile sizes, and no other expression involves those k dimensions. This 1448 /// function stores a vector of tuples (`tileSizePos`) including AffineExpr for 1449 /// tile size, positions of corresponding `floordiv` and `mod`. If it is not a 1450 /// tiled layout, an empty vector is returned. 1451 static LogicalResult getTileSizePos( 1452 AffineMap map, 1453 SmallVectorImpl<std::tuple<AffineExpr, unsigned, unsigned>> &tileSizePos) { 1454 // Create `floordivExprs` which is a vector of tuples including LHS and RHS of 1455 // `floordiv` and its position in `map` output. 1456 // Example: #tiled_2d_128x256 = affine_map<(d0, d1) 1457 // -> (d0 div 128, d1 div 256, d0 mod 128, d1 mod 256)> 1458 // In this example, `floordivExprs` includes {d0, 128, 0} and {d1, 256, 1}. 1459 SmallVector<std::tuple<AffineExpr, AffineExpr, unsigned>, 4> floordivExprs; 1460 unsigned pos = 0; 1461 for (AffineExpr expr : map.getResults()) { 1462 if (expr.getKind() == AffineExprKind::FloorDiv) { 1463 AffineBinaryOpExpr binaryExpr = expr.cast<AffineBinaryOpExpr>(); 1464 if (binaryExpr.getRHS().isa<AffineConstantExpr>()) 1465 floordivExprs.emplace_back( 1466 std::make_tuple(binaryExpr.getLHS(), binaryExpr.getRHS(), pos)); 1467 } 1468 pos++; 1469 } 1470 // Not tiled layout if `floordivExprs` is empty. 1471 if (floordivExprs.empty()) { 1472 tileSizePos = SmallVector<std::tuple<AffineExpr, unsigned, unsigned>>{}; 1473 return success(); 1474 } 1475 1476 // Check if LHS of `floordiv` is used in LHS of `mod`. If not used, `map` is 1477 // not tiled layout. 1478 for (std::tuple<AffineExpr, AffineExpr, unsigned> fexpr : floordivExprs) { 1479 AffineExpr floordivExprLHS = std::get<0>(fexpr); 1480 AffineExpr floordivExprRHS = std::get<1>(fexpr); 1481 unsigned floordivPos = std::get<2>(fexpr); 1482 1483 // Walk affinexpr of `map` output except `fexpr`, and check if LHS and RHS 1484 // of `fexpr` are used in LHS and RHS of `mod`. If LHS of `fexpr` is used 1485 // other expr, the map is not tiled layout. Example of non tiled layout: 1486 // affine_map<(d0, d1, d2) -> (d0, d1, d2 floordiv 256, d2 floordiv 256)> 1487 // affine_map<(d0, d1, d2) -> (d0, d1, d2 floordiv 256, d2 mod 128)> 1488 // affine_map<(d0, d1, d2) -> (d0, d1, d2 floordiv 256, d2 mod 256, d2 mod 1489 // 256)> 1490 bool found = false; 1491 pos = 0; 1492 for (AffineExpr expr : map.getResults()) { 1493 bool notTiled = false; 1494 if (pos != floordivPos) { 1495 expr.walk([&](AffineExpr e) { 1496 if (e == floordivExprLHS) { 1497 if (expr.getKind() == AffineExprKind::Mod) { 1498 AffineBinaryOpExpr binaryExpr = expr.cast<AffineBinaryOpExpr>(); 1499 // If LHS and RHS of `mod` are the same with those of floordiv. 1500 if (floordivExprLHS == binaryExpr.getLHS() && 1501 floordivExprRHS == binaryExpr.getRHS()) { 1502 // Save tile size (RHS of `mod`), and position of `floordiv` and 1503 // `mod` if same expr with `mod` is not found yet. 1504 if (!found) { 1505 tileSizePos.emplace_back( 1506 std::make_tuple(binaryExpr.getRHS(), floordivPos, pos)); 1507 found = true; 1508 } else { 1509 // Non tiled layout: Have multilpe `mod` with the same LHS. 1510 // eg. affine_map<(d0, d1, d2) -> (d0, d1, d2 floordiv 256, d2 1511 // mod 256, d2 mod 256)> 1512 notTiled = true; 1513 } 1514 } else { 1515 // Non tiled layout: RHS of `mod` is different from `floordiv`. 1516 // eg. affine_map<(d0, d1, d2) -> (d0, d1, d2 floordiv 256, d2 1517 // mod 128)> 1518 notTiled = true; 1519 } 1520 } else { 1521 // Non tiled layout: LHS is the same, but not `mod`. 1522 // eg. affine_map<(d0, d1, d2) -> (d0, d1, d2 floordiv 256, d2 1523 // floordiv 256)> 1524 notTiled = true; 1525 } 1526 } 1527 }); 1528 } 1529 if (notTiled) { 1530 tileSizePos = SmallVector<std::tuple<AffineExpr, unsigned, unsigned>>{}; 1531 return success(); 1532 } 1533 pos++; 1534 } 1535 } 1536 return success(); 1537 } 1538 1539 /// Check if `dim` dimension of memrefType with `layoutMap` becomes dynamic 1540 /// after normalization. Dimensions that include dynamic dimensions in the map 1541 /// output will become dynamic dimensions. Return true if `dim` is dynamic 1542 /// dimension. 1543 /// 1544 /// Example: 1545 /// #map0 = affine_map<(d0, d1) -> (d0, d1 floordiv 32, d1 mod 32)> 1546 /// 1547 /// If d1 is dynamic dimension, 2nd and 3rd dimension of map output are dynamic. 1548 /// memref<4x?xf32, #map0> ==> memref<4x?x?xf32> 1549 static bool 1550 isNormalizedMemRefDynamicDim(unsigned dim, AffineMap layoutMap, 1551 SmallVectorImpl<unsigned> &inMemrefTypeDynDims, 1552 MLIRContext *context) { 1553 bool isDynamicDim = false; 1554 AffineExpr expr = layoutMap.getResults()[dim]; 1555 // Check if affine expr of the dimension includes dynamic dimension of input 1556 // memrefType. 1557 expr.walk([&inMemrefTypeDynDims, &isDynamicDim, &context](AffineExpr e) { 1558 if (e.isa<AffineDimExpr>()) { 1559 for (unsigned dm : inMemrefTypeDynDims) { 1560 if (e == getAffineDimExpr(dm, context)) { 1561 isDynamicDim = true; 1562 } 1563 } 1564 } 1565 }); 1566 return isDynamicDim; 1567 } 1568 1569 /// Create affine expr to calculate dimension size for a tiled-layout map. 1570 static AffineExpr createDimSizeExprForTiledLayout(AffineExpr oldMapOutput, 1571 TileExprPattern pat) { 1572 // Create map output for the patterns. 1573 // "floordiv <tile size>" ==> "ceildiv <tile size>" 1574 // "mod <tile size>" ==> "<tile size>" 1575 AffineExpr newMapOutput; 1576 AffineBinaryOpExpr binaryExpr = nullptr; 1577 switch (pat) { 1578 case TileExprPattern::TileMod: 1579 binaryExpr = oldMapOutput.cast<AffineBinaryOpExpr>(); 1580 newMapOutput = binaryExpr.getRHS(); 1581 break; 1582 case TileExprPattern::TileFloorDiv: 1583 binaryExpr = oldMapOutput.cast<AffineBinaryOpExpr>(); 1584 newMapOutput = getAffineBinaryOpExpr( 1585 AffineExprKind::CeilDiv, binaryExpr.getLHS(), binaryExpr.getRHS()); 1586 break; 1587 default: 1588 newMapOutput = oldMapOutput; 1589 } 1590 return newMapOutput; 1591 } 1592 1593 /// Create new maps to calculate each dimension size of `newMemRefType`, and 1594 /// create `newDynamicSizes` from them by using AffineApplyOp. 1595 /// 1596 /// Steps for normalizing dynamic memrefs for a tiled layout map 1597 /// Example: 1598 /// #map0 = affine_map<(d0, d1) -> (d0, d1 floordiv 32, d1 mod 32)> 1599 /// %0 = dim %arg0, %c1 :memref<4x?xf32> 1600 /// %1 = alloc(%0) : memref<4x?xf32, #map0> 1601 /// 1602 /// (Before this function) 1603 /// 1. Check if `map`(#map0) is a tiled layout using `getTileSizePos()`. Only 1604 /// single layout map is supported. 1605 /// 1606 /// 2. Create normalized memrefType using `isNormalizedMemRefDynamicDim()`. It 1607 /// is memref<4x?x?xf32> in the above example. 1608 /// 1609 /// (In this function) 1610 /// 3. Create new maps to calculate each dimension of the normalized memrefType 1611 /// using `createDimSizeExprForTiledLayout()`. In the tiled layout, the 1612 /// dimension size can be calculated by replacing "floordiv <tile size>" with 1613 /// "ceildiv <tile size>" and "mod <tile size>" with "<tile size>". 1614 /// - New map in the above example 1615 /// #map0 = affine_map<(d0, d1) -> (d0)> 1616 /// #map1 = affine_map<(d0, d1) -> (d1 ceildiv 32)> 1617 /// #map2 = affine_map<(d0, d1) -> (32)> 1618 /// 1619 /// 4. Create AffineApplyOp to apply the new maps. The output of AffineApplyOp 1620 /// is used in dynamicSizes of new AllocOp. 1621 /// %0 = dim %arg0, %c1 : memref<4x?xf32> 1622 /// %c4 = arith.constant 4 : index 1623 /// %1 = affine.apply #map1(%c4, %0) 1624 /// %2 = affine.apply #map2(%c4, %0) 1625 static void createNewDynamicSizes(MemRefType oldMemRefType, 1626 MemRefType newMemRefType, AffineMap map, 1627 memref::AllocOp *allocOp, OpBuilder b, 1628 SmallVectorImpl<Value> &newDynamicSizes) { 1629 // Create new input for AffineApplyOp. 1630 SmallVector<Value, 4> inAffineApply; 1631 ArrayRef<int64_t> oldMemRefShape = oldMemRefType.getShape(); 1632 unsigned dynIdx = 0; 1633 for (unsigned d = 0; d < oldMemRefType.getRank(); ++d) { 1634 if (oldMemRefShape[d] < 0) { 1635 // Use dynamicSizes of allocOp for dynamic dimension. 1636 inAffineApply.emplace_back(allocOp->dynamicSizes()[dynIdx]); 1637 dynIdx++; 1638 } else { 1639 // Create ConstantOp for static dimension. 1640 Attribute constantAttr = 1641 b.getIntegerAttr(b.getIndexType(), oldMemRefShape[d]); 1642 inAffineApply.emplace_back( 1643 b.create<arith::ConstantOp>(allocOp->getLoc(), constantAttr)); 1644 } 1645 } 1646 1647 // Create new map to calculate each dimension size of new memref for each 1648 // original map output. Only for dynamic dimesion of `newMemRefType`. 1649 unsigned newDimIdx = 0; 1650 ArrayRef<int64_t> newMemRefShape = newMemRefType.getShape(); 1651 SmallVector<std::tuple<AffineExpr, unsigned, unsigned>> tileSizePos; 1652 (void)getTileSizePos(map, tileSizePos); 1653 for (AffineExpr expr : map.getResults()) { 1654 if (newMemRefShape[newDimIdx] < 0) { 1655 // Create new maps to calculate each dimension size of new memref. 1656 enum TileExprPattern pat = TileExprPattern::TileNone; 1657 for (auto pos : tileSizePos) { 1658 if (newDimIdx == std::get<1>(pos)) 1659 pat = TileExprPattern::TileFloorDiv; 1660 else if (newDimIdx == std::get<2>(pos)) 1661 pat = TileExprPattern::TileMod; 1662 } 1663 AffineExpr newMapOutput = createDimSizeExprForTiledLayout(expr, pat); 1664 AffineMap newMap = 1665 AffineMap::get(map.getNumInputs(), map.getNumSymbols(), newMapOutput); 1666 Value affineApp = 1667 b.create<AffineApplyOp>(allocOp->getLoc(), newMap, inAffineApply); 1668 newDynamicSizes.emplace_back(affineApp); 1669 } 1670 newDimIdx++; 1671 } 1672 } 1673 1674 // TODO: Currently works for static memrefs with a single layout map. 1675 LogicalResult mlir::normalizeMemRef(memref::AllocOp *allocOp) { 1676 MemRefType memrefType = allocOp->getType(); 1677 OpBuilder b(*allocOp); 1678 1679 // Fetch a new memref type after normalizing the old memref to have an 1680 // identity map layout. 1681 MemRefType newMemRefType = 1682 normalizeMemRefType(memrefType, b, allocOp->symbolOperands().size()); 1683 if (newMemRefType == memrefType) 1684 // Either memrefType already had an identity map or the map couldn't be 1685 // transformed to an identity map. 1686 return failure(); 1687 1688 Value oldMemRef = allocOp->getResult(); 1689 1690 SmallVector<Value, 4> symbolOperands(allocOp->symbolOperands()); 1691 AffineMap layoutMap = memrefType.getLayout().getAffineMap(); 1692 memref::AllocOp newAlloc; 1693 // Check if `layoutMap` is a tiled layout. Only single layout map is 1694 // supported for normalizing dynamic memrefs. 1695 SmallVector<std::tuple<AffineExpr, unsigned, unsigned>> tileSizePos; 1696 (void)getTileSizePos(layoutMap, tileSizePos); 1697 if (newMemRefType.getNumDynamicDims() > 0 && !tileSizePos.empty()) { 1698 MemRefType oldMemRefType = oldMemRef.getType().cast<MemRefType>(); 1699 SmallVector<Value, 4> newDynamicSizes; 1700 createNewDynamicSizes(oldMemRefType, newMemRefType, layoutMap, allocOp, b, 1701 newDynamicSizes); 1702 // Add the new dynamic sizes in new AllocOp. 1703 newAlloc = 1704 b.create<memref::AllocOp>(allocOp->getLoc(), newMemRefType, 1705 newDynamicSizes, allocOp->alignmentAttr()); 1706 } else { 1707 newAlloc = b.create<memref::AllocOp>(allocOp->getLoc(), newMemRefType, 1708 allocOp->alignmentAttr()); 1709 } 1710 // Replace all uses of the old memref. 1711 if (failed(replaceAllMemRefUsesWith(oldMemRef, /*newMemRef=*/newAlloc, 1712 /*extraIndices=*/{}, 1713 /*indexRemap=*/layoutMap, 1714 /*extraOperands=*/{}, 1715 /*symbolOperands=*/symbolOperands, 1716 /*domOpFilter=*/nullptr, 1717 /*postDomOpFilter=*/nullptr, 1718 /*allowNonDereferencingOps=*/true))) { 1719 // If it failed (due to escapes for example), bail out. 1720 newAlloc.erase(); 1721 return failure(); 1722 } 1723 // Replace any uses of the original alloc op and erase it. All remaining uses 1724 // have to be dealloc's; RAMUW above would've failed otherwise. 1725 assert(llvm::all_of(oldMemRef.getUsers(), [](Operation *op) { 1726 return isa<memref::DeallocOp>(op); 1727 })); 1728 oldMemRef.replaceAllUsesWith(newAlloc); 1729 allocOp->erase(); 1730 return success(); 1731 } 1732 1733 MemRefType mlir::normalizeMemRefType(MemRefType memrefType, OpBuilder b, 1734 unsigned numSymbolicOperands) { 1735 unsigned rank = memrefType.getRank(); 1736 if (rank == 0) 1737 return memrefType; 1738 1739 if (memrefType.getLayout().isIdentity()) { 1740 // Either no maps is associated with this memref or this memref has 1741 // a trivial (identity) map. 1742 return memrefType; 1743 } 1744 AffineMap layoutMap = memrefType.getLayout().getAffineMap(); 1745 1746 // We don't do any checks for one-to-one'ness; we assume that it is 1747 // one-to-one. 1748 1749 // Normalize only static memrefs and dynamic memrefs with a tiled-layout map 1750 // for now. 1751 // TODO: Normalize the other types of dynamic memrefs. 1752 SmallVector<std::tuple<AffineExpr, unsigned, unsigned>> tileSizePos; 1753 (void)getTileSizePos(layoutMap, tileSizePos); 1754 if (memrefType.getNumDynamicDims() > 0 && tileSizePos.empty()) 1755 return memrefType; 1756 1757 // We have a single map that is not an identity map. Create a new memref 1758 // with the right shape and an identity layout map. 1759 ArrayRef<int64_t> shape = memrefType.getShape(); 1760 // FlatAffineConstraint may later on use symbolicOperands. 1761 FlatAffineConstraints fac(rank, numSymbolicOperands); 1762 SmallVector<unsigned, 4> memrefTypeDynDims; 1763 for (unsigned d = 0; d < rank; ++d) { 1764 // Use constraint system only in static dimensions. 1765 if (shape[d] > 0) { 1766 fac.addBound(FlatAffineConstraints::LB, d, 0); 1767 fac.addBound(FlatAffineConstraints::UB, d, shape[d] - 1); 1768 } else { 1769 memrefTypeDynDims.emplace_back(d); 1770 } 1771 } 1772 // We compose this map with the original index (logical) space to derive 1773 // the upper bounds for the new index space. 1774 unsigned newRank = layoutMap.getNumResults(); 1775 if (failed(fac.composeMatchingMap(layoutMap))) 1776 return memrefType; 1777 // TODO: Handle semi-affine maps. 1778 // Project out the old data dimensions. 1779 fac.projectOut(newRank, fac.getNumIds() - newRank - fac.getNumLocalIds()); 1780 SmallVector<int64_t, 4> newShape(newRank); 1781 for (unsigned d = 0; d < newRank; ++d) { 1782 // Check if each dimension of normalized memrefType is dynamic. 1783 bool isDynDim = isNormalizedMemRefDynamicDim( 1784 d, layoutMap, memrefTypeDynDims, b.getContext()); 1785 if (isDynDim) { 1786 newShape[d] = -1; 1787 } else { 1788 // The lower bound for the shape is always zero. 1789 auto ubConst = fac.getConstantBound(FlatAffineConstraints::UB, d); 1790 // For a static memref and an affine map with no symbols, this is 1791 // always bounded. 1792 assert(ubConst.hasValue() && "should always have an upper bound"); 1793 if (ubConst.getValue() < 0) 1794 // This is due to an invalid map that maps to a negative space. 1795 return memrefType; 1796 // If dimension of new memrefType is dynamic, the value is -1. 1797 newShape[d] = ubConst.getValue() + 1; 1798 } 1799 } 1800 1801 // Create the new memref type after trivializing the old layout map. 1802 MemRefType newMemRefType = 1803 MemRefType::Builder(memrefType) 1804 .setShape(newShape) 1805 .setLayout(AffineMapAttr::get(b.getMultiDimIdentityMap(newRank))); 1806 1807 return newMemRefType; 1808 } 1809