From 00e4dedef81a31a5c8b3386f2a3253412903140a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 13 Aug 2026 16:24:07 -0700 Subject: [PATCH 1/4] test --- .../lit/passes/constraint-analysis-loops.wast | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index c04a163c948..2ec1fd0e211 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -1060,4 +1060,141 @@ ) ) ) + + ;; CHECK: (func $nested (type $1) (param $len i32) + ;; CHECK-NEXT: (local $i i32) + ;; CHECK-NEXT: (local $j i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (loop $outer + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.ge_s + ;; CHECK-NEXT: (local.get $i) + ;; CHECK-NEXT: (local.get $len) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $out) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $i + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $i) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $in + ;; CHECK-NEXT: (local.set $j + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (loop $inner + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.ge_s + ;; CHECK-NEXT: (local.get $j) + ;; CHECK-NEXT: (local.get $i) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $in) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $j + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $j) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $inner) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $outer) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $nested (param $len i32) + (local $i i32) + (local $j i32) + (block $out + ;; Outer loop, i goes from 0 to len. + (loop $outer + (if + (i32.ge_s + (local.get $i) + (local.get $len) + ) + (then + (br $out) + ) + ) + ;; We can infer both of these to be 1. + (drop + (i32.lt_s + (local.get $i) + (local.get $len) + ) + ) + (drop + (i32.ge_s + (local.get $i) + (i32.const 0) + ) + ) + (local.set $i + (i32.add + (local.get $i) + (i32.const 1) + ) + ) + + ;; Inner loop, j goes from 0 to i. + (block $in + (local.set $j + (i32.const 0) + ) + (loop $inner + (if + (i32.ge_s + (local.get $j) + (local.get $i) + ) + (then + (br $in) + ) + ) + ;; We can infer both of these to be 1. + (drop + (i32.lt_s + (local.get $j) + (local.get $i) + ) + ) + (drop + (i32.ge_s + (local.get $j) + (i32.const 0) + ) + ) + (local.set $j + (i32.add + (local.get $j) + (i32.const 1) + ) + ) + (br $inner) + ) + ) + + (br $outer) + ) + ) + ) ) From 2dcd4e2f50c38df039389376f23f4e49d992ec76 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 13 Aug 2026 16:39:07 -0700 Subject: [PATCH 2/4] test2 --- .../lit/passes/constraint-analysis-loops.wast | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 2ec1fd0e211..c0929a314f1 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -1197,4 +1197,144 @@ ) ) ) + + ;; CHECK: (func $nested-2 (type $1) (param $len i32) + ;; CHECK-NEXT: (local $i i32) + ;; CHECK-NEXT: (local $j i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (loop $outer + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.ge_s + ;; CHECK-NEXT: (local.get $i) + ;; CHECK-NEXT: (local.get $len) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $out) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $i + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $i) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $in + ;; CHECK-NEXT: (local.set $j + ;; CHECK-NEXT: (local.get $i) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (loop $inner + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.ge_s + ;; CHECK-NEXT: (local.get $j) + ;; CHECK-NEXT: (local.get $len) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $in) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.ge_s + ;; CHECK-NEXT: (local.get $j) + ;; CHECK-NEXT: (local.get $i) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $j + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $j) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $inner) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $outer) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $nested-2 (param $len i32) + ;; As above, but the inner loop is i..len rather than 0..i + (local $i i32) + (local $j i32) + (block $out + (loop $outer + (if + (i32.ge_s + (local.get $i) + (local.get $len) + ) + (then + (br $out) + ) + ) + ;; We can infer both of these to be 1. + (drop + (i32.lt_s + (local.get $i) + (local.get $len) + ) + ) + (drop + (i32.ge_s + (local.get $i) + (i32.const 0) + ) + ) + (local.set $i + (i32.add + (local.get $i) + (i32.const 1) + ) + ) + + (block $in + (local.set $j + (local.get $i) + ) + (loop $inner + (if + (i32.ge_s + (local.get $j) + (local.get $len) + ) + (then + (br $in) + ) + ) + ;; We don't infer the second of these, as we don't extend ranges for + ;; non-constant initial values yet. TODO + (drop + (i32.lt_s + (local.get $j) + (local.get $len) + ) + ) + (drop + (i32.ge_s + (local.get $j) + (local.get $i) + ) + ) + (local.set $j + (i32.add + (local.get $j) + (i32.const 1) + ) + ) + (br $inner) + ) + ) + + (br $outer) + ) + ) + ) ) From deaf2d593cf2dc6841de34e922a6b9bf310b9106 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 13 Aug 2026 16:42:49 -0700 Subject: [PATCH 3/4] test --- .../lit/passes/constraint-analysis-loops.wast | 142 +++++++++++++++++- 1 file changed, 140 insertions(+), 2 deletions(-) diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index c0929a314f1..9e503b01e95 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -1198,7 +1198,145 @@ ) ) - ;; CHECK: (func $nested-2 (type $1) (param $len i32) + ;; CHECK: (func $nested-increment-i-later (type $1) (param $len i32) + ;; CHECK-NEXT: (local $i i32) + ;; CHECK-NEXT: (local $j i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (loop $outer + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.ge_s + ;; CHECK-NEXT: (local.get $i) + ;; CHECK-NEXT: (local.get $len) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $out) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $in + ;; CHECK-NEXT: (local.set $j + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (loop $inner + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.ge_s + ;; CHECK-NEXT: (local.get $j) + ;; CHECK-NEXT: (local.get $i) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $in) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $j + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $j) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $inner) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $i + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $i) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $outer) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $nested-increment-i-later (param $len i32) + ;; As $nested, but i is incremented after the inner loop. + (local $i i32) + (local $j i32) + (block $out + (loop $outer + (if + (i32.ge_s + (local.get $i) + (local.get $len) + ) + (then + (br $out) + ) + ) + ;; We can infer both of these to be 1. + (drop + (i32.lt_s + (local.get $i) + (local.get $len) + ) + ) + (drop + (i32.ge_s + (local.get $i) + (i32.const 0) + ) + ) + + ;; Inner loop. + (block $in + (local.set $j + (i32.const 0) + ) + (loop $inner + (if + (i32.ge_s + (local.get $j) + (local.get $i) + ) + (then + (br $in) + ) + ) + ;; We can infer both of these to be 1. + (drop + (i32.lt_s + (local.get $j) + (local.get $i) + ) + ) + (drop + (i32.ge_s + (local.get $j) + (i32.const 0) + ) + ) + (local.set $j + (i32.add + (local.get $j) + (i32.const 1) + ) + ) + (br $inner) + ) + ) + + ;; Outer loop's increment+branch. + (local.set $i + (i32.add + (local.get $i) + (i32.const 1) + ) + ) + (br $outer) + ) + ) + ) + + ;; CHECK: (func $nested-from-nonconstant-no (type $1) (param $len i32) ;; CHECK-NEXT: (local $i i32) ;; CHECK-NEXT: (local $j i32) ;; CHECK-NEXT: (block $out @@ -1260,7 +1398,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $nested-2 (param $len i32) + (func $nested-from-nonconstant-no (param $len i32) ;; As above, but the inner loop is i..len rather than 0..i (local $i i32) (local $j i32) From bacc4f9c4185008e297b8bd74b49ddfdbd0019a0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 13 Aug 2026 16:52:13 -0700 Subject: [PATCH 4/4] clarify --- src/passes/ConstraintAnalysis.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index fbcba1d422c..0f55dc871ca 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -609,12 +609,17 @@ struct ConstraintAnalysis // handling constants here, we only extend ranges for loop variables (and // extending ranges can have downsides, so it is good we do it in a // targeted way). - // * The case of a constant is exactly what we want to optimize here: most - // typical loop patterns iterate from 0 or 1 or such. + // * The case of a constant for the initial value N is exactly what we want + // to optimize here: most typical loop patterns iterate from 0 or 1 or + // such. // // So things work out perfectly here: constants are safe to optimize (no // risk of extension causing downsides) and are exactly what we want to // optimize. + // + // (Note that there is no limitation on *M*, the upper bound of the loop: we + // can iterate up to a constant or to a local. I.e. loops from 0 to 100 and + // 5 to x work, but not loops from x to 100 or x to y.) auto N = constraints.get(branch.local).getLiteral(); if (!N) { return false;