From ab1c83b8e713a32d6dfc5e1526c844a8fccca6a6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 15 Jul 2026 13:11:30 -0700 Subject: [PATCH 01/23] fix --- src/ir/constraint.cpp | 13 +++++++++++-- test/gtest/constraint.cpp | 21 +++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 3dd7fd0aa9a..346541e4bb6 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -137,8 +137,6 @@ void AndedConstraintSet::approximateAnd(const Constraint& c) { auto result = proves(c); if (result == True) { // We already prove c to be true, so it adds nothing. - // TODO: we could also see if c proves us true, and replace things we - // already have with c when possible return; } else if (result == False) { // We are now a contradiction. @@ -146,6 +144,17 @@ void AndedConstraintSet::approximateAnd(const Constraint& c) { return; } + // If c proves something already present to be true, it can just replace it. + for (auto& existing : *this) { + auto result = provesPair(c, existing); + if (result == True) { + existing = c; + return; + } + // There cannot be a contradiction here, because we checked for that above. + assert(result != False); + } + if (size() < MaxConstraints) { push_back(c); return; diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 025ccf75749..ea3fc10167c 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -219,5 +219,26 @@ TEST(ConstraintTest, TestDeduplication) { EXPECT_EQ(s.size(), 1); } +TEST(ConstraintTest, TestDeredundancy) { + Constraint eq0{Eq, {Literal(int32_t(0))}}; + Constraint ne1{Ne, {Literal(int32_t(1))}}; + + // If x == 0, then x != 1 is redundant, and does not need to be added, is it + // is implied by x == 0. + AndedConstraintSet s; + s.set(eq0); + s.approximateAnd(ne1); + EXPECT_EQ(s.size(), 1); + EXPECT_EQ(s[0], eq0); + + // Reverse order, same result, even though we added x == 0 last: we remove + // x != 1. + AndedConstraintSet t; + t.set(ne1); + t.approximateAnd(eq0); + EXPECT_EQ(t.size(), 1); + EXPECT_EQ(t[0], eq0); +} + // TODO: test an approximateOr of { x = 10 } and { x >= 0 }, once we support // inequalities From 642229c1858598252273f1cd303373be95d90ba9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 16 Jul 2026 08:43:35 -0700 Subject: [PATCH 02/23] fix sorting --- src/ir/constraint.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index e1ac6e6af5f..7c138bc1371 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -149,8 +149,13 @@ void AndedConstraintSet::approximateAnd(const Constraint& c) { auto result = provesPair(c, existing); if (result == True) { existing = c; + + // Sort to ensure we are in the right place. + std::sort(begin(), end()); + return; } + // There cannot be a contradiction here, because we checked for that above. assert(result != False); } From c839fe5d509fa542ae95c03235e3af42053a4e3e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 16 Jul 2026 13:11:03 -0700 Subject: [PATCH 03/23] go --- src/passes/ConstraintAnalysis.cpp | 88 ++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 6 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 356e6e035b1..3f66db748f8 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -84,6 +84,29 @@ struct ConstraintAnalysis // state in the function. bool ignoreBranchesOutsideOfFunc = true; + // A relevant local is one that is used as part of an expression that we can + // optimize (often, many locals are irrelevant). + std::vector relevantLocals; + // Track local copies too, as if one local is relevant, another it is copied + // to is relevant as well. We store pairs here of [target, source]. + std::vector> localCopies; + + void markRelevant(Expression* curr) { + // If this parses into a constraint on a local, that local is relevant. + if (auto parsed = LocalConstraint::parseCondition(curr)) { + relevantLocals[parsed->local] = true; + if (auto* other = std::get_if(&parsed->constraint.term)) { + relevantLocals[*other] = true; + } + } + } + + void doWalkFunction(Function* func) { + relevantLocals.assign(func->getNumLocals(), false); + + Super::doWalkFunction(func); + } + // Store the actions we care about. void addAction() { if (currBasicBlock) { @@ -91,11 +114,33 @@ struct ConstraintAnalysis } } - void visitLocalSet(LocalSet* curr) { addAction(); } - void visitUnary(Unary* curr) { addAction(); } - void visitBinary(Binary* curr) { addAction(); } - void visitRefEq(RefEq* curr) { addAction(); } - void visitRefIsNull(RefIsNull* curr) { addAction(); } + void visitLocalSet(LocalSet* curr) { + addAction(); + if (auto* get = curr->value->dynCast()) { + // TODO: handle tees once we handle them elsewhere + localCopies.push_back({curr->index, get->index}); + } + } + + void visitUnary(Unary* curr) { + addAction(); + markRelevant(curr); + } + + void visitBinary(Binary* curr) { + addAction(); + markRelevant(curr); + } + + void visitRefEq(RefEq* curr) { + addAction(); + markRelevant(curr); + } + + void visitRefIsNull(RefIsNull* curr) { + addAction(); + markRelevant(curr); + } static void doStartIfTrue(ConstraintAnalysis* self, Expression** currp) { // We are right after the condition, so we are in the block before the If's @@ -103,6 +148,9 @@ struct ConstraintAnalysis if (self->currBasicBlock) { self->currBasicBlock->contents.brancher = *currp; } + if (auto* iff = (*currp)->dynCast()) { + self->markRelevant(iff->condition); + } Super::doStartIfTrue(self, currp); } @@ -110,6 +158,13 @@ struct ConstraintAnalysis if (self->currBasicBlock) { self->currBasicBlock->contents.brancher = *currp; } + if (auto* br = (*currp)->dynCast()) { + if (br->condition) { + self->markRelevant(br->condition); + } + } else if (auto* brOn = (*currp)->dynCast()) { + self->markRelevant(brOn->ref); + } Super::doEndBranch(self, currp); } @@ -118,11 +173,26 @@ struct ConstraintAnalysis // Body is unreachable, no entry block. return; } - // TODO: optimize for speed, find relevant locals etc. + + computeRelevantLocals(); flow(); optimize(); } + void computeRelevantLocals() { + // Every relevant local makes the things it is copied to relevant as well. + bool changed = true; + while (changed) { + changed = false; + for (auto& [target, source] : localCopies) { + if (relevantLocals[source] && !relevantLocals[target]) { + relevantLocals[target] = true; + changed = true; + } + } + } + } + // Flow infos around until we have inferred all we can about the constraints // in each location. void flow() { @@ -132,6 +202,9 @@ struct ConstraintAnalysis auto& entryConstraints = entry->contents.startConstraints; auto* func = getFunction(); for (Index i = func->getVarIndexBase(); i < func->getNumLocals(); i++) { + if (!relevantLocals[i]) { + continue; + } auto type = func->getLocalType(i); // TODO: support tuples if (type.size() == 1 && LiteralUtils::canMakeZero(type)) { @@ -317,6 +390,9 @@ struct ConstraintAnalysis void applyToConstraints(Expression* curr, BasicBlockConstraintMap& constraints) { if (auto* set = curr->dynCast()) { + if (!relevantLocals[set->index]) { + return; + } if (Properties::isSingleConstantExpression(set->value)) { // Apply a constraint to this value. auto value = Properties::getLiteral(set->value); From 45c4788bcce4c980cfb1e473b1a61a2df641176b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 16 Jul 2026 13:22:49 -0700 Subject: [PATCH 04/23] fix --- src/passes/ConstraintAnalysis.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 3f66db748f8..d25222e1c00 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -85,7 +85,15 @@ struct ConstraintAnalysis bool ignoreBranchesOutsideOfFunc = true; // A relevant local is one that is used as part of an expression that we can - // optimize (often, many locals are irrelevant). + // optimize (often, many locals are irrelevant). Note that this is a stronger + // property than just being able to be reason about constraints on that local: + // for example, + // + // x = 0; + // if (y) { ..; x = 1; } + // return x; + // + // x XXX std::vector relevantLocals; // Track local copies too, as if one local is relevant, another it is copied // to is relevant as well. We store pairs here of [target, source]. @@ -148,9 +156,6 @@ struct ConstraintAnalysis if (self->currBasicBlock) { self->currBasicBlock->contents.brancher = *currp; } - if (auto* iff = (*currp)->dynCast()) { - self->markRelevant(iff->condition); - } Super::doStartIfTrue(self, currp); } @@ -158,13 +163,6 @@ struct ConstraintAnalysis if (self->currBasicBlock) { self->currBasicBlock->contents.brancher = *currp; } - if (auto* br = (*currp)->dynCast()) { - if (br->condition) { - self->markRelevant(br->condition); - } - } else if (auto* brOn = (*currp)->dynCast()) { - self->markRelevant(brOn->ref); - } Super::doEndBranch(self, currp); } From 06c03af9ba3f9c4711668f8992211edd1231a8fa Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 16 Jul 2026 13:23:12 -0700 Subject: [PATCH 05/23] form --- src/passes/ConstraintAnalysis.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index d25222e1c00..e2426234d5f 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -85,15 +85,7 @@ struct ConstraintAnalysis bool ignoreBranchesOutsideOfFunc = true; // A relevant local is one that is used as part of an expression that we can - // optimize (often, many locals are irrelevant). Note that this is a stronger - // property than just being able to be reason about constraints on that local: - // for example, - // - // x = 0; - // if (y) { ..; x = 1; } - // return x; - // - // x XXX + // optimize (often, many locals are irrelevant). std::vector relevantLocals; // Track local copies too, as if one local is relevant, another it is copied // to is relevant as well. We store pairs here of [target, source]. From 7266144758d8eabd36d88644285f271643de7b52 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 16 Jul 2026 13:24:03 -0700 Subject: [PATCH 06/23] form --- src/passes/ConstraintAnalysis.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index e2426234d5f..56af7d9c794 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -193,6 +193,7 @@ struct ConstraintAnalysis auto* func = getFunction(); for (Index i = func->getVarIndexBase(); i < func->getNumLocals(); i++) { if (!relevantLocals[i]) { + // No point to apply a constraint to an irrelevant local. continue; } auto type = func->getLocalType(i); @@ -381,6 +382,7 @@ struct ConstraintAnalysis BasicBlockConstraintMap& constraints) { if (auto* set = curr->dynCast()) { if (!relevantLocals[set->index]) { + // No point to apply a constraint to an irrelevant local. return; } if (Properties::isSingleConstantExpression(set->value)) { From 72eba6ad210940cb71f5099d673161e18306b9f1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 16 Jul 2026 14:42:19 -0700 Subject: [PATCH 07/23] fix --- src/passes/ConstraintAnalysis.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 56af7d9c794..af3baa8a724 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -285,6 +285,14 @@ struct ConstraintAnalysis return; } + // We are about to optimize this, so it must use only relevant locals - + // otherwise we might not be aware of the right constraints, as we ignored + // irrelevant locals earlier. + assert(relevantLocals[parsed->local]); + if ([[maybe_unused]] auto* other = std::get_if(&parsed->constraint.term)) { + assert(relevantLocals[*other]); + } + auto localConstraints = constraints.get(parsed->local); Result result = localConstraints.proves(parsed->constraint); if (result == Unknown) { From 57b156b28b7731d877bfaa00eba333ec15ea2d37 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 16 Jul 2026 14:42:25 -0700 Subject: [PATCH 08/23] fix --- 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 af3baa8a724..dcc4ede6892 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -289,7 +289,8 @@ struct ConstraintAnalysis // otherwise we might not be aware of the right constraints, as we ignored // irrelevant locals earlier. assert(relevantLocals[parsed->local]); - if ([[maybe_unused]] auto* other = std::get_if(&parsed->constraint.term)) { + if ([[maybe_unused]] auto* other = + std::get_if(&parsed->constraint.term)) { assert(relevantLocals[*other]); } From 2c48869eed2e8e6f2d23e541eb32fd1ece3afb87 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 16 Jul 2026 14:52:10 -0700 Subject: [PATCH 09/23] test --- test/lit/passes/constraint-analysis.wast | 66 ++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index b278ae458de..3978c967117 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -1317,6 +1317,72 @@ ) ) + ;; CHECK: (func $conditional-binary-contradiction-other (type $0) (param $x i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $conditional-binary-contradiction-other (type $0) (param $x i32) + ;; OPTIN-NEXT: (if + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (i32.const 10) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (then + ;; OPTIN-NEXT: (if + ;; OPTIN-NEXT: (i32.const 0) + ;; OPTIN-NEXT: (then + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 30) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $conditional-binary-contradiction-other (param $x i32) + (if + (i32.eq + (local.get $x) + (i32.const 10) + ) + (then + (if + (i32.eq + (local.get $x) + (i32.const 20) + ) + (then + ;; This is only reached if x is both 10 and 20, which is a + ;; contradiction, so it is unreachable. We optimize to unreachable + ;; here, even though this is a Binary that we do not have anything + ;; to do with otherwise (no constraint on a local is implied by this + ;; expression). + (drop + (i32.add + (i32.const 10) + (i32.const 20) + ) + ) + ) + ) + ) + ) + ) + ;; CHECK: (func $contadiction-during-flipping (type $1) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local $y i32) From f3abddf9d263324ac922b1b7efc331c74f28dc3d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 16 Jul 2026 15:34:46 -0700 Subject: [PATCH 10/23] fix --- src/passes/ConstraintAnalysis.cpp | 10 +++ test/lit/passes/constraint-analysis.wast | 96 +++++++++++++++++++++++- 2 files changed, 105 insertions(+), 1 deletion(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index dcc4ede6892..18827b199ea 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -148,6 +148,9 @@ struct ConstraintAnalysis if (self->currBasicBlock) { self->currBasicBlock->contents.brancher = *currp; } + if (auto* iff = (*currp)->dynCast()) { + self->markRelevant(iff->condition); + } Super::doStartIfTrue(self, currp); } @@ -155,6 +158,13 @@ struct ConstraintAnalysis if (self->currBasicBlock) { self->currBasicBlock->contents.brancher = *currp; } + if (auto* br = (*currp)->dynCast()) { + if (br->condition) { + self->markRelevant(br->condition); + } + } else if (auto* brOn = (*currp)->dynCast()) { + self->markRelevant(brOn->ref); + } Super::doEndBranch(self, currp); } diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index 3978c967117..bdb7c2f4973 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -1370,7 +1370,9 @@ ;; contradiction, so it is unreachable. We optimize to unreachable ;; here, even though this is a Binary that we do not have anything ;; to do with otherwise (no constraint on a local is implied by this - ;; expression). + ;; expression). This checks that we optimize unreachability even + ;; when it is caused by locals that are not outside of if + ;; conditions. (drop (i32.add (i32.const 10) @@ -1383,6 +1385,98 @@ ) ) + ;; CHECK: (func $conditional-binary-contradiction-other-default (type $1) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $conditional-binary-contradiction-other-default (type $1) + ;; OPTIN-NEXT: (local $x i32) + ;; OPTIN-NEXT: (if + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (then + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 30) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $conditional-binary-contradiction-other-default + (local $x i32) + ;; As above, but now with a single if. The contradiction tested is + ;; between the default value and the if condition. + (if + (local.get $x) + (then + (drop + ;; This is unreachable. + (i32.add + (i32.const 10) + (i32.const 20) + ) + ) + ) + ) + ) + + ;; CHECK: (func $conditional-binary-contradiction-other-set (type $1) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $conditional-binary-contradiction-other-set (type $1) + ;; OPTIN-NEXT: (local $x i32) + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (i32.const 10) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (if + ;; OPTIN-NEXT: (i32.const 0) + ;; OPTIN-NEXT: (then + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 30) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $conditional-binary-contradiction-other-set + (local $x i32) + ;; As above, but now the contradiction tested is between a local.set and + ;; the if condition. + (local.set $x + (i32.const 10) + ) + (if + (i32.eq + (local.get $x) + (i32.const 20) + ) + (then + (drop + ;; This is unreachable. + (i32.add + (i32.const 10) + (i32.const 20) + ) + ) + ) + ) + ) + ;; CHECK: (func $contadiction-during-flipping (type $1) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local $y i32) From baace2360794773641a67850fb37e6b831aa4d48 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 16 Jul 2026 15:40:22 -0700 Subject: [PATCH 11/23] assert --- src/passes/ConstraintAnalysis.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 18827b199ea..63fe6424187 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -241,6 +241,7 @@ struct ConstraintAnalysis // branch, and use them. auto sentConstraints = constraints; if (auto branch = getBranchConstraints(block, out)) { + verifyRelevancy(*branch); sentConstraints.approximateAnd(branch->local, branch->constraint); } @@ -295,14 +296,7 @@ struct ConstraintAnalysis return; } - // We are about to optimize this, so it must use only relevant locals - - // otherwise we might not be aware of the right constraints, as we ignored - // irrelevant locals earlier. - assert(relevantLocals[parsed->local]); - if ([[maybe_unused]] auto* other = - std::get_if(&parsed->constraint.term)) { - assert(relevantLocals[*other]); - } + verifyRelevancy(*parsed); auto localConstraints = constraints.get(parsed->local); Result result = localConstraints.proves(parsed->constraint); @@ -417,6 +411,17 @@ struct ConstraintAnalysis } } } + + // When we are about to use or apply a constraint to a local, it must be on a + // relevant one - otherwise we misidentified which are relevant, which could + // lead to missed opportunities or misoptimizations. + void verifyRelevancy(const LocalConstraint& parsed) { + assert(relevantLocals[parsed.local]); + if ([[maybe_unused]] auto* other = + std::get_if(&parsed.constraint.term)) { + assert(relevantLocals[*other]); + } + } }; } // anonymous namespace From c33dc89107d0e3881cbf669ae150d5da31e70bd0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 17 Jul 2026 08:53:25 -0700 Subject: [PATCH 12/23] fix --- src/passes/ConstraintAnalysis.cpp | 35 +++++++++++++++++----- test/lit/passes/constraint-analysis.wast | 38 ++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 63fe6424187..c31ca14dbae 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -241,8 +241,9 @@ struct ConstraintAnalysis // branch, and use them. auto sentConstraints = constraints; if (auto branch = getBranchConstraints(block, out)) { - verifyRelevancy(*branch); - sentConstraints.approximateAnd(branch->local, branch->constraint); + if (checkRelevancy(*branch)) { + sentConstraints.approximateAnd(branch->local, branch->constraint); + } } // If anything changed at the start of the target block, flow onwards. @@ -295,8 +296,9 @@ struct ConstraintAnalysis if (!parsed) { return; } - - verifyRelevancy(*parsed); + if (!checkRelevancy(*parsed)) { + return; + } auto localConstraints = constraints.get(parsed->local); Result result = localConstraints.proves(parsed->constraint); @@ -414,13 +416,30 @@ struct ConstraintAnalysis // When we are about to use or apply a constraint to a local, it must be on a // relevant one - otherwise we misidentified which are relevant, which could - // lead to missed opportunities or misoptimizations. - void verifyRelevancy(const LocalConstraint& parsed) { - assert(relevantLocals[parsed.local]); + // lead to missed opportunities or misoptimizations. This returns true if we + // are operating on proper, relevant data. Normally this is all that can + // happen, but intermediate optimizations can make things become relevant, + // consider this: + // + // x == (y < 10) + // + // The outer == is initially not relevant: we are comparing x to something we + // can't parse into a constraint's term. However, if we get lucky and optimize + // y < 10 into a constant, then it does become parseable, but because we did + // not consider x as relevant (and so we do not have all the relevant + // information about it), we must return false here and not operate on it + // (later optimization cycles can get to it). + bool checkRelevancy(const LocalConstraint& parsed) { + if (!relevantLocals[parsed.local]) { + return false; + } if ([[maybe_unused]] auto* other = std::get_if(&parsed.constraint.term)) { - assert(relevantLocals[*other]); + if (!relevantLocals[*other]) { + return false; + } } + return true; } }; diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index bdb7c2f4973..e9bc9778e97 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -3851,5 +3851,43 @@ ) ) ) + + ;; CHECK: (func $nested-binaries (type $1) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (local $e eqref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.lt_u + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $nested-binaries (type $1) + ;; OPTIN-NEXT: (local $x i32) + ;; OPTIN-NEXT: (local $e eqref) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.gt_u + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $nested-binaries + (local $x i32) + (local $e eqref) + ;; Nested binaries. The outer one is initially not relevant - we cannot + ;; parse the right hand side - but after optimization it simplifies. We + ;; should not assert here, and only optimize the inner one, leaving the + ;; outer for later. + (drop + (i32.lt_u + (local.get $x) + (ref.eq + (local.get $e) + (ref.null none) + ) + ) + ) + ) ) From 83f1424452eeaeb97dcb7971f6720ae51113e582 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 20 Jul 2026 14:06:41 -0700 Subject: [PATCH 13/23] simpl --- src/passes/ConstraintAnalysis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 3ad93a80a51..e94c3841842 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -435,7 +435,7 @@ struct ConstraintAnalysis if (!relevantLocals[parsed.local]) { return false; } - if ([[maybe_unused]] auto* other = + if (auto* other = std::get_if(&parsed.constraint.term)) { if (!relevantLocals[*other]) { return false; From 745ecdbce31496550099463aed5fbb0b89210fa1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 20 Jul 2026 14:08:43 -0700 Subject: [PATCH 14/23] simpl --- test/lit/passes/constraint-analysis.wast | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index e9bc9778e97..e73d679e9a6 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -1370,9 +1370,8 @@ ;; contradiction, so it is unreachable. We optimize to unreachable ;; here, even though this is a Binary that we do not have anything ;; to do with otherwise (no constraint on a local is implied by this - ;; expression). This checks that we optimize unreachability even - ;; when it is caused by locals that are not outside of if - ;; conditions. + ;; expression). This checks that we optimize unreachability even on + ;; expressions without relevant locals. (drop (i32.add (i32.const 10) From ce848d0255c01de44386099387563fcde21d5408 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 20 Jul 2026 14:18:45 -0700 Subject: [PATCH 15/23] try --- src/passes/ConstraintAnalysis.cpp | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index e94c3841842..58ca16067ee 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -88,8 +88,8 @@ struct ConstraintAnalysis // optimize (often, many locals are irrelevant). std::vector relevantLocals; // Track local copies too, as if one local is relevant, another it is copied - // to is relevant as well. We store pairs here of [target, source]. - std::vector> localCopies; + // to is relevant as well. We store pairs here of key=source, value=targets. + std::unordered_map> localCopyTargets; void markRelevant(Expression* curr) { // If this parses into a constraint on a local, that local is relevant. @@ -118,7 +118,7 @@ struct ConstraintAnalysis addAction(); if (auto* get = curr->value->dynCast()) { // TODO: handle tees once we handle them elsewhere - localCopies.push_back({curr->index, get->index}); + localCopyTargets[get->index].push_back(curr->index); } } @@ -179,15 +179,26 @@ struct ConstraintAnalysis optimize(); } + // Every relevant local makes the things it is copied to relevant as well. void computeRelevantLocals() { - // Every relevant local makes the things it is copied to relevant as well. - bool changed = true; - while (changed) { - changed = false; - for (auto& [target, source] : localCopies) { - if (relevantLocals[source] && !relevantLocals[target]) { + UniqueDeferredQueue work; + for (auto iter : localCopyTargets) { + if (relevantLocals(iter.first)) { + work.push(iter); + } + } + while (!work.empty()) { + auto iter = work.pop(); + auto source = iter->first; + assert(relevantLocals[source]); + auto& targets = iter->second; + + for (auto target : targets) { + if (!relevantLocals[target]) { relevantLocals[target] = true; - changed = true; + if (auto iter = localCopyTargets.find(target); iter != localCopyTargets.end()) { + work.push(iter); + } } } } From 2aee76413401cb19b5ec99216a194a0684abd494 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 20 Jul 2026 14:23:49 -0700 Subject: [PATCH 16/23] fix --- src/passes/ConstraintAnalysis.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 58ca16067ee..978a66d4339 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -181,23 +181,20 @@ struct ConstraintAnalysis // Every relevant local makes the things it is copied to relevant as well. void computeRelevantLocals() { - UniqueDeferredQueue work; - for (auto iter : localCopyTargets) { - if (relevantLocals(iter.first)) { - work.push(iter); + UniqueDeferredQueue work; + for (auto& [source, _] : localCopyTargets) { + if (relevantLocals[source]) { + work.push(source); } } while (!work.empty()) { - auto iter = work.pop(); - auto source = iter->first; + auto source = work.pop(); assert(relevantLocals[source]); - auto& targets = iter->second; - - for (auto target : targets) { - if (!relevantLocals[target]) { - relevantLocals[target] = true; - if (auto iter = localCopyTargets.find(target); iter != localCopyTargets.end()) { - work.push(iter); + if (auto iter = localCopyTargets.find(source); iter != localCopyTargets.end()) { + for (auto target : iter->second) { + if (!relevantLocals[target]) { + relevantLocals[target] = true; + work.push(target); } } } From 8f47c164b71be45906b4baccee898d13eab57823 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 20 Jul 2026 14:23:57 -0700 Subject: [PATCH 17/23] go --- src/passes/ConstraintAnalysis.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 978a66d4339..117fd9fa65d 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -190,7 +190,8 @@ struct ConstraintAnalysis while (!work.empty()) { auto source = work.pop(); assert(relevantLocals[source]); - if (auto iter = localCopyTargets.find(source); iter != localCopyTargets.end()) { + if (auto iter = localCopyTargets.find(source); + iter != localCopyTargets.end()) { for (auto target : iter->second) { if (!relevantLocals[target]) { relevantLocals[target] = true; @@ -443,8 +444,7 @@ struct ConstraintAnalysis if (!relevantLocals[parsed.local]) { return false; } - if (auto* other = - std::get_if(&parsed.constraint.term)) { + if (auto* other = std::get_if(&parsed.constraint.term)) { if (!relevantLocals[*other]) { return false; } From 06ae43c4537632417d3be544053b4bf41fba55d4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 21 Jul 2026 08:24:27 -0700 Subject: [PATCH 18/23] rename as suggested --- src/passes/ConstraintAnalysis.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 117fd9fa65d..fe440f24c5d 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -91,7 +91,7 @@ struct ConstraintAnalysis // to is relevant as well. We store pairs here of key=source, value=targets. std::unordered_map> localCopyTargets; - void markRelevant(Expression* curr) { + void maybeMarkRelevant(Expression* curr) { // If this parses into a constraint on a local, that local is relevant. if (auto parsed = LocalConstraint::parseCondition(curr)) { relevantLocals[parsed->local] = true; @@ -124,22 +124,22 @@ struct ConstraintAnalysis void visitUnary(Unary* curr) { addAction(); - markRelevant(curr); + maybeMarkRelevant(curr); } void visitBinary(Binary* curr) { addAction(); - markRelevant(curr); + maybeMarkRelevant(curr); } void visitRefEq(RefEq* curr) { addAction(); - markRelevant(curr); + maybeMarkRelevant(curr); } void visitRefIsNull(RefIsNull* curr) { addAction(); - markRelevant(curr); + maybeMarkRelevant(curr); } static void doStartIfTrue(ConstraintAnalysis* self, Expression** currp) { @@ -149,7 +149,7 @@ struct ConstraintAnalysis self->currBasicBlock->contents.brancher = *currp; } if (auto* iff = (*currp)->dynCast()) { - self->markRelevant(iff->condition); + self->maybeMarkRelevant(iff->condition); } Super::doStartIfTrue(self, currp); } @@ -160,10 +160,10 @@ struct ConstraintAnalysis } if (auto* br = (*currp)->dynCast()) { if (br->condition) { - self->markRelevant(br->condition); + self->maybeMarkRelevant(br->condition); } } else if (auto* brOn = (*currp)->dynCast()) { - self->markRelevant(brOn->ref); + self->maybeMarkRelevant(brOn->ref); } Super::doEndBranch(self, currp); } From 7502a4c2e7a2e1ab5b029f1fcd3181d4fd594bb2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 21 Jul 2026 08:33:02 -0700 Subject: [PATCH 19/23] fix --- src/passes/ConstraintAnalysis.cpp | 37 +++++++++++++++++------- test/lit/passes/constraint-analysis.wast | 36 +++++++++++++++++++++++ 2 files changed, 62 insertions(+), 11 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index fe440f24c5d..f4875e2d22e 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -181,21 +181,36 @@ struct ConstraintAnalysis // Every relevant local makes the things it is copied to relevant as well. void computeRelevantLocals() { + // We'll start from all relevant locals, and flow from there. UniqueDeferredQueue work; - for (auto& [source, _] : localCopyTargets) { - if (relevantLocals[source]) { - work.push(source); + for (Index i = 0; i < relevantLocals.size(); i++) { + if (relevantLocals[i]) { + work.push(i); } } + if (work.empty()) { + return; + } + + // Reverse the map of targets, as we need to flow in both directions. + std::unordered_map> localCopySources; + for (auto& [source, targets] : localCopyTargets) { + for (auto target : targets) { + localCopySources[target].push_back(source); + } + } + + // Flow. while (!work.empty()) { - auto source = work.pop(); - assert(relevantLocals[source]); - if (auto iter = localCopyTargets.find(source); - iter != localCopyTargets.end()) { - for (auto target : iter->second) { - if (!relevantLocals[target]) { - relevantLocals[target] = true; - work.push(target); + auto curr = work.pop(); + assert(relevantLocals[curr]); + for (auto& map : { localCopyTargets, localCopySources }) { + if (auto iter = map.find(curr); iter != map.end()) { + for (auto other : iter->second) { + if (!relevantLocals[other]) { + relevantLocals[other] = true; + work.push(other); + } } } } diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index e73d679e9a6..14e804a53f7 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -3888,5 +3888,41 @@ ) ) ) + + ;; CHECK: (func $relevant-copy (type $1) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (local $y i32) + ;; CHECK-NEXT: (local.set $y + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $relevant-copy (type $1) + ;; OPTIN-NEXT: (local $x i32) + ;; OPTIN-NEXT: (local $y i32) + ;; OPTIN-NEXT: (local.set $y + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $relevant-copy + (local $x i32) + (local $y i32) + ;; x is not relevant, but it is copied to y, which is, so we must track x as + ;; relevant too. + (local.set $y + (local.get $x) + ) + (drop + (i32.eq + (local.get $y) + (i32.const 0) + ) + ) + ) ) From 3026d40e77f8bc0ed63358b421f1fcee2cf2c393 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 21 Jul 2026 08:33:07 -0700 Subject: [PATCH 20/23] format --- src/passes/ConstraintAnalysis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index f4875e2d22e..2123e117e00 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -204,7 +204,7 @@ struct ConstraintAnalysis while (!work.empty()) { auto curr = work.pop(); assert(relevantLocals[curr]); - for (auto& map : { localCopyTargets, localCopySources }) { + for (auto& map : {localCopyTargets, localCopySources}) { if (auto iter = map.find(curr); iter != map.end()) { for (auto other : iter->second) { if (!relevantLocals[other]) { From 8bb6a22abdb0cac158cde858319bbc4da04a9114 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 21 Jul 2026 08:45:56 -0700 Subject: [PATCH 21/23] simpl --- src/passes/ConstraintAnalysis.cpp | 32 +++++++++++++------------------ 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 2123e117e00..7afa6eed9f9 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -87,9 +87,12 @@ struct ConstraintAnalysis // A relevant local is one that is used as part of an expression that we can // optimize (often, many locals are irrelevant). std::vector relevantLocals; - // Track local copies too, as if one local is relevant, another it is copied - // to is relevant as well. We store pairs here of key=source, value=targets. - std::unordered_map> localCopyTargets; + // Track local copies too, as if one local is relevant, it can make another + // relevant. We store pairs here of key=target, value=sources, which is the + // direction we will flow in the analysis: if we check x == 10, making it + // relevant, and x = y earlier, then we must track that source, y, so that we + // know what it writes to x. + std::unordered_map> localCopySources; void maybeMarkRelevant(Expression* curr) { // If this parses into a constraint on a local, that local is relevant. @@ -118,7 +121,7 @@ struct ConstraintAnalysis addAction(); if (auto* get = curr->value->dynCast()) { // TODO: handle tees once we handle them elsewhere - localCopyTargets[get->index].push_back(curr->index); + localCopySources[curr->index].push_back(get->index); } } @@ -192,25 +195,16 @@ struct ConstraintAnalysis return; } - // Reverse the map of targets, as we need to flow in both directions. - std::unordered_map> localCopySources; - for (auto& [source, targets] : localCopyTargets) { - for (auto target : targets) { - localCopySources[target].push_back(source); - } - } - // Flow. while (!work.empty()) { auto curr = work.pop(); assert(relevantLocals[curr]); - for (auto& map : {localCopyTargets, localCopySources}) { - if (auto iter = map.find(curr); iter != map.end()) { - for (auto other : iter->second) { - if (!relevantLocals[other]) { - relevantLocals[other] = true; - work.push(other); - } + if (auto iter = localCopySources.find(curr); + iter != localCopySources.end()) { + for (auto source : iter->second) { + if (!relevantLocals[source]) { + relevantLocals[source] = true; + work.push(source); } } } From c309b7a202d1c2735203059d93952625e7ecd376 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 21 Jul 2026 10:11:27 -0700 Subject: [PATCH 22/23] add asserts --- src/passes/ConstraintAnalysis.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 7afa6eed9f9..23b76a8b0a4 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -110,10 +110,19 @@ struct ConstraintAnalysis Super::doWalkFunction(func); } +#ifndef NDEBUG + // We use these in asserts, see below. + std::unordered_set originalActions; +#endif + // Store the actions we care about. void addAction() { if (currBasicBlock) { - currBasicBlock->contents.actions.push_back(getCurrentPointer()); + auto* currp = getCurrentPointer(); + currBasicBlock->contents.actions.push_back(currp); +#ifndef NDEBUG + originalActions.insert(*currp); +#endif } } @@ -317,6 +326,12 @@ struct ConstraintAnalysis return; } if (!checkRelevancy(*parsed)) { +#ifndef NDEBUG + // If this is not relevant, then it must be one of the original actions we + // care about, i.e., not the result of optimizations. See the comment + // below on checkRelevancy. + assert(originalActions.contains(curr)); +#endif return; } From 80e9f58fc6f3bba200d4b4b73ad52acd62ace08a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 21 Jul 2026 10:21:51 -0700 Subject: [PATCH 23/23] Update src/passes/ConstraintAnalysis.cpp Co-authored-by: Thomas Lively --- src/passes/ConstraintAnalysis.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 23b76a8b0a4..5eb6a24fcf4 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -200,9 +200,6 @@ struct ConstraintAnalysis work.push(i); } } - if (work.empty()) { - return; - } // Flow. while (!work.empty()) {