1 //===- Hoisting.cpp - Linalg hoisting transformations ---------------------===// 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 functions concerned with hoisting invariant operations 10 // in the context of Linalg transformations. 11 // 12 //===----------------------------------------------------------------------===// 13 14 #include "mlir/Dialect/Linalg/Transforms/Hoisting.h" 15 #include "mlir/Analysis/SliceAnalysis.h" 16 #include "mlir/Dialect/Linalg/IR/LinalgOps.h" 17 #include "mlir/Dialect/SCF/SCF.h" 18 #include "mlir/Dialect/SCF/Utils.h" 19 #include "mlir/Dialect/StandardOps/IR/Ops.h" 20 #include "mlir/Dialect/Vector/VectorOps.h" 21 #include "mlir/Dialect/Vector/VectorUtils.h" 22 #include "mlir/IR/BuiltinOps.h" 23 #include "mlir/IR/Dominance.h" 24 #include "mlir/Transforms/LoopUtils.h" 25 #include "llvm/ADT/StringRef.h" 26 #include "llvm/Support/Debug.h" 27 28 #define DEBUG_TYPE "linalg-hoisting" 29 30 #define DBGS() (dbgs() << '[' << DEBUG_TYPE << "] ") 31 32 using namespace mlir; 33 using namespace mlir::linalg; 34 35 using llvm::dbgs; 36 37 void mlir::linalg::hoistViewAllocOps(FuncOp func) { 38 bool changed = true; 39 while (changed) { 40 changed = false; 41 func.walk([&changed](Operation *op) { 42 if (!isa<AllocOp, AllocaOp, DeallocOp>(op)) 43 return; 44 45 LLVM_DEBUG(DBGS() << "Candidate for hoisting: " << *op << "\n"); 46 auto loop = dyn_cast<scf::ForOp>(op->getParentOp()); 47 LLVM_DEBUG(DBGS() << "Parent op: " << *op->getParentOp() << "\n"); 48 49 // Only hoist out of immediately enclosing scf::ForOp. 50 if (!loop) 51 return; 52 53 // If any operand is defined inside the loop don't hoist. 54 if (llvm::any_of(op->getOperands(), [&](Value v) { 55 return !loop.isDefinedOutsideOfLoop(v); 56 })) 57 return; 58 59 LLVM_DEBUG(DBGS() << "All operands defined outside \n"); 60 61 // If alloc has other uses than ViewLikeOp and DeallocOp don't hoist. 62 Value v; 63 if (op->getNumResults() > 0) { 64 assert(op->getNumResults() == 1 && "Unexpected multi-result alloc"); 65 v = op->getResult(0); 66 } 67 if (v && !llvm::all_of(v.getUses(), [&](OpOperand &operand) { 68 return isa<ViewLikeOpInterface, DeallocOp>(operand.getOwner()); 69 })) { 70 LLVM_DEBUG(DBGS() << "Found non view-like or dealloc use: bail\n"); 71 return; 72 } 73 74 // Move AllocOp before the loop. 75 if (isa<AllocOp, AllocaOp>(op)) 76 (void)loop.moveOutOfLoop({op}); 77 else // Move DeallocOp outside of the loop. 78 op->moveAfter(loop); 79 changed = true; 80 }); 81 } 82 } 83 84 /// Look for a transfer_read, in the given tensor uses, accessing the same 85 /// offset as the transfer_write. 86 static vector::TransferReadOp 87 findMatchingTransferRead(vector::TransferWriteOp write, Value srcTensor) { 88 for (Operation *user : srcTensor.getUsers()) { 89 auto read = dyn_cast<vector::TransferReadOp>(user); 90 if (read && read.indices() == write.indices() && 91 read.getVectorType() == write.getVectorType()) { 92 return read; 93 } 94 } 95 return nullptr; 96 } 97 98 /// Check if the chunk of data inserted by the transfer_write in the given 99 /// tensor are read by any other op than the read candidate. 100 static bool tensorChunkAccessedByUnknownOp(vector::TransferWriteOp write, 101 vector::TransferReadOp candidateRead, 102 Value srcTensor) { 103 // Make sure none of the other uses read the part of the tensor modified 104 // by the transfer_write. 105 llvm::SmallVector<Value::use_range, 1> uses; 106 uses.push_back(srcTensor.getUses()); 107 while (!uses.empty()) { 108 for (OpOperand &use : uses.pop_back_val()) { 109 Operation *user = use.getOwner(); 110 // Skip the candidate use, only inspect the "other" uses. 111 if (user == candidateRead.getOperation() || user == write.getOperation()) 112 continue; 113 // Consider all transitive uses through a vector.transfer_write. 114 if (auto writeUser = dyn_cast<vector::TransferWriteOp>(user)) { 115 uses.push_back(writeUser->getResult(0).getUses()); 116 continue; 117 } 118 // Consider all nested uses through an scf::ForOp. We may have 119 // pass-through tensor arguments left from previous level of 120 // hoisting. 121 if (auto forUser = dyn_cast<scf::ForOp>(user)) { 122 Value arg = forUser.getLoopBody().getArgument( 123 use.getOperandNumber() - forUser.getNumControlOperands() + 124 /*iv value*/ 1); 125 uses.push_back(arg.getUses()); 126 continue; 127 } 128 // Follow the use yield as long as it doesn't escape the original 129 // region. 130 scf::YieldOp yieldUser = dyn_cast<scf::YieldOp>(user); 131 if (yieldUser && 132 write->getParentOp()->isAncestor(yieldUser->getParentOp())) { 133 Value ret = yieldUser->getParentOp()->getResult(use.getOperandNumber()); 134 uses.push_back(ret.getUses()); 135 continue; 136 } 137 auto read = dyn_cast<vector::TransferReadOp>(user); 138 if (!read || !isDisjointTransferIndices( 139 cast<VectorTransferOpInterface>(read.getOperation()), 140 cast<VectorTransferOpInterface>(write.getOperation()))) { 141 return true; 142 } 143 } 144 } 145 return false; 146 } 147 148 // To hoist transfer op on tensor the logic can be significantly simplified 149 // compared to the case on buffer. The transformation follows this logic: 150 // 1. Look for transfer_write with a single use from ForOp yield 151 // 2. Check the uses of the matching block argument and look for a transfer_read 152 // with the same indices. 153 // 3. Check that all the other uses of the tensor argument are either disjoint 154 // tensor_read or transfer_write. For transfer_write uses recurse to make sure 155 // the new tensor has the same restrictions on its uses. 156 // 4. Hoist the tensor_read/tensor_write and update the tensor SSA links. 157 // After this transformation the scf.forOp may have unused arguments that can be 158 // remove by the canonicalization pass. 159 void mlir::linalg::hoistRedundantVectorTransfersOnTensor(FuncOp func) { 160 bool changed = true; 161 while (changed) { 162 changed = false; 163 func.walk([&](scf::ForOp forOp) { 164 Operation *yield = forOp.getBody()->getTerminator(); 165 for (auto it : llvm::enumerate(forOp.getRegionIterArgs())) { 166 Value ret = yield->getOperand(it.index()); 167 auto write = ret.getDefiningOp<vector::TransferWriteOp>(); 168 if (!write || !write->hasOneUse()) 169 continue; 170 LLVM_DEBUG(DBGS() << "Candidate write for hoisting: " 171 << *write.getOperation() << "\n"); 172 if (llvm::any_of(write.indices(), [&forOp](Value index) { 173 return !forOp.isDefinedOutsideOfLoop(index); 174 })) 175 continue; 176 // Find a read with the same type and indices. 177 vector::TransferReadOp matchingRead = 178 findMatchingTransferRead(write, it.value()); 179 // Make sure none of the other uses read the part of the tensor modified 180 // by the transfer_write. 181 if (!matchingRead || 182 tensorChunkAccessedByUnknownOp(write, matchingRead, it.value())) 183 continue; 184 185 // Hoist read before. 186 if (failed(forOp.moveOutOfLoop({matchingRead}))) 187 llvm_unreachable( 188 "Unexpected failure to move transfer read out of loop"); 189 // Update the source tensor. 190 matchingRead.sourceMutable().assign(forOp.initArgs()[it.index()]); 191 192 // Hoist write after. 193 write->moveAfter(forOp); 194 yield->setOperand(it.index(), write.source()); 195 196 // Rewrite `loop` with new yields by cloning and erase the original 197 // loop. 198 OpBuilder b(matchingRead); 199 auto newForOp = 200 cloneWithNewYields(b, forOp, matchingRead.vector(), write.vector()); 201 202 // Transfer write has been hoisted, need to update the vector and tensor 203 // source. Replace the result of the loop to use the new tensor created 204 // outside the loop. 205 newForOp.getResult(it.index()).replaceAllUsesWith(write.getResult(0)); 206 write.vectorMutable().assign(newForOp.getResults().back()); 207 write.sourceMutable().assign(newForOp.getResult(it.index())); 208 209 changed = true; 210 forOp.erase(); 211 // Need to interrupt and restart because erasing the loop messes up the 212 // walk. 213 return WalkResult::interrupt(); 214 } 215 return WalkResult::advance(); 216 }); 217 } 218 } 219 220 void mlir::linalg::hoistRedundantVectorTransfers(FuncOp func) { 221 bool changed = true; 222 while (changed) { 223 changed = false; 224 225 func.walk([&](vector::TransferReadOp transferRead) { 226 if (!transferRead.getShapedType().isa<MemRefType>()) 227 return WalkResult::advance(); 228 229 LLVM_DEBUG(DBGS() << "Candidate for hoisting: " 230 << *transferRead.getOperation() << "\n"); 231 auto loop = dyn_cast<scf::ForOp>(transferRead->getParentOp()); 232 LLVM_DEBUG(DBGS() << "Parent op: " << *transferRead->getParentOp() 233 << "\n"); 234 if (!loop) 235 return WalkResult::advance(); 236 237 if (failed(moveLoopInvariantCode( 238 cast<LoopLikeOpInterface>(loop.getOperation())))) 239 llvm_unreachable( 240 "Unexpected failure to move invariant code out of loop"); 241 242 LLVM_DEBUG(DBGS() << "Candidate read: " << *transferRead.getOperation() 243 << "\n"); 244 245 llvm::SetVector<Operation *> forwardSlice; 246 getForwardSlice(transferRead, &forwardSlice); 247 248 // Look for the last TransferWriteOp in the forwardSlice of 249 // `transferRead` that operates on the same memref. 250 vector::TransferWriteOp transferWrite; 251 for (auto *sliceOp : llvm::reverse(forwardSlice)) { 252 auto candidateWrite = dyn_cast<vector::TransferWriteOp>(sliceOp); 253 if (!candidateWrite || candidateWrite.source() != transferRead.source()) 254 continue; 255 transferWrite = candidateWrite; 256 } 257 258 // All operands of the TransferRead must be defined outside of the loop. 259 for (auto operand : transferRead.getOperands()) 260 if (!loop.isDefinedOutsideOfLoop(operand)) 261 return WalkResult::advance(); 262 263 // Only hoist transfer_read / transfer_write pairs for now. 264 if (!transferWrite) 265 return WalkResult::advance(); 266 267 LLVM_DEBUG(DBGS() << "Candidate: " << *transferWrite.getOperation() 268 << "\n"); 269 270 // Approximate aliasing by checking that: 271 // 1. indices are the same, 272 // 2. no other operations in the loop access the same memref except 273 // for transfer_read/transfer_write accessing statically disjoint 274 // slices. 275 if (transferRead.indices() != transferWrite.indices() && 276 transferRead.getVectorType() == transferWrite.getVectorType()) 277 return WalkResult::advance(); 278 279 // TODO: may want to memoize this information for performance but it 280 // likely gets invalidated often. 281 DominanceInfo dom(loop); 282 if (!dom.properlyDominates(transferRead.getOperation(), transferWrite)) 283 return WalkResult::advance(); 284 for (auto &use : transferRead.source().getUses()) { 285 if (!dom.properlyDominates(loop, use.getOwner())) 286 continue; 287 if (use.getOwner() == transferRead.getOperation() || 288 use.getOwner() == transferWrite.getOperation()) 289 continue; 290 if (auto transferWriteUse = 291 dyn_cast<vector::TransferWriteOp>(use.getOwner())) { 292 if (!isDisjointTransferSet( 293 cast<VectorTransferOpInterface>(transferWrite.getOperation()), 294 cast<VectorTransferOpInterface>( 295 transferWriteUse.getOperation()))) 296 return WalkResult::advance(); 297 } else if (auto transferReadUse = 298 dyn_cast<vector::TransferReadOp>(use.getOwner())) { 299 if (!isDisjointTransferSet( 300 cast<VectorTransferOpInterface>(transferWrite.getOperation()), 301 cast<VectorTransferOpInterface>( 302 transferReadUse.getOperation()))) 303 return WalkResult::advance(); 304 } else { 305 // Unknown use, we cannot prove that it doesn't alias with the 306 // transferRead/transferWrite operations. 307 return WalkResult::advance(); 308 } 309 } 310 311 // Hoist read before. 312 if (failed(loop.moveOutOfLoop({transferRead}))) 313 llvm_unreachable( 314 "Unexpected failure to move transfer read out of loop"); 315 316 // Hoist write after. 317 transferWrite->moveAfter(loop); 318 319 // Rewrite `loop` with new yields by cloning and erase the original loop. 320 OpBuilder b(transferRead); 321 auto newForOp = cloneWithNewYields(b, loop, transferRead.vector(), 322 transferWrite.vector()); 323 324 // Transfer write has been hoisted, need to update the written value to 325 // the value yielded by the newForOp. 326 transferWrite.vector().replaceAllUsesWith( 327 newForOp.getResults().take_back()[0]); 328 329 changed = true; 330 loop.erase(); 331 // Need to interrupt and restart because erasing the loop messes up the 332 // walk. 333 return WalkResult::interrupt(); 334 }); 335 } 336 } 337 338 /// Ensure prerequisites that guarantee pad op hoisting can occur. 339 /// Return failure in the cases when we cannot perform hoisting; i.e. if either: 340 /// 1. There exists a use of `padTensorOp` that is not a linalg input operand. 341 /// 2. There isn't an enclosing `outermostEnclosingForOp` loop. 342 /// 3. There exists an op with a region that is dominated by 343 /// `outermostEnclosingForOp` and that isn't a LoopLikeInterface or a 344 /// LinalgOp. 345 /// 3. There exists an op with side effects that is dominated by 346 /// `outermostEnclosingForOp` and that isn't a LoopLikeInterface. 347 /// 348 /// While ensuring prerequisites: 349 /// 1. Fill the `backwardSlice` to contain the topologically sorted ops 350 /// dominated by `outermostEnclosingForOp`. 351 /// 2. Fill the `packingLoops` to contain only the enclosing loops of 352 /// `backwardSlice` whose IV is actually used in computing padding. Loops that 353 /// remain in `backwardSlice` but that are not in `packingLoops` are 354 /// dimensions of reuse. 355 static LogicalResult 356 hoistPaddingOnTensorsPrerequisites(linalg::PadTensorOp padTensorOp, int nLevels, 357 llvm::SetVector<Operation *> &backwardSlice, 358 llvm::SetVector<Operation *> &packingLoops) { 359 // Bail on any use that isn't an input of a Linalg op. 360 // Hoisting of inplace updates happens after vectorization. 361 for (OpOperand &use : padTensorOp.result().getUses()) { 362 auto linalgUser = dyn_cast<linalg::LinalgOp>(use.getOwner()); 363 if (!linalgUser || !linalgUser.isInputTensor(&use)) 364 return failure(); 365 } 366 367 // Get at most nLevels of enclosing loops. 368 SmallVector<LoopLikeOpInterface> reverseEnclosingLoops; 369 Operation *outermostEnclosingForOp = nullptr, 370 *nextEnclosingForOp = 371 padTensorOp->getParentOfType<LoopLikeOpInterface>(); 372 while (nLevels-- > 0 && nextEnclosingForOp) { 373 outermostEnclosingForOp = nextEnclosingForOp; 374 reverseEnclosingLoops.push_back(outermostEnclosingForOp); 375 nextEnclosingForOp = 376 nextEnclosingForOp->getParentOfType<LoopLikeOpInterface>(); 377 } 378 if (!outermostEnclosingForOp) 379 return failure(); 380 381 // Get the backwards slice from `padTensorOp` that is dominated by the 382 // outermost enclosing loop. 383 DominanceInfo domInfo(outermostEnclosingForOp); 384 getBackwardSlice(padTensorOp, &backwardSlice, [&](Operation *op) { 385 return domInfo.dominates(outermostEnclosingForOp, op); 386 }); 387 388 // Bail on any op with a region that is not a LoopLikeInterface or a LinalgOp. 389 if (llvm::any_of(backwardSlice, [](Operation *op) { 390 return op->getNumRegions() > 0 && !isa<LoopLikeOpInterface>(op) && 391 !isa<LinalgOp>(op); 392 })) 393 return failure(); 394 395 // Filter out the loops whose induction variable is not used to compute the 396 // padded result. As a first approximation, just look for IVs that have no use 397 // in the backwardSlice. 398 // These are the dimensions of reuse that we can exploit to reduce the amount 399 // of work / memory. 400 // TODO: would this optimization compose better as a canonicalization? 401 for (LoopLikeOpInterface loop : reverseEnclosingLoops) { 402 auto forOp = dyn_cast<scf::ForOp>(loop.getOperation()); 403 if (!forOp) 404 continue; 405 for (Operation *user : forOp.getInductionVar().getUsers()) { 406 if (backwardSlice.contains(user)) { 407 packingLoops.insert(forOp); 408 break; 409 } 410 } 411 } 412 413 // Backward slice is a topologically sorted list of ops starting at 414 // `outermostEnclosingForOp`. 415 assert(outermostEnclosingForOp == backwardSlice.front()); 416 417 return success(); 418 } 419 420 /// Return the number of iterations in the loop (ub - lb).ceilDiv(step). 421 static Value buildLoopTripCount(OpBuilder &b, scf::ForOp forOp) { 422 MLIRContext *ctx = forOp->getContext(); 423 AffineExpr lb, ub, step; 424 bindDims(ctx, lb, ub); 425 bindSymbols(ctx, step); 426 return b.create<AffineApplyOp>( 427 forOp->getLoc(), AffineMap::get(2, 1, {(ub - lb).ceilDiv(step)}, ctx), 428 ValueRange{forOp.lowerBound(), forOp.upperBound(), forOp.step()}); 429 } 430 431 /// Return the current iteration number in the loop (iv - lb).ceilDiv(step). 432 static Value buildLoopIterationCount(OpBuilder &b, scf::ForOp forOp) { 433 MLIRContext *ctx = forOp->getContext(); 434 AffineExpr iv, lb, step; 435 bindDims(ctx, iv, lb); 436 bindSymbols(ctx, step); 437 return b.create<AffineApplyOp>( 438 forOp->getLoc(), AffineMap::get(2, 1, {(iv - lb).ceilDiv(step)}, ctx), 439 ValueRange{forOp.getInductionVar(), forOp.lowerBound(), forOp.step()}); 440 } 441 442 LogicalResult mlir::linalg::hoistPaddingOnTensors(PadTensorOp &padTensorOp, 443 unsigned nLoops) { 444 llvm::SetVector<Operation *> backwardSlice, packingLoops; 445 if (failed(hoistPaddingOnTensorsPrerequisites(padTensorOp, nLoops, 446 backwardSlice, packingLoops))) 447 return failure(); 448 449 // Update actual number of loops, which may be smaller. 450 nLoops = packingLoops.size(); 451 452 Location loc = padTensorOp->getLoc(); 453 RankedTensorType paddedTensorType = padTensorOp.getResultType(); 454 unsigned paddedRank = paddedTensorType.getRank(); 455 456 // Backward slice is a topologically sorted list of ops starting at 457 // `outermostEnclosingForOp`. 458 Operation *outermostEnclosingForOp = backwardSlice.front(); 459 // IP just before the outermost loop considered that we hoist above. 460 OpBuilder b(outermostEnclosingForOp); 461 462 // Create the packed tensor<?x?x..?xpadded_shape> into which we amortize 463 // padding. 464 SmallVector<int64_t> packedShape(nLoops, ShapedType::kDynamicSize); 465 // TODO: go grab dims when necessary, for now PadTensorOp returns a static 466 // tensor. 467 llvm::append_range(packedShape, paddedTensorType.getShape()); 468 auto packedTensorType = 469 RankedTensorType::get(packedShape, paddedTensorType.getElementType()); 470 auto dynamicSizes = 471 llvm::to_vector<4>(llvm::map_range(packingLoops, [&](Operation *op) { 472 return buildLoopTripCount(b, cast<scf::ForOp>(op)); 473 })); 474 Value packedTensor = b.create<linalg::InitTensorOp>( 475 loc, dynamicSizes, packedTensorType.getShape(), 476 packedTensorType.getElementType()); 477 478 // Clone the operations involved in the backward slice, iteratively stepping 479 // into the loops that we encounter. 480 // The implementation proceeds in a stack-like fashion: 481 // 1. Iteratively clone and step into the loops, pushing the `packedTensor` 482 // deeper in the stack. 483 // 2. Create a SubTensorInsert at the top of the stack. 484 // 3. Iteratively pop and yield the result of the SubTensorInsertOp across 485 // the cloned loops. 486 SmallVector<Value> clonedLoopIvs, leadingPackedTensorIndexings; 487 clonedLoopIvs.reserve(nLoops); 488 leadingPackedTensorIndexings.reserve(nLoops); 489 BlockAndValueMapping bvm; 490 // Stack step 1. iteratively clone loops and push `packedTensor`. 491 // Insert `padTensorOp` into the backwardSlice so we clone it too. 492 backwardSlice.insert(padTensorOp); 493 for (Operation *op : backwardSlice) { 494 if (op->getNumRegions() == 0 || isa<linalg::PadTensorOp>(op)) { 495 b.clone(*op, bvm); 496 continue; 497 } 498 // TODO: support more cases as they appear. 499 auto forOp = dyn_cast<scf::ForOp>(op); 500 assert(forOp && "Expected scf::ForOp when hoisting pad ops"); 501 // Unused loop, just skip it. 502 if (!packingLoops.contains(forOp)) 503 continue; 504 auto clonedForOp = 505 b.create<scf::ForOp>(loc, forOp.lowerBound(), forOp.upperBound(), 506 forOp.step(), packedTensor); 507 assert(clonedForOp->getNumRegions() == 1); 508 clonedLoopIvs.push_back(clonedForOp.getInductionVar()); 509 b.setInsertionPointToStart(&clonedForOp->getRegion(0).front()); 510 leadingPackedTensorIndexings.push_back( 511 buildLoopIterationCount(b, clonedForOp)); 512 bvm.map(forOp.getInductionVar(), clonedLoopIvs.back()); 513 packedTensor = clonedForOp.getRegionIterArgs().front(); 514 } 515 516 // Stack step 2. create SubTensorInsertOp at the top of the stack. 517 // offsets = [clonedLoopIvs, 0 .. 0]. 518 SmallVector<OpFoldResult> offsets(leadingPackedTensorIndexings.begin(), 519 leadingPackedTensorIndexings.end()); 520 offsets.append(paddedRank, b.getIndexAttr(0)); 521 // sizes = [1 .. 1, paddedShape]. 522 SmallVector<OpFoldResult> sizes(nLoops, b.getIndexAttr(1)); 523 for (int64_t sz : paddedTensorType.getShape()) { 524 // TODO: go grab dims when necessary, for now PadTensorOp returns a static 525 // tensor. 526 assert(!ShapedType::isDynamic(sz) && "padded tensor needs static sizes"); 527 sizes.push_back(b.getIndexAttr(sz)); 528 } 529 // strides = [1 .. 1]. 530 SmallVector<OpFoldResult> strides(nLoops + paddedRank, b.getIndexAttr(1)); 531 532 Value inserted = 533 b.create<SubTensorInsertOp>(loc, bvm.lookup(padTensorOp.result()), 534 packedTensor, offsets, sizes, strides); 535 536 // Stack step 3. iteratively pop the stack and propagate the yield. 537 Value valueToYield = inserted; 538 for (Value iv : llvm::reverse(clonedLoopIvs)) { 539 auto forOp = scf::getForInductionVarOwner(iv); 540 b.setInsertionPointToEnd(&forOp.getRegion().front()); 541 b.create<scf::YieldOp>(loc, valueToYield); 542 valueToYield = forOp.getResult(0); 543 } 544 545 // Now the packed tensor is ready, replace the original padding op by a 546 // 1x..x1 SubTensor [originalLoopIvs, 0 .. 0][1 .. 1, paddedShape][1 .. 1]. 547 b.setInsertionPoint(padTensorOp); 548 SmallVector<Value> loopIterationCounts = 549 llvm::to_vector<4>(llvm::map_range(packingLoops, [&](Operation *loop) { 550 return buildLoopIterationCount(b, cast<scf::ForOp>(loop)); 551 })); 552 // offsets = [originalLoopIvs, 0 .. 0]. 553 offsets.assign(loopIterationCounts.begin(), loopIterationCounts.end()); 554 offsets.append(paddedRank, b.getIndexAttr(0)); 555 // sizes = [1 .. 1, paddedShape] (definedabove). 556 // strides = [1 .. 1] (defined above) 557 packedTensor = 558 scf::getForInductionVarOwner(clonedLoopIvs.front())->getResult(0); 559 padTensorOp.replaceAllUsesWith( 560 b.create<SubTensorOp>(loc, padTensorOp.getResultType(), packedTensor, 561 offsets, sizes, strides) 562 ->getResult(0)); 563 564 Operation *toErase = padTensorOp; 565 566 // Make the newly cloned `padTensorOp` available to the caller. 567 padTensorOp = 568 cast<PadTensorOp>(bvm.lookup(padTensorOp.result()).getDefiningOp()); 569 570 toErase->erase(); 571 572 return success(); 573 } 574