[Mlir-commits] [mlir] [mlir][scf] Fold Self-addition of the induction variable into the loop range (PR #228725)

Matthias Springer llvmlistbot at llvm.org
Sun Oct 4 18:47:26 PDT 2026


https://github.com/matthias-springer updated https://github.com/llvm/llvm-project/pull/228725

>From 79e052e7cfb1dc824be84db011fb5b3126917b8d Mon Sep 17 00:00:00 2001
From: Prashanth <ping at starone01.me>
Date: Sat, 3 Oct 2026 21:04:23 +0530
Subject: [PATCH] [mlir][scf] Fold Self-addition of the induction variable into
 the loop range

scf-for-loop-range-folding only folded an arith op into the loop range if the induction variable had a single use (hasOneUse()). That counts operand slots rather than users, so `arith.addi %i, %i`, a single
operation that uses the induction variable twice, was rejected.

now we check whether the induction variable is used in a single operation (user).

Fixes #228474

Assisted-by: Claude
---
 .../SCF/Transforms/LoopRangeFolding.cpp       | 20 ++++-
 mlir/test/Dialect/SCF/loop-range.mlir         | 73 +++++++++++++++++++
 2 files changed, 89 insertions(+), 4 deletions(-)

diff --git a/mlir/lib/Dialect/SCF/Transforms/LoopRangeFolding.cpp b/mlir/lib/Dialect/SCF/Transforms/LoopRangeFolding.cpp
index d4b4c4cfc063f..0d7d310cfaafd 100644
--- a/mlir/lib/Dialect/SCF/Transforms/LoopRangeFolding.cpp
+++ b/mlir/lib/Dialect/SCF/Transforms/LoopRangeFolding.cpp
@@ -45,12 +45,18 @@ void ForLoopRangeFolding::runOnOperation() {
     // Fold until a fixed point is reached
     while (true) {
 
-      // If the induction variable is used more than once, we can't fold its
-      // arith ops into the loop range
-      if (!indVar.hasOneUse())
+      // If the induction variable is used by more than one operation, we can't
+      // fold its arith ops into the loop range. Note that a single operation
+      // may use the induction variable several times (e.g. `arith.addi %i,
+      // %i`), so check for a single user rather than a single use.
+      if (indVar.use_empty())
         break;
 
       Operation *user = *indVar.getUsers().begin();
+      if (!llvm::all_of(indVar.getUsers(),
+                        [&](Operation *u) { return u == user; }))
+        break;
+
       if (!isa<arith::AddIOp, arith::MulIOp>(user))
         break;
 
@@ -65,13 +71,19 @@ void ForLoopRangeFolding::runOnOperation() {
       IRMapping stepMap;
       stepMap.map(indVar, op.getStep());
 
-      if (isa<arith::AddIOp>(user)) {
+      if (auto addOp = dyn_cast<arith::AddIOp>(user)) {
         Operation *lbFold = b.clone(*user, lbMap);
         Operation *ubFold = b.clone(*user, ubMap);
 
         op.setLowerBound(lbFold->getResult(0));
         op.setUpperBound(ubFold->getResult(0));
 
+        // `arith.addi %i, %i` is `2 * %i`, so the step has to be doubled too.
+        if (addOp.getLhs() == indVar && addOp.getRhs() == indVar) {
+          Operation *stepFold = b.clone(*user, stepMap);
+          op.setStep(stepFold->getResult(0));
+        }
+
       } else if (auto mulOp = dyn_cast<arith::MulIOp>(user)) {
         // Only fold if the multiplier is a known strictly positive constant.
         // Multiplying by zero or a negative value would produce an invalid
diff --git a/mlir/test/Dialect/SCF/loop-range.mlir b/mlir/test/Dialect/SCF/loop-range.mlir
index 79e74f430618a..3cf4c857e3f8c 100644
--- a/mlir/test/Dialect/SCF/loop-range.mlir
+++ b/mlir/test/Dialect/SCF/loop-range.mlir
@@ -218,3 +218,76 @@ func.func @no_fold_runtime_mul(%multiplier: index) {
 // CHECK:       scf.for %[[I:.*]] = %[[C0]] to %[[C10]] step %[[C1]] {
 // CHECK:         %[[R:.*]] = arith.muli %[[I]], {{.*}} : index
 // CHECK:         "test.sink"(%[[R]])
+
+// `arith.addi %i, %i` is `2 * %i`, so it can be folded into the loop range like
+// a multiplication by 2: the lower bound, the upper bound and the step are all
+// doubled. The step has to be doubled too, otherwise the folded loop would visit
+// every index instead of every second one.
+func.func @fold_self_add(%arg0: memref<16xi32>, %arg1: i32) {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %c8 = arith.constant 8 : index
+  scf.for %i = %c0 to %c8 step %c1 {
+    %0 = arith.addi %i, %i : index
+    memref.store %arg1, %arg0[%0] : memref<16xi32>
+  }
+  return
+}
+
+// CHECK-LABEL: func @fold_self_add
+// CHECK-SAME:  (%[[ARG0:[a-z0-9]+]]: {{.*}}, %[[ARG1:[a-z0-9]+]]: {{.*}})
+// CHECK:       %[[C0:.*]] = arith.constant 0 : index
+// CHECK:       %[[C1:.*]] = arith.constant 1 : index
+// CHECK:       %[[C8:.*]] = arith.constant 8 : index
+// CHECK:       %[[LB:.*]] = arith.addi %[[C0]], %[[C0]] : index
+// CHECK:       %[[UB:.*]] = arith.addi %[[C8]], %[[C8]] : index
+// CHECK:       %[[STEP:.*]] = arith.addi %[[C1]], %[[C1]] : index
+// CHECK:       scf.for %[[I:.*]] = %[[LB]] to %[[UB]] step %[[STEP]] {
+// CHECK-NEXT:    memref.store %[[ARG1]], %[[ARG0]]{{\[}}%[[I]]
+
+// The induction variable has a user other than the `arith.addi`. Folding would
+// change what that user sees (`2 * %i` instead of `%i`), so the loop must be
+// left unchanged.
+func.func @no_fold_self_add_extra_user() {
+  %c0 = arith.constant 0 : index
+  %c8 = arith.constant 8 : index
+  %c1 = arith.constant 1 : index
+  scf.for %i = %c0 to %c8 step %c1 {
+    %0 = arith.addi %i, %i : index
+    "test.sink"(%0) : (index) -> ()
+    "test.sink"(%i) : (index) -> ()
+  }
+  return
+}
+
+// CHECK-LABEL: func @no_fold_self_add_extra_user
+// CHECK:       %[[C0:.*]] = arith.constant 0 : index
+// CHECK:       %[[C8:.*]] = arith.constant 8 : index
+// CHECK:       %[[C1:.*]] = arith.constant 1 : index
+// CHECK:       scf.for %[[I:.*]] = %[[C0]] to %[[C8]] step %[[C1]] {
+// CHECK:         %[[R:.*]] = arith.addi %[[I]], %[[I]] : index
+// CHECK:         "test.sink"(%[[R]])
+// CHECK:         "test.sink"(%[[I]])
+
+// `arith.muli %i, %i` is not a scaling of the induction variable by a constant,
+// so it cannot be expressed as a new loop range. The pass only folds a
+// multiplication by a known positive constant, so the loop must be left
+// unchanged.
+func.func @no_fold_self_mul() {
+  %c0 = arith.constant 0 : index
+  %c8 = arith.constant 8 : index
+  %c1 = arith.constant 1 : index
+  scf.for %i = %c0 to %c8 step %c1 {
+    %0 = arith.muli %i, %i : index
+    "test.sink"(%0) : (index) -> ()
+  }
+  return
+}
+
+// CHECK-LABEL: func @no_fold_self_mul
+// CHECK:       %[[C0:.*]] = arith.constant 0 : index
+// CHECK:       %[[C8:.*]] = arith.constant 8 : index
+// CHECK:       %[[C1:.*]] = arith.constant 1 : index
+// CHECK:       scf.for %[[I:.*]] = %[[C0]] to %[[C8]] step %[[C1]] {
+// CHECK:         %[[R:.*]] = arith.muli %[[I]], %[[I]] : index
+// CHECK:         "test.sink"(%[[R]])



More information about the Mlir-commits mailing list