[Flow] Fold chained tensor.slice ops (#24628)

This PR Adds a canonicalization pattern for `flow.tensor.slice` that
folds only when the producer of the slice source is also a
`flow.tensor.slice`.

**Example :**

%producer = flow.tensor.slice %src[producer_start for producer_length]
%consumer = flow.tensor.slice %producer[consumer_start for
consumer_length]

**Canonicalizes to :**

%consumer = flow.tensor.slice %src[producer_start + consumer_start ] for
consumer_length

---------

Signed-off-by: LekkalaSravya3 <lekkala.sravya@multicorewareinc.com>
diff --git a/compiler/src/iree/compiler/Dialect/Flow/IR/FlowOpFolders.cpp b/compiler/src/iree/compiler/Dialect/Flow/IR/FlowOpFolders.cpp
index 9e46d78..311120f 100644
--- a/compiler/src/iree/compiler/Dialect/Flow/IR/FlowOpFolders.cpp
+++ b/compiler/src/iree/compiler/Dialect/Flow/IR/FlowOpFolders.cpp
@@ -1041,9 +1041,57 @@
   return {};
 }
 
+namespace {
+
+// Folds consecutive flow.tensor.slice ops by composing their offsets.
+//
+// Example:
+//   %src -> slice[1,1] -> slice[0,1]
+//      becomes
+//   %src -> slice[1,2]
+//
+// fused_start = inner_start + outer_start
+// fused_length = outer_length
+struct FoldTensorSliceOfSlice : OpRewritePattern<TensorSliceOp> {
+  using OpRewritePattern<TensorSliceOp>::OpRewritePattern;
+
+  LogicalResult matchAndRewrite(TensorSliceOp consumerSlice,
+                                PatternRewriter &rewriter) const override {
+    auto producerSlice =
+        consumerSlice.getSource().getDefiningOp<TensorSliceOp>();
+    if (!producerSlice) {
+      return failure();
+    }
+    if (!producerSlice->hasOneUse()) {
+      return failure();
+    }
+
+    auto loc =
+        rewriter.getFusedLoc({producerSlice.getLoc(), consumerSlice.getLoc()});
+
+    SmallVector<Value> fusedStartIndices;
+    for (auto [producerStart, consumerStart] :
+         llvm::zip(producerSlice.getStartIndices(),
+                   consumerSlice.getStartIndices())) {
+      fusedStartIndices.push_back(rewriter.createOrFold<arith::AddIOp>(
+          loc, producerStart, consumerStart));
+    }
+
+    rewriter.replaceOpWithNewOp<TensorSliceOp>(
+        consumerSlice, consumerSlice.getResult().getType(),
+        producerSlice.getSource(), producerSlice.getSourceDims(),
+        fusedStartIndices, consumerSlice.getLengths(),
+        consumerSlice.getResultDims());
+
+    return success();
+  }
+};
+
+} // namespace
+
 void TensorSliceOp::getCanonicalizationPatterns(RewritePatternSet &results,
                                                 MLIRContext *context) {
-  // TODO(benvanik): canonicalize multiple slices (traverse upward through ssa).
+  results.insert<FoldTensorSliceOfSlice>(context);
   results.insert<ReplaceOpIfTensorOperandZeroElements<TensorSliceOp, 0>>(
       context);
   results.insert<ReplaceOpIfTensorResultZeroElements<TensorSliceOp, 0>>(
diff --git a/compiler/src/iree/compiler/Dialect/Flow/IR/test/tensor_folding.mlir b/compiler/src/iree/compiler/Dialect/Flow/IR/test/tensor_folding.mlir
index 10a470b..af7731f 100644
--- a/compiler/src/iree/compiler/Dialect/Flow/IR/test/tensor_folding.mlir
+++ b/compiler/src/iree/compiler/Dialect/Flow/IR/test/tensor_folding.mlir
@@ -690,6 +690,71 @@
 
 // -----
 
+// CHECK-LABEL: @sliceOfSliceStatic2D
+util.func public @sliceOfSliceStatic2D(%src: tensor<3x3xf32>) -> tensor<1x1xf32> {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %c2 = arith.constant 2 : index
+  // CHECK-NOT: flow.tensor.slice %{{.*}}[%c1, %c1
+  %inner = flow.tensor.slice %src[%c1, %c1 for %c2, %c2] : tensor<3x3xf32> -> tensor<2x2xf32>
+  // CHECK: flow.tensor.slice %{{.*}}[%c1, %c2 for %c1, %c1] : tensor<3x3xf32> -> tensor<1x1xf32>
+  %outer = flow.tensor.slice %inner[%c0, %c1 for %c1, %c1] : tensor<2x2xf32> -> tensor<1x1xf32>
+  util.return %outer : tensor<1x1xf32>
+}
+
+// -----
+
+// CHECK-LABEL: @sliceOfSliceDynamic1D
+// CHECK-SAME: (%{{.*}}, %[[DIM:arg[0-9]+]]: index, %[[S0:arg[0-9]+]]: index, %{{.*}}, %[[S1:arg[0-9]+]]: index, %[[L1:arg[0-9]+]]: index)
+// Chained slices with dynamic offsets: fused start emits arith.addi at runtime.
+util.func public @sliceOfSliceDynamic1D(%src: tensor<?xf32>, %dim: index,
+                                        %s0: index, %l0: index,
+                                        %s1: index, %l1: index) -> tensor<?xf32> {
+  // CHECK-NOT: flow.tensor.slice %{{.*}}[%[[S0]]
+  %inner = flow.tensor.slice %src[%s0 for %l0] : tensor<?xf32>{%dim} -> tensor<?xf32>{%l0}
+  // CHECK: %[[ADD:.+]] = arith.addi %[[S0]], %[[S1]]
+  // CHECK: flow.tensor.slice %{{.*}}[%[[ADD]] for %[[L1]]] : tensor<?xf32>{%[[DIM]]} -> tensor<?xf32>{%[[L1]]}
+  %outer = flow.tensor.slice %inner[%s1 for %l1] : tensor<?xf32>{%l0} -> tensor<?xf32>{%l1}
+  util.return %outer : tensor<?xf32>
+}
+
+// -----
+
+// CHECK-LABEL: @sliceOfSliceMultiUseProducerNoFold
+util.func public @sliceOfSliceMultiUseProducerNoFold(%src: tensor<3x3xf32>) -> (tensor<1x1xf32>, tensor<2x2xf32>) {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %c2 = arith.constant 2 : index
+  // CHECK: %[[INNER:.+]] = flow.tensor.slice %{{.*}}[%c1, %c1 for %c2, %c2]
+  %inner = flow.tensor.slice %src[%c1, %c1 for %c2, %c2] : tensor<3x3xf32> -> tensor<2x2xf32>
+  // CHECK: flow.tensor.slice %[[INNER]][%c0, %c1 for %c1, %c1]
+  %outer = flow.tensor.slice %inner[%c0, %c1 for %c1, %c1] : tensor<2x2xf32> -> tensor<1x1xf32>
+  util.return %outer, %inner : tensor<1x1xf32>, tensor<2x2xf32>
+}
+
+// -----
+
+// CHECK-LABEL: @sliceOfSliceTripleChain
+// Three consecutive slices fully fold into one via fixed-point iteration.
+// slice1: [1 for 5], slice2: [1 for 3], slice3: [1 for 1]
+// pass1 fuses slice3+slice2: start=1+1=2, length=1
+// pass2 fuses that + slice1: start=1+2=3, length=1 -> final [3 for 1] on %src
+util.func public @sliceOfSliceTripleChain(%src: tensor<8xf32>) -> tensor<1xf32> {
+  %c1 = arith.constant 1 : index
+  %c3 = arith.constant 3 : index
+  %c5 = arith.constant 5 : index
+  // CHECK-NOT: flow.tensor.slice %{{.*}}[%c1 for %c5
+  %s1 = flow.tensor.slice %src[%c1 for %c5] : tensor<8xf32> -> tensor<5xf32>
+  // CHECK-NOT: flow.tensor.slice %{{.*}}[%c1 for %c3
+  %s2 = flow.tensor.slice %s1[%c1 for %c3]  : tensor<5xf32> -> tensor<3xf32>
+  // CHECK: %[[C3:.+]] = arith.constant 3 : index
+  // CHECK: flow.tensor.slice %{{.*}}[%[[C3]] for %c1] : tensor<8xf32> -> tensor<1xf32>
+  %s3 = flow.tensor.slice %s2[%c1 for %c1]  : tensor<3xf32> -> tensor<1xf32>
+  util.return %s3 : tensor<1xf32>
+}
+
+// -----
+
 // CHECK-LABEL: @updateConst0D
 util.func public @updateConst0D() -> tensor<i32> {
   %0 = arith.constant dense<0> : tensor<i32>