[Mlir-commits] [mlir] [MLIR][SCF] Fix ForLoopRangeFolding miscompile with non-positive MulIOp multiplier (PR #188995)
Mehdi Amini
llvmlistbot at llvm.org
Fri Mar 27 06:33:26 PDT 2026
https://github.com/joker-eph created https://github.com/llvm/llvm-project/pull/188995
The scf-for-loop-range-folding pass transforms loops of the form
for (i = lb; i < ub; i += step) { use(i * c) }
into
for (j = lb*c; j < ub*c; j += step*c) { use(j) }
This transformation is only valid when c is strictly positive, since scf.for requires a positive step. When c is zero or negative, the new step becomes zero or effectively negative (wrapping in unsigned arithmetic for index type), producing an incorrect loop.
Add a guard that restricts the MulIOp folding to cases where the loop-invariant multiplier is a statically known positive integer constant. Non-constant loop-invariant multipliers are also excluded since their sign cannot be determined at compile time.
Fixes #56235
Fixes #116664
Assisted-by: Claude Code
>From 6b89ee850af8f74284befd2e952437aa8b166740 Mon Sep 17 00:00:00 2001
From: Mehdi Amini <joker.eph at gmail.com>
Date: Thu, 26 Mar 2026 16:58:21 -0700
Subject: [PATCH] [MLIR][SCF] Fix ForLoopRangeFolding miscompile with
non-positive MulIOp multiplier
The scf-for-loop-range-folding pass transforms loops of the form
for (i = lb; i < ub; i += step) { use(i * c) }
into
for (j = lb*c; j < ub*c; j += step*c) { use(j) }
This transformation is only valid when c is strictly positive, since
scf.for requires a positive step. When c is zero or negative, the new
step becomes zero or effectively negative (wrapping in unsigned
arithmetic for index type), producing an incorrect loop.
Add a guard that restricts the MulIOp folding to cases where the
loop-invariant multiplier is a statically known positive integer
constant. Non-constant loop-invariant multipliers are also excluded
since their sign cannot be determined at compile time.
Fixes #56235
Fixes #116664
Assisted-by: Claude Code
---
.../SCF/Transforms/LoopRangeFolding.cpp | 12 ++-
mlir/test/Dialect/SCF/loop-range.mlir | 87 +++++++++++++++++++
2 files changed, 98 insertions(+), 1 deletion(-)
diff --git a/mlir/lib/Dialect/SCF/Transforms/LoopRangeFolding.cpp b/mlir/lib/Dialect/SCF/Transforms/LoopRangeFolding.cpp
index 0c2a71eb25fe5..d4b4c4cfc063f 100644
--- a/mlir/lib/Dialect/SCF/Transforms/LoopRangeFolding.cpp
+++ b/mlir/lib/Dialect/SCF/Transforms/LoopRangeFolding.cpp
@@ -16,6 +16,7 @@
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/Dialect/SCF/Transforms/Transforms.h"
#include "mlir/Dialect/SCF/Utils/Utils.h"
+#include "mlir/Dialect/Utils/StaticValueUtils.h"
#include "mlir/IR/IRMapping.h"
namespace mlir {
@@ -71,7 +72,16 @@ void ForLoopRangeFolding::runOnOperation() {
op.setLowerBound(lbFold->getResult(0));
op.setUpperBound(ubFold->getResult(0));
- } else if (isa<arith::MulIOp>(user)) {
+ } 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
+ // step (scf.for requires a strictly positive step).
+ Value multiplier =
+ (mulOp.getLhs() == indVar) ? mulOp.getRhs() : mulOp.getLhs();
+ std::optional<int64_t> multiplierVal = getConstantIntValue(multiplier);
+ if (!multiplierVal || *multiplierVal <= 0)
+ break;
+
Operation *lbFold = b.clone(*user, lbMap);
Operation *ubFold = b.clone(*user, ubMap);
Operation *stepFold = b.clone(*user, stepMap);
diff --git a/mlir/test/Dialect/SCF/loop-range.mlir b/mlir/test/Dialect/SCF/loop-range.mlir
index e50db8ae9f35b..79e74f430618a 100644
--- a/mlir/test/Dialect/SCF/loop-range.mlir
+++ b/mlir/test/Dialect/SCF/loop-range.mlir
@@ -131,3 +131,90 @@ func.func @fold_only_first_add(%arg0: memref<?xi32>, %arg1: index, %arg2: index)
// CHECK: %[[I4:.*]] = memref.load %[[ARG0]]{{\[}}%[[I3]]
// CHECK: %[[I5:.*]] = arith.muli %[[I4]], %[[I4]] : i32
// CHECK: memref.store %[[I5]], %[[ARG0]]{{\[}}%[[I3]]
+
+// Do not fold arith.muli with a negative multiplier: the resulting loop step
+// would be negative, which is invalid for scf.for.
+func.func @no_fold_negative_mul() {
+ %cm1 = arith.constant -1 : index
+ %c0 = arith.constant 0 : index
+ %c10 = arith.constant 10 : index
+ %c1 = arith.constant 1 : index
+ scf.for %i = %c0 to %c10 step %c1 {
+ %0 = arith.muli %i, %cm1 : index
+ "test.sink"(%0) : (index) -> ()
+ }
+ return
+}
+
+// CHECK-LABEL: func @no_fold_negative_mul
+// CHECK: %[[CM1:.*]] = arith.constant -1 : index
+// CHECK: %[[C0:.*]] = arith.constant 0 : index
+// CHECK: %[[C10:.*]] = arith.constant 10 : index
+// CHECK: %[[C1:.*]] = arith.constant 1 : index
+// CHECK: scf.for %[[I:.*]] = %[[C0]] to %[[C10]] step %[[C1]] {
+// CHECK: %[[R:.*]] = arith.muli %[[I]], %[[CM1]] : index
+// CHECK: "test.sink"(%[[R]])
+
+// Do not fold arith.muli with a negative multiplier when the induction variable
+// is on the RHS.
+func.func @no_fold_negative_mul_indvar_rhs() {
+ %cm1 = arith.constant -1 : index
+ %c0 = arith.constant 0 : index
+ %c10 = arith.constant 10 : index
+ %c1 = arith.constant 1 : index
+ scf.for %i = %c0 to %c10 step %c1 {
+ %0 = arith.muli %cm1, %i : index
+ "test.sink"(%0) : (index) -> ()
+ }
+ return
+}
+
+// CHECK-LABEL: func @no_fold_negative_mul_indvar_rhs
+// CHECK: %[[CM1:.*]] = arith.constant -1 : index
+// CHECK: %[[C0:.*]] = arith.constant 0 : index
+// CHECK: %[[C10:.*]] = arith.constant 10 : index
+// CHECK: %[[C1:.*]] = arith.constant 1 : index
+// CHECK: scf.for %[[I:.*]] = %[[C0]] to %[[C10]] step %[[C1]] {
+// CHECK: %[[R:.*]] = arith.muli %[[CM1]], %[[I]] : index
+// CHECK: "test.sink"(%[[R]])
+
+// Do not fold arith.muli with a zero multiplier.
+func.func @no_fold_zero_mul() {
+ %c0 = arith.constant 0 : index
+ %c10 = arith.constant 10 : index
+ %c1 = arith.constant 1 : index
+ scf.for %i = %c0 to %c10 step %c1 {
+ %0 = arith.muli %i, %c0 : index
+ "test.sink"(%0) : (index) -> ()
+ }
+ return
+}
+
+// CHECK-LABEL: func @no_fold_zero_mul
+// CHECK: %[[C0:.*]] = arith.constant 0 : index
+// CHECK: %[[C10:.*]] = arith.constant 10 : index
+// CHECK: %[[C1:.*]] = arith.constant 1 : index
+// CHECK: scf.for %[[I:.*]] = %[[C0]] to %[[C10]] step %[[C1]] {
+// CHECK: %[[R:.*]] = arith.muli %[[I]], %[[C0]] : index
+// CHECK: "test.sink"(%[[R]])
+
+// Do not fold arith.muli when the multiplier is a non-constant loop-invariant
+// value, since its sign is unknown at compile time.
+func.func @no_fold_runtime_mul(%multiplier: index) {
+ %c0 = arith.constant 0 : index
+ %c10 = arith.constant 10 : index
+ %c1 = arith.constant 1 : index
+ scf.for %i = %c0 to %c10 step %c1 {
+ %0 = arith.muli %i, %multiplier : index
+ "test.sink"(%0) : (index) -> ()
+ }
+ return
+}
+
+// CHECK-LABEL: func @no_fold_runtime_mul
+// CHECK: %[[C0:.*]] = arith.constant 0 : index
+// CHECK: %[[C10:.*]] = arith.constant 10 : index
+// CHECK: %[[C1:.*]] = arith.constant 1 : index
+// CHECK: scf.for %[[I:.*]] = %[[C0]] to %[[C10]] step %[[C1]] {
+// CHECK: %[[R:.*]] = arith.muli %[[I]], {{.*}} : index
+// CHECK: "test.sink"(%[[R]])
More information about the Mlir-commits
mailing list