[Mlir-commits] [mlir] 5db3655 - [mlir][scf] Fix scf.for value bounds for empty and unsigned loops (#226761)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Fri Oct 2 18:45:59 PDT 2026
Author: Dhairyashil R G
Date: 2026-10-03T01:45:51Z
New Revision: 5db36554489df699c4e7449c44b9c4c465488f20
URL: https://github.com/llvm/llvm-project/commit/5db36554489df699c4e7449c44b9c4c465488f20
DIFF: https://github.com/llvm/llvm-project/commit/5db36554489df699c4e7449c44b9c4c465488f20.diff
LOG: [mlir][scf] Fix scf.for value bounds for empty and unsigned loops (#226761)
Value bounds assumes that an `scf.for` result is `init + ceildiv(ub -
lb, step) * (yield - iter_arg)`. This is only true if the loop runs at
least once. When the loop does not run, `ceildiv(ub - lb, step)` is
negative instead of 0. For unsigned loops, `ub - lb` is computed as if
the bounds were signed, which also gives a wrong trip count. The
induction variable bounds of unsigned loops have the same signed
problem.
Example:
```mlir
%r = scf.for %iv = %c5 to %c2 step %c1 iter_args(%arg = %c7) -> index {
%n = arith.addi %arg, %c1 : index
scf.yield %n : index
}
%m = affine.min affine_map<(d0) -> (d0, 5)>(%r)
```
The loop does not run, so `%r` is 7 and `%m` should be 5. Value bounds
thinks `%r` is 4, so `affine-simplify-min-max` replaces `%m` with `%r`
and the result becomes 7.
Canonicalization usually removes a constant empty loop like this one,
but the analysis should not depend on that. The unsigned case is not
removed by canonicalization and gives wrong results too.
With this change, the result formula is used only for signed loops that
are known to run at least once, and unsigned loops get no induction
variable bounds.
Assisted-by: Claude
Co-authored-by: Matthias Springer <me at m-sp.org>
Added:
Modified:
mlir/lib/Dialect/SCF/IR/ValueBoundsOpInterfaceImpl.cpp
mlir/test/Dialect/SCF/value-bounds-op-interface-impl.mlir
Removed:
################################################################################
diff --git a/mlir/lib/Dialect/SCF/IR/ValueBoundsOpInterfaceImpl.cpp b/mlir/lib/Dialect/SCF/IR/ValueBoundsOpInterfaceImpl.cpp
index 496a7b036e65d1..171ab3f19e09af 100644
--- a/mlir/lib/Dialect/SCF/IR/ValueBoundsOpInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/SCF/IR/ValueBoundsOpInterfaceImpl.cpp
@@ -101,6 +101,16 @@ struct ForOpInterface
if (dim.has_value() || isa<BlockArgument>(value))
return;
+ // The closed form below holds only if the loop runs at least once (the
+ // trip count is not clamped at 0) and the bounds are compared as signed
+ // integers.
+ if (forOp.getUnsignedCmp() ||
+ !cstr.populateAndCompare(
+ forOp.getUpperBound(),
+ ValueBoundsConstraintSet::ComparisonOperator::GT,
+ forOp.getLowerBound()))
+ return;
+
// `value` is result of `forOp`, we can prove that:
// %result == %init_arg + trip_count * (%yielded_value - %iter_arg).
// Where trip_count is (ub - lb) / step.
@@ -117,6 +127,9 @@ struct ForOpInterface
auto forOp = cast<ForOp>(op);
if (value == forOp.getInductionVar()) {
+ // The IV bounds are signed; unsigned loops are not modeled.
+ if (forOp.getUnsignedCmp())
+ return;
return populateIVBounds(forOp.getLowerBound(), forOp.getUpperBound(),
forOp.getStep(), value, cstr);
}
diff --git a/mlir/test/Dialect/SCF/value-bounds-op-interface-impl.mlir b/mlir/test/Dialect/SCF/value-bounds-op-interface-impl.mlir
index 60fe96d52d20b2..ab3e0b3989cb16 100644
--- a/mlir/test/Dialect/SCF/value-bounds-op-interface-impl.mlir
+++ b/mlir/test/Dialect/SCF/value-bounds-op-interface-impl.mlir
@@ -388,3 +388,57 @@ func.func @scf_forall_computed_upper_bound(%x: index) {
}
return
}
+
+// -----
+
+// The loop does not run (5 to 2), so %0 is 3. ceildiv(ub - lb, step) is
+// negative and must not be used as the trip count: %0 is not 6.
+func.func @scf_for_result_empty() {
+ %c1 = arith.constant 1 : index
+ %c2 = arith.constant 2 : index
+ %c3 = arith.constant 3 : index
+ %c5 = arith.constant 5 : index
+ %c6 = arith.constant 6 : index
+ %0 = scf.for %iv = %c5 to %c2 step %c1 iter_args(%arg = %c3) -> index {
+ %1 = arith.subi %arg, %c1 : index
+ scf.yield %1 : index
+ }
+ // expected-error @below{{unknown}}
+ "test.compare"(%0, %c6) {cmp = "EQ"} : (index, index) -> ()
+ return
+}
+
+// -----
+
+// Unsigned loop from 2^64 - 1 to 5: the loop does not run, so %0 is 3. The
+// signed
diff erence of the bounds must not be used as the trip count: %0 is
+// not 9.
+func.func @scf_for_unsigned_result() {
+ %c1 = arith.constant 1 : index
+ %c3 = arith.constant 3 : index
+ %c5 = arith.constant 5 : index
+ %c9 = arith.constant 9 : index
+ %lb = arith.constant -1 : index
+ %0 = scf.for unsigned %iv = %lb to %c5 step %c1 iter_args(%arg = %c3) -> index {
+ %1 = arith.addi %arg, %c1 : index
+ scf.yield %1 : index
+ }
+ // expected-error @below{{unknown}}
+ "test.compare"(%0, %c9) {cmp = "EQ"} : (index, index) -> ()
+ return
+}
+
+// -----
+
+// Unsigned loop: %iv is 1, 2^62 + 1 and 2^63 + 1. Signed bounds do not hold.
+func.func @scf_for_unsigned_induction_var() {
+ %c1 = arith.constant 1 : index
+ %ub = arith.constant -4611686018427387904 : index
+ %step = arith.constant 4611686018427387904 : index
+ scf.for unsigned %iv = %c1 to %ub step %step {
+ // expected-error @below{{could not reify bound}}
+ %0 = "test.reify_bound"(%iv) {type = "UB", constant} : (index) -> (index)
+ "test.some_use"(%0) : (index) -> ()
+ }
+ return
+}
More information about the Mlir-commits
mailing list