From 548153cd0444dcbf1f5ed2548c3944dc88d03bca Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 8 Jul 2026 11:25:01 -0700 Subject: [PATCH 1/6] start --- src/ir/constraint.cpp | 14 +++++- test/lit/passes/constraint-analysis.wast | 57 ++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index a1be14c6bf0..2f946354006 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -270,12 +270,22 @@ void LocalConstraint::flip() { void BasicBlockConstraintMap::set(Index index, const Constraint& c) { assert(!unreachable); eraseStaleRefs(index); + + // Set the constraint. map[index].set(c); noteRefs(index, c); - // If the constraint refers to another local, add it there too. - if (std::holds_alternative(c.term)) { + if (auto* other = std::get_if(&c.term)) { + // The constraint refers to another local: add it there too. approximateAndInternal(index, c, true); + + // If this constraint is simply "== x", then we are equal to that other + // local x, and can copy its constraints. + if (c.op == Abstract::Eq) { + for (auto& otherC : get(*other)) { + approximateAnd(index, otherC); + } + } } } diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index ce5d59afa7b..17d6c12cc53 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -2400,4 +2400,61 @@ ) ) ) + + ;; CHECK: (func $multi-local-copy (type $1) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (local $y i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $y + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $multi-local-copy (type $1) + ;; OPTIN-NEXT: (local $x i32) + ;; OPTIN-NEXT: (local $y i32) + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (i32.const 10) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (local.set $y + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $multi-local-copy + (local $x i32) + (local $y i32) + ;; x is 10, y is copied. + (local.set $x + (i32.const 10) + ) + (local.set $y + (local.get $x) + ) + ;; Verify those values. + (drop + (i32.eq + (local.get $x) + (i32.const 10) + ) + ) + (drop + (i32.eq + (local.get $y) + (i32.const 10) + ) + ) + ) ) From daadc57561047d2fd3fff38cf1071986b87b759d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 8 Jul 2026 12:28:52 -0700 Subject: [PATCH 2/6] fnish --- src/passes/ConstraintAnalysis.cpp | 4 + test/lit/passes/constraint-analysis.wast | 140 +++++++++++++++++++++-- 2 files changed, 137 insertions(+), 7 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 5c2fe99b875..3428ac22f5b 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -54,6 +54,10 @@ struct Info { // For each local index, we track the constraints we know about it. We only do // so at the start of each block, which is enough for the analysis below. BasicBlockConstraintMap startConstraints; + + void dump(Function* func) { + std::cout << "Info{" << actions.size() << ", " << brancher << ", " << startConstraints << "}\n"; + } }; struct ConstraintAnalysis diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index 17d6c12cc53..5ff024ca8f4 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -1008,7 +1008,7 @@ ) ) - ;; CHECK: (func $conditional-binary-nesting (type $4) (param $x i32) (param $y i32) + ;; CHECK: (func $conditional-binary-nesting (type $3) (param $x i32) (param $y i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.eq ;; CHECK-NEXT: (local.get $x) @@ -1038,7 +1038,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $conditional-binary-nesting (type $4) (param $x i32) (param $y i32) + ;; OPTIN: (func $conditional-binary-nesting (type $3) (param $x i32) (param $y i32) ;; OPTIN-NEXT: (if ;; OPTIN-NEXT: (i32.eq ;; OPTIN-NEXT: (local.get $x) @@ -1510,7 +1510,7 @@ ) ) - ;; CHECK: (func $br_on_null (type $3) (param $param anyref) + ;; CHECK: (func $br_on_null (type $4) (param $param anyref) ;; CHECK-NEXT: (block $block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.is_null @@ -1531,7 +1531,7 @@ ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $br_on_null (type $3) (param $param anyref) + ;; OPTIN: (func $br_on_null (type $4) (param $param anyref) ;; OPTIN-NEXT: (block $block ;; OPTIN-NEXT: (drop ;; OPTIN-NEXT: (ref.is_null @@ -1581,7 +1581,7 @@ ) ) - ;; CHECK: (func $br_on_non_null (type $3) (param $param anyref) + ;; CHECK: (func $br_on_non_null (type $4) (param $param anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $block (result (ref any)) ;; CHECK-NEXT: (drop @@ -1602,7 +1602,7 @@ ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $br_on_non_null (type $3) (param $param anyref) + ;; OPTIN: (func $br_on_non_null (type $4) (param $param anyref) ;; OPTIN-NEXT: (drop ;; OPTIN-NEXT: (block $block (result (ref any)) ;; OPTIN-NEXT: (drop @@ -2443,7 +2443,7 @@ (local.set $y (local.get $x) ) - ;; Verify those values. + ;; Verify those values: both x and y are equal to 10. (drop (i32.eq (local.get $x) @@ -2457,4 +2457,130 @@ ) ) ) + + ;; CHECK: (func $multi-local-copy-if (type $3) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $y + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $y + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $multi-local-copy-if (type $3) (param $x i32) (param $y i32) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $y) + ;; OPTIN-NEXT: (i32.const 42) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (local.set $y + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $y) + ;; OPTIN-NEXT: (i32.const 42) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (i32.const 42) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $multi-local-copy-if (param $x i32) (param $y i32) + (if + (i32.eq + (local.get $x) + (i32.const 42) + ) + (then + ;; We don't know anything yet. + (drop + (i32.eq + (local.get $y) + (i32.const 42) + ) + ) + ;; Copy x into y, and see that it is now equal to 42. + (local.set $y + (local.get $x) + ) + (drop + (i32.eq + (local.get $y) + (i32.const 42) + ) + ) + ;; x was not changed (equal to 42) + (drop + (i32.eq + (local.get $x) + (i32.const 42) + ) + ) + ) + (else + ;; We don't know anything yet. + (drop + (i32.eq + (local.get $y) + (i32.const 42) + ) + ) + ;; Copy x into y, and see that it is now *not* equal to 42. + (local.set $y + (local.get $x) + ) + (drop + (i32.eq + (local.get $y) + (i32.const 42) + ) + ) + ;; x was not changed (not equal to 42) + (drop + (i32.eq + (local.get $x) + (i32.const 42) + ) + ) + ) + ) + ) ) From 145d54b5deac604768c9ffe8d37815c51c4d1398 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 8 Jul 2026 12:28:57 -0700 Subject: [PATCH 3/6] format --- src/passes/ConstraintAnalysis.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 3428ac22f5b..25173ee8d9f 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -56,7 +56,8 @@ struct Info { BasicBlockConstraintMap startConstraints; void dump(Function* func) { - std::cout << "Info{" << actions.size() << ", " << brancher << ", " << startConstraints << "}\n"; + std::cout << "Info{" << actions.size() << ", " << brancher << ", " + << startConstraints << "}\n"; } }; From 39667e2222c27b46f42759bdaddb087dc665a144 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 9 Jul 2026 09:51:07 -0700 Subject: [PATCH 4/6] unify logic for copying in approximateAnd --- src/ir/constraint.cpp | 74 +++++++---- src/ir/constraint.h | 5 +- test/lit/passes/constraint-analysis.wast | 154 +++++++++++++++++++++-- 3 files changed, 195 insertions(+), 38 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index ff69fa7062e..8d7b66911ca 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -274,25 +274,19 @@ void LocalConstraint::flip() { } void BasicBlockConstraintMap::set(Index index, const Constraint& c) { + // We should not set values in unreachable code. assert(!unreachable); - eraseStaleRefs(index); - - // Set the constraint. - map[index].set(c); - noteRefs(index, c); - - if (auto* other = std::get_if(&c.term)) { - // The constraint refers to another local: add it there too. - approximateAndInternal(index, c, true); - // If this constraint is simply "== x", then we are equal to that other - // local x, and can copy its constraints. - if (c.op == Abstract::Eq) { - for (auto& otherC : get(*other)) { - approximateAnd(index, otherC); - } - } - } + // Clear the old state. + eraseStaleRefs(index); + map[index].setProvesNothing(); + // Note that this last line puts us in a temporarily invalid state: we do not + // normally store a constraint of proves-nothing in the map. Doing it this + // way, until approximateAnd adds the constraint, is more efficient than + // erasing and re-adding. + + // Apply the constraint. + approximateAnd(index, c); } void BasicBlockConstraintMap::setProvesNothing(Index index) { @@ -329,7 +323,8 @@ void BasicBlockConstraintMap::approximateOr( void BasicBlockConstraintMap::approximateAndInternal(Index index, const Constraint& c, - bool flip) { + bool flip, + bool isCopy) { Constraint actual = c; if (flip) { LocalConstraint flipped{index, c}; @@ -338,10 +333,28 @@ void BasicBlockConstraintMap::approximateAndInternal(Index index, actual = flipped.constraint; } - auto combined = get(index); - combined.approximateAnd(actual); + // Never add constraints to ourselves (x == x, etc., which can happen due to + // copying/flipping). + if (auto* other = std::get_if(&actual.term)) { + if (*other == index) { + return; + } + } + + // Refer to the constraints for this index. If this is the first access of + // the local, then we insert a new item into the map, which has a default of + // proxesEverything, which we need to flip (provesEverything cannot otherwise + // be found in the map, as we never store it). + auto& indexConstraints = map[index]; + if (indexConstraints.provesEverything()) { + indexConstraints.setProvesNothing(); + // As in ::set(), this makes the map temporarily invalid until the + // approximateAnd, as we don't store proves-nothing in the map, normally. + } + + indexConstraints.approximateAnd(actual); - if (combined.provesEverything()) { + if (indexConstraints.provesEverything()) { // We just proved we are in unreachable code. unreachable = true; map.clear(); @@ -350,10 +363,7 @@ void BasicBlockConstraintMap::approximateAndInternal(Index index, // We just added a constraint, so we can prove something (we may lose some // information as this is an approximate AND, but we cannot lose it all). - assert(!combined.provesNothing()); - - // Otherwise, this is an interesting state; set it. - map[index] = std::move(combined); + assert(!indexConstraints.provesNothing()); // Add a ref of what we are adding. Note that the approximation above may end // up not actually adding this, or adding only part of this, but it is safe to @@ -363,7 +373,19 @@ void BasicBlockConstraintMap::approximateAndInternal(Index index, // If this is not the flipped version, and it refers to a local, add the // flipped one too. if (!flip && std::holds_alternative(actual.term)) { - approximateAndInternal(index, actual, true); + approximateAndInternal(index, actual, true, isCopy); + } + + // If this constraint is simply "== x", then we are equal to that other local + // x, and can copy its constraints (if we are not already such a copy). + if (!isCopy) { + if (auto* other = std::get_if(&actual.term)) { + if (actual.op == Abstract::Eq) { + for (auto& otherC : get(*other)) { + approximateAndInternal(index, otherC, false, true); + } + } + } } } diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 31a5dd04fed..ddb1cc748eb 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -287,9 +287,10 @@ struct BasicBlockConstraintMap { // Internal version, with a flag to flip the constraint. Whenever we apply // e.g. x == y, we also apply y == x to y, to maintain the invariant described // above. When flip is true, we flip the constraint and apply it to the other - // index (y == x, in this example). + // index (y == x, in this example). When isCopy is true, we are a copied + // constraint from another local, and we do not need to add new copies of it. void - approximateAndInternal(Index index, const Constraint& c, bool flip = false); + approximateAndInternal(Index index, const Constraint& c, bool flip = false, bool isCopy = false); }; std::ostream& operator<<(std::ostream& o, const Constraint& c); diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index 5ff024ca8f4..c5c7eb93245 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -1008,7 +1008,7 @@ ) ) - ;; CHECK: (func $conditional-binary-nesting (type $3) (param $x i32) (param $y i32) + ;; CHECK: (func $conditional-binary-nesting (type $2) (param $x i32) (param $y i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.eq ;; CHECK-NEXT: (local.get $x) @@ -1038,7 +1038,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $conditional-binary-nesting (type $3) (param $x i32) (param $y i32) + ;; OPTIN: (func $conditional-binary-nesting (type $2) (param $x i32) (param $y i32) ;; OPTIN-NEXT: (if ;; OPTIN-NEXT: (i32.eq ;; OPTIN-NEXT: (local.get $x) @@ -1652,7 +1652,7 @@ ) ) - ;; CHECK: (func $local-changes (type $2) (param $x i32) (param $y i32) (param $z i32) + ;; CHECK: (func $local-changes (type $3) (param $x i32) (param $y i32) (param $z i32) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) @@ -1708,7 +1708,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $local-changes (type $2) (param $x i32) (param $y i32) (param $z i32) + ;; OPTIN: (func $local-changes (type $3) (param $x i32) (param $y i32) (param $z i32) ;; OPTIN-NEXT: (local.set $x ;; OPTIN-NEXT: (local.get $y) ;; OPTIN-NEXT: ) @@ -1838,7 +1838,7 @@ ) ) - ;; CHECK: (func $local-changes-2 (type $2) (param $x i32) (param $y i32) (param $z i32) + ;; CHECK: (func $local-changes-2 (type $3) (param $x i32) (param $y i32) (param $z i32) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) @@ -1894,7 +1894,7 @@ ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $local-changes-2 (type $2) (param $x i32) (param $y i32) (param $z i32) + ;; OPTIN: (func $local-changes-2 (type $3) (param $x i32) (param $y i32) (param $z i32) ;; OPTIN-NEXT: (local.set $x ;; OPTIN-NEXT: (local.get $y) ;; OPTIN-NEXT: ) @@ -2228,7 +2228,7 @@ ) ) - ;; CHECK: (func $local-changes-ne (type $2) (param $x i32) (param $y i32) (param $z i32) + ;; CHECK: (func $local-changes-ne (type $3) (param $x i32) (param $y i32) (param $z i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.ne ;; CHECK-NEXT: (local.get $x) @@ -2280,7 +2280,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $local-changes-ne (type $2) (param $x i32) (param $y i32) (param $z i32) + ;; OPTIN: (func $local-changes-ne (type $3) (param $x i32) (param $y i32) (param $z i32) ;; OPTIN-NEXT: (if ;; OPTIN-NEXT: (i32.ne ;; OPTIN-NEXT: (local.get $x) @@ -2458,7 +2458,7 @@ ) ) - ;; CHECK: (func $multi-local-copy-if (type $3) (param $x i32) (param $y i32) + ;; CHECK: (func $multi-local-copy-if (type $2) (param $x i32) (param $y i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.eq ;; CHECK-NEXT: (local.get $x) @@ -2500,7 +2500,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $multi-local-copy-if (type $3) (param $x i32) (param $y i32) + ;; OPTIN: (func $multi-local-copy-if (type $2) (param $x i32) (param $y i32) ;; OPTIN-NEXT: (drop ;; OPTIN-NEXT: (i32.eq ;; OPTIN-NEXT: (local.get $y) @@ -2583,4 +2583,138 @@ ) ) ) + + ;; CHECK: (func $multi-local-copy-if-2 (type $2) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $multi-local-copy-if-2 (type $2) (param $x i32) (param $y i32) + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (i32.const 42) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (if + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (local.get $y) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (then + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $multi-local-copy-if-2 (param $x i32) (param $y i32) + ;; As above, but the if's condition is on x == y, so that is where we + ;; apply equality between them. + (local.set $x + (i32.const 42) + ) + (if + (i32.eq + (local.get $x) + (local.get $y) + ) + (then + ;; This is true. + (drop + (i32.eq + (local.get $y) + (i32.const 42) + ) + ) + ;; x was not changed, so this is still true. + (drop + (i32.eq + (local.get $x) + (i32.const 42) + ) + ) + ) + ) + ) + + ;; CHECK: (func $multi-local-copy-if-3 (type $2) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $multi-local-copy-if-3 (type $2) (param $x i32) (param $y i32) + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (i32.const 42) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (if + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (local.get $y) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (then + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $multi-local-copy-if-3 (param $x i32) (param $y i32) + ;; As above, but the if's condition is flipped. + (local.set $x + (i32.const 42) + ) + (if + (i32.eq + (local.get $y) + (local.get $x) + ) + (then + ;; These are true. + (drop + (i32.eq + (local.get $y) + (i32.const 42) + ) + ) + (drop + (i32.eq + (local.get $x) + (i32.const 42) + ) + ) + ) + ) + ) ) From b74712962849ec83c2ee7bb9f12ec8838095ba90 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 9 Jul 2026 09:51:13 -0700 Subject: [PATCH 5/6] format --- src/ir/constraint.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index ddb1cc748eb..1c91e0d1632 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -289,8 +289,10 @@ struct BasicBlockConstraintMap { // above. When flip is true, we flip the constraint and apply it to the other // index (y == x, in this example). When isCopy is true, we are a copied // constraint from another local, and we do not need to add new copies of it. - void - approximateAndInternal(Index index, const Constraint& c, bool flip = false, bool isCopy = false); + void approximateAndInternal(Index index, + const Constraint& c, + bool flip = false, + bool isCopy = false); }; std::ostream& operator<<(std::ostream& o, const Constraint& c); From 451767f0e5dfaf503b08241927be0a828e44cc2c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 9 Jul 2026 13:00:30 -0700 Subject: [PATCH 6/6] feedback: use map.insert --- src/ir/constraint.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 8d7b66911ca..fc273301712 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -345,12 +345,10 @@ void BasicBlockConstraintMap::approximateAndInternal(Index index, // the local, then we insert a new item into the map, which has a default of // proxesEverything, which we need to flip (provesEverything cannot otherwise // be found in the map, as we never store it). - auto& indexConstraints = map[index]; - if (indexConstraints.provesEverything()) { - indexConstraints.setProvesNothing(); - // As in ::set(), this makes the map temporarily invalid until the - // approximateAnd, as we don't store proves-nothing in the map, normally. - } + auto [iter, _] = map.insert({index, AndedConstraintSet::makeProvesNothing()}); + auto& indexConstraints = iter->second; + // As in ::set(), this makes the map temporarily invalid until the + // approximateAnd, as we don't store proves-nothing in the map, normally. indexConstraints.approximateAnd(actual);