From a6cb42c5f3ac80a4bff3d653a53080b09fbe40a8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 27 Aug 2026 14:41:17 -0700 Subject: [PATCH 01/53] go --- src/ir/constraint.cpp | 139 ++++++++++++++++++++++-------------------- src/ir/constraint.h | 14 ++++- 2 files changed, 86 insertions(+), 67 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index c38c97162b6..8b0710df771 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -669,91 +669,100 @@ bool AndedConstraintSet::approximateOr(const AndedConstraintSet& other) { return changed; } -std::optional LocalConstraint::parse(Expression* curr) { - auto parseEqZArgument = - [&](Expression* value) -> std::optional { - if (auto* get = value->dynCast()) { - // Canonicalize EqZ to Eq of 0. - auto value = Literal::makeZero(get->type); - return LocalConstraint{get->index, Constraint{Abstract::Eq, {value}}}; - } - // TODO: Recursively parse and reverse a constraint - return {}; - }; +SmallVector LocalConstraint::parse(Expression* curr) { + // The final return value. + SmallVector ret; + + // Starting form |curr|, parse and recurse into sub-trees: when we see an AND, + // for example, we can push both children as further work. + SmallVector work; + work.push_pack(curr); + while (!work.empty()) { + auto* curr = work.pop_back(); + + auto parseEqZArgument = + [&](Expression* value) -> SmallVector { + if (auto* get = value->dynCast()) { + // Canonicalize EqZ to Eq of 0. + auto value = Literal::makeZero(get->type); + ret.push_back(LocalConstraint{get->index, Constraint{Abstract::Eq, {value}}}); + } + // TODO: Recursively parse and reverse a constraint + }; - if (auto* unary = curr->dynCast()) { - if (Abstract::getUnary(unary->value->type, Abstract::EqZ) == unary->op) { - return parseEqZArgument(unary->value); + if (auto* unary = curr->dynCast()) { + if (Abstract::getUnary(unary->value->type, Abstract::EqZ) == unary->op) { + parseEqZArgument(unary->value); + } + continue; } - return {}; - } - if (auto* refIsNull = curr->dynCast()) { - return parseEqZArgument(refIsNull->value); - } - - // Parse a get or a constant. - auto parseTerm = [&](Expression* expr) -> std::optional { - if (auto* get = expr->dynCast()) { - return Term{get->index}; - } - if (Properties::isSingleConstantExpression(expr)) { - return Term{Properties::getLiteral(expr)}; + if (auto* refIsNull = curr->dynCast()) { + return parseEqZArgument(refIsNull->value); } - return {}; - }; - - auto parseBinaryArguments = - [&](Abstract::Op op, - Expression* left, - Expression* right) -> std::optional { - // The left must be a get. - if (auto* get = left->dynCast()) { - // The right can be any term. - if (auto value = parseTerm(right)) { - return LocalConstraint{get->index, Constraint{op, *value}}; + + // Parse a get or a constant. + auto parseTerm = [&](Expression* expr) -> std::optional { + if (auto* get = expr->dynCast()) { + return Term{get->index}; } - } - return {}; - }; - - if (auto* binary = curr->dynCast()) { - // The operation must be one we recognize. - for (auto op : {Abstract::Eq, - Abstract::Ne, - Abstract::LtS, - Abstract::LtU, - Abstract::LeS, - Abstract::LeU, - Abstract::GtS, - Abstract::GtU, - Abstract::GeS, - Abstract::GeU}) { - if (Abstract::getBinary(binary->left->type, op) == binary->op) { - return parseBinaryArguments(op, binary->left, binary->right); + if (Properties::isSingleConstantExpression(expr)) { + return Term{Properties::getLiteral(expr)}; + } + return {}; + }; + + auto parseBinaryArguments = + [&](Abstract::Op op, + Expression* left, + Expression* right) { + // The left must be a get. + if (auto* get = left->dynCast()) { + // The right can be any term. + if (auto value = parseTerm(right)) { + ret.push_back(LocalConstraint{get->index, Constraint{op, *value}}); + } + } + }; + + if (auto* binary = curr->dynCast()) { + // The operation must be one we recognize. + for (auto op : {Abstract::Eq, + Abstract::Ne, + Abstract::LtS, + Abstract::LtU, + Abstract::LeS, + Abstract::LeU, + Abstract::GtS, + Abstract::GtU, + Abstract::GeS, + Abstract::GeU}) { + if (Abstract::getBinary(binary->left->type, op) == binary->op) { + parseBinaryArguments(op, binary->left, binary->right); + } } + continue; } - return {}; - } - if (auto* refEq = curr->dynCast()) { - return parseBinaryArguments(Abstract::Eq, refEq->left, refEq->right); + if (auto* refEq = curr->dynCast()) { + parseBinaryArguments(Abstract::Eq, refEq->left, refEq->right); + } } - return {}; + return ret; } -std::optional +SmallVector LocalConstraint::parseCondition(Expression* curr) { // A get by itself is a check for not being null. if (auto* get = curr->dynCast()) { auto value = Literal::makeZero(get->type); - return LocalConstraint{get->index, Constraint{Abstract::Ne, {value}}}; + return {LocalConstraint{get->index, Constraint{Abstract::Ne, {value}}}}; } // Otherwise, parse normally. return parse(curr); -}; +} void LocalConstraint::flip() { auto other = std::get(constraint.term); diff --git a/src/ir/constraint.h b/src/ir/constraint.h index f9f424949b9..22e6775b498 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -27,6 +27,7 @@ #include "ir/abstract.h" #include "support/inplace_vector.h" +#include "support/small_vector.h" #include "support/span.h" #include "support/utilities.h" #include "wasm.h" @@ -239,11 +240,20 @@ struct LocalConstraint { // // LocalConstraint($r, { x == 10 }) // - static std::optional parse(Expression* curr); + // Usually a single constraint is found, but if the expression is an AND over + // several, a vector is returned: + // + // (i32.and (..A..) (..B..)) + // + // parses into + // + // [ A, B ] + // + static SmallVector parse(Expression* curr); // Parse in a condition context, i.e., where (local.get $x) is the same as // $x != 0 (e.g., in an if condition, or a br_on ref). - static std::optional parseCondition(Expression* curr); + static SmallVector parseCondition(Expression* curr); // Reverse the constraint. The constraint's term must, of course, be another // local. From 06240fa40c0255e335a50b94896626e4c80f4d05 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 27 Aug 2026 14:41:24 -0700 Subject: [PATCH 02/53] format --- src/ir/constraint.cpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 8b0710df771..78d20ac2d18 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -685,7 +685,8 @@ SmallVector LocalConstraint::parse(Expression* curr) { if (auto* get = value->dynCast()) { // Canonicalize EqZ to Eq of 0. auto value = Literal::makeZero(get->type); - ret.push_back(LocalConstraint{get->index, Constraint{Abstract::Eq, {value}}}); + ret.push_back( + LocalConstraint{get->index, Constraint{Abstract::Eq, {value}}}); } // TODO: Recursively parse and reverse a constraint }; @@ -713,17 +714,15 @@ SmallVector LocalConstraint::parse(Expression* curr) { }; auto parseBinaryArguments = - [&](Abstract::Op op, - Expression* left, - Expression* right) { - // The left must be a get. - if (auto* get = left->dynCast()) { - // The right can be any term. - if (auto value = parseTerm(right)) { - ret.push_back(LocalConstraint{get->index, Constraint{op, *value}}); + [&](Abstract::Op op, Expression* left, Expression* right) { + // The left must be a get. + if (auto* get = left->dynCast()) { + // The right can be any term. + if (auto value = parseTerm(right)) { + ret.push_back(LocalConstraint{get->index, Constraint{op, *value}}); + } } - } - }; + }; if (auto* binary = curr->dynCast()) { // The operation must be one we recognize. From c0a41e4af4d051fb09d9bb9e5ecd7b2f4afc8b05 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 27 Aug 2026 14:47:28 -0700 Subject: [PATCH 03/53] work --- src/ir/constraint.cpp | 6 ++--- src/passes/ConstraintAnalysis.cpp | 39 ++++++++++++++++++------------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 78d20ac2d18..f89025b36e1 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -675,13 +675,13 @@ SmallVector LocalConstraint::parse(Expression* curr) { // Starting form |curr|, parse and recurse into sub-trees: when we see an AND, // for example, we can push both children as further work. - SmallVector work; - work.push_pack(curr); + SmallVector work; + work.push_back(curr); while (!work.empty()) { auto* curr = work.pop_back(); auto parseEqZArgument = - [&](Expression* value) -> SmallVector { + [&](Expression* value) { if (auto* get = value->dynCast()) { // Canonicalize EqZ to Eq of 0. auto value = Literal::makeZero(get->type); diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index f1b04cb5a79..d8e2422d2d8 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -146,9 +146,9 @@ struct ConstraintAnalysis 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; - if (auto* other = std::get_if(&parsed->constraint.term)) { + for (const auto& parsed : LocalConstraint::parseCondition(curr)) { + relevantLocals[parsed.local] = true; + if (auto* other = std::get_if(parsed->constraint.term)) { relevantLocals[*other] = true; } } @@ -426,10 +426,11 @@ struct ConstraintAnalysis const BasicBlockConstraintMap& constraints) { auto* curr = *currp; auto parsed = LocalConstraint::parse(curr); - if (!parsed) { + // TODO: optimize cases of more than one. + if (parsed.size() != 1) { return; } - if (!checkRelevancy(*parsed)) { + if (!checkRelevancy(parsed[0])) { #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 @@ -439,7 +440,7 @@ struct ConstraintAnalysis return; } - auto result = constraints.proves(*parsed); + auto result = constraints.proves(parsed[0]); if (result == Unknown) { // If we parsed something using two locals, like x != y, we can also look // for the flipped condition among y's constraints TODO @@ -456,7 +457,7 @@ struct ConstraintAnalysis // Given a predecessor and one of its successors, find new constraints that // can be added due to the flow to that specific successor. - std::optional getBranchConstraints(BasicBlock* pred, + SmallVector getBranchConstraints(BasicBlock* pred, BasicBlock* succ) { auto* brancher = pred->contents.brancher; if (!brancher) { @@ -486,31 +487,35 @@ struct ConstraintAnalysis return {}; } - std::optional getConstraintsFromIf(If* iff, + SmallVector getConstraintsFromIf(If* iff, bool physicalSuccessor) { auto parsed = LocalConstraint::parseCondition(iff->condition); - if (parsed && !physicalSuccessor) { + if (!physicalSuccessor) { // We are in the ifFalse, so negate the condition. - parsed->constraint = parsed->constraint.negate(); + for (auto& constraint : parsed) { + constraint = constraint.negate(); + } } return parsed; } - std::optional + SmallVector getConstraintsFromBreak(Break* br, bool physicalSuccessor) { // We get here when there is more than one successor, so there must be a // condition. assert(br->condition); auto parsed = LocalConstraint::parseCondition(br->condition); - if (parsed && physicalSuccessor) { + if (physicalSuccessor) { // The branch was not taken, so negate the condition. - parsed->constraint = parsed->constraint.negate(); + for (auto& constraint : parsed) { + constraint = constraint.negate(); + } } return parsed; } - std::optional + SmallVector getConstraintsFromBrOn(BrOn* brOn, bool physicalSuccessor) { // The constraint on that local depends on the op. // TODO: Handle BrOnCast* etc using subtyping operations. @@ -523,8 +528,10 @@ struct ConstraintAnalysis // can reuse it. auto parsed = LocalConstraint::parseCondition(brOn->ref); // Negate depending on the op and (similar to Break) the successor. - if (parsed && ((brOn->op == BrOnNull) ^ physicalSuccessor)) { - parsed->constraint = parsed->constraint.negate(); + if ((brOn->op == BrOnNull) ^ physicalSuccessor) { + for (auto& constraint : parsed) { + constraint = constraint.negate(); + } } return parsed; } From 2d23f5e2db10f3bb99a26527548c4f0170f7a807 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 27 Aug 2026 14:48:09 -0700 Subject: [PATCH 04/53] work --- src/passes/ConstraintAnalysis.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index d8e2422d2d8..41cbe656fae 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -148,7 +148,7 @@ struct ConstraintAnalysis // If this parses into a constraint on a local, that local is relevant. for (const auto& parsed : LocalConstraint::parseCondition(curr)) { relevantLocals[parsed.local] = true; - if (auto* other = std::get_if(parsed->constraint.term)) { + if (auto* other = std::get_if(parsed.constraint.term)) { relevantLocals[*other] = true; } } @@ -492,8 +492,8 @@ struct ConstraintAnalysis auto parsed = LocalConstraint::parseCondition(iff->condition); if (!physicalSuccessor) { // We are in the ifFalse, so negate the condition. - for (auto& constraint : parsed) { - constraint = constraint.negate(); + for (auto& pair : parsed) { + pair.constraint = pair.constraint.negate(); } } return parsed; @@ -508,8 +508,8 @@ struct ConstraintAnalysis auto parsed = LocalConstraint::parseCondition(br->condition); if (physicalSuccessor) { // The branch was not taken, so negate the condition. - for (auto& constraint : parsed) { - constraint = constraint.negate(); + for (auto& pair : parsed) { + pair.constraint = pair.constraint.negate(); } } return parsed; @@ -529,8 +529,8 @@ struct ConstraintAnalysis auto parsed = LocalConstraint::parseCondition(brOn->ref); // Negate depending on the op and (similar to Break) the successor. if ((brOn->op == BrOnNull) ^ physicalSuccessor) { - for (auto& constraint : parsed) { - constraint = constraint.negate(); + for (auto& pair : parsed) { + pair.constraint = pair.constraint.negate(); } } return parsed; From 71997f9826f57b01a4e5e1e417095a3dca34d2b9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 27 Aug 2026 14:50:26 -0700 Subject: [PATCH 05/53] work --- src/ir/constraint.cpp | 8 ++++++-- src/passes/ConstraintAnalysis.cpp | 6 +++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index f89025b36e1..1a8e8b26921 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -678,7 +678,8 @@ SmallVector LocalConstraint::parse(Expression* curr) { SmallVector work; work.push_back(curr); while (!work.empty()) { - auto* curr = work.pop_back(); + auto* curr = work.back(); + work.pop_back(); auto parseEqZArgument = [&](Expression* value) { @@ -699,7 +700,8 @@ SmallVector LocalConstraint::parse(Expression* curr) { } if (auto* refIsNull = curr->dynCast()) { - return parseEqZArgument(refIsNull->value); + parseEqZArgument(refIsNull->value); + continue; } // Parse a get or a constant. @@ -738,6 +740,7 @@ SmallVector LocalConstraint::parse(Expression* curr) { Abstract::GeU}) { if (Abstract::getBinary(binary->left->type, op) == binary->op) { parseBinaryArguments(op, binary->left, binary->right); + break; } } continue; @@ -745,6 +748,7 @@ SmallVector LocalConstraint::parse(Expression* curr) { if (auto* refEq = curr->dynCast()) { parseBinaryArguments(Abstract::Eq, refEq->left, refEq->right); + continue; } } diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 41cbe656fae..116e546b0c3 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -146,9 +146,9 @@ struct ConstraintAnalysis void maybeMarkRelevant(Expression* curr) { // If this parses into a constraint on a local, that local is relevant. - for (const auto& parsed : LocalConstraint::parseCondition(curr)) { - relevantLocals[parsed.local] = true; - if (auto* other = std::get_if(parsed.constraint.term)) { + for (const auto& pair : LocalConstraint::parseCondition(curr)) { + relevantLocals[pair.local] = true; + if (auto* other = std::get_if(pair.constraint.term)) { relevantLocals[*other] = true; } } From 1490b10b13b019f1182a19196bdb4f416f3f5fc4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 27 Aug 2026 14:51:21 -0700 Subject: [PATCH 06/53] work --- src/passes/ConstraintAnalysis.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 116e546b0c3..891348a64e4 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -146,9 +146,9 @@ struct ConstraintAnalysis void maybeMarkRelevant(Expression* curr) { // If this parses into a constraint on a local, that local is relevant. - for (const auto& pair : LocalConstraint::parseCondition(curr)) { + for (auto& pair : LocalConstraint::parseCondition(curr)) { relevantLocals[pair.local] = true; - if (auto* other = std::get_if(pair.constraint.term)) { + if (auto* other = std::get_if(&pair.constraint.term)) { relevantLocals[*other] = true; } } From bf44254913093ce9e165fa38efe66a1ef41ee694 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 27 Aug 2026 14:53:21 -0700 Subject: [PATCH 07/53] work --- src/passes/ConstraintAnalysis.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 891348a64e4..3fda3e86879 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -355,9 +355,9 @@ struct ConstraintAnalysis // Find the constraints sent to this specific successor, if there is a // branch, and use them. if (auto branch = getBranchConstraints(block, out); - branch && checkRelevancy(*branch)) { + !branch.empty() && checkRelevancy(branch)) { auto sentConstraints = constraints; - applyBranchConstraints(*branch, sentConstraints); + applyBranchConstraints(branch, sentConstraints); #if CONSTRAINT_DEBUG std::cout << block << " sending branch to " << out << " with sent constraints: " << sentConstraints << '\n'; @@ -624,6 +624,12 @@ struct ConstraintAnalysis return true; } + bool checkRelevancy(const SmallVector& parsed) { + return std::any_of([&](const LocalConstraint& pair) { + return checkRelevancy(pair); + }); + } + // Apply branch constraints to the current set of constraints. void applyBranchConstraints(const LocalConstraint& branch, BasicBlockConstraintMap& constraints) { From e290c24803bc3c70552a9a8fbcad5dec6744ed28 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 27 Aug 2026 14:54:29 -0700 Subject: [PATCH 08/53] work --- src/passes/ConstraintAnalysis.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 3fda3e86879..1ac4db91541 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -631,16 +631,18 @@ struct ConstraintAnalysis } // Apply branch constraints to the current set of constraints. - void applyBranchConstraints(const LocalConstraint& branch, + void applyBranchConstraints(const const SmallVector& branch, BasicBlockConstraintMap& constraints) { - // Extend the range of values in the "jump ahead" manner described in the - // top-level comment. - if (applyBranchRangeExtensionToConstraints(branch, constraints)) { - return; - } + for (auto& pair : branch) { + // Extend the range of values in the "jump ahead" manner described in the + // top-level comment. + if (applyBranchRangeExtensionToConstraints(pair, constraints)) { + return; + } - // Otherwise, apply the constraint normally. - constraints.approximateAnd(branch.local, branch.constraint); + // Otherwise, apply the constraint normally. + constraints.approximateAnd(pair.local, pair.constraint); + } } bool From 8e2c218fe2a9cc02b67c68686f3ce5264e9a8d2e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 27 Aug 2026 14:54:40 -0700 Subject: [PATCH 09/53] work --- 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 1ac4db91541..5ce3d52f4bc 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -631,7 +631,7 @@ struct ConstraintAnalysis } // Apply branch constraints to the current set of constraints. - void applyBranchConstraints(const const SmallVector& branch, + void applyBranchConstraints(const SmallVector& branch, BasicBlockConstraintMap& constraints) { for (auto& pair : branch) { // Extend the range of values in the "jump ahead" manner described in the From 4117c1e87d6d53ce67d679f0518d1bdabfb1186a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 27 Aug 2026 14:56:19 -0700 Subject: [PATCH 10/53] work --- 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 5ce3d52f4bc..1f7290c6667 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -625,7 +625,7 @@ struct ConstraintAnalysis } bool checkRelevancy(const SmallVector& parsed) { - return std::any_of([&](const LocalConstraint& pair) { + return std::any_true([&](const LocalConstraint& pair) { return checkRelevancy(pair); }); } From ea13cd0006109f87098703fb01a9fcb2a201d71e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 27 Aug 2026 14:57:08 -0700 Subject: [PATCH 11/53] work --- src/passes/ConstraintAnalysis.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 1f7290c6667..987894433b2 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -66,6 +66,8 @@ // function analysis). // +#include + #include "cfg/cfg-traversal.h" #include "ir/constraint.h" #include "ir/drop.h" @@ -625,7 +627,7 @@ struct ConstraintAnalysis } bool checkRelevancy(const SmallVector& parsed) { - return std::any_true([&](const LocalConstraint& pair) { + return std::any_of(parsed.begin(), parsed.end(), [&](const LocalConstraint& pair) { return checkRelevancy(pair); }); } From ec0eea325c062fa48aeec820dbbd305eac525307 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 27 Aug 2026 14:57:16 -0700 Subject: [PATCH 12/53] work --- src/ir/constraint.cpp | 3 +-- src/passes/ConstraintAnalysis.cpp | 11 ++++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 1a8e8b26921..5b7fe905dc5 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -681,8 +681,7 @@ SmallVector LocalConstraint::parse(Expression* curr) { auto* curr = work.back(); work.pop_back(); - auto parseEqZArgument = - [&](Expression* value) { + auto parseEqZArgument = [&](Expression* value) { if (auto* get = value->dynCast()) { // Canonicalize EqZ to Eq of 0. auto value = Literal::makeZero(get->type); diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 987894433b2..2c23f880150 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -460,7 +460,7 @@ struct ConstraintAnalysis // Given a predecessor and one of its successors, find new constraints that // can be added due to the flow to that specific successor. SmallVector getBranchConstraints(BasicBlock* pred, - BasicBlock* succ) { + BasicBlock* succ) { auto* brancher = pred->contents.brancher; if (!brancher) { return {}; @@ -490,7 +490,7 @@ struct ConstraintAnalysis } SmallVector getConstraintsFromIf(If* iff, - bool physicalSuccessor) { + bool physicalSuccessor) { auto parsed = LocalConstraint::parseCondition(iff->condition); if (!physicalSuccessor) { // We are in the ifFalse, so negate the condition. @@ -627,9 +627,10 @@ struct ConstraintAnalysis } bool checkRelevancy(const SmallVector& parsed) { - return std::any_of(parsed.begin(), parsed.end(), [&](const LocalConstraint& pair) { - return checkRelevancy(pair); - }); + return std::any_of( + parsed.begin(), parsed.end(), [&](const LocalConstraint& pair) { + return checkRelevancy(pair); + }); } // Apply branch constraints to the current set of constraints. From fedaa18139b7b7843f179e506d45ee988975c749 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 27 Aug 2026 15:25:44 -0700 Subject: [PATCH 13/53] work --- src/ir/constraint.cpp | 14 ++++ test/lit/passes/constraint-analysis.wast | 94 ++++++++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 5b7fe905dc5..dcac6a53206 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -687,7 +687,21 @@ SmallVector LocalConstraint::parse(Expression* curr) { auto value = Literal::makeZero(get->type); ret.push_back( LocalConstraint{get->index, Constraint{Abstract::Eq, {value}}}); + return; } + + // EqZ of EqZ means a check that the value is *not* zero. + if (auto* nested = value->dynCast()) { + if (Abstract::getUnary(nested->value->type, Abstract::EqZ) == nested->op) { + if (auto* get = value->dynCast()) { + auto value = Literal::makeZero(get->type); + ret.push_back( + LocalConstraint{get->index, Constraint{Abstract::Ne, {value}}}); + return; + } + } + } + // TODO: Recursively parse and reverse a constraint }; diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index 96f911de30a..efb9d88afb1 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -5079,4 +5079,98 @@ ) ) ) + + (func $eqz_eqz (param $a i32) + (if + ;; !!a => a is not 0. + (i32.eqz + (i32.eqz + (local.get $a) + ) + ) + (then + (drop + ;; This is true. + (i32.ne + (local.get $a) + (i32.const 0) + ) + ) + ) + ) + ) + + (func $several (param $a i32) (param $b i32) (param $c i32) (param $d i32) (param $e i32) + ;; Four constraints in one if condition. + (if + (i32.and + (i32.and + ;; !!a => a is not 0. + (i32.eqz + (i32.eqz + (local.get $a) + ) + ) + ;; b == 10 + (i32.eq + (local.get $b) + (i32.const 10) + ) + ) + (i32.and + ;; d == 20 + (i32.eq + (local.get $b) + (i32.const 10) + ) + ;; e == 30 + (i32.eq + (local.get $b) + (i32.const 10) + ) + ) + ) + (then + ;; These are all true. + (drop + (i32.ne + (local.get $a) + (i32.const 0) + ) + ) + (drop + (i32.eq + (local.get $b) + (i32.const 10) + ) + ) + (drop + (i32.eq + (local.get $d) + (i32.const 20) + ) + ) + (drop + (i32.eq + (local.get $e) + (i32.const 30) + ) + ) + ;; This is false. + (drop + (i32.eq + (local.get $a) + (i32.const 0) + ) + ) + ;; This local is unknown. + (drop + (i32.eq + (local.get $c) + (i32.const 40) + ) + ) + ) + ) + ) ) From be901a81e48abba964e58b46d2189eaa94aecd43 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 27 Aug 2026 15:26:13 -0700 Subject: [PATCH 14/53] work --- test/lit/passes/constraint-analysis.wast | 145 ++++++++++++++++++++++- 1 file changed, 144 insertions(+), 1 deletion(-) diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index efb9d88afb1..b1efb18a403 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -5080,6 +5080,33 @@ ) ) + ;; CHECK: (func $eqz_eqz (type $0) (param $a i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $eqz_eqz (type $0) (param $a i32) + ;; OPTIN-NEXT: (if + ;; OPTIN-NEXT: (local.get $a) + ;; OPTIN-NEXT: (then + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) (func $eqz_eqz (param $a i32) (if ;; !!a => a is not 0. @@ -5100,7 +5127,123 @@ ) ) - (func $several (param $a i32) (param $b i32) (param $c i32) (param $d i32) (param $e i32) + ;; CHECK: (func $several (type $11) (param $a i32) (param $b i32) (param $c i32) (param $d i32) (param $e i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $b) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $b) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $b) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $b) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $e) + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $c) + ;; CHECK-NEXT: (i32.const 40) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $several (type $11) (param $a i32) (param $b i32) (param $c i32) (param $d i32) (param $e i32) + ;; OPTIN-NEXT: (if + ;; OPTIN-NEXT: (i32.and + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $b) + ;; OPTIN-NEXT: (i32.const 10) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (i32.ne + ;; OPTIN-NEXT: (local.get $a) + ;; OPTIN-NEXT: (i32.const 0) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (then + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.ne + ;; OPTIN-NEXT: (local.get $a) + ;; OPTIN-NEXT: (i32.const 0) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $b) + ;; OPTIN-NEXT: (i32.const 10) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $d) + ;; OPTIN-NEXT: (i32.const 20) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $e) + ;; OPTIN-NEXT: (i32.const 30) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.eqz + ;; OPTIN-NEXT: (local.get $a) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $c) + ;; OPTIN-NEXT: (i32.const 40) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $several (param $a i32) (param $b i32) (param $c i32) (param $d i32) (param $e i32) ;; Four constraints in one if condition. (if (i32.and From d7c118297620615d67362711f580e2ac59f4b27e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 27 Aug 2026 15:39:47 -0700 Subject: [PATCH 15/53] work --- src/ir/constraint.cpp | 24 ++++++++++++------------ test/lit/passes/constraint-analysis.wast | 5 +---- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index dcac6a53206..0cdb6c6593d 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -690,23 +690,23 @@ SmallVector LocalConstraint::parse(Expression* curr) { return; } - // EqZ of EqZ means a check that the value is *not* zero. - if (auto* nested = value->dynCast()) { - if (Abstract::getUnary(nested->value->type, Abstract::EqZ) == nested->op) { - if (auto* get = value->dynCast()) { - auto value = Literal::makeZero(get->type); - ret.push_back( - LocalConstraint{get->index, Constraint{Abstract::Ne, {value}}}); - return; - } - } - } - // TODO: Recursively parse and reverse a constraint }; if (auto* unary = curr->dynCast()) { if (Abstract::getUnary(unary->value->type, Abstract::EqZ) == unary->op) { + // EqZ of EqZ means a check that the value is *not* zero. + if (auto* nested = unary->value->dynCast()) { + if (Abstract::getUnary(nested->value->type, Abstract::EqZ) == nested->op) { + if (auto* get = nested->value->dynCast()) { + auto value = Literal::makeZero(get->type); + ret.push_back( + LocalConstraint{get->index, Constraint{Abstract::Ne, {value}}}); + continue; + } + } + } + parseEqZArgument(unary->value); } continue; diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index b1efb18a403..91c71ab53db 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -5089,10 +5089,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.ne - ;; CHECK-NEXT: (local.get $a) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) From 5f3bce72c936c9aab1bf3c794fdd08548af1ca79 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 27 Aug 2026 15:43:21 -0700 Subject: [PATCH 16/53] work --- src/ir/constraint.cpp | 9 ++- test/lit/passes/constraint-analysis.wast | 85 ++++++++++-------------- 2 files changed, 42 insertions(+), 52 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 0cdb6c6593d..20a15b843d8 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -740,7 +740,14 @@ SmallVector LocalConstraint::parse(Expression* curr) { }; if (auto* binary = curr->dynCast()) { - // The operation must be one we recognize. + // An AND can be recursively processed: both sides must be true. + if (Abstract::getBinary(binary->left->type, Abstract::And) == binary->op) { + work.push_back(binary->left); + work.push_back(binary->right); + continue; + } + + // Otherwise, the operation must be one we can express as a constraint. for (auto op : {Abstract::Eq, Abstract::Ne, Abstract::LtS, diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index 91c71ab53db..fc49dc18c5d 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -5140,45 +5140,30 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.and ;; CHECK-NEXT: (i32.eq - ;; CHECK-NEXT: (local.get $b) - ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.eq - ;; CHECK-NEXT: (local.get $b) - ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (local.get $e) + ;; CHECK-NEXT: (i32.const 30) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.ne - ;; CHECK-NEXT: (local.get $a) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.eq - ;; CHECK-NEXT: (local.get $b) - ;; CHECK-NEXT: (i32.const 10) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.eq - ;; CHECK-NEXT: (local.get $d) - ;; CHECK-NEXT: (i32.const 20) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.eq - ;; CHECK-NEXT: (local.get $e) - ;; CHECK-NEXT: (i32.const 30) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.eq - ;; CHECK-NEXT: (local.get $a) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.eq @@ -5192,44 +5177,42 @@ ;; OPTIN: (func $several (type $11) (param $a i32) (param $b i32) (param $c i32) (param $d i32) (param $e i32) ;; OPTIN-NEXT: (if ;; OPTIN-NEXT: (i32.and - ;; OPTIN-NEXT: (i32.eq - ;; OPTIN-NEXT: (local.get $b) - ;; OPTIN-NEXT: (i32.const 10) - ;; OPTIN-NEXT: ) - ;; OPTIN-NEXT: (i32.ne - ;; OPTIN-NEXT: (local.get $a) - ;; OPTIN-NEXT: (i32.const 0) - ;; OPTIN-NEXT: ) - ;; OPTIN-NEXT: ) - ;; OPTIN-NEXT: (then - ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.and + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $b) + ;; OPTIN-NEXT: (i32.const 10) + ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (i32.ne ;; OPTIN-NEXT: (local.get $a) ;; OPTIN-NEXT: (i32.const 0) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) - ;; OPTIN-NEXT: (drop - ;; OPTIN-NEXT: (i32.eq - ;; OPTIN-NEXT: (local.get $b) - ;; OPTIN-NEXT: (i32.const 10) - ;; OPTIN-NEXT: ) - ;; OPTIN-NEXT: ) - ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.and ;; OPTIN-NEXT: (i32.eq ;; OPTIN-NEXT: (local.get $d) ;; OPTIN-NEXT: (i32.const 20) ;; OPTIN-NEXT: ) - ;; OPTIN-NEXT: ) - ;; OPTIN-NEXT: (drop ;; OPTIN-NEXT: (i32.eq ;; OPTIN-NEXT: (local.get $e) ;; OPTIN-NEXT: (i32.const 30) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (then ;; OPTIN-NEXT: (drop - ;; OPTIN-NEXT: (i32.eqz - ;; OPTIN-NEXT: (local.get $a) - ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 0) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (drop ;; OPTIN-NEXT: (i32.eq @@ -5260,13 +5243,13 @@ (i32.and ;; d == 20 (i32.eq - (local.get $b) - (i32.const 10) + (local.get $d) + (i32.const 20) ) ;; e == 30 (i32.eq - (local.get $b) - (i32.const 10) + (local.get $e) + (i32.const 30) ) ) ) From e84e822147eb6cc13948f580f604a03015a36f61 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 27 Aug 2026 15:43:28 -0700 Subject: [PATCH 17/53] work --- src/ir/constraint.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 20a15b843d8..6e85492e258 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -697,7 +697,8 @@ SmallVector LocalConstraint::parse(Expression* curr) { if (Abstract::getUnary(unary->value->type, Abstract::EqZ) == unary->op) { // EqZ of EqZ means a check that the value is *not* zero. if (auto* nested = unary->value->dynCast()) { - if (Abstract::getUnary(nested->value->type, Abstract::EqZ) == nested->op) { + if (Abstract::getUnary(nested->value->type, Abstract::EqZ) == + nested->op) { if (auto* get = nested->value->dynCast()) { auto value = Literal::makeZero(get->type); ret.push_back( @@ -741,7 +742,8 @@ SmallVector LocalConstraint::parse(Expression* curr) { if (auto* binary = curr->dynCast()) { // An AND can be recursively processed: both sides must be true. - if (Abstract::getBinary(binary->left->type, Abstract::And) == binary->op) { + if (Abstract::getBinary(binary->left->type, Abstract::And) == + binary->op) { work.push_back(binary->left); work.push_back(binary->right); continue; From f5616bf2219008d01afbf51cd8dc021b469e1de2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 27 Aug 2026 15:45:10 -0700 Subject: [PATCH 18/53] work --- src/ir/constraint.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 6e85492e258..1a90d4c865a 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -748,6 +748,7 @@ SmallVector LocalConstraint::parse(Expression* curr) { work.push_back(binary->right); continue; } + // TODO: support OR // Otherwise, the operation must be one we can express as a constraint. for (auto op : {Abstract::Eq, From 3b9d0450c029dc144fb4a62d49d043f5ca83f522 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 27 Aug 2026 17:06:04 -0700 Subject: [PATCH 19/53] work --- src/passes/ConstraintAnalysis.cpp | 2 +- test/lit/passes/constraint-analysis.wast | 87 ++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index ca3da3bb399..090a01587a3 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -149,7 +149,7 @@ struct ConstraintAnalysis void maybeMarkRelevant(Expression* curr) { // If this parses into a constraint on a local, that local is relevant. for (auto& pair : LocalConstraint::parseCondition(curr)) { - if (isRelevantType(getFunction()->getLocalType(pair.local)) { + if (isRelevantType(getFunction()->getLocalType(pair.local))) { relevantLocals[pair.local] = true; if (auto* other = std::get_if(&pair.constraint.term)) { relevantLocals[*other] = true; diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index 242ae481c49..90ae65df7cf 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -5070,6 +5070,93 @@ ) ) + ;; CHECK: (func $pair (type $2) (param $a i32) (param $b i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $b) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; 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: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $pair (type $2) (param $a i32) (param $b i32) + ;; OPTIN-NEXT: (if + ;; OPTIN-NEXT: (i32.and + ;; OPTIN-NEXT: (i32.eqz + ;; OPTIN-NEXT: (local.get $a) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $b) + ;; OPTIN-NEXT: (i32.const 42) + ;; OPTIN-NEXT: ) + ;; 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: (drop + ;; OPTIN-NEXT: (i32.const 0) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $pair (param $a i32) (param $b i32) + ;; Two constraints in one if condition. + (if + (i32.and + ;; a == 0 + (i32.eqz + (local.get $a) + ) + ;; b == 42 + (i32.eq + (local.get $b) + (i32.const 42) + ) + ) + (then + ;; These are all true. + (drop + (i32.eq + (local.get $a) + (i32.const 0) + ) + ) + (drop + (i32.eq + (local.get $b) + (i32.const 42) + ) + ) + ;; This is not. + (drop + (i32.eq + (local.get $a) + (i32.const 42) + ) + ) + ) + ) + ) + ;; CHECK: (func $several (type $11) (param $a i32) (param $b i32) (param $c i32) (param $d i32) (param $e i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.and From b47a8f15eb562e85436b6d40e106a4ca55c00d3d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 28 Aug 2026 10:15:31 -0700 Subject: [PATCH 20/53] work --- src/passes/ConstraintAnalysis.cpp | 35 +++++++----- test/lit/passes/constraint-analysis.wast | 73 ++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 13 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 090a01587a3..27ab293079d 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -511,9 +511,7 @@ struct ConstraintAnalysis auto parsed = LocalConstraint::parseCondition(iff->condition); if (!physicalSuccessor) { // We are in the ifFalse, so negate the condition. - for (auto& pair : parsed) { - pair.constraint = pair.constraint.negate(); - } + negate(parsed); } return parsed; } @@ -527,9 +525,7 @@ struct ConstraintAnalysis auto parsed = LocalConstraint::parseCondition(br->condition); if (physicalSuccessor) { // The branch was not taken, so negate the condition. - for (auto& pair : parsed) { - pair.constraint = pair.constraint.negate(); - } + negate(parsed); } return parsed; } @@ -548,13 +544,24 @@ struct ConstraintAnalysis auto parsed = LocalConstraint::parseCondition(brOn->ref); // Negate depending on the op and (similar to Break) the successor. if ((brOn->op == BrOnNull) ^ physicalSuccessor) { - for (auto& pair : parsed) { - pair.constraint = pair.constraint.negate(); - } + negate(parsed); } return parsed; } + // Given a list of parsed constraints on locals, negate them. + void negate(SmallVector& parsed) { + // The input is a list of constraints all applying at once, A & B & C. The + // negation is !A | !B | !C, but we cannot expression a general OR like + // that, so we only negate a list of one. TODO: if all the constraints are + // on the same local, we could use approximateOr. + if (parsed.size() == 1) { + parsed[0].constraint = parsed[0].constraint.negate(); + } else { + parsed.clear(); + } + } + // When applying constraints for a binary operation like x = y + 1, we may // end up with lots of nonlinear work, in a loop: x may go from 0 to 1, then // branch back to the top and merge, making it in the range [0, 1], then get @@ -656,12 +663,14 @@ struct ConstraintAnalysis for (auto& pair : branch) { // Extend the range of values in the "jump ahead" manner described in the // top-level comment. - if (applyBranchRangeExtensionToConstraints(pair, constraints)) { - return; + if (!applyBranchRangeExtensionToConstraints(pair, constraints)) { + // Otherwise, apply the constraint normally. + constraints.approximateAnd(pair.local, pair.constraint); } - // Otherwise, apply the constraint normally. - constraints.approximateAnd(pair.local, pair.constraint); + if (constraints.unreachable) { + return; + } } } diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index 90ae65df7cf..b77158de7d3 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -5092,6 +5092,29 @@ ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $b) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; OPTIN: (func $pair (type $2) (param $a i32) (param $b i32) @@ -5116,6 +5139,28 @@ ;; OPTIN-NEXT: (i32.const 0) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (else + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.eqz + ;; OPTIN-NEXT: (local.get $a) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $b) + ;; OPTIN-NEXT: (i32.const 42) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $a) + ;; OPTIN-NEXT: (i32.const 42) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 42) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) (func $pair (param $a i32) (param $b i32) @@ -5154,6 +5199,34 @@ ) ) ) + (else + ;; The same expressions as in the (then ..). Here, at least one must be + ;; false, not not necessarily all of them, so we infer nothing. + ;; These are all true. + (drop + (i32.eq + (local.get $a) + (i32.const 0) + ) + ) + (drop + (i32.eq + (local.get $b) + (i32.const 42) + ) + ) + (drop + (i32.eq + (local.get $a) + (i32.const 42) + ) + ) + ;; A silly extra instruction to stop optimize-instructions from + ;; folding the if-else arms. + (drop + (i32.const 42) + ) + ) ) ) From 012ee79c4c9f1e561711970df45bc64ba41df741 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 28 Aug 2026 10:38:43 -0700 Subject: [PATCH 21/53] work --- test/lit/passes/constraint-analysis.wast | 78 ++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index b77158de7d3..690314ab782 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -5402,4 +5402,82 @@ ) ) ) + + ;; CHECK: (func $three.contradiction.middle (type $0) (param $x i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.lt_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.ge_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $three.contradiction.middle (type $0) (param $x i32) + ;; OPTIN-NEXT: (if + ;; OPTIN-NEXT: (i32.and + ;; OPTIN-NEXT: (i32.eqz + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (i32.and + ;; OPTIN-NEXT: (i32.lt_s + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (i32.const 0) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (i32.ge_s + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (i32.const 0) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (then + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (unreachable) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $three.contradiction.middle (param $x i32) + ;; First we set x == 0, then x < 0, then x >= 0. The first two are a + ;; contradiction. We should not error (after a contradiction, we should not + ;; apply further constraints), and can optimize away the if body. + (if + (i32.and + (i32.eqz + (local.get $x) + ) + (i32.and + (i32.lt_s + (local.get $x) + (i32.const 0) + ) + (i32.ge_s + (local.get $x) + (i32.const 0) + ) + ) + ) + (then + (drop + (i32.eqz + (local.get $x) + ) + ) + ) + ) + ) ) From 39d675ccc836bf45df401cec45539252405d056d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 2 Sep 2026 09:14:10 -0700 Subject: [PATCH 22/53] show bug --- test/lit/passes/constraint-analysis.wast | 118 ++++++++++++++++++++--- 1 file changed, 104 insertions(+), 14 deletions(-) diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index 690314ab782..851bcf0cd8f 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -13,6 +13,10 @@ ;; OPTIN: (type $array (array (mut i32))) (type $array (array (mut i32))) + ;; CHECK: (import "a" "b" (func $import (type $3) (result i32))) + ;; OPTIN: (import "a" "b" (func $import (type $3) (result i32))) + (import "a" "b" (func $import (result i32))) + ;; CHECK: (func $simple (type $1) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local.set $x @@ -1731,7 +1735,7 @@ ) ) - ;; CHECK: (func $br_on_null (type $5) (param $param anyref) + ;; CHECK: (func $br_on_null (type $6) (param $param anyref) ;; CHECK-NEXT: (block $block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.is_null @@ -1752,7 +1756,7 @@ ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $br_on_null (type $5) (param $param anyref) + ;; OPTIN: (func $br_on_null (type $6) (param $param anyref) ;; OPTIN-NEXT: (block $block ;; OPTIN-NEXT: (drop ;; OPTIN-NEXT: (ref.is_null @@ -1802,7 +1806,7 @@ ) ) - ;; CHECK: (func $br_on_non_null (type $5) (param $param anyref) + ;; CHECK: (func $br_on_non_null (type $6) (param $param anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $block (result (ref any)) ;; CHECK-NEXT: (drop @@ -1823,7 +1827,7 @@ ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $br_on_non_null (type $5) (param $param anyref) + ;; OPTIN: (func $br_on_non_null (type $6) (param $param anyref) ;; OPTIN-NEXT: (drop ;; OPTIN-NEXT: (block $block (result (ref any)) ;; OPTIN-NEXT: (drop @@ -2476,7 +2480,7 @@ ) ) - ;; CHECK: (func $local-changes (type $3) (param $x i32) (param $y i32) (param $z i32) + ;; CHECK: (func $local-changes (type $4) (param $x i32) (param $y i32) (param $z i32) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) @@ -2532,7 +2536,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $local-changes (type $3) (param $x i32) (param $y i32) (param $z i32) + ;; OPTIN: (func $local-changes (type $4) (param $x i32) (param $y i32) (param $z i32) ;; OPTIN-NEXT: (local.set $x ;; OPTIN-NEXT: (local.get $y) ;; OPTIN-NEXT: ) @@ -2662,7 +2666,7 @@ ) ) - ;; CHECK: (func $local-changes-2 (type $3) (param $x i32) (param $y i32) (param $z i32) + ;; CHECK: (func $local-changes-2 (type $4) (param $x i32) (param $y i32) (param $z i32) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) @@ -2718,7 +2722,7 @@ ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $local-changes-2 (type $3) (param $x i32) (param $y i32) (param $z i32) + ;; OPTIN: (func $local-changes-2 (type $4) (param $x i32) (param $y i32) (param $z i32) ;; OPTIN-NEXT: (local.set $x ;; OPTIN-NEXT: (local.get $y) ;; OPTIN-NEXT: ) @@ -3052,7 +3056,7 @@ ) ) - ;; CHECK: (func $local-changes-ne (type $3) (param $x i32) (param $y i32) (param $z i32) + ;; CHECK: (func $local-changes-ne (type $4) (param $x i32) (param $y i32) (param $z i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.ne ;; CHECK-NEXT: (local.get $x) @@ -3104,7 +3108,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $local-changes-ne (type $3) (param $x i32) (param $y i32) (param $z i32) + ;; OPTIN: (func $local-changes-ne (type $4) (param $x i32) (param $y i32) (param $z i32) ;; OPTIN-NEXT: (if ;; OPTIN-NEXT: (i32.ne ;; OPTIN-NEXT: (local.get $x) @@ -4457,7 +4461,7 @@ ) ) - ;; CHECK: (func $flipped-contradiction (type $6) (result i32) + ;; CHECK: (func $flipped-contradiction (type $3) (result i32) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (loop $loop ;; CHECK-NEXT: (br_if $loop @@ -4469,7 +4473,7 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $flipped-contradiction (type $6) (result i32) + ;; OPTIN: (func $flipped-contradiction (type $3) (result i32) ;; OPTIN-NEXT: (local $x i32) ;; OPTIN-NEXT: (loop $loop ;; OPTIN-NEXT: (br_if $loop @@ -4503,7 +4507,7 @@ ) ) - ;; CHECK: (func $flipped-contradiction-no (type $6) (result i32) + ;; CHECK: (func $flipped-contradiction-no (type $3) (result i32) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (loop $loop (result i32) ;; CHECK-NEXT: (br_if $loop @@ -4517,7 +4521,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $flipped-contradiction-no (type $6) (result i32) + ;; OPTIN: (func $flipped-contradiction-no (type $3) (result i32) ;; OPTIN-NEXT: (local $x i32) ;; OPTIN-NEXT: (loop $loop (result i32) ;; OPTIN-NEXT: (br_if $loop @@ -5230,6 +5234,92 @@ ) ) + ;; CHECK: (func $pair-partial (type $0) (param $a i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $pair-partial (type $0) (param $a i32) + ;; OPTIN-NEXT: (if + ;; OPTIN-NEXT: (i32.and + ;; OPTIN-NEXT: (call $import) + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $a) + ;; OPTIN-NEXT: (i32.const 42) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (then + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (else + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 0) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 42) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $pair-partial (param $a i32) + (if + (i32.and + ;; a == 42 + (i32.eq + (local.get $a) + (i32.const 42) + ) + ;; ANDed with something we know nothing about + (call $import) + ) + (then + ;; a must be 42, to get here. + (drop + (i32.eq + (local.get $a) + (i32.const 42) + ) + ) + ) + (else + ;; But we don't know anything about a here: we get to this place when + ;; a != 42 OR the unknown was 0, so a could be anything. + (drop + (i32.eq + (local.get $a) + (i32.const 42) + ) + ) + ;; A silly extra instruction to stop optimize-instructions from + ;; folding the if-else arms. + (drop + (i32.const 42) + ) + ) + ) + ) + ;; CHECK: (func $several (type $11) (param $a i32) (param $b i32) (param $c i32) (param $d i32) (param $e i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.and From d706843abb8c7c743d0b7d4eaecde70e6c269a01 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 2 Sep 2026 09:16:35 -0700 Subject: [PATCH 23/53] work --- src/ir/constraint.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 25b892772b1..9b11200b838 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -236,17 +236,16 @@ struct LocalConstraint { Index local; Constraint constraint; - // Try to parse BinaryenIR into a local to which a constraint is applied. For + // Try to parse BinaryenIR into local to whom constraints are all applied. For // example // // (i32.eq (local.get $r) (i32.const 10)) // // parses into // - // LocalConstraint($r, { x == 10 }) + // [ LocalConstraint($r, { x == 10 }) ] // - // Usually a single constraint is found, but if the expression is an AND over - // several, a vector is returned: + // If the expression is an AND over several constraints, a vector is returned: // // (i32.and (..A..) (..B..)) // From 576ca49dbefc6398d583c07171e76bac301f1c82 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 2 Sep 2026 10:23:18 -0700 Subject: [PATCH 24/53] work --- src/ir/constraint.cpp | 6 +++--- src/ir/constraint.h | 13 +++++++++++-- src/passes/ConstraintAnalysis.cpp | 14 +++++++------- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 90424b013e5..25e2bf334e7 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -733,9 +733,9 @@ bool AndedConstraintSet::approximateOr(const AndedConstraintSet& other) { return changed; } -SmallVector LocalConstraint::parse(Expression* curr) { +Parsed LocalConstraint::parse(Expression* curr) { // The final return value. - SmallVector ret; + Parsed ret; // Starting form |curr|, parse and recurse into sub-trees: when we see an AND, // for example, we can push both children as further work. @@ -842,7 +842,7 @@ SmallVector LocalConstraint::parse(Expression* curr) { return ret; } -SmallVector +Parsed LocalConstraint::parseCondition(Expression* curr) { // A get by itself is a check for not being null. if (auto* get = curr->dynCast()) { diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 9b11200b838..25687a06591 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -253,11 +253,20 @@ struct LocalConstraint { // // [ A, B ] // - static SmallVector parse(Expression* curr); + struct Parsed : public Parsed { + // Whether, in addition to the expressions we parsed into constraints, there + // were also other unknown things. For example, + // + // (i32.and (i32.eq (local.get $r) (i32.const 10)) (call $unknown)) + // + // Would parse into $r == 10 and also set hasUnknown. + bool hasUnknown = false; + }; + static Parsed parse(Expression* curr); // Parse in a condition context, i.e., where (local.get $x) is the same as // $x != 0 (e.g., in an if condition, or a br_on ref). - static SmallVector parseCondition(Expression* curr); + static Parsed parseCondition(Expression* curr); // Reverse the constraint. The constraint's term must, of course, be another // local. diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 2bb5e85ebe2..e5aaf445777 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -476,7 +476,7 @@ struct ConstraintAnalysis // Given a predecessor and one of its successors, find new constraints that // can be added due to the flow to that specific successor. - SmallVector getBranchConstraints(BasicBlock* pred, + LocalConstraint::Parsed getBranchConstraints(BasicBlock* pred, BasicBlock* succ) { auto* brancher = pred->contents.brancher; if (!brancher) { @@ -506,7 +506,7 @@ struct ConstraintAnalysis return {}; } - SmallVector getConstraintsFromIf(If* iff, + LocalConstraint::Parsed getConstraintsFromIf(If* iff, bool physicalSuccessor) { auto parsed = LocalConstraint::parseCondition(iff->condition); if (!physicalSuccessor) { @@ -516,7 +516,7 @@ struct ConstraintAnalysis return parsed; } - SmallVector + LocalConstraint::Parsed getConstraintsFromBreak(Break* br, bool physicalSuccessor) { // We get here when there is more than one successor, so there must be a // condition. @@ -530,7 +530,7 @@ struct ConstraintAnalysis return parsed; } - SmallVector + LocalConstraint::Parsed getConstraintsFromBrOn(BrOn* brOn, bool physicalSuccessor) { // The constraint on that local depends on the op. // TODO: Handle BrOnCast* etc using subtyping operations. @@ -550,7 +550,7 @@ struct ConstraintAnalysis } // Given a list of parsed constraints on locals, negate them. - void negate(SmallVector& parsed) { + void negate(LocalConstraint::Parsed& parsed) { // The input is a list of constraints all applying at once, A & B & C. The // negation is !A | !B | !C, but we cannot expression a general OR like // that, so we only negate a list of one. TODO: if all the constraints are @@ -681,7 +681,7 @@ struct ConstraintAnalysis return true; } - bool checkRelevancy(const SmallVector& parsed) { + bool checkRelevancy(const LocalConstraint::Parsed& parsed) { return std::any_of( parsed.begin(), parsed.end(), [&](const LocalConstraint& pair) { return checkRelevancy(pair); @@ -689,7 +689,7 @@ struct ConstraintAnalysis } // Apply branch constraints to the current set of constraints. - void applyBranchConstraints(const SmallVector& branch, + void applyBranchConstraints(const LocalConstraint::Parsed& branch, BasicBlockConstraintMap& constraints) { for (auto& pair : branch) { // Extend the range of values in the "jump ahead" manner described in the From 9730b9796db2f90046ec1802b2b05ed2cc53a683 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 2 Sep 2026 11:38:28 -0700 Subject: [PATCH 25/53] work --- src/ir/constraint.cpp | 8 +++--- src/ir/constraint.h | 61 ++++++++++++++++++++++--------------------- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 25e2bf334e7..0e072099e29 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -733,9 +733,9 @@ bool AndedConstraintSet::approximateOr(const AndedConstraintSet& other) { return changed; } -Parsed LocalConstraint::parse(Expression* curr) { +ParsedAndedConstraints ParsedAndedConstraints::parse(Expression* curr) { // The final return value. - Parsed ret; + ParsedAndedConstraints ret; // Starting form |curr|, parse and recurse into sub-trees: when we see an AND, // for example, we can push both children as further work. @@ -842,8 +842,8 @@ Parsed LocalConstraint::parse(Expression* curr) { return ret; } -Parsed -LocalConstraint::parseCondition(Expression* curr) { +ParsedAndedConstraints +ParsedAndedConstraints::parseCondition(Expression* curr) { // A get by itself is a check for not being null. if (auto* get = curr->dynCast()) { auto value = Literal::makeZero(get->type); diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 25687a06591..20d5fec30bf 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -236,41 +236,42 @@ struct LocalConstraint { Index local; Constraint constraint; - // Try to parse BinaryenIR into local to whom constraints are all applied. For - // example - // - // (i32.eq (local.get $r) (i32.const 10)) - // - // parses into - // - // [ LocalConstraint($r, { x == 10 }) ] - // - // If the expression is an AND over several constraints, a vector is returned: - // - // (i32.and (..A..) (..B..)) - // - // parses into + // Reverse the constraint. The constraint's term must, of course, be another + // local. + void flip(); +}; + +// A utility to parse BinaryenIR into local and constraints on them. For +// example: +// +// (i32.eq (local.get $r) (i32.const 10)) +// +// parses into +// +// [ LocalConstraint($r, { x == 10 }) ] +// +// If the expression is an AND over several constraints, a vector is returned: +// +// (i32.and (..A..) (..B..)) +// +// parses into +// +// [ A, B ] +// +struct ParsedAndedConstraints : public SmallVector { + // Whether, in addition to the expressions we parsed into constraints, there + // were also other unknown things. For example, // - // [ A, B ] + // (i32.and (i32.eq (local.get $r) (i32.const 10)) (call $unknown)) // - struct Parsed : public Parsed { - // Whether, in addition to the expressions we parsed into constraints, there - // were also other unknown things. For example, - // - // (i32.and (i32.eq (local.get $r) (i32.const 10)) (call $unknown)) - // - // Would parse into $r == 10 and also set hasUnknown. - bool hasUnknown = false; - }; - static Parsed parse(Expression* curr); + // Would parse into $r == 10 and also set hasUnknown. + bool hasUnknown = false; + + static ParsedAndedConstraints parse(Expression* curr); // Parse in a condition context, i.e., where (local.get $x) is the same as // $x != 0 (e.g., in an if condition, or a br_on ref). - static Parsed parseCondition(Expression* curr); - - // Reverse the constraint. The constraint's term must, of course, be another - // local. - void flip(); + static ParsedAndedConstraints parseCondition(Expression* curr); }; // A map of locals and their constraints, representing the state at a basic From e1d11722b978268ebf1cd237ca0cc021cdc44ff0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 2 Sep 2026 11:45:09 -0700 Subject: [PATCH 26/53] fixes --- src/ir/constraint.h | 2 ++ src/passes/ConstraintAnalysis.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 20d5fec30bf..704965bded7 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -259,6 +259,8 @@ struct LocalConstraint { // [ A, B ] // struct ParsedAndedConstraints : public SmallVector { + using SmallVector::SmallVector; + // Whether, in addition to the expressions we parsed into constraints, there // were also other unknown things. For example, // diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index e5aaf445777..f4716dfc2cc 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -148,7 +148,7 @@ struct ConstraintAnalysis void maybeMarkRelevant(Expression* curr) { // If this parses into a constraint on a local, that local is relevant. - for (auto& pair : LocalConstraint::parseCondition(curr)) { + for (auto& pair : ParsedAndedConstraints::parseCondition(curr)) { if (isRelevantType(getFunction()->getLocalType(pair.local))) { relevantLocals[pair.local] = true; if (auto* other = std::get_if(&pair.constraint.term)) { From 376845d9ffde1c85f7423a85a77aa187de5f75d4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 2 Sep 2026 11:46:36 -0700 Subject: [PATCH 27/53] work --- src/passes/ConstraintAnalysis.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index f4716dfc2cc..53e99d7a637 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -444,7 +444,7 @@ struct ConstraintAnalysis void optimizeExpression(Expression** currp, const BasicBlockConstraintMap& constraints) { auto* curr = *currp; - auto parsed = LocalConstraint::parse(curr); + auto parsed = ParsedAndedConstraints::parse(curr); // TODO: optimize cases of more than one. if (parsed.size() != 1) { return; @@ -476,7 +476,7 @@ struct ConstraintAnalysis // Given a predecessor and one of its successors, find new constraints that // can be added due to the flow to that specific successor. - LocalConstraint::Parsed getBranchConstraints(BasicBlock* pred, + ParsedAndedConstraints getBranchConstraints(BasicBlock* pred, BasicBlock* succ) { auto* brancher = pred->contents.brancher; if (!brancher) { @@ -506,9 +506,9 @@ struct ConstraintAnalysis return {}; } - LocalConstraint::Parsed getConstraintsFromIf(If* iff, + ParsedAndedConstraints getConstraintsFromIf(If* iff, bool physicalSuccessor) { - auto parsed = LocalConstraint::parseCondition(iff->condition); + auto parsed = ParsedAndedConstraints::parseCondition(iff->condition); if (!physicalSuccessor) { // We are in the ifFalse, so negate the condition. negate(parsed); @@ -516,13 +516,13 @@ struct ConstraintAnalysis return parsed; } - LocalConstraint::Parsed + ParsedAndedConstraints getConstraintsFromBreak(Break* br, bool physicalSuccessor) { // We get here when there is more than one successor, so there must be a // condition. assert(br->condition); - auto parsed = LocalConstraint::parseCondition(br->condition); + auto parsed = ParsedAndedConstraints::parseCondition(br->condition); if (physicalSuccessor) { // The branch was not taken, so negate the condition. negate(parsed); @@ -530,7 +530,7 @@ struct ConstraintAnalysis return parsed; } - LocalConstraint::Parsed + ParsedAndedConstraints getConstraintsFromBrOn(BrOn* brOn, bool physicalSuccessor) { // The constraint on that local depends on the op. // TODO: Handle BrOnCast* etc using subtyping operations. @@ -541,7 +541,7 @@ struct ConstraintAnalysis // parseCondition can parse more things than a local.get, which is all we // handle here, but there is no other valid IR that can appear there, so we // can reuse it. - auto parsed = LocalConstraint::parseCondition(brOn->ref); + auto parsed = ParsedAndedConstraints::parseCondition(brOn->ref); // Negate depending on the op and (similar to Break) the successor. if ((brOn->op == BrOnNull) ^ physicalSuccessor) { negate(parsed); @@ -550,7 +550,7 @@ struct ConstraintAnalysis } // Given a list of parsed constraints on locals, negate them. - void negate(LocalConstraint::Parsed& parsed) { + void negate(ParsedAndedConstraints& parsed) { // The input is a list of constraints all applying at once, A & B & C. The // negation is !A | !B | !C, but we cannot expression a general OR like // that, so we only negate a list of one. TODO: if all the constraints are @@ -681,7 +681,7 @@ struct ConstraintAnalysis return true; } - bool checkRelevancy(const LocalConstraint::Parsed& parsed) { + bool checkRelevancy(const ParsedAndedConstraints& parsed) { return std::any_of( parsed.begin(), parsed.end(), [&](const LocalConstraint& pair) { return checkRelevancy(pair); @@ -689,7 +689,7 @@ struct ConstraintAnalysis } // Apply branch constraints to the current set of constraints. - void applyBranchConstraints(const LocalConstraint::Parsed& branch, + void applyBranchConstraints(const ParsedAndedConstraints& branch, BasicBlockConstraintMap& constraints) { for (auto& pair : branch) { // Extend the range of values in the "jump ahead" manner described in the From a671a6341432680fd5890a9bd2f01b8219d137e6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 2 Sep 2026 11:50:02 -0700 Subject: [PATCH 28/53] work --- src/ir/constraint.cpp | 3 +++ src/passes/ConstraintAnalysis.cpp | 8 ++++---- test/lit/passes/constraint-analysis.wast | 10 ++++++++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 0e072099e29..92b71536392 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -837,6 +837,9 @@ ParsedAndedConstraints ParsedAndedConstraints::parse(Expression* curr) { parseBinaryArguments(Abstract::Eq, refEq->left, refEq->right); continue; } + + // We failed to parse this. + ret.hasUnknown = true; } return ret; diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 53e99d7a637..aee6bf58f79 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -552,10 +552,10 @@ struct ConstraintAnalysis // Given a list of parsed constraints on locals, negate them. void negate(ParsedAndedConstraints& parsed) { // The input is a list of constraints all applying at once, A & B & C. The - // negation is !A | !B | !C, but we cannot expression a general OR like - // that, so we only negate a list of one. TODO: if all the constraints are - // on the same local, we could use approximateOr. - if (parsed.size() == 1) { + // negation is !A | !B | !C, but we cannot express a general OR like that, + // so we only negate a list of one (and where nothing else exists). + // TODO: if all the constraints are on the same local, use approximateOr. + if (parsed.size() == 1 && !parsed.hasUnknown) { parsed[0].constraint = parsed[0].constraint.negate(); } else { parsed.clear(); diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index 851bcf0cd8f..4c158029ef1 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -5250,7 +5250,10 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 42) @@ -5274,7 +5277,10 @@ ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (else ;; OPTIN-NEXT: (drop - ;; OPTIN-NEXT: (i32.const 0) + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $a) + ;; OPTIN-NEXT: (i32.const 42) + ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (drop ;; OPTIN-NEXT: (i32.const 42) From 6005e4cc5f7f28ec970df82f68c2831f0dd16749 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 2 Sep 2026 11:50:08 -0700 Subject: [PATCH 29/53] work --- src/passes/ConstraintAnalysis.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index aee6bf58f79..7e3f71e6245 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -477,7 +477,7 @@ struct ConstraintAnalysis // Given a predecessor and one of its successors, find new constraints that // can be added due to the flow to that specific successor. ParsedAndedConstraints getBranchConstraints(BasicBlock* pred, - BasicBlock* succ) { + BasicBlock* succ) { auto* brancher = pred->contents.brancher; if (!brancher) { return {}; @@ -506,8 +506,7 @@ struct ConstraintAnalysis return {}; } - ParsedAndedConstraints getConstraintsFromIf(If* iff, - bool physicalSuccessor) { + ParsedAndedConstraints getConstraintsFromIf(If* iff, bool physicalSuccessor) { auto parsed = ParsedAndedConstraints::parseCondition(iff->condition); if (!physicalSuccessor) { // We are in the ifFalse, so negate the condition. @@ -516,8 +515,8 @@ struct ConstraintAnalysis return parsed; } - ParsedAndedConstraints - getConstraintsFromBreak(Break* br, bool physicalSuccessor) { + ParsedAndedConstraints getConstraintsFromBreak(Break* br, + bool physicalSuccessor) { // We get here when there is more than one successor, so there must be a // condition. assert(br->condition); @@ -530,8 +529,8 @@ struct ConstraintAnalysis return parsed; } - ParsedAndedConstraints - getConstraintsFromBrOn(BrOn* brOn, bool physicalSuccessor) { + ParsedAndedConstraints getConstraintsFromBrOn(BrOn* brOn, + bool physicalSuccessor) { // The constraint on that local depends on the op. // TODO: Handle BrOnCast* etc using subtyping operations. if (brOn->op != BrOnNull && brOn->op != BrOnNonNull) { From a0da7e04b6be6cd1352f691d4f49bda93248d6f1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 2 Sep 2026 11:54:40 -0700 Subject: [PATCH 30/53] work --- src/passes/ConstraintAnalysis.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 7e3f71e6245..ead94b4b9a0 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -550,12 +550,18 @@ struct ConstraintAnalysis // Given a list of parsed constraints on locals, negate them. void negate(ParsedAndedConstraints& parsed) { + if (parsed.hasUnknown) { + // This includes things we don't know about, and don't know how to negate. + parsed.clear(); + return; + } + // The input is a list of constraints all applying at once, A & B & C. The // negation is !A | !B | !C, but we cannot express a general OR like that, - // so we only negate a list of one (and where nothing else exists). - // TODO: if all the constraints are on the same local, use approximateOr. - if (parsed.size() == 1 && !parsed.hasUnknown) { + // except in the simple case of one constraint. + if (parsed.size() == 1) { parsed[0].constraint = parsed[0].constraint.negate(); + return; } else { parsed.clear(); } From d8553ec0a129b4dedf272bcf63f0d66cb18eeca0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 2 Sep 2026 12:01:36 -0700 Subject: [PATCH 31/53] work --- src/passes/ConstraintAnalysis.cpp | 35 +++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index ead94b4b9a0..e41d2f6af8c 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -550,6 +550,10 @@ struct ConstraintAnalysis // Given a list of parsed constraints on locals, negate them. void negate(ParsedAndedConstraints& parsed) { + if (parsed.empty()) { + return; + } + if (parsed.hasUnknown) { // This includes things we don't know about, and don't know how to negate. parsed.clear(); @@ -558,13 +562,32 @@ struct ConstraintAnalysis // The input is a list of constraints all applying at once, A & B & C. The // negation is !A | !B | !C, but we cannot express a general OR like that, - // except in the simple case of one constraint. - if (parsed.size() == 1) { - parsed[0].constraint = parsed[0].constraint.negate(); - return; - } else { - parsed.clear(); + // except in the simple case where they all talk about the same local: then + // we can at least approximateOr them all into one constraint. + for (Index i = 1; i < parsed.size(); i++) { + if (parsed[i].local != parsed[0].local) { + // They refer to different locals. Give up. + parsed.clear(); + return; + } } + + // Negate them, then OR. + for (auto& pair : parsed) { + pair.constraint = pair.constraint.negate(); + } + + for (Index i = 1; i < parsed.size(); i++) { + parsed[0].constraint.approximateOr(parsed[i].constraint); + if (parsed[0].constraint.provesNothing()) { + // We have nothing useful here. + parsed.clear(); + return; + } + } + + // Return only the OR'ed result. + parsed.resize(1); } // When applying constraints for a binary operation like x = y + 1, we may From 4b6f293d703628acee5521dced82e56ee249df84 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 2 Sep 2026 13:24:42 -0700 Subject: [PATCH 32/53] work --- src/passes/ConstraintAnalysis.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index e41d2f6af8c..8a07d568236 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -572,14 +572,22 @@ struct ConstraintAnalysis } } - // Negate them, then OR. + // Negate them before the OR. for (auto& pair : parsed) { pair.constraint = pair.constraint.negate(); } + if (parsed.size() == 1) { + // The simple case of 1 doesn't need any more work. + return; + } + + // Do the OR. + AndedConstraintSet anded; + anded.set(parsed[0].constraint); for (Index i = 1; i < parsed.size(); i++) { - parsed[0].constraint.approximateOr(parsed[i].constraint); - if (parsed[0].constraint.provesNothing()) { + anded.approximateOr(parsed[i].constraint); + if (anded.provesNothing()) { // We have nothing useful here. parsed.clear(); return; @@ -587,7 +595,11 @@ struct ConstraintAnalysis } // Return only the OR'ed result. - parsed.resize(1); + auto local = parsed[0].local; + parsed.clear(); + for (auto& c : anded) { + parsed.emplace_back(local, c); + } } // When applying constraints for a binary operation like x = y + 1, we may From 4c3b1f5a4e965fb6e1f1030568cad4b98baaac4e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 2 Sep 2026 13:33:11 -0700 Subject: [PATCH 33/53] work --- 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 8a07d568236..f0c56a61889 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -586,7 +586,7 @@ struct ConstraintAnalysis AndedConstraintSet anded; anded.set(parsed[0].constraint); for (Index i = 1; i < parsed.size(); i++) { - anded.approximateOr(parsed[i].constraint); + anded.approximateOr({parsed[i].constraint}); if (anded.provesNothing()) { // We have nothing useful here. parsed.clear(); From 89ea6720e28908ac55fa7786e2005b56b43eb394 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 2 Sep 2026 13:36:57 -0700 Subject: [PATCH 34/53] work --- test/lit/passes/constraint-analysis.wast | 91 +++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index 4c158029ef1..d6c6f5ef3fc 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -5206,7 +5206,6 @@ (else ;; The same expressions as in the (then ..). Here, at least one must be ;; false, not not necessarily all of them, so we infer nothing. - ;; These are all true. (drop (i32.eq (local.get $a) @@ -5234,6 +5233,96 @@ ) ) + ;; CHECK: (func $pair-same-local (type $0) (param $a i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.gt_u + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.gt_u + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.le_u + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $pair-same-local (type $0) (param $a i32) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (if (result i32) + ;; OPTIN-NEXT: (i32.and + ;; OPTIN-NEXT: (i32.gt_u + ;; OPTIN-NEXT: (local.get $a) + ;; OPTIN-NEXT: (i32.const 42) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (i32.gt_u + ;; OPTIN-NEXT: (local.get $a) + ;; OPTIN-NEXT: (i32.const 1337) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (then + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (else + ;; OPTIN-NEXT: (i32.le_u + ;; OPTIN-NEXT: (local.get $a) + ;; OPTIN-NEXT: (i32.const 42) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $pair-same-local (param $a i32) + ;; Similar to above, but now the two ANDed conditions are about the same + ;; local + (if + (i32.and + ;; a > 42 + (i32.gt_u + (local.get $a) + (i32.const 42) + ) + ;; a > 1337 + (i32.gt_u + (local.get $a) + (i32.const 1337) + ) + ) + (then + ;; This is true. + (drop + (i32.gt_u + (local.get $a) + (i32.const 1337) + ) + ) + ) + (else + ;; The negation of a > 42 && a > 1337 is a <= 42 || a <= 1337, so we + ;; can infer a <= 42. + (drop + (i32.le_u + (local.get $a) + (i32.const 42) + ) + ) + ) + ) + ) + ;; CHECK: (func $pair-partial (type $0) (param $a i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.and From f038bbe2cfa67ec9fa48a58ccc1aa6be1d1ba6d1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 2 Sep 2026 13:39:42 -0700 Subject: [PATCH 35/53] work --- test/lit/passes/constraint-analysis.wast | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index d6c6f5ef3fc..5dc86ffcbbb 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -5252,10 +5252,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.le_u - ;; CHECK-NEXT: (local.get $a) - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -5277,10 +5274,7 @@ ;; OPTIN-NEXT: (i32.const 1) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (else - ;; OPTIN-NEXT: (i32.le_u - ;; OPTIN-NEXT: (local.get $a) - ;; OPTIN-NEXT: (i32.const 42) - ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (i32.const 1) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) @@ -5312,11 +5306,11 @@ ) (else ;; The negation of a > 42 && a > 1337 is a <= 42 || a <= 1337, so we - ;; can infer a <= 42. + ;; can infer a <= 1337. (drop (i32.le_u (local.get $a) - (i32.const 42) + (i32.const 1337) ) ) ) From 8d4c3acc30f9083e477b6f330f242ed284f33e96 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 2 Sep 2026 14:07:17 -0700 Subject: [PATCH 36/53] work --- test/lit/passes/constraint-analysis.wast | 157 +++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index 5dc86ffcbbb..0deff510977 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -5317,6 +5317,163 @@ ) ) + ;; CHECK: (func $pair-same-local-bad-or (type $0) (param $a i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; 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: (drop + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (i32.const 500) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (i32.const 500) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $pair-same-local-bad-or (type $0) (param $a i32) + ;; OPTIN-NEXT: (if + ;; OPTIN-NEXT: (i32.and + ;; OPTIN-NEXT: (i32.ne + ;; OPTIN-NEXT: (local.get $a) + ;; OPTIN-NEXT: (i32.const 42) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (i32.ne + ;; OPTIN-NEXT: (local.get $a) + ;; OPTIN-NEXT: (i32.const 1337) + ;; OPTIN-NEXT: ) + ;; 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: (drop + ;; OPTIN-NEXT: (i32.ne + ;; OPTIN-NEXT: (local.get $a) + ;; OPTIN-NEXT: (i32.const 500) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (else + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $a) + ;; OPTIN-NEXT: (i32.const 42) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $a) + ;; OPTIN-NEXT: (i32.const 1337) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $a) + ;; OPTIN-NEXT: (i32.const 500) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $pair-same-local-bad-or (param $a i32) + ;; As above, but the OR we do for the else fails. + (if + (i32.and + ;; a != 42 && a != 1337 + (i32.ne + (local.get $a) + (i32.const 42) + ) + (i32.ne + (local.get $a) + (i32.const 1337) + ) + ) + (then + ;; These are true. + (drop + (i32.ne + (local.get $a) + (i32.const 42) + ) + ) + (drop + (i32.ne + (local.get $a) + (i32.const 1337) + ) + ) + ;; This is unknown. + (drop + (i32.ne + (local.get $a) + (i32.const 500) + ) + ) + ) + (else + ;; ORing a == 42 || a == 1337, we get nothing useful, and can prove + ;; nothing. TODO: we could infer a span here + (drop + (i32.eq + (local.get $a) + (i32.const 42) + ) + ) + (drop + (i32.eq + (local.get $a) + (i32.const 1337) + ) + ) + (drop + (i32.eq + (local.get $a) + (i32.const 500) + ) + ) + ) + ) + ) + ;; CHECK: (func $pair-partial (type $0) (param $a i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.and From f8a26492dd6b137f275f1926a96428fdf6b0f202 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 2 Sep 2026 14:43:10 -0700 Subject: [PATCH 37/53] work --- src/ir/constraint.h | 3 +- src/passes/ConstraintAnalysis.cpp | 4 +-- test/lit/passes/constraint-analysis.wast | 36 ++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 704965bded7..d53e9683a0d 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -250,7 +250,8 @@ struct LocalConstraint { // // [ LocalConstraint($r, { x == 10 }) ] // -// If the expression is an AND over several constraints, a vector is returned: +// If the expression is an AND over several things, several constraints may be +// returned: // // (i32.and (..A..) (..B..)) // diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index f0c56a61889..6d6451f9861 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -445,8 +445,8 @@ struct ConstraintAnalysis const BasicBlockConstraintMap& constraints) { auto* curr = *currp; auto parsed = ParsedAndedConstraints::parse(curr); - // TODO: optimize cases of more than one. - if (parsed.size() != 1) { + // TODO: optimize cases of more than one, and with unknowns + if (parsed.size() != 1 || parsed.hasUnknown) { return; } if (!checkRelevancy(parsed[0])) { diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index 0deff510977..e768dbcb4fc 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -5816,4 +5816,40 @@ ) ) ) + + ;; CHECK: (func $optimize-unknown (type $1) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $optimize-unknown (type $1) + ;; OPTIN-NEXT: (local $x i32) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.and + ;; OPTIN-NEXT: (call $import) + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $optimize-unknown + (local $x i32) + ;; We can optimize the i32.eq (x == 0 as the default value), but we should + ;; not do anything to the i32.and (it might appear like something we can + ;; optimize, as one arm is parseable, but the other is not; in any event, we + ;; process the i32.eq first, so we don't even get the chance to mis-optimize + ;; here, but this test at least verifies the i32.and is not touched). + (drop + (i32.and + (i32.eq + (local.get $x) + (i32.const 0) + ) + (call $import) + ) + ) + ) ) From 68bca39129bd06381f3438ddabf3af0841d2600b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 2 Sep 2026 15:53:22 -0700 Subject: [PATCH 38/53] Add missing cases --- src/ir/constraint.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 92b71536392..d700176e77c 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -754,7 +754,9 @@ ParsedAndedConstraints ParsedAndedConstraints::parse(Expression* curr) { return; } - // TODO: Recursively parse and reverse a constraint + // We did not recognize this, so the output contains unknown things. + // TODO: Recursively parse and negate things other than local.get + ret.hasUnknown = true; }; if (auto* unary = curr->dynCast()) { @@ -773,7 +775,10 @@ ParsedAndedConstraints ParsedAndedConstraints::parse(Expression* curr) { } parseEqZArgument(unary->value); + continue; } + + ret.hasUnknown = true; continue; } @@ -790,6 +795,7 @@ ParsedAndedConstraints ParsedAndedConstraints::parse(Expression* curr) { if (Properties::isSingleConstantExpression(expr)) { return Term{Properties::getLiteral(expr)}; } + ret.hasUnknown = true; return {}; }; @@ -800,8 +806,11 @@ ParsedAndedConstraints ParsedAndedConstraints::parse(Expression* curr) { // The right can be any term. if (auto value = parseTerm(right)) { ret.push_back(LocalConstraint{get->index, Constraint{op, *value}}); + return; } } + + ret.hasUnknown = true; }; if (auto* binary = curr->dynCast()) { @@ -815,6 +824,7 @@ ParsedAndedConstraints ParsedAndedConstraints::parse(Expression* curr) { // TODO: support OR // Otherwise, the operation must be one we can express as a constraint. + bool handled = false; for (auto op : {Abstract::Eq, Abstract::Ne, Abstract::LtS, @@ -827,9 +837,15 @@ ParsedAndedConstraints ParsedAndedConstraints::parse(Expression* curr) { Abstract::GeU}) { if (Abstract::getBinary(binary->left->type, op) == binary->op) { parseBinaryArguments(op, binary->left, binary->right); + handled = true; break; } } + + if (!handled) { + ret.hasUnknown = true; + } + continue; } From 6d2e4ad534a83f098800074e9cdf0a0f353cce56 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 2 Sep 2026 16:41:43 -0700 Subject: [PATCH 39/53] test --- src/ir/constraint.cpp | 7 +- src/ir/constraint.h | 3 + test/gtest/constraint.cpp | 380 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 389 insertions(+), 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index d700176e77c..a2835d2abcf 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -737,7 +737,7 @@ ParsedAndedConstraints ParsedAndedConstraints::parse(Expression* curr) { // The final return value. ParsedAndedConstraints ret; - // Starting form |curr|, parse and recurse into sub-trees: when we see an AND, + // Starting from |curr|, parse and recurse into sub-trees: when we see an AND, // for example, we can push both children as further work. SmallVector work; work.push_back(curr); @@ -1206,6 +1206,11 @@ std::ostream& operator<<(std::ostream& o, const Constraint& c) { return o; } +std::ostream& operator<<(std::ostream& o, const LocalConstraint& c) { + o << "LocalConstraint{$" << c.local << ", " << c.constraint << '}'; + return o; +} + std::ostream& operator<<(std::ostream& o, const AndedConstraintSet& set) { if (set.provesEverything()) { o << "AndedConstraintSet(contradiction)"; diff --git a/src/ir/constraint.h b/src/ir/constraint.h index d53e9683a0d..c63021bcb8f 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -236,6 +236,8 @@ struct LocalConstraint { Index local; Constraint constraint; + bool operator==(const LocalConstraint&) const = default; + // Reverse the constraint. The constraint's term must, of course, be another // local. void flip(); @@ -388,6 +390,7 @@ struct BasicBlockConstraintMap { }; std::ostream& operator<<(std::ostream& o, const Constraint& c); +std::ostream& operator<<(std::ostream& o, const LocalConstraint& c); std::ostream& operator<<(std::ostream& o, const AndedConstraintSet& set); } // namespace wasm::constraint diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index ae0a77d61a3..45bb36e26f5 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -2,6 +2,7 @@ #include "ir/constraint.h" #include "ir/abstract.h" +#include "wasm-builder.h" #include "gtest/gtest.h" using namespace wasm; @@ -1644,3 +1645,382 @@ TEST(ConstraintTest, EqualTermPairs) { sGtUContra.approximateAnd(eq1); EXPECT_TRUE(sGtUContra.provesEverything()); } + +TEST(ConstraintTest, ParseUnaryEqZ) { + Module wasm; + Builder builder(wasm); + + // 1. Single i32.eqz of a local.get: parsed as x == 0. + // Covers: parseEqZArgument with LocalGet, Return 1, Continue 2. + { + auto* expr = builder.makeUnary(EqZInt32, builder.makeLocalGet(0, Type::i32)); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_FALSE(parsed.hasUnknown); + ASSERT_EQ(parsed.size(), 1); + EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(0))}}})); + } + + // 2. Single i64.eqz of a local.get: parsed as x == 0_i64. + { + auto* expr = builder.makeUnary(EqZInt64, builder.makeLocalGet(1, Type::i64)); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_FALSE(parsed.hasUnknown); + ASSERT_EQ(parsed.size(), 1); + EXPECT_EQ(parsed[0], (LocalConstraint{1, Constraint{Eq, {Literal(int64_t(0))}}})); + } + + // 3. Nested eqz of eqz: parsed as x != 0. + // Covers: Continue 1. + { + auto* inner = builder.makeUnary(EqZInt32, builder.makeLocalGet(0, Type::i32)); + auto* expr = builder.makeUnary(EqZInt32, inner); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_FALSE(parsed.hasUnknown); + ASSERT_EQ(parsed.size(), 1); + EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{Ne, {Literal(int32_t(0))}}})); + } + + // 4. Nested eqz of eqz with 64-bit inner: parsed as x != 0_i64. + { + auto* inner = builder.makeUnary(EqZInt64, builder.makeLocalGet(1, Type::i64)); + auto* expr = builder.makeUnary(EqZInt32, inner); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_FALSE(parsed.hasUnknown); + ASSERT_EQ(parsed.size(), 1); + EXPECT_EQ(parsed[0], (LocalConstraint{1, Constraint{Ne, {Literal(int64_t(0))}}})); + } + + // 5. eqz of non-local.get (e.g. call): unhandled, sets hasUnknown. + // Covers: Unknown 1 (in parseEqZArgument), Continue 2. + { + auto* call = builder.makeCall("foo", {}, Type::i32); + auto* expr = builder.makeUnary(EqZInt32, call); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_TRUE(parsed.empty()); + EXPECT_TRUE(parsed.hasUnknown); + } + + // 6. Double eqz where inner value is not a local.get: sets hasUnknown. + { + auto* call = builder.makeCall("foo", {}, Type::i32); + auto* inner = builder.makeUnary(EqZInt32, call); + auto* expr = builder.makeUnary(EqZInt32, inner); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_TRUE(parsed.empty()); + EXPECT_TRUE(parsed.hasUnknown); + } + + // 7. Unary operation that is not EqZ: sets hasUnknown. + // Covers: Unknown 2, Continue 3. + { + auto* expr = builder.makeUnary(ClzInt32, builder.makeLocalGet(0, Type::i32)); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_TRUE(parsed.empty()); + EXPECT_TRUE(parsed.hasUnknown); + } +} + +TEST(ConstraintTest, ParseRefIsNull) { + Module wasm; + Builder builder(wasm); + auto anyref = Type(HeapType::any, Nullable); + + // 1. ref.is_null of local.get: parsed as x == null. + // Covers: Continue 4, Return 1. + { + auto* expr = builder.makeRefIsNull(builder.makeLocalGet(0, anyref)); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_FALSE(parsed.hasUnknown); + ASSERT_EQ(parsed.size(), 1); + EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{Eq, {Literal::makeNull(HeapType::any)}}})); + } + + // 2. ref.is_null of non-local.get (e.g. call): unhandled, sets hasUnknown. + // Covers: Unknown 1 (in parseEqZArgument via RefIsNull), Continue 4. + { + auto* call = builder.makeCall("foo", {}, anyref); + auto* expr = builder.makeRefIsNull(call); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_TRUE(parsed.empty()); + EXPECT_TRUE(parsed.hasUnknown); + } + + // 3. ref.is_null of constant: unhandled, sets hasUnknown. + { + auto* expr = builder.makeRefIsNull(builder.makeRefNull(HeapType::any)); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_TRUE(parsed.empty()); + EXPECT_TRUE(parsed.hasUnknown); + } +} + +TEST(ConstraintTest, ParseBinary) { + Module wasm; + Builder builder(wasm); + + // 1. Binary comparison with constant on right: parsed as local constraint. + // Covers: Return 3, Return 5, Continue 6. + { + auto* expr = builder.makeBinary( + EqInt32, builder.makeLocalGet(0, Type::i32), builder.makeConst(Literal(int32_t(42)))); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_FALSE(parsed.hasUnknown); + ASSERT_EQ(parsed.size(), 1); + EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(42))}}})); + } + + // 2. Binary comparison with local.get on right: parsed as local constraint with local term. + // Covers: Return 2, Return 5, Continue 6. + { + auto* expr = builder.makeBinary( + NeInt32, builder.makeLocalGet(0, Type::i32), builder.makeLocalGet(1, Type::i32)); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_FALSE(parsed.hasUnknown); + ASSERT_EQ(parsed.size(), 1); + EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{Ne, {Index(1)}}})); + } + + // 3. All relational binary operators: + for (auto [wasmOp, abstractOp] : { + std::pair{EqInt32, Eq}, + std::pair{NeInt32, Ne}, + std::pair{LtSInt32, LtS}, + std::pair{LtUInt32, LtU}, + std::pair{LeSInt32, LeS}, + std::pair{LeUInt32, LeU}, + std::pair{GtSInt32, GtS}, + std::pair{GtUInt32, GtU}, + std::pair{GeSInt32, GeS}, + std::pair{GeUInt32, GeU}, + }) { + auto* expr = builder.makeBinary( + wasmOp, builder.makeLocalGet(0, Type::i32), builder.makeConst(Literal(int32_t(5)))); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_FALSE(parsed.hasUnknown); + ASSERT_EQ(parsed.size(), 1); + EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{abstractOp, {Literal(int32_t(5))}}})); + } + + // 64-bit comparison: + { + auto* expr = builder.makeBinary( + LtSInt64, builder.makeLocalGet(0, Type::i64), builder.makeConst(Literal(int64_t(100)))); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_FALSE(parsed.hasUnknown); + ASSERT_EQ(parsed.size(), 1); + EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{LtS, {Literal(int64_t(100))}}})); + } + + // 4. Comparison where right is not a term (e.g. call): unhandled, sets hasUnknown. + // Covers: Unknown 3 (in parseTerm), Return 4, Continue 6. + { + auto* call = builder.makeCall("foo", {}, Type::i32); + auto* expr = builder.makeBinary( + EqInt32, builder.makeLocalGet(0, Type::i32), call); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_TRUE(parsed.empty()); + EXPECT_TRUE(parsed.hasUnknown); + } + + // 5. Comparison where left is not a local.get: unhandled, sets hasUnknown. + // Covers: Unknown 4 (in parseBinaryArguments), Continue 6. + { + auto* expr = builder.makeBinary( + EqInt32, builder.makeConst(Literal(int32_t(1))), builder.makeConst(Literal(int32_t(2)))); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_TRUE(parsed.empty()); + EXPECT_TRUE(parsed.hasUnknown); + } + { + auto* expr = builder.makeBinary( + EqInt32, builder.makeConst(Literal(int32_t(1))), builder.makeLocalGet(0, Type::i32)); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_TRUE(parsed.empty()); + EXPECT_TRUE(parsed.hasUnknown); + } + + // 6. Binary operation that is not a comparison or AND (e.g. Add, Sub, Mul, Or, Xor): sets hasUnknown. + // Covers: Unknown 5 (in Binary when !handled), Continue 6. + for (auto op : {AddInt32, SubInt32, MulInt32, OrInt32, XorInt32}) { + auto* expr = builder.makeBinary( + op, builder.makeLocalGet(0, Type::i32), builder.makeConst(Literal(int32_t(1)))); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_TRUE(parsed.empty()); + EXPECT_TRUE(parsed.hasUnknown); + } +} + +TEST(ConstraintTest, ParseRefEq) { + Module wasm; + Builder builder(wasm); + auto anyref = Type(HeapType::any, Nullable); + + // 1. ref.eq with local.get on both sides. + // Covers: Continue 7, Return 2, Return 5. + { + auto* expr = builder.makeRefEq( + builder.makeLocalGet(0, anyref), builder.makeLocalGet(1, anyref)); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_FALSE(parsed.hasUnknown); + ASSERT_EQ(parsed.size(), 1); + EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{Eq, {Index(1)}}})); + } + + // 2. ref.eq with local.get and ref.null. + // Covers: Continue 7, Return 3, Return 5. + { + auto* expr = builder.makeRefEq( + builder.makeLocalGet(0, anyref), builder.makeRefNull(HeapType::any)); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_FALSE(parsed.hasUnknown); + ASSERT_EQ(parsed.size(), 1); + EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{Eq, {Literal::makeNull(HeapType::any)}}})); + } + + // 3. ref.eq where left is not a local.get (e.g. null on left): sets hasUnknown. + // Covers: Unknown 4 (in parseBinaryArguments via RefEq), Continue 7. + { + auto* expr = builder.makeRefEq( + builder.makeRefNull(HeapType::any), builder.makeLocalGet(0, anyref)); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_TRUE(parsed.empty()); + EXPECT_TRUE(parsed.hasUnknown); + } + + // 4. ref.eq where right is not a term (e.g. call): sets hasUnknown. + // Covers: Unknown 3 (in parseTerm via RefEq), Return 4, Continue 7. + { + auto* call = builder.makeCall("foo", {}, anyref); + auto* expr = builder.makeRefEq( + builder.makeLocalGet(0, anyref), call); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_TRUE(parsed.empty()); + EXPECT_TRUE(parsed.hasUnknown); + } +} + +TEST(ConstraintTest, ParseAnd) { + Module wasm; + Builder builder(wasm); + + // 1. AND over two valid comparisons: both constraints returned. + // Covers: Continue 5. + { + auto* left = builder.makeBinary( + EqInt32, builder.makeLocalGet(0, Type::i32), builder.makeConst(Literal(int32_t(1)))); + auto* right = builder.makeBinary( + EqInt32, builder.makeLocalGet(1, Type::i32), builder.makeConst(Literal(int32_t(2)))); + auto* expr = builder.makeBinary(AndInt32, left, right); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_FALSE(parsed.hasUnknown); + ASSERT_EQ(parsed.size(), 2); + // Work stack processes right then left. + EXPECT_EQ(parsed[0], (LocalConstraint{1, Constraint{Eq, {Literal(int32_t(2))}}})); + EXPECT_EQ(parsed[1], (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(1))}}})); + } + + // 2. AND with known constraint and unknown expression (the case motivating commit 68bca391): + // (i32.and (i32.eq (local.get $0) (i32.const 10)) (call $unknown)) + // Parses the constraint and also sets hasUnknown. + { + auto* left = builder.makeBinary( + EqInt32, builder.makeLocalGet(0, Type::i32), builder.makeConst(Literal(int32_t(10)))); + auto* right = builder.makeCall("unknown", {}, Type::i32); + auto* expr = builder.makeBinary(AndInt32, left, right); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_TRUE(parsed.hasUnknown); + ASSERT_EQ(parsed.size(), 1); + EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(10))}}})); + } + + // 3. Same as above with unknown on left: + { + auto* left = builder.makeCall("unknown", {}, Type::i32); + auto* right = builder.makeBinary( + EqInt32, builder.makeLocalGet(0, Type::i32), builder.makeConst(Literal(int32_t(10)))); + auto* expr = builder.makeBinary(AndInt32, left, right); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_TRUE(parsed.hasUnknown); + ASSERT_EQ(parsed.size(), 1); + EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(10))}}})); + } + + // 4. AND with known constraint and unhandled binary op (e.g. Add): + { + auto* left = builder.makeBinary( + EqInt32, builder.makeLocalGet(0, Type::i32), builder.makeConst(Literal(int32_t(1)))); + auto* right = builder.makeBinary( + AddInt32, builder.makeLocalGet(1, Type::i32), builder.makeConst(Literal(int32_t(2)))); + auto* expr = builder.makeBinary(AndInt32, left, right); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_TRUE(parsed.hasUnknown); + ASSERT_EQ(parsed.size(), 1); + EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(1))}}})); + } + + // 5. AND with known constraint and unhandled unary op (e.g. Clz): + { + auto* left = builder.makeBinary( + EqInt32, builder.makeLocalGet(0, Type::i32), builder.makeConst(Literal(int32_t(1)))); + auto* right = builder.makeUnary(ClzInt32, builder.makeLocalGet(1, Type::i32)); + auto* expr = builder.makeBinary(AndInt32, left, right); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_TRUE(parsed.hasUnknown); + ASSERT_EQ(parsed.size(), 1); + EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(1))}}})); + } + + // 6. AND with known constraint and unhandled eqz argument: + { + auto* left = builder.makeBinary( + EqInt32, builder.makeLocalGet(0, Type::i32), builder.makeConst(Literal(int32_t(1)))); + auto* right = builder.makeUnary(EqZInt32, builder.makeCall("foo", {}, Type::i32)); + auto* expr = builder.makeBinary(AndInt32, left, right); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_TRUE(parsed.hasUnknown); + ASSERT_EQ(parsed.size(), 1); + EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(1))}}})); + } + + // 7. AND with two unknowns: + { + auto* left = builder.makeCall("foo", {}, Type::i32); + auto* right = builder.makeCall("bar", {}, Type::i32); + auto* expr = builder.makeBinary(AndInt32, left, right); + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_TRUE(parsed.hasUnknown); + EXPECT_TRUE(parsed.empty()); + } +} + +TEST(ConstraintTest, ParseOtherUnknowns) { + Module wasm; + Builder builder(wasm); + + // General expression types not handled by parse: + // Covers: Unknown 6 (bottom of loop), Return 6. + for (Expression* expr : { + (Expression*)builder.makeCall("foo", {}, Type::i32), + (Expression*)builder.makeConst(Literal(int32_t(42))), + (Expression*)builder.makeLocalGet(0, Type::i32), + (Expression*)builder.makeNop(), + (Expression*)builder.makeBlock(), + }) { + auto parsed = ParsedAndedConstraints::parse(expr); + EXPECT_TRUE(parsed.empty()); + EXPECT_TRUE(parsed.hasUnknown); + } + + // Contrast parse vs parseCondition for a bare local.get: + // parse treats it as an unhandled expression (unknown), while + // parseCondition recognizes it as x != 0 in a condition context. + auto* get = builder.makeLocalGet(0, Type::i32); + auto parsedAsExpr = ParsedAndedConstraints::parse(get); + EXPECT_TRUE(parsedAsExpr.empty()); + EXPECT_TRUE(parsedAsExpr.hasUnknown); + + auto parsedAsCondition = ParsedAndedConstraints::parseCondition(get); + EXPECT_FALSE(parsedAsCondition.hasUnknown); + ASSERT_EQ(parsedAsCondition.size(), 1); + EXPECT_EQ(parsedAsCondition[0], (LocalConstraint{0, Constraint{Ne, {Literal(int32_t(0))}}})); +} From b20b331ef7ccaca761837894931a3dbd0add299d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 2 Sep 2026 16:41:52 -0700 Subject: [PATCH 40/53] format --- test/gtest/constraint.cpp | 195 +++++++++++++++++++++++--------------- 1 file changed, 119 insertions(+), 76 deletions(-) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 45bb36e26f5..315dc2bf3f0 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -1,7 +1,7 @@ #include -#include "ir/constraint.h" #include "ir/abstract.h" +#include "ir/constraint.h" #include "wasm-builder.h" #include "gtest/gtest.h" @@ -1653,41 +1653,49 @@ TEST(ConstraintTest, ParseUnaryEqZ) { // 1. Single i32.eqz of a local.get: parsed as x == 0. // Covers: parseEqZArgument with LocalGet, Return 1, Continue 2. { - auto* expr = builder.makeUnary(EqZInt32, builder.makeLocalGet(0, Type::i32)); + auto* expr = + builder.makeUnary(EqZInt32, builder.makeLocalGet(0, Type::i32)); auto parsed = ParsedAndedConstraints::parse(expr); EXPECT_FALSE(parsed.hasUnknown); ASSERT_EQ(parsed.size(), 1); - EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(0))}}})); + EXPECT_EQ(parsed[0], + (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(0))}}})); } // 2. Single i64.eqz of a local.get: parsed as x == 0_i64. { - auto* expr = builder.makeUnary(EqZInt64, builder.makeLocalGet(1, Type::i64)); + auto* expr = + builder.makeUnary(EqZInt64, builder.makeLocalGet(1, Type::i64)); auto parsed = ParsedAndedConstraints::parse(expr); EXPECT_FALSE(parsed.hasUnknown); ASSERT_EQ(parsed.size(), 1); - EXPECT_EQ(parsed[0], (LocalConstraint{1, Constraint{Eq, {Literal(int64_t(0))}}})); + EXPECT_EQ(parsed[0], + (LocalConstraint{1, Constraint{Eq, {Literal(int64_t(0))}}})); } // 3. Nested eqz of eqz: parsed as x != 0. // Covers: Continue 1. { - auto* inner = builder.makeUnary(EqZInt32, builder.makeLocalGet(0, Type::i32)); + auto* inner = + builder.makeUnary(EqZInt32, builder.makeLocalGet(0, Type::i32)); auto* expr = builder.makeUnary(EqZInt32, inner); auto parsed = ParsedAndedConstraints::parse(expr); EXPECT_FALSE(parsed.hasUnknown); ASSERT_EQ(parsed.size(), 1); - EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{Ne, {Literal(int32_t(0))}}})); + EXPECT_EQ(parsed[0], + (LocalConstraint{0, Constraint{Ne, {Literal(int32_t(0))}}})); } // 4. Nested eqz of eqz with 64-bit inner: parsed as x != 0_i64. { - auto* inner = builder.makeUnary(EqZInt64, builder.makeLocalGet(1, Type::i64)); + auto* inner = + builder.makeUnary(EqZInt64, builder.makeLocalGet(1, Type::i64)); auto* expr = builder.makeUnary(EqZInt32, inner); auto parsed = ParsedAndedConstraints::parse(expr); EXPECT_FALSE(parsed.hasUnknown); ASSERT_EQ(parsed.size(), 1); - EXPECT_EQ(parsed[0], (LocalConstraint{1, Constraint{Ne, {Literal(int64_t(0))}}})); + EXPECT_EQ(parsed[0], + (LocalConstraint{1, Constraint{Ne, {Literal(int64_t(0))}}})); } // 5. eqz of non-local.get (e.g. call): unhandled, sets hasUnknown. @@ -1713,7 +1721,8 @@ TEST(ConstraintTest, ParseUnaryEqZ) { // 7. Unary operation that is not EqZ: sets hasUnknown. // Covers: Unknown 2, Continue 3. { - auto* expr = builder.makeUnary(ClzInt32, builder.makeLocalGet(0, Type::i32)); + auto* expr = + builder.makeUnary(ClzInt32, builder.makeLocalGet(0, Type::i32)); auto parsed = ParsedAndedConstraints::parse(expr); EXPECT_TRUE(parsed.empty()); EXPECT_TRUE(parsed.hasUnknown); @@ -1732,7 +1741,9 @@ TEST(ConstraintTest, ParseRefIsNull) { auto parsed = ParsedAndedConstraints::parse(expr); EXPECT_FALSE(parsed.hasUnknown); ASSERT_EQ(parsed.size(), 1); - EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{Eq, {Literal::makeNull(HeapType::any)}}})); + EXPECT_EQ( + parsed[0], + (LocalConstraint{0, Constraint{Eq, {Literal::makeNull(HeapType::any)}}})); } // 2. ref.is_null of non-local.get (e.g. call): unhandled, sets hasUnknown. @@ -1761,19 +1772,22 @@ TEST(ConstraintTest, ParseBinary) { // 1. Binary comparison with constant on right: parsed as local constraint. // Covers: Return 3, Return 5, Continue 6. { - auto* expr = builder.makeBinary( - EqInt32, builder.makeLocalGet(0, Type::i32), builder.makeConst(Literal(int32_t(42)))); + auto* expr = builder.makeBinary(EqInt32, + builder.makeLocalGet(0, Type::i32), + builder.makeConst(Literal(int32_t(42)))); auto parsed = ParsedAndedConstraints::parse(expr); EXPECT_FALSE(parsed.hasUnknown); ASSERT_EQ(parsed.size(), 1); - EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(42))}}})); + EXPECT_EQ(parsed[0], + (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(42))}}})); } - // 2. Binary comparison with local.get on right: parsed as local constraint with local term. - // Covers: Return 2, Return 5, Continue 6. + // 2. Binary comparison with local.get on right: parsed as local constraint + // with local term. Covers: Return 2, Return 5, Continue 6. { - auto* expr = builder.makeBinary( - NeInt32, builder.makeLocalGet(0, Type::i32), builder.makeLocalGet(1, Type::i32)); + auto* expr = builder.makeBinary(NeInt32, + builder.makeLocalGet(0, Type::i32), + builder.makeLocalGet(1, Type::i32)); auto parsed = ParsedAndedConstraints::parse(expr); EXPECT_FALSE(parsed.hasUnknown); ASSERT_EQ(parsed.size(), 1); @@ -1793,30 +1807,35 @@ TEST(ConstraintTest, ParseBinary) { std::pair{GeSInt32, GeS}, std::pair{GeUInt32, GeU}, }) { - auto* expr = builder.makeBinary( - wasmOp, builder.makeLocalGet(0, Type::i32), builder.makeConst(Literal(int32_t(5)))); + auto* expr = builder.makeBinary(wasmOp, + builder.makeLocalGet(0, Type::i32), + builder.makeConst(Literal(int32_t(5)))); auto parsed = ParsedAndedConstraints::parse(expr); EXPECT_FALSE(parsed.hasUnknown); ASSERT_EQ(parsed.size(), 1); - EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{abstractOp, {Literal(int32_t(5))}}})); + EXPECT_EQ( + parsed[0], + (LocalConstraint{0, Constraint{abstractOp, {Literal(int32_t(5))}}})); } // 64-bit comparison: { - auto* expr = builder.makeBinary( - LtSInt64, builder.makeLocalGet(0, Type::i64), builder.makeConst(Literal(int64_t(100)))); + auto* expr = builder.makeBinary(LtSInt64, + builder.makeLocalGet(0, Type::i64), + builder.makeConst(Literal(int64_t(100)))); auto parsed = ParsedAndedConstraints::parse(expr); EXPECT_FALSE(parsed.hasUnknown); ASSERT_EQ(parsed.size(), 1); - EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{LtS, {Literal(int64_t(100))}}})); + EXPECT_EQ(parsed[0], + (LocalConstraint{0, Constraint{LtS, {Literal(int64_t(100))}}})); } - // 4. Comparison where right is not a term (e.g. call): unhandled, sets hasUnknown. - // Covers: Unknown 3 (in parseTerm), Return 4, Continue 6. + // 4. Comparison where right is not a term (e.g. call): unhandled, sets + // hasUnknown. Covers: Unknown 3 (in parseTerm), Return 4, Continue 6. { auto* call = builder.makeCall("foo", {}, Type::i32); - auto* expr = builder.makeBinary( - EqInt32, builder.makeLocalGet(0, Type::i32), call); + auto* expr = + builder.makeBinary(EqInt32, builder.makeLocalGet(0, Type::i32), call); auto parsed = ParsedAndedConstraints::parse(expr); EXPECT_TRUE(parsed.empty()); EXPECT_TRUE(parsed.hasUnknown); @@ -1825,25 +1844,29 @@ TEST(ConstraintTest, ParseBinary) { // 5. Comparison where left is not a local.get: unhandled, sets hasUnknown. // Covers: Unknown 4 (in parseBinaryArguments), Continue 6. { - auto* expr = builder.makeBinary( - EqInt32, builder.makeConst(Literal(int32_t(1))), builder.makeConst(Literal(int32_t(2)))); + auto* expr = builder.makeBinary(EqInt32, + builder.makeConst(Literal(int32_t(1))), + builder.makeConst(Literal(int32_t(2)))); auto parsed = ParsedAndedConstraints::parse(expr); EXPECT_TRUE(parsed.empty()); EXPECT_TRUE(parsed.hasUnknown); } { - auto* expr = builder.makeBinary( - EqInt32, builder.makeConst(Literal(int32_t(1))), builder.makeLocalGet(0, Type::i32)); + auto* expr = builder.makeBinary(EqInt32, + builder.makeConst(Literal(int32_t(1))), + builder.makeLocalGet(0, Type::i32)); auto parsed = ParsedAndedConstraints::parse(expr); EXPECT_TRUE(parsed.empty()); EXPECT_TRUE(parsed.hasUnknown); } - // 6. Binary operation that is not a comparison or AND (e.g. Add, Sub, Mul, Or, Xor): sets hasUnknown. - // Covers: Unknown 5 (in Binary when !handled), Continue 6. + // 6. Binary operation that is not a comparison or AND (e.g. Add, Sub, Mul, + // Or, Xor): sets hasUnknown. Covers: Unknown 5 (in Binary when !handled), + // Continue 6. for (auto op : {AddInt32, SubInt32, MulInt32, OrInt32, XorInt32}) { - auto* expr = builder.makeBinary( - op, builder.makeLocalGet(0, Type::i32), builder.makeConst(Literal(int32_t(1)))); + auto* expr = builder.makeBinary(op, + builder.makeLocalGet(0, Type::i32), + builder.makeConst(Literal(int32_t(1)))); auto parsed = ParsedAndedConstraints::parse(expr); EXPECT_TRUE(parsed.empty()); EXPECT_TRUE(parsed.hasUnknown); @@ -1858,8 +1881,8 @@ TEST(ConstraintTest, ParseRefEq) { // 1. ref.eq with local.get on both sides. // Covers: Continue 7, Return 2, Return 5. { - auto* expr = builder.makeRefEq( - builder.makeLocalGet(0, anyref), builder.makeLocalGet(1, anyref)); + auto* expr = builder.makeRefEq(builder.makeLocalGet(0, anyref), + builder.makeLocalGet(1, anyref)); auto parsed = ParsedAndedConstraints::parse(expr); EXPECT_FALSE(parsed.hasUnknown); ASSERT_EQ(parsed.size(), 1); @@ -1869,19 +1892,22 @@ TEST(ConstraintTest, ParseRefEq) { // 2. ref.eq with local.get and ref.null. // Covers: Continue 7, Return 3, Return 5. { - auto* expr = builder.makeRefEq( - builder.makeLocalGet(0, anyref), builder.makeRefNull(HeapType::any)); + auto* expr = builder.makeRefEq(builder.makeLocalGet(0, anyref), + builder.makeRefNull(HeapType::any)); auto parsed = ParsedAndedConstraints::parse(expr); EXPECT_FALSE(parsed.hasUnknown); ASSERT_EQ(parsed.size(), 1); - EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{Eq, {Literal::makeNull(HeapType::any)}}})); + EXPECT_EQ( + parsed[0], + (LocalConstraint{0, Constraint{Eq, {Literal::makeNull(HeapType::any)}}})); } - // 3. ref.eq where left is not a local.get (e.g. null on left): sets hasUnknown. - // Covers: Unknown 4 (in parseBinaryArguments via RefEq), Continue 7. + // 3. ref.eq where left is not a local.get (e.g. null on left): sets + // hasUnknown. Covers: Unknown 4 (in parseBinaryArguments via RefEq), + // Continue 7. { - auto* expr = builder.makeRefEq( - builder.makeRefNull(HeapType::any), builder.makeLocalGet(0, anyref)); + auto* expr = builder.makeRefEq(builder.makeRefNull(HeapType::any), + builder.makeLocalGet(0, anyref)); auto parsed = ParsedAndedConstraints::parse(expr); EXPECT_TRUE(parsed.empty()); EXPECT_TRUE(parsed.hasUnknown); @@ -1891,8 +1917,7 @@ TEST(ConstraintTest, ParseRefEq) { // Covers: Unknown 3 (in parseTerm via RefEq), Return 4, Continue 7. { auto* call = builder.makeCall("foo", {}, anyref); - auto* expr = builder.makeRefEq( - builder.makeLocalGet(0, anyref), call); + auto* expr = builder.makeRefEq(builder.makeLocalGet(0, anyref), call); auto parsed = ParsedAndedConstraints::parse(expr); EXPECT_TRUE(parsed.empty()); EXPECT_TRUE(parsed.hasUnknown); @@ -1906,80 +1931,97 @@ TEST(ConstraintTest, ParseAnd) { // 1. AND over two valid comparisons: both constraints returned. // Covers: Continue 5. { - auto* left = builder.makeBinary( - EqInt32, builder.makeLocalGet(0, Type::i32), builder.makeConst(Literal(int32_t(1)))); - auto* right = builder.makeBinary( - EqInt32, builder.makeLocalGet(1, Type::i32), builder.makeConst(Literal(int32_t(2)))); + auto* left = builder.makeBinary(EqInt32, + builder.makeLocalGet(0, Type::i32), + builder.makeConst(Literal(int32_t(1)))); + auto* right = builder.makeBinary(EqInt32, + builder.makeLocalGet(1, Type::i32), + builder.makeConst(Literal(int32_t(2)))); auto* expr = builder.makeBinary(AndInt32, left, right); auto parsed = ParsedAndedConstraints::parse(expr); EXPECT_FALSE(parsed.hasUnknown); ASSERT_EQ(parsed.size(), 2); // Work stack processes right then left. - EXPECT_EQ(parsed[0], (LocalConstraint{1, Constraint{Eq, {Literal(int32_t(2))}}})); - EXPECT_EQ(parsed[1], (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(1))}}})); + EXPECT_EQ(parsed[0], + (LocalConstraint{1, Constraint{Eq, {Literal(int32_t(2))}}})); + EXPECT_EQ(parsed[1], + (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(1))}}})); } - // 2. AND with known constraint and unknown expression (the case motivating commit 68bca391): - // (i32.and (i32.eq (local.get $0) (i32.const 10)) (call $unknown)) - // Parses the constraint and also sets hasUnknown. + // 2. AND with known constraint and unknown expression (the case motivating + // commit 68bca391): (i32.and (i32.eq (local.get $0) (i32.const 10)) (call + // $unknown)) Parses the constraint and also sets hasUnknown. { - auto* left = builder.makeBinary( - EqInt32, builder.makeLocalGet(0, Type::i32), builder.makeConst(Literal(int32_t(10)))); + auto* left = builder.makeBinary(EqInt32, + builder.makeLocalGet(0, Type::i32), + builder.makeConst(Literal(int32_t(10)))); auto* right = builder.makeCall("unknown", {}, Type::i32); auto* expr = builder.makeBinary(AndInt32, left, right); auto parsed = ParsedAndedConstraints::parse(expr); EXPECT_TRUE(parsed.hasUnknown); ASSERT_EQ(parsed.size(), 1); - EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(10))}}})); + EXPECT_EQ(parsed[0], + (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(10))}}})); } // 3. Same as above with unknown on left: { auto* left = builder.makeCall("unknown", {}, Type::i32); - auto* right = builder.makeBinary( - EqInt32, builder.makeLocalGet(0, Type::i32), builder.makeConst(Literal(int32_t(10)))); + auto* right = builder.makeBinary(EqInt32, + builder.makeLocalGet(0, Type::i32), + builder.makeConst(Literal(int32_t(10)))); auto* expr = builder.makeBinary(AndInt32, left, right); auto parsed = ParsedAndedConstraints::parse(expr); EXPECT_TRUE(parsed.hasUnknown); ASSERT_EQ(parsed.size(), 1); - EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(10))}}})); + EXPECT_EQ(parsed[0], + (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(10))}}})); } // 4. AND with known constraint and unhandled binary op (e.g. Add): { - auto* left = builder.makeBinary( - EqInt32, builder.makeLocalGet(0, Type::i32), builder.makeConst(Literal(int32_t(1)))); - auto* right = builder.makeBinary( - AddInt32, builder.makeLocalGet(1, Type::i32), builder.makeConst(Literal(int32_t(2)))); + auto* left = builder.makeBinary(EqInt32, + builder.makeLocalGet(0, Type::i32), + builder.makeConst(Literal(int32_t(1)))); + auto* right = builder.makeBinary(AddInt32, + builder.makeLocalGet(1, Type::i32), + builder.makeConst(Literal(int32_t(2)))); auto* expr = builder.makeBinary(AndInt32, left, right); auto parsed = ParsedAndedConstraints::parse(expr); EXPECT_TRUE(parsed.hasUnknown); ASSERT_EQ(parsed.size(), 1); - EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(1))}}})); + EXPECT_EQ(parsed[0], + (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(1))}}})); } // 5. AND with known constraint and unhandled unary op (e.g. Clz): { - auto* left = builder.makeBinary( - EqInt32, builder.makeLocalGet(0, Type::i32), builder.makeConst(Literal(int32_t(1)))); - auto* right = builder.makeUnary(ClzInt32, builder.makeLocalGet(1, Type::i32)); + auto* left = builder.makeBinary(EqInt32, + builder.makeLocalGet(0, Type::i32), + builder.makeConst(Literal(int32_t(1)))); + auto* right = + builder.makeUnary(ClzInt32, builder.makeLocalGet(1, Type::i32)); auto* expr = builder.makeBinary(AndInt32, left, right); auto parsed = ParsedAndedConstraints::parse(expr); EXPECT_TRUE(parsed.hasUnknown); ASSERT_EQ(parsed.size(), 1); - EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(1))}}})); + EXPECT_EQ(parsed[0], + (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(1))}}})); } // 6. AND with known constraint and unhandled eqz argument: { - auto* left = builder.makeBinary( - EqInt32, builder.makeLocalGet(0, Type::i32), builder.makeConst(Literal(int32_t(1)))); - auto* right = builder.makeUnary(EqZInt32, builder.makeCall("foo", {}, Type::i32)); + auto* left = builder.makeBinary(EqInt32, + builder.makeLocalGet(0, Type::i32), + builder.makeConst(Literal(int32_t(1)))); + auto* right = + builder.makeUnary(EqZInt32, builder.makeCall("foo", {}, Type::i32)); auto* expr = builder.makeBinary(AndInt32, left, right); auto parsed = ParsedAndedConstraints::parse(expr); EXPECT_TRUE(parsed.hasUnknown); ASSERT_EQ(parsed.size(), 1); - EXPECT_EQ(parsed[0], (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(1))}}})); + EXPECT_EQ(parsed[0], + (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(1))}}})); } // 7. AND with two unknowns: @@ -2022,5 +2064,6 @@ TEST(ConstraintTest, ParseOtherUnknowns) { auto parsedAsCondition = ParsedAndedConstraints::parseCondition(get); EXPECT_FALSE(parsedAsCondition.hasUnknown); ASSERT_EQ(parsedAsCondition.size(), 1); - EXPECT_EQ(parsedAsCondition[0], (LocalConstraint{0, Constraint{Ne, {Literal(int32_t(0))}}})); + EXPECT_EQ(parsedAsCondition[0], + (LocalConstraint{0, Constraint{Ne, {Literal(int32_t(0))}}})); } From 44cd3273f238bd3404c91d37c666833bd408640b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 3 Sep 2026 08:46:24 -0700 Subject: [PATCH 41/53] work --- test/gtest/constraint.cpp | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 315dc2bf3f0..0c30d3c0ede 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -1651,7 +1651,6 @@ TEST(ConstraintTest, ParseUnaryEqZ) { Builder builder(wasm); // 1. Single i32.eqz of a local.get: parsed as x == 0. - // Covers: parseEqZArgument with LocalGet, Return 1, Continue 2. { auto* expr = builder.makeUnary(EqZInt32, builder.makeLocalGet(0, Type::i32)); @@ -1674,7 +1673,6 @@ TEST(ConstraintTest, ParseUnaryEqZ) { } // 3. Nested eqz of eqz: parsed as x != 0. - // Covers: Continue 1. { auto* inner = builder.makeUnary(EqZInt32, builder.makeLocalGet(0, Type::i32)); @@ -1699,7 +1697,6 @@ TEST(ConstraintTest, ParseUnaryEqZ) { } // 5. eqz of non-local.get (e.g. call): unhandled, sets hasUnknown. - // Covers: Unknown 1 (in parseEqZArgument), Continue 2. { auto* call = builder.makeCall("foo", {}, Type::i32); auto* expr = builder.makeUnary(EqZInt32, call); @@ -1719,7 +1716,6 @@ TEST(ConstraintTest, ParseUnaryEqZ) { } // 7. Unary operation that is not EqZ: sets hasUnknown. - // Covers: Unknown 2, Continue 3. { auto* expr = builder.makeUnary(ClzInt32, builder.makeLocalGet(0, Type::i32)); @@ -1735,7 +1731,6 @@ TEST(ConstraintTest, ParseRefIsNull) { auto anyref = Type(HeapType::any, Nullable); // 1. ref.is_null of local.get: parsed as x == null. - // Covers: Continue 4, Return 1. { auto* expr = builder.makeRefIsNull(builder.makeLocalGet(0, anyref)); auto parsed = ParsedAndedConstraints::parse(expr); @@ -1747,7 +1742,6 @@ TEST(ConstraintTest, ParseRefIsNull) { } // 2. ref.is_null of non-local.get (e.g. call): unhandled, sets hasUnknown. - // Covers: Unknown 1 (in parseEqZArgument via RefIsNull), Continue 4. { auto* call = builder.makeCall("foo", {}, anyref); auto* expr = builder.makeRefIsNull(call); @@ -1770,7 +1764,6 @@ TEST(ConstraintTest, ParseBinary) { Builder builder(wasm); // 1. Binary comparison with constant on right: parsed as local constraint. - // Covers: Return 3, Return 5, Continue 6. { auto* expr = builder.makeBinary(EqInt32, builder.makeLocalGet(0, Type::i32), @@ -1783,7 +1776,6 @@ TEST(ConstraintTest, ParseBinary) { } // 2. Binary comparison with local.get on right: parsed as local constraint - // with local term. Covers: Return 2, Return 5, Continue 6. { auto* expr = builder.makeBinary(NeInt32, builder.makeLocalGet(0, Type::i32), @@ -1804,7 +1796,7 @@ TEST(ConstraintTest, ParseBinary) { std::pair{LeUInt32, LeU}, std::pair{GtSInt32, GtS}, std::pair{GtUInt32, GtU}, - std::pair{GeSInt32, GeS}, + std::pair{GeSInt32, GeS}, std::pair{GeUInt32, GeU}, }) { auto* expr = builder.makeBinary(wasmOp, @@ -1831,7 +1823,7 @@ TEST(ConstraintTest, ParseBinary) { } // 4. Comparison where right is not a term (e.g. call): unhandled, sets - // hasUnknown. Covers: Unknown 3 (in parseTerm), Return 4, Continue 6. + // hasUnknown. { auto* call = builder.makeCall("foo", {}, Type::i32); auto* expr = @@ -1842,7 +1834,6 @@ TEST(ConstraintTest, ParseBinary) { } // 5. Comparison where left is not a local.get: unhandled, sets hasUnknown. - // Covers: Unknown 4 (in parseBinaryArguments), Continue 6. { auto* expr = builder.makeBinary(EqInt32, builder.makeConst(Literal(int32_t(1))), @@ -1851,6 +1842,7 @@ TEST(ConstraintTest, ParseBinary) { EXPECT_TRUE(parsed.empty()); EXPECT_TRUE(parsed.hasUnknown); } + // Ditto, local on right. { auto* expr = builder.makeBinary(EqInt32, builder.makeConst(Literal(int32_t(1))), @@ -1861,8 +1853,7 @@ TEST(ConstraintTest, ParseBinary) { } // 6. Binary operation that is not a comparison or AND (e.g. Add, Sub, Mul, - // Or, Xor): sets hasUnknown. Covers: Unknown 5 (in Binary when !handled), - // Continue 6. + // Or, Xor): sets hasUnknown. for (auto op : {AddInt32, SubInt32, MulInt32, OrInt32, XorInt32}) { auto* expr = builder.makeBinary(op, builder.makeLocalGet(0, Type::i32), @@ -1879,7 +1870,6 @@ TEST(ConstraintTest, ParseRefEq) { auto anyref = Type(HeapType::any, Nullable); // 1. ref.eq with local.get on both sides. - // Covers: Continue 7, Return 2, Return 5. { auto* expr = builder.makeRefEq(builder.makeLocalGet(0, anyref), builder.makeLocalGet(1, anyref)); @@ -1890,7 +1880,6 @@ TEST(ConstraintTest, ParseRefEq) { } // 2. ref.eq with local.get and ref.null. - // Covers: Continue 7, Return 3, Return 5. { auto* expr = builder.makeRefEq(builder.makeLocalGet(0, anyref), builder.makeRefNull(HeapType::any)); @@ -1903,8 +1892,7 @@ TEST(ConstraintTest, ParseRefEq) { } // 3. ref.eq where left is not a local.get (e.g. null on left): sets - // hasUnknown. Covers: Unknown 4 (in parseBinaryArguments via RefEq), - // Continue 7. + // hasUnknown. { auto* expr = builder.makeRefEq(builder.makeRefNull(HeapType::any), builder.makeLocalGet(0, anyref)); @@ -1914,7 +1902,6 @@ TEST(ConstraintTest, ParseRefEq) { } // 4. ref.eq where right is not a term (e.g. call): sets hasUnknown. - // Covers: Unknown 3 (in parseTerm via RefEq), Return 4, Continue 7. { auto* call = builder.makeCall("foo", {}, anyref); auto* expr = builder.makeRefEq(builder.makeLocalGet(0, anyref), call); @@ -1929,7 +1916,6 @@ TEST(ConstraintTest, ParseAnd) { Builder builder(wasm); // 1. AND over two valid comparisons: both constraints returned. - // Covers: Continue 5. { auto* left = builder.makeBinary(EqInt32, builder.makeLocalGet(0, Type::i32), @@ -2040,7 +2026,6 @@ TEST(ConstraintTest, ParseOtherUnknowns) { Builder builder(wasm); // General expression types not handled by parse: - // Covers: Unknown 6 (bottom of loop), Return 6. for (Expression* expr : { (Expression*)builder.makeCall("foo", {}, Type::i32), (Expression*)builder.makeConst(Literal(int32_t(42))), From 657bfb72cb28e4675b09e73438958b9e6bd69335 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 3 Sep 2026 08:52:31 -0700 Subject: [PATCH 42/53] work --- test/gtest/constraint.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 0c30d3c0ede..de378d44c30 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -1934,9 +1934,8 @@ TEST(ConstraintTest, ParseAnd) { (LocalConstraint{0, Constraint{Eq, {Literal(int32_t(1))}}})); } - // 2. AND with known constraint and unknown expression (the case motivating - // commit 68bca391): (i32.and (i32.eq (local.get $0) (i32.const 10)) (call - // $unknown)) Parses the constraint and also sets hasUnknown. + // 2. AND with known constraint and unknown expression (call): Parses the + // constraint and also sets hasUnknown. { auto* left = builder.makeBinary(EqInt32, builder.makeLocalGet(0, Type::i32), From e4723340305149f1369c1db8f44623d4926b08a5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 3 Sep 2026 09:19:26 -0700 Subject: [PATCH 43/53] work --- src/ir/constraint.cpp | 172 +++++++++++++++++++----------------------- src/ir/constraint.h | 25 +++--- 2 files changed, 91 insertions(+), 106 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index a2835d2abcf..60690c17cb9 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -733,6 +733,80 @@ bool AndedConstraintSet::approximateOr(const AndedConstraintSet& other) { return changed; } +std::optional LocalConstraint::parse(Expression* curr) { + auto parseEqZArgument = + [&](Expression* value) -> std::optional { + if (auto* get = value->dynCast()) { + // Canonicalize EqZ to Eq of 0. + auto value = Literal::makeZero(get->type); + return LocalConstraint{get->index, Constraint{Abstract::Eq, {value}}}; + } + // TODO: Recursively parse and reverse a constraint + return {}; + }; + + if (auto* unary = curr->dynCast()) { + if (Abstract::getUnary(unary->value->type, Abstract::EqZ) == unary->op) { + return parseEqZArgument(unary->value); + } + return {}; + } + + if (auto* refIsNull = curr->dynCast()) { + return parseEqZArgument(refIsNull->value); + } + + // Parse a get or a constant. + auto parseTerm = [&](Expression* expr) -> std::optional { + if (auto* get = expr->dynCast()) { + return Term{get->index}; + } + if (Properties::isSingleConstantExpression(expr)) { + return Term{Properties::getLiteral(expr)}; + } + return {}; + }; + + auto parseBinaryArguments = + [&](Abstract::Op op, + Expression* left, + Expression* right) -> std::optional { + // The left must be a get. + if (auto* get = left->dynCast()) { + // The right can be any term. + if (auto value = parseTerm(right)) { + return LocalConstraint{get->index, Constraint{op, *value}}; + } + } + return {}; + }; + + if (auto* binary = curr->dynCast()) { + // The operation must be one we recognize. + for (auto op : {Abstract::Eq, + Abstract::Ne, + Abstract::LtS, + Abstract::LtU, + Abstract::LeS, + Abstract::LeU, + Abstract::GtS, + Abstract::GtU, + Abstract::GeS, + Abstract::GeU}) { + if (Abstract::getBinary(binary->left->type, op) == binary->op) { + return parseBinaryArguments(op, binary->left, binary->right); + } + } + return {}; + } + + if (auto* refEq = curr->dynCast()) { + return parseBinaryArguments(Abstract::Eq, refEq->left, refEq->right); + } + + return {}; +} + ParsedAndedConstraints ParsedAndedConstraints::parse(Expression* curr) { // The final return value. ParsedAndedConstraints ret; @@ -745,74 +819,12 @@ ParsedAndedConstraints ParsedAndedConstraints::parse(Expression* curr) { auto* curr = work.back(); work.pop_back(); - auto parseEqZArgument = [&](Expression* value) { - if (auto* get = value->dynCast()) { - // Canonicalize EqZ to Eq of 0. - auto value = Literal::makeZero(get->type); - ret.push_back( - LocalConstraint{get->index, Constraint{Abstract::Eq, {value}}}); - return; - } - - // We did not recognize this, so the output contains unknown things. - // TODO: Recursively parse and negate things other than local.get - ret.hasUnknown = true; - }; - - if (auto* unary = curr->dynCast()) { - if (Abstract::getUnary(unary->value->type, Abstract::EqZ) == unary->op) { - // EqZ of EqZ means a check that the value is *not* zero. - if (auto* nested = unary->value->dynCast()) { - if (Abstract::getUnary(nested->value->type, Abstract::EqZ) == - nested->op) { - if (auto* get = nested->value->dynCast()) { - auto value = Literal::makeZero(get->type); - ret.push_back( - LocalConstraint{get->index, Constraint{Abstract::Ne, {value}}}); - continue; - } - } - } - - parseEqZArgument(unary->value); - continue; - } - - ret.hasUnknown = true; - continue; - } - - if (auto* refIsNull = curr->dynCast()) { - parseEqZArgument(refIsNull->value); + auto parsed = LocalConstraint::parse(curr); + if (parsed) { + ret.push_back(*parsed); continue; } - // Parse a get or a constant. - auto parseTerm = [&](Expression* expr) -> std::optional { - if (auto* get = expr->dynCast()) { - return Term{get->index}; - } - if (Properties::isSingleConstantExpression(expr)) { - return Term{Properties::getLiteral(expr)}; - } - ret.hasUnknown = true; - return {}; - }; - - auto parseBinaryArguments = - [&](Abstract::Op op, Expression* left, Expression* right) { - // The left must be a get. - if (auto* get = left->dynCast()) { - // The right can be any term. - if (auto value = parseTerm(right)) { - ret.push_back(LocalConstraint{get->index, Constraint{op, *value}}); - return; - } - } - - ret.hasUnknown = true; - }; - if (auto* binary = curr->dynCast()) { // An AND can be recursively processed: both sides must be true. if (Abstract::getBinary(binary->left->type, Abstract::And) == @@ -822,36 +834,6 @@ ParsedAndedConstraints ParsedAndedConstraints::parse(Expression* curr) { continue; } // TODO: support OR - - // Otherwise, the operation must be one we can express as a constraint. - bool handled = false; - for (auto op : {Abstract::Eq, - Abstract::Ne, - Abstract::LtS, - Abstract::LtU, - Abstract::LeS, - Abstract::LeU, - Abstract::GtS, - Abstract::GtU, - Abstract::GeS, - Abstract::GeU}) { - if (Abstract::getBinary(binary->left->type, op) == binary->op) { - parseBinaryArguments(op, binary->left, binary->right); - handled = true; - break; - } - } - - if (!handled) { - ret.hasUnknown = true; - } - - continue; - } - - if (auto* refEq = curr->dynCast()) { - parseBinaryArguments(Abstract::Eq, refEq->left, refEq->right); - continue; } // We failed to parse this. diff --git a/src/ir/constraint.h b/src/ir/constraint.h index c63021bcb8f..4a72864ad12 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -238,22 +238,25 @@ struct LocalConstraint { bool operator==(const LocalConstraint&) const = default; + // Try to parse BinaryenIR into a local to which a constraint is applied. For + // example + // + // (i32.eq (local.get $r) (i32.const 10)) + // + // parses into + // + // LocalConstraint($r, { x == 10 }) + // + static std::optional parse(Expression* curr); + // Reverse the constraint. The constraint's term must, of course, be another // local. void flip(); }; -// A utility to parse BinaryenIR into local and constraints on them. For -// example: -// -// (i32.eq (local.get $r) (i32.const 10)) -// -// parses into -// -// [ LocalConstraint($r, { x == 10 }) ] -// -// If the expression is an AND over several things, several constraints may be -// returned: +// A utility to parse BinaryenIR into local and constraints on them. This is +// similar to LocalConstraint::parse, but that parses a single constraint, while +// this can handle a list of ANDed ones: // // (i32.and (..A..) (..B..)) // From 1e89d01b6025e6556d549c78410890afa2f7a6ac Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 3 Sep 2026 09:20:47 -0700 Subject: [PATCH 44/53] work --- src/ir/constraint.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 60690c17cb9..6c88f7b058d 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -747,8 +747,21 @@ std::optional LocalConstraint::parse(Expression* curr) { if (auto* unary = curr->dynCast()) { if (Abstract::getUnary(unary->value->type, Abstract::EqZ) == unary->op) { + // EqZ of EqZ means a check that the value is *not* zero. + if (auto* nested = unary->value->dynCast()) { + if (Abstract::getUnary(nested->value->type, Abstract::EqZ) == + nested->op) { + if (auto* get = nested->value->dynCast()) { + auto value = Literal::makeZero(get->type); + return + LocalConstraint{get->index, Constraint{Abstract::Ne, {value}}}; + } + } + } + return parseEqZArgument(unary->value); } + return {}; } From 70e08652889ac311d06028e860507a46dfa988fd Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 3 Sep 2026 09:20:58 -0700 Subject: [PATCH 45/53] work --- src/ir/constraint.cpp | 4 ++-- test/gtest/constraint.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 6c88f7b058d..f282a56d423 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -753,8 +753,8 @@ std::optional LocalConstraint::parse(Expression* curr) { nested->op) { if (auto* get = nested->value->dynCast()) { auto value = Literal::makeZero(get->type); - return - LocalConstraint{get->index, Constraint{Abstract::Ne, {value}}}; + return LocalConstraint{get->index, + Constraint{Abstract::Ne, {value}}}; } } } diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index de378d44c30..d02aae4c329 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -1796,7 +1796,7 @@ TEST(ConstraintTest, ParseBinary) { std::pair{LeUInt32, LeU}, std::pair{GtSInt32, GtS}, std::pair{GtUInt32, GtU}, - std::pair{GeSInt32, GeS}, + std::pair{GeSInt32, GeS}, std::pair{GeUInt32, GeU}, }) { auto* expr = builder.makeBinary(wasmOp, From d00e0dacd0837601c3917bd94fc9fd2464ed62fe Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 3 Sep 2026 09:23:09 -0700 Subject: [PATCH 46/53] work --- src/passes/ConstraintAnalysis.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 6d6451f9861..7a87d6ff049 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -444,12 +444,14 @@ struct ConstraintAnalysis void optimizeExpression(Expression** currp, const BasicBlockConstraintMap& constraints) { auto* curr = *currp; - auto parsed = ParsedAndedConstraints::parse(curr); - // TODO: optimize cases of more than one, and with unknowns - if (parsed.size() != 1 || parsed.hasUnknown) { + // Note that we don't need to try to parse a series of constraints with + // ParsedAndedConstraints: if there is such a tree, we will simply optimize + // it as we walk it. + auto parsed = LocalConstraint::parse(curr); + if (!parsed) { return; } - if (!checkRelevancy(parsed[0])) { + 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 @@ -459,7 +461,7 @@ struct ConstraintAnalysis return; } - auto result = constraints.proves(parsed[0]); + auto result = constraints.proves(*parsed); if (result == Unknown) { // If we parsed something using two locals, like x != y, we can also look // for the flipped condition among y's constraints TODO From b6a4a76ed21972667b6a45f3a76730353f1f1c0a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 3 Sep 2026 10:43:14 -0700 Subject: [PATCH 47/53] work --- src/ir/constraint.cpp | 2 +- src/ir/constraint.h | 17 ++++++++--------- src/passes/ConstraintAnalysis.cpp | 4 ++-- test/lit/passes/constraint-analysis.wast | 2 ++ 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index f282a56d423..8519462f65b 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -825,7 +825,7 @@ ParsedAndedConstraints ParsedAndedConstraints::parse(Expression* curr) { ParsedAndedConstraints ret; // Starting from |curr|, parse and recurse into sub-trees: when we see an AND, - // for example, we can push both children as further work. + // we push both children as further work. SmallVector work; work.push_back(curr); while (!work.empty()) { diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 4a72864ad12..e3e2005d3ec 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -254,25 +254,24 @@ struct LocalConstraint { void flip(); }; -// A utility to parse BinaryenIR into local and constraints on them. This is +// A utility to parse BinaryenIR into locals and constraints on them. This is // similar to LocalConstraint::parse, but that parses a single constraint, while // this can handle a list of ANDed ones: // // (i32.and (..A..) (..B..)) // -// parses into +// parses into [ A, B ]. // -// [ A, B ] +// We also set a field |hasUnknown| if we saw things we could not parse. E.g. // +// (i32.and (call $unknown) (i32.eqz (local.get $x))) +// +// This parses into [ $x == 0 ] and sets hasUnknown=true. Even if there are +// unknown things, we do know that definitely $x == 0 at least, which is useful +// in some cases. struct ParsedAndedConstraints : public SmallVector { using SmallVector::SmallVector; - // Whether, in addition to the expressions we parsed into constraints, there - // were also other unknown things. For example, - // - // (i32.and (i32.eq (local.get $r) (i32.const 10)) (call $unknown)) - // - // Would parse into $r == 10 and also set hasUnknown. bool hasUnknown = false; static ParsedAndedConstraints parse(Expression* curr); diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 7a87d6ff049..7b1027488bf 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -445,8 +445,8 @@ struct ConstraintAnalysis const BasicBlockConstraintMap& constraints) { auto* curr = *currp; // Note that we don't need to try to parse a series of constraints with - // ParsedAndedConstraints: if there is such a tree, we will simply optimize - // it as we walk it. + // ParsedAndedConstraints: if there is a tree of ANDed things, we will + // simply optimize it as we walk it, each time handling one. auto parsed = LocalConstraint::parse(curr); if (!parsed) { return; diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index e768dbcb4fc..9cf0ce874b0 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -5852,4 +5852,6 @@ ) ) ) + +br_if and ) From 56dd5755f7b292c6c0bef26f2b07b9aed78e63b6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 3 Sep 2026 10:47:26 -0700 Subject: [PATCH 48/53] work --- test/lit/passes/constraint-analysis.wast | 98 +++++++++++++++++++----- 1 file changed, 79 insertions(+), 19 deletions(-) diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index 9cf0ce874b0..c180cec1d6d 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -5817,41 +5817,101 @@ ) ) - ;; CHECK: (func $optimize-unknown (type $1) - ;; CHECK-NEXT: (local $x i32) + ;; CHECK: (func $br_if_and (type $0) (param $param i32) + ;; CHECK-NEXT: (block $block + ;; CHECK-NEXT: (br_if $block + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (local.get $param) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (local.get $param) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.and ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (local.get $param) + ;; CHECK-NEXT: (i32.const 30) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $optimize-unknown (type $1) - ;; OPTIN-NEXT: (local $x i32) + ;; OPTIN: (func $br_if_and (type $0) (param $param i32) + ;; OPTIN-NEXT: (block $block + ;; OPTIN-NEXT: (br_if $block + ;; OPTIN-NEXT: (i32.and + ;; OPTIN-NEXT: (i32.ne + ;; OPTIN-NEXT: (local.get $param) + ;; OPTIN-NEXT: (i32.const 10) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (i32.ne + ;; OPTIN-NEXT: (local.get $param) + ;; OPTIN-NEXT: (i32.const 20) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (return) + ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (drop ;; OPTIN-NEXT: (i32.and - ;; OPTIN-NEXT: (call $import) ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.ne + ;; OPTIN-NEXT: (local.get $param) + ;; OPTIN-NEXT: (i32.const 30) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) - (func $optimize-unknown - (local $x i32) - ;; We can optimize the i32.eq (x == 0 as the default value), but we should - ;; not do anything to the i32.and (it might appear like something we can - ;; optimize, as one arm is parseable, but the other is not; in any event, we - ;; process the i32.eq first, so we don't even get the chance to mis-optimize - ;; here, but this test at least verifies the i32.and is not touched). + (func $br_if_and (param $param i32) + ;; An AND in a br_if condition. + (block $block + (br_if $block + (i32.and + (i32.ne + (local.get $param) + (i32.const 10) + ) + (i32.ne + (local.get $param) + (i32.const 20) + ) + ) + ) + (return) + ) + ;; If we get here, param != 10 && param != 20. We optimize each arm of the + ;; AND here. (drop (i32.and - (i32.eq - (local.get $x) - (i32.const 0) + (i32.ne + (local.get $param) + (i32.const 10) ) - (call $import) + (i32.ne + (local.get $param) + (i32.const 20) + ) + ) + ) + ;; This one we don't know. + (drop + (i32.ne + (local.get $param) + (i32.const 30) ) ) ) - -br_if and ) From 82bf613892ed87838ef2d54a03781fe1aa8dcd18 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 4 Sep 2026 08:08:34 -0700 Subject: [PATCH 49/53] work --- src/ir/constraint.cpp | 54 ++++++++++++++++++++++++++++ src/ir/constraint.h | 5 +++ src/passes/ConstraintAnalysis.cpp | 60 ++----------------------------- 3 files changed, 62 insertions(+), 57 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 8519462f65b..7cc20d7e636 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -868,6 +868,60 @@ ParsedAndedConstraints::parseCondition(Expression* curr) { return parse(curr); } +void ParsedAndedConstraints::negate() { + if (empty()) { + return; + } + + if (hasUnknown) { + // This includes things we don't know about, and don't know how to negate. + clear(); + return; + } + + // The input is a list of constraints all applying at once, A & B & C. The + // negation is !A | !B | !C, but we cannot express a general OR like that, + // except in the simple case where they all talk about the same local: then + // we can at least approximateOr them all into one constraint. + auto& self = *this; + for (Index i = 1; i < size(); i++) { + if (self[i].local != self[0].local) { + // They refer to different locals. Give up. + clear(); + return; + } + } + + // Negate them before the OR. + for (auto& pair : self) { + pair.constraint = pair.constraint.negate(); + } + + if (size() == 1) { + // The simple case of 1 doesn't need any more work. + return; + } + + // Do the OR. + AndedConstraintSet anded; + anded.set(self[0].constraint); + for (Index i = 1; i < size(); i++) { + anded.approximateOr({self[i].constraint}); + if (anded.provesNothing()) { + // We have nothing useful here. + clear(); + return; + } + } + + // Return only the OR'ed result. + auto local = self[0].local; + clear(); + for (auto& c : anded) { + emplace_back(local, c); + } +} + void LocalConstraint::flip() { auto other = std::get(constraint.term); constraint.term = Term{local}; diff --git a/src/ir/constraint.h b/src/ir/constraint.h index e3e2005d3ec..c959e3b64e5 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -279,6 +279,11 @@ struct ParsedAndedConstraints : public SmallVector { // Parse in a condition context, i.e., where (local.get $x) is the same as // $x != 0 (e.g., in an if condition, or a br_on ref). static ParsedAndedConstraints parseCondition(Expression* curr); + + // Negate the entire list of constraints. If we fail to generate something + // that can be represented as a list of ANDed constraints, the list will be + // empty (i.e., we can prove nothing). + void negate(); }; // A map of locals and their constraints, representing the state at a basic diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 7b1027488bf..f98591ca8e6 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -512,7 +512,7 @@ struct ConstraintAnalysis auto parsed = ParsedAndedConstraints::parseCondition(iff->condition); if (!physicalSuccessor) { // We are in the ifFalse, so negate the condition. - negate(parsed); + parsed.negate(); } return parsed; } @@ -526,7 +526,7 @@ struct ConstraintAnalysis auto parsed = ParsedAndedConstraints::parseCondition(br->condition); if (physicalSuccessor) { // The branch was not taken, so negate the condition. - negate(parsed); + parsed.negate(); } return parsed; } @@ -545,65 +545,11 @@ struct ConstraintAnalysis auto parsed = ParsedAndedConstraints::parseCondition(brOn->ref); // Negate depending on the op and (similar to Break) the successor. if ((brOn->op == BrOnNull) ^ physicalSuccessor) { - negate(parsed); + parsed.negate(); } return parsed; } - // Given a list of parsed constraints on locals, negate them. - void negate(ParsedAndedConstraints& parsed) { - if (parsed.empty()) { - return; - } - - if (parsed.hasUnknown) { - // This includes things we don't know about, and don't know how to negate. - parsed.clear(); - return; - } - - // The input is a list of constraints all applying at once, A & B & C. The - // negation is !A | !B | !C, but we cannot express a general OR like that, - // except in the simple case where they all talk about the same local: then - // we can at least approximateOr them all into one constraint. - for (Index i = 1; i < parsed.size(); i++) { - if (parsed[i].local != parsed[0].local) { - // They refer to different locals. Give up. - parsed.clear(); - return; - } - } - - // Negate them before the OR. - for (auto& pair : parsed) { - pair.constraint = pair.constraint.negate(); - } - - if (parsed.size() == 1) { - // The simple case of 1 doesn't need any more work. - return; - } - - // Do the OR. - AndedConstraintSet anded; - anded.set(parsed[0].constraint); - for (Index i = 1; i < parsed.size(); i++) { - anded.approximateOr({parsed[i].constraint}); - if (anded.provesNothing()) { - // We have nothing useful here. - parsed.clear(); - return; - } - } - - // Return only the OR'ed result. - auto local = parsed[0].local; - parsed.clear(); - for (auto& c : anded) { - parsed.emplace_back(local, c); - } - } - // When applying constraints for a binary operation like x = y + 1, we may // end up with lots of nonlinear work, in a loop: x may go from 0 to 1, then // branch back to the top and merge, making it in the range [0, 1], then get From 5062fef76f27be2b6f43508b8a080c6e05a6ae9e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 4 Sep 2026 09:11:56 -0700 Subject: [PATCH 50/53] fix apple compiler error --- src/ir/constraint.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index c959e3b64e5..2e15bec5ff0 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -236,6 +236,10 @@ struct LocalConstraint { Index local; Constraint constraint; + LocalConstraint() = default; + LocalConstraint(Index local, Constraint constraint) + : local(local), constraint(std::move(constraint)) {} + bool operator==(const LocalConstraint&) const = default; // Try to parse BinaryenIR into a local to which a constraint is applied. For From 8a5e5d576aa647dcf217dbaaa5870607cd31f50b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 4 Sep 2026 11:18:57 -0700 Subject: [PATCH 51/53] filter relevant locals in branches --- src/passes/ConstraintAnalysis.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index f98591ca8e6..9b6f96b5bab 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -374,7 +374,7 @@ struct ConstraintAnalysis // Find the constraints sent to this specific successor, if there is a // branch, and use them. if (auto branch = getBranchConstraints(block, out); - !branch.empty() && checkRelevancy(branch)) { + filterRelevant(branch), !branch.empty()) { auto sentConstraints = constraints; applyBranchConstraints(branch, sentConstraints); #if CONSTRAINT_DEBUG @@ -669,11 +669,14 @@ struct ConstraintAnalysis return true; } - bool checkRelevancy(const ParsedAndedConstraints& parsed) { - return std::any_of( - parsed.begin(), parsed.end(), [&](const LocalConstraint& pair) { - return checkRelevancy(pair); - }); + // Filters out constraints on irrelevant locals. + void filterRelevant(ParsedAndedConstraints& parsed) { + parsed.erase( + std::remove_if( + parsed.begin(), + parsed.end(), + [&](const LocalConstraint& pair) { return !checkRelevancy(pair); }), + parsed.end()); } // Apply branch constraints to the current set of constraints. From bc12f04195a0d69d89b6b744bb6124edb513d9be Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 4 Sep 2026 11:50:31 -0700 Subject: [PATCH 52/53] format --- src/passes/ConstraintAnalysis.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 9b6f96b5bab..9e275665e9e 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -671,12 +671,12 @@ struct ConstraintAnalysis // Filters out constraints on irrelevant locals. void filterRelevant(ParsedAndedConstraints& parsed) { - parsed.erase( - std::remove_if( - parsed.begin(), - parsed.end(), - [&](const LocalConstraint& pair) { return !checkRelevancy(pair); }), - parsed.end()); + parsed.erase(std::remove_if(parsed.begin(), + parsed.end(), + [&](const LocalConstraint& pair) { + return !checkRelevancy(pair); + }), + parsed.end()); } // Apply branch constraints to the current set of constraints. From c5a683618bd3031bc2d2418c01b5fa3933a779b9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 4 Sep 2026 16:32:43 -0700 Subject: [PATCH 53/53] remove some iterators that seem to fix a compiler error --- src/support/inplace_vector.h | 1 - src/support/small_vector.h | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/support/inplace_vector.h b/src/support/inplace_vector.h index cebf6f112fb..5f0d3628ebb 100644 --- a/src/support/inplace_vector.h +++ b/src/support/inplace_vector.h @@ -154,7 +154,6 @@ template class inplace_vector { ConstIterator(const Iterator& other) : wasm::ParentIndexIterator*, ConstIterator>{ other.parent, other.index} {} - ConstIterator(const ConstIterator& other) = default; const T& operator*() const { return (*this->parent)[this->index]; } const T* operator->() const { return &(*this->parent)[this->index]; } diff --git a/src/support/small_vector.h b/src/support/small_vector.h index 6a7a6ec89ec..93c8899bc59 100644 --- a/src/support/small_vector.h +++ b/src/support/small_vector.h @@ -175,7 +175,6 @@ template class SmallVector { Iterator(SmallVector* parent, size_t index) : ParentIndexIterator*, Iterator>{parent, index} {} - Iterator(const Iterator& other) = default; T& operator*() const { return (*this->parent)[this->index]; } }; @@ -189,7 +188,6 @@ template class SmallVector { ConstIterator(const SmallVector* parent, size_t index) : ParentIndexIterator*, ConstIterator>{parent, index} {} - ConstIterator(const ConstIterator& other) = default; const T& operator*() const { return (*this->parent)[this->index]; } };