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