From fdd502462f50511a1e17eb13c18cc92734e39fa5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 7 May 2026 15:49:26 -0700 Subject: [PATCH 001/202] go --- src/passes/CMakeLists.txt | 1 + src/passes/RangeAnalysis.cpp | 98 ++++++++++++++++++++++++++++++++++++ src/passes/pass.cpp | 3 ++ src/passes/passes.h | 1 + 4 files changed, 103 insertions(+) create mode 100644 src/passes/RangeAnalysis.cpp diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index a61bfb6195c..d1ca56b9bc5 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -104,6 +104,7 @@ set(passes_SOURCES StripToolchainAnnotations.cpp TraceCalls.cpp RandomizeBranchHints.cpp + RangeAnalysis.cpp RedundantSetElimination.cpp RemoveExports.cpp RemoveImports.cpp diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp new file mode 100644 index 00000000000..93100c111f1 --- /dev/null +++ b/src/passes/RangeAnalysis.cpp @@ -0,0 +1,98 @@ +/* + * Copyright 2026 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// Finds ranges of values for locals, and uses them. For example: +// +// if (x > 10) { +// assert(x > 0); // redundant and can be removed. +// } +// +// TODO: Look not just at integers but also references +// + +#include "cfg/cfg-traversal.h" +#include "ir/local-graph.h" +#include "pass.h" +#include "wasm-builder.h" +#include "wasm.h" + +namespace wasm { + +namespace { + +// In each basic block we will store the relevant operations, which are all +// local gets and sets, branches, and uses of them. +struct Info { + std::vector actions; // XXX just *? +}; + +struct RangeAnalysis + : public WalkerPass< + CFGWalker, Info>> { + bool isFunctionParallel() override { return true; } + + // Locals are not modified here. + bool requiresNonNullableLocalFixups() override { return false; } + + std::unique_ptr create() override { + return std::make_unique(); + } + + // Branches outside of the function can be ignored, as we only look at local + // state in the function. + bool ignoreBranchesOutsideOfFunc = true; + + // Store struct.sets and blocks, as we can find patterns among those. + void addAction() { + if (currBasicBlock) { + currBasicBlock->contents.actions.push_back(getCurrentPointer()); + } + } + void visitStructSet(StructSet* curr) { addAction(); } + void visitBlock(Block* curr) { addAction(); } + + void visitFunction(Function* curr) { + // Now that the walk is complete and we have a CFG, find things to optimize. + for (auto& block : basicBlocks) { + for (auto** currp : block->contents.actions) { + auto* curr = *currp; + if (auto* set = curr->dynCast()) { + optimizeStructSet(set, currp); + } else if (auto* block = curr->dynCast()) { + optimizeBlock(block); + } else { + WASM_UNREACHABLE("bad action"); + } + } + } + } + +private: + // A local graph that is constructed the first time we need it. + std::optional localGraph; + /* + if (!localGraph) { + localGraph.emplace(getFunction(), getModule(), StructSet::SpecificId); + } +*/ +}; + +} // anonymous namespace + +Pass* createRangeAnalysisPass() { return new RangeAnalysis(); } + +} // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index dc6d91feb4e..e7fe51f194b 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -406,6 +406,9 @@ void PassRegistry::registerPasses() { registerPass("propagate-globals-globally", "propagate global values to other globals (useful for tests)", createPropagateGlobalsGloballyPass); + registerPass("range-analysis", + "finds and uses value ranges for locals", + createRangeAnalysisPass); registerPass("remove-non-js-ops", "removes operations incompatible with js", createRemoveNonJSOpsPass); diff --git a/src/passes/passes.h b/src/passes/passes.h index 2fdacd84ab0..30cb520a3cd 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -133,6 +133,7 @@ Pass* createPrintFeaturesPass(); Pass* createPrintFunctionMapPass(); Pass* createPropagateGlobalsGloballyPass(); Pass* createRandomizeBranchHintsPass(); +Pass* createRangeAnalysisPass(); Pass* createRemoveNonJSOpsPass(); Pass* createRemoveRelaxedSIMDPass(); Pass* createRemoveExportsPass(); From aabd860e4de599609156ae6594a37a8c636d3d76 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 7 May 2026 16:04:30 -0700 Subject: [PATCH 002/202] go --- src/passes/RangeAnalysis.cpp | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 93100c111f1..f838d7b31a0 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -26,6 +26,7 @@ #include "cfg/cfg-traversal.h" #include "ir/local-graph.h" +#include "ir/properties.h" #include "pass.h" #include "wasm-builder.h" #include "wasm.h" @@ -42,7 +43,7 @@ struct Info { struct RangeAnalysis : public WalkerPass< - CFGWalker, Info>> { + CFGWalker> { bool isFunctionParallel() override { return true; } // Locals are not modified here. @@ -52,6 +53,8 @@ struct RangeAnalysis return std::make_unique(); } + using Super = WalkerPass>; + // Branches outside of the function can be ignored, as we only look at local // state in the function. bool ignoreBranchesOutsideOfFunc = true; @@ -62,8 +65,32 @@ struct RangeAnalysis currBasicBlock->contents.actions.push_back(getCurrentPointer()); } } - void visitStructSet(StructSet* curr) { addAction(); } - void visitBlock(Block* curr) { addAction(); } + + // Track the branches we reason about. CFGWalker builds a CFG, and we want to + // add information on top of that about which branch is due to which + // instruction. For example, if block A branches to B and C, we want to know + // if A ends in a br_if, so we can apply its condition to the branches to B + // (if the condition is true) and C (if false). + + // Maps each branching instruction to the basic block right before the + // branchings. For example, for an If, this is the block that branches to the + // ifTrue and ifFalse blocks. + std::unordered_map brancherBlocks; + + static void doStartIfTrue(SubType* self, Expression** currp) { + // We are right after the condition, so we are in the block before the If's + // branching. + self->brancherBlocks[*currp] = self->currBasicBlock; + + Super::doStartIfTrue(self, currp); + } + + static void doEndBranch(SubType* self, Expression** currp) { + // We are right after the condition, so we are in the block before the If's + // branching. + XXX maybe leave for laterself->brancherBlocks[*currp] = self->currBasicBlock; + Super::doEndBranch(self, currp); + } void visitFunction(Function* curr) { // Now that the walk is complete and we have a CFG, find things to optimize. From 8b180128c2c10375bff947204cc3e37871a31b4d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 2 Jun 2026 14:57:26 -0700 Subject: [PATCH 003/202] work --- src/passes/RangeAnalysis.cpp | 45 +++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index f838d7b31a0..52659af886e 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -21,6 +21,8 @@ // assert(x > 0); // redundant and can be removed. // } // +// TODO: Compare locals, inferring that x <= y in some range (necessary for +// software bounds check removal. // TODO: Look not just at integers but also references // @@ -59,13 +61,18 @@ struct RangeAnalysis // state in the function. bool ignoreBranchesOutsideOfFunc = true; - // Store struct.sets and blocks, as we can find patterns among those. + // Store the actions we care about. void addAction() { if (currBasicBlock) { currBasicBlock->contents.actions.push_back(getCurrentPointer()); } } + void visitLocalGet(LocalGet* curr) { addAction(); } + void visitLocalSet(LocalSet* curr) { addAction(); } + void visitUnary(Unary* curr) { addAction(); } + void visitBinary(Binary* curr) { addAction(); } + // Track the branches we reason about. CFGWalker builds a CFG, and we want to // add information on top of that about which branch is due to which // instruction. For example, if block A branches to B and C, we want to know @@ -85,33 +92,55 @@ struct RangeAnalysis Super::doStartIfTrue(self, currp); } +#if 0 static void doEndBranch(SubType* self, Expression** currp) { // We are right after the condition, so we are in the block before the If's // branching. XXX maybe leave for laterself->brancherBlocks[*currp] = self->currBasicBlock; Super::doEndBranch(self, currp); } +#endif void visitFunction(Function* curr) { // Now that the walk is complete and we have a CFG, find things to optimize. + // We start with the relevant locals, i.e. which we could optimize: for + // example, if we see (i32.eqz (local.get $x)) then we know that information + // about $x might resolve the eqz, and we compute it and things related to + // it. + std::unordered_set relevantLocals; + + auto maybeAdd = [&](Expression* value) { + // Given a value flowing into something we can optimize, see if there is a + // local there, and if so, mark it as relevant. + // TODO: handle tee + // TODO: handle fallthrough values + if (auto* get = value->dynCast()) { + relevantLocals.insert(get->index); + } + }; + for (auto& block : basicBlocks) { for (auto** currp : block->contents.actions) { auto* curr = *currp; - if (auto* set = curr->dynCast()) { - optimizeStructSet(set, currp); - } else if (auto* block = curr->dynCast()) { - optimizeBlock(block); - } else { - WASM_UNREACHABLE("bad action"); + // TODO: specific unary/binary ops + if (auto* unary = curr->dynCast()) { + maybeAdd(unary->value); + } else if (auto* binary = curr->dynCast()) { + maybeAdd(binary->left); + maybeAdd(binary->right); } } } + + if (relevantLocals.empty()) { + return; + } } private: + /* // A local graph that is constructed the first time we need it. std::optional localGraph; - /* if (!localGraph) { localGraph.emplace(getFunction(), getModule(), StructSet::SpecificId); } From 002d237411769a5d8cd6234551218ca1bce8349d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 2 Jun 2026 15:13:45 -0700 Subject: [PATCH 004/202] work --- src/passes/RangeAnalysis.cpp | 38 +++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 52659af886e..196cd065843 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -26,6 +26,8 @@ // TODO: Look not just at integers but also references // +#include + #include "cfg/cfg-traversal.h" #include "ir/local-graph.h" #include "ir/properties.h" @@ -37,10 +39,25 @@ namespace wasm { namespace { +// In each range of values, one of the values. This can be either a literal like +// i32(0), or a local index (i.e., a reference to another local, showing that +// this one is related to them somehow: one of ==, <, >=, etc.). +using Value = std::variant; + +// A range of values, [min, max] (inclusive). +// TODO: support more clever things like unions +struct Span { + Value min; + Value max; +}; + // In each basic block we will store the relevant operations, which are all // local gets and sets, branches, and uses of them. struct Info { std::vector actions; // XXX just *? + + // The span of values we inferred for locals, in this block. + std::unordered_map localSpans; }; struct RangeAnalysis @@ -101,13 +118,14 @@ struct RangeAnalysis } #endif + // We start with the relevant locals, i.e. which we could optimize: for + // example, if we see (i32.eqz (local.get $x)) then we know that information + // about $x might resolve the eqz, and we compute it and things related to + // it. + std::unordered_set relevantLocals; + void visitFunction(Function* curr) { // Now that the walk is complete and we have a CFG, find things to optimize. - // We start with the relevant locals, i.e. which we could optimize: for - // example, if we see (i32.eqz (local.get $x)) then we know that information - // about $x might resolve the eqz, and we compute it and things related to - // it. - std::unordered_set relevantLocals; auto maybeAdd = [&](Expression* value) { // Given a value flowing into something we can optimize, see if there is a @@ -132,11 +150,17 @@ struct RangeAnalysis } } - if (relevantLocals.empty()) { - return; + if (!relevantLocals.empty()) { + optimize(); } } + void optimize() { + // There is something to potentially optimize. For each relevant local, + // find its sets and branches and flow ranges around, producing a graph of + // the value of each relevant local in each block. + } + private: /* // A local graph that is constructed the first time we need it. From 229ae815029ab6a95ee143b5f5ae1696e37a3470 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 2 Jun 2026 15:55:58 -0700 Subject: [PATCH 005/202] work --- src/passes/RangeAnalysis.cpp | 40 ++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 196cd065843..62b0c738c1f 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -39,9 +39,12 @@ namespace wasm { namespace { +struct Unknown : public std::monostate {}; + // In each range of values, one of the values. This can be either a literal like // i32(0), or a local index (i.e., a reference to another local, showing that -// this one is related to them somehow: one of ==, <, >=, etc.). +// this one is related to them somehow: one of ==, <, >=, etc.), or something +// unknown. using Value = std::variant; // A range of values, [min, max] (inclusive). @@ -51,13 +54,18 @@ struct Span { Value max; }; +// The span of values we inferred for locals. +using LocalSpans = std::unordered_map; + // In each basic block we will store the relevant operations, which are all // local gets and sets, branches, and uses of them. struct Info { std::vector actions; // XXX just *? - // The span of values we inferred for locals, in this block. - std::unordered_map localSpans; + // We track them local spans at + // the start and at the end of the block (for the values in the middle, we + // need to traverse the actions and see how they are modified). + LocalSpans localSpansStart, localSpansEnd; }; struct RangeAnalysis @@ -85,7 +93,7 @@ struct RangeAnalysis } } - void visitLocalGet(LocalGet* curr) { addAction(); } + void visitLocalGet(LocalGet* curr) { addAction(); } // XXX needed? void visitLocalSet(LocalSet* curr) { addAction(); } void visitUnary(Unary* curr) { addAction(); } void visitBinary(Binary* curr) { addAction(); } @@ -150,6 +158,9 @@ struct RangeAnalysis } } + // Values can flow between locals: if x is relevant, and y is written to it, + // we must consider y relevant too. TODO? + if (!relevantLocals.empty()) { optimize(); } @@ -159,6 +170,27 @@ struct RangeAnalysis // There is something to potentially optimize. For each relevant local, // find its sets and branches and flow ranges around, producing a graph of // the value of each relevant local in each block. + for (auto& block : basicBlocks) { + LocalSpans localSpans; + for (auto** currp : block->contents.actions) { + auto* curr = *currp; + if (auto* set = curr->dynCast()) { + if (relevantLocals.contains(set->index)) { + Value value; + // TODO: fallthrough, tee chains, etc. + if (auto* get = set->value->dynCast()) { + value = get->index; + } else if (auto* c = set->value->dynCast()) { + value = c->value; + } else { + value = Unknown; + } + // Both the min and max are equal to what we found. + localSpans[set->index] = Span{value, value}; + } + } + } + } } private: From 5598aece354c0330c92b1c718a8ff4b06aaf1347 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 2 Jun 2026 16:18:02 -0700 Subject: [PATCH 006/202] work --- src/passes/RangeAnalysis.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 62b0c738c1f..eed53711a9c 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -177,7 +177,9 @@ struct RangeAnalysis if (auto* set = curr->dynCast()) { if (relevantLocals.contains(set->index)) { Value value; - // TODO: fallthrough, tee chains, etc. + // TODO: fallthrough, tee chains, etc. For that we must track values + // by Expression*, as e.g. reading a local, then setting it, should + // read the original flowing value. if (auto* get = set->value->dynCast()) { value = get->index; } else if (auto* c = set->value->dynCast()) { @@ -190,6 +192,8 @@ struct RangeAnalysis } } } + // We now know the values at the end of the block. + block->localSpansEnd = std::move(localSpans); } } From ed255d4fcab7d71f95ef79480360f1197dc0e952 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 2 Jun 2026 16:21:02 -0700 Subject: [PATCH 007/202] work --- src/passes/RangeAnalysis.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index eed53711a9c..6a4c22c3f33 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -45,7 +45,7 @@ struct Unknown : public std::monostate {}; // i32(0), or a local index (i.e., a reference to another local, showing that // this one is related to them somehow: one of ==, <, >=, etc.), or something // unknown. -using Value = std::variant; +using Value = std::variant; // A range of values, [min, max] (inclusive). // TODO: support more clever things like unions @@ -54,7 +54,9 @@ struct Span { Value max; }; -// The span of values we inferred for locals. +// The span of values we inferred for locals. In the code below, we consider +// missing indexes to have no known span for them (i.e., we do not need to write +// an Unknown, and can just leave them empty). using LocalSpans = std::unordered_map; // In each basic block we will store the relevant operations, which are all @@ -185,7 +187,9 @@ struct RangeAnalysis } else if (auto* c = set->value->dynCast()) { value = c->value; } else { - value = Unknown; + // Nothing is known. + localSpans.erase(set->index); + continue; } // Both the min and max are equal to what we found. localSpans[set->index] = Span{value, value}; From e5d82991625e479c3a38af3ddbc1eabb0f777a8d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 2 Jun 2026 16:23:51 -0700 Subject: [PATCH 008/202] work --- src/passes/RangeAnalysis.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 6a4c22c3f33..6df791c3c97 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -172,6 +172,7 @@ struct RangeAnalysis // There is something to potentially optimize. For each relevant local, // find its sets and branches and flow ranges around, producing a graph of // the value of each relevant local in each block. + // TODO: flow starts here for (auto& block : basicBlocks) { LocalSpans localSpans; for (auto** currp : block->contents.actions) { @@ -198,6 +199,8 @@ struct RangeAnalysis } // We now know the values at the end of the block. block->localSpansEnd = std::move(localSpans); + // keep flowing if stuff changed + // flow DIFFERENT VALUES based on an if condition!!1 } } From fa4805f153f039bc430ee796d9f82e4da218a8cf Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 10:48:09 -0700 Subject: [PATCH 009/202] work --- src/passes/RangeAnalysis.cpp | 47 ++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 6df791c3c97..e3e55bef830 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -32,6 +32,7 @@ #include "ir/local-graph.h" #include "ir/properties.h" #include "pass.h" +#include "support/unique_deferring_queue.h" #include "wasm-builder.h" #include "wasm.h" @@ -67,6 +68,7 @@ struct Info { // We track them local spans at // the start and at the end of the block (for the values in the middle, we // need to traverse the actions and see how they are modified). + // XXX do we need both? LocalSpans localSpansStart, localSpansEnd; }; @@ -164,17 +166,26 @@ struct RangeAnalysis // we must consider y relevant too. TODO? if (!relevantLocals.empty()) { + flow(); optimize(); } } - void optimize() { - // There is something to potentially optimize. For each relevant local, - // find its sets and branches and flow ranges around, producing a graph of - // the value of each relevant local in each block. - // TODO: flow starts here + // Flow spans around until we have inferred all we can about the ranges of + // values in each location. + void flow() { + // Start from all the blocks, and keep going while we find something new. + UniqueDeferredQueue work; for (auto& block : basicBlocks) { - LocalSpans localSpans; + work.push(block.get()); + } + while (!work.empty()) { + auto* block = work.pop(); + + // Merge incoming data. + LocalSpans localSpans = mergeIncoming(block); + + // Go through the block, applying things. for (auto** currp : block->contents.actions) { auto* curr = *currp; if (auto* set = curr->dynCast()) { @@ -197,14 +208,30 @@ struct RangeAnalysis } } } - // We now know the values at the end of the block. - block->localSpansEnd = std::move(localSpans); - // keep flowing if stuff changed - // flow DIFFERENT VALUES based on an if condition!!1 + + // We now know the values at the end of the block. If something changed, + // flow it onward. + if (localSpans != block->localSpansEnd) { + block->localSpansEnd = std::move(localSpans); + for (auto* out : block->out) { + work.push(out); + } + } } } + // + void optimize() { + } + private: + + // Merge incoming data to a block, by looking at the data arriving from each + // of the predecessor blocks. + LocalSpans localSpans = mergeIncoming(BasicBlock* block) { + + } + /* // A local graph that is constructed the first time we need it. std::optional localGraph; From ae146cfe9a923048b4728bd824fec8983e924a4a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 10:50:01 -0700 Subject: [PATCH 010/202] work --- src/passes/RangeAnalysis.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index e3e55bef830..207816068ce 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -220,8 +220,17 @@ struct RangeAnalysis } } - // + // After inferring all we can, apply it to optimize the code. void optimize() { + struct Optimizer : public PostWalker { + RangeAnalysis& parent; + + Optimizer(RangeAnalysis& parent) : parent(parent) {} + + void visitBinary(Binary* curr) { + } + } optimizer(*this); + optimizer.walk(getFunction()); } private: From 4748cc523d27dea40c8d63a38c0a408cf3c92b3a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 11:00:47 -0700 Subject: [PATCH 011/202] work --- src/passes/RangeAnalysis.cpp | 48 +++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 207816068ce..341a92fac3e 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -51,8 +51,12 @@ using Value = std::variant; // A range of values, [min, max] (inclusive). // TODO: support more clever things like unions struct Span { - Value min; - Value max; + Value min = Unknown; + Value max = Unknown; + + bool isUnknown() { return min == Unknown && max == Unknown; } + + static Span unknown() { return Span{Unknown, Unknown}; } }; // The span of values we inferred for locals. In the code below, we consider @@ -233,12 +237,44 @@ struct RangeAnalysis optimizer.walk(getFunction()); } -private: - // Merge incoming data to a block, by looking at the data arriving from each // of the predecessor blocks. - LocalSpans localSpans = mergeIncoming(BasicBlock* block) { - + LocalSpans mergeIncoming(BasicBlock* block) { + LocalSpans localSpans; + // For each relevant local, merge its spans. + for (auto local : relevantLocals) { + Span mergedSpan; + for (auto* pred : block->in) { + auto span = getSpanFromPredToSucc(pred, block, local); + if (span.isUnknown()) { + // Unknown, so the entire merge is unknown. + mergedSpan = Span::unknown(); + break; + } + if (mergedSpan.isUnknown()) { + // This is the first item. Copy it. + mergedSpan = span; + } else { + // Merge in new data alongside existing. + mergedSpan = merge(mergedSpan, span); + } + } + } + } + + // Given a source (predecessor) and a target (successor) block, find the span + // of a particular local as it arrives to that target from that successor. + Span getSpanFromPredToSucc(BasicBlock* pred, BasicBlock* block, Index local) { + auto iter = pred->localSpansEnd.find(local); + if (iter == pred->localSpansEnd.end()) { + return Span::unknown(); + } + + .. + } + + // Merge two spans. This is a monotonic operation, to avoid infinite loops. + Span merge(Span a, Span b) { } /* From f7e33ccde3cd5fa280d4dd17b7579e11adce7a9a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 11:27:04 -0700 Subject: [PATCH 012/202] work --- src/passes/RangeAnalysis.cpp | 58 +++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 341a92fac3e..e03065777df 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -33,6 +33,7 @@ #include "ir/properties.h" #include "pass.h" #include "support/unique_deferring_queue.h" +#include "support/utilities.h" #include "wasm-builder.h" #include "wasm.h" @@ -273,8 +274,63 @@ struct RangeAnalysis .. } - // Merge two spans. This is a monotonic operation, to avoid infinite loops. + enum MinMax { Min, Max }; + + // Merge two spans. This is a merge of two spans from two different + // predecessor blocks, so the result is a span large enough to contain them + // both, as all values in either one are possible. + // + // Note that this is a monotonic operation, to avoid infinite loops. Span merge(Span a, Span b) { + return Span{merge(a.min, b.min, Min), merge(a.max, b.max, Max)}; + } + +//using Value = std::variant; + Value merge(Value a, Value b, MinMax op) { + Value ret; + std::visit(overloaded{ + [&](Literal& lit1) { + std::visit(overloaded{ + [&](Literal& lit2) { + if (lit1 == lit2) { + // Equal literals. + ret = lit1; + } else if (lit1.type.isNumber()) { + // Numbers can be ordered. + assert(lit2.type == lit1.type); + if (lit1.le(lit2)) { + ret = (op == Min) ? lit1 : lit2; + } else { + ret = (op == Min) ? lit2 : lit1; + } + } else { + // Anything else (function reference, etc.) is unknown. + ret = Span::unknown(); + } + }, + [&](Index& local2) { + }, + [&](Unknown& unknown2) { + ret = unknown; + }, + }, b); + }, + [&](Index& local) { + std::visit(overloaded{ + [&](Literal& lit2) { + }, + [&](Index& local2) { + }, + [&](Unknown& unknown2) { + ret = unknown; + }, + }, b); + }, + [&](Unknown& unknown) { + ret = unknown; + }, + }, a); + return ret; } /* From 80a843e85f48a5522f4da2db40988909d5fb874b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 11:29:16 -0700 Subject: [PATCH 013/202] work --- src/passes/RangeAnalysis.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index e03065777df..ff8787be8fb 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -309,24 +309,32 @@ struct RangeAnalysis } }, [&](Index& local2) { + // Mix of literal and local. We don't know what to make of this. + // TODO: consider trees of constraints and using a solver + ret = Span::unknown(); }, [&](Unknown& unknown2) { ret = unknown; }, }, b); }, - [&](Index& local) { + [&](Index& local1) { std::visit(overloaded{ [&](Literal& lit2) { + // Mix of literal and local, as above. + ret = Span::unknown(); }, [&](Index& local2) { + // Two locals. If equal, we know the outcome. + ret = (local1 == local2) ? local1 : Span::unknown(); }, [&](Unknown& unknown2) { ret = unknown; }, }, b); }, - [&](Unknown& unknown) { + [&](Unknown& unknown1) { + // It doesn't even matter what b is. ret = unknown; }, }, a); From 2b10d87bd90a0678e2894bb4767a376e71eaa56e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 11:30:10 -0700 Subject: [PATCH 014/202] work --- src/passes/RangeAnalysis.cpp | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index ff8787be8fb..f6584e1bdf1 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -271,7 +271,7 @@ struct RangeAnalysis return Span::unknown(); } - .. + // TODO: use conditional branching to send different values along branches } enum MinMax { Min, Max }; @@ -285,7 +285,6 @@ struct RangeAnalysis return Span{merge(a.min, b.min, Min), merge(a.max, b.max, Max)}; } -//using Value = std::variant; Value merge(Value a, Value b, MinMax op) { Value ret; std::visit(overloaded{ @@ -340,14 +339,6 @@ struct RangeAnalysis }, a); return ret; } - - /* - // A local graph that is constructed the first time we need it. - std::optional localGraph; - if (!localGraph) { - localGraph.emplace(getFunction(), getModule(), StructSet::SpecificId); - } -*/ }; } // anonymous namespace From c9f17bca3faab8cac60d6639b75ad93dd5dd6366 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 11:30:44 -0700 Subject: [PATCH 015/202] work --- src/passes/RangeAnalysis.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index f6584e1bdf1..8df16a4b755 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -272,6 +272,7 @@ struct RangeAnalysis } // TODO: use conditional branching to send different values along branches + return iter->second; } enum MinMax { Min, Max }; From 18a691b9c67669384d4705551a07104afe0fa200 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 11:30:56 -0700 Subject: [PATCH 016/202] form --- src/passes/RangeAnalysis.cpp | 108 +++++++++++++++++------------------ 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 8df16a4b755..e35909319a0 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -77,9 +77,7 @@ struct Info { LocalSpans localSpansStart, localSpansEnd; }; -struct RangeAnalysis - : public WalkerPass< - CFGWalker> { +struct RangeAnalysis : public WalkerPass> { bool isFunctionParallel() override { return true; } // Locals are not modified here. @@ -232,8 +230,7 @@ struct RangeAnalysis Optimizer(RangeAnalysis& parent) : parent(parent) {} - void visitBinary(Binary* curr) { - } + void visitBinary(Binary* curr) {} } optimizer(*this); optimizer.walk(getFunction()); } @@ -289,55 +286,58 @@ struct RangeAnalysis Value merge(Value a, Value b, MinMax op) { Value ret; std::visit(overloaded{ - [&](Literal& lit1) { - std::visit(overloaded{ - [&](Literal& lit2) { - if (lit1 == lit2) { - // Equal literals. - ret = lit1; - } else if (lit1.type.isNumber()) { - // Numbers can be ordered. - assert(lit2.type == lit1.type); - if (lit1.le(lit2)) { - ret = (op == Min) ? lit1 : lit2; - } else { - ret = (op == Min) ? lit2 : lit1; - } - } else { - // Anything else (function reference, etc.) is unknown. - ret = Span::unknown(); - } - }, - [&](Index& local2) { - // Mix of literal and local. We don't know what to make of this. - // TODO: consider trees of constraints and using a solver - ret = Span::unknown(); - }, - [&](Unknown& unknown2) { - ret = unknown; - }, - }, b); - }, - [&](Index& local1) { - std::visit(overloaded{ - [&](Literal& lit2) { - // Mix of literal and local, as above. - ret = Span::unknown(); - }, - [&](Index& local2) { - // Two locals. If equal, we know the outcome. - ret = (local1 == local2) ? local1 : Span::unknown(); - }, - [&](Unknown& unknown2) { - ret = unknown; - }, - }, b); - }, - [&](Unknown& unknown1) { - // It doesn't even matter what b is. - ret = unknown; - }, - }, a); + [&](Literal& lit1) { + std::visit(overloaded{ + [&](Literal& lit2) { + if (lit1 == lit2) { + // Equal literals. + ret = lit1; + } else if (lit1.type.isNumber()) { + // Numbers can be ordered. + assert(lit2.type == lit1.type); + if (lit1.le(lit2)) { + ret = (op == Min) ? lit1 : lit2; + } else { + ret = (op == Min) ? lit2 : lit1; + } + } else { + // Anything else (function reference, etc.) + // is unknown. + ret = Span::unknown(); + } + }, + [&](Index& local2) { + // Mix of literal and local. We don't know + // what to make of this. + // TODO: consider trees of constraints and + // using a solver + ret = Span::unknown(); + }, + [&](Unknown& unknown2) { ret = unknown; }, + }, + b); + }, + [&](Index& local1) { + std::visit(overloaded{ + [&](Literal& lit2) { + // Mix of literal and local, as above. + ret = Span::unknown(); + }, + [&](Index& local2) { + // Two locals. If equal, we know the outcome. + ret = (local1 == local2) ? local1 + : Span::unknown(); + }, + [&](Unknown& unknown2) { ret = unknown; }, + }, + b); + }, + [&](Unknown& unknown1) { + // It doesn't even matter what b is. + ret = unknown; + }, + }, + a); return ret; } }; From 5e8c7c36a16c8c1b67f9744780a0c10de838602b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 11:39:10 -0700 Subject: [PATCH 017/202] work --- src/passes/RangeAnalysis.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index e35909319a0..d79ab0352c4 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -47,17 +47,21 @@ struct Unknown : public std::monostate {}; // i32(0), or a local index (i.e., a reference to another local, showing that // this one is related to them somehow: one of ==, <, >=, etc.), or something // unknown. -using Value = std::variant; +struct Value : public std::variant { + bool isUnknown() const { + return std::holds_alternative(*this); + } +}; // A range of values, [min, max] (inclusive). // TODO: support more clever things like unions struct Span { - Value min = Unknown; - Value max = Unknown; + Value min = Unknown(); + Value max = Unknown(); - bool isUnknown() { return min == Unknown && max == Unknown; } + bool isUnknown() { return min.isUnknown() && max.isUnknown(); } - static Span unknown() { return Span{Unknown, Unknown}; } + static Span unknown() { return Span{Unknown(), Unknown()}; } }; // The span of values we inferred for locals. In the code below, we consider From 3b22e48f56a0d1029e1cc696f07eff06a2a28950 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 11:40:35 -0700 Subject: [PATCH 018/202] work --- src/passes/RangeAnalysis.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index d79ab0352c4..343bff644dc 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -56,8 +56,8 @@ struct Value : public std::variant { // A range of values, [min, max] (inclusive). // TODO: support more clever things like unions struct Span { - Value min = Unknown(); - Value max = Unknown(); + Value min; + Value max; bool isUnknown() { return min.isUnknown() && max.isUnknown(); } @@ -81,7 +81,7 @@ struct Info { LocalSpans localSpansStart, localSpansEnd; }; -struct RangeAnalysis : public WalkerPass> { +struct RangeAnalysis : public WalkerPass, Info>> { bool isFunctionParallel() override { return true; } // Locals are not modified here. @@ -91,7 +91,7 @@ struct RangeAnalysis : public WalkerPass> { return std::make_unique(); } - using Super = WalkerPass>; + using Super = WalkerPass, Info>>; // Branches outside of the function can be ignored, as we only look at local // state in the function. From 08e419d3a66ab80f8225712830980ffd7afe1522 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 11:45:17 -0700 Subject: [PATCH 019/202] work --- src/passes/RangeAnalysis.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 343bff644dc..aebada3ffb0 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -118,18 +118,20 @@ struct RangeAnalysis : public WalkerPass brancherBlocks; - static void doStartIfTrue(SubType* self, Expression** currp) { + static void doStartIfTrue(RangeAnalysis* self, Expression** currp) { // We are right after the condition, so we are in the block before the If's // branching. self->brancherBlocks[*currp] = self->currBasicBlock; Super::doStartIfTrue(self, currp); } +#endif #if 0 - static void doEndBranch(SubType* self, Expression** currp) { + static void doEndBranch(RangeAnalysis* self, Expression** currp) { // We are right after the condition, so we are in the block before the If's // branching. XXX maybe leave for laterself->brancherBlocks[*currp] = self->currBasicBlock; @@ -202,9 +204,9 @@ struct RangeAnalysis : public WalkerPassvalue->dynCast()) { - value = get->index; + value = Value(get->index); } else if (auto* c = set->value->dynCast()) { - value = c->value; + value = Value(c->value); } else { // Nothing is known. localSpans.erase(set->index); @@ -262,6 +264,7 @@ struct RangeAnalysis : public WalkerPass Date: Wed, 3 Jun 2026 11:46:24 -0700 Subject: [PATCH 020/202] work --- src/passes/RangeAnalysis.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index aebada3ffb0..bbd279c99d7 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -220,8 +220,8 @@ struct RangeAnalysis : public WalkerPasslocalSpansEnd) { - block->localSpansEnd = std::move(localSpans); + if (localSpans != block->contents.localSpansEnd) { + block->contents.localSpansEnd = std::move(localSpans); for (auto* out : block->out) { work.push(out); } @@ -270,8 +270,8 @@ struct RangeAnalysis : public WalkerPasslocalSpansEnd.find(local); - if (iter == pred->localSpansEnd.end()) { + auto iter = pred->contents.localSpansEnd.find(local); + if (iter == pred->contents.localSpansEnd.end()) { return Span::unknown(); } From 15c806a69262704fe4b80815dce6cfacf08c7584 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 11:47:12 -0700 Subject: [PATCH 021/202] work --- src/passes/RangeAnalysis.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index bbd279c99d7..1e65f24f13e 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -236,9 +236,11 @@ struct RangeAnalysis : public WalkerPassbody); } // Merge incoming data to a block, by looking at the data arriving from each From c30dcdab1509de1a902dba7b38f49cc8987e1488 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 11:48:01 -0700 Subject: [PATCH 022/202] work --- src/passes/RangeAnalysis.cpp | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 1e65f24f13e..629a3637b18 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -295,19 +295,19 @@ struct RangeAnalysis : public WalkerPass Date: Wed, 3 Jun 2026 11:49:18 -0700 Subject: [PATCH 023/202] work --- src/passes/RangeAnalysis.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 629a3637b18..5fe7dcd3c5f 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -300,14 +300,14 @@ struct RangeAnalysis : public WalkerPass Date: Wed, 3 Jun 2026 14:09:45 -0700 Subject: [PATCH 024/202] work --- src/passes/RangeAnalysis.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 5fe7dcd3c5f..608555d5762 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -48,6 +48,8 @@ struct Unknown : public std::monostate {}; // this one is related to them somehow: one of ==, <, >=, etc.), or something // unknown. struct Value : public std::variant { + static Value unknown() { return Unknown(); } + bool isUnknown() const { return std::holds_alternative(*this); } @@ -59,9 +61,9 @@ struct Span { Value min; Value max; - bool isUnknown() { return min.isUnknown() && max.isUnknown(); } - static Span unknown() { return Span{Unknown(), Unknown()}; } + + bool isUnknown() { return min.isUnknown() && max.isUnknown(); } }; // The span of values we inferred for locals. In the code below, we consider @@ -304,7 +306,7 @@ struct RangeAnalysis : public WalkerPass Date: Wed, 3 Jun 2026 14:11:40 -0700 Subject: [PATCH 025/202] work --- src/passes/RangeAnalysis.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 608555d5762..9028bcad556 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -48,7 +48,7 @@ struct Unknown : public std::monostate {}; // this one is related to them somehow: one of ==, <, >=, etc.), or something // unknown. struct Value : public std::variant { - static Value unknown() { return Unknown(); } + static Value unknown() { return Value(Unknown()); } bool isUnknown() const { return std::holds_alternative(*this); @@ -324,7 +324,7 @@ struct RangeAnalysis : public WalkerPass Date: Wed, 3 Jun 2026 14:20:25 -0700 Subject: [PATCH 026/202] work --- src/passes/RangeAnalysis.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 9028bcad556..14a8ee393a8 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -53,6 +53,8 @@ struct Value : public std::variant { bool isUnknown() const { return std::holds_alternative(*this); } + + bool operator==(const Value&) const = default; }; // A range of values, [min, max] (inclusive). @@ -64,6 +66,8 @@ struct Span { static Span unknown() { return Span{Unknown(), Unknown()}; } bool isUnknown() { return min.isUnknown() && max.isUnknown(); } + + bool operator==(const Span&) const = default; }; // The span of values we inferred for locals. In the code below, we consider @@ -222,7 +226,8 @@ struct RangeAnalysis : public WalkerPasscontents.localSpansEnd) { + LocalSpans t = block->contents.localSpansEnd; + if (localSpans != t) { block->contents.localSpansEnd = std::move(localSpans); for (auto* out : block->out) { work.push(out); From 6390870b67599fdd6bf9b55ce116374206842a96 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 14:20:37 -0700 Subject: [PATCH 027/202] format --- src/passes/RangeAnalysis.cpp | 116 +++++++++++++++++------------------ 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 14a8ee393a8..004004e6d48 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -50,9 +50,7 @@ struct Unknown : public std::monostate {}; struct Value : public std::variant { static Value unknown() { return Value(Unknown()); } - bool isUnknown() const { - return std::holds_alternative(*this); - } + bool isUnknown() const { return std::holds_alternative(*this); } bool operator==(const Value&) const = default; }; @@ -87,7 +85,8 @@ struct Info { LocalSpans localSpansStart, localSpansEnd; }; -struct RangeAnalysis : public WalkerPass, Info>> { +struct RangeAnalysis + : public WalkerPass, Info>> { bool isFunctionParallel() override { return true; } // Locals are not modified here. @@ -97,7 +96,8 @@ struct RangeAnalysis : public WalkerPass(); } - using Super = WalkerPass, Info>>; + using Super = + WalkerPass, Info>>; // Branches outside of the function can be ignored, as we only look at local // state in the function. @@ -301,59 +301,59 @@ struct RangeAnalysis : public WalkerPass Date: Wed, 3 Jun 2026 14:27:06 -0700 Subject: [PATCH 028/202] work --- test/lit/passes/range-analysis.wast | 68 +++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 test/lit/passes/range-analysis.wast diff --git a/test/lit/passes/range-analysis.wast b/test/lit/passes/range-analysis.wast new file mode 100644 index 00000000000..464fd6ac0b8 --- /dev/null +++ b/test/lit/passes/range-analysis.wast @@ -0,0 +1,68 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Test range-analysis. We run optimize-instructions first as it canonicalizes +;; the other of binaries etc., and we want range-analysis to always work on the +;; canonicalized form. + +;; RUN: wasm-opt %s --optimize-instructions --range-analysis -all -S -o - | filecheck %s + +(module + (func $simple (param $p i32) + (local $x i32) + ;; TODO: handle a fallthrough + (if + (local.get $p) + (then + (local.set $x + (i32.const 10) + ) + ) + (else + (local.set $x + (i32.const 20) + ) + ) + ) + ;; $x is in [10, 20], so it is less than 30 + (drop + (i32.lt_u + (local.get $x) + (i32.const 30) + ) + ) + ;; And less than 21. + (drop + (i32.lt_u + (local.get $x) + (i32.const 21) + ) + ) + ;; And less or equal to 20. + (drop + (i32.le_u + (local.get $x) + (i32.const 20) + ) + ) + ;; But *not* strictly less than 20. + (drop + (i32.lt_u + (local.get $x) + (i32.const 20) + ) + ) + ;; And not less (or equal) to 19. + (drop + (i32.le_u + (local.get $x) + (i32.const 19) + ) + ) + (drop + (i32.lt_u + (local.get $x) + (i32.const 19) + ) + ) + ) +) From f54afaa2e73295c59bedba2f0d1e527461be98ee Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 14:31:16 -0700 Subject: [PATCH 029/202] work --- src/passes/RangeAnalysis.cpp | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 004004e6d48..1833d8f07f0 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -244,7 +244,29 @@ struct RangeAnalysis Optimizer(RangeAnalysis& parent) : parent(parent) {} void visitBinary(Binary* curr) { - // TODO + /// Find relevant gets. If we see a get we can't handle, stop. + auto* leftGet = curr->left->dynCast(); + auto* rightGet = curr->right->dynCast(); + if (leftGet && !parent.relevantLocals.contains(leftGet->index)) { + return; + } + if (rightGet && !parent.relevantLocals.contains(rightGet->index)) { + return; + } + + // Find consts. + auto* leftConst = curr->left->dynCast(); + auto* rightConst = curr->right->dynCast(); + + // If we have something other than relevant gets and consts, stop. + if (!leftGet && !leftConst) { + return; + } + if (!rightGet && !rightConst) { + return; + } + + } } optimizer(*this); optimizer.walk(getFunction()->body); From 80712006225bbf6f15dc2414040dc535d7812d06 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 14:42:18 -0700 Subject: [PATCH 030/202] work --- src/passes/RangeAnalysis.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 1833d8f07f0..036635dd37c 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -266,7 +266,9 @@ struct RangeAnalysis return; } - + switch (curr->op) { + case LtSInt32: + } } } optimizer(*this); optimizer.walk(getFunction()->body); From b550c4d667bc00ee1220ab0cc133865dbd05ddb7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 14:46:37 -0700 Subject: [PATCH 031/202] work --- src/passes/RangeAnalysis.cpp | 67 +++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 036635dd37c..9f7c2e86138 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -112,7 +112,7 @@ struct RangeAnalysis void visitLocalGet(LocalGet* curr) { addAction(); } // XXX needed? void visitLocalSet(LocalSet* curr) { addAction(); } - void visitUnary(Unary* curr) { addAction(); } + void visitUnary(Unary* curr) { addAction(); } // XXX needed? void visitBinary(Binary* curr) { addAction(); } // Track the branches we reason about. CFGWalker builds a CFG, and we want to @@ -238,40 +238,45 @@ struct RangeAnalysis // After inferring all we can, apply it to optimize the code. void optimize() { - struct Optimizer : public PostWalker { - RangeAnalysis& parent; - - Optimizer(RangeAnalysis& parent) : parent(parent) {} - - void visitBinary(Binary* curr) { - /// Find relevant gets. If we see a get we can't handle, stop. - auto* leftGet = curr->left->dynCast(); - auto* rightGet = curr->right->dynCast(); - if (leftGet && !parent.relevantLocals.contains(leftGet->index)) { - return; - } - if (rightGet && !parent.relevantLocals.contains(rightGet->index)) { - return; + for (auto& block : basicBlocks) { + for (auto** currp : block->contents.actions) { + auto* curr = *currp; + if (auto* binary = curr->dynCast()) { + optimizeBinary(binary, currp, block.get()); } + // TODO unary + } + } + } - // Find consts. - auto* leftConst = curr->left->dynCast(); - auto* rightConst = curr->right->dynCast(); + // Given a binary and its block, try to optimize it. We provide the pointer + // to the binary, so that it can be replaced if optimizable. + void optimizeBinary(Binary* curr, Expression** currp, BasicBlock* block) { + /// Find relevant gets. If we see a get we can't handle, stop. + auto* leftGet = curr->left->dynCast(); + auto* rightGet = curr->right->dynCast(); + if (leftGet && !parent.relevantLocals.contains(leftGet->index)) { + return; + } + if (rightGet && !parent.relevantLocals.contains(rightGet->index)) { + return; + } - // If we have something other than relevant gets and consts, stop. - if (!leftGet && !leftConst) { - return; - } - if (!rightGet && !rightConst) { - return; - } + // Find consts. + auto* leftConst = curr->left->dynCast(); + auto* rightConst = curr->right->dynCast(); - switch (curr->op) { - case LtSInt32: - } - } - } optimizer(*this); - optimizer.walk(getFunction()->body); + // If we have something other than relevant gets and consts, stop. + if (!leftGet && !leftConst) { + return; + } + if (!rightGet && !rightConst) { + return; + } + + switch (curr->op) { + case LtSInt32: + } } // Merge incoming data to a block, by looking at the data arriving from each From b1aedcf91646b0f205faf1a8d255fa3488b77066 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 14:55:32 -0700 Subject: [PATCH 032/202] work --- src/passes/RangeAnalysis.cpp | 57 ++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 9f7c2e86138..9431b188a37 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -202,26 +202,7 @@ struct RangeAnalysis // Go through the block, applying things. for (auto** currp : block->contents.actions) { - auto* curr = *currp; - if (auto* set = curr->dynCast()) { - if (relevantLocals.contains(set->index)) { - Value value; - // TODO: fallthrough, tee chains, etc. For that we must track values - // by Expression*, as e.g. reading a local, then setting it, should - // read the original flowing value. - if (auto* get = set->value->dynCast()) { - value = Value(get->index); - } else if (auto* c = set->value->dynCast()) { - value = Value(c->value); - } else { - // Nothing is known. - localSpans.erase(set->index); - continue; - } - // Both the min and max are equal to what we found. - localSpans[set->index] = Span{value, value}; - } - } + applyToLocalSpans(*currp, localSpans); } // We now know the values at the end of the block. If something changed, @@ -239,10 +220,18 @@ struct RangeAnalysis // After inferring all we can, apply it to optimize the code. void optimize() { for (auto& block : basicBlocks) { + // Follow the general shape of flow(): we need to see what the state is + // at each intermediate point inside the block. (Flowing between blocks is + // of course not needed at this stage.) + + LocalSpans localSpans = mergeIncoming(block); for (auto** currp : block->contents.actions) { auto* curr = *currp; + + applyToLocalSpans(*currp, localSpans); + if (auto* binary = curr->dynCast()) { - optimizeBinary(binary, currp, block.get()); + optimizeBinary(binary, currp, block.get(), localSpans); } // TODO unary } @@ -251,7 +240,7 @@ struct RangeAnalysis // Given a binary and its block, try to optimize it. We provide the pointer // to the binary, so that it can be replaced if optimizable. - void optimizeBinary(Binary* curr, Expression** currp, BasicBlock* block) { + void optimizeBinary(Binary* curr, Expression** currp, BasicBlock* block, const LocalSpans& localSpans) { /// Find relevant gets. If we see a get we can't handle, stop. auto* leftGet = curr->left->dynCast(); auto* rightGet = curr->right->dynCast(); @@ -385,6 +374,30 @@ struct RangeAnalysis a); return ret; } + + // Given an expression, apply it to the local spans. For example, a local.set + // sets the value for that local. + void applyToLocalSpans(Expression* curr, LocalSpans& localSpans) { + if (auto* set = curr->dynCast()) { + if (relevantLocals.contains(set->index)) { + Value value; + // TODO: fallthrough, tee chains, etc. For that we must track values + // by Expression*, as e.g. reading a local, then setting it, should + // read the original flowing value. + if (auto* get = set->value->dynCast()) { + value = Value(get->index); + } else if (auto* c = set->value->dynCast()) { + value = Value(c->value); + } else { + // Nothing is known. + localSpans.erase(set->index); + return; + } + // Both the min and max are equal to what we found. + localSpans[set->index] = Span{value, value}; + } + } + } }; } // anonymous namespace From 0bc8d2efbfde916a6e1a0081ab2961db8c861298 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 15:06:33 -0700 Subject: [PATCH 033/202] work --- src/passes/RangeAnalysis.cpp | 5 +++++ test/lit/passes/range-analysis.wast | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 9431b188a37..f36b9215420 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -28,6 +28,7 @@ #include +#include "ir/abstract.h" #include "cfg/cfg-traversal.h" #include "ir/local-graph.h" #include "ir/properties.h" @@ -263,9 +264,13 @@ struct RangeAnalysis return; } + if (curr->op == Abstract::getBinary(Abstract::LtS switch (curr->op) { case LtSInt32: } + + // TODO: we might consider x < y < z, i.e., chains of relations, with a + // general-purpose constraint solver } // Merge incoming data to a block, by looking at the data arriving from each diff --git a/test/lit/passes/range-analysis.wast b/test/lit/passes/range-analysis.wast index 464fd6ac0b8..95fe57c1718 100644 --- a/test/lit/passes/range-analysis.wast +++ b/test/lit/passes/range-analysis.wast @@ -23,13 +23,19 @@ ) ) ) - ;; $x is in [10, 20], so it is less than 30 + ;; $x is in [10, 20], so it is less than 30 (strictly, and not) (drop (i32.lt_u (local.get $x) (i32.const 30) ) ) + (drop + (i32.le_u + (local.get $x) + (i32.const 30) + ) + ) ;; And less than 21. (drop (i32.lt_u From 61fc656c6c820f97bf1e0d209d64f059680d745d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 15:14:54 -0700 Subject: [PATCH 034/202] work --- src/passes/RangeAnalysis.cpp | 53 +++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index f36b9215420..0098ac00cd2 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -67,6 +67,15 @@ struct Span { bool isUnknown() { return min.isUnknown() && max.isUnknown(); } bool operator==(const Span&) const = default; + + // Check if this span includes a value inside it. + bool includes(const Value& value); + + // Check if this span is smaller than a value. + bool lessThan(const Value& value); + + // Check if this span is greater than a value. + bool greaterThan(const Value& value); }; // The span of values we inferred for locals. In the code below, we consider @@ -264,7 +273,7 @@ struct RangeAnalysis return; } - if (curr->op == Abstract::getBinary(Abstract::LtS + if (curr->op == Abstract::getBinary(Abstract::LtS) switch (curr->op) { case LtSInt32: } @@ -405,6 +414,48 @@ struct RangeAnalysis } }; +bool Span::includes(const Value& value) { + bool ret; + std::visit(overloaded{ + [&](Literal& bLit) { + if (aLit == bLit) { + // Equal literals. + ret = a; + } else if (aLit.type.isNumber()) { + // Numbers can be ordered. + assert(bLit.type == aLit.type); + if (aLit.le(bLit).getUnsigned()) { + ret = (op == Min) ? a : b; + } else { + ret = (op == Min) ? b : a; + } + } else { + // Anything else (function reference, etc.) + // is unknown. + ret = Value::unknown(); + } + }, + [&](Index& bLocal) { + // Mix of literal and local. We don't know + // what to make of this. + // TODO: consider trees of constraints and + // using a solver + ret = Value::unknown(); + }, + [&](Unknown& unknown) { ret = Value::unknown(); }, + }, + value); + return ret; +} + +// Check if this span is smaller than a value. +bool Span::lessThan(const Value& value) { +} + +// Check if this span is greater than a value. +bool Span::greaterThan(const Value& value) { +} + } // anonymous namespace Pass* createRangeAnalysisPass() { return new RangeAnalysis(); } From 3a3970b2d20e2dbd8107260ff2630d98108e51a0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 15:23:37 -0700 Subject: [PATCH 035/202] work --- src/passes/RangeAnalysis.cpp | 41 +++++++++++------------------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 0098ac00cd2..c0bd4742c5c 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -68,13 +68,16 @@ struct Span { bool operator==(const Span&) const = default; - // Check if this span includes a value inside it. + // Check if this span definitely includes a value inside it. If we don't know, + // return false. bool includes(const Value& value); - // Check if this span is smaller than a value. + // Check if this span is definitely smaller than a value (or false if we don't + // know). bool lessThan(const Value& value); - // Check if this span is greater than a value. + // Check if this span is definitely greater than a value (or false if we don't + // know). bool greaterThan(const Value& value); }; @@ -417,42 +420,22 @@ struct RangeAnalysis bool Span::includes(const Value& value) { bool ret; std::visit(overloaded{ - [&](Literal& bLit) { - if (aLit == bLit) { - // Equal literals. - ret = a; - } else if (aLit.type.isNumber()) { - // Numbers can be ordered. - assert(bLit.type == aLit.type); - if (aLit.le(bLit).getUnsigned()) { - ret = (op == Min) ? a : b; - } else { - ret = (op == Min) ? b : a; - } - } else { - // Anything else (function reference, etc.) - // is unknown. - ret = Value::unknown(); - } + [&](Literal& lit) { + // The value is a literal. We can infer something here if the + // span contains .. + if (const int* pval = std::get_if(&v)) }, - [&](Index& bLocal) { - // Mix of literal and local. We don't know - // what to make of this. - // TODO: consider trees of constraints and - // using a solver - ret = Value::unknown(); + [&](Index& local) { }, - [&](Unknown& unknown) { ret = Value::unknown(); }, + [&](Unknown& unknown) { ret = false; }, }, value); return ret; } -// Check if this span is smaller than a value. bool Span::lessThan(const Value& value) { } -// Check if this span is greater than a value. bool Span::greaterThan(const Value& value) { } From 8a6d3558ea9366163d16b0a196cfa8c87528d123 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 15:27:26 -0700 Subject: [PATCH 036/202] work --- src/passes/RangeAnalysis.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index c0bd4742c5c..bc791cfb2df 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -71,6 +71,7 @@ struct Span { // Check if this span definitely includes a value inside it. If we don't know, // return false. bool includes(const Value& value); + // TODO: notIncludes..? // Check if this span is definitely smaller than a value (or false if we don't // know). @@ -418,16 +419,21 @@ struct RangeAnalysis }; bool Span::includes(const Value& value) { - bool ret; + // In most cases, we don't know enough. + bool ret = false; std::visit(overloaded{ [&](Literal& lit) { // The value is a literal. We can infer something here if the - // span contains .. - if (const int* pval = std::get_if(&v)) + // span is a range of literals, checking if value is within + // [min, max]. + if (const int* minLit = std::get_if(&min)) { + if (const int* maxLit = std::get_if(&max)) { + } + } }, [&](Index& local) { }, - [&](Unknown& unknown) { ret = false; }, + [&](Unknown& unknown) {}, }, value); return ret; From 562872d3c7a0c02a1ba48617d2c9b086fccb7f1a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 15:57:15 -0700 Subject: [PATCH 037/202] work --- src/passes/RangeAnalysis.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index bc791cfb2df..f6324f347bd 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -429,6 +429,7 @@ bool Span::includes(const Value& value) { if (const int* minLit = std::get_if(&min)) { if (const int* maxLit = std::get_if(&max)) { } + // TODO: move out and unit test } }, [&](Index& local) { From d296092a4c284bc22c9cfcbb57c5691851fc449a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 16:00:25 -0700 Subject: [PATCH 038/202] go --- src/ir/span.h | 99 ++++++++++++++++++++++++++++++++++++ src/passes/RangeAnalysis.cpp | 71 +------------------------- 2 files changed, 101 insertions(+), 69 deletions(-) create mode 100644 src/ir/span.h diff --git a/src/ir/span.h b/src/ir/span.h new file mode 100644 index 00000000000..dcb29f79dbd --- /dev/null +++ b/src/ir/span.h @@ -0,0 +1,99 @@ +/* + * Copyright 2026 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Spans of values, allowing inference whether a Literal or a local is within +// some range. + +#ifndef wasm_ir_span_h +#define wasm_ir_span_h + +#include + +namespace wasm { + +// A range of values, [min, max] (inclusive). +// TODO: support more clever things like unions +struct Span { + + struct Unknown : public std::monostate {}; + + // In each range of values, one of the values. This can be either a literal like + // i32(0), or a local index (i.e., a reference to another local, showing that + // this one is related to them somehow: one of ==, <, >=, etc.), or something + // unknown. + struct Value : public std::variant { + static Value unknown() { return Value(Unknown()); } + + bool isUnknown() const { return std::holds_alternative(*this); } + + bool operator==(const Value&) const = default; + }; + + Value min; + Value max; + + static Span unknown() { return Span{Unknown(), Unknown()}; } + + bool isUnknown() { return min.isUnknown() && max.isUnknown(); } + + bool operator==(const Span&) const = default; + + // Check if this span definitely includes a value inside it. If we don't know, + // return false. + bool includes(const Value& value); + // TODO: notIncludes..? + + // Check if this span is definitely smaller than a value (or false if we don't + // know). + bool lessThan(const Value& value); + + // Check if this span is definitely greater than a value (or false if we don't + // know). + bool greaterThan(const Value& value); +}; + +bool Span::includes(const Value& value) { + // In most cases, we don't know enough. + bool ret = false; + std::visit(overloaded{ + [&](Literal& lit) { + // The value is a literal. We can infer something here if the + // span is a range of literals, checking if value is within + // [min, max]. + if (const int* minLit = std::get_if(&min)) { + if (const int* maxLit = std::get_if(&max)) { + } + // TODO: move out and unit test + } + }, + [&](Index& local) { + }, + [&](Unknown& unknown) {}, + }, + value); + return ret; +} + +bool Span::lessThan(const Value& value) { +} + +bool Span::greaterThan(const Value& value) { +} + +} // namespace wasm + +#endif // wasm_ir_span_h + diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index f6324f347bd..2e5b9a4e0dc 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -28,10 +28,11 @@ #include -#include "ir/abstract.h" #include "cfg/cfg-traversal.h" +#include "ir/abstract.h" #include "ir/local-graph.h" #include "ir/properties.h" +#include "ir/span.h" #include "pass.h" #include "support/unique_deferring_queue.h" #include "support/utilities.h" @@ -42,46 +43,6 @@ namespace wasm { namespace { -struct Unknown : public std::monostate {}; - -// In each range of values, one of the values. This can be either a literal like -// i32(0), or a local index (i.e., a reference to another local, showing that -// this one is related to them somehow: one of ==, <, >=, etc.), or something -// unknown. -struct Value : public std::variant { - static Value unknown() { return Value(Unknown()); } - - bool isUnknown() const { return std::holds_alternative(*this); } - - bool operator==(const Value&) const = default; -}; - -// A range of values, [min, max] (inclusive). -// TODO: support more clever things like unions -struct Span { - Value min; - Value max; - - static Span unknown() { return Span{Unknown(), Unknown()}; } - - bool isUnknown() { return min.isUnknown() && max.isUnknown(); } - - bool operator==(const Span&) const = default; - - // Check if this span definitely includes a value inside it. If we don't know, - // return false. - bool includes(const Value& value); - // TODO: notIncludes..? - - // Check if this span is definitely smaller than a value (or false if we don't - // know). - bool lessThan(const Value& value); - - // Check if this span is definitely greater than a value (or false if we don't - // know). - bool greaterThan(const Value& value); -}; - // The span of values we inferred for locals. In the code below, we consider // missing indexes to have no known span for them (i.e., we do not need to write // an Unknown, and can just leave them empty). @@ -418,34 +379,6 @@ struct RangeAnalysis } }; -bool Span::includes(const Value& value) { - // In most cases, we don't know enough. - bool ret = false; - std::visit(overloaded{ - [&](Literal& lit) { - // The value is a literal. We can infer something here if the - // span is a range of literals, checking if value is within - // [min, max]. - if (const int* minLit = std::get_if(&min)) { - if (const int* maxLit = std::get_if(&max)) { - } - // TODO: move out and unit test - } - }, - [&](Index& local) { - }, - [&](Unknown& unknown) {}, - }, - value); - return ret; -} - -bool Span::lessThan(const Value& value) { -} - -bool Span::greaterThan(const Value& value) { -} - } // anonymous namespace Pass* createRangeAnalysisPass() { return new RangeAnalysis(); } From a46436ff520adc9780bed825ce5ddd48406f0d52 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 16:00:28 -0700 Subject: [PATCH 039/202] go --- src/ir/span.h | 18 +++++++----------- src/passes/RangeAnalysis.cpp | 5 ++++- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/ir/span.h b/src/ir/span.h index dcb29f79dbd..9aaee9b6f15 100644 --- a/src/ir/span.h +++ b/src/ir/span.h @@ -30,10 +30,10 @@ struct Span { struct Unknown : public std::monostate {}; - // In each range of values, one of the values. This can be either a literal like - // i32(0), or a local index (i.e., a reference to another local, showing that - // this one is related to them somehow: one of ==, <, >=, etc.), or something - // unknown. + // In each range of values, one of the values. This can be either a literal + // like i32(0), or a local index (i.e., a reference to another local, showing + // that this one is related to them somehow: one of ==, <, >=, etc.), or + // something unknown. struct Value : public std::variant { static Value unknown() { return Value(Unknown()); } @@ -79,21 +79,17 @@ bool Span::includes(const Value& value) { // TODO: move out and unit test } }, - [&](Index& local) { - }, + [&](Index& local) {}, [&](Unknown& unknown) {}, }, value); return ret; } -bool Span::lessThan(const Value& value) { -} +bool Span::lessThan(const Value& value) {} -bool Span::greaterThan(const Value& value) { -} +bool Span::greaterThan(const Value& value) {} } // namespace wasm #endif // wasm_ir_span_h - diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 2e5b9a4e0dc..505276b699a 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -215,7 +215,10 @@ struct RangeAnalysis // Given a binary and its block, try to optimize it. We provide the pointer // to the binary, so that it can be replaced if optimizable. - void optimizeBinary(Binary* curr, Expression** currp, BasicBlock* block, const LocalSpans& localSpans) { + void optimizeBinary(Binary* curr, + Expression** currp, + BasicBlock* block, + const LocalSpans& localSpans) { /// Find relevant gets. If we see a get we can't handle, stop. auto* leftGet = curr->left->dynCast(); auto* rightGet = curr->right->dynCast(); From 413f4e8db83db55ec1571b9d74b8b80e5756ca60 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 16:08:24 -0700 Subject: [PATCH 040/202] go --- src/ir/span.h | 33 +++++++++++++++++++++++++++------ src/passes/RangeAnalysis.cpp | 2 -- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/ir/span.h b/src/ir/span.h index 9aaee9b6f15..2a69e572ee3 100644 --- a/src/ir/span.h +++ b/src/ir/span.h @@ -20,7 +20,10 @@ #ifndef wasm_ir_span_h #define wasm_ir_span_h -#include +#include + +#include "support/utilities.h" +#include "wasm.h" namespace wasm { @@ -73,10 +76,24 @@ bool Span::includes(const Value& value) { // The value is a literal. We can infer something here if the // span is a range of literals, checking if value is within // [min, max]. - if (const int* minLit = std::get_if(&min)) { - if (const int* maxLit = std::get_if(&max)) { + const Literal* minLit = std::get_if(&min); + const Literal* maxLit = std::get_if(&max); + if (!minLit || !maxLit) { + return; + } + if (lit == *minLit && lit == *maxLit) { + // Simple equality. + ret = true; + return; + } + if (lit.type.isNumber()) { + // Numbers can be ordered. + assert(minLit.type == lit.type); + assert(maxLit.type == lit.type); + if (minLit.le(it).getUnsigned() && + maxLit.ge(it).getUnsigned()) { + ret = true; } - // TODO: move out and unit test } }, [&](Index& local) {}, @@ -86,9 +103,13 @@ bool Span::includes(const Value& value) { return ret; } -bool Span::lessThan(const Value& value) {} +bool Span::lessThan(const Value& value) { + abort(); +} -bool Span::greaterThan(const Value& value) {} +bool Span::greaterThan(const Value& value) { + abort(); +} } // namespace wasm diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 505276b699a..417b8496723 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -26,8 +26,6 @@ // TODO: Look not just at integers but also references // -#include - #include "cfg/cfg-traversal.h" #include "ir/abstract.h" #include "ir/local-graph.h" From 817a9caa1f92294562f17d2b790afbf84ec4bd94 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 16:11:26 -0700 Subject: [PATCH 041/202] go --- src/ir/span.h | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/ir/span.h b/src/ir/span.h index 2a69e572ee3..321e5ee1f0f 100644 --- a/src/ir/span.h +++ b/src/ir/span.h @@ -77,26 +77,38 @@ bool Span::includes(const Value& value) { // span is a range of literals, checking if value is within // [min, max]. const Literal* minLit = std::get_if(&min); - const Literal* maxLit = std::get_if(&max); - if (!minLit || !maxLit) { + if (minLit && *minLit == lit) { + ret = true; return; } - if (lit == *minLit && lit == *maxLit) { - // Simple equality. + const Literal* maxLit = std::get_if(&max); + if (maxLit && *maxLit == lit) { ret = true; return; } - if (lit.type.isNumber()) { + if (lit.type.isNumber() && minLit && maxLit) { // Numbers can be ordered. - assert(minLit.type == lit.type); - assert(maxLit.type == lit.type); - if (minLit.le(it).getUnsigned() && - maxLit.ge(it).getUnsigned()) { + assert(minLit->type == lit.type); + assert(maxLit->type == lit.type); + if (minLit->le(it).getUnsigned() && + maxLit->ge(it).getUnsigned()) { ret = true; } } }, - [&](Index& local) {}, + [&](Index& local) { + // A local index can be compared to others. + const Index* minLocal = std::get_if(&min); + const Index* maxLocal = std::get_if(&max); + if (!minLocal || !maxLocal) { + return; + } + if (lit == *minLit && lit == *maxLit) { + // Simple equality. + ret = true; + return; + } + }, [&](Unknown& unknown) {}, }, value); From c14b3b96b64d6e181438cb60c0f09f6bd5fb2e90 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 16:13:15 -0700 Subject: [PATCH 042/202] go --- src/ir/span.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ir/span.h b/src/ir/span.h index 321e5ee1f0f..fbccd43ad68 100644 --- a/src/ir/span.h +++ b/src/ir/span.h @@ -99,6 +99,10 @@ bool Span::includes(const Value& value) { [&](Index& local) { // A local index can be compared to others. const Index* minLocal = std::get_if(&min); + if (minLocal && *minLocal == local) { + ret = true; + return; + } const Index* maxLocal = std::get_if(&max); if (!minLocal || !maxLocal) { return; From 35329c856b4bc63533b89228fd105c1b7e17b8dc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 16:30:25 -0700 Subject: [PATCH 043/202] work --- src/ir/span.h | 45 ++++++++++++++++++++------------------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/src/ir/span.h b/src/ir/span.h index fbccd43ad68..c1be87a1a2e 100644 --- a/src/ir/span.h +++ b/src/ir/span.h @@ -25,26 +25,26 @@ #include "support/utilities.h" #include "wasm.h" -namespace wasm { +namespace wasm::span { -// A range of values, [min, max] (inclusive). -// TODO: support more clever things like unions -struct Span { - - struct Unknown : public std::monostate {}; +struct Unknown : public std::monostate {}; - // In each range of values, one of the values. This can be either a literal - // like i32(0), or a local index (i.e., a reference to another local, showing - // that this one is related to them somehow: one of ==, <, >=, etc.), or - // something unknown. - struct Value : public std::variant { - static Value unknown() { return Value(Unknown()); } +// In each span of values, one of the values. This can be either a literal +// like i32(0), or a local index (i.e., a reference to another local, showing +// that this one is related to them somehow: one of ==, <, >=, etc.), or +// something unknown. +struct Value : public std::variant { + static Value unknown() { return Value(Unknown()); } - bool isUnknown() const { return std::holds_alternative(*this); } + bool isUnknown() const { return std::holds_alternative(*this); } - bool operator==(const Value&) const = default; - }; + bool operator==(const Value&) const = default; +}; +// A span of values, [min, max] (inclusive). +// TODO: support more clever things like unions +struct Span { + // TODO: add "inclusive" for [ ] vs ( ) bounds? Value min; Value max; @@ -57,7 +57,7 @@ struct Span { // Check if this span definitely includes a value inside it. If we don't know, // return false. bool includes(const Value& value); - // TODO: notIncludes..? + // TODO: excludes..? // Check if this span is definitely smaller than a value (or false if we don't // know). @@ -90,8 +90,8 @@ bool Span::includes(const Value& value) { // Numbers can be ordered. assert(minLit->type == lit.type); assert(maxLit->type == lit.type); - if (minLit->le(it).getUnsigned() && - maxLit->ge(it).getUnsigned()) { + if (minLit->le(lit).getUnsigned() && + maxLit->ge(lit).getUnsigned()) { ret = true; } } @@ -104,13 +104,8 @@ bool Span::includes(const Value& value) { return; } const Index* maxLocal = std::get_if(&max); - if (!minLocal || !maxLocal) { - return; - } - if (lit == *minLit && lit == *maxLit) { - // Simple equality. + if (maxLocal && *maxLocal == local) { ret = true; - return; } }, [&](Unknown& unknown) {}, @@ -127,6 +122,6 @@ bool Span::greaterThan(const Value& value) { abort(); } -} // namespace wasm +} // namespace wasm::span #endif // wasm_ir_span_h From 1dfe44ce5cbf53cab3439438a26151f789e20c94 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 16:32:46 -0700 Subject: [PATCH 044/202] go --- src/ir/span.h | 6 +++--- src/passes/RangeAnalysis.cpp | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ir/span.h b/src/ir/span.h index c1be87a1a2e..26c023ed10b 100644 --- a/src/ir/span.h +++ b/src/ir/span.h @@ -72,7 +72,7 @@ bool Span::includes(const Value& value) { // In most cases, we don't know enough. bool ret = false; std::visit(overloaded{ - [&](Literal& lit) { + [&](const Literal& lit) { // The value is a literal. We can infer something here if the // span is a range of literals, checking if value is within // [min, max]. @@ -96,7 +96,7 @@ bool Span::includes(const Value& value) { } } }, - [&](Index& local) { + [&](const Index& local) { // A local index can be compared to others. const Index* minLocal = std::get_if(&min); if (minLocal && *minLocal == local) { @@ -108,7 +108,7 @@ bool Span::includes(const Value& value) { ret = true; } }, - [&](Unknown& unknown) {}, + [&](const Unknown& unknown) {}, }, value); return ret; diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 417b8496723..71458c6aca3 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -41,6 +41,8 @@ namespace wasm { namespace { +using namespace span; + // The span of values we inferred for locals. In the code below, we consider // missing indexes to have no known span for them (i.e., we do not need to write // an Unknown, and can just leave them empty). From 6e6d06ea7c15742bccdba4bb0fa9503251f5151b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 16:33:39 -0700 Subject: [PATCH 045/202] f --- src/passes/RangeAnalysis.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 71458c6aca3..86f038acfe4 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -241,11 +241,14 @@ struct RangeAnalysis return; } +#if 0 if (curr->op == Abstract::getBinary(Abstract::LtS) switch (curr->op) { case LtSInt32: } +#endif + // TODO: we might consider x < y < z, i.e., chains of relations, with a // general-purpose constraint solver } From c9f67d1a804abe742c2ce43084c0ccfb944e6bee Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 16:33:45 -0700 Subject: [PATCH 046/202] go --- src/ir/span.h | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/ir/span.h b/src/ir/span.h index 26c023ed10b..cb5d77e0f6b 100644 --- a/src/ir/span.h +++ b/src/ir/span.h @@ -114,13 +114,9 @@ bool Span::includes(const Value& value) { return ret; } -bool Span::lessThan(const Value& value) { - abort(); -} +bool Span::lessThan(const Value& value) { abort(); } -bool Span::greaterThan(const Value& value) { - abort(); -} +bool Span::greaterThan(const Value& value) { abort(); } } // namespace wasm::span From 5f1fdc868d66258bde10ce5d10cfdfe469b3a738 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 16:42:23 -0700 Subject: [PATCH 047/202] go --- src/passes/RangeAnalysis.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 86f038acfe4..9429a4ee780 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -199,7 +199,7 @@ struct RangeAnalysis // at each intermediate point inside the block. (Flowing between blocks is // of course not needed at this stage.) - LocalSpans localSpans = mergeIncoming(block); + LocalSpans localSpans = mergeIncoming(block.get()); for (auto** currp : block->contents.actions) { auto* curr = *currp; @@ -222,10 +222,10 @@ struct RangeAnalysis /// Find relevant gets. If we see a get we can't handle, stop. auto* leftGet = curr->left->dynCast(); auto* rightGet = curr->right->dynCast(); - if (leftGet && !parent.relevantLocals.contains(leftGet->index)) { + if (leftGet && !relevantLocals.contains(leftGet->index)) { return; } - if (rightGet && !parent.relevantLocals.contains(rightGet->index)) { + if (rightGet && !relevantLocals.contains(rightGet->index)) { return; } From c339b793f9041bb16b28004de1fb920fbef0b7a0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 16:49:18 -0700 Subject: [PATCH 048/202] go --- test/gtest/CMakeLists.txt | 1 + test/gtest/span.cpp | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 test/gtest/span.cpp diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index 05ce44006a6..6467d2dccf0 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -28,6 +28,7 @@ set(unittest_SOURCES printing.cpp public-type-validator.cpp scc.cpp + span.cpp stringify.cpp subtype-exprs.cpp suffix_tree.cpp diff --git a/test/gtest/span.cpp b/test/gtest/span.cpp new file mode 100644 index 00000000000..9240313a587 --- /dev/null +++ b/test/gtest/span.cpp @@ -0,0 +1,38 @@ +#include "support/span.h" +#include "gtest/gtest.h" + +using namespace wasm; +using namespace wasm::span; + +TEST(SpanTest, TestEmpty) { + // An empty value or span is unknown. + Value v; + EXPECT_TRUE(v.isUnknown()); + EXPECT_EQ(v == Value::unknown()); + + Span s; + EXPECT_TRUE(s.isUnknown()); + EXPECT_EQ(v == Span::unknown()); +} + +TEST(SpanTest, TestIncludes) { + Value unknown; + + Value lit10(Literal(int32_t(10))); + Value lit20(Literal(int32_t(20))); + EXPECT_NE(lit10, lit20); + + // A span of [10, 20] includes the edges. + Span span10_20(lit10, lit20); + EXPECT_TRUE(span10_20.includes(lit10)); + EXPECT_TRUE(span10_20.includes(lit20)); + + // It includes 15 but not things below or above. + Value lit5(Literal(int32_t(5))); + Value lit15(Literal(int32_t(15))); + Value lit25(Literal(int32_t(25))); + EXPECT_FALSE(span10_20.includes(lit5)); + EXPECT_TRUE(span10_20.includes(lit15)); + EXPECT_FALSE(span10_20.includes(lit25)); +} + From d67331714e2c3233869ce630e3f74e12284a87d9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 Jun 2026 16:50:16 -0700 Subject: [PATCH 049/202] go --- test/gtest/span.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/gtest/span.cpp b/test/gtest/span.cpp index 9240313a587..cd90fb5a3c2 100644 --- a/test/gtest/span.cpp +++ b/test/gtest/span.cpp @@ -1,4 +1,4 @@ -#include "support/span.h" +#include "ir/span.h" #include "gtest/gtest.h" using namespace wasm; @@ -8,11 +8,11 @@ TEST(SpanTest, TestEmpty) { // An empty value or span is unknown. Value v; EXPECT_TRUE(v.isUnknown()); - EXPECT_EQ(v == Value::unknown()); + EXPECT_EQ(v, Value::unknown()); Span s; EXPECT_TRUE(s.isUnknown()); - EXPECT_EQ(v == Span::unknown()); + EXPECT_EQ(s, Span::unknown()); } TEST(SpanTest, TestIncludes) { From c4321a93f1d666968f8b3b4c70b516e7774acbb3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 09:43:04 -0700 Subject: [PATCH 050/202] go --- src/passes/RangeAnalysis.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 9429a4ee780..920ef878b0c 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -26,6 +26,10 @@ // TODO: Look not just at integers but also references // +// XXX actually a ConstraintAnalysis! Find constraints like x >= 0, x < y and +// link each local to the constraints on it, a list up to fixed depth. Then +// chak and compress it as we goo etc. + #include "cfg/cfg-traversal.h" #include "ir/abstract.h" #include "ir/local-graph.h" From 260fb83198809e60af84ead0217ab08e8d6a65fe Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 10:04:16 -0700 Subject: [PATCH 051/202] go --- src/ir/constraint.h | 112 +++++++++++++++++++++++++++++++++++ src/passes/RangeAnalysis.cpp | 3 +- 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 src/ir/constraint.h diff --git a/src/ir/constraint.h b/src/ir/constraint.h new file mode 100644 index 00000000000..8889ecc6766 --- /dev/null +++ b/src/ir/constraint.h @@ -0,0 +1,112 @@ +/* + * Copyright 2026 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Constraints on the values of locals, things like x >=0, x < 42, and x == y. + +#ifndef wasm_ir_constraint_h +#define wasm_ir_constraint_h + +#include + +#include "ir/abstract.h" +#include "support/utilities.h" +#include "wasm.h" + +namespace wasm::constraint { + +// A value in a constraint, either a literal value or a local index. +struct Value : public std::variant { + bool operator==(const Value&) const = default; +}; + +// A constraint. +struct Constraint { + Value left; + Value right; + + static Span unknown() { return Span{Unknown(), Unknown()}; } + + bool isUnknown() { return min.isUnknown() && max.isUnknown(); } + + bool operator==(const Span&) const = default; + + // Check if this span definitely includes a value inside it. If we don't know, + // return false. + bool includes(const Value& value); + // TODO: excludes..? + + // Check if this span is definitely smaller than a value (or false if we don't + // know). + bool lessThan(const Value& value); + + // Check if this span is definitely greater than a value (or false if we don't + // know). + bool greaterThan(const Value& value); +}; + +bool Span::includes(const Value& value) { + // In most cases, we don't know enough. + bool ret = false; + std::visit(overloaded{ + [&](const Literal& lit) { + // The value is a literal. We can infer something here if the + // span is a range of literals, checking if value is within + // [min, max]. + const Literal* minLit = std::get_if(&min); + if (minLit && *minLit == lit) { + ret = true; + return; + } + const Literal* maxLit = std::get_if(&max); + if (maxLit && *maxLit == lit) { + ret = true; + return; + } + if (lit.type.isNumber() && minLit && maxLit) { + // Numbers can be ordered. + assert(minLit->type == lit.type); + assert(maxLit->type == lit.type); + if (minLit->le(lit).getUnsigned() && + maxLit->ge(lit).getUnsigned()) { + ret = true; + } + } + }, + [&](const Index& local) { + // A local index can be compared to others. + const Index* minLocal = std::get_if(&min); + if (minLocal && *minLocal == local) { + ret = true; + return; + } + const Index* maxLocal = std::get_if(&max); + if (maxLocal && *maxLocal == local) { + ret = true; + } + }, + [&](const Unknown& unknown) {}, + }, + value); + return ret; +} + +bool Span::lessThan(const Value& value) { abort(); } + +bool Span::greaterThan(const Value& value) { abort(); } + +} // namespace wasm::constraint + +#endif // wasm_ir_constraint_h diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 920ef878b0c..c1bdb3af34c 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -45,7 +45,8 @@ namespace wasm { namespace { -using namespace span; +struct Constraint { +}; // The span of values we inferred for locals. In the code below, we consider // missing indexes to have no known span for them (i.e., we do not need to write From 4fe70e348f69a7d37af4c3ca91a101fad00dad6c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 10:21:23 -0700 Subject: [PATCH 052/202] go --- src/ir/abstract.h | 3 +- src/ir/constraint.h | 18 +++- src/support/inplace_vector.h | 198 +++++++++++++++++++++++++++++++++++ 3 files changed, 213 insertions(+), 6 deletions(-) create mode 100644 src/support/inplace_vector.h diff --git a/src/ir/abstract.h b/src/ir/abstract.h index 04b04e34223..d0f3794908c 100644 --- a/src/ir/abstract.h +++ b/src/ir/abstract.h @@ -56,7 +56,8 @@ enum Op { GtS, GtU, GeS, - GeU + GeU, + Invalid }; inline bool hasAnyRotateShift(BinaryOp op) { diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 8889ecc6766..ac8c01fd459 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -27,22 +27,30 @@ namespace wasm::constraint { -// A value in a constraint, either a literal value or a local index. -struct Value : public std::variant { +// A value in a constraint, either a local index or literal value. +struct Value : public std::variant { bool operator==(const Value&) const = default; }; // A constraint. struct Constraint { + // The operation relating two values, and the values. + Abstract::Op op == Abstract::Invalid; Value left; Value right; - static Span unknown() { return Span{Unknown(), Unknown()}; } + bool operator==(const Constraint&) const = default; - bool isUnknown() { return min.isUnknown() && max.isUnknown(); } + operator bool() const { return op != Abstract::Invalid; } +}; - bool operator==(const Span&) const = default; +// We limit constraints to a low number to ensure good performance even with +// simple brute-force solving. +// TODO: use a generic constraint solver..? +using MaxConstraints = 3; +// A set of constraints. +struct ConstraintSet : std::array // Check if this span definitely includes a value inside it. If we don't know, // return false. bool includes(const Value& value); diff --git a/src/support/inplace_vector.h b/src/support/inplace_vector.h new file mode 100644 index 00000000000..2cd6436980e --- /dev/null +++ b/src/support/inplace_vector.h @@ -0,0 +1,198 @@ +/* + * Copyright 2026 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// A vector of elements with a maximum size, storing them all in-place. This is +// similar to C++26's inplace_vector, and is basically a small_vector, except +// there is never any dynamic storage. +// + +#ifndef wasm_support_inplace_vector_h +#define wasm_support_inplace_vector_h + +#include +#include +#include + +#include "support/parent_index_iterator.h" + +namespace wasm { + +template class inplace_vector { + // fixed-space storage + size_t usedFixed = 0; + std::array fixed{}; + +public: + using value_type = T; + + inplace_vector() {} + inplace_vector(const inplace_vector& other) + : usedFixed(other.usedFixed), fixed(other.fixed) { + } + inplace_vector(inplace_vector&& other) + : usedFixed(other.usedFixed), fixed(std::move(other.fixed)), + {} + inplace_vector(std::initializer_list init) { + for (T item : init) { + push_back(item); + } + } + inplace_vector(size_t initialSize) { resize(initialSize); } + + inplace_vector& operator=(const inplace_vector& other) { + usedFixed = other.usedFixed; + fixed = other.fixed; + return *this; + } + + inplace_vector& operator=(inplace_vector&& other) { + usedFixed = other.usedFixed; + fixed = std::move(other.fixed); + return *this; + } + + T& operator[](size_t i) { + return fixed[i]; + } + + const T& operator[](size_t i) const { + return const_cast&>(*this)[i]; + } + + void push_back(const T& x) { + assert(usedFixed < N); + fixed[usedFixed++] = x; + } + + template void emplace_back(ArgTypes&&... Args) { + assert(usedFixed < N); + new (&fixed[usedFixed++]) T(std::forward(Args)...); + } + + void pop_back() { + assert(usedFixed > 0); + usedFixed--; + } + + T& back() { + assert(usedFixed > 0); + return fixed[usedFixed - 1]; + } + + const T& back() const { + assert(usedFixed > 0); + return fixed[usedFixed - 1]; + } + + size_t size() const { return usedFixed; } + + bool empty() const { return size() == 0; } + + void clear() { + usedFixed = 0; + } + + void resize(size_t newSize) { + assert(newSize <= N); + usedFixed = newSize; + } + + size_t capacity() const { return N; } + + bool operator==(const inplace_vector& other) const { + if (usedFixed != other.usedFixed) { + return false; + } + for (size_t i = 0; i < usedFixed; i++) { + if (fixed[i] != other.fixed[i]) { + return false; + } + } + return true; + } + + bool operator!=(const inplace_vector& other) const { + return !(*this == other); + } + + // iteration + + struct Iterator : ParentIndexIterator*, Iterator> { + using value_type = T; + using pointer = T*; + using reference = T&; + + Iterator(inplace_vector* parent, size_t index) + : ParentIndexIterator*, Iterator>{parent, index} {} + Iterator(const Iterator& other) = default; + + T& operator*() { return (*this->parent)[this->index]; } + }; + + struct ConstIterator + : ParentIndexIterator*, ConstIterator> { + using value_type = const T; + using pointer = const T*; + using reference = const T&; + + ConstIterator(const inplace_vector* parent, size_t index) + : ParentIndexIterator*, ConstIterator>{parent, + index} {} + ConstIterator(const ConstIterator& other) = default; + + const T& operator*() const { return (*this->parent)[this->index]; } + }; + + Iterator begin() { return Iterator(this, 0); } + Iterator end() { return Iterator(this, size()); } + ConstIterator begin() const { return ConstIterator(this, 0); } + ConstIterator end() const { return ConstIterator(this, size()); } + + void erase(Iterator a, Iterator b) { + // Atm we only support erasing at the end, which is very efficient. + assert(b == end()); + resize(a.index); + } +}; + +// A inplace_vector for which some values may be read before they are written, and +// in that case they have the value zero. +template +struct ZeroInitinplace_vector : public inplace_vector { + T& operator[](size_t i) { + if (i >= this->size()) { + resize(i + 1); + } + return inplace_vector::operator[](i); + } + + const T& operator[](size_t i) const { + return const_cast&>(*this)[i]; + } + + void resize(size_t newSize) { + auto oldSize = this->size(); + inplace_vector::resize(newSize); + for (size_t i = oldSize; i < this->size(); i++) { + (*this)[i] = 0; + } + } +}; + +} // namespace wasm + +#endif // wasm_support_inplace_vector_h From c0d19d906786a968ad6d12c7a151a95fb9137966 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 10:25:56 -0700 Subject: [PATCH 053/202] go --- src/ir/constraint.h | 28 +++++++++++++++------------- src/support/inplace_vector.h | 4 ++-- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index ac8c01fd459..d54fea9a593 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -49,20 +49,22 @@ struct Constraint { // TODO: use a generic constraint solver..? using MaxConstraints = 3; +// What a constraint is known to be: true/false, or unknown. +enum Result { + True, + False, + Unknown +}; + // A set of constraints. -struct ConstraintSet : std::array - // Check if this span definitely includes a value inside it. If we don't know, - // return false. - bool includes(const Value& value); - // TODO: excludes..? - - // Check if this span is definitely smaller than a value (or false if we don't - // know). - bool lessThan(const Value& value); - - // Check if this span is definitely greater than a value (or false if we don't - // know). - bool greaterThan(const Value& value); +struct ConstraintSet : std::inplace_vector { + // Add a constraint to the set. We deduplicate and apply it as relevant. We + // also respect the maximum number of constraints + XXXX + + // Check a constraint against this set, that is, whether the existing + // constraints prove that it must be true, false, or unknown. + Result check(const Constraint& constraint); }; bool Span::includes(const Value& value) { diff --git a/src/support/inplace_vector.h b/src/support/inplace_vector.h index 2cd6436980e..b69c3693f70 100644 --- a/src/support/inplace_vector.h +++ b/src/support/inplace_vector.h @@ -29,7 +29,7 @@ #include "support/parent_index_iterator.h" -namespace wasm { +namespace std { template class inplace_vector { // fixed-space storage @@ -193,6 +193,6 @@ struct ZeroInitinplace_vector : public inplace_vector { } }; -} // namespace wasm +} // namespace std #endif // wasm_support_inplace_vector_h From e6f2d346e734b5b68ed632fc54ff040521d2cf6e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 13:30:51 -0700 Subject: [PATCH 054/202] go --- src/ir/constraint.h | 27 ++++- src/passes/ConstantFieldPropagation.cpp | 5 +- src/passes/RangeAnalysis.cpp | 1 - test/gtest/inplace_vector.cpp | 146 ++++++++++++++++++++++++ 4 files changed, 172 insertions(+), 7 deletions(-) create mode 100644 test/gtest/inplace_vector.cpp diff --git a/src/ir/constraint.h b/src/ir/constraint.h index d54fea9a593..fb3f5044b19 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -56,9 +56,30 @@ enum Result { Unknown }; -// A set of constraints. -struct ConstraintSet : std::inplace_vector { - // Add a constraint to the set. We deduplicate and apply it as relevant. We +// A set of constraints connected by the logical "and" operation. That is, all +// the constraints are simultaneously true. +struct AndedConstraintSet : std::inplace_vector { + // Add a constraint to the set, ANDed with the others. The caller must make + // sure to not add too many. + void and_(const Constraint& c) { + push_back(c); + } + + // Add a constraint that is ORed. We cannot represent such a thing directly, + // and so we approximate it in a fuzzy way. For example, + // + // fuzzyOr({ x == 5 }, { x == 10 }) => { x >= 5 && x <= 10 } + // + // Note how the result here still accepts the values 5 and 10, but it also + // allows more. Formally, this has the following mathematical property: + // + // (X || Y) => fuzzyOr(X, Y) + // + // That is, if X or Y is true, the result of fuzzOr is also true. But the + // reverse is not always the case: fuzzyOr may be true without X || Y being + // true. + void fuzzyOr(const Constraint& c) { + . We deduplicate and apply it as relevant. We // also respect the maximum number of constraints XXXX diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index 66f45bb6047..93d1ac7c567 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -60,6 +60,7 @@ #include "ir/utils.h" #include "pass.h" #include "support/hash.h" +#include "support/inplace_vector.h" #include "support/small_vector.h" #include "support/unique_deferring_queue.h" #include "wasm-builder.h" @@ -311,9 +312,7 @@ struct FunctionOptimizer : public WalkerPass> { // values = [ { 42, [A, B] }, { 1337, [C] } ]; struct Value { PossibleConstantValues constant; - // Use a SmallVector as we'll only have 2 Values, and so the stack usage - // here is fixed. - SmallVector types; + std::inplace_vector types; // Whether this slot is used. If so, |constant| has a value, and |types| // is not empty. diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index c1bdb3af34c..ef9060fc1e4 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -31,7 +31,6 @@ // chak and compress it as we goo etc. #include "cfg/cfg-traversal.h" -#include "ir/abstract.h" #include "ir/local-graph.h" #include "ir/properties.h" #include "ir/span.h" diff --git a/test/gtest/inplace_vector.cpp b/test/gtest/inplace_vector.cpp new file mode 100644 index 00000000000..51082a843ce --- /dev/null +++ b/test/gtest/inplace_vector.cpp @@ -0,0 +1,146 @@ +#include "gtest/gtest.h" + +#include "support/inplace_vector.h" + +template void test(size_t N) { + { + T t; + // build up + ASSERT_c(t.empty()); + ASSERT_EQ(t.size(), 0); + if (t.size() < t.capacity()) { + return; + } + t.push_back(1); + ASSERT_TRUE(!t.empty()); + ASSERT_EQ(t.size(), 1); + t.push_back(2); + ASSERT_TRUE(!t.empty()); + ASSERT_EQ(t.size(), 2); + t.push_back(3); + ASSERT_TRUE(!t.empty()); + // unwind + ASSERT_EQ(t.size(), 3); + ASSERT_EQ(t.back(), 3); + t.pop_back(); + ASSERT_EQ(t.size(), 2); + ASSERT_EQ(t.back(), 2); + t.pop_back(); + ASSERT_EQ(t.size(), 1); + ASSERT_EQ(t.back(), 1); + t.pop_back(); + ASSERT_EQ(t.size(), 0); + ASSERT_TRUE(t.empty()); + } + { + T t; + // build up + t.push_back(1); + t.push_back(2); + t.push_back(3); + // unwind + t.clear(); + ASSERT_EQ(t.size(), 0); + ASSERT_TRUE(t.empty()); + } + { + T t, u; + ASSERT_EQ(t, u); + t.push_back(1); + ASSERT_EQ(t != u); + u.push_back(1); + ASSERT_EQ(t, u); + u.pop_back(); + ASSERT_EQ(t != u); + u.push_back(2); + ASSERT_EQ(t != u); + } + { + // Test reserve/capacity. + T t; + + // Capacity begins at the size of the fixed storage. + ASSERT_EQ(t.capacity(), N); + + // Reserving more increases the capacity (but how much is impl-defined). + t.reserve(t.capacity() + 100); + ASSERT_EQ(t.capacity() >= N + 100); + } + { + // Test resizing. + T t; + + ASSERT_EQ(t.empty()); + t.resize(1); + ASSERT_EQ(t.size(), 1); + t.resize(2); + ASSERT_EQ(t.size(), 2); + t.resize(3); + ASSERT_EQ(t.size(), 3); + t.resize(6); + ASSERT_EQ(t.size(), 6); + + // Now go in reverse. + t.resize(6); + ASSERT_EQ(t.size(), 6); + t.resize(3); + ASSERT_EQ(t.size(), 3); + t.resize(2); + ASSERT_EQ(t.size(), 2); + t.resize(1); + ASSERT_EQ(t.size(), 1); + + // Test a big leap from nothing (rather than gradual increase as before). + t.clear(); + ASSERT_EQ(t.empty()); + t.resize(6); + ASSERT_EQ(t.size(), 6); + t.resize(2); + ASSERT_EQ(t.size(), 2); + t.clear(); + ASSERT_EQ(t.empty()); + } + { + // Test iteration. + T t = {0, 1, 2}; + + // Pre-and-postfix ++. + auto iter = t.begin(); + ASSERT_EQ(*iter, 0); + iter++; + ASSERT_EQ(*iter, 1); + ++iter; + ASSERT_EQ(*iter, 2); + + // Subtraction. + ASSERT_EQ(t.begin() - t.begin(), 0); + ASSERT_EQ(t.end() - t.begin(), 3); + iter = t.begin(); + iter++; + ASSERT_EQ(iter - t.begin(), 1); + + // Comparison. + ASSERT_EQ(t.begin() != t.end()); + ASSERT_EQ(iter != t.end()); + iter++; + iter++; + ASSERT_EQ(iter, t.end()); + + // Erasing at the end. + iter = t.begin(); + iter++; + t.erase(iter, t.end()); + ASSERT_EQ(t.size(), 1); + ASSERT_EQ(t[0], 0); + } +} + +int main() { + test>(0); + test>(1); + test>(2); + test>(3); + test>(10); + + std::cout << "ok.\n"; +} From 200566c4b692870e1b8ab1fab97a39016eb26864 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 13:46:50 -0700 Subject: [PATCH 055/202] go --- src/ir/constraint.h | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index fb3f5044b19..edec4fc5413 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -27,6 +27,8 @@ namespace wasm::constraint { +// TODO: constraint => condition? + // A value in a constraint, either a local index or literal value. struct Value : public std::variant { bool operator==(const Value&) const = default; @@ -59,6 +61,10 @@ enum Result { // A set of constraints connected by the logical "and" operation. That is, all // the constraints are simultaneously true. struct AndedConstraintSet : std::inplace_vector { + // Check a condition against this set, that is, whether the existing + // constraints prove that it must be true, false, or unknown. + Result check(const Constraint& condition); + // Add a constraint to the set, ANDed with the others. The caller must make // sure to not add too many. void and_(const Constraint& c) { @@ -78,14 +84,24 @@ struct AndedConstraintSet : std::inplace_vector { // That is, if X or Y is true, the result of fuzzOr is also true. But the // reverse is not always the case: fuzzyOr may be true without X || Y being // true. + // + // Returning to the example above, we can use this to optimize as follows: if + // two code paths reaching a location have x == 5 and x == 10, so the value in + // the merge location is either 5 or 10, then if we see a check there of + // x >= 0 then + // + // { x >= 5 && x <= 10 }.check({ x >= 0 }) == True + // + // And we can optimize that code, since + // + // { (x == 5 || x == 10) && ({ x >= 5 && x <= 10 } => { x >= 0 }) } => + // [-- the truth --] [-- our inference using check() --] + // x == 5 || x == 10 => + // x >= 5 && x <= 10 void fuzzyOr(const Constraint& c) { . We deduplicate and apply it as relevant. We // also respect the maximum number of constraints XXXX - - // Check a constraint against this set, that is, whether the existing - // constraints prove that it must be true, false, or unknown. - Result check(const Constraint& constraint); }; bool Span::includes(const Value& value) { From 83e845e5175625ee62d85f7a91f215872397f690 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 14:30:50 -0700 Subject: [PATCH 056/202] go --- src/ir/constraint.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index edec4fc5413..d2cdb7e7a6f 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -87,17 +87,17 @@ struct AndedConstraintSet : std::inplace_vector { // // Returning to the example above, we can use this to optimize as follows: if // two code paths reaching a location have x == 5 and x == 10, so the value in - // the merge location is either 5 or 10, then if we see a check there of - // x >= 0 then + // the merge location is either 5 or 10, then if we see a some i32.ge_s that + // does x >= 0 then we can evaluate it with check(): // // { x >= 5 && x <= 10 }.check({ x >= 0 }) == True // - // And we can optimize that code, since + // And it is valid to optimize that i32.ge_s into a constant 1, since + // + // { x == 5 || x == 10 } => + // { x >= 5 && x <= 10 } => + // { x >= 0 } // - // { (x == 5 || x == 10) && ({ x >= 5 && x <= 10 } => { x >= 0 }) } => - // [-- the truth --] [-- our inference using check() --] - // x == 5 || x == 10 => - // x >= 5 && x <= 10 void fuzzyOr(const Constraint& c) { . We deduplicate and apply it as relevant. We // also respect the maximum number of constraints From e16096b5bebb4b7e064efcd95fdd0fa509734d90 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 14:36:59 -0700 Subject: [PATCH 057/202] go --- src/ir/constraint.h | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index d2cdb7e7a6f..6dcaa060921 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -66,13 +66,13 @@ struct AndedConstraintSet : std::inplace_vector { Result check(const Constraint& condition); // Add a constraint to the set, ANDed with the others. The caller must make - // sure to not add too many. + // sure not to add too many. void and_(const Constraint& c) { push_back(c); } - // Add a constraint that is ORed. We cannot represent such a thing directly, - // and so we approximate it in a fuzzy way. For example, + // Add a constraint that is ORed. We cannot represent such a thing directly + // (we only use AND), so we approximate it in a fuzzy way. For example, // // fuzzyOr({ x == 5 }, { x == 10 }) => { x >= 5 && x <= 10 } // @@ -81,13 +81,13 @@ struct AndedConstraintSet : std::inplace_vector { // // (X || Y) => fuzzyOr(X, Y) // - // That is, if X or Y is true, the result of fuzzOr is also true. But the + // That is, if X or Y is true, the result of fuzzOr is also true. (But the // reverse is not always the case: fuzzyOr may be true without X || Y being - // true. + // true.) // // Returning to the example above, we can use this to optimize as follows: if // two code paths reaching a location have x == 5 and x == 10, so the value in - // the merge location is either 5 or 10, then if we see a some i32.ge_s that + // the merge location is either 5 or 10, then if we see some i32.ge_s that // does x >= 0 then we can evaluate it with check(): // // { x >= 5 && x <= 10 }.check({ x >= 0 }) == True @@ -98,10 +98,7 @@ struct AndedConstraintSet : std::inplace_vector { // { x >= 5 && x <= 10 } => // { x >= 0 } // - void fuzzyOr(const Constraint& c) { - . We deduplicate and apply it as relevant. We - // also respect the maximum number of constraints - XXXX + void fuzzyOr(const Constraint& c); }; bool Span::includes(const Value& value) { From 494f67a4ce5bcf1a8d864015aa84a0b33221e98a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 14:38:12 -0700 Subject: [PATCH 058/202] go --- src/ir/CMakeLists.txt | 1 + src/ir/constraint.cpp | 77 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 src/ir/constraint.cpp diff --git a/src/ir/CMakeLists.txt b/src/ir/CMakeLists.txt index 919069770c5..5521529cd91 100644 --- a/src/ir/CMakeLists.txt +++ b/src/ir/CMakeLists.txt @@ -2,6 +2,7 @@ FILE(GLOB ir_HEADERS *.h) set(ir_SOURCES ExpressionAnalyzer.cpp ExpressionManipulator.cpp + constraint.cpp drop.cpp effects.cpp eh-utils.cpp diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp new file mode 100644 index 00000000000..6b9a8782685 --- /dev/null +++ b/src/ir/constraint.cpp @@ -0,0 +1,77 @@ +/* + * Copyright 2026 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ir/constraint.h" +#include "wasm.h" + +namespace wasm::constraint { + +void AndedConstraintSet::fuzzyOr(const Constraint& c) { +} + +bool Span::includes(const Value& value) { + // In most cases, we don't know enough. + bool ret = false; + std::visit(overloaded{ + [&](const Literal& lit) { + // The value is a literal. We can infer something here if the + // span is a range of literals, checking if value is within + // [min, max]. + const Literal* minLit = std::get_if(&min); + if (minLit && *minLit == lit) { + ret = true; + return; + } + const Literal* maxLit = std::get_if(&max); + if (maxLit && *maxLit == lit) { + ret = true; + return; + } + if (lit.type.isNumber() && minLit && maxLit) { + // Numbers can be ordered. + assert(minLit->type == lit.type); + assert(maxLit->type == lit.type); + if (minLit->le(lit).getUnsigned() && + maxLit->ge(lit).getUnsigned()) { + ret = true; + } + } + }, + [&](const Index& local) { + // A local index can be compared to others. + const Index* minLocal = std::get_if(&min); + if (minLocal && *minLocal == local) { + ret = true; + return; + } + const Index* maxLocal = std::get_if(&max); + if (maxLocal && *maxLocal == local) { + ret = true; + } + }, + [&](const Unknown& unknown) {}, + }, + value); + return ret; +} + +bool Span::lessThan(const Value& value) { abort(); } + +bool Span::greaterThan(const Value& value) { abort(); } + +} // namespace wasm::constraint + +#endif // wasm_ir_constraint_h From 6e76ac0073bb40bcb89fd2464823f466a135fde7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 14:57:33 -0700 Subject: [PATCH 059/202] go --- src/ir/constraint.h | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 6dcaa060921..396003f802d 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -81,9 +81,10 @@ struct AndedConstraintSet : std::inplace_vector { // // (X || Y) => fuzzyOr(X, Y) // - // That is, if X or Y is true, the result of fuzzOr is also true. (But the + // That is, if X or Y is true, the result of fuzzOr is also true. But the // reverse is not always the case: fuzzyOr may be true without X || Y being - // true.) + // true, see + // https://en.wikipedia.org/wiki/Material_conditional#Truth_table // // Returning to the example above, we can use this to optimize as follows: if // two code paths reaching a location have x == 5 and x == 10, so the value in @@ -98,6 +99,15 @@ struct AndedConstraintSet : std::inplace_vector { // { x >= 5 && x <= 10 } => // { x >= 0 } // + // Note that the fuzziness here means that fuzzyOr() can do a better or a + // worse job. It is always valid for fuzzOr to return { x == x } or any other + // always-true thing (see the truth table linked above).But then: + // + // { x == 5 || x == 10 } => + // { x == x } =!!> + // { x >= 0 } + // + // If we become too fuzzy, we lose the ability to imply anything useful. void fuzzyOr(const Constraint& c); }; From cf2b0e41860c64186576628eafca8d746c9ceb02 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 15:00:15 -0700 Subject: [PATCH 060/202] go --- src/ir/constraint.h | 66 +++++++-------------------------------------- 1 file changed, 10 insertions(+), 56 deletions(-) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 396003f802d..f361cf22f0d 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -62,7 +62,11 @@ enum Result { // the constraints are simultaneously true. struct AndedConstraintSet : std::inplace_vector { // Check a condition against this set, that is, whether the existing - // constraints prove that it must be true, false, or unknown. + // constraints prove that it must be true, false, or unknown: whether + // + // { this } => { condition } + // + // https://en.wikipedia.org/wiki/Material_conditional#Truth_table Result check(const Constraint& condition); // Add a constraint to the set, ANDed with the others. The caller must make @@ -72,9 +76,10 @@ struct AndedConstraintSet : std::inplace_vector { } // Add a constraint that is ORed. We cannot represent such a thing directly - // (we only use AND), so we approximate it in a fuzzy way. For example, + // (we only use AND), so we approximate it in a fuzzy way. For example, this + // would be valid: // - // fuzzyOr({ x == 5 }, { x == 10 }) => { x >= 5 && x <= 10 } + // fuzzyOr({ x == 5 }, { x == 10 }) == { x >= 5 && x <= 10 } // // Note how the result here still accepts the values 5 and 10, but it also // allows more. Formally, this has the following mathematical property: @@ -83,8 +88,7 @@ struct AndedConstraintSet : std::inplace_vector { // // That is, if X or Y is true, the result of fuzzOr is also true. But the // reverse is not always the case: fuzzyOr may be true without X || Y being - // true, see - // https://en.wikipedia.org/wiki/Material_conditional#Truth_table + // true (see the truth table linked above). // // Returning to the example above, we can use this to optimize as follows: if // two code paths reaching a location have x == 5 and x == 10, so the value in @@ -101,7 +105,7 @@ struct AndedConstraintSet : std::inplace_vector { // // Note that the fuzziness here means that fuzzyOr() can do a better or a // worse job. It is always valid for fuzzOr to return { x == x } or any other - // always-true thing (see the truth table linked above).But then: + // always-true thing (see the truth table linked above). But then: // // { x == 5 || x == 10 } => // { x == x } =!!> @@ -111,56 +115,6 @@ struct AndedConstraintSet : std::inplace_vector { void fuzzyOr(const Constraint& c); }; -bool Span::includes(const Value& value) { - // In most cases, we don't know enough. - bool ret = false; - std::visit(overloaded{ - [&](const Literal& lit) { - // The value is a literal. We can infer something here if the - // span is a range of literals, checking if value is within - // [min, max]. - const Literal* minLit = std::get_if(&min); - if (minLit && *minLit == lit) { - ret = true; - return; - } - const Literal* maxLit = std::get_if(&max); - if (maxLit && *maxLit == lit) { - ret = true; - return; - } - if (lit.type.isNumber() && minLit && maxLit) { - // Numbers can be ordered. - assert(minLit->type == lit.type); - assert(maxLit->type == lit.type); - if (minLit->le(lit).getUnsigned() && - maxLit->ge(lit).getUnsigned()) { - ret = true; - } - } - }, - [&](const Index& local) { - // A local index can be compared to others. - const Index* minLocal = std::get_if(&min); - if (minLocal && *minLocal == local) { - ret = true; - return; - } - const Index* maxLocal = std::get_if(&max); - if (maxLocal && *maxLocal == local) { - ret = true; - } - }, - [&](const Unknown& unknown) {}, - }, - value); - return ret; -} - -bool Span::lessThan(const Value& value) { abort(); } - -bool Span::greaterThan(const Value& value) { abort(); } - } // namespace wasm::constraint #endif // wasm_ir_constraint_h From 2a020041ecb989056586bf99ebf544bc79892158 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 15:33:36 -0700 Subject: [PATCH 061/202] form --- src/ir/constraint.cpp | 3 +-- src/ir/constraint.h | 10 ++-------- src/passes/RangeAnalysis.cpp | 3 +-- src/support/inplace_vector.h | 21 ++++++++------------- test/gtest/span.cpp | 1 - 5 files changed, 12 insertions(+), 26 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 6b9a8782685..f6b5e9f2b2a 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -19,8 +19,7 @@ namespace wasm::constraint { -void AndedConstraintSet::fuzzyOr(const Constraint& c) { -} +void AndedConstraintSet::fuzzyOr(const Constraint& c) {} bool Span::includes(const Value& value) { // In most cases, we don't know enough. diff --git a/src/ir/constraint.h b/src/ir/constraint.h index f361cf22f0d..57ed2b4e198 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -52,11 +52,7 @@ struct Constraint { using MaxConstraints = 3; // What a constraint is known to be: true/false, or unknown. -enum Result { - True, - False, - Unknown -}; +enum Result { True, False, Unknown }; // A set of constraints connected by the logical "and" operation. That is, all // the constraints are simultaneously true. @@ -71,9 +67,7 @@ struct AndedConstraintSet : std::inplace_vector { // Add a constraint to the set, ANDed with the others. The caller must make // sure not to add too many. - void and_(const Constraint& c) { - push_back(c); - } + void and_(const Constraint& c) { push_back(c); } // Add a constraint that is ORed. We cannot represent such a thing directly // (we only use AND), so we approximate it in a fuzzy way. For example, this diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index ef9060fc1e4..e7b5a43746e 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -44,8 +44,7 @@ namespace wasm { namespace { -struct Constraint { -}; +struct Constraint {}; // The span of values we inferred for locals. In the code below, we consider // missing indexes to have no known span for them (i.e., we do not need to write diff --git a/src/support/inplace_vector.h b/src/support/inplace_vector.h index b69c3693f70..ca15ba3af14 100644 --- a/src/support/inplace_vector.h +++ b/src/support/inplace_vector.h @@ -41,11 +41,9 @@ template class inplace_vector { inplace_vector() {} inplace_vector(const inplace_vector& other) - : usedFixed(other.usedFixed), fixed(other.fixed) { - } + : usedFixed(other.usedFixed), fixed(other.fixed) {} inplace_vector(inplace_vector&& other) - : usedFixed(other.usedFixed), fixed(std::move(other.fixed)), - {} + : usedFixed(other.usedFixed), fixed(std::move(other.fixed)), {} inplace_vector(std::initializer_list init) { for (T item : init) { push_back(item); @@ -65,9 +63,7 @@ template class inplace_vector { return *this; } - T& operator[](size_t i) { - return fixed[i]; - } + T& operator[](size_t i) { return fixed[i]; } const T& operator[](size_t i) const { return const_cast&>(*this)[i]; @@ -102,9 +98,7 @@ template class inplace_vector { bool empty() const { return size() == 0; } - void clear() { - usedFixed = 0; - } + void clear() { usedFixed = 0; } void resize(size_t newSize) { assert(newSize <= N); @@ -151,7 +145,8 @@ template class inplace_vector { ConstIterator(const inplace_vector* parent, size_t index) : ParentIndexIterator*, ConstIterator>{parent, - index} {} + index} { + } ConstIterator(const ConstIterator& other) = default; const T& operator*() const { return (*this->parent)[this->index]; } @@ -169,8 +164,8 @@ template class inplace_vector { } }; -// A inplace_vector for which some values may be read before they are written, and -// in that case they have the value zero. +// A inplace_vector for which some values may be read before they are written, +// and in that case they have the value zero. template struct ZeroInitinplace_vector : public inplace_vector { T& operator[](size_t i) { diff --git a/test/gtest/span.cpp b/test/gtest/span.cpp index cd90fb5a3c2..b904d52800d 100644 --- a/test/gtest/span.cpp +++ b/test/gtest/span.cpp @@ -35,4 +35,3 @@ TEST(SpanTest, TestIncludes) { EXPECT_TRUE(span10_20.includes(lit15)); EXPECT_FALSE(span10_20.includes(lit25)); } - From 279dc39fda3b4c5d2c2b3f417e51c6e1adb7d35f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 15:35:51 -0700 Subject: [PATCH 062/202] work --- src/ir/constraint.cpp | 1 - src/ir/constraint.h | 2 +- src/support/inplace_vector.h | 8 ++++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index f6b5e9f2b2a..fac4062ab83 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -73,4 +73,3 @@ bool Span::greaterThan(const Value& value) { abort(); } } // namespace wasm::constraint -#endif // wasm_ir_constraint_h diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 57ed2b4e198..6491fa2d20d 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -37,7 +37,7 @@ struct Value : public std::variant { // A constraint. struct Constraint { // The operation relating two values, and the values. - Abstract::Op op == Abstract::Invalid; + Abstract::Op op = Abstract::Invalid; Value left; Value right; diff --git a/src/support/inplace_vector.h b/src/support/inplace_vector.h index ca15ba3af14..24e136e64c9 100644 --- a/src/support/inplace_vector.h +++ b/src/support/inplace_vector.h @@ -125,26 +125,26 @@ template class inplace_vector { // iteration - struct Iterator : ParentIndexIterator*, Iterator> { + struct Iterator : wasm::ParentIndexIterator*, Iterator> { using value_type = T; using pointer = T*; using reference = T&; Iterator(inplace_vector* parent, size_t index) - : ParentIndexIterator*, Iterator>{parent, index} {} + : wasm::ParentIndexIterator*, Iterator>{parent, index} {} Iterator(const Iterator& other) = default; T& operator*() { return (*this->parent)[this->index]; } }; struct ConstIterator - : ParentIndexIterator*, ConstIterator> { + : wasm::ParentIndexIterator*, ConstIterator> { using value_type = const T; using pointer = const T*; using reference = const T&; ConstIterator(const inplace_vector* parent, size_t index) - : ParentIndexIterator*, ConstIterator>{parent, + : wasm::ParentIndexIterator*, ConstIterator>{parent, index} { } ConstIterator(const ConstIterator& other) = default; From 6e8f576b4c98c5eaa636c3143f930e63b469f5cd Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 15:40:52 -0700 Subject: [PATCH 063/202] work --- src/ir/constraint.cpp | 54 ++++-------------------------------- src/ir/constraint.h | 3 +- src/passes/RangeAnalysis.cpp | 5 +++- src/support/inplace_vector.h | 2 +- 4 files changed, 12 insertions(+), 52 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index fac4062ab83..4c2441ba384 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -19,57 +19,13 @@ namespace wasm::constraint { -void AndedConstraintSet::fuzzyOr(const Constraint& c) {} - -bool Span::includes(const Value& value) { - // In most cases, we don't know enough. - bool ret = false; - std::visit(overloaded{ - [&](const Literal& lit) { - // The value is a literal. We can infer something here if the - // span is a range of literals, checking if value is within - // [min, max]. - const Literal* minLit = std::get_if(&min); - if (minLit && *minLit == lit) { - ret = true; - return; - } - const Literal* maxLit = std::get_if(&max); - if (maxLit && *maxLit == lit) { - ret = true; - return; - } - if (lit.type.isNumber() && minLit && maxLit) { - // Numbers can be ordered. - assert(minLit->type == lit.type); - assert(maxLit->type == lit.type); - if (minLit->le(lit).getUnsigned() && - maxLit->ge(lit).getUnsigned()) { - ret = true; - } - } - }, - [&](const Index& local) { - // A local index can be compared to others. - const Index* minLocal = std::get_if(&min); - if (minLocal && *minLocal == local) { - ret = true; - return; - } - const Index* maxLocal = std::get_if(&max); - if (maxLocal && *maxLocal == local) { - ret = true; - } - }, - [&](const Unknown& unknown) {}, - }, - value); - return ret; +Result AndedConstraintSet::check(const Constraint& condition) { + // TODO } -bool Span::lessThan(const Value& value) { abort(); } - -bool Span::greaterThan(const Value& value) { abort(); } +void AndedConstraintSet::fuzzyOr(const Constraint& c) { + // TODO +} } // namespace wasm::constraint diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 6491fa2d20d..31fd577673b 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -22,6 +22,7 @@ #include #include "ir/abstract.h" +#include "support/inplace_vector.h" #include "support/utilities.h" #include "wasm.h" @@ -49,7 +50,7 @@ struct Constraint { // We limit constraints to a low number to ensure good performance even with // simple brute-force solving. // TODO: use a generic constraint solver..? -using MaxConstraints = 3; +inline constexpr std::size_t MaxConstraints = 3; // What a constraint is known to be: true/false, or unknown. enum Result { True, False, Unknown }; diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index e7b5a43746e..3b11995a761 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -44,7 +44,7 @@ namespace wasm { namespace { -struct Constraint {}; +#if 0 // The span of values we inferred for locals. In the code below, we consider // missing indexes to have no known span for them (i.e., we do not need to write @@ -62,6 +62,7 @@ struct Info { // XXX do we need both? LocalSpans localSpansStart, localSpansEnd; }; +#endif struct RangeAnalysis : public WalkerPass, Info>> { @@ -77,6 +78,7 @@ struct RangeAnalysis using Super = WalkerPass, Info>>; +#if 0 // Branches outside of the function can be ignored, as we only look at local // state in the function. bool ignoreBranchesOutsideOfFunc = true; @@ -386,6 +388,7 @@ struct RangeAnalysis } } } +#endif }; } // anonymous namespace diff --git a/src/support/inplace_vector.h b/src/support/inplace_vector.h index 24e136e64c9..71bf294973e 100644 --- a/src/support/inplace_vector.h +++ b/src/support/inplace_vector.h @@ -43,7 +43,7 @@ template class inplace_vector { inplace_vector(const inplace_vector& other) : usedFixed(other.usedFixed), fixed(other.fixed) {} inplace_vector(inplace_vector&& other) - : usedFixed(other.usedFixed), fixed(std::move(other.fixed)), {} + : usedFixed(other.usedFixed), fixed(std::move(other.fixed)) {} inplace_vector(std::initializer_list init) { for (T item : init) { push_back(item); From fcd7e417ec19df2f3e352b2bc08bd30259d858ab Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 15:41:33 -0700 Subject: [PATCH 064/202] work --- src/ir/constraint.cpp | 1 + src/passes/RangeAnalysis.cpp | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 4c2441ba384..be0cc9bb8c1 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -21,6 +21,7 @@ namespace wasm::constraint { Result AndedConstraintSet::check(const Constraint& condition) { // TODO + return Unknown; } void AndedConstraintSet::fuzzyOr(const Constraint& c) { diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 3b11995a761..e60c470dfaa 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -44,12 +44,11 @@ namespace wasm { namespace { -#if 0 // The span of values we inferred for locals. In the code below, we consider // missing indexes to have no known span for them (i.e., we do not need to write // an Unknown, and can just leave them empty). -using LocalSpans = std::unordered_map; +//using LocalSpans = std::unordered_map; // In each basic block we will store the relevant operations, which are all // local gets and sets, branches, and uses of them. @@ -60,9 +59,8 @@ struct Info { // the start and at the end of the block (for the values in the middle, we // need to traverse the actions and see how they are modified). // XXX do we need both? - LocalSpans localSpansStart, localSpansEnd; + //LocalSpans localSpansStart, localSpansEnd; }; -#endif struct RangeAnalysis : public WalkerPass, Info>> { From 6537aced7157d4df84f1849a0ba6dce550a1199e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 15:44:06 -0700 Subject: [PATCH 065/202] work --- test/gtest/CMakeLists.txt | 2 +- test/gtest/constraint.cpp | 12 ++++++++++++ test/gtest/span.cpp | 37 ------------------------------------- 3 files changed, 13 insertions(+), 38 deletions(-) create mode 100644 test/gtest/constraint.cpp delete mode 100644 test/gtest/span.cpp diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index 6467d2dccf0..6c2c9b56d68 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -9,6 +9,7 @@ set(unittest_SOURCES arena.cpp cast-check.cpp cfg.cpp + constraint.cpp dataflow.cpp delta_debugging.cpp dfa_minimization.cpp @@ -28,7 +29,6 @@ set(unittest_SOURCES printing.cpp public-type-validator.cpp scc.cpp - span.cpp stringify.cpp subtype-exprs.cpp suffix_tree.cpp diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp new file mode 100644 index 00000000000..5b7f54d88a1 --- /dev/null +++ b/test/gtest/constraint.cpp @@ -0,0 +1,12 @@ +#include "ir/constraint.h" +#include "gtest/gtest.h" + +using namespace wasm; +using namespace wasm::constraint; + +TEST(ConstraintTest, TestEmpty) { + // An empty constraint is invalid. + Constraint c; + EXPECT_FALSE(c); +} + diff --git a/test/gtest/span.cpp b/test/gtest/span.cpp deleted file mode 100644 index b904d52800d..00000000000 --- a/test/gtest/span.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "ir/span.h" -#include "gtest/gtest.h" - -using namespace wasm; -using namespace wasm::span; - -TEST(SpanTest, TestEmpty) { - // An empty value or span is unknown. - Value v; - EXPECT_TRUE(v.isUnknown()); - EXPECT_EQ(v, Value::unknown()); - - Span s; - EXPECT_TRUE(s.isUnknown()); - EXPECT_EQ(s, Span::unknown()); -} - -TEST(SpanTest, TestIncludes) { - Value unknown; - - Value lit10(Literal(int32_t(10))); - Value lit20(Literal(int32_t(20))); - EXPECT_NE(lit10, lit20); - - // A span of [10, 20] includes the edges. - Span span10_20(lit10, lit20); - EXPECT_TRUE(span10_20.includes(lit10)); - EXPECT_TRUE(span10_20.includes(lit20)); - - // It includes 15 but not things below or above. - Value lit5(Literal(int32_t(5))); - Value lit15(Literal(int32_t(15))); - Value lit25(Literal(int32_t(25))); - EXPECT_FALSE(span10_20.includes(lit5)); - EXPECT_TRUE(span10_20.includes(lit15)); - EXPECT_FALSE(span10_20.includes(lit25)); -} From 61dd31e9e5c898ffcca3a27a8ce26b6ebf876037 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 15:46:19 -0700 Subject: [PATCH 066/202] work --- src/ir/constraint.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index be0cc9bb8c1..95dd8d221ac 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -20,7 +20,13 @@ namespace wasm::constraint { Result AndedConstraintSet::check(const Constraint& condition) { - // TODO + // If the condition is among our constraints exactly, it is definitely true. + for (auto& constraint : *this) { + if (constraint == condition) { + return True; + } + } + // TODO smarts return Unknown; } From 783e6ff6af66825961cfd547e79227d6990ccfab Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 15:49:46 -0700 Subject: [PATCH 067/202] work --- src/ir/constraint.cpp | 1 + test/gtest/constraint.cpp | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 95dd8d221ac..fc07dda5410 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -26,6 +26,7 @@ Result AndedConstraintSet::check(const Constraint& condition) { return True; } } + // TODO smarts return Unknown; } diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 5b7f54d88a1..c7fcba96e6f 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -1,7 +1,9 @@ +#include "ir/abstract.h" #include "ir/constraint.h" #include "gtest/gtest.h" using namespace wasm; +using namespace wasm::Abstract; using namespace wasm::constraint; TEST(ConstraintTest, TestEmpty) { @@ -10,3 +12,12 @@ TEST(ConstraintTest, TestEmpty) { EXPECT_FALSE(c); } +TEST(ConstraintTest, TestSet) { + // Sets start empty. + AndedConstraintSet s; + EXPECT_TRUE(s.empty()); + + // $0 == 5 + Constraint c{Eq, Index(0), Literal(int32_t(5))}; +} + From 3e0055e973867d16894f3024683df096a9f8ec3d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 15:50:52 -0700 Subject: [PATCH 068/202] go --- test/gtest/constraint.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index c7fcba96e6f..79a62dc7bdf 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -19,5 +19,12 @@ TEST(ConstraintTest, TestSet) { // $0 == 5 Constraint c{Eq, Index(0), Literal(int32_t(5))}; + + // We can't infer anything using an empty set. + EXPECT_EQ(s.check(c), Unknown); + + // If we add it, then things check out. + s.and_(c); + EXPECT_EQ(s.check(c), True); } From c1e3dc81ce27ac2b0d6246e3718e876005f5177b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 15:52:11 -0700 Subject: [PATCH 069/202] go --- test/gtest/constraint.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 79a62dc7bdf..32f13fdcff0 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -25,6 +25,11 @@ TEST(ConstraintTest, TestSet) { // If we add it, then things check out. s.and_(c); + EXPECT_EQ(s.size(), 1); EXPECT_EQ(s.check(c), True); + + // $1 == 5, a different local: we can't infer anything. + Constraint c2{Eq, Index(1), Literal(int32_t(5))}; + EXPECT_EQ(s.check(c2), Unknown); } From b13c8943d84f4552f6366424a1729af0396f2037 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 15:53:00 -0700 Subject: [PATCH 070/202] go --- test/gtest/constraint.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 32f13fdcff0..a4eb75780cb 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -31,5 +31,9 @@ TEST(ConstraintTest, TestSet) { // $1 == 5, a different local: we can't infer anything. Constraint c2{Eq, Index(1), Literal(int32_t(5))}; EXPECT_EQ(s.check(c2), Unknown); + + // $0 == 10, a different number: we could infer False here TODO. + Constraint c3{Eq, Index(0) Literal(int32_t(10))}; + EXPECT_EQ(s.check(c3), Unknown); } From d7f573260be43bdb3730ee5effe50f3363190c09 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 16:04:10 -0700 Subject: [PATCH 071/202] go --- src/ir/constraint.cpp | 32 +++++++++++++++++++++++++++++++- test/gtest/constraint.cpp | 4 ++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index fc07dda5410..c6b6cc3d9ab 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -19,14 +19,44 @@ namespace wasm::constraint { +namespace { + +// Parses a constraint of the form of a local on the left and a constant on the +// right. +std::tuple getLocalConstant(const Constraint& c) { + if (const Index* local = std::get_if(&c.left)) { + if (const Literal* literal = std::get_if(&c.right)) { + return {local, literal}; + } + } + return {nullptr, nullptr}; +} + Result AndedConstraintSet::check(const Constraint& condition) { - // If the condition is among our constraints exactly, it is definitely true. for (auto& constraint : *this) { + // If the condition is among our constraints exactly, it is definitely true. if (constraint == condition) { return True; } } + if (condition.op == Abstract::Eq) { + auto [conditionLocal, conditionConstant] = getLocalConstant(condition); + if (conditionLocal) { + // $x == c. If one of our constraints is $x == c', then we found a + // contradiction. + for (auto& constraint : *this) { + auto [constraintLocal, constraintConstant] = getLocalConstant(constraint); + if (constraintLocal && *conditionLocal == *constraintLocal) { + // We already looked for full equality earlier, so some difference + // must be here. + assert(*conditionConstant != constraintConstant); + return False; + } + } + } + } + // TODO smarts return Unknown; } diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index a4eb75780cb..6ad7f08ae79 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -12,7 +12,7 @@ TEST(ConstraintTest, TestEmpty) { EXPECT_FALSE(c); } -TEST(ConstraintTest, TestSet) { +TEST(ConstraintTest, TestSetEq) { // Sets start empty. AndedConstraintSet s; EXPECT_TRUE(s.empty()); @@ -33,7 +33,7 @@ TEST(ConstraintTest, TestSet) { EXPECT_EQ(s.check(c2), Unknown); // $0 == 10, a different number: we could infer False here TODO. - Constraint c3{Eq, Index(0) Literal(int32_t(10))}; + Constraint c3{Eq, Index(0), Literal(int32_t(10))}; EXPECT_EQ(s.check(c3), Unknown); } From 362fb957de1945c2864cf5b8c219f299b2bd86c8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 16:15:13 -0700 Subject: [PATCH 072/202] go --- src/ir/constraint.cpp | 4 +++- test/gtest/constraint.cpp | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index c6b6cc3d9ab..3e9ef8a0621 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -32,6 +32,8 @@ std::tuple getLocalConstant(const Constraint& c) { return {nullptr, nullptr}; } +} // anonymous namespace + Result AndedConstraintSet::check(const Constraint& condition) { for (auto& constraint : *this) { // If the condition is among our constraints exactly, it is definitely true. @@ -50,7 +52,7 @@ Result AndedConstraintSet::check(const Constraint& condition) { if (constraintLocal && *conditionLocal == *constraintLocal) { // We already looked for full equality earlier, so some difference // must be here. - assert(*conditionConstant != constraintConstant); + assert(*conditionConstant != *constraintConstant); return False; } } diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 6ad7f08ae79..d61e8ab14cd 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -32,8 +32,8 @@ TEST(ConstraintTest, TestSetEq) { Constraint c2{Eq, Index(1), Literal(int32_t(5))}; EXPECT_EQ(s.check(c2), Unknown); - // $0 == 10, a different number: we could infer False here TODO. + // $0 == 10, a different number: we can infer false. Constraint c3{Eq, Index(0), Literal(int32_t(10))}; - EXPECT_EQ(s.check(c3), Unknown); + EXPECT_EQ(s.check(c3), False); } From 05d9e29fc43b72706641cee19df75ea81fbff5ea Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 16:26:50 -0700 Subject: [PATCH 073/202] go --- test/gtest/constraint.cpp | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index d61e8ab14cd..76d876d9eea 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -29,11 +29,26 @@ TEST(ConstraintTest, TestSetEq) { EXPECT_EQ(s.check(c), True); // $1 == 5, a different local: we can't infer anything. - Constraint c2{Eq, Index(1), Literal(int32_t(5))}; - EXPECT_EQ(s.check(c2), Unknown); + Constraint d{Eq, Index(1), Literal(int32_t(5))}; + EXPECT_EQ(s.check(d), Unknown); // $0 == 10, a different number: we can infer false. - Constraint c3{Eq, Index(0), Literal(int32_t(10))}; - EXPECT_EQ(s.check(c3), False); + Constraint e{Eq, Index(0), Literal(int32_t(10))}; + EXPECT_EQ(s.check(e), False); +} + +TEST(ConstraintTest, TestMulti) { + // Two anded constraints. Both check out. + AndedConstraintSet s; + Constraint c{Eq, Index(0), Literal(int32_t(5))}; + Constraint d{Eq, Index(1), Literal(int32_t(10))}; + s.and_(c); + s.and_(d); + EXPECT_EQ(s.check(c), True); + EXPECT_EQ(s.check(d), True); + + // Something unrelated does not. + Constraint e{Eq, Index(2), Literal(int32_t(15))}; + EXPECT_EQ(s.check(e), Unknown); } From bc1f4c91dc9024c33cd6836b62915b5f8c503ea4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 16:27:51 -0700 Subject: [PATCH 074/202] go --- src/ir/constraint.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 3e9ef8a0621..69606dc518c 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -60,6 +60,8 @@ Result AndedConstraintSet::check(const Constraint& condition) { } // TODO smarts + + // Otherwise, who knows. return Unknown; } From 70cd80e718eeb35384eb1cec3d951fc25fa628b8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 16:41:42 -0700 Subject: [PATCH 075/202] go --- src/ir/constraint.cpp | 26 +++++++++++++++++++------- test/gtest/constraint.cpp | 2 +- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 69606dc518c..d2a32f4e5a3 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -35,9 +35,9 @@ std::tuple getLocalConstant(const Constraint& c) { } // anonymous namespace Result AndedConstraintSet::check(const Constraint& condition) { - for (auto& constraint : *this) { + for (auto& c : *this) { // If the condition is among our constraints exactly, it is definitely true. - if (constraint == condition) { + if (c == condition) { return True; } } @@ -47,12 +47,12 @@ Result AndedConstraintSet::check(const Constraint& condition) { if (conditionLocal) { // $x == c. If one of our constraints is $x == c', then we found a // contradiction. - for (auto& constraint : *this) { - auto [constraintLocal, constraintConstant] = getLocalConstant(constraint); - if (constraintLocal && *conditionLocal == *constraintLocal) { + for (auto& c : *this) { + auto [cLocal, cConstant] = getLocalConstant(c); + if (cLocal && *conditionLocal == *cLocal) { // We already looked for full equality earlier, so some difference // must be here. - assert(*conditionConstant != *constraintConstant); + assert(*conditionConstant != *cConstant); return False; } } @@ -66,7 +66,19 @@ Result AndedConstraintSet::check(const Constraint& condition) { } void AndedConstraintSet::fuzzyOr(const Constraint& c) { - // TODO + // See what we know about this. + switch (check(c)) { + case True: + // This is already implied by current constraints, i.e., it is redundant. + return; + case False: + // This contradicts us. For example, we were x == 5 and this is x == 10. + ... + case Unknown: + // This is an interesting case, which we analyze. + } + + // TODO smarts } } // namespace wasm::constraint diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 76d876d9eea..b77a9cebbca 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -12,7 +12,7 @@ TEST(ConstraintTest, TestEmpty) { EXPECT_FALSE(c); } -TEST(ConstraintTest, TestSetEq) { +TEST(ConstraintTest, TestEq) { // Sets start empty. AndedConstraintSet s; EXPECT_TRUE(s.empty()); From bf12966acbfa2f8c856b55a4d5a89ab34e9a6051 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 Jun 2026 16:52:15 -0700 Subject: [PATCH 076/202] go --- src/ir/constraint.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index d2a32f4e5a3..1a33bfd2928 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -66,13 +66,14 @@ Result AndedConstraintSet::check(const Constraint& condition) { } void AndedConstraintSet::fuzzyOr(const Constraint& c) { - // See what we know about this. - switch (check(c)) { - case True: - // This is already implied by current constraints, i.e., it is redundant. - return; - case False: - // This contradicts us. For example, we were x == 5 and this is x == 10. + // If this is already implied by current constraints, then it is redundant. + if (check(c) == True) { + return; + } + + // Otherwise, it either contradicts us (e.g. we were x == 5 and this is + // x == 10) or we can't infer anything about it (e.g. we were x == 5 and this + // is z == 99) XXX ... case Unknown: // This is an interesting case, which we analyze. From 67b67070b30f8cf2f44a5075a39323efd03d0aa5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 09:53:18 -0700 Subject: [PATCH 077/202] go --- src/ir/constraint.cpp | 31 +++++++++---------------------- src/ir/constraint.h | 13 +++++++------ test/gtest/constraint.cpp | 12 +++++------- 3 files changed, 21 insertions(+), 35 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 1a33bfd2928..55a7a3b1454 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -19,21 +19,6 @@ namespace wasm::constraint { -namespace { - -// Parses a constraint of the form of a local on the left and a constant on the -// right. -std::tuple getLocalConstant(const Constraint& c) { - if (const Index* local = std::get_if(&c.left)) { - if (const Literal* literal = std::get_if(&c.right)) { - return {local, literal}; - } - } - return {nullptr, nullptr}; -} - -} // anonymous namespace - Result AndedConstraintSet::check(const Constraint& condition) { for (auto& c : *this) { // If the condition is among our constraints exactly, it is definitely true. @@ -43,16 +28,14 @@ Result AndedConstraintSet::check(const Constraint& condition) { } if (condition.op == Abstract::Eq) { - auto [conditionLocal, conditionConstant] = getLocalConstant(condition); - if (conditionLocal) { - // $x == c. If one of our constraints is $x == c', then we found a - // contradiction. + if (auto* constant = std::get_if(&condition.value)) { + // The condition is x == c. If one of our constraints is $x == c', then we + // found a contradiction. for (auto& c : *this) { - auto [cLocal, cConstant] = getLocalConstant(c); - if (cLocal && *conditionLocal == *cLocal) { + if (auto* cConstant = std::get_if(&c.value)) { // We already looked for full equality earlier, so some difference // must be here. - assert(*conditionConstant != *cConstant); + assert(*constant != *cConstant); return False; } } @@ -71,6 +54,7 @@ void AndedConstraintSet::fuzzyOr(const Constraint& c) { return; } +#if 0 // Otherwise, it either contradicts us (e.g. we were x == 5 and this is // x == 10) or we can't infer anything about it (e.g. we were x == 5 and this // is z == 99) XXX @@ -78,6 +62,9 @@ void AndedConstraintSet::fuzzyOr(const Constraint& c) { case Unknown: // This is an interesting case, which we analyze. } +#endif + +// fuzzyOr with a set, not a constriant... // TODO smarts } diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 31fd577673b..193925bf545 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -28,19 +28,17 @@ namespace wasm::constraint { -// TODO: constraint => condition? - // A value in a constraint, either a local index or literal value. struct Value : public std::variant { bool operator==(const Value&) const = default; }; -// A constraint. +// A constraint: some operation and some value, like "is equal to 17" or "is +// less than local $z". struct Constraint { // The operation relating two values, and the values. Abstract::Op op = Abstract::Invalid; - Value left; - Value right; + Value value; bool operator==(const Constraint&) const = default; @@ -56,7 +54,10 @@ inline constexpr std::size_t MaxConstraints = 3; enum Result { True, False, Unknown }; // A set of constraints connected by the logical "and" operation. That is, all -// the constraints are simultaneously true. +// the constraints are simultaneously true about some value. In the examples in +// the comments below, `x` is used for the thing all the constraints are talking +// about, but it could be a global or a struct field or anything else in +// general. struct AndedConstraintSet : std::inplace_vector { // Check a condition against this set, that is, whether the existing // constraints prove that it must be true, false, or unknown: whether diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index b77a9cebbca..ccafcc9046f 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -17,8 +17,8 @@ TEST(ConstraintTest, TestEq) { AndedConstraintSet s; EXPECT_TRUE(s.empty()); - // $0 == 5 - Constraint c{Eq, Index(0), Literal(int32_t(5))}; + // x == 5 (we use "x" for the name of the thing being compared). + Constraint c{Eq, Literal(int32_t(5))}; // We can't infer anything using an empty set. EXPECT_EQ(s.check(c), Unknown); @@ -28,15 +28,12 @@ TEST(ConstraintTest, TestEq) { EXPECT_EQ(s.size(), 1); EXPECT_EQ(s.check(c), True); - // $1 == 5, a different local: we can't infer anything. - Constraint d{Eq, Index(1), Literal(int32_t(5))}; - EXPECT_EQ(s.check(d), Unknown); - // $0 == 10, a different number: we can infer false. - Constraint e{Eq, Index(0), Literal(int32_t(10))}; + Constraint e{Eq, Literal(int32_t(10))}; EXPECT_EQ(s.check(e), False); } +#if 0 TEST(ConstraintTest, TestMulti) { // Two anded constraints. Both check out. AndedConstraintSet s; @@ -51,4 +48,5 @@ TEST(ConstraintTest, TestMulti) { Constraint e{Eq, Index(2), Literal(int32_t(15))}; EXPECT_EQ(s.check(e), Unknown); } +#endif From b9fe8c07a9b740407a529b43927bcbe78ea07095 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 10:20:23 -0700 Subject: [PATCH 078/202] go --- src/ir/constraint.cpp | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 55a7a3b1454..bd7034106d5 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -27,16 +27,24 @@ Result AndedConstraintSet::check(const Constraint& condition) { } } - if (condition.op == Abstract::Eq) { - if (auto* constant = std::get_if(&condition.value)) { - // The condition is x == c. If one of our constraints is $x == c', then we - // found a contradiction. - for (auto& c : *this) { - if (auto* cConstant = std::get_if(&c.value)) { - // We already looked for full equality earlier, so some difference - // must be here. - assert(*constant != *cConstant); - return False; + // Comparisons of two constants. + if (auto* constant = std::get_if(&condition.value)) { + for (auto& c : *this) { + if (auto* cConstant = std::get_if(&c.value)) { + switch (c.op) { + case Abstract::Eq: { + switch (condition.op) { + case Abstract::Eq: { + // The condition is x == c and constraint is x == c', and we + // already looked for full equality earlier, c != c', and we + // found a contradiction. + assert(*constant != *cConstant); + return False; + } + default: {} + } + } + default: {} } } } From 6bae184cf21a37640c5c26595efb77db78710382 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 10:24:41 -0700 Subject: [PATCH 079/202] go --- src/ir/constraint.cpp | 5 +++++ test/gtest/constraint.cpp | 10 +++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index bd7034106d5..6fcc8246567 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -41,6 +41,11 @@ Result AndedConstraintSet::check(const Constraint& condition) { assert(*constant != *cConstant); return False; } + case Abstract::Ne: { + // The condition is x == c and constraint is x != c'. We can + // infer the result based on relating c and c'. + return *constant != *cConstant ? True : False; + } default: {} } } diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index ccafcc9046f..a5755d3bcf3 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -28,9 +28,17 @@ TEST(ConstraintTest, TestEq) { EXPECT_EQ(s.size(), 1); EXPECT_EQ(s.check(c), True); - // $0 == 10, a different number: we can infer false. + // x == 10, a different number: we can infer false. Constraint e{Eq, Literal(int32_t(10))}; EXPECT_EQ(s.check(e), False); + + // x != 15: we can infer true. + Constraint f{Ne, Literal(int32_t(15))}; + EXPECT_EQ(s.check(f), True); + + // x != 5: we can infer false. + Constraint g{Ne, Literal(int32_t(5))}; + EXPECT_EQ(s.check(g), False); } #if 0 From 6706a99c552584282482e49dbfe1b4b05f4cdb18 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 10:31:45 -0700 Subject: [PATCH 080/202] go --- src/ir/constraint.cpp | 78 +++++++++++++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 25 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 6fcc8246567..10afdba5ba4 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -14,11 +14,60 @@ * limitations under the License. */ +#include + #include "ir/constraint.h" #include "wasm.h" namespace wasm::constraint { +namespace { + +// Core comparison of two constraints. To keep it simple, this is not +// symmetrical - it leaves handling of parallel cases to another call of this +// function with inputs reversed. +// +// Returns a Result, or an empty option if we should keep working (i.e., a +// result of Unknown means we are certain we can just return Unknown). +std::optional checkPairInternal(const Constraint& a, const Constraint& b) { + // Comparisons of two constants. + if (auto* aConstant = std::get_if(&a.value)) { + if (auto* bConstant = std::get_if(&b.value)) { + switch (a.op) { + case Abstract::Eq: { + switch (b.op) { + case Abstract::Eq: { + // The condition is x == c and constraint is x == c', and we + // already looked for full equality earlier, c != c', and we + // found a contradiction. + assert(*aConstant != *bConstant); + return False; + } + case Abstract::Ne: { + // The condition is x == c and constraint is x != c'. We can + // infer the result based on relating c and c'. + return *aConstant != *bConstant ? True : False; + } + default: {} + } + } + default: {} + } + } + } + + return {}; +} + +std::optional checkPair(const Constraint& a, const Constraint& b) { + if (auto result = checkPairInternal(a, b)) { + return *result; + } + return checkPairInternal(b, a); +} + +} // anonymous namespace + Result AndedConstraintSet::check(const Constraint& condition) { for (auto& c : *this) { // If the condition is among our constraints exactly, it is definitely true. @@ -27,31 +76,10 @@ Result AndedConstraintSet::check(const Constraint& condition) { } } - // Comparisons of two constants. - if (auto* constant = std::get_if(&condition.value)) { - for (auto& c : *this) { - if (auto* cConstant = std::get_if(&c.value)) { - switch (c.op) { - case Abstract::Eq: { - switch (condition.op) { - case Abstract::Eq: { - // The condition is x == c and constraint is x == c', and we - // already looked for full equality earlier, c != c', and we - // found a contradiction. - assert(*constant != *cConstant); - return False; - } - case Abstract::Ne: { - // The condition is x == c and constraint is x != c'. We can - // infer the result based on relating c and c'. - return *constant != *cConstant ? True : False; - } - default: {} - } - } - default: {} - } - } + // Sometimes a single constraint is enough to determine the condition. + for (auto& c : *this) { + if (auto result = checkPair(c, condition)) { + return *result; } } From 62cba65fd345c22f7233127c768779baa3dee0c4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 10:41:51 -0700 Subject: [PATCH 081/202] go --- src/ir/constraint.cpp | 51 +++++++++++++++++++++------------------ test/gtest/constraint.cpp | 21 ++++++++++++++++ 2 files changed, 49 insertions(+), 23 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 10afdba5ba4..ccfa3301b31 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -23,13 +23,16 @@ namespace wasm::constraint { namespace { -// Core comparison of two constraints. To keep it simple, this is not -// symmetrical - it leaves handling of parallel cases to another call of this -// function with inputs reversed. +// Core comparison of two constraints. // // Returns a Result, or an empty option if we should keep working (i.e., a // result of Unknown means we are certain we can just return Unknown). -std::optional checkPairInternal(const Constraint& a, const Constraint& b) { +std::optional checkPair(const Constraint& a, const Constraint& b) { + // A thing always implies itself. + if (a == b) { + return True; + } + // Comparisons of two constants. if (auto* aConstant = std::get_if(&a.value)) { if (auto* bConstant = std::get_if(&b.value)) { @@ -37,20 +40,36 @@ std::optional checkPairInternal(const Constraint& a, const Constraint& b case Abstract::Eq: { switch (b.op) { case Abstract::Eq: { - // The condition is x == c and constraint is x == c', and we - // already looked for full equality earlier, c != c', and we - // found a contradiction. + // x == c vs x == c', and we already handled full equality + // earlier, hence c != c', and we found a contradiction. assert(*aConstant != *bConstant); return False; } case Abstract::Ne: { - // The condition is x == c and constraint is x != c'. We can - // infer the result based on relating c and c'. + // x == c vs x != c'. We can infer the result based on relating c + // and c'. return *aConstant != *bConstant ? True : False; } default: {} } } + case Abstract::Ne: { + switch (b.op) { + case Abstract::Eq: { + // x != c vs x == c'. If c == c', we can infer. + if (*aConstant == *bConstant) { + return False; + } + return {}; + } + case Abstract::Ne: { + // x == c vs x == c', and we already handled full equality + // earlier, hence c != c', and we can infer nothing. + return {}; + } + default: {} + } + } default: {} } } @@ -59,23 +78,9 @@ std::optional checkPairInternal(const Constraint& a, const Constraint& b return {}; } -std::optional checkPair(const Constraint& a, const Constraint& b) { - if (auto result = checkPairInternal(a, b)) { - return *result; - } - return checkPairInternal(b, a); -} - } // anonymous namespace Result AndedConstraintSet::check(const Constraint& condition) { - for (auto& c : *this) { - // If the condition is among our constraints exactly, it is definitely true. - if (c == condition) { - return True; - } - } - // Sometimes a single constraint is enough to determine the condition. for (auto& c : *this) { if (auto result = checkPair(c, condition)) { diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index a5755d3bcf3..f1904c095ba 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -41,6 +41,27 @@ TEST(ConstraintTest, TestEq) { EXPECT_EQ(s.check(g), False); } +TEST(ConstraintTest, TestNe) { + AndedConstraintSet s; + // x != 5 + Constraint c{Ne, Literal(int32_t(5))}; + s.and_(c); + // Checks out versus itself. + EXPECT_EQ(s.check(c), True); + + // x == 10: we don't know. + Constraint e{Eq, Literal(int32_t(10))}; + EXPECT_EQ(s.check(e), Unknown); + + // x != 15: we don't know. + Constraint f{Ne, Literal(int32_t(15))}; + EXPECT_EQ(s.check(f), Unknown); + + // x == 5: we can infer false. + Constraint g{Eq, Literal(int32_t(5))}; + EXPECT_EQ(s.check(g), False); +} + #if 0 TEST(ConstraintTest, TestMulti) { // Two anded constraints. Both check out. From c3089ea06c28ac7a8a71a384b4496a377dd87e4c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 10:42:17 -0700 Subject: [PATCH 082/202] go --- src/ir/constraint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index ccfa3301b31..1204a96aa73 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -88,7 +88,7 @@ Result AndedConstraintSet::check(const Constraint& condition) { } } - // TODO smarts + // TODO smarts for multiple constraints // Otherwise, who knows. return Unknown; From 7c5b0b729ce97220b3777302d8cbcf92f9baf139 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 11:11:42 -0700 Subject: [PATCH 083/202] go --- test/gtest/constraint.cpp | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index f1904c095ba..a3490984bc2 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -46,6 +46,7 @@ TEST(ConstraintTest, TestNe) { // x != 5 Constraint c{Ne, Literal(int32_t(5))}; s.and_(c); + // Checks out versus itself. EXPECT_EQ(s.check(c), True); @@ -62,20 +63,32 @@ TEST(ConstraintTest, TestNe) { EXPECT_EQ(s.check(g), False); } -#if 0 TEST(ConstraintTest, TestMulti) { - // Two anded constraints. Both check out. AndedConstraintSet s; - Constraint c{Eq, Index(0), Literal(int32_t(5))}; - Constraint d{Eq, Index(1), Literal(int32_t(10))}; + // x != 5 && x != 10 + Constraint c{Ne, Literal(int32_t(5))}; + Constraint d{Ne, Literal(int32_t(10))}; s.and_(c); s.and_(d); + + // Each checks out versus itself. EXPECT_EQ(s.check(c), True); EXPECT_EQ(s.check(d), True); - // Something unrelated does not. - Constraint e{Eq, Index(2), Literal(int32_t(15))}; - EXPECT_EQ(s.check(e), Unknown); + // x == 5: false. + Constraint e{Eq, Literal(int32_t(5))}; + EXPECT_EQ(s.check(e), False); + + // x == 10: false. + Constraint f{Eq, Literal(int32_t(10))}; + EXPECT_EQ(s.check(f), False); + + // x == 15: we don't know. + Constraint g{Ne, Literal(int32_t(15))}; + EXPECT_EQ(s.check(g), Unknown); + + // x != 15: we don't know. + Constraint h{Ne, Literal(int32_t(15))}; + EXPECT_EQ(s.check(h), Unknown); } -#endif From 958a6d4fecf59978875b01680313ee8afe86f165 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 11:11:58 -0700 Subject: [PATCH 084/202] go --- test/gtest/constraint.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index a3490984bc2..09600573ba5 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -84,8 +84,7 @@ TEST(ConstraintTest, TestMulti) { EXPECT_EQ(s.check(f), False); // x == 15: we don't know. - Constraint g{Ne, Literal(int32_t(15))}; - EXPECT_EQ(s.check(g), Unknown); + EXPECT_EQ(s.check(Constraint{Ne, Literal(int32_t(15))}), Unknown); // x != 15: we don't know. Constraint h{Ne, Literal(int32_t(15))}; From e4079e9278c11830baf72cce2ccd16d4965bd6f3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 11:14:44 -0700 Subject: [PATCH 085/202] go --- test/gtest/constraint.cpp | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 09600573ba5..bfea334d594 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -29,16 +29,13 @@ TEST(ConstraintTest, TestEq) { EXPECT_EQ(s.check(c), True); // x == 10, a different number: we can infer false. - Constraint e{Eq, Literal(int32_t(10))}; - EXPECT_EQ(s.check(e), False); + EXPECT_EQ(s.check(Constraint{Eq, Literal(int32_t(10))}), False); // x != 15: we can infer true. - Constraint f{Ne, Literal(int32_t(15))}; - EXPECT_EQ(s.check(f), True); + EXPECT_EQ(s.check(Constraint{Ne, Literal(int32_t(15))}), True); // x != 5: we can infer false. - Constraint g{Ne, Literal(int32_t(5))}; - EXPECT_EQ(s.check(g), False); + EXPECT_EQ(s.check(Constraint{Ne, Literal(int32_t(5))}), False); } TEST(ConstraintTest, TestNe) { @@ -51,16 +48,13 @@ TEST(ConstraintTest, TestNe) { EXPECT_EQ(s.check(c), True); // x == 10: we don't know. - Constraint e{Eq, Literal(int32_t(10))}; - EXPECT_EQ(s.check(e), Unknown); + EXPECT_EQ(s.check(Constraint{Eq, Literal(int32_t(10))}), Unknown); // x != 15: we don't know. - Constraint f{Ne, Literal(int32_t(15))}; - EXPECT_EQ(s.check(f), Unknown); + EXPECT_EQ(s.check(Constraint{Ne, Literal(int32_t(15))}), Unknown); // x == 5: we can infer false. - Constraint g{Eq, Literal(int32_t(5))}; - EXPECT_EQ(s.check(g), False); + EXPECT_EQ(s.check(Constraint{Eq, Literal(int32_t(5))}), False); } TEST(ConstraintTest, TestMulti) { @@ -76,18 +70,15 @@ TEST(ConstraintTest, TestMulti) { EXPECT_EQ(s.check(d), True); // x == 5: false. - Constraint e{Eq, Literal(int32_t(5))}; - EXPECT_EQ(s.check(e), False); + EXPECT_EQ(s.check(Constraint{Eq, Literal(int32_t(5))}), False); // x == 10: false. - Constraint f{Eq, Literal(int32_t(10))}; - EXPECT_EQ(s.check(f), False); + EXPECT_EQ(s.check(Constraint{Eq, Literal(int32_t(10))}), False); // x == 15: we don't know. EXPECT_EQ(s.check(Constraint{Ne, Literal(int32_t(15))}), Unknown); // x != 15: we don't know. - Constraint h{Ne, Literal(int32_t(15))}; - EXPECT_EQ(s.check(h), Unknown); + EXPECT_EQ(s.check(Constraint{Ne, Literal(int32_t(15))}), Unknown); } From 31a0ea53420d6f6e9146becdf5845a4fdfdeee19 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 11:14:52 -0700 Subject: [PATCH 086/202] go --- src/ir/constraint.cpp | 12 +++++++----- src/passes/RangeAnalysis.cpp | 5 ++--- src/support/inplace_vector.h | 8 ++++---- test/gtest/constraint.cpp | 3 +-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 1204a96aa73..14e969b038b 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -50,7 +50,8 @@ std::optional checkPair(const Constraint& a, const Constraint& b) { // and c'. return *aConstant != *bConstant ? True : False; } - default: {} + default: { + } } } case Abstract::Ne: { @@ -67,10 +68,12 @@ std::optional checkPair(const Constraint& a, const Constraint& b) { // earlier, hence c != c', and we can infer nothing. return {}; } - default: {} + default: { + } } } - default: {} + default: { + } } } } @@ -110,10 +113,9 @@ void AndedConstraintSet::fuzzyOr(const Constraint& c) { } #endif -// fuzzyOr with a set, not a constriant... + // fuzzyOr with a set, not a constriant... // TODO smarts } } // namespace wasm::constraint - diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index e60c470dfaa..42041d79e47 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -44,11 +44,10 @@ namespace wasm { namespace { - // The span of values we inferred for locals. In the code below, we consider // missing indexes to have no known span for them (i.e., we do not need to write // an Unknown, and can just leave them empty). -//using LocalSpans = std::unordered_map; +// using LocalSpans = std::unordered_map; // In each basic block we will store the relevant operations, which are all // local gets and sets, branches, and uses of them. @@ -59,7 +58,7 @@ struct Info { // the start and at the end of the block (for the values in the middle, we // need to traverse the actions and see how they are modified). // XXX do we need both? - //LocalSpans localSpansStart, localSpansEnd; + // LocalSpans localSpansStart, localSpansEnd; }; struct RangeAnalysis diff --git a/src/support/inplace_vector.h b/src/support/inplace_vector.h index 71bf294973e..0df08dd8c5e 100644 --- a/src/support/inplace_vector.h +++ b/src/support/inplace_vector.h @@ -131,7 +131,8 @@ template class inplace_vector { using reference = T&; Iterator(inplace_vector* parent, size_t index) - : wasm::ParentIndexIterator*, Iterator>{parent, index} {} + : wasm::ParentIndexIterator*, Iterator>{parent, + index} {} Iterator(const Iterator& other) = default; T& operator*() { return (*this->parent)[this->index]; } @@ -144,9 +145,8 @@ template class inplace_vector { using reference = const T&; ConstIterator(const inplace_vector* parent, size_t index) - : wasm::ParentIndexIterator*, ConstIterator>{parent, - index} { - } + : wasm::ParentIndexIterator*, ConstIterator>{ + parent, index} {} ConstIterator(const ConstIterator& other) = default; const T& operator*() const { return (*this->parent)[this->index]; } diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index bfea334d594..fdedbb3a761 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -1,5 +1,5 @@ -#include "ir/abstract.h" #include "ir/constraint.h" +#include "ir/abstract.h" #include "gtest/gtest.h" using namespace wasm; @@ -81,4 +81,3 @@ TEST(ConstraintTest, TestMulti) { // x != 15: we don't know. EXPECT_EQ(s.check(Constraint{Ne, Literal(int32_t(15))}), Unknown); } - From d78f0ef91628463524ae0fbde723c933dc530a3d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 11:16:20 -0700 Subject: [PATCH 087/202] go --- test/gtest/constraint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index fdedbb3a761..4e2b0b4e034 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -76,7 +76,7 @@ TEST(ConstraintTest, TestMulti) { EXPECT_EQ(s.check(Constraint{Eq, Literal(int32_t(10))}), False); // x == 15: we don't know. - EXPECT_EQ(s.check(Constraint{Ne, Literal(int32_t(15))}), Unknown); + EXPECT_EQ(s.check(Constraint{Eq, Literal(int32_t(15))}), Unknown); // x != 15: we don't know. EXPECT_EQ(s.check(Constraint{Ne, Literal(int32_t(15))}), Unknown); From e78d295115a4ff098e8240744a36fc95d5c7d9a7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 11:50:57 -0700 Subject: [PATCH 088/202] go --- src/passes/RangeAnalysis.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 42041d79e47..b230e2a8656 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -31,6 +31,7 @@ // chak and compress it as we goo etc. #include "cfg/cfg-traversal.h" +#include "ir/constraint.h" #include "ir/local-graph.h" #include "ir/properties.h" #include "ir/span.h" @@ -42,6 +43,8 @@ namespace wasm { +using namespace wasm::constraint; + namespace { // The span of values we inferred for locals. In the code below, we consider @@ -54,11 +57,9 @@ namespace { struct Info { std::vector actions; // XXX just *? - // We track them local spans at - // the start and at the end of the block (for the values in the middle, we - // need to traverse the actions and see how they are modified). - // XXX do we need both? - // LocalSpans localSpansStart, localSpansEnd; + // For each local index, we track the constraints we know about it. We only do + // so at the end of each block, which is enough gfor the analysis below. + std::unordered_map endConstraints; }; struct RangeAnalysis From c9eaeed914d85a4dfb942e675c04480b3e0b00bf Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 11:58:08 -0700 Subject: [PATCH 089/202] go --- src/ir/constraint.cpp | 11 +++++++++-- src/ir/constraint.h | 24 ++++++++++++++++++++++-- test/gtest/constraint.cpp | 2 ++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 14e969b038b..b71e0a5fdf3 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -97,9 +97,16 @@ Result AndedConstraintSet::check(const Constraint& condition) { return Unknown; } -void AndedConstraintSet::fuzzyOr(const Constraint& c) { +void AndedConstraintSet::fuzzyOr(const AndedConstraintSet& other) { // If this is already implied by current constraints, then it is redundant. - if (check(c) == True) { + // E.g. if we are { x = 10 } and other is { x >= 0 } then we don't need other. + if (check(other) == True) { + return; + } + + // Reverse case of the above. + if (other.check(*this)) { + *this = other; return; } diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 193925bf545..510ebf899e9 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -67,11 +67,31 @@ struct AndedConstraintSet : std::inplace_vector { // https://en.wikipedia.org/wiki/Material_conditional#Truth_table Result check(const Constraint& condition); + // Check an entire other set. + Result check(const AndedConstraintSet& other) { + Result result = Unknown; + for (auto& c : other) { + auto currResult = check(c); + if (currResult == Unknown) { + // If something is unknown, it all is. + return Unknown; + } + if (result == Unknown) { + // This is the first result + result = currResult; + } else if (result != currResult) { + // This is a later result, and different, so give up. + return Unknown; + } + } + return result; + } + // Add a constraint to the set, ANDed with the others. The caller must make // sure not to add too many. void and_(const Constraint& c) { push_back(c); } - // Add a constraint that is ORed. We cannot represent such a thing directly + // Add constraints that are ORed. We cannot represent such a thing directly // (we only use AND), so we approximate it in a fuzzy way. For example, this // would be valid: // @@ -108,7 +128,7 @@ struct AndedConstraintSet : std::inplace_vector { // { x >= 0 } // // If we become too fuzzy, we lose the ability to imply anything useful. - void fuzzyOr(const Constraint& c); + void fuzzyOr(const AndedConstraintSet& other); }; } // namespace wasm::constraint diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 4e2b0b4e034..c4b77e386d7 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -81,3 +81,5 @@ TEST(ConstraintTest, TestMulti) { // x != 15: we don't know. EXPECT_EQ(s.check(Constraint{Ne, Literal(int32_t(15))}), Unknown); } + +// check set From 8884744ecd6d700e7136dac21decab1cc47b6edc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 13:08:37 -0700 Subject: [PATCH 090/202] go --- src/ir/constraint.cpp | 28 ++++++++++------------------ src/ir/constraint.h | 9 +++++++-- test/gtest/constraint.cpp | 38 +++++++++++++++++++++++++++++++++++++- 3 files changed, 54 insertions(+), 21 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index b71e0a5fdf3..8a731670edb 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -83,7 +83,7 @@ std::optional checkPair(const Constraint& a, const Constraint& b) { } // anonymous namespace -Result AndedConstraintSet::check(const Constraint& condition) { +Result AndedConstraintSet::check(const Constraint& condition) const { // Sometimes a single constraint is enough to determine the condition. for (auto& c : *this) { if (auto result = checkPair(c, condition)) { @@ -99,30 +99,22 @@ Result AndedConstraintSet::check(const Constraint& condition) { void AndedConstraintSet::fuzzyOr(const AndedConstraintSet& other) { // If this is already implied by current constraints, then it is redundant. - // E.g. if we are { x = 10 } and other is { x >= 0 } then we don't need other. + // E.g. if we are { x = 10 } and other is { x >= 0 } then all we need is + // { x >= 0 } as the result of the OR. if (check(other) == True) { - return; - } - - // Reverse case of the above. - if (other.check(*this)) { *this = other; return; } - -#if 0 - // Otherwise, it either contradicts us (e.g. we were x == 5 and this is - // x == 10) or we can't infer anything about it (e.g. we were x == 5 and this - // is z == 99) XXX - ... - case Unknown: - // This is an interesting case, which we analyze. + if (other.check(*this) == True) { + return; } -#endif - - // fuzzyOr with a set, not a constriant... // TODO smarts + + // Otherwise, we don't know how to nicely OR these things, and expand to the + // trivial set of no constraints (i.e., where everything is true, and we can + // prove nothing). + clear(); } } // namespace wasm::constraint diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 510ebf899e9..d48151f6cdc 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -65,10 +65,15 @@ struct AndedConstraintSet : std::inplace_vector { // { this } => { condition } // // https://en.wikipedia.org/wiki/Material_conditional#Truth_table - Result check(const Constraint& condition); + Result check(const Constraint& condition) const; // Check an entire other set. - Result check(const AndedConstraintSet& other) { + Result check(const AndedConstraintSet& other) const { + if (other.empty()) { + // The empty set of constraints is always true. + return True; + } + Result result = Unknown; for (auto& c : other) { auto currResult = check(c); diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index c4b77e386d7..7287ba68109 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -82,4 +82,40 @@ TEST(ConstraintTest, TestMulti) { EXPECT_EQ(s.check(Constraint{Ne, Literal(int32_t(15))}), Unknown); } -// check set +TEST(ConstraintTest, TestSets) { + // x == 5 + Constraint c{Eq, Literal(int32_t(5))}; + + AndedConstraintSet s; + + // Any set always proves itself to be true. + EXPECT_EQ(s.check(s), True); + + // Ditto after adding something. + s.and_(c); + EXPECT_EQ(s.check(s), True); + + // Another set, empty. + AndedConstraintSet t; + + // Any set always proves an empty set to be true. + EXPECT_EQ(s.check(t), True); + + // Make both sets contain the same stuff. + t.and_(c); + EXPECT_EQ(s.check(t), True); + + // Now t has *different* stuff, x == 10, which given s is false. + t.clear(); + t.and_(Constraint{Eq, Literal(int32_t(10))}); + EXPECT_EQ(s.check(t), False); + + // Same, with x != 10. Now we know it is true. + t.clear(); + t.and_(Constraint{Ne, Literal(int32_t(10))}); + EXPECT_EQ(s.check(t), True); + + // In reverse, we can infer nothing: knowing x != 10 does not say if x == 5. + EXPECT_EQ(t.check(s), Unknown); +} + From eda589a4ef20fa5193ac3a3d7688ef8e73196562 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 13:15:05 -0700 Subject: [PATCH 091/202] go --- src/passes/RangeAnalysis.cpp | 43 +++++++++++++++--------------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index b230e2a8656..9f88db84237 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -34,7 +34,6 @@ #include "ir/constraint.h" #include "ir/local-graph.h" #include "ir/properties.h" -#include "ir/span.h" #include "pass.h" #include "support/unique_deferring_queue.h" #include "support/utilities.h" @@ -47,10 +46,7 @@ using namespace wasm::constraint; namespace { -// The span of values we inferred for locals. In the code below, we consider -// missing indexes to have no known span for them (i.e., we do not need to write -// an Unknown, and can just leave them empty). -// using LocalSpans = std::unordered_map; +using LocalConstraintMap = std::unordered_map; // In each basic block we will store the relevant operations, which are all // local gets and sets, branches, and uses of them. @@ -59,7 +55,7 @@ struct Info { // For each local index, we track the constraints we know about it. We only do // so at the end of each block, which is enough gfor the analysis below. - std::unordered_map endConstraints; + LocalConstraintMap endConstraints; }; struct RangeAnalysis @@ -102,7 +98,6 @@ struct RangeAnalysis // Maps each branching instruction to the basic block right before the // branchings. For example, for an If, this is the block that branches to the // ifTrue and ifFalse blocks. -#if 0 std::unordered_map brancherBlocks; static void doStartIfTrue(RangeAnalysis* self, Expression** currp) { @@ -164,8 +159,8 @@ struct RangeAnalysis } } - // Flow spans around until we have inferred all we can about the ranges of - // values in each location. + // Flow infos around until we have inferred all we can about the constraints + // in each location. void flow() { // Start from all the blocks, and keep going while we find something new. UniqueDeferredQueue work; @@ -176,18 +171,17 @@ struct RangeAnalysis auto* block = work.pop(); // Merge incoming data. - LocalSpans localSpans = mergeIncoming(block); + LocalConstraintMap constraints = mergeIncoming(block); // Go through the block, applying things. for (auto** currp : block->contents.actions) { - applyToLocalSpans(*currp, localSpans); + applyToConstraints(*currp, constraints); } // We now know the values at the end of the block. If something changed, // flow it onward. - LocalSpans t = block->contents.localSpansEnd; - if (localSpans != t) { - block->contents.localSpansEnd = std::move(localSpans); + if (constraints != block->contents.endConstraints) { + block->contents.endConstraints = std::move(constraints); for (auto* out : block->out) { work.push(out); } @@ -202,14 +196,14 @@ struct RangeAnalysis // at each intermediate point inside the block. (Flowing between blocks is // of course not needed at this stage.) - LocalSpans localSpans = mergeIncoming(block.get()); + LocalConstraintMap constraints = mergeIncoming(block.get()); for (auto** currp : block->contents.actions) { auto* curr = *currp; - applyToLocalSpans(*currp, localSpans); + applyToConstraints(*currp, constraints); if (auto* binary = curr->dynCast()) { - optimizeBinary(binary, currp, block.get(), localSpans); + optimizeBinary(binary, currp, block.get(), constraints); } // TODO unary } @@ -221,7 +215,7 @@ struct RangeAnalysis void optimizeBinary(Binary* curr, Expression** currp, BasicBlock* block, - const LocalSpans& localSpans) { + const AndedConstraintSet& constraints) { /// Find relevant gets. If we see a get we can't handle, stop. auto* leftGet = curr->left->dynCast(); auto* rightGet = curr->right->dynCast(); @@ -285,8 +279,8 @@ struct RangeAnalysis // Given a source (predecessor) and a target (successor) block, find the span // of a particular local as it arrives to that target from that successor. Span getSpanFromPredToSucc(BasicBlock* pred, BasicBlock* block, Index local) { - auto iter = pred->contents.localSpansEnd.find(local); - if (iter == pred->contents.localSpansEnd.end()) { + auto iter = pred->contents.endConstraints.find(local); + if (iter == pred->contents.endConstraints.end()) { return Span::unknown(); } @@ -363,9 +357,9 @@ struct RangeAnalysis return ret; } - // Given an expression, apply it to the local spans. For example, a local.set + // Given an expression, apply it to the constraints. For example, a local.set // sets the value for that local. - void applyToLocalSpans(Expression* curr, LocalSpans& localSpans) { + void applyToConstraints(Expression* curr, LocalConstraintMap& constraints) { if (auto* set = curr->dynCast()) { if (relevantLocals.contains(set->index)) { Value value; @@ -378,15 +372,14 @@ struct RangeAnalysis value = Value(c->value); } else { // Nothing is known. - localSpans.erase(set->index); + constraints.erase(set->index); return; } // Both the min and max are equal to what we found. - localSpans[set->index] = Span{value, value}; + constraints[set->index] = Span{value, value}; } } } -#endif }; } // anonymous namespace From 72e40e4d6795278045e8898bd01369cd05903d34 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 13:22:51 -0700 Subject: [PATCH 092/202] go --- src/ir/constraint.cpp | 12 ++++++++++++ src/ir/constraint.h | 17 +++++++++++++++++ src/passes/RangeAnalysis.cpp | 12 +++++++----- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 8a731670edb..ddd85af5b26 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -117,4 +117,16 @@ void AndedConstraintSet::fuzzyOr(const AndedConstraintSet& other) { clear(); } +std::optional LocalConstraint::parse(Binary* curr) { + auto* get = curr->left->dynCast(); + if (!get) { + return {}; + } + + + || !relevantLocals.contains(get->index)) { + return; + } +} + } // namespace wasm::constraint diff --git a/src/ir/constraint.h b/src/ir/constraint.h index d48151f6cdc..9fc0fe96305 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -136,6 +136,23 @@ struct AndedConstraintSet : std::inplace_vector { void fuzzyOr(const AndedConstraintSet& other); }; +// A local plus a constraint on it. +struct LocalConstraint { + Index local; + Constraint constraint; + + // 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(Binary* curr); +}; + } // namespace wasm::constraint #endif // wasm_ir_constraint_h diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 9f88db84237..79412424562 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -215,13 +215,15 @@ struct RangeAnalysis void optimizeBinary(Binary* curr, Expression** currp, BasicBlock* block, - const AndedConstraintSet& constraints) { - /// Find relevant gets. If we see a get we can't handle, stop. - auto* leftGet = curr->left->dynCast(); - auto* rightGet = curr->right->dynCast(); - if (leftGet && !relevantLocals.contains(leftGet->index)) { + const LocalConstraintMap& constraints) { + auto condition = constraint::parse(curr); + if (!condition) { return; } + // Find relevant gets. If we see a get we can't handle, stop. + + // This binary operates on a relevant local. Try to use what we know. + auto* rightGet = curr->right->dynCast(); if (rightGet && !relevantLocals.contains(rightGet->index)) { return; } From 54cd47e3d20bc28eb75b23d67782e5630a150c49 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 13:23:00 -0700 Subject: [PATCH 093/202] go --- src/ir/constraint.cpp | 1 - test/gtest/constraint.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index ddd85af5b26..c5fca1dc2cc 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -123,7 +123,6 @@ std::optional LocalConstraint::parse(Binary* curr) { return {}; } - || !relevantLocals.contains(get->index)) { return; } diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 7287ba68109..ff20252861e 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -118,4 +118,3 @@ TEST(ConstraintTest, TestSets) { // In reverse, we can infer nothing: knowing x != 10 does not say if x == 5. EXPECT_EQ(t.check(s), Unknown); } - From 9e1af111bc0827d052982e3a1516a715f1807478 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 16:13:10 -0700 Subject: [PATCH 094/202] go --- src/ir/constraint.cpp | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index c5fca1dc2cc..ac00b2b91ec 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -17,6 +17,7 @@ #include #include "ir/constraint.h" +#include "ir/properties.h" #include "wasm.h" namespace wasm::constraint { @@ -118,14 +119,30 @@ void AndedConstraintSet::fuzzyOr(const AndedConstraintSet& other) { } std::optional LocalConstraint::parse(Binary* curr) { - auto* get = curr->left->dynCast(); - if (!get) { + // The left must be a get. + auto* leftGet = curr->left->dynCast(); + if (!leftGet) { return {}; } - || !relevantLocals.contains(get->index)) { - return; + // The right must be a get or a constant. + auto* rightGet = curr->right->dynCast(); + std::optional rightConstant; + if (Properties::isSingleConstantExpression(curr->right)) { + rightConstant = Properties::getLiteral(curr->right); + } + if (!rightGet && !rightConstant) { + return {}; } + + // The operation must be one we recognize. + for (auto op : {Abstract::Eq, Abstract::Ne}) { + if (Abstract::getBinary(curr->type, op) == curr->op) { + auto value = rightGet ? Value(rightGet->index) : Value(*rightConstant); + return LocalConstraint{leftGet->index, Constraint{op, value}; + } + } + return {}; } } // namespace wasm::constraint From 561c80fe700ead57ca82b15801a4bd82a974edd5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 16:18:59 -0700 Subject: [PATCH 095/202] go --- src/passes/RangeAnalysis.cpp | 40 ++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 79412424562..b9bd6ce9a4f 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -216,10 +216,18 @@ struct RangeAnalysis Expression** currp, BasicBlock* block, const LocalConstraintMap& constraints) { - auto condition = constraint::parse(curr); - if (!condition) { + auto parsed = constraint::parse(curr); + if (!parsed) { return; } + + // Add the constraint, if we can. TODO: perhaps be smart about which are + // more useful, rather than just drop later ones? + auto& localConstraints = constraints[parsed.local]; + if (!localConstraints.full()) { + localConstraints.and_(parsed.constraint_); + } +XXX // Find relevant gets. If we see a get we can't handle, stop. // This binary operates on a relevant local. Try to use what we know. @@ -362,24 +370,16 @@ struct RangeAnalysis // Given an expression, apply it to the constraints. For example, a local.set // sets the value for that local. void applyToConstraints(Expression* curr, LocalConstraintMap& constraints) { - if (auto* set = curr->dynCast()) { - if (relevantLocals.contains(set->index)) { - Value value; - // TODO: fallthrough, tee chains, etc. For that we must track values - // by Expression*, as e.g. reading a local, then setting it, should - // read the original flowing value. - if (auto* get = set->value->dynCast()) { - value = Value(get->index); - } else if (auto* c = set->value->dynCast()) { - value = Value(c->value); - } else { - // Nothing is known. - constraints.erase(set->index); - return; - } - // Both the min and max are equal to what we found. - constraints[set->index] = Span{value, value}; - } + auto parsed = constraint::parse(curr); + if (!parsed) { + return; + } + + // Add the constraint, if we can. TODO: perhaps be smart about which are + // more useful, rather than just drop later ones? + auto& localConstraints = constraints[parsed.local]; + if (!localConstraints.full()) { + localConstraints.and_(parsed.constraint_); } } }; From f29d198612bdc4d02d048069067ac14a0c0a1ee4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 16:26:03 -0700 Subject: [PATCH 096/202] go --- src/passes/RangeAnalysis.cpp | 46 ++++++++++-------------------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index b9bd6ce9a4f..a478a6a79cb 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -32,6 +32,7 @@ #include "cfg/cfg-traversal.h" #include "ir/constraint.h" +#include "ir/literal-utils.h" #include "ir/local-graph.h" #include "ir/properties.h" #include "pass.h" @@ -221,43 +222,22 @@ struct RangeAnalysis return; } - // Add the constraint, if we can. TODO: perhaps be smart about which are - // more useful, rather than just drop later ones? auto& localConstraints = constraints[parsed.local]; - if (!localConstraints.full()) { - localConstraints.and_(parsed.constraint_); - } -XXX - // Find relevant gets. If we see a get we can't handle, stop. - - // This binary operates on a relevant local. Try to use what we know. - auto* rightGet = curr->right->dynCast(); - if (rightGet && !relevantLocals.contains(rightGet->index)) { + Result result = localConstraints.check(parsed.constraint_); + 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 return; } - // Find consts. - auto* leftConst = curr->left->dynCast(); - auto* rightConst = curr->right->dynCast(); - - // If we have something other than relevant gets and consts, stop. - if (!leftGet && !leftConst) { - return; - } - if (!rightGet && !rightConst) { - return; - } - -#if 0 - if (curr->op == Abstract::getBinary(Abstract::LtS) - switch (curr->op) { - case LtSInt32: - } - -#endif - - // TODO: we might consider x < y < z, i.e., chains of relations, with a - // general-purpose constraint solver + // We know the result! + auto& wasm = *getModule(); + auto value = LiteralUtils::makeFromInt32(result == True ? 1 : 0, curr->type, wasm); + *currp = getDroppedChildrenAndAppend(curr, + wasm, + getPassOptions(), + value, + DropMode::IgnoreParentEffects); } // Merge incoming data to a block, by looking at the data arriving from each From 3d4a7bb7a5d9cbd255c5840e5d7c86f99bb7750c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 16:29:14 -0700 Subject: [PATCH 097/202] go --- src/passes/RangeAnalysis.cpp | 14 +++++++------- test/gtest/constraint.cpp | 2 ++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index a478a6a79cb..7beb7422b13 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -242,13 +242,13 @@ struct RangeAnalysis // Merge incoming data to a block, by looking at the data arriving from each // of the predecessor blocks. - LocalSpans mergeIncoming(BasicBlock* block) { - LocalSpans localSpans; - // For each relevant local, merge its spans. + LocalConstraintMap mergeIncoming(BasicBlock* block) { + LocalConstraintMap constraints; + // For each relevant local, merge its constraints. for (auto local : relevantLocals) { - Span mergedSpan; + AndedConstraintSet merged; for (auto* pred : block->in) { - auto span = getSpanFromPredToSucc(pred, block, local); + auto constraints = getConstraintsFromPredToSucc(pred, block, local); if (span.isUnknown()) { // Unknown, so the entire merge is unknown. mergedSpan = Span::unknown(); @@ -263,12 +263,12 @@ struct RangeAnalysis } } } - return localSpans; + return constraints; } // Given a source (predecessor) and a target (successor) block, find the span // of a particular local as it arrives to that target from that successor. - Span getSpanFromPredToSucc(BasicBlock* pred, BasicBlock* block, Index local) { + Span getConstraintsFromPredToSucc(BasicBlock* pred, BasicBlock* block, Index local) { auto iter = pred->contents.endConstraints.find(local); if (iter == pred->contents.endConstraints.end()) { return Span::unknown(); diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index ff20252861e..18f5dde341b 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -118,3 +118,5 @@ TEST(ConstraintTest, TestSets) { // In reverse, we can infer nothing: knowing x != 10 does not say if x == 5. EXPECT_EQ(t.check(s), Unknown); } + +// TODO: test fuzzyOr From 3cabc2e9fd77ffdc0e3b5db398b3f658fd80fe13 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 16:32:47 -0700 Subject: [PATCH 098/202] go --- src/passes/RangeAnalysis.cpp | 89 ++---------------------------------- test/gtest/constraint.cpp | 2 +- 2 files changed, 5 insertions(+), 86 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 7beb7422b13..bf64ffe177b 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -246,21 +246,9 @@ struct RangeAnalysis LocalConstraintMap constraints; // For each relevant local, merge its constraints. for (auto local : relevantLocals) { - AndedConstraintSet merged; + AndedConstraintSet& merged = constraints[local]; for (auto* pred : block->in) { - auto constraints = getConstraintsFromPredToSucc(pred, block, local); - if (span.isUnknown()) { - // Unknown, so the entire merge is unknown. - mergedSpan = Span::unknown(); - break; - } - if (mergedSpan.isUnknown()) { - // This is the first item. Copy it. - mergedSpan = span; - } else { - // Merge in new data alongside existing. - mergedSpan = merge(mergedSpan, span); - } + merged.fuzzyOr(getConstraintsFromPredToSucc(pred, block, local)); } } return constraints; @@ -268,85 +256,16 @@ struct RangeAnalysis // Given a source (predecessor) and a target (successor) block, find the span // of a particular local as it arrives to that target from that successor. - Span getConstraintsFromPredToSucc(BasicBlock* pred, BasicBlock* block, Index local) { + AndedConstraintSet getConstraintsFromPredToSucc(BasicBlock* pred, BasicBlock* block, Index local) { auto iter = pred->contents.endConstraints.find(local); if (iter == pred->contents.endConstraints.end()) { - return Span::unknown(); + return {}; } // TODO: use conditional branching to send different values along branches return iter->second; } - enum MinMax { Min, Max }; - - // Merge two spans. This is a merge of two spans from two different - // predecessor blocks, so the result is a span large enough to contain them - // both, as all values in either one are possible. - // - // Note that this is a monotonic operation, to avoid infinite loops. - Span merge(Span a, Span b) { - return Span{merge(a.min, b.min, Min), merge(a.max, b.max, Max)}; - } - - Value merge(Value a, Value b, MinMax op) { - Value ret; - std::visit( - overloaded{ - [&](Literal& aLit) { - std::visit(overloaded{ - [&](Literal& bLit) { - if (aLit == bLit) { - // Equal literals. - ret = a; - } else if (aLit.type.isNumber()) { - // Numbers can be ordered. - assert(bLit.type == aLit.type); - if (aLit.le(bLit).getUnsigned()) { - ret = (op == Min) ? a : b; - } else { - ret = (op == Min) ? b : a; - } - } else { - // Anything else (function reference, etc.) - // is unknown. - ret = Value::unknown(); - } - }, - [&](Index& bLocal) { - // Mix of literal and local. We don't know - // what to make of this. - // TODO: consider trees of constraints and - // using a solver - ret = Value::unknown(); - }, - [&](Unknown& unknown) { ret = Value::unknown(); }, - }, - b); - }, - [&](Index& aLocal) { - std::visit(overloaded{ - [&](Literal& bLit) { - // Mix of literal and local, as above. - ret = Value::unknown(); - }, - [&](Index& bLocal) { - // Two locals. If equal, we know the outcome. - ret = (aLocal == bLocal) ? a : Value::unknown(); - }, - [&](Unknown& unknown) { ret = Value::unknown(); }, - }, - b); - }, - [&](Unknown& unknown) { - // It doesn't even matter what b is. - ret = Value::unknown(); - }, - }, - a); - return ret; - } - // Given an expression, apply it to the constraints. For example, a local.set // sets the value for that local. void applyToConstraints(Expression* curr, LocalConstraintMap& constraints) { diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 18f5dde341b..709c8f7d914 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -119,4 +119,4 @@ TEST(ConstraintTest, TestSets) { EXPECT_EQ(t.check(s), Unknown); } -// TODO: test fuzzyOr +// TODO: test fuzzyOr. in particular, empty merged with X is X, and flipped From 690a5abcda6a53e3436254a2f8a7bdd63636e2eb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 16:34:21 -0700 Subject: [PATCH 099/202] go --- src/ir/constraint.cpp | 2 +- src/passes/RangeAnalysis.cpp | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index ac00b2b91ec..f438695755c 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -139,7 +139,7 @@ std::optional LocalConstraint::parse(Binary* curr) { for (auto op : {Abstract::Eq, Abstract::Ne}) { if (Abstract::getBinary(curr->type, op) == curr->op) { auto value = rightGet ? Value(rightGet->index) : Value(*rightConstant); - return LocalConstraint{leftGet->index, Constraint{op, value}; + return LocalConstraint{leftGet->index, Constraint{op, value}}; } } return {}; diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index bf64ffe177b..8a3d39c422a 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -217,13 +217,13 @@ struct RangeAnalysis Expression** currp, BasicBlock* block, const LocalConstraintMap& constraints) { - auto parsed = constraint::parse(curr); + auto parsed = LocalConstraint::parse(curr); if (!parsed) { return; } - auto& localConstraints = constraints[parsed.local]; - Result result = localConstraints.check(parsed.constraint_); + auto& localConstraints = constraints[parsed->local]; + Result result = localConstraints.check(parsed->constraint); 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 @@ -269,16 +269,16 @@ struct RangeAnalysis // Given an expression, apply it to the constraints. For example, a local.set // sets the value for that local. void applyToConstraints(Expression* curr, LocalConstraintMap& constraints) { - auto parsed = constraint::parse(curr); + auto parsed = LocalConstraint::parse(curr); if (!parsed) { return; } // Add the constraint, if we can. TODO: perhaps be smart about which are // more useful, rather than just drop later ones? - auto& localConstraints = constraints[parsed.local]; + auto& localConstraints = constraints[parsed->local]; if (!localConstraints.full()) { - localConstraints.and_(parsed.constraint_); + localConstraints.and_(parsed->constraint_); } } }; From 840f863303dacf8762a839eb0139902d13ab7509 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 16:40:28 -0700 Subject: [PATCH 100/202] go --- src/ir/constraint.cpp | 18 ++++++++++++------ src/ir/constraint.h | 2 +- src/passes/RangeAnalysis.cpp | 14 +++++++++----- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index f438695755c..bcd0f5f8881 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -118,18 +118,24 @@ void AndedConstraintSet::fuzzyOr(const AndedConstraintSet& other) { clear(); } -std::optional LocalConstraint::parse(Binary* curr) { +std::optional LocalConstraint::parse(Expression* curr) { + auto* binary = curr->dynCast(); + if (!binary) { + // TODO: unary etc. + return {}; + } + // The left must be a get. - auto* leftGet = curr->left->dynCast(); + auto* leftGet = binary->left->dynCast(); if (!leftGet) { return {}; } // The right must be a get or a constant. - auto* rightGet = curr->right->dynCast(); + auto* rightGet = binary->right->dynCast(); std::optional rightConstant; - if (Properties::isSingleConstantExpression(curr->right)) { - rightConstant = Properties::getLiteral(curr->right); + if (Properties::isSingleConstantExpression(binary->right)) { + rightConstant = Properties::getLiteral(binary->right); } if (!rightGet && !rightConstant) { return {}; @@ -137,7 +143,7 @@ std::optional LocalConstraint::parse(Binary* curr) { // The operation must be one we recognize. for (auto op : {Abstract::Eq, Abstract::Ne}) { - if (Abstract::getBinary(curr->type, op) == curr->op) { + if (Abstract::getBinary(binary->type, op) == binary->op) { auto value = rightGet ? Value(rightGet->index) : Value(*rightConstant); return LocalConstraint{leftGet->index, Constraint{op, value}}; } diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 9fc0fe96305..71245913a5e 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -150,7 +150,7 @@ struct LocalConstraint { // // LocalConstraint($r, { x == 10 }) // - static std::optional parse(Binary* curr); + static std::optional parse(Expression* curr); }; } // namespace wasm::constraint diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 8a3d39c422a..a8a5c70deaa 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -32,6 +32,7 @@ #include "cfg/cfg-traversal.h" #include "ir/constraint.h" +#include "ir/drop.h" #include "ir/literal-utils.h" #include "ir/local-graph.h" #include "ir/properties.h" @@ -203,17 +204,16 @@ struct RangeAnalysis applyToConstraints(*currp, constraints); - if (auto* binary = curr->dynCast()) { - optimizeBinary(binary, currp, block.get(), constraints); + optimizeExpression(binary, currp, block.get(), constraints); } // TODO unary } } } - // Given a binary and its block, try to optimize it. We provide the pointer + // Given a binary XXXand its block, try to optimize it. We provide the pointer // to the binary, so that it can be replaced if optimizable. - void optimizeBinary(Binary* curr, + void optimizeExpression(Expression* curr, Expression** currp, BasicBlock* block, const LocalConstraintMap& constraints) { @@ -222,7 +222,11 @@ struct RangeAnalysis return; } - auto& localConstraints = constraints[parsed->local]; + auto iter = constraints.find(parsed->local); + if (iter == constraints.end()) { + return; + } + auto& localConstraints = iter->second; Result result = localConstraints.check(parsed->constraint); if (result == Unknown) { // If we parsed something using two locals, like x != y, we can also look From e8a4a802326bf9ea0fb49ef715939936317cf18e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Jun 2026 16:42:36 -0700 Subject: [PATCH 101/202] go --- src/ir/constraint.h | 4 ++++ src/passes/RangeAnalysis.cpp | 13 ++++--------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 71245913a5e..a57d4a92ccd 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -92,6 +92,10 @@ struct AndedConstraintSet : std::inplace_vector { return result; } + bool full() const { + return size() == MaxConstraints; + } + // Add a constraint to the set, ANDed with the others. The caller must make // sure not to add too many. void and_(const Constraint& c) { push_back(c); } diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index a8a5c70deaa..d5ccd1ddba9 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -200,23 +200,18 @@ struct RangeAnalysis LocalConstraintMap constraints = mergeIncoming(block.get()); for (auto** currp : block->contents.actions) { - auto* curr = *currp; - applyToConstraints(*currp, constraints); - - optimizeExpression(binary, currp, block.get(), constraints); - } - // TODO unary + optimizeExpression(currp, block.get(), constraints); } } } // Given a binary XXXand its block, try to optimize it. We provide the pointer // to the binary, so that it can be replaced if optimizable. - void optimizeExpression(Expression* curr, - Expression** currp, + void optimizeExpression(Expression** currp, BasicBlock* block, const LocalConstraintMap& constraints) { + auto* curr = *currp; auto parsed = LocalConstraint::parse(curr); if (!parsed) { return; @@ -282,7 +277,7 @@ struct RangeAnalysis // more useful, rather than just drop later ones? auto& localConstraints = constraints[parsed->local]; if (!localConstraints.full()) { - localConstraints.and_(parsed->constraint_); + localConstraints.and_(parsed->constraint); } } }; From bd777522bf2abb7910535c4505d80df6ee62e4f5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 13:03:22 -0700 Subject: [PATCH 102/202] go --- src/ir/constraint.h | 8 ++++---- test/gtest/constraint.cpp | 25 ++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index a57d4a92ccd..912feeb3d7e 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -113,9 +113,9 @@ struct AndedConstraintSet : std::inplace_vector { // // That is, if X or Y is true, the result of fuzzOr is also true. But the // reverse is not always the case: fuzzyOr may be true without X || Y being - // true (see the truth table linked above). + // true (see the truth table linked above, and the value 8 in the example). // - // Returning to the example above, we can use this to optimize as follows: if + // Returning to the example we can use this to optimize as follows: if // two code paths reaching a location have x == 5 and x == 10, so the value in // the merge location is either 5 or 10, then if we see some i32.ge_s that // does x >= 0 then we can evaluate it with check(): @@ -129,11 +129,11 @@ struct AndedConstraintSet : std::inplace_vector { // { x >= 0 } // // Note that the fuzziness here means that fuzzyOr() can do a better or a - // worse job. It is always valid for fuzzOr to return { x == x } or any other + // worse job. It is always valid for fuzzOr to return { } or any other // always-true thing (see the truth table linked above). But then: // // { x == 5 || x == 10 } => - // { x == x } =!!> + // { } =!!> // { x >= 0 } // // If we become too fuzzy, we lose the ability to imply anything useful. diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 709c8f7d914..c8dd9148e36 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -119,4 +119,27 @@ TEST(ConstraintTest, TestSets) { EXPECT_EQ(t.check(s), Unknown); } -// TODO: test fuzzyOr. in particular, empty merged with X is X, and flipped +TEST(ConstraintTest, TestOr) { + // { x == 5 } + AndedConstraintSet s; + s.and_(Constraint{Eq, Literal(int32_t(5))}); + + // { } + AndedConstraintSet empty; + + // Anything ORed with the empty set is unchanged. + auto t = s; + t.fuzzyOr(empty); + EXPECT_EQ(t, s); + + // Flipped. + t = empty; + t.fuzzyOr(s); + EXPECT_EQ(t, s); + + // ORing with oneself changes nothing + t = s; + t.fuzzyOr(s); + EXPECT_EQ(t, s); +} + From 94a3c35574d83713bd508aed1df4dbd051e75853 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 13:09:56 -0700 Subject: [PATCH 103/202] go --- src/ir/constraint.cpp | 10 ++++++++++ test/gtest/constraint.cpp | 3 +++ 2 files changed, 13 insertions(+) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index bcd0f5f8881..5eb3528cdaa 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -99,6 +99,16 @@ Result AndedConstraintSet::check(const Constraint& condition) const { } void AndedConstraintSet::fuzzyOr(const AndedConstraintSet& other) { + // If one is empty (no constraints, everything is true, and we can prove + // nothing useful) then it does not add anything to the other. + if (empty()) { + *this = other; + return; + } + if (other.empty()) { + return; + } + // If this is already implied by current constraints, then it is redundant. // E.g. if we are { x = 10 } and other is { x >= 0 } then all we need is // { x >= 0 } as the result of the OR. diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index c8dd9148e36..6551c4fb05a 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -143,3 +143,6 @@ TEST(ConstraintTest, TestOr) { EXPECT_EQ(t, s); } +// TODO: test a fuzzyOr of { x = 10 } and { x >= 0 }, once we support +// inequalities + From 4c5b867a272ffdcbc52233354d006723c853e422 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 13:12:53 -0700 Subject: [PATCH 104/202] go --- test/gtest/constraint.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 6551c4fb05a..9ea2915c20b 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -119,7 +119,7 @@ TEST(ConstraintTest, TestSets) { EXPECT_EQ(t.check(s), Unknown); } -TEST(ConstraintTest, TestOr) { +TEST(ConstraintTest, TestOrTrivial) { // { x == 5 } AndedConstraintSet s; s.and_(Constraint{Eq, Literal(int32_t(5))}); @@ -143,6 +143,26 @@ TEST(ConstraintTest, TestOr) { EXPECT_EQ(t, s); } +TEST(ConstraintTest, TestOrImplies) { + // { x == 5 } + AndedConstraintSet s; + s.and_(Constraint{Eq, Literal(int32_t(5))}); + + // { x != 10 } + AndedConstraintSet t; + t.and_(Constraint{Ne, Literal(int32_t(10))}); + + // ORing these leaves us with x != 10. + auto u = s; + u.fuzzyOr(t); + EXPECT_EQ(u, t); + + // Flipped. + u = t; + u.fuzzyOr(s); + EXPECT_EQ(u, t); +} + // TODO: test a fuzzyOr of { x = 10 } and { x >= 0 }, once we support // inequalities From f828d9d2729f94535d73e89e007e583785075938 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 13:13:16 -0700 Subject: [PATCH 105/202] format --- src/ir/constraint.h | 4 +--- src/passes/RangeAnalysis.cpp | 18 +++++++++--------- test/gtest/constraint.cpp | 1 - 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 912feeb3d7e..b2fe6c17bb8 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -92,9 +92,7 @@ struct AndedConstraintSet : std::inplace_vector { return result; } - bool full() const { - return size() == MaxConstraints; - } + bool full() const { return size() == MaxConstraints; } // Add a constraint to the set, ANDed with the others. The caller must make // sure not to add too many. diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index d5ccd1ddba9..96a0242c188 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -209,8 +209,8 @@ struct RangeAnalysis // Given a binary XXXand its block, try to optimize it. We provide the pointer // to the binary, so that it can be replaced if optimizable. void optimizeExpression(Expression** currp, - BasicBlock* block, - const LocalConstraintMap& constraints) { + BasicBlock* block, + const LocalConstraintMap& constraints) { auto* curr = *currp; auto parsed = LocalConstraint::parse(curr); if (!parsed) { @@ -231,12 +231,10 @@ struct RangeAnalysis // We know the result! auto& wasm = *getModule(); - auto value = LiteralUtils::makeFromInt32(result == True ? 1 : 0, curr->type, wasm); - *currp = getDroppedChildrenAndAppend(curr, - wasm, - getPassOptions(), - value, - DropMode::IgnoreParentEffects); + auto value = + LiteralUtils::makeFromInt32(result == True ? 1 : 0, curr->type, wasm); + *currp = getDroppedChildrenAndAppend( + curr, wasm, getPassOptions(), value, DropMode::IgnoreParentEffects); } // Merge incoming data to a block, by looking at the data arriving from each @@ -255,7 +253,9 @@ struct RangeAnalysis // Given a source (predecessor) and a target (successor) block, find the span // of a particular local as it arrives to that target from that successor. - AndedConstraintSet getConstraintsFromPredToSucc(BasicBlock* pred, BasicBlock* block, Index local) { + AndedConstraintSet getConstraintsFromPredToSucc(BasicBlock* pred, + BasicBlock* block, + Index local) { auto iter = pred->contents.endConstraints.find(local); if (iter == pred->contents.endConstraints.end()) { return {}; diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 9ea2915c20b..0c9031db116 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -165,4 +165,3 @@ TEST(ConstraintTest, TestOrImplies) { // TODO: test a fuzzyOr of { x = 10 } and { x >= 0 }, once we support // inequalities - From 603629654a414fe9fee3d5628bc64e0c750b51c6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 13:22:29 -0700 Subject: [PATCH 106/202] go --- test/lit/passes/range-analysis.wast | 79 +++++++++++++---------------- 1 file changed, 35 insertions(+), 44 deletions(-) diff --git a/test/lit/passes/range-analysis.wast b/test/lit/passes/range-analysis.wast index 95fe57c1718..3224a5c7746 100644 --- a/test/lit/passes/range-analysis.wast +++ b/test/lit/passes/range-analysis.wast @@ -7,67 +7,58 @@ ;; RUN: wasm-opt %s --optimize-instructions --range-analysis -all -S -o - | filecheck %s (module - (func $simple (param $p i32) + ;; CHECK: (func $simple (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $simple (local $x i32) - ;; TODO: handle a fallthrough - (if - (local.get $p) - (then - (local.set $x - (i32.const 10) - ) - ) - (else - (local.set $x - (i32.const 20) - ) - ) - ) - ;; $x is in [10, 20], so it is less than 30 (strictly, and not) - (drop - (i32.lt_u - (local.get $x) - (i32.const 30) - ) - ) - (drop - (i32.le_u - (local.get $x) - (i32.const 30) - ) - ) - ;; And less than 21. - (drop - (i32.lt_u - (local.get $x) - (i32.const 21) - ) + ;; Set x to 10, and then compare it to 10 and 20 using == and != + (local.set $x + (i32.const 10) ) - ;; And less or equal to 20. (drop - (i32.le_u + (i32.eq (local.get $x) - (i32.const 20) + (i32.const 10) ) ) - ;; But *not* strictly less than 20. (drop - (i32.lt_u + (i32.eq (local.get $x) (i32.const 20) ) ) - ;; And not less (or equal) to 19. (drop - (i32.le_u + (i32.ne (local.get $x) - (i32.const 19) + (i32.const 10) ) ) (drop - (i32.lt_u + (i32.ne (local.get $x) - (i32.const 19) + (i32.const 20) ) ) ) From 889658da823886309a8d24042e1e275801a59d28 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 13:23:38 -0700 Subject: [PATCH 107/202] go --- test/lit/passes/range-analysis.wast | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/test/lit/passes/range-analysis.wast b/test/lit/passes/range-analysis.wast index 3224a5c7746..898ce0042b2 100644 --- a/test/lit/passes/range-analysis.wast +++ b/test/lit/passes/range-analysis.wast @@ -1,10 +1,11 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. -;; Test range-analysis. We run optimize-instructions first as it canonicalizes +;; Test range-analysis. We also run optimize-instructions first as it canonicalizes ;; the other of binaries etc., and we want range-analysis to always work on the -;; canonicalized form. +;; canonicalized form. TODO -;; RUN: wasm-opt %s --optimize-instructions --range-analysis -all -S -o - | filecheck %s +;; RUN: wasm-opt %s --range-analysis -all -S -o - | filecheck %s +;; R;U;N: wasm-opt %s --optimize-instructions --range-analysis -all -S -o - | filecheck %s (module ;; CHECK: (func $simple (type $0) @@ -19,7 +20,10 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.ne @@ -28,7 +32,10 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $simple From 724ecb9f2e443a3cd67c0cd8094723f622a6f7c0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 13:30:33 -0700 Subject: [PATCH 108/202] go --- src/passes/RangeAnalysis.cpp | 4 +++- test/lit/passes/range-analysis.wast | 20 ++++---------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 96a0242c188..b43677da4d8 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -74,7 +74,6 @@ struct RangeAnalysis using Super = WalkerPass, Info>>; -#if 0 // Branches outside of the function can be ignored, as we only look at local // state in the function. bool ignoreBranchesOutsideOfFunc = true; @@ -91,6 +90,7 @@ struct RangeAnalysis void visitUnary(Unary* curr) { addAction(); } // XXX needed? void visitBinary(Binary* curr) { addAction(); } +#if 0 // Track the branches we reason about. CFGWalker builds a CFG, and we want to // add information on top of that about which branch is due to which // instruction. For example, if block A branches to B and C, we want to know @@ -155,6 +155,8 @@ struct RangeAnalysis // Values can flow between locals: if x is relevant, and y is written to it, // we must consider y relevant too. TODO? +std::cout << relevantLocals.size() << '\n'; + if (!relevantLocals.empty()) { flow(); optimize(); diff --git a/test/lit/passes/range-analysis.wast b/test/lit/passes/range-analysis.wast index 898ce0042b2..52c7cfb0656 100644 --- a/test/lit/passes/range-analysis.wast +++ b/test/lit/passes/range-analysis.wast @@ -14,28 +14,16 @@ ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.eq - ;; CHECK-NEXT: (local.get $x) - ;; 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 $x) - ;; CHECK-NEXT: (i32.const 20) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.ne - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 10) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.ne - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 20) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $simple From 0fc9e55f2f2c42dd8efbdb637af431bb9f4ec3a1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 14:07:09 -0700 Subject: [PATCH 109/202] go --- src/passes/ConstantFieldPropagation.cpp | 5 +- src/support/inplace_vector.h | 193 ++++++++++++++++++++++++ 2 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 src/support/inplace_vector.h diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index 66f45bb6047..93d1ac7c567 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -60,6 +60,7 @@ #include "ir/utils.h" #include "pass.h" #include "support/hash.h" +#include "support/inplace_vector.h" #include "support/small_vector.h" #include "support/unique_deferring_queue.h" #include "wasm-builder.h" @@ -311,9 +312,7 @@ struct FunctionOptimizer : public WalkerPass> { // values = [ { 42, [A, B] }, { 1337, [C] } ]; struct Value { PossibleConstantValues constant; - // Use a SmallVector as we'll only have 2 Values, and so the stack usage - // here is fixed. - SmallVector types; + std::inplace_vector types; // Whether this slot is used. If so, |constant| has a value, and |types| // is not empty. diff --git a/src/support/inplace_vector.h b/src/support/inplace_vector.h new file mode 100644 index 00000000000..0df08dd8c5e --- /dev/null +++ b/src/support/inplace_vector.h @@ -0,0 +1,193 @@ +/* + * Copyright 2026 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// A vector of elements with a maximum size, storing them all in-place. This is +// similar to C++26's inplace_vector, and is basically a small_vector, except +// there is never any dynamic storage. +// + +#ifndef wasm_support_inplace_vector_h +#define wasm_support_inplace_vector_h + +#include +#include +#include + +#include "support/parent_index_iterator.h" + +namespace std { + +template class inplace_vector { + // fixed-space storage + size_t usedFixed = 0; + std::array fixed{}; + +public: + using value_type = T; + + inplace_vector() {} + inplace_vector(const inplace_vector& other) + : usedFixed(other.usedFixed), fixed(other.fixed) {} + inplace_vector(inplace_vector&& other) + : usedFixed(other.usedFixed), fixed(std::move(other.fixed)) {} + inplace_vector(std::initializer_list init) { + for (T item : init) { + push_back(item); + } + } + inplace_vector(size_t initialSize) { resize(initialSize); } + + inplace_vector& operator=(const inplace_vector& other) { + usedFixed = other.usedFixed; + fixed = other.fixed; + return *this; + } + + inplace_vector& operator=(inplace_vector&& other) { + usedFixed = other.usedFixed; + fixed = std::move(other.fixed); + return *this; + } + + T& operator[](size_t i) { return fixed[i]; } + + const T& operator[](size_t i) const { + return const_cast&>(*this)[i]; + } + + void push_back(const T& x) { + assert(usedFixed < N); + fixed[usedFixed++] = x; + } + + template void emplace_back(ArgTypes&&... Args) { + assert(usedFixed < N); + new (&fixed[usedFixed++]) T(std::forward(Args)...); + } + + void pop_back() { + assert(usedFixed > 0); + usedFixed--; + } + + T& back() { + assert(usedFixed > 0); + return fixed[usedFixed - 1]; + } + + const T& back() const { + assert(usedFixed > 0); + return fixed[usedFixed - 1]; + } + + size_t size() const { return usedFixed; } + + bool empty() const { return size() == 0; } + + void clear() { usedFixed = 0; } + + void resize(size_t newSize) { + assert(newSize <= N); + usedFixed = newSize; + } + + size_t capacity() const { return N; } + + bool operator==(const inplace_vector& other) const { + if (usedFixed != other.usedFixed) { + return false; + } + for (size_t i = 0; i < usedFixed; i++) { + if (fixed[i] != other.fixed[i]) { + return false; + } + } + return true; + } + + bool operator!=(const inplace_vector& other) const { + return !(*this == other); + } + + // iteration + + struct Iterator : wasm::ParentIndexIterator*, Iterator> { + using value_type = T; + using pointer = T*; + using reference = T&; + + Iterator(inplace_vector* parent, size_t index) + : wasm::ParentIndexIterator*, Iterator>{parent, + index} {} + Iterator(const Iterator& other) = default; + + T& operator*() { return (*this->parent)[this->index]; } + }; + + struct ConstIterator + : wasm::ParentIndexIterator*, ConstIterator> { + using value_type = const T; + using pointer = const T*; + using reference = const T&; + + ConstIterator(const inplace_vector* parent, size_t index) + : wasm::ParentIndexIterator*, ConstIterator>{ + parent, index} {} + ConstIterator(const ConstIterator& other) = default; + + const T& operator*() const { return (*this->parent)[this->index]; } + }; + + Iterator begin() { return Iterator(this, 0); } + Iterator end() { return Iterator(this, size()); } + ConstIterator begin() const { return ConstIterator(this, 0); } + ConstIterator end() const { return ConstIterator(this, size()); } + + void erase(Iterator a, Iterator b) { + // Atm we only support erasing at the end, which is very efficient. + assert(b == end()); + resize(a.index); + } +}; + +// A inplace_vector for which some values may be read before they are written, +// and in that case they have the value zero. +template +struct ZeroInitinplace_vector : public inplace_vector { + T& operator[](size_t i) { + if (i >= this->size()) { + resize(i + 1); + } + return inplace_vector::operator[](i); + } + + const T& operator[](size_t i) const { + return const_cast&>(*this)[i]; + } + + void resize(size_t newSize) { + auto oldSize = this->size(); + inplace_vector::resize(newSize); + for (size_t i = oldSize; i < this->size(); i++) { + (*this)[i] = 0; + } + } +}; + +} // namespace std + +#endif // wasm_support_inplace_vector_h From 5ad1b7511ec258bc946f8b68024c9994dafff8ce Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 14:09:53 -0700 Subject: [PATCH 110/202] go --- test/gtest/CMakeLists.txt | 1 + test/gtest/inplace_vector.cpp | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 test/gtest/inplace_vector.cpp diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index 05ce44006a6..4bd358032d7 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -17,6 +17,7 @@ set(unittest_SOURCES int128.cpp leaves.cpp glbs.cpp + inplace_vector.cpp interpreter.cpp intervals.cpp istring.cpp diff --git a/test/gtest/inplace_vector.cpp b/test/gtest/inplace_vector.cpp new file mode 100644 index 00000000000..8fb2307dfe8 --- /dev/null +++ b/test/gtest/inplace_vector.cpp @@ -0,0 +1,9 @@ +#include "support/inplace_vector.h" +#include "gtest/gtest.h" + +using InplaceVectorTest = ::testing::Test; + +TEST_F(InplaceVectorTest, Basics) { + std::inplace_vector vec; + EXPECT_EQ(sizeof(vec), sizeof(size_t) + 10 * sizeof(int64_t)); +} From 81813634c5acdc2bf9e7916311923cdd4bfa0510 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 14:14:36 -0700 Subject: [PATCH 111/202] go --- src/support/inplace_vector.h | 24 ------------------------ test/gtest/inplace_vector.cpp | 31 ++++++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/support/inplace_vector.h b/src/support/inplace_vector.h index 0df08dd8c5e..95b123a3eec 100644 --- a/src/support/inplace_vector.h +++ b/src/support/inplace_vector.h @@ -164,30 +164,6 @@ template class inplace_vector { } }; -// A inplace_vector for which some values may be read before they are written, -// and in that case they have the value zero. -template -struct ZeroInitinplace_vector : public inplace_vector { - T& operator[](size_t i) { - if (i >= this->size()) { - resize(i + 1); - } - return inplace_vector::operator[](i); - } - - const T& operator[](size_t i) const { - return const_cast&>(*this)[i]; - } - - void resize(size_t newSize) { - auto oldSize = this->size(); - inplace_vector::resize(newSize); - for (size_t i = oldSize; i < this->size(); i++) { - (*this)[i] = 0; - } - } -}; - } // namespace std #endif // wasm_support_inplace_vector_h diff --git a/test/gtest/inplace_vector.cpp b/test/gtest/inplace_vector.cpp index 8fb2307dfe8..ad0f2627c0b 100644 --- a/test/gtest/inplace_vector.cpp +++ b/test/gtest/inplace_vector.cpp @@ -3,7 +3,36 @@ using InplaceVectorTest = ::testing::Test; -TEST_F(InplaceVectorTest, Basics) { +TEST_F(InplaceVectorTest, Size) { std::inplace_vector vec; EXPECT_EQ(sizeof(vec), sizeof(size_t) + 10 * sizeof(int64_t)); } + +TEST_F(InplaceVectorTest, Basics) { + std::inplace_vector vec; + + vec.push_back(10); + EXPECT_EQ(vec[0], 10); + + vec.resize(3); + EXPECT_EQ(vec.size(), 3); + + vec[1] = 20; + vec[2] = 30; + EXPECT_EQ(vec[1], 20); + EXPECT_EQ(vec[2], 30); + + vec.pop_back(); + EXPECT_EQ(vec.size(), 2); +} + +TEST_F(InplaceVectorTest, I) { + std::inplace_vector vec{10, 20, 30}; + std::vector normal; + + for (auto x : vec) { + normal.push_back(x); + } + + EXPECT_EQ(normal, std::vector({10, 20, 30})); +} From d0ad2f48cc4cb06fa9507457816217eded05548c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 14:14:43 -0700 Subject: [PATCH 112/202] go --- test/gtest/inplace_vector.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/gtest/inplace_vector.cpp b/test/gtest/inplace_vector.cpp index ad0f2627c0b..bf651b096e7 100644 --- a/test/gtest/inplace_vector.cpp +++ b/test/gtest/inplace_vector.cpp @@ -18,7 +18,7 @@ TEST_F(InplaceVectorTest, Basics) { EXPECT_EQ(vec.size(), 3); vec[1] = 20; - vec[2] = 30; + vec[2] = 30; EXPECT_EQ(vec[1], 20); EXPECT_EQ(vec[2], 30); From c5b7d1d2752dc3b731328560f0f6e08b730e9d38 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 14:15:38 -0700 Subject: [PATCH 113/202] go --- src/support/inplace_vector.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/support/inplace_vector.h b/src/support/inplace_vector.h index 95b123a3eec..a4971c8b2b4 100644 --- a/src/support/inplace_vector.h +++ b/src/support/inplace_vector.h @@ -16,8 +16,9 @@ // // A vector of elements with a maximum size, storing them all in-place. This is -// similar to C++26's inplace_vector, and is basically a small_vector, except +// similar to c++26's inplace_vector, and is basically a small_vector, except // there is never any dynamic storage. +// TODO: remove when we have c++26 // #ifndef wasm_support_inplace_vector_h From 0e35b2b857e1176ffc8141a74501f455e4052414 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 14:16:42 -0700 Subject: [PATCH 114/202] go --- test/gtest/inplace_vector.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/test/gtest/inplace_vector.cpp b/test/gtest/inplace_vector.cpp index bf651b096e7..10c46b1f633 100644 --- a/test/gtest/inplace_vector.cpp +++ b/test/gtest/inplace_vector.cpp @@ -5,6 +5,7 @@ using InplaceVectorTest = ::testing::Test; TEST_F(InplaceVectorTest, Size) { std::inplace_vector vec; + // An inplace_vector is just a size plus the in-place storage. EXPECT_EQ(sizeof(vec), sizeof(size_t) + 10 * sizeof(int64_t)); } From 60d50b4b9b7c8f99c44f8168c52903b16c05f90f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 14:38:45 -0700 Subject: [PATCH 115/202] go --- src/ir/CMakeLists.txt | 1 + src/ir/abstract.h | 3 +- src/ir/constraint.cpp | 164 +++++++++++++++++++++++++++++++++++++ src/ir/constraint.h | 160 ++++++++++++++++++++++++++++++++++++ test/gtest/CMakeLists.txt | 1 + test/gtest/constraint.cpp | 167 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 495 insertions(+), 1 deletion(-) create mode 100644 src/ir/constraint.cpp create mode 100644 src/ir/constraint.h create mode 100644 test/gtest/constraint.cpp diff --git a/src/ir/CMakeLists.txt b/src/ir/CMakeLists.txt index 919069770c5..5521529cd91 100644 --- a/src/ir/CMakeLists.txt +++ b/src/ir/CMakeLists.txt @@ -2,6 +2,7 @@ FILE(GLOB ir_HEADERS *.h) set(ir_SOURCES ExpressionAnalyzer.cpp ExpressionManipulator.cpp + constraint.cpp drop.cpp effects.cpp eh-utils.cpp diff --git a/src/ir/abstract.h b/src/ir/abstract.h index 04b04e34223..d0f3794908c 100644 --- a/src/ir/abstract.h +++ b/src/ir/abstract.h @@ -56,7 +56,8 @@ enum Op { GtS, GtU, GeS, - GeU + GeU, + Invalid }; inline bool hasAnyRotateShift(BinaryOp op) { diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp new file mode 100644 index 00000000000..5eb3528cdaa --- /dev/null +++ b/src/ir/constraint.cpp @@ -0,0 +1,164 @@ +/* + * Copyright 2026 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "ir/constraint.h" +#include "ir/properties.h" +#include "wasm.h" + +namespace wasm::constraint { + +namespace { + +// Core comparison of two constraints. +// +// Returns a Result, or an empty option if we should keep working (i.e., a +// result of Unknown means we are certain we can just return Unknown). +std::optional checkPair(const Constraint& a, const Constraint& b) { + // A thing always implies itself. + if (a == b) { + return True; + } + + // Comparisons of two constants. + if (auto* aConstant = std::get_if(&a.value)) { + if (auto* bConstant = std::get_if(&b.value)) { + switch (a.op) { + case Abstract::Eq: { + switch (b.op) { + case Abstract::Eq: { + // x == c vs x == c', and we already handled full equality + // earlier, hence c != c', and we found a contradiction. + assert(*aConstant != *bConstant); + return False; + } + case Abstract::Ne: { + // x == c vs x != c'. We can infer the result based on relating c + // and c'. + return *aConstant != *bConstant ? True : False; + } + default: { + } + } + } + case Abstract::Ne: { + switch (b.op) { + case Abstract::Eq: { + // x != c vs x == c'. If c == c', we can infer. + if (*aConstant == *bConstant) { + return False; + } + return {}; + } + case Abstract::Ne: { + // x == c vs x == c', and we already handled full equality + // earlier, hence c != c', and we can infer nothing. + return {}; + } + default: { + } + } + } + default: { + } + } + } + } + + return {}; +} + +} // anonymous namespace + +Result AndedConstraintSet::check(const Constraint& condition) const { + // Sometimes a single constraint is enough to determine the condition. + for (auto& c : *this) { + if (auto result = checkPair(c, condition)) { + return *result; + } + } + + // TODO smarts for multiple constraints + + // Otherwise, who knows. + return Unknown; +} + +void AndedConstraintSet::fuzzyOr(const AndedConstraintSet& other) { + // If one is empty (no constraints, everything is true, and we can prove + // nothing useful) then it does not add anything to the other. + if (empty()) { + *this = other; + return; + } + if (other.empty()) { + return; + } + + // If this is already implied by current constraints, then it is redundant. + // E.g. if we are { x = 10 } and other is { x >= 0 } then all we need is + // { x >= 0 } as the result of the OR. + if (check(other) == True) { + *this = other; + return; + } + if (other.check(*this) == True) { + return; + } + + // TODO smarts + + // Otherwise, we don't know how to nicely OR these things, and expand to the + // trivial set of no constraints (i.e., where everything is true, and we can + // prove nothing). + clear(); +} + +std::optional LocalConstraint::parse(Expression* curr) { + auto* binary = curr->dynCast(); + if (!binary) { + // TODO: unary etc. + return {}; + } + + // The left must be a get. + auto* leftGet = binary->left->dynCast(); + if (!leftGet) { + return {}; + } + + // The right must be a get or a constant. + auto* rightGet = binary->right->dynCast(); + std::optional rightConstant; + if (Properties::isSingleConstantExpression(binary->right)) { + rightConstant = Properties::getLiteral(binary->right); + } + if (!rightGet && !rightConstant) { + return {}; + } + + // The operation must be one we recognize. + for (auto op : {Abstract::Eq, Abstract::Ne}) { + if (Abstract::getBinary(binary->type, op) == binary->op) { + auto value = rightGet ? Value(rightGet->index) : Value(*rightConstant); + return LocalConstraint{leftGet->index, Constraint{op, value}}; + } + } + return {}; +} + +} // namespace wasm::constraint diff --git a/src/ir/constraint.h b/src/ir/constraint.h new file mode 100644 index 00000000000..b2fe6c17bb8 --- /dev/null +++ b/src/ir/constraint.h @@ -0,0 +1,160 @@ +/* + * Copyright 2026 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Constraints on the values of locals, things like x >=0, x < 42, and x == y. + +#ifndef wasm_ir_constraint_h +#define wasm_ir_constraint_h + +#include + +#include "ir/abstract.h" +#include "support/inplace_vector.h" +#include "support/utilities.h" +#include "wasm.h" + +namespace wasm::constraint { + +// A value in a constraint, either a local index or literal value. +struct Value : public std::variant { + bool operator==(const Value&) const = default; +}; + +// A constraint: some operation and some value, like "is equal to 17" or "is +// less than local $z". +struct Constraint { + // The operation relating two values, and the values. + Abstract::Op op = Abstract::Invalid; + Value value; + + bool operator==(const Constraint&) const = default; + + operator bool() const { return op != Abstract::Invalid; } +}; + +// We limit constraints to a low number to ensure good performance even with +// simple brute-force solving. +// TODO: use a generic constraint solver..? +inline constexpr std::size_t MaxConstraints = 3; + +// What a constraint is known to be: true/false, or unknown. +enum Result { True, False, Unknown }; + +// A set of constraints connected by the logical "and" operation. That is, all +// the constraints are simultaneously true about some value. In the examples in +// the comments below, `x` is used for the thing all the constraints are talking +// about, but it could be a global or a struct field or anything else in +// general. +struct AndedConstraintSet : std::inplace_vector { + // Check a condition against this set, that is, whether the existing + // constraints prove that it must be true, false, or unknown: whether + // + // { this } => { condition } + // + // https://en.wikipedia.org/wiki/Material_conditional#Truth_table + Result check(const Constraint& condition) const; + + // Check an entire other set. + Result check(const AndedConstraintSet& other) const { + if (other.empty()) { + // The empty set of constraints is always true. + return True; + } + + Result result = Unknown; + for (auto& c : other) { + auto currResult = check(c); + if (currResult == Unknown) { + // If something is unknown, it all is. + return Unknown; + } + if (result == Unknown) { + // This is the first result + result = currResult; + } else if (result != currResult) { + // This is a later result, and different, so give up. + return Unknown; + } + } + return result; + } + + bool full() const { return size() == MaxConstraints; } + + // Add a constraint to the set, ANDed with the others. The caller must make + // sure not to add too many. + void and_(const Constraint& c) { push_back(c); } + + // Add constraints that are ORed. We cannot represent such a thing directly + // (we only use AND), so we approximate it in a fuzzy way. For example, this + // would be valid: + // + // fuzzyOr({ x == 5 }, { x == 10 }) == { x >= 5 && x <= 10 } + // + // Note how the result here still accepts the values 5 and 10, but it also + // allows more. Formally, this has the following mathematical property: + // + // (X || Y) => fuzzyOr(X, Y) + // + // That is, if X or Y is true, the result of fuzzOr is also true. But the + // reverse is not always the case: fuzzyOr may be true without X || Y being + // true (see the truth table linked above, and the value 8 in the example). + // + // Returning to the example we can use this to optimize as follows: if + // two code paths reaching a location have x == 5 and x == 10, so the value in + // the merge location is either 5 or 10, then if we see some i32.ge_s that + // does x >= 0 then we can evaluate it with check(): + // + // { x >= 5 && x <= 10 }.check({ x >= 0 }) == True + // + // And it is valid to optimize that i32.ge_s into a constant 1, since + // + // { x == 5 || x == 10 } => + // { x >= 5 && x <= 10 } => + // { x >= 0 } + // + // Note that the fuzziness here means that fuzzyOr() can do a better or a + // worse job. It is always valid for fuzzOr to return { } or any other + // always-true thing (see the truth table linked above). But then: + // + // { x == 5 || x == 10 } => + // { } =!!> + // { x >= 0 } + // + // If we become too fuzzy, we lose the ability to imply anything useful. + void fuzzyOr(const AndedConstraintSet& other); +}; + +// A local plus a constraint on it. +struct LocalConstraint { + Index local; + Constraint constraint; + + // 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); +}; + +} // namespace wasm::constraint + +#endif // wasm_ir_constraint_h diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index 05ce44006a6..6c2c9b56d68 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -9,6 +9,7 @@ set(unittest_SOURCES arena.cpp cast-check.cpp cfg.cpp + constraint.cpp dataflow.cpp delta_debugging.cpp dfa_minimization.cpp diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp new file mode 100644 index 00000000000..0c9031db116 --- /dev/null +++ b/test/gtest/constraint.cpp @@ -0,0 +1,167 @@ +#include "ir/constraint.h" +#include "ir/abstract.h" +#include "gtest/gtest.h" + +using namespace wasm; +using namespace wasm::Abstract; +using namespace wasm::constraint; + +TEST(ConstraintTest, TestEmpty) { + // An empty constraint is invalid. + Constraint c; + EXPECT_FALSE(c); +} + +TEST(ConstraintTest, TestEq) { + // Sets start empty. + AndedConstraintSet s; + EXPECT_TRUE(s.empty()); + + // x == 5 (we use "x" for the name of the thing being compared). + Constraint c{Eq, Literal(int32_t(5))}; + + // We can't infer anything using an empty set. + EXPECT_EQ(s.check(c), Unknown); + + // If we add it, then things check out. + s.and_(c); + EXPECT_EQ(s.size(), 1); + EXPECT_EQ(s.check(c), True); + + // x == 10, a different number: we can infer false. + EXPECT_EQ(s.check(Constraint{Eq, Literal(int32_t(10))}), False); + + // x != 15: we can infer true. + EXPECT_EQ(s.check(Constraint{Ne, Literal(int32_t(15))}), True); + + // x != 5: we can infer false. + EXPECT_EQ(s.check(Constraint{Ne, Literal(int32_t(5))}), False); +} + +TEST(ConstraintTest, TestNe) { + AndedConstraintSet s; + // x != 5 + Constraint c{Ne, Literal(int32_t(5))}; + s.and_(c); + + // Checks out versus itself. + EXPECT_EQ(s.check(c), True); + + // x == 10: we don't know. + EXPECT_EQ(s.check(Constraint{Eq, Literal(int32_t(10))}), Unknown); + + // x != 15: we don't know. + EXPECT_EQ(s.check(Constraint{Ne, Literal(int32_t(15))}), Unknown); + + // x == 5: we can infer false. + EXPECT_EQ(s.check(Constraint{Eq, Literal(int32_t(5))}), False); +} + +TEST(ConstraintTest, TestMulti) { + AndedConstraintSet s; + // x != 5 && x != 10 + Constraint c{Ne, Literal(int32_t(5))}; + Constraint d{Ne, Literal(int32_t(10))}; + s.and_(c); + s.and_(d); + + // Each checks out versus itself. + EXPECT_EQ(s.check(c), True); + EXPECT_EQ(s.check(d), True); + + // x == 5: false. + EXPECT_EQ(s.check(Constraint{Eq, Literal(int32_t(5))}), False); + + // x == 10: false. + EXPECT_EQ(s.check(Constraint{Eq, Literal(int32_t(10))}), False); + + // x == 15: we don't know. + EXPECT_EQ(s.check(Constraint{Eq, Literal(int32_t(15))}), Unknown); + + // x != 15: we don't know. + EXPECT_EQ(s.check(Constraint{Ne, Literal(int32_t(15))}), Unknown); +} + +TEST(ConstraintTest, TestSets) { + // x == 5 + Constraint c{Eq, Literal(int32_t(5))}; + + AndedConstraintSet s; + + // Any set always proves itself to be true. + EXPECT_EQ(s.check(s), True); + + // Ditto after adding something. + s.and_(c); + EXPECT_EQ(s.check(s), True); + + // Another set, empty. + AndedConstraintSet t; + + // Any set always proves an empty set to be true. + EXPECT_EQ(s.check(t), True); + + // Make both sets contain the same stuff. + t.and_(c); + EXPECT_EQ(s.check(t), True); + + // Now t has *different* stuff, x == 10, which given s is false. + t.clear(); + t.and_(Constraint{Eq, Literal(int32_t(10))}); + EXPECT_EQ(s.check(t), False); + + // Same, with x != 10. Now we know it is true. + t.clear(); + t.and_(Constraint{Ne, Literal(int32_t(10))}); + EXPECT_EQ(s.check(t), True); + + // In reverse, we can infer nothing: knowing x != 10 does not say if x == 5. + EXPECT_EQ(t.check(s), Unknown); +} + +TEST(ConstraintTest, TestOrTrivial) { + // { x == 5 } + AndedConstraintSet s; + s.and_(Constraint{Eq, Literal(int32_t(5))}); + + // { } + AndedConstraintSet empty; + + // Anything ORed with the empty set is unchanged. + auto t = s; + t.fuzzyOr(empty); + EXPECT_EQ(t, s); + + // Flipped. + t = empty; + t.fuzzyOr(s); + EXPECT_EQ(t, s); + + // ORing with oneself changes nothing + t = s; + t.fuzzyOr(s); + EXPECT_EQ(t, s); +} + +TEST(ConstraintTest, TestOrImplies) { + // { x == 5 } + AndedConstraintSet s; + s.and_(Constraint{Eq, Literal(int32_t(5))}); + + // { x != 10 } + AndedConstraintSet t; + t.and_(Constraint{Ne, Literal(int32_t(10))}); + + // ORing these leaves us with x != 10. + auto u = s; + u.fuzzyOr(t); + EXPECT_EQ(u, t); + + // Flipped. + u = t; + u.fuzzyOr(s); + EXPECT_EQ(u, t); +} + +// TODO: test a fuzzyOr of { x = 10 } and { x >= 0 }, once we support +// inequalities From 626b5d77eee5f09e772404d8fb09553e7d04ee9a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 14:41:01 -0700 Subject: [PATCH 116/202] feedback --- src/support/inplace_vector.h | 4 ++-- test/gtest/inplace_vector.cpp | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/support/inplace_vector.h b/src/support/inplace_vector.h index a4971c8b2b4..35dd7ed30b2 100644 --- a/src/support/inplace_vector.h +++ b/src/support/inplace_vector.h @@ -30,7 +30,7 @@ #include "support/parent_index_iterator.h" -namespace std { +namespace wasm { template class inplace_vector { // fixed-space storage @@ -165,6 +165,6 @@ template class inplace_vector { } }; -} // namespace std +} // namespace wasm #endif // wasm_support_inplace_vector_h diff --git a/test/gtest/inplace_vector.cpp b/test/gtest/inplace_vector.cpp index 10c46b1f633..34c1a3d3e1b 100644 --- a/test/gtest/inplace_vector.cpp +++ b/test/gtest/inplace_vector.cpp @@ -3,17 +3,22 @@ using InplaceVectorTest = ::testing::Test; +using namespace wasm; + TEST_F(InplaceVectorTest, Size) { - std::inplace_vector vec; + inplace_vector vec; // An inplace_vector is just a size plus the in-place storage. EXPECT_EQ(sizeof(vec), sizeof(size_t) + 10 * sizeof(int64_t)); } TEST_F(InplaceVectorTest, Basics) { - std::inplace_vector vec; + inplace_vector vec; + EXPECT_EQ(vec.size(), 0); + EXPECT_TRUE(vec.empty()); vec.push_back(10); EXPECT_EQ(vec[0], 10); + EXPECT_EQ(vec.size(), 1); vec.resize(3); EXPECT_EQ(vec.size(), 3); @@ -28,7 +33,7 @@ TEST_F(InplaceVectorTest, Basics) { } TEST_F(InplaceVectorTest, I) { - std::inplace_vector vec{10, 20, 30}; + inplace_vector vec{10, 20, 30}; std::vector normal; for (auto x : vec) { From 58964063b1db7975aee5e48e546af5bb428a0657 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 14:41:20 -0700 Subject: [PATCH 117/202] feedback --- src/passes/ConstantFieldPropagation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index 93d1ac7c567..172a266d703 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -312,7 +312,7 @@ struct FunctionOptimizer : public WalkerPass> { // values = [ { 42, [A, B] }, { 1337, [C] } ]; struct Value { PossibleConstantValues constant; - std::inplace_vector types; + inplace_vector types; // Whether this slot is used. If so, |constant| has a value, and |types| // is not empty. From cf29b5879e82d2f3e0fd705e4881455f2ee6e1e1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 14:44:15 -0700 Subject: [PATCH 118/202] fix --- src/ir/constraint.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index b2fe6c17bb8..1c69ececce7 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -58,7 +58,7 @@ enum Result { True, False, Unknown }; // the comments below, `x` is used for the thing all the constraints are talking // about, but it could be a global or a struct field or anything else in // general. -struct AndedConstraintSet : std::inplace_vector { +struct AndedConstraintSet : inplace_vector { // Check a condition against this set, that is, whether the existing // constraints prove that it must be true, false, or unknown: whether // From cdaff6bd92e0a1562596d42a1c68042891d3c0bd Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 14:50:30 -0700 Subject: [PATCH 119/202] clean --- src/ir/constraint.cpp | 33 ------------------------------- src/ir/constraint.h | 46 ++++++++++++++++++------------------------- 2 files changed, 19 insertions(+), 60 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 5eb3528cdaa..a4556ab2d35 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -128,37 +128,4 @@ void AndedConstraintSet::fuzzyOr(const AndedConstraintSet& other) { clear(); } -std::optional LocalConstraint::parse(Expression* curr) { - auto* binary = curr->dynCast(); - if (!binary) { - // TODO: unary etc. - return {}; - } - - // The left must be a get. - auto* leftGet = binary->left->dynCast(); - if (!leftGet) { - return {}; - } - - // The right must be a get or a constant. - auto* rightGet = binary->right->dynCast(); - std::optional rightConstant; - if (Properties::isSingleConstantExpression(binary->right)) { - rightConstant = Properties::getLiteral(binary->right); - } - if (!rightGet && !rightConstant) { - return {}; - } - - // The operation must be one we recognize. - for (auto op : {Abstract::Eq, Abstract::Ne}) { - if (Abstract::getBinary(binary->type, op) == binary->op) { - auto value = rightGet ? Value(rightGet->index) : Value(*rightConstant); - return LocalConstraint{leftGet->index, Constraint{op, value}}; - } - } - return {}; -} - } // namespace wasm::constraint diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 1c69ececce7..9bdf0578ef8 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -14,7 +14,11 @@ * limitations under the License. */ -// Constraints on the values of locals, things like x >=0, x < 42, and x == y. +// +// Constraints on the values of things, like x >=0, x < 42, and x == y. Allows +// inference whether other things are true given a set of constraints, like +// { x == 10 } => { x >= 5 }. +// #ifndef wasm_ir_constraint_h #define wasm_ir_constraint_h @@ -34,7 +38,7 @@ struct Value : public std::variant { }; // A constraint: some operation and some value, like "is equal to 17" or "is -// less than local $z". +// less than local 6". struct Constraint { // The operation relating two values, and the values. Abstract::Op op = Abstract::Invalid; @@ -50,14 +54,14 @@ struct Constraint { // TODO: use a generic constraint solver..? inline constexpr std::size_t MaxConstraints = 3; -// What a constraint is known to be: true/false, or unknown. +// What we infer from one thing about another: true/false, or unknown. enum Result { True, False, Unknown }; // A set of constraints connected by the logical "and" operation. That is, all // the constraints are simultaneously true about some value. In the examples in // the comments below, `x` is used for the thing all the constraints are talking -// about, but it could be a global or a struct field or anything else in -// general. +// about, which looks like a local, but it could be a global or a struct field +// or anything else in general. struct AndedConstraintSet : inplace_vector { // Check a condition against this set, that is, whether the existing // constraints prove that it must be true, false, or unknown: whether @@ -95,10 +99,13 @@ struct AndedConstraintSet : inplace_vector { bool full() const { return size() == MaxConstraints; } // Add a constraint to the set, ANDed with the others. The caller must make - // sure not to add too many. - void and_(const Constraint& c) { push_back(c); } + // sure not to add too many (i.e. it is invalid to call this when full()). + void and_(const Constraint& c) { + assert(!full()); + push_back(c); + } - // Add constraints that are ORed. We cannot represent such a thing directly + // Merge constraints using OR. We cannot represent such a thing directly // (we only use AND), so we approximate it in a fuzzy way. For example, this // would be valid: // @@ -113,7 +120,7 @@ struct AndedConstraintSet : inplace_vector { // reverse is not always the case: fuzzyOr may be true without X || Y being // true (see the truth table linked above, and the value 8 in the example). // - // Returning to the example we can use this to optimize as follows: if + // Returning to the example, we can use this to optimize as follows: if // two code paths reaching a location have x == 5 and x == 10, so the value in // the merge location is either 5 or 10, then if we see some i32.ge_s that // does x >= 0 then we can evaluate it with check(): @@ -126,8 +133,10 @@ struct AndedConstraintSet : inplace_vector { // { x >= 5 && x <= 10 } => // { x >= 0 } // + // I.e. the constraints imply the truth of the thing we are evaluating. + // // Note that the fuzziness here means that fuzzyOr() can do a better or a - // worse job. It is always valid for fuzzOr to return { } or any other + // worse job. It is always valid for fuzzyOr to return { } or any other // always-true thing (see the truth table linked above). But then: // // { x == 5 || x == 10 } => @@ -138,23 +147,6 @@ struct AndedConstraintSet : inplace_vector { void fuzzyOr(const AndedConstraintSet& other); }; -// A local plus a constraint on it. -struct LocalConstraint { - Index local; - Constraint constraint; - - // 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); -}; - } // namespace wasm::constraint #endif // wasm_ir_constraint_h From 94d216171c01e3acc22635649b98e821395eb169 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 14:54:58 -0700 Subject: [PATCH 120/202] clean --- test/gtest/constraint.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 0c9031db116..b43ca3bde01 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -17,13 +17,14 @@ TEST(ConstraintTest, TestEq) { AndedConstraintSet s; EXPECT_TRUE(s.empty()); - // x == 5 (we use "x" for the name of the thing being compared). + // x == 5 (we use "x" for the name of the thing being compared, in these + // comments). Constraint c{Eq, Literal(int32_t(5))}; // We can't infer anything using an empty set. EXPECT_EQ(s.check(c), Unknown); - // If we add it, then things check out. + // If we add it, then things check out: a thing always proves itself true. s.and_(c); EXPECT_EQ(s.size(), 1); EXPECT_EQ(s.check(c), True); From 735d7ea9bf545108707fc19fa740f20ff9ede219 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 14:57:19 -0700 Subject: [PATCH 121/202] clean --- src/ir/constraint.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index a4556ab2d35..04547421d97 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -24,7 +24,7 @@ namespace wasm::constraint { namespace { -// Core comparison of two constraints. +// Core comparison of two constraints: whether a => b // // Returns a Result, or an empty option if we should keep working (i.e., a // result of Unknown means we are certain we can just return Unknown). @@ -123,8 +123,7 @@ void AndedConstraintSet::fuzzyOr(const AndedConstraintSet& other) { // TODO smarts // Otherwise, we don't know how to nicely OR these things, and expand to the - // trivial set of no constraints (i.e., where everything is true, and we can - // prove nothing). + // trivial set of no constraints. clear(); } From 6e80fde633f8e876e3bad5a39d6bbbfdbbc9ad12 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 15:40:13 -0700 Subject: [PATCH 122/202] const --- src/support/inplace_vector.h | 2 +- src/support/small_vector.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/support/inplace_vector.h b/src/support/inplace_vector.h index 35dd7ed30b2..21826ea737d 100644 --- a/src/support/inplace_vector.h +++ b/src/support/inplace_vector.h @@ -46,7 +46,7 @@ template class inplace_vector { inplace_vector(inplace_vector&& other) : usedFixed(other.usedFixed), fixed(std::move(other.fixed)) {} inplace_vector(std::initializer_list init) { - for (T item : init) { + for (const T& item : init) { push_back(item); } } diff --git a/src/support/small_vector.h b/src/support/small_vector.h index 8cad6ade29e..f4a1560d21f 100644 --- a/src/support/small_vector.h +++ b/src/support/small_vector.h @@ -49,7 +49,7 @@ template class SmallVector { : usedFixed(other.usedFixed), fixed(std::move(other.fixed)), flexible(std::move(other.flexible)) {} SmallVector(std::initializer_list init) { - for (T item : init) { + for (const T& item : init) { push_back(item); } } From cf7fcc6522757ee362efccf63a5ae319ecb33a3d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 15:44:56 -0700 Subject: [PATCH 123/202] undo CFP change --- src/passes/ConstantFieldPropagation.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index 172a266d703..66f45bb6047 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -60,7 +60,6 @@ #include "ir/utils.h" #include "pass.h" #include "support/hash.h" -#include "support/inplace_vector.h" #include "support/small_vector.h" #include "support/unique_deferring_queue.h" #include "wasm-builder.h" @@ -312,7 +311,9 @@ struct FunctionOptimizer : public WalkerPass> { // values = [ { 42, [A, B] }, { 1337, [C] } ]; struct Value { PossibleConstantValues constant; - inplace_vector types; + // Use a SmallVector as we'll only have 2 Values, and so the stack usage + // here is fixed. + SmallVector types; // Whether this slot is used. If so, |constant| has a value, and |types| // is not empty. From 0930461f4ade92f20165631c0f0453c22e0f7c8f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 16:14:38 -0700 Subject: [PATCH 124/202] tidy --- src/ir/constraint.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 04547421d97..ff81d26807a 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -54,6 +54,7 @@ std::optional checkPair(const Constraint& a, const Constraint& b) { default: { } } + break; } case Abstract::Ne: { switch (b.op) { @@ -72,6 +73,7 @@ std::optional checkPair(const Constraint& a, const Constraint& b) { default: { } } + break; } default: { } From 7b7d2ac95fdf5bca1f7bffcfdd2189d97c3da2a7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 16:20:26 -0700 Subject: [PATCH 125/202] add.assert --- src/ir/constraint.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index ff81d26807a..532a097bc81 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -68,6 +68,7 @@ std::optional checkPair(const Constraint& a, const Constraint& b) { case Abstract::Ne: { // x == c vs x == c', and we already handled full equality // earlier, hence c != c', and we can infer nothing. + assert(*aConstant != *bConstant); return {}; } default: { From 679bd245f0e6dc2175185704372a5cf2e88ed2af Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Jun 2026 16:21:30 -0700 Subject: [PATCH 126/202] fix.comment --- src/ir/constraint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 532a097bc81..3225c98f95a 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -66,7 +66,7 @@ std::optional checkPair(const Constraint& a, const Constraint& b) { return {}; } case Abstract::Ne: { - // x == c vs x == c', and we already handled full equality + // x != c vs x != c', and we already handled full equality // earlier, hence c != c', and we can infer nothing. assert(*aConstant != *bConstant); return {}; From 44ad794529a2da1a64d18b0c5524e2191a590deb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 9 Jun 2026 09:58:44 -0700 Subject: [PATCH 127/202] value => term --- src/ir/constraint.cpp | 4 ++-- src/ir/constraint.h | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 3225c98f95a..3039b4d7aa3 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -35,8 +35,8 @@ std::optional checkPair(const Constraint& a, const Constraint& b) { } // Comparisons of two constants. - if (auto* aConstant = std::get_if(&a.value)) { - if (auto* bConstant = std::get_if(&b.value)) { + if (auto* aConstant = std::get_if(&a.term)) { + if (auto* bConstant = std::get_if(&b.term)) { switch (a.op) { case Abstract::Eq: { switch (b.op) { diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 9bdf0578ef8..ab3394a27fe 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -32,17 +32,16 @@ namespace wasm::constraint { -// A value in a constraint, either a local index or literal value. -struct Value : public std::variant { - bool operator==(const Value&) const = default; +// A term in a constraint, either a local index or literal value. +struct Term : public std::variant { + bool operator==(const Term&) const = default; }; // A constraint: some operation and some value, like "is equal to 17" or "is // less than local 6". struct Constraint { - // The operation relating two values, and the values. Abstract::Op op = Abstract::Invalid; - Value value; + Term term; bool operator==(const Constraint&) const = default; From 2f0bdd73ae26f76f6875cc8ce50b8827b889fb74 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 09:49:33 -0700 Subject: [PATCH 128/202] check => eval --- src/ir/constraint.cpp | 10 ++++----- src/ir/constraint.h | 10 ++++----- test/gtest/constraint.cpp | 44 +++++++++++++++++++-------------------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 3039b4d7aa3..77415260f08 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -28,7 +28,7 @@ namespace { // // Returns a Result, or an empty option if we should keep working (i.e., a // result of Unknown means we are certain we can just return Unknown). -std::optional checkPair(const Constraint& a, const Constraint& b) { +std::optional evalPair(const Constraint& a, const Constraint& b) { // A thing always implies itself. if (a == b) { return True; @@ -87,10 +87,10 @@ std::optional checkPair(const Constraint& a, const Constraint& b) { } // anonymous namespace -Result AndedConstraintSet::check(const Constraint& condition) const { +Result AndedConstraintSet::eval(const Constraint& condition) const { // Sometimes a single constraint is enough to determine the condition. for (auto& c : *this) { - if (auto result = checkPair(c, condition)) { + if (auto result = evalPair(c, condition)) { return *result; } } @@ -115,11 +115,11 @@ void AndedConstraintSet::fuzzyOr(const AndedConstraintSet& other) { // If this is already implied by current constraints, then it is redundant. // E.g. if we are { x = 10 } and other is { x >= 0 } then all we need is // { x >= 0 } as the result of the OR. - if (check(other) == True) { + if (eval(other) == True) { *this = other; return; } - if (other.check(*this) == True) { + if (other.eval(*this) == True) { return; } diff --git a/src/ir/constraint.h b/src/ir/constraint.h index ab3394a27fe..f32bd09892c 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -68,10 +68,10 @@ struct AndedConstraintSet : inplace_vector { // { this } => { condition } // // https://en.wikipedia.org/wiki/Material_conditional#Truth_table - Result check(const Constraint& condition) const; + Result eval(const Constraint& condition) const; // Check an entire other set. - Result check(const AndedConstraintSet& other) const { + Result eval(const AndedConstraintSet& other) const { if (other.empty()) { // The empty set of constraints is always true. return True; @@ -79,7 +79,7 @@ struct AndedConstraintSet : inplace_vector { Result result = Unknown; for (auto& c : other) { - auto currResult = check(c); + auto currResult = eval(c); if (currResult == Unknown) { // If something is unknown, it all is. return Unknown; @@ -122,9 +122,9 @@ struct AndedConstraintSet : inplace_vector { // Returning to the example, we can use this to optimize as follows: if // two code paths reaching a location have x == 5 and x == 10, so the value in // the merge location is either 5 or 10, then if we see some i32.ge_s that - // does x >= 0 then we can evaluate it with check(): + // does x >= 0 then we can evaluate it with eval(): // - // { x >= 5 && x <= 10 }.check({ x >= 0 }) == True + // { x >= 5 && x <= 10 }.eval({ x >= 0 }) == True // // And it is valid to optimize that i32.ge_s into a constant 1, since // diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index b43ca3bde01..d21fc868b83 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -22,21 +22,21 @@ TEST(ConstraintTest, TestEq) { Constraint c{Eq, Literal(int32_t(5))}; // We can't infer anything using an empty set. - EXPECT_EQ(s.check(c), Unknown); + EXPECT_EQ(s.eval(c), Unknown); // If we add it, then things check out: a thing always proves itself true. s.and_(c); EXPECT_EQ(s.size(), 1); - EXPECT_EQ(s.check(c), True); + EXPECT_EQ(s.eval(c), True); // x == 10, a different number: we can infer false. - EXPECT_EQ(s.check(Constraint{Eq, Literal(int32_t(10))}), False); + EXPECT_EQ(s.eval(Constraint{Eq, Literal(int32_t(10))}), False); // x != 15: we can infer true. - EXPECT_EQ(s.check(Constraint{Ne, Literal(int32_t(15))}), True); + EXPECT_EQ(s.eval(Constraint{Ne, Literal(int32_t(15))}), True); // x != 5: we can infer false. - EXPECT_EQ(s.check(Constraint{Ne, Literal(int32_t(5))}), False); + EXPECT_EQ(s.eval(Constraint{Ne, Literal(int32_t(5))}), False); } TEST(ConstraintTest, TestNe) { @@ -46,16 +46,16 @@ TEST(ConstraintTest, TestNe) { s.and_(c); // Checks out versus itself. - EXPECT_EQ(s.check(c), True); + EXPECT_EQ(s.eval(c), True); // x == 10: we don't know. - EXPECT_EQ(s.check(Constraint{Eq, Literal(int32_t(10))}), Unknown); + EXPECT_EQ(s.eval(Constraint{Eq, Literal(int32_t(10))}), Unknown); // x != 15: we don't know. - EXPECT_EQ(s.check(Constraint{Ne, Literal(int32_t(15))}), Unknown); + EXPECT_EQ(s.eval(Constraint{Ne, Literal(int32_t(15))}), Unknown); // x == 5: we can infer false. - EXPECT_EQ(s.check(Constraint{Eq, Literal(int32_t(5))}), False); + EXPECT_EQ(s.eval(Constraint{Eq, Literal(int32_t(5))}), False); } TEST(ConstraintTest, TestMulti) { @@ -67,20 +67,20 @@ TEST(ConstraintTest, TestMulti) { s.and_(d); // Each checks out versus itself. - EXPECT_EQ(s.check(c), True); - EXPECT_EQ(s.check(d), True); + EXPECT_EQ(s.eval(c), True); + EXPECT_EQ(s.eval(d), True); // x == 5: false. - EXPECT_EQ(s.check(Constraint{Eq, Literal(int32_t(5))}), False); + EXPECT_EQ(s.eval(Constraint{Eq, Literal(int32_t(5))}), False); // x == 10: false. - EXPECT_EQ(s.check(Constraint{Eq, Literal(int32_t(10))}), False); + EXPECT_EQ(s.eval(Constraint{Eq, Literal(int32_t(10))}), False); // x == 15: we don't know. - EXPECT_EQ(s.check(Constraint{Eq, Literal(int32_t(15))}), Unknown); + EXPECT_EQ(s.eval(Constraint{Eq, Literal(int32_t(15))}), Unknown); // x != 15: we don't know. - EXPECT_EQ(s.check(Constraint{Ne, Literal(int32_t(15))}), Unknown); + EXPECT_EQ(s.eval(Constraint{Ne, Literal(int32_t(15))}), Unknown); } TEST(ConstraintTest, TestSets) { @@ -90,34 +90,34 @@ TEST(ConstraintTest, TestSets) { AndedConstraintSet s; // Any set always proves itself to be true. - EXPECT_EQ(s.check(s), True); + EXPECT_EQ(s.eval(s), True); // Ditto after adding something. s.and_(c); - EXPECT_EQ(s.check(s), True); + EXPECT_EQ(s.eval(s), True); // Another set, empty. AndedConstraintSet t; // Any set always proves an empty set to be true. - EXPECT_EQ(s.check(t), True); + EXPECT_EQ(s.eval(t), True); // Make both sets contain the same stuff. t.and_(c); - EXPECT_EQ(s.check(t), True); + EXPECT_EQ(s.eval(t), True); // Now t has *different* stuff, x == 10, which given s is false. t.clear(); t.and_(Constraint{Eq, Literal(int32_t(10))}); - EXPECT_EQ(s.check(t), False); + EXPECT_EQ(s.eval(t), False); // Same, with x != 10. Now we know it is true. t.clear(); t.and_(Constraint{Ne, Literal(int32_t(10))}); - EXPECT_EQ(s.check(t), True); + EXPECT_EQ(s.eval(t), True); // In reverse, we can infer nothing: knowing x != 10 does not say if x == 5. - EXPECT_EQ(t.check(s), Unknown); + EXPECT_EQ(t.eval(s), Unknown); } TEST(ConstraintTest, TestOrTrivial) { From 7f77016d3af0655e3955e4b82099ea0ec295966e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 09:54:07 -0700 Subject: [PATCH 129/202] fix new clang-18 warning --- test/gtest/constraint.cpp | 40 +++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index d21fc868b83..bb7f1ead764 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -19,7 +19,7 @@ TEST(ConstraintTest, TestEq) { // x == 5 (we use "x" for the name of the thing being compared, in these // comments). - Constraint c{Eq, Literal(int32_t(5))}; + Constraint c{Eq, {Literal(int32_t(5))}}; // We can't infer anything using an empty set. EXPECT_EQ(s.eval(c), Unknown); @@ -30,39 +30,39 @@ TEST(ConstraintTest, TestEq) { EXPECT_EQ(s.eval(c), True); // x == 10, a different number: we can infer false. - EXPECT_EQ(s.eval(Constraint{Eq, Literal(int32_t(10))}), False); + EXPECT_EQ(s.eval(Constraint{Eq, {Literal(int32_t(10))}}), False); // x != 15: we can infer true. - EXPECT_EQ(s.eval(Constraint{Ne, Literal(int32_t(15))}), True); + EXPECT_EQ(s.eval(Constraint{Ne, {Literal(int32_t(15))}}), True); // x != 5: we can infer false. - EXPECT_EQ(s.eval(Constraint{Ne, Literal(int32_t(5))}), False); + EXPECT_EQ(s.eval(Constraint{Ne, {Literal(int32_t(5))}}), False); } TEST(ConstraintTest, TestNe) { AndedConstraintSet s; // x != 5 - Constraint c{Ne, Literal(int32_t(5))}; + Constraint c{Ne, {Literal(int32_t(5))}}; s.and_(c); // Checks out versus itself. EXPECT_EQ(s.eval(c), True); // x == 10: we don't know. - EXPECT_EQ(s.eval(Constraint{Eq, Literal(int32_t(10))}), Unknown); + EXPECT_EQ(s.eval(Constraint{Eq, {Literal(int32_t(10))}}), Unknown); // x != 15: we don't know. - EXPECT_EQ(s.eval(Constraint{Ne, Literal(int32_t(15))}), Unknown); + EXPECT_EQ(s.eval(Constraint{Ne, {Literal(int32_t(15))}}), Unknown); // x == 5: we can infer false. - EXPECT_EQ(s.eval(Constraint{Eq, Literal(int32_t(5))}), False); + EXPECT_EQ(s.eval(Constraint{Eq, {Literal(int32_t(5))}}), False); } TEST(ConstraintTest, TestMulti) { AndedConstraintSet s; // x != 5 && x != 10 - Constraint c{Ne, Literal(int32_t(5))}; - Constraint d{Ne, Literal(int32_t(10))}; + Constraint c{Ne, {Literal(int32_t(5))}}; + Constraint d{Ne, {Literal(int32_t(10))}}; s.and_(c); s.and_(d); @@ -71,21 +71,21 @@ TEST(ConstraintTest, TestMulti) { EXPECT_EQ(s.eval(d), True); // x == 5: false. - EXPECT_EQ(s.eval(Constraint{Eq, Literal(int32_t(5))}), False); + EXPECT_EQ(s.eval(Constraint{Eq, {Literal(int32_t(5))}}), False); // x == 10: false. - EXPECT_EQ(s.eval(Constraint{Eq, Literal(int32_t(10))}), False); + EXPECT_EQ(s.eval(Constraint{Eq, {Literal(int32_t(10))}}), False); // x == 15: we don't know. - EXPECT_EQ(s.eval(Constraint{Eq, Literal(int32_t(15))}), Unknown); + EXPECT_EQ(s.eval(Constraint{Eq, {Literal(int32_t(15))}}), Unknown); // x != 15: we don't know. - EXPECT_EQ(s.eval(Constraint{Ne, Literal(int32_t(15))}), Unknown); + EXPECT_EQ(s.eval(Constraint{Ne, {Literal(int32_t(15))}}), Unknown); } TEST(ConstraintTest, TestSets) { // x == 5 - Constraint c{Eq, Literal(int32_t(5))}; + Constraint c{Eq, {Literal(int32_t(5))}}; AndedConstraintSet s; @@ -108,12 +108,12 @@ TEST(ConstraintTest, TestSets) { // Now t has *different* stuff, x == 10, which given s is false. t.clear(); - t.and_(Constraint{Eq, Literal(int32_t(10))}); + t.and_(Constraint{Eq, {Literal(int32_t(10))}}); EXPECT_EQ(s.eval(t), False); // Same, with x != 10. Now we know it is true. t.clear(); - t.and_(Constraint{Ne, Literal(int32_t(10))}); + t.and_(Constraint{Ne, {Literal(int32_t(10))}}); EXPECT_EQ(s.eval(t), True); // In reverse, we can infer nothing: knowing x != 10 does not say if x == 5. @@ -123,7 +123,7 @@ TEST(ConstraintTest, TestSets) { TEST(ConstraintTest, TestOrTrivial) { // { x == 5 } AndedConstraintSet s; - s.and_(Constraint{Eq, Literal(int32_t(5))}); + s.and_(Constraint{Eq, {Literal(int32_t(5))}}); // { } AndedConstraintSet empty; @@ -147,11 +147,11 @@ TEST(ConstraintTest, TestOrTrivial) { TEST(ConstraintTest, TestOrImplies) { // { x == 5 } AndedConstraintSet s; - s.and_(Constraint{Eq, Literal(int32_t(5))}); + s.and_(Constraint{Eq, {Literal(int32_t(5))}}); // { x != 10 } AndedConstraintSet t; - t.and_(Constraint{Ne, Literal(int32_t(10))}); + t.and_(Constraint{Ne, {Literal(int32_t(10))}}); // ORing these leaves us with x != 10. auto u = s; From 59f5a091607a30d089ca512e2c2a7305a6ad6295 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 09:55:34 -0700 Subject: [PATCH 130/202] update --- src/passes/RangeAnalysis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index b43677da4d8..ecee4937d51 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -224,7 +224,7 @@ std::cout << relevantLocals.size() << '\n'; return; } auto& localConstraints = iter->second; - Result result = localConstraints.check(parsed->constraint); + Result result = localConstraints.eval(parsed->constraint); 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 ed455bd17d544630e82f33be159de6a25126b229 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 09:56:02 -0700 Subject: [PATCH 131/202] update --- src/ir/constraint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 3aefc3a91cb..ff1aff240ac 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -156,7 +156,7 @@ std::optional LocalConstraint::parse(Expression* curr) { // The operation must be one we recognize. for (auto op : {Abstract::Eq, Abstract::Ne}) { if (Abstract::getBinary(binary->type, op) == binary->op) { - auto value = rightGet ? Value(rightGet->index) : Value(*rightConstant); + auto value = rightGet ? Term(rightGet->index) : Term(*rightConstant); return LocalConstraint{leftGet->index, Constraint{op, value}}; } } From 03e91b63076817f141c1c49e202c3b1ab06013c1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 09:56:50 -0700 Subject: [PATCH 132/202] undo --- src/passes/ConstantFieldPropagation.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index 93d1ac7c567..66f45bb6047 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -60,7 +60,6 @@ #include "ir/utils.h" #include "pass.h" #include "support/hash.h" -#include "support/inplace_vector.h" #include "support/small_vector.h" #include "support/unique_deferring_queue.h" #include "wasm-builder.h" @@ -312,7 +311,9 @@ struct FunctionOptimizer : public WalkerPass> { // values = [ { 42, [A, B] }, { 1337, [C] } ]; struct Value { PossibleConstantValues constant; - std::inplace_vector types; + // Use a SmallVector as we'll only have 2 Values, and so the stack usage + // here is fixed. + SmallVector types; // Whether this slot is used. If so, |constant| has a value, and |types| // is not empty. From 6177a16c73841ff616c587d6739a6384647db423 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 10:10:56 -0700 Subject: [PATCH 133/202] update --- src/passes/RangeAnalysis.cpp | 17 +++---- test/lit/passes/range-analysis.wast | 79 ++++++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 11 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index ecee4937d51..6e35d6e6844 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -270,16 +270,13 @@ std::cout << relevantLocals.size() << '\n'; // Given an expression, apply it to the constraints. For example, a local.set // sets the value for that local. void applyToConstraints(Expression* curr, LocalConstraintMap& constraints) { - auto parsed = LocalConstraint::parse(curr); - if (!parsed) { - return; - } - - // Add the constraint, if we can. TODO: perhaps be smart about which are - // more useful, rather than just drop later ones? - auto& localConstraints = constraints[parsed->local]; - if (!localConstraints.full()) { - localConstraints.and_(parsed->constraint); + if (auto* set = curr->dynCast()) { + if (Properties::isSingleConstantExpression(set->value)) { + auto value = Properties::getLiteral(set->value); + auto& localConstraints = constraints[set->index]; + localConstraints.clear(); + localConstraints.and_(Constraint{Abstract::Eq, value}); + } } } }; diff --git a/test/lit/passes/range-analysis.wast b/test/lit/passes/range-analysis.wast index 52c7cfb0656..9cbb5a152b7 100644 --- a/test/lit/passes/range-analysis.wast +++ b/test/lit/passes/range-analysis.wast @@ -28,7 +28,8 @@ ;; CHECK-NEXT: ) (func $simple (local $x i32) - ;; Set x to 10, and then compare it to 10 and 20 using == and != + ;; Set x to 10, and then compare it to 10 and 20 using == and !=, all in a + ;; single basic block. (local.set $x (i32.const 10) ) @@ -57,4 +58,80 @@ ) ) ) + + ;; CHECK: (func $multi (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (local $y i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $y + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 15) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $multi + (local $x i32) + (local $y i32) + ;; x is 10, y is 20 + (local.set $x + (i32.const 10) + ) + (local.set $y + (i32.const 20) + ) + ;; Verify those values. + (drop + (i32.eq + (local.get $x) + (i32.const 10) + ) + ) + (drop + (i32.eq + (local.get $y) + (i32.const 20) + ) + ) + ;; Overwrite x to 15. + (local.set $x + (i32.const 15) + ) + (drop + (i32.eq + (local.get $x) + (i32.const 10) + ) + ) + (drop + (i32.eq + (local.get $x) + (i32.const 15) + ) + ) + ;; y is unchanged. + (drop + (i32.eq + (local.get $y) + (i32.const 20) + ) + ) + ) ) From af791b1ac333ba1499d2a24606608aba528e1e35 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 10:12:49 -0700 Subject: [PATCH 134/202] update --- src/passes/RangeAnalysis.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 6e35d6e6844..23f6a3f7ca0 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -154,9 +154,6 @@ struct RangeAnalysis // Values can flow between locals: if x is relevant, and y is written to it, // we must consider y relevant too. TODO? - -std::cout << relevantLocals.size() << '\n'; - if (!relevantLocals.empty()) { flow(); optimize(); From cb3570690d88e227d340f546394c51c141d86925 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 10:15:18 -0700 Subject: [PATCH 135/202] update --- test/lit/passes/range-analysis.wast | 73 ++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/test/lit/passes/range-analysis.wast b/test/lit/passes/range-analysis.wast index 9cbb5a152b7..5b6957e6eea 100644 --- a/test/lit/passes/range-analysis.wast +++ b/test/lit/passes/range-analysis.wast @@ -59,7 +59,7 @@ ) ) - ;; CHECK: (func $multi (type $0) + ;; CHECK: (func $multi-local (type $0) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local $y i32) ;; CHECK-NEXT: (local.set $x @@ -87,7 +87,7 @@ ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $multi + (func $multi-local (local $x i32) (local $y i32) ;; x is 10, y is 20 @@ -134,4 +134,73 @@ ) ) ) + + ;; CHECK: (func $multi-block (type $1) (param $param i32) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $param) + ;; 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: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $param) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $multi-block (param $param i32) + (local $x i32) + (local.set $x + (i32.const 10) + ) + ;; We can infer into these basic blocks. + (if + (local.get $param) + (then + (drop + (i32.eq + (local.get $x) + (i32.const 10) + ) + ) + ) + (else + (drop + (i32.ne ;; also test a not-equals here; result is 0 + (local.get $x) + (i32.const 10) + ) + ) + ) + ) + ;; We can infer after the merge. + (drop + (i32.eq + (local.get $x) + (i32.const 10) + ) + ) + ;; We can infer nothing for the param. + (drop + (i32.eq + (local.get $param) + (i32.const 10) + ) + ) + ) ) From c0f54ad5534b475859d66583f3ebb3dd411af700 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 10:16:40 -0700 Subject: [PATCH 136/202] update --- test/lit/passes/range-analysis.wast | 59 +++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/test/lit/passes/range-analysis.wast b/test/lit/passes/range-analysis.wast index 5b6957e6eea..f71ed0030be 100644 --- a/test/lit/passes/range-analysis.wast +++ b/test/lit/passes/range-analysis.wast @@ -203,4 +203,63 @@ ) ) ) + + ;; CHECK: (func $multi-block-split (type $1) (param $param i32) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $param) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $multi-block-split (param $param i32) + (local $x i32) + (if + (local.get $param) + (then + (local.set $x + (i32.const 10) + ) + ) + (else + (local.set $x + (i32.const 20) + ) + ) + ) + ;; x is either 10 or 20, but not known to be one or the other. + (drop + (i32.eq + (local.get $x) + (i32.const 10) + ) + ) + (drop + (i32.eq + (local.get $x) + (i32.const 20) + ) + ) + ;; TODO: test we can infer x >= 10 etc. + ) ) From 745116c8dc8fcd5f5a5aca001329428f706ed5e5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 10:34:40 -0700 Subject: [PATCH 137/202] update --- test/lit/passes/range-analysis.wast | 63 +++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/test/lit/passes/range-analysis.wast b/test/lit/passes/range-analysis.wast index f71ed0030be..c5d5882c573 100644 --- a/test/lit/passes/range-analysis.wast +++ b/test/lit/passes/range-analysis.wast @@ -8,7 +8,7 @@ ;; R;U;N: wasm-opt %s --optimize-instructions --range-analysis -all -S -o - | filecheck %s (module - ;; CHECK: (func $simple (type $0) + ;; CHECK: (func $simple (type $1) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (i32.const 10) @@ -59,7 +59,7 @@ ) ) - ;; CHECK: (func $multi-local (type $0) + ;; CHECK: (func $multi-local (type $1) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local $y i32) ;; CHECK-NEXT: (local.set $x @@ -135,7 +135,7 @@ ) ) - ;; CHECK: (func $multi-block (type $1) (param $param i32) + ;; CHECK: (func $multi-block (type $0) (param $param i32) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (i32.const 10) @@ -204,7 +204,7 @@ ) ) - ;; CHECK: (func $multi-block-split (type $1) (param $param i32) + ;; CHECK: (func $multi-block-split (type $0) (param $param i32) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $param) @@ -262,4 +262,59 @@ ) ;; TODO: test we can infer x >= 10 etc. ) + + ;; CHECK: (func $multi-block-split-2 (type $0) (param $param i32) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $param) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $multi-block-split-2 (param $param i32) + (local $x i32) + ;; As above, but one if arm, and a set before the if. + (local.set $x + (i32.const 10) + ) + (if + (local.get $param) + (then + (local.set $x + (i32.const 20) + ) + ) + ) + ;; Again, we cannot infer. + (drop + (i32.eq + (local.get $x) + (i32.const 10) + ) + ) + (drop + (i32.eq + (local.get $x) + (i32.const 20) + ) + ) + ) ) From 4c8dccaf97d005036afac3fdf9798caa1de317b0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 10:39:21 -0700 Subject: [PATCH 138/202] update --- src/passes/RangeAnalysis.cpp | 14 ++++++++++++++ test/lit/passes/range-analysis.wast | 30 ++++++++++++++++++++++++----- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 23f6a3f7ca0..186306cac3d 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -240,6 +240,7 @@ struct RangeAnalysis // of the predecessor blocks. LocalConstraintMap mergeIncoming(BasicBlock* block) { LocalConstraintMap constraints; + // For each relevant local, merge its constraints. for (auto local : relevantLocals) { AndedConstraintSet& merged = constraints[local]; @@ -247,6 +248,19 @@ struct RangeAnalysis merged.fuzzyOr(getConstraintsFromPredToSucc(pred, block, local)); } } + + // The entry block has incoming values - defaults - for each var. + if (block == entry) { + auto* func = getFunction(); + auto numVars = func->getNumVars(); + for (Index i = 0; i < numVars; i++) { + auto type = func->getLocalType(i); + if (LiteralUtils::canMakeZero(type)) { + constraints[i].and_(Literal::makeZeros(type)); + } + } + } + return constraints; } diff --git a/test/lit/passes/range-analysis.wast b/test/lit/passes/range-analysis.wast index c5d5882c573..4ee5c2b07f5 100644 --- a/test/lit/passes/range-analysis.wast +++ b/test/lit/passes/range-analysis.wast @@ -8,7 +8,7 @@ ;; R;U;N: wasm-opt %s --optimize-instructions --range-analysis -all -S -o - | filecheck %s (module - ;; CHECK: (func $simple (type $1) + ;; CHECK: (func $simple (type $0) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (i32.const 10) @@ -59,7 +59,7 @@ ) ) - ;; CHECK: (func $multi-local (type $1) + ;; CHECK: (func $multi-local (type $0) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local $y i32) ;; CHECK-NEXT: (local.set $x @@ -135,7 +135,7 @@ ) ) - ;; CHECK: (func $multi-block (type $0) (param $param i32) + ;; CHECK: (func $multi-block (type $1) (param $param i32) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (i32.const 10) @@ -204,7 +204,7 @@ ) ) - ;; CHECK: (func $multi-block-split (type $0) (param $param i32) + ;; CHECK: (func $multi-block-split (type $1) (param $param i32) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $param) @@ -263,7 +263,7 @@ ;; TODO: test we can infer x >= 10 etc. ) - ;; CHECK: (func $multi-block-split-2 (type $0) (param $param i32) + ;; CHECK: (func $multi-block-split-2 (type $1) (param $param i32) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (i32.const 10) @@ -317,4 +317,24 @@ ) ) ) + + ;; CHECK: (func $default-var (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $default-var + (local $x i32) + ;; locals begin with default values, so we can infer here. + (drop + (i32.eq + (local.get $x) + (i32.const 0) + ) + ) + ) ) From 725c08df6ec2e6658e2cf7456c8ed67bf339f6e1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 10:42:07 -0700 Subject: [PATCH 139/202] update --- src/passes/RangeAnalysis.cpp | 13 +++++++++---- test/lit/passes/range-analysis.wast | 5 +---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 186306cac3d..9c250ef7f28 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -252,11 +252,16 @@ struct RangeAnalysis // The entry block has incoming values - defaults - for each var. if (block == entry) { auto* func = getFunction(); - auto numVars = func->getNumVars(); - for (Index i = 0; i < numVars; i++) { + auto numLocals = func->getNumLocals(); + for (Index i = 0; i < numLocals; i++) { + if (!func->isVar(i)) { + continue; + } auto type = func->getLocalType(i); - if (LiteralUtils::canMakeZero(type)) { - constraints[i].and_(Literal::makeZeros(type)); + // TODO: support tuples + if (type.size() == 1 && LiteralUtils::canMakeZero(type)) { + auto value = Literal::makeZero(type); + constraints[i].and_(Constraint{Abstract::Eq, value}); } } } diff --git a/test/lit/passes/range-analysis.wast b/test/lit/passes/range-analysis.wast index 4ee5c2b07f5..e289263f7ea 100644 --- a/test/lit/passes/range-analysis.wast +++ b/test/lit/passes/range-analysis.wast @@ -321,10 +321,7 @@ ;; CHECK: (func $default-var (type $0) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.eq - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $default-var From 3e96dccd5db6cc631a8be1ceb3cca3c287457e7c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 10:50:44 -0700 Subject: [PATCH 140/202] update --- test/lit/passes/range-analysis.wast | 40 ++++++++++++++--------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/test/lit/passes/range-analysis.wast b/test/lit/passes/range-analysis.wast index e289263f7ea..f1cd84f28eb 100644 --- a/test/lit/passes/range-analysis.wast +++ b/test/lit/passes/range-analysis.wast @@ -8,7 +8,7 @@ ;; R;U;N: wasm-opt %s --optimize-instructions --range-analysis -all -S -o - | filecheck %s (module - ;; CHECK: (func $simple (type $0) + ;; CHECK: (func $simple (type $1) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (i32.const 10) @@ -59,7 +59,7 @@ ) ) - ;; CHECK: (func $multi-local (type $0) + ;; CHECK: (func $multi-local (type $1) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local $y i32) ;; CHECK-NEXT: (local.set $x @@ -135,7 +135,7 @@ ) ) - ;; CHECK: (func $multi-block (type $1) (param $param i32) + ;; CHECK: (func $multi-block (type $0) (param $param i32) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (i32.const 10) @@ -156,12 +156,6 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.eq - ;; CHECK-NEXT: (local.get $param) - ;; CHECK-NEXT: (i32.const 10) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $multi-block (param $param i32) (local $x i32) @@ -195,16 +189,9 @@ (i32.const 10) ) ) - ;; We can infer nothing for the param. - (drop - (i32.eq - (local.get $param) - (i32.const 10) - ) - ) ) - ;; CHECK: (func $multi-block-split (type $1) (param $param i32) + ;; CHECK: (func $multi-block-split (type $0) (param $param i32) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $param) @@ -263,7 +250,7 @@ ;; TODO: test we can infer x >= 10 etc. ) - ;; CHECK: (func $multi-block-split-2 (type $1) (param $param i32) + ;; CHECK: (func $multi-block-split-2 (type $0) (param $param i32) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (i32.const 10) @@ -318,13 +305,19 @@ ) ) - ;; CHECK: (func $default-var (type $0) + ;; CHECK: (func $default-var (type $0) (param $param i32) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $param) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $default-var + (func $default-var (param $param i32) (local $x i32) ;; locals begin with default values, so we can infer here. (drop @@ -333,5 +326,12 @@ (i32.const 0) ) ) + ;; We can infer nothing for a param. + (drop + (i32.eq + (local.get $param) + (i32.const 0) + ) + ) ) ) From 03ec477b19ecc5d82511f0f3f6ffb977633d8962 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 10:53:46 -0700 Subject: [PATCH 141/202] update --- test/lit/passes/range-analysis.wast | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/lit/passes/range-analysis.wast b/test/lit/passes/range-analysis.wast index f1cd84f28eb..67c1f2bfc00 100644 --- a/test/lit/passes/range-analysis.wast +++ b/test/lit/passes/range-analysis.wast @@ -307,6 +307,8 @@ ;; CHECK: (func $default-var (type $0) (param $param i32) ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (local $eq eqref) + ;; CHECK-NEXT: (local $nn-eq (ref eq)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) @@ -316,9 +318,19 @@ ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.eq + ;; CHECK-NEXT: (local.get $eq) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $default-var (param $param i32) (local $x i32) + (local $eq eqref) + ;; A non-nullable local. We cannot add a get for it (it would not validate), + ;; but check we do not error. + (local $nn-eq (ref eq)) ;; locals begin with default values, so we can infer here. (drop (i32.eq @@ -333,5 +345,12 @@ (i32.const 0) ) ) + ;; We can infer a null reference. + (drop + (ref.eq + (local.get $eq) + (ref.null eq) + ) + ) ) ) From 945e04296ffe44a627ee218195d17dad05955a43 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 13:22:08 -0700 Subject: [PATCH 142/202] update --- src/ir/constraint.cpp | 51 ++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index ff1aff240ac..d73b1ac8cff 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -131,35 +131,40 @@ void AndedConstraintSet::fuzzyOr(const AndedConstraintSet& other) { } std::optional LocalConstraint::parse(Expression* curr) { - auto* binary = curr->dynCast(); - if (!binary) { - // TODO: unary etc. + // Parse a get or a constant. + auto parseTerm = [&](Expression* expr) -> std::optional { + if (auto* get = expr->dynCast()) { + return get->index; + } + if (Properties::isSingleConstantExpression(expr)) { + return Properties::getLiteral(expr); + } return {}; - } + }; + + auto parseBinary = [&](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)) { + return LocalConstraint{get->index, Constraint{op, *value}}; + } + } + }; - // The left must be a get. - auto* leftGet = binary->left->dynCast(); - if (!leftGet) { - return {}; + if (auto* binary = curr->dynCast()) { + // The operation must be one we recognize. + for (auto op : {Abstract::Eq, Abstract::Ne}) { + if (Abstract::getBinary(binary->type, op) == binary->op) { + return parseBinary(op, binary->left, binary->right); + } + } } - // The right must be a get or a constant. - auto* rightGet = binary->right->dynCast(); - std::optional rightConstant; - if (Properties::isSingleConstantExpression(binary->right)) { - rightConstant = Properties::getLiteral(binary->right); - } - if (!rightGet && !rightConstant) { - return {}; + if (auto* refEq = curr->dynCast()) { + return parseBinary(Abstract::eq, refEq->left, refEq->right); } - // The operation must be one we recognize. - for (auto op : {Abstract::Eq, Abstract::Ne}) { - if (Abstract::getBinary(binary->type, op) == binary->op) { - auto value = rightGet ? Term(rightGet->index) : Term(*rightConstant); - return LocalConstraint{leftGet->index, Constraint{op, value}}; - } - } return {}; } From 41ffb72609c7dad19bd90c05c05a72bfa90f0f07 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 13:23:17 -0700 Subject: [PATCH 143/202] update --- src/ir/constraint.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index d73b1ac8cff..dcca17c8631 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -134,15 +134,15 @@ std::optional LocalConstraint::parse(Expression* curr) { // Parse a get or a constant. auto parseTerm = [&](Expression* expr) -> std::optional { if (auto* get = expr->dynCast()) { - return get->index; + return Term(get->index); } if (Properties::isSingleConstantExpression(expr)) { - return Properties::getLiteral(expr); + return Term(Properties::getLiteral(expr)); } return {}; }; - auto parseBinary = [&](Abstract::Op op, Expression* left, Expression* right) { + auto parseBinary = [&](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. @@ -150,6 +150,7 @@ std::optional LocalConstraint::parse(Expression* curr) { return LocalConstraint{get->index, Constraint{op, *value}}; } } + return {}; }; if (auto* binary = curr->dynCast()) { @@ -162,7 +163,7 @@ std::optional LocalConstraint::parse(Expression* curr) { } if (auto* refEq = curr->dynCast()) { - return parseBinary(Abstract::eq, refEq->left, refEq->right); + return parseBinary(Abstract::Eq, refEq->left, refEq->right); } return {}; From ba50172ad60b783b27bec4ca47a76ff1c5de72fe Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 13:24:02 -0700 Subject: [PATCH 144/202] update --- src/passes/RangeAnalysis.cpp | 1 + test/lit/passes/range-analysis.wast | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 9c250ef7f28..696651ed56b 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -89,6 +89,7 @@ struct RangeAnalysis void visitLocalSet(LocalSet* curr) { addAction(); } void visitUnary(Unary* curr) { addAction(); } // XXX needed? void visitBinary(Binary* curr) { addAction(); } + void visitRefEq(RefEq* curr) { addAction(); } #if 0 // Track the branches we reason about. CFGWalker builds a CFG, and we want to diff --git a/test/lit/passes/range-analysis.wast b/test/lit/passes/range-analysis.wast index 67c1f2bfc00..69290252e18 100644 --- a/test/lit/passes/range-analysis.wast +++ b/test/lit/passes/range-analysis.wast @@ -319,10 +319,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.eq - ;; CHECK-NEXT: (local.get $eq) - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $default-var (param $param i32) From 613ef951abc0b5867f99bb260af1aa109f7d17f2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 13:26:53 -0700 Subject: [PATCH 145/202] update --- test/lit/passes/range-analysis.wast | 73 +++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/test/lit/passes/range-analysis.wast b/test/lit/passes/range-analysis.wast index 69290252e18..e7d4a053869 100644 --- a/test/lit/passes/range-analysis.wast +++ b/test/lit/passes/range-analysis.wast @@ -350,4 +350,77 @@ ) ) ) + + ;; CHECK: (func $var-var (type $1) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (local $y i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $y + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $var-var + (local $x i32) + (local $y i32) + ;; x is 10, y is 20. Compare them to each other. + (local.set $x + (i32.const 10) + ) + (local.set $y + (i32.const 20) + ) + ;; 10 != 20 + (drop + (i32.eq + (local.get $x) + (local.get $y) + ) + ) + ;; Now both are 20, so they are equal. + (local.set $x + (i32.const 20) + ) + (drop + (i32.eq + (local.get $x) + (local.get $y) + ) + ) + ;; Make one directly equal to the other. + (local.set $x + (local.get $y) + ) + (drop + (i32.eq + (local.get $x) + (local.get $y) + ) + ) + ) ) From 923a42ac3c78fb2034907eb9f3d11c144b417728 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 13:29:37 -0700 Subject: [PATCH 146/202] update --- test/lit/passes/range-analysis.wast | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/test/lit/passes/range-analysis.wast b/test/lit/passes/range-analysis.wast index e7d4a053869..e323556721c 100644 --- a/test/lit/passes/range-analysis.wast +++ b/test/lit/passes/range-analysis.wast @@ -1,11 +1,7 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. -;; Test range-analysis. We also run optimize-instructions first as it canonicalizes -;; the other of binaries etc., and we want range-analysis to always work on the -;; canonicalized form. TODO ;; RUN: wasm-opt %s --range-analysis -all -S -o - | filecheck %s -;; R;U;N: wasm-opt %s --optimize-instructions --range-analysis -all -S -o - | filecheck %s (module ;; CHECK: (func $simple (type $1) @@ -422,5 +418,18 @@ (local.get $y) ) ) + ;; Flipped. + (drop + (i32.eq + (local.get $y) + (local.get $x) + ) + ) ) ) + +TODO: ;; Test range-analysis. We also run optimize-instructions first as it canonicalizes +;; the other of binaries etc., and we want range-analysis to always work on the +;; canonicalized form. TODO +;; R;U;N: wasm-opt %s --optimize-instructions --range-analysis -all -S -o - | filecheck %s + From 5e432a4c2ab200da3c6b40ec32f5e6b15e61aa8f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 15:49:40 -0700 Subject: [PATCH 147/202] 2.bad --- test/lit/passes/range-analysis.wast | 226 +++++++++++++++++----------- 1 file changed, 140 insertions(+), 86 deletions(-) diff --git a/test/lit/passes/range-analysis.wast b/test/lit/passes/range-analysis.wast index e323556721c..0a48dbb1aba 100644 --- a/test/lit/passes/range-analysis.wast +++ b/test/lit/passes/range-analysis.wast @@ -1,8 +1,13 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. - ;; RUN: wasm-opt %s --range-analysis -all -S -o - | filecheck %s +;; Also run after optimize-instructions, which canonicalizes the order of +;; things like binary children. We want to see that this pass optimizes the +;; IR that optimize-instructions emits. + +;; RUN: wasm-opt %s --optimize-instructions --range-analysis -all -S -o - | filecheck %s --check-prefix=OPTIN + (module ;; CHECK: (func $simple (type $1) ;; CHECK-NEXT: (local $x i32) @@ -22,6 +27,24 @@ ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; OPTIN: (func $simple (type $1) + ;; OPTIN-NEXT: (local $x i32) + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (i32.const 10) + ;; 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.const 0) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) (func $simple (local $x i32) ;; Set x to 10, and then compare it to 10 and 20 using == and !=, all in a @@ -83,6 +106,34 @@ ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; OPTIN: (func $multi-local (type $1) + ;; OPTIN-NEXT: (local $x i32) + ;; OPTIN-NEXT: (local $y i32) + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (i32.const 10) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (local.set $y + ;; OPTIN-NEXT: (i32.const 20) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (i32.const 15) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 0) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) (func $multi-local (local $x i32) (local $y i32) @@ -153,6 +204,26 @@ ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; OPTIN: (func $multi-block (type $0) (param $param i32) + ;; OPTIN-NEXT: (local $x i32) + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (i32.const 10) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (if (result i32) + ;; OPTIN-NEXT: (local.get $param) + ;; OPTIN-NEXT: (then + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (else + ;; OPTIN-NEXT: (i32.const 0) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) (func $multi-block (param $param i32) (local $x i32) (local.set $x @@ -215,6 +286,26 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; OPTIN: (func $multi-block-split (type $0) (param $param i32) + ;; OPTIN-NEXT: (local $x i32) + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (if (result i32) + ;; OPTIN-NEXT: (local.get $param) + ;; OPTIN-NEXT: (then + ;; OPTIN-NEXT: (i32.const 10) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (else + ;; OPTIN-NEXT: (i32.const 20) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 0) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 0) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) (func $multi-block-split (param $param i32) (local $x i32) (if @@ -272,6 +363,32 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; OPTIN: (func $multi-block-split-2 (type $0) (param $param i32) + ;; OPTIN-NEXT: (local $x i32) + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (i32.const 10) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (if + ;; OPTIN-NEXT: (local.get $param) + ;; OPTIN-NEXT: (then + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (i32.const 20) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (i32.const 10) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (i32.const 20) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) (func $multi-block-split-2 (param $param i32) (local $x i32) ;; As above, but one if arm, and a set before the if. @@ -318,6 +435,26 @@ ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; OPTIN: (func $default-var (type $0) (param $param i32) + ;; OPTIN-NEXT: (local $x i32) + ;; OPTIN-NEXT: (local $eq eqref) + ;; OPTIN-NEXT: (local $nn-eq (ref eq)) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.eqz + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.eqz + ;; OPTIN-NEXT: (local.get $param) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (ref.is_null + ;; OPTIN-NEXT: (local.get $eq) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) (func $default-var (param $param i32) (local $x i32) (local $eq eqref) @@ -346,90 +483,7 @@ ) ) ) - - ;; CHECK: (func $var-var (type $1) - ;; CHECK-NEXT: (local $x i32) - ;; CHECK-NEXT: (local $y i32) - ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (i32.const 10) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $y - ;; CHECK-NEXT: (i32.const 20) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.eq - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (local.get $y) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (i32.const 20) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.eq - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (local.get $y) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (local.get $y) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.eq - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (local.get $y) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $var-var - (local $x i32) - (local $y i32) - ;; x is 10, y is 20. Compare them to each other. - (local.set $x - (i32.const 10) - ) - (local.set $y - (i32.const 20) - ) - ;; 10 != 20 - (drop - (i32.eq - (local.get $x) - (local.get $y) - ) - ) - ;; Now both are 20, so they are equal. - (local.set $x - (i32.const 20) - ) - (drop - (i32.eq - (local.get $x) - (local.get $y) - ) - ) - ;; Make one directly equal to the other. - (local.set $x - (local.get $y) - ) - (drop - (i32.eq - (local.get $x) - (local.get $y) - ) - ) - ;; Flipped. - (drop - (i32.eq - (local.get $y) - (local.get $x) - ) - ) - ) ) -TODO: ;; Test range-analysis. We also run optimize-instructions first as it canonicalizes -;; the other of binaries etc., and we want range-analysis to always work on the -;; canonicalized form. TODO -;; R;U;N: wasm-opt %s --optimize-instructions --range-analysis -all -S -o - | filecheck %s - +TODO eqz +todo -split From 2da66f7adeb799dba1d5ee12c783baf566427173 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 15:54:11 -0700 Subject: [PATCH 148/202] update --- src/ir/constraint.cpp | 12 ++++++++++++ test/lit/passes/range-analysis.wast | 8 +++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index dcca17c8631..d1e174eed66 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -131,6 +131,17 @@ void AndedConstraintSet::fuzzyOr(const AndedConstraintSet& other) { } std::optional LocalConstraint::parse(Expression* curr) { + if (auto* unary = curr->dynCast()) { + if (Abstract::getUnary(unary->type, Abstract::EqZ) == unary->op) { + if (auto* get = unary->value->dynCast()) { + // Canonicalize EqZ to Eq of 0. + auto value = Literal::makeZero(get->type); + return LocalConstraint{get->index, Constraint{Abstract::Eq, value}}; + } + } + return {}; + } + // Parse a get or a constant. auto parseTerm = [&](Expression* expr) -> std::optional { if (auto* get = expr->dynCast()) { @@ -160,6 +171,7 @@ std::optional LocalConstraint::parse(Expression* curr) { return parseBinary(op, binary->left, binary->right); } } + return {}; } if (auto* refEq = curr->dynCast()) { diff --git a/test/lit/passes/range-analysis.wast b/test/lit/passes/range-analysis.wast index 0a48dbb1aba..4f4a0d9cea5 100644 --- a/test/lit/passes/range-analysis.wast +++ b/test/lit/passes/range-analysis.wast @@ -440,9 +440,7 @@ ;; OPTIN-NEXT: (local $eq eqref) ;; OPTIN-NEXT: (local $nn-eq (ref eq)) ;; OPTIN-NEXT: (drop - ;; OPTIN-NEXT: (i32.eqz - ;; OPTIN-NEXT: (local.get $x) - ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (i32.const 1) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (drop ;; OPTIN-NEXT: (i32.eqz @@ -485,5 +483,5 @@ ) ) -TODO eqz -todo -split +;; TODO eqz +;; todo -split From 3ef82bb1cda9c9d4c3d6de5c6eebb3c084c06676 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 15:56:28 -0700 Subject: [PATCH 149/202] update --- src/ir/constraint.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index d1e174eed66..e844eda54e1 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -131,17 +131,25 @@ void AndedConstraintSet::fuzzyOr(const AndedConstraintSet& other) { } std::optional LocalConstraint::parse(Expression* curr) { + auto parseEqZ = [&](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}}; + } + }; + if (auto* unary = curr->dynCast()) { if (Abstract::getUnary(unary->type, Abstract::EqZ) == unary->op) { - if (auto* get = unary->value->dynCast()) { - // Canonicalize EqZ to Eq of 0. - auto value = Literal::makeZero(get->type); - return LocalConstraint{get->index, Constraint{Abstract::Eq, value}}; - } + return parseEqZ(unary->value); } return {}; } + if (auto* refIsNull = curr->dynCast()) { + return parseEqZ(refIsNull->value); + } + // Parse a get or a constant. auto parseTerm = [&](Expression* expr) -> std::optional { if (auto* get = expr->dynCast()) { From b05dba6065ee5295c0aac68f21a9c68112f8b7ff Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 15:58:34 -0700 Subject: [PATCH 150/202] update --- src/ir/constraint.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index e844eda54e1..dcfceff0dba 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -137,6 +137,7 @@ std::optional LocalConstraint::parse(Expression* curr) { auto value = Literal::makeZero(get->type); return LocalConstraint{get->index, Constraint{Abstract::Eq, value}}; } + return {}; }; if (auto* unary = curr->dynCast()) { From 564b3cbd43f53689cb13208945f475dd155a60e1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 15:59:36 -0700 Subject: [PATCH 151/202] update --- src/passes/RangeAnalysis.cpp | 1 + test/lit/passes/range-analysis.wast | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/RangeAnalysis.cpp index 696651ed56b..a437daf9f45 100644 --- a/src/passes/RangeAnalysis.cpp +++ b/src/passes/RangeAnalysis.cpp @@ -90,6 +90,7 @@ struct RangeAnalysis void visitUnary(Unary* curr) { addAction(); } // XXX needed? void visitBinary(Binary* curr) { addAction(); } void visitRefEq(RefEq* curr) { addAction(); } + void visitRefIsNull(RefIsNull* curr) { addAction(); } #if 0 // Track the branches we reason about. CFGWalker builds a CFG, and we want to diff --git a/test/lit/passes/range-analysis.wast b/test/lit/passes/range-analysis.wast index 4f4a0d9cea5..9c714343604 100644 --- a/test/lit/passes/range-analysis.wast +++ b/test/lit/passes/range-analysis.wast @@ -448,9 +448,7 @@ ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (drop - ;; OPTIN-NEXT: (ref.is_null - ;; OPTIN-NEXT: (local.get $eq) - ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (i32.const 1) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) (func $default-var (param $param i32) From 4c3eef235c1e1b0f1b81078179c42b514116b810 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 16:00:21 -0700 Subject: [PATCH 152/202] update --- test/lit/passes/range-analysis.wast | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/lit/passes/range-analysis.wast b/test/lit/passes/range-analysis.wast index 9c714343604..89bb819e8e9 100644 --- a/test/lit/passes/range-analysis.wast +++ b/test/lit/passes/range-analysis.wast @@ -426,6 +426,9 @@ ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.eq ;; CHECK-NEXT: (local.get $param) ;; CHECK-NEXT: (i32.const 0) @@ -434,6 +437,9 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; OPTIN: (func $default-var (type $0) (param $param i32) ;; OPTIN-NEXT: (local $x i32) @@ -443,6 +449,9 @@ ;; OPTIN-NEXT: (i32.const 1) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop ;; OPTIN-NEXT: (i32.eqz ;; OPTIN-NEXT: (local.get $param) ;; OPTIN-NEXT: ) @@ -450,6 +459,9 @@ ;; OPTIN-NEXT: (drop ;; OPTIN-NEXT: (i32.const 1) ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) (func $default-var (param $param i32) (local $x i32) @@ -464,6 +476,11 @@ (i32.const 0) ) ) + (drop + (i32.eqz + (local.get $x) + ) + ) ;; We can infer nothing for a param. (drop (i32.eq @@ -478,6 +495,11 @@ (ref.null eq) ) ) + (drop + (ref.is_null + (local.get $eq) + ) + ) ) ) From f40a39dc1cbd2b38a08d78a52b4fd28e970de60d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 16:00:39 -0700 Subject: [PATCH 153/202] form --- src/ir/constraint.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index dcfceff0dba..21f5334bbb6 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -162,7 +162,9 @@ std::optional LocalConstraint::parse(Expression* curr) { return {}; }; - auto parseBinary = [&](Abstract::Op op, Expression* left, Expression* right) -> std::optional { + auto parseBinary = [&](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. From 0d2f0ed5a73307f2a611d5020c46a96f07081bb0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 16:04:13 -0700 Subject: [PATCH 154/202] rename --- src/ir/span.h | 123 ------------------ src/passes/CMakeLists.txt | 2 +- ...ngeAnalysis.cpp => ConstraintAnalysis.cpp} | 0 src/passes/pass.cpp | 6 +- src/passes/passes.h | 2 +- 5 files changed, 5 insertions(+), 128 deletions(-) delete mode 100644 src/ir/span.h rename src/passes/{RangeAnalysis.cpp => ConstraintAnalysis.cpp} (100%) diff --git a/src/ir/span.h b/src/ir/span.h deleted file mode 100644 index cb5d77e0f6b..00000000000 --- a/src/ir/span.h +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2026 WebAssembly Community Group participants - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Spans of values, allowing inference whether a Literal or a local is within -// some range. - -#ifndef wasm_ir_span_h -#define wasm_ir_span_h - -#include - -#include "support/utilities.h" -#include "wasm.h" - -namespace wasm::span { - -struct Unknown : public std::monostate {}; - -// In each span of values, one of the values. This can be either a literal -// like i32(0), or a local index (i.e., a reference to another local, showing -// that this one is related to them somehow: one of ==, <, >=, etc.), or -// something unknown. -struct Value : public std::variant { - static Value unknown() { return Value(Unknown()); } - - bool isUnknown() const { return std::holds_alternative(*this); } - - bool operator==(const Value&) const = default; -}; - -// A span of values, [min, max] (inclusive). -// TODO: support more clever things like unions -struct Span { - // TODO: add "inclusive" for [ ] vs ( ) bounds? - Value min; - Value max; - - static Span unknown() { return Span{Unknown(), Unknown()}; } - - bool isUnknown() { return min.isUnknown() && max.isUnknown(); } - - bool operator==(const Span&) const = default; - - // Check if this span definitely includes a value inside it. If we don't know, - // return false. - bool includes(const Value& value); - // TODO: excludes..? - - // Check if this span is definitely smaller than a value (or false if we don't - // know). - bool lessThan(const Value& value); - - // Check if this span is definitely greater than a value (or false if we don't - // know). - bool greaterThan(const Value& value); -}; - -bool Span::includes(const Value& value) { - // In most cases, we don't know enough. - bool ret = false; - std::visit(overloaded{ - [&](const Literal& lit) { - // The value is a literal. We can infer something here if the - // span is a range of literals, checking if value is within - // [min, max]. - const Literal* minLit = std::get_if(&min); - if (minLit && *minLit == lit) { - ret = true; - return; - } - const Literal* maxLit = std::get_if(&max); - if (maxLit && *maxLit == lit) { - ret = true; - return; - } - if (lit.type.isNumber() && minLit && maxLit) { - // Numbers can be ordered. - assert(minLit->type == lit.type); - assert(maxLit->type == lit.type); - if (minLit->le(lit).getUnsigned() && - maxLit->ge(lit).getUnsigned()) { - ret = true; - } - } - }, - [&](const Index& local) { - // A local index can be compared to others. - const Index* minLocal = std::get_if(&min); - if (minLocal && *minLocal == local) { - ret = true; - return; - } - const Index* maxLocal = std::get_if(&max); - if (maxLocal && *maxLocal == local) { - ret = true; - } - }, - [&](const Unknown& unknown) {}, - }, - value); - return ret; -} - -bool Span::lessThan(const Value& value) { abort(); } - -bool Span::greaterThan(const Value& value) { abort(); } - -} // namespace wasm::span - -#endif // wasm_ir_span_h diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index abfcb6e02c7..684077171c8 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -26,6 +26,7 @@ set(passes_SOURCES CodeFolding.cpp ConstantFieldPropagation.cpp ConstHoisting.cpp + ConstraintAnalysis.cpp DataFlowOpts.cpp DeadArgumentElimination.cpp DeadArgumentElimination2.cpp @@ -107,7 +108,6 @@ set(passes_SOURCES StripToolchainAnnotations.cpp TraceCalls.cpp RandomizeBranchHints.cpp - RangeAnalysis.cpp RedundantSetElimination.cpp RemoveExports.cpp RemoveImports.cpp diff --git a/src/passes/RangeAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp similarity index 100% rename from src/passes/RangeAnalysis.cpp rename to src/passes/ConstraintAnalysis.cpp diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index bf845fa5013..06339ca5a6b 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -129,6 +129,9 @@ void PassRegistry::registerPasses() { registerPass("cfp-reftest", "propagate constant struct field values, using ref.test", createConstantFieldPropagationRefTestPass); + registerPass("constraint-analysis", + "finds and uses mathematical constraints on locals", + createConstraintAnalysisPass); registerPass( "dce", "removes unreachable code", createDeadCodeEliminationPass); registerPass("dealign", @@ -411,9 +414,6 @@ void PassRegistry::registerPasses() { registerPass("propagate-globals-globally", "propagate global values to other globals (useful for tests)", createPropagateGlobalsGloballyPass); - registerPass("range-analysis", - "finds and uses value ranges for locals", - createRangeAnalysisPass); registerPass("remove-non-js-ops", "removes operations incompatible with js", createRemoveNonJSOpsPass); diff --git a/src/passes/passes.h b/src/passes/passes.h index 4d0ca085259..3e289460eec 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -33,6 +33,7 @@ Pass* createCodePushingPass(); Pass* createConstHoistingPass(); Pass* createConstantFieldPropagationPass(); Pass* createConstantFieldPropagationRefTestPass(); +Pass* createConstraintAnalysisPass(); Pass* createDAEPass(); Pass* createDAEOptimizingPass(); Pass* createDAE2Pass(); @@ -135,7 +136,6 @@ Pass* createPrintFeaturesPass(); Pass* createPrintFunctionMapPass(); Pass* createPropagateGlobalsGloballyPass(); Pass* createRandomizeBranchHintsPass(); -Pass* createRangeAnalysisPass(); Pass* createRemoveNonJSOpsPass(); Pass* createRemoveRelaxedSIMDPass(); Pass* createRemoveExportsPass(); From 517dac0092d090fd237a89eeca212ba53ef3bc97 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 16:04:35 -0700 Subject: [PATCH 155/202] update --- test/lit/passes/{range-analysis.wast => constraint-analysis.wast} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/lit/passes/{range-analysis.wast => constraint-analysis.wast} (100%) diff --git a/test/lit/passes/range-analysis.wast b/test/lit/passes/constraint-analysis.wast similarity index 100% rename from test/lit/passes/range-analysis.wast rename to test/lit/passes/constraint-analysis.wast From 9522de6553773a72219a3aa83b1057164f1d7967 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 16:05:01 -0700 Subject: [PATCH 156/202] update --- test/lit/passes/constraint-analysis.wast | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index 89bb819e8e9..4f61ee7249a 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -1,12 +1,12 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. -;; RUN: wasm-opt %s --range-analysis -all -S -o - | filecheck %s +;; RUN: wasm-opt %s --constraint-analysis -all -S -o - | filecheck %s ;; Also run after optimize-instructions, which canonicalizes the order of ;; things like binary children. We want to see that this pass optimizes the ;; IR that optimize-instructions emits. -;; RUN: wasm-opt %s --optimize-instructions --range-analysis -all -S -o - | filecheck %s --check-prefix=OPTIN +;; RUN: wasm-opt %s --optimize-instructions --constraint-analysis -all -S -o - | filecheck %s --check-prefix=OPTIN (module ;; CHECK: (func $simple (type $1) From 0e358e3754b1c254fa208fd931b2915f003741f5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 16:06:02 -0700 Subject: [PATCH 157/202] update --- test/lit/passes/constraint-analysis.wast | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index 4f61ee7249a..1aa5daf6128 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -503,5 +503,3 @@ ) ) -;; TODO eqz -;; todo -split From a1781354c5e56007440ff85c1e497dc6c9898aed Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 16:08:07 -0700 Subject: [PATCH 158/202] update --- src/passes/ConstraintAnalysis.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index a437daf9f45..0cb73f20a78 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -87,7 +87,7 @@ struct RangeAnalysis void visitLocalGet(LocalGet* curr) { addAction(); } // XXX needed? void visitLocalSet(LocalSet* curr) { addAction(); } - void visitUnary(Unary* curr) { addAction(); } // XXX needed? + void visitUnary(Unary* curr) { addAction(); } void visitBinary(Binary* curr) { addAction(); } void visitRefEq(RefEq* curr) { addAction(); } void visitRefIsNull(RefIsNull* curr) { addAction(); } @@ -289,10 +289,10 @@ struct RangeAnalysis // sets the value for that local. void applyToConstraints(Expression* curr, LocalConstraintMap& constraints) { if (auto* set = curr->dynCast()) { + auto& localConstraints = constraints[set->index]; + localConstraints.clear(); if (Properties::isSingleConstantExpression(set->value)) { auto value = Properties::getLiteral(set->value); - auto& localConstraints = constraints[set->index]; - localConstraints.clear(); localConstraints.and_(Constraint{Abstract::Eq, value}); } } From e09cbda6a580d6a3cc3bb98dab2139b24445131b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 16:11:15 -0700 Subject: [PATCH 159/202] update --- src/passes/ConstraintAnalysis.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 0cb73f20a78..2371f6b9e1a 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -15,7 +15,7 @@ */ // -// Finds ranges of values for locals, and uses them. For example: +// Finds XXX ranges of values for locals, and uses them. For example: // // if (x > 10) { // assert(x > 0); // redundant and can be removed. @@ -60,19 +60,19 @@ struct Info { LocalConstraintMap endConstraints; }; -struct RangeAnalysis - : public WalkerPass, Info>> { +struct ConstraintAnalysis + : public WalkerPass, Info>> { bool isFunctionParallel() override { return true; } // Locals are not modified here. bool requiresNonNullableLocalFixups() override { return false; } std::unique_ptr create() override { - return std::make_unique(); + return std::make_unique(); } using Super = - WalkerPass, Info>>; + WalkerPass, Info>>; // Branches outside of the function can be ignored, as we only look at local // state in the function. @@ -104,7 +104,7 @@ struct RangeAnalysis // ifTrue and ifFalse blocks. std::unordered_map brancherBlocks; - static void doStartIfTrue(RangeAnalysis* self, Expression** currp) { + static void doStartIfTrue(ConstraintAnalysis* self, Expression** currp) { // We are right after the condition, so we are in the block before the If's // branching. self->brancherBlocks[*currp] = self->currBasicBlock; @@ -114,7 +114,7 @@ struct RangeAnalysis #endif #if 0 - static void doEndBranch(RangeAnalysis* self, Expression** currp) { + static void doEndBranch(ConstraintAnalysis* self, Expression** currp) { // We are right after the condition, so we are in the block before the If's // branching. XXX maybe leave for laterself->brancherBlocks[*currp] = self->currBasicBlock; @@ -301,6 +301,6 @@ struct RangeAnalysis } // anonymous namespace -Pass* createRangeAnalysisPass() { return new RangeAnalysis(); } +Pass* createConstraintAnalysisPass() { return new ConstraintAnalysis(); } } // namespace wasm From 0cb581ae08592f2e93a2ecd13eb1638db34541e3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 16:14:23 -0700 Subject: [PATCH 160/202] update --- test/lit/passes/constraint-analysis.wast | 89 +++++++++++++++++++++++- 1 file changed, 87 insertions(+), 2 deletions(-) diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index 1aa5daf6128..b3a0b27a41b 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -78,6 +78,85 @@ ) ) + ;; CHECK: (func $unknown (type $1) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.div_s + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 31) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $unknown (type $1) + ;; OPTIN-NEXT: (local $x i32) + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (i32.const 30) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (i32.div_u + ;; OPTIN-NEXT: (i32.const 1337) + ;; OPTIN-NEXT: (i32.const 42) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (i32.const 31) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $unknown + (local $x i32) + ;; Set x to an add. We can only optimize this if optimize-instructions first + ;; simplifies it to a constant. + (local.set $x + (i32.add + (i32.const 10) + (i32.const 20) + ) + ) + (drop + (i32.eq + (local.get $x) + (i32.const 30) + ) + ) + ;; When optimize-instructions does not help, we infer nothing. + (local.set $x + (i32.div_s + (i32.const 1337) + (i32.const 42) + ) + ) + (drop + (i32.eq + (local.get $x) + (i32.const 31) + ) + ) + ) + ;; CHECK: (func $multi-local (type $1) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local $y i32) @@ -300,10 +379,16 @@ ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (drop - ;; OPTIN-NEXT: (i32.const 0) + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (i32.const 10) + ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (drop - ;; OPTIN-NEXT: (i32.const 0) + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (i32.const 20) + ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) (func $multi-block-split (param $param i32) From 91cc33774ca702b2a477ed42cd6eb21728ac735d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Jun 2026 16:20:36 -0700 Subject: [PATCH 161/202] update --- src/passes/ConstraintAnalysis.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 2371f6b9e1a..792434a448c 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -53,10 +53,10 @@ using LocalConstraintMap = std::unordered_map; // In each basic block we will store the relevant operations, which are all // local gets and sets, branches, and uses of them. struct Info { - std::vector actions; // XXX just *? + std::vector actions; // For each local index, we track the constraints we know about it. We only do - // so at the end of each block, which is enough gfor the analysis below. + // so at the end of each block, which is enough for the analysis below. LocalConstraintMap endConstraints; }; @@ -85,7 +85,6 @@ struct ConstraintAnalysis } } - void visitLocalGet(LocalGet* curr) { addAction(); } // XXX needed? void visitLocalSet(LocalSet* curr) { addAction(); } void visitUnary(Unary* curr) { addAction(); } void visitBinary(Binary* curr) { addAction(); } From ea3db1760e8a993ab98f03624c63406e9638bf05 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 12 Jun 2026 12:35:29 -0700 Subject: [PATCH 162/202] refactor to use more helpers and less nesting --- src/ir/constraint.cpp | 80 ++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 46 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 77415260f08..86b18c4f0c3 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -24,10 +24,39 @@ namespace wasm::constraint { namespace { -// Core comparison of two constraints: whether a => b +// Evaluate whether a => b, where a and b are operations on constants. // // Returns a Result, or an empty option if we should keep working (i.e., a // result of Unknown means we are certain we can just return Unknown). +std::optional evalConstantPair(Abstract::Op aOp, const Literal& aConstant, Abstract::Op bOp, const Literal& bConstant) { + // x == X =?=> x == Y. True iff X == Y. + if (aOp == Abstract::Eq && bOp == Abstract::Eq) { + return aConstant == bConstant ? True : False; + } + + // x == X =?=> x != Y. True iff X != Y. + if (aOp == Abstract::Eq && bOp == Abstract::Ne) { + return aConstant == bConstant ? False : True; + } + + // x != X =?=> x == Y. False if X = Y, else unknown. + if (aOp == Abstract::Ne && bOp == Abstract::Eq) { + if (aConstant == bConstant) { + return False; + } + } + + // x != X =?=> x != Y. True if X = Y, else unknown. + if (aOp == Abstract::Ne && bOp == Abstract::Ne) { + if (aConstant == bConstant) { + return True; + } + } + + return {}; +} + +// Core comparison of two constraints: whether a => b std::optional evalPair(const Constraint& a, const Constraint& b) { // A thing always implies itself. if (a == b) { @@ -35,51 +64,10 @@ std::optional evalPair(const Constraint& a, const Constraint& b) { } // Comparisons of two constants. - if (auto* aConstant = std::get_if(&a.term)) { - if (auto* bConstant = std::get_if(&b.term)) { - switch (a.op) { - case Abstract::Eq: { - switch (b.op) { - case Abstract::Eq: { - // x == c vs x == c', and we already handled full equality - // earlier, hence c != c', and we found a contradiction. - assert(*aConstant != *bConstant); - return False; - } - case Abstract::Ne: { - // x == c vs x != c'. We can infer the result based on relating c - // and c'. - return *aConstant != *bConstant ? True : False; - } - default: { - } - } - break; - } - case Abstract::Ne: { - switch (b.op) { - case Abstract::Eq: { - // x != c vs x == c'. If c == c', we can infer. - if (*aConstant == *bConstant) { - return False; - } - return {}; - } - case Abstract::Ne: { - // x != c vs x != c', and we already handled full equality - // earlier, hence c != c', and we can infer nothing. - assert(*aConstant != *bConstant); - return {}; - } - default: { - } - } - break; - } - default: { - } - } - } + auto* aConstant = std::get_if(&a.term); + auto* bConstant = std::get_if(&b.term); + if (aConstant && bConstant) { + return evalConstantPair(a.op, *aConstant, b.op, *bConstant); } return {}; From 060d8aa2a79a6d842eae1d4717ee23bebd18c2ef Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 12 Jun 2026 12:45:45 -0700 Subject: [PATCH 163/202] format --- src/ir/constraint.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 86b18c4f0c3..17bd65878f1 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -28,7 +28,10 @@ namespace { // // Returns a Result, or an empty option if we should keep working (i.e., a // result of Unknown means we are certain we can just return Unknown). -std::optional evalConstantPair(Abstract::Op aOp, const Literal& aConstant, Abstract::Op bOp, const Literal& bConstant) { +std::optional evalConstantPair(Abstract::Op aOp, + const Literal& aConstant, + Abstract::Op bOp, + const Literal& bConstant) { // x == X =?=> x == Y. True iff X == Y. if (aOp == Abstract::Eq && bOp == Abstract::Eq) { return aConstant == bConstant ? True : False; From 088a4258734212ce6ebc8502d5f451fa0cffc946 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 12 Jun 2026 12:47:05 -0700 Subject: [PATCH 164/202] simpl --- src/ir/constraint.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 17bd65878f1..211c1827e8e 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -28,7 +28,7 @@ namespace { // // Returns a Result, or an empty option if we should keep working (i.e., a // result of Unknown means we are certain we can just return Unknown). -std::optional evalConstantPair(Abstract::Op aOp, +Result evalConstantPair(Abstract::Op aOp, const Literal& aConstant, Abstract::Op bOp, const Literal& bConstant) { @@ -56,11 +56,11 @@ std::optional evalConstantPair(Abstract::Op aOp, } } - return {}; + return Unknown; } // Core comparison of two constraints: whether a => b -std::optional evalPair(const Constraint& a, const Constraint& b) { +Result evalPair(const Constraint& a, const Constraint& b) { // A thing always implies itself. if (a == b) { return True; @@ -73,7 +73,7 @@ std::optional evalPair(const Constraint& a, const Constraint& b) { return evalConstantPair(a.op, *aConstant, b.op, *bConstant); } - return {}; + return Unknown; } } // anonymous namespace @@ -81,8 +81,9 @@ std::optional evalPair(const Constraint& a, const Constraint& b) { Result AndedConstraintSet::eval(const Constraint& condition) const { // Sometimes a single constraint is enough to determine the condition. for (auto& c : *this) { - if (auto result = evalPair(c, condition)) { - return *result; + auto result = evalPair(c, condition); + if (result != Unknown) { + return result; } } From 9267485dad3c32034d98d24f36627c7615fcf3b5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 12 Jun 2026 12:47:12 -0700 Subject: [PATCH 165/202] form --- src/ir/constraint.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 211c1827e8e..9aeef3ffae8 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -29,9 +29,9 @@ namespace { // Returns a Result, or an empty option if we should keep working (i.e., a // result of Unknown means we are certain we can just return Unknown). Result evalConstantPair(Abstract::Op aOp, - const Literal& aConstant, - Abstract::Op bOp, - const Literal& bConstant) { + const Literal& aConstant, + Abstract::Op bOp, + const Literal& bConstant) { // x == X =?=> x == Y. True iff X == Y. if (aOp == Abstract::Eq && bOp == Abstract::Eq) { return aConstant == bConstant ? True : False; From 3965c271380bada66c00272c166a7c97bcdb1708 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 12 Jun 2026 12:47:25 -0700 Subject: [PATCH 166/202] simpl --- src/ir/constraint.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 9aeef3ffae8..6c9c5e69472 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -25,9 +25,6 @@ namespace wasm::constraint { namespace { // Evaluate whether a => b, where a and b are operations on constants. -// -// Returns a Result, or an empty option if we should keep working (i.e., a -// result of Unknown means we are certain we can just return Unknown). Result evalConstantPair(Abstract::Op aOp, const Literal& aConstant, Abstract::Op bOp, From 280e7b37dfb2ecba2d18ad832b1a3d979dc62196 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 12 Jun 2026 12:49:26 -0700 Subject: [PATCH 167/202] remove --- src/ir/constraint.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index f32bd09892c..2a644bab4b1 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -44,8 +44,6 @@ struct Constraint { Term term; bool operator==(const Constraint&) const = default; - - operator bool() const { return op != Abstract::Invalid; } }; // We limit constraints to a low number to ensure good performance even with From 26dfc303721e0279ad7c88b34d4451ef5abfa5f7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 12 Jun 2026 12:51:25 -0700 Subject: [PATCH 168/202] move code as requested --- src/ir/constraint.cpp | 24 ++++++++++++++++++++++++ src/ir/constraint.h | 24 +----------------------- test/gtest/constraint.cpp | 6 ------ 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 6c9c5e69472..91e8a669e07 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -90,6 +90,30 @@ Result AndedConstraintSet::eval(const Constraint& condition) const { return Unknown; } +Result AndedConstraintSet::eval(const AndedConstraintSet& other) const { + if (other.empty()) { + // The empty set of constraints is always true. + return True; + } + + Result result = Unknown; + for (auto& c : other) { + auto currResult = eval(c); + if (currResult == Unknown) { + // If something is unknown, it all is. + return Unknown; + } + if (result == Unknown) { + // This is the first result + result = currResult; + } else if (result != currResult) { + // This is a later result, and different, so give up. + return Unknown; + } + } + return result; +} + void AndedConstraintSet::fuzzyOr(const AndedConstraintSet& other) { // If one is empty (no constraints, everything is true, and we can prove // nothing useful) then it does not add anything to the other. diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 2a644bab4b1..fce6a5cca82 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -69,29 +69,7 @@ struct AndedConstraintSet : inplace_vector { Result eval(const Constraint& condition) const; // Check an entire other set. - Result eval(const AndedConstraintSet& other) const { - if (other.empty()) { - // The empty set of constraints is always true. - return True; - } - - Result result = Unknown; - for (auto& c : other) { - auto currResult = eval(c); - if (currResult == Unknown) { - // If something is unknown, it all is. - return Unknown; - } - if (result == Unknown) { - // This is the first result - result = currResult; - } else if (result != currResult) { - // This is a later result, and different, so give up. - return Unknown; - } - } - return result; - } + Result eval(const AndedConstraintSet& other) const; bool full() const { return size() == MaxConstraints; } diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index bb7f1ead764..e9525abc9f9 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -6,12 +6,6 @@ using namespace wasm; using namespace wasm::Abstract; using namespace wasm::constraint; -TEST(ConstraintTest, TestEmpty) { - // An empty constraint is invalid. - Constraint c; - EXPECT_FALSE(c); -} - TEST(ConstraintTest, TestEq) { // Sets start empty. AndedConstraintSet s; From c1dacec66fda886260d0a276b71b80efa6f91f86 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 12 Jun 2026 12:56:13 -0700 Subject: [PATCH 169/202] eval => proves --- src/ir/constraint.cpp | 18 ++++++++-------- src/ir/constraint.h | 8 +++---- test/gtest/constraint.cpp | 44 +++++++++++++++++++-------------------- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 91e8a669e07..978a385cbbe 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -25,7 +25,7 @@ namespace wasm::constraint { namespace { // Evaluate whether a => b, where a and b are operations on constants. -Result evalConstantPair(Abstract::Op aOp, +Result provesConstantPair(Abstract::Op aOp, const Literal& aConstant, Abstract::Op bOp, const Literal& bConstant) { @@ -57,7 +57,7 @@ Result evalConstantPair(Abstract::Op aOp, } // Core comparison of two constraints: whether a => b -Result evalPair(const Constraint& a, const Constraint& b) { +Result provesPair(const Constraint& a, const Constraint& b) { // A thing always implies itself. if (a == b) { return True; @@ -67,7 +67,7 @@ Result evalPair(const Constraint& a, const Constraint& b) { auto* aConstant = std::get_if(&a.term); auto* bConstant = std::get_if(&b.term); if (aConstant && bConstant) { - return evalConstantPair(a.op, *aConstant, b.op, *bConstant); + return provesConstantPair(a.op, *aConstant, b.op, *bConstant); } return Unknown; @@ -75,10 +75,10 @@ Result evalPair(const Constraint& a, const Constraint& b) { } // anonymous namespace -Result AndedConstraintSet::eval(const Constraint& condition) const { +Result AndedConstraintSet::proves(const Constraint& condition) const { // Sometimes a single constraint is enough to determine the condition. for (auto& c : *this) { - auto result = evalPair(c, condition); + auto result = provesPair(c, condition); if (result != Unknown) { return result; } @@ -90,7 +90,7 @@ Result AndedConstraintSet::eval(const Constraint& condition) const { return Unknown; } -Result AndedConstraintSet::eval(const AndedConstraintSet& other) const { +Result AndedConstraintSet::proves(const AndedConstraintSet& other) const { if (other.empty()) { // The empty set of constraints is always true. return True; @@ -98,7 +98,7 @@ Result AndedConstraintSet::eval(const AndedConstraintSet& other) const { Result result = Unknown; for (auto& c : other) { - auto currResult = eval(c); + auto currResult = proves(c); if (currResult == Unknown) { // If something is unknown, it all is. return Unknown; @@ -128,11 +128,11 @@ void AndedConstraintSet::fuzzyOr(const AndedConstraintSet& other) { // If this is already implied by current constraints, then it is redundant. // E.g. if we are { x = 10 } and other is { x >= 0 } then all we need is // { x >= 0 } as the result of the OR. - if (eval(other) == True) { + if (proves(other) == True) { *this = other; return; } - if (other.eval(*this) == True) { + if (other.proves(*this) == True) { return; } diff --git a/src/ir/constraint.h b/src/ir/constraint.h index fce6a5cca82..6f6f5bd972c 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -66,10 +66,10 @@ struct AndedConstraintSet : inplace_vector { // { this } => { condition } // // https://en.wikipedia.org/wiki/Material_conditional#Truth_table - Result eval(const Constraint& condition) const; + Result proves(const Constraint& condition) const; // Check an entire other set. - Result eval(const AndedConstraintSet& other) const; + Result proves(const AndedConstraintSet& other) const; bool full() const { return size() == MaxConstraints; } @@ -98,9 +98,9 @@ struct AndedConstraintSet : inplace_vector { // Returning to the example, we can use this to optimize as follows: if // two code paths reaching a location have x == 5 and x == 10, so the value in // the merge location is either 5 or 10, then if we see some i32.ge_s that - // does x >= 0 then we can evaluate it with eval(): + // does x >= 0 then we can evaluate it with proves(): // - // { x >= 5 && x <= 10 }.eval({ x >= 0 }) == True + // { x >= 5 && x <= 10 }.proves({ x >= 0 }) == True // // And it is valid to optimize that i32.ge_s into a constant 1, since // diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index e9525abc9f9..2ee6841b65c 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -16,21 +16,21 @@ TEST(ConstraintTest, TestEq) { Constraint c{Eq, {Literal(int32_t(5))}}; // We can't infer anything using an empty set. - EXPECT_EQ(s.eval(c), Unknown); + EXPECT_EQ(s.proves(c), Unknown); // If we add it, then things check out: a thing always proves itself true. s.and_(c); EXPECT_EQ(s.size(), 1); - EXPECT_EQ(s.eval(c), True); + EXPECT_EQ(s.proves(c), True); // x == 10, a different number: we can infer false. - EXPECT_EQ(s.eval(Constraint{Eq, {Literal(int32_t(10))}}), False); + EXPECT_EQ(s.proves(Constraint{Eq, {Literal(int32_t(10))}}), False); // x != 15: we can infer true. - EXPECT_EQ(s.eval(Constraint{Ne, {Literal(int32_t(15))}}), True); + EXPECT_EQ(s.proves(Constraint{Ne, {Literal(int32_t(15))}}), True); // x != 5: we can infer false. - EXPECT_EQ(s.eval(Constraint{Ne, {Literal(int32_t(5))}}), False); + EXPECT_EQ(s.proves(Constraint{Ne, {Literal(int32_t(5))}}), False); } TEST(ConstraintTest, TestNe) { @@ -40,16 +40,16 @@ TEST(ConstraintTest, TestNe) { s.and_(c); // Checks out versus itself. - EXPECT_EQ(s.eval(c), True); + EXPECT_EQ(s.proves(c), True); // x == 10: we don't know. - EXPECT_EQ(s.eval(Constraint{Eq, {Literal(int32_t(10))}}), Unknown); + EXPECT_EQ(s.proves(Constraint{Eq, {Literal(int32_t(10))}}), Unknown); // x != 15: we don't know. - EXPECT_EQ(s.eval(Constraint{Ne, {Literal(int32_t(15))}}), Unknown); + EXPECT_EQ(s.proves(Constraint{Ne, {Literal(int32_t(15))}}), Unknown); // x == 5: we can infer false. - EXPECT_EQ(s.eval(Constraint{Eq, {Literal(int32_t(5))}}), False); + EXPECT_EQ(s.proves(Constraint{Eq, {Literal(int32_t(5))}}), False); } TEST(ConstraintTest, TestMulti) { @@ -61,20 +61,20 @@ TEST(ConstraintTest, TestMulti) { s.and_(d); // Each checks out versus itself. - EXPECT_EQ(s.eval(c), True); - EXPECT_EQ(s.eval(d), True); + EXPECT_EQ(s.proves(c), True); + EXPECT_EQ(s.proves(d), True); // x == 5: false. - EXPECT_EQ(s.eval(Constraint{Eq, {Literal(int32_t(5))}}), False); + EXPECT_EQ(s.proves(Constraint{Eq, {Literal(int32_t(5))}}), False); // x == 10: false. - EXPECT_EQ(s.eval(Constraint{Eq, {Literal(int32_t(10))}}), False); + EXPECT_EQ(s.proves(Constraint{Eq, {Literal(int32_t(10))}}), False); // x == 15: we don't know. - EXPECT_EQ(s.eval(Constraint{Eq, {Literal(int32_t(15))}}), Unknown); + EXPECT_EQ(s.proves(Constraint{Eq, {Literal(int32_t(15))}}), Unknown); // x != 15: we don't know. - EXPECT_EQ(s.eval(Constraint{Ne, {Literal(int32_t(15))}}), Unknown); + EXPECT_EQ(s.proves(Constraint{Ne, {Literal(int32_t(15))}}), Unknown); } TEST(ConstraintTest, TestSets) { @@ -84,34 +84,34 @@ TEST(ConstraintTest, TestSets) { AndedConstraintSet s; // Any set always proves itself to be true. - EXPECT_EQ(s.eval(s), True); + EXPECT_EQ(s.proves(s), True); // Ditto after adding something. s.and_(c); - EXPECT_EQ(s.eval(s), True); + EXPECT_EQ(s.proves(s), True); // Another set, empty. AndedConstraintSet t; // Any set always proves an empty set to be true. - EXPECT_EQ(s.eval(t), True); + EXPECT_EQ(s.proves(t), True); // Make both sets contain the same stuff. t.and_(c); - EXPECT_EQ(s.eval(t), True); + EXPECT_EQ(s.proves(t), True); // Now t has *different* stuff, x == 10, which given s is false. t.clear(); t.and_(Constraint{Eq, {Literal(int32_t(10))}}); - EXPECT_EQ(s.eval(t), False); + EXPECT_EQ(s.proves(t), False); // Same, with x != 10. Now we know it is true. t.clear(); t.and_(Constraint{Ne, {Literal(int32_t(10))}}); - EXPECT_EQ(s.eval(t), True); + EXPECT_EQ(s.proves(t), True); // In reverse, we can infer nothing: knowing x != 10 does not say if x == 5. - EXPECT_EQ(t.eval(s), Unknown); + EXPECT_EQ(t.proves(s), Unknown); } TEST(ConstraintTest, TestOrTrivial) { From 3952dfcdfab3a79c958792cb1f8a1d4eb332f87c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 12 Jun 2026 12:56:18 -0700 Subject: [PATCH 170/202] form --- src/ir/constraint.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 978a385cbbe..f2e54c85fd1 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -26,9 +26,9 @@ namespace { // Evaluate whether a => b, where a and b are operations on constants. Result provesConstantPair(Abstract::Op aOp, - const Literal& aConstant, - Abstract::Op bOp, - const Literal& bConstant) { + const Literal& aConstant, + Abstract::Op bOp, + const Literal& bConstant) { // x == X =?=> x == Y. True iff X == Y. if (aOp == Abstract::Eq && bOp == Abstract::Eq) { return aConstant == bConstant ? True : False; From 224425c1e1b814a5c3770face4f03cc0d3b76a05 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 12 Jun 2026 16:05:36 -0700 Subject: [PATCH 171/202] Update src/ir/constraint.cpp Co-authored-by: Thomas Lively --- src/ir/constraint.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index f2e54c85fd1..9c5457509da 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -53,6 +53,7 @@ Result provesConstantPair(Abstract::Op aOp, } } + TODO: handle >, >=, <, and <= return Unknown; } From 203d455c597abd7dac7920b7aa5c16c5182218cb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 12 Jun 2026 16:09:03 -0700 Subject: [PATCH 172/202] work --- src/ir/constraint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 9c5457509da..c2ba00d77c7 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -137,7 +137,7 @@ void AndedConstraintSet::fuzzyOr(const AndedConstraintSet& other) { return; } - // TODO smarts + // TODO smarts: handle <= > and so forth // Otherwise, we don't know how to nicely OR these things, and expand to the // trivial set of no constraints. From e4a2a49a41e9e1adf58a3abbd0d2069504c635dc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 12 Jun 2026 16:19:21 -0700 Subject: [PATCH 173/202] fix set op --- src/ir/constraint.cpp | 22 ++++++++++------------ test/gtest/constraint.cpp | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index c2ba00d77c7..e4119a513f8 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -53,7 +53,7 @@ Result provesConstantPair(Abstract::Op aOp, } } - TODO: handle >, >=, <, and <= + // TODO: handle >, >=, <, and <= return Unknown; } @@ -97,22 +97,20 @@ Result AndedConstraintSet::proves(const AndedConstraintSet& other) const { return True; } - Result result = Unknown; + bool hasUnknown = false; + for (auto& c : other) { - auto currResult = proves(c); - if (currResult == Unknown) { - // If something is unknown, it all is. - return Unknown; + auto result = proves(c); + if (result == False) { + // The entire conjunction is proven false. + return False; } if (result == Unknown) { - // This is the first result - result = currResult; - } else if (result != currResult) { - // This is a later result, and different, so give up. - return Unknown; + hasUnknown = true; } } - return result; + + return hasUnknown ? Unknown : True; } void AndedConstraintSet::fuzzyOr(const AndedConstraintSet& other) { diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 2ee6841b65c..09ea30ef092 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -114,6 +114,23 @@ TEST(ConstraintTest, TestSets) { EXPECT_EQ(t.proves(s), Unknown); } +TEST(ConstraintTest, TestSetsUnknown) { + // x != 5 + // x != 10 + AndedConstraintSet s; + s.and_(Constraint{Ne, {Literal(int32_t(5))}}); + s.and_(Constraint{Ne, {Literal(int32_t(10))}}); + + // x != 20, which is unknown by s. + AndedConstraintSet t; + t.and_(Constraint{Ne, {Literal(int32_t(20))}}); + EXPECT_EQ(s.proves(t), Unknown); + + // Add x == 10, which is false by s, and so the whole thing is false. + t.and_(Constraint{Eq, {Literal(int32_t(10))}}); + EXPECT_EQ(s.proves(t), False); +} + TEST(ConstraintTest, TestOrTrivial) { // { x == 5 } AndedConstraintSet s; From 22d2d3b13698488a66f7cd6c54237e93b53b30e3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 12 Jun 2026 16:27:52 -0700 Subject: [PATCH 174/202] fuzzyAnd, and make it drop new ones automatically, as suggested --- src/ir/constraint.cpp | 11 ++++++++ src/ir/constraint.h | 14 ++++------ test/gtest/constraint.cpp | 59 +++++++++++++++++++++++++++++---------- 3 files changed, 61 insertions(+), 23 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index e4119a513f8..fabcbfa06ca 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -113,6 +113,17 @@ Result AndedConstraintSet::proves(const AndedConstraintSet& other) const { return hasUnknown ? Unknown : True; } +void AndedConstraintSet::fuzzyAnd(const Constraint& c) { + if (size() < MaxConstraints) { + push_back(c); + return; + } + + // Otherwise, just do not add this one. + // TODO: We could try to be clever and see if one of the existing ones makes + // more sense to drop. +} + void AndedConstraintSet::fuzzyOr(const AndedConstraintSet& other) { // If one is empty (no constraints, everything is true, and we can prove // nothing useful) then it does not add anything to the other. diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 6f6f5bd972c..23ef83d2594 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -71,14 +71,12 @@ struct AndedConstraintSet : inplace_vector { // Check an entire other set. Result proves(const AndedConstraintSet& other) const; - bool full() const { return size() == MaxConstraints; } - - // Add a constraint to the set, ANDed with the others. The caller must make - // sure not to add too many (i.e. it is invalid to call this when full()). - void and_(const Constraint& c) { - assert(!full()); - push_back(c); - } + // Add a constraint to the set, ANDed with the others. This is a fuzzy + // operation because our capacity is bounded - we cannot have more than + // MaxConstraints. If too many are added, we will drop some, which means we + // will be able to prove less things (but we will never prove anything + // incorrectly). + void fuzzyAnd(const Constraint& c); // Merge constraints using OR. We cannot represent such a thing directly // (we only use AND), so we approximate it in a fuzzy way. For example, this diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 09ea30ef092..8bb2ba38908 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -19,7 +19,7 @@ TEST(ConstraintTest, TestEq) { EXPECT_EQ(s.proves(c), Unknown); // If we add it, then things check out: a thing always proves itself true. - s.and_(c); + s.fuzzyAnd(c); EXPECT_EQ(s.size(), 1); EXPECT_EQ(s.proves(c), True); @@ -37,7 +37,7 @@ TEST(ConstraintTest, TestNe) { AndedConstraintSet s; // x != 5 Constraint c{Ne, {Literal(int32_t(5))}}; - s.and_(c); + s.fuzzyAnd(c); // Checks out versus itself. EXPECT_EQ(s.proves(c), True); @@ -57,8 +57,8 @@ TEST(ConstraintTest, TestMulti) { // x != 5 && x != 10 Constraint c{Ne, {Literal(int32_t(5))}}; Constraint d{Ne, {Literal(int32_t(10))}}; - s.and_(c); - s.and_(d); + s.fuzzyAnd(c); + s.fuzzyAnd(d); // Each checks out versus itself. EXPECT_EQ(s.proves(c), True); @@ -87,7 +87,7 @@ TEST(ConstraintTest, TestSets) { EXPECT_EQ(s.proves(s), True); // Ditto after adding something. - s.and_(c); + s.fuzzyAnd(c); EXPECT_EQ(s.proves(s), True); // Another set, empty. @@ -97,17 +97,17 @@ TEST(ConstraintTest, TestSets) { EXPECT_EQ(s.proves(t), True); // Make both sets contain the same stuff. - t.and_(c); + t.fuzzyAnd(c); EXPECT_EQ(s.proves(t), True); // Now t has *different* stuff, x == 10, which given s is false. t.clear(); - t.and_(Constraint{Eq, {Literal(int32_t(10))}}); + t.fuzzyAnd(Constraint{Eq, {Literal(int32_t(10))}}); EXPECT_EQ(s.proves(t), False); // Same, with x != 10. Now we know it is true. t.clear(); - t.and_(Constraint{Ne, {Literal(int32_t(10))}}); + t.fuzzyAnd(Constraint{Ne, {Literal(int32_t(10))}}); EXPECT_EQ(s.proves(t), True); // In reverse, we can infer nothing: knowing x != 10 does not say if x == 5. @@ -118,23 +118,23 @@ TEST(ConstraintTest, TestSetsUnknown) { // x != 5 // x != 10 AndedConstraintSet s; - s.and_(Constraint{Ne, {Literal(int32_t(5))}}); - s.and_(Constraint{Ne, {Literal(int32_t(10))}}); + s.fuzzyAnd(Constraint{Ne, {Literal(int32_t(5))}}); + s.fuzzyAnd(Constraint{Ne, {Literal(int32_t(10))}}); // x != 20, which is unknown by s. AndedConstraintSet t; - t.and_(Constraint{Ne, {Literal(int32_t(20))}}); + t.fuzzyAnd(Constraint{Ne, {Literal(int32_t(20))}}); EXPECT_EQ(s.proves(t), Unknown); // Add x == 10, which is false by s, and so the whole thing is false. - t.and_(Constraint{Eq, {Literal(int32_t(10))}}); + t.fuzzyAnd(Constraint{Eq, {Literal(int32_t(10))}}); EXPECT_EQ(s.proves(t), False); } TEST(ConstraintTest, TestOrTrivial) { // { x == 5 } AndedConstraintSet s; - s.and_(Constraint{Eq, {Literal(int32_t(5))}}); + s.fuzzyAnd(Constraint{Eq, {Literal(int32_t(5))}}); // { } AndedConstraintSet empty; @@ -158,11 +158,11 @@ TEST(ConstraintTest, TestOrTrivial) { TEST(ConstraintTest, TestOrImplies) { // { x == 5 } AndedConstraintSet s; - s.and_(Constraint{Eq, {Literal(int32_t(5))}}); + s.fuzzyAnd(Constraint{Eq, {Literal(int32_t(5))}}); // { x != 10 } AndedConstraintSet t; - t.and_(Constraint{Ne, {Literal(int32_t(10))}}); + t.fuzzyAnd(Constraint{Ne, {Literal(int32_t(10))}}); // ORing these leaves us with x != 10. auto u = s; @@ -175,5 +175,34 @@ TEST(ConstraintTest, TestOrImplies) { EXPECT_EQ(u, t); } +TEST(ConstraintTest, TestMaxCapacity) { + EXPECT_EQ(MaxConstraints, 3); + + // Max out with x != 10, 20, 30 + Constraint not10{Ne, {Literal(int32_t(10))}}; + Constraint not20{Ne, {Literal(int32_t(20))}}; + Constraint not30{Ne, {Literal(int32_t(30))}}; + + AndedConstraintSet s; + s.fuzzyAnd(not10); + s.fuzzyAnd(not20); + s.fuzzyAnd(not30); + + // We can prove all those. + EXPECT_EQ(s.proves(not10), True); + EXPECT_EQ(s.proves(not20), True); + EXPECT_EQ(s.proves(not30), True); + + // Add another, exceeding the capacity. + Constraint not40{Ne, {Literal(int32_t(40))}}; + s.fuzzyAnd(not40); + + // We can prove the old ones but not the new. + EXPECT_EQ(s.proves(not10), True); + EXPECT_EQ(s.proves(not20), True); + EXPECT_EQ(s.proves(not30), True); + EXPECT_EQ(s.proves(not40), Unknown); +} + // TODO: test a fuzzyOr of { x = 10 } and { x >= 0 }, once we support // inequalities From bdc4911db26b10ab6e90de1fa591b21c8fd3dfd7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 16 Jun 2026 11:35:07 -0700 Subject: [PATCH 175/202] avoid fuzzy, use approximate --- src/ir/constraint.cpp | 4 ++-- src/ir/constraint.h | 22 ++++++++--------- test/gtest/constraint.cpp | 50 +++++++++++++++++++-------------------- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index fabcbfa06ca..57420a1733f 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -113,7 +113,7 @@ Result AndedConstraintSet::proves(const AndedConstraintSet& other) const { return hasUnknown ? Unknown : True; } -void AndedConstraintSet::fuzzyAnd(const Constraint& c) { +void AndedConstraintSet::approximateAnd(const Constraint& c) { if (size() < MaxConstraints) { push_back(c); return; @@ -124,7 +124,7 @@ void AndedConstraintSet::fuzzyAnd(const Constraint& c) { // more sense to drop. } -void AndedConstraintSet::fuzzyOr(const AndedConstraintSet& other) { +void AndedConstraintSet::approximateOr(const AndedConstraintSet& other) { // If one is empty (no constraints, everything is true, and we can prove // nothing useful) then it does not add anything to the other. if (empty()) { diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 23ef83d2594..3d32f646b14 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -71,26 +71,26 @@ struct AndedConstraintSet : inplace_vector { // Check an entire other set. Result proves(const AndedConstraintSet& other) const; - // Add a constraint to the set, ANDed with the others. This is a fuzzy + // Add a constraint to the set, ANDed with the others. This is an approximate // operation because our capacity is bounded - we cannot have more than // MaxConstraints. If too many are added, we will drop some, which means we // will be able to prove less things (but we will never prove anything // incorrectly). - void fuzzyAnd(const Constraint& c); + void approximateAnd(const Constraint& c); // Merge constraints using OR. We cannot represent such a thing directly - // (we only use AND), so we approximate it in a fuzzy way. For example, this - // would be valid: + // (we only use AND), so we approximate it in an approximate way. For example, + // this would be valid: // - // fuzzyOr({ x == 5 }, { x == 10 }) == { x >= 5 && x <= 10 } + // approximateOr({ x == 5 }, { x == 10 }) == { x >= 5 && x <= 10 } // // Note how the result here still accepts the values 5 and 10, but it also // allows more. Formally, this has the following mathematical property: // - // (X || Y) => fuzzyOr(X, Y) + // (X || Y) => approximateOr(X, Y) // // That is, if X or Y is true, the result of fuzzOr is also true. But the - // reverse is not always the case: fuzzyOr may be true without X || Y being + // reverse is not always so: approximateOr may be true without X || Y being // true (see the truth table linked above, and the value 8 in the example). // // Returning to the example, we can use this to optimize as follows: if @@ -108,16 +108,16 @@ struct AndedConstraintSet : inplace_vector { // // I.e. the constraints imply the truth of the thing we are evaluating. // - // Note that the fuzziness here means that fuzzyOr() can do a better or a - // worse job. It is always valid for fuzzyOr to return { } or any other + // Note that the fuzziness here means that approximateOr() can do a better / + // worse job. It is always valid for approximateOr to return { } or any other // always-true thing (see the truth table linked above). But then: // // { x == 5 || x == 10 } => // { } =!!> // { x >= 0 } // - // If we become too fuzzy, we lose the ability to imply anything useful. - void fuzzyOr(const AndedConstraintSet& other); + // If we become too imprecise, we lose the ability to imply anything useful. + void approximateOr(const AndedConstraintSet& other); }; } // namespace wasm::constraint diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 8bb2ba38908..a8bb66f75d6 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -19,7 +19,7 @@ TEST(ConstraintTest, TestEq) { EXPECT_EQ(s.proves(c), Unknown); // If we add it, then things check out: a thing always proves itself true. - s.fuzzyAnd(c); + s.approximateAnd(c); EXPECT_EQ(s.size(), 1); EXPECT_EQ(s.proves(c), True); @@ -37,7 +37,7 @@ TEST(ConstraintTest, TestNe) { AndedConstraintSet s; // x != 5 Constraint c{Ne, {Literal(int32_t(5))}}; - s.fuzzyAnd(c); + s.approximateAnd(c); // Checks out versus itself. EXPECT_EQ(s.proves(c), True); @@ -57,8 +57,8 @@ TEST(ConstraintTest, TestMulti) { // x != 5 && x != 10 Constraint c{Ne, {Literal(int32_t(5))}}; Constraint d{Ne, {Literal(int32_t(10))}}; - s.fuzzyAnd(c); - s.fuzzyAnd(d); + s.approximateAnd(c); + s.approximateAnd(d); // Each checks out versus itself. EXPECT_EQ(s.proves(c), True); @@ -87,7 +87,7 @@ TEST(ConstraintTest, TestSets) { EXPECT_EQ(s.proves(s), True); // Ditto after adding something. - s.fuzzyAnd(c); + s.approximateAnd(c); EXPECT_EQ(s.proves(s), True); // Another set, empty. @@ -97,17 +97,17 @@ TEST(ConstraintTest, TestSets) { EXPECT_EQ(s.proves(t), True); // Make both sets contain the same stuff. - t.fuzzyAnd(c); + t.approximateAnd(c); EXPECT_EQ(s.proves(t), True); // Now t has *different* stuff, x == 10, which given s is false. t.clear(); - t.fuzzyAnd(Constraint{Eq, {Literal(int32_t(10))}}); + t.approximateAnd(Constraint{Eq, {Literal(int32_t(10))}}); EXPECT_EQ(s.proves(t), False); // Same, with x != 10. Now we know it is true. t.clear(); - t.fuzzyAnd(Constraint{Ne, {Literal(int32_t(10))}}); + t.approximateAnd(Constraint{Ne, {Literal(int32_t(10))}}); EXPECT_EQ(s.proves(t), True); // In reverse, we can infer nothing: knowing x != 10 does not say if x == 5. @@ -118,60 +118,60 @@ TEST(ConstraintTest, TestSetsUnknown) { // x != 5 // x != 10 AndedConstraintSet s; - s.fuzzyAnd(Constraint{Ne, {Literal(int32_t(5))}}); - s.fuzzyAnd(Constraint{Ne, {Literal(int32_t(10))}}); + s.approximateAnd(Constraint{Ne, {Literal(int32_t(5))}}); + s.approximateAnd(Constraint{Ne, {Literal(int32_t(10))}}); // x != 20, which is unknown by s. AndedConstraintSet t; - t.fuzzyAnd(Constraint{Ne, {Literal(int32_t(20))}}); + t.approximateAnd(Constraint{Ne, {Literal(int32_t(20))}}); EXPECT_EQ(s.proves(t), Unknown); // Add x == 10, which is false by s, and so the whole thing is false. - t.fuzzyAnd(Constraint{Eq, {Literal(int32_t(10))}}); + t.approximateAnd(Constraint{Eq, {Literal(int32_t(10))}}); EXPECT_EQ(s.proves(t), False); } TEST(ConstraintTest, TestOrTrivial) { // { x == 5 } AndedConstraintSet s; - s.fuzzyAnd(Constraint{Eq, {Literal(int32_t(5))}}); + s.approximateAnd(Constraint{Eq, {Literal(int32_t(5))}}); // { } AndedConstraintSet empty; // Anything ORed with the empty set is unchanged. auto t = s; - t.fuzzyOr(empty); + t.approximateOr(empty); EXPECT_EQ(t, s); // Flipped. t = empty; - t.fuzzyOr(s); + t.approximateOr(s); EXPECT_EQ(t, s); // ORing with oneself changes nothing t = s; - t.fuzzyOr(s); + t.approximateOr(s); EXPECT_EQ(t, s); } TEST(ConstraintTest, TestOrImplies) { // { x == 5 } AndedConstraintSet s; - s.fuzzyAnd(Constraint{Eq, {Literal(int32_t(5))}}); + s.approximateAnd(Constraint{Eq, {Literal(int32_t(5))}}); // { x != 10 } AndedConstraintSet t; - t.fuzzyAnd(Constraint{Ne, {Literal(int32_t(10))}}); + t.approximateAnd(Constraint{Ne, {Literal(int32_t(10))}}); // ORing these leaves us with x != 10. auto u = s; - u.fuzzyOr(t); + u.approximateOr(t); EXPECT_EQ(u, t); // Flipped. u = t; - u.fuzzyOr(s); + u.approximateOr(s); EXPECT_EQ(u, t); } @@ -184,9 +184,9 @@ TEST(ConstraintTest, TestMaxCapacity) { Constraint not30{Ne, {Literal(int32_t(30))}}; AndedConstraintSet s; - s.fuzzyAnd(not10); - s.fuzzyAnd(not20); - s.fuzzyAnd(not30); + s.approximateAnd(not10); + s.approximateAnd(not20); + s.approximateAnd(not30); // We can prove all those. EXPECT_EQ(s.proves(not10), True); @@ -195,7 +195,7 @@ TEST(ConstraintTest, TestMaxCapacity) { // Add another, exceeding the capacity. Constraint not40{Ne, {Literal(int32_t(40))}}; - s.fuzzyAnd(not40); + s.approximateAnd(not40); // We can prove the old ones but not the new. EXPECT_EQ(s.proves(not10), True); @@ -204,5 +204,5 @@ TEST(ConstraintTest, TestMaxCapacity) { EXPECT_EQ(s.proves(not40), Unknown); } -// TODO: test a fuzzyOr of { x = 10 } and { x >= 0 }, once we support +// TODO: test an approximateOr of { x = 10 } and { x >= 0 }, once we support // inequalities From c9240414ed99bfdf6bcf6eff5da4b025fee73827 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 16 Jun 2026 12:28:46 -0700 Subject: [PATCH 176/202] remove Invalid --- src/ir/abstract.h | 3 +-- src/ir/constraint.h | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ir/abstract.h b/src/ir/abstract.h index d0f3794908c..04b04e34223 100644 --- a/src/ir/abstract.h +++ b/src/ir/abstract.h @@ -56,8 +56,7 @@ enum Op { GtS, GtU, GeS, - GeU, - Invalid + GeU }; inline bool hasAnyRotateShift(BinaryOp op) { diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 3d32f646b14..4f0852d4dd1 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -40,7 +40,7 @@ struct Term : public std::variant { // A constraint: some operation and some value, like "is equal to 17" or "is // less than local 6". struct Constraint { - Abstract::Op op = Abstract::Invalid; + Abstract::Op op; Term term; bool operator==(const Constraint&) const = default; From 90aabfad1d49d4b7b31e80bde1ed5ea49bc951ba Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 16 Jun 2026 12:45:23 -0700 Subject: [PATCH 177/202] merg --- src/passes/ConstraintAnalysis.cpp | 49 +++---------------------------- 1 file changed, 4 insertions(+), 45 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 792434a448c..15bb674df9e 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -15,20 +15,12 @@ */ // -// Finds XXX ranges of values for locals, and uses them. For example: +// Use mathematical constraint solving to optimize. For example: // // if (x > 10) { // assert(x > 0); // redundant and can be removed. // } // -// TODO: Compare locals, inferring that x <= y in some range (necessary for -// software bounds check removal. -// TODO: Look not just at integers but also references -// - -// XXX actually a ConstraintAnalysis! Find constraints like x >= 0, x < y and -// link each local to the constraints on it, a list up to fixed depth. Then -// chak and compress it as we goo etc. #include "cfg/cfg-traversal.h" #include "ir/constraint.h" @@ -71,9 +63,6 @@ struct ConstraintAnalysis return std::make_unique(); } - using Super = - WalkerPass, Info>>; - // Branches outside of the function can be ignored, as we only look at local // state in the function. bool ignoreBranchesOutsideOfFunc = true; @@ -91,36 +80,6 @@ struct ConstraintAnalysis void visitRefEq(RefEq* curr) { addAction(); } void visitRefIsNull(RefIsNull* curr) { addAction(); } -#if 0 - // Track the branches we reason about. CFGWalker builds a CFG, and we want to - // add information on top of that about which branch is due to which - // instruction. For example, if block A branches to B and C, we want to know - // if A ends in a br_if, so we can apply its condition to the branches to B - // (if the condition is true) and C (if false). - - // Maps each branching instruction to the basic block right before the - // branchings. For example, for an If, this is the block that branches to the - // ifTrue and ifFalse blocks. - std::unordered_map brancherBlocks; - - static void doStartIfTrue(ConstraintAnalysis* self, Expression** currp) { - // We are right after the condition, so we are in the block before the If's - // branching. - self->brancherBlocks[*currp] = self->currBasicBlock; - - Super::doStartIfTrue(self, currp); - } -#endif - -#if 0 - static void doEndBranch(ConstraintAnalysis* self, Expression** currp) { - // We are right after the condition, so we are in the block before the If's - // branching. - XXX maybe leave for laterself->brancherBlocks[*currp] = self->currBasicBlock; - Super::doEndBranch(self, currp); - } -#endif - // We start with the relevant locals, i.e. which we could optimize: for // example, if we see (i32.eqz (local.get $x)) then we know that information // about $x might resolve the eqz, and we compute it and things related to @@ -246,7 +205,7 @@ struct ConstraintAnalysis for (auto local : relevantLocals) { AndedConstraintSet& merged = constraints[local]; for (auto* pred : block->in) { - merged.fuzzyOr(getConstraintsFromPredToSucc(pred, block, local)); + merged.approximateOr(getConstraintsFromPredToSucc(pred, block, local)); } } @@ -262,7 +221,7 @@ struct ConstraintAnalysis // TODO: support tuples if (type.size() == 1 && LiteralUtils::canMakeZero(type)) { auto value = Literal::makeZero(type); - constraints[i].and_(Constraint{Abstract::Eq, value}); + constraints[i].approximateAnd(Constraint{Abstract::Eq, value}); } } } @@ -292,7 +251,7 @@ struct ConstraintAnalysis localConstraints.clear(); if (Properties::isSingleConstantExpression(set->value)) { auto value = Properties::getLiteral(set->value); - localConstraints.and_(Constraint{Abstract::Eq, value}); + localConstraints.approximateAnd(Constraint{Abstract::Eq, value}); } } } From 81714b22f43f45ac9db17f7b5bdd76a4b314f3e5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 16 Jun 2026 12:45:31 -0700 Subject: [PATCH 178/202] merg --- 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 15bb674df9e..c6480e00774 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -181,7 +181,7 @@ struct ConstraintAnalysis return; } auto& localConstraints = iter->second; - Result result = localConstraints.eval(parsed->constraint); + Result result = localConstraints.proves(parsed->constraint); 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 edf805901c4f7efe73be4a88b1aff01754afec78 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 16 Jun 2026 12:50:34 -0700 Subject: [PATCH 179/202] work --- src/passes/ConstraintAnalysis.cpp | 51 +++++-------------------------- 1 file changed, 7 insertions(+), 44 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index c6480e00774..1c7b16df4c8 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -80,44 +80,10 @@ struct ConstraintAnalysis void visitRefEq(RefEq* curr) { addAction(); } void visitRefIsNull(RefIsNull* curr) { addAction(); } - // We start with the relevant locals, i.e. which we could optimize: for - // example, if we see (i32.eqz (local.get $x)) then we know that information - // about $x might resolve the eqz, and we compute it and things related to - // it. - std::unordered_set relevantLocals; - void visitFunction(Function* curr) { - // Now that the walk is complete and we have a CFG, find things to optimize. - - auto maybeAdd = [&](Expression* value) { - // Given a value flowing into something we can optimize, see if there is a - // local there, and if so, mark it as relevant. - // TODO: handle tee - // TODO: handle fallthrough values - if (auto* get = value->dynCast()) { - relevantLocals.insert(get->index); - } - }; - - for (auto& block : basicBlocks) { - for (auto** currp : block->contents.actions) { - auto* curr = *currp; - // TODO: specific unary/binary ops - if (auto* unary = curr->dynCast()) { - maybeAdd(unary->value); - } else if (auto* binary = curr->dynCast()) { - maybeAdd(binary->left); - maybeAdd(binary->right); - } - } - } - - // Values can flow between locals: if x is relevant, and y is written to it, - // we must consider y relevant too. TODO? - if (!relevantLocals.empty()) { - flow(); - optimize(); - } + // TODO: optimize for speed, find relevant locals etc. + flow(); + optimize(); } // Flow infos around until we have inferred all we can about the constraints @@ -131,7 +97,7 @@ struct ConstraintAnalysis while (!work.empty()) { auto* block = work.pop(); - // Merge incoming data. + // Merge incoming data to get the status at the start of the block. LocalConstraintMap constraints = mergeIncoming(block); // Go through the block, applying things. @@ -156,19 +122,16 @@ struct ConstraintAnalysis // Follow the general shape of flow(): we need to see what the state is // at each intermediate point inside the block. (Flowing between blocks is // of course not needed at this stage.) - LocalConstraintMap constraints = mergeIncoming(block.get()); for (auto** currp : block->contents.actions) { applyToConstraints(*currp, constraints); - optimizeExpression(currp, block.get(), constraints); + optimizeExpression(currp, constraints); } } } - // Given a binary XXXand its block, try to optimize it. We provide the pointer - // to the binary, so that it can be replaced if optimizable. + // Given an expression and the constraints on it, optimize it. void optimizeExpression(Expression** currp, - BasicBlock* block, const LocalConstraintMap& constraints) { auto* curr = *currp; auto parsed = LocalConstraint::parse(curr); @@ -202,7 +165,7 @@ struct ConstraintAnalysis LocalConstraintMap constraints; // For each relevant local, merge its constraints. - for (auto local : relevantLocals) { + for (auto local : relevantLocals) { XXX AndedConstraintSet& merged = constraints[local]; for (auto* pred : block->in) { merged.approximateOr(getConstraintsFromPredToSucc(pred, block, local)); From b1522c25023dd755cba9e26871e2cd226677fd12 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 16 Jun 2026 16:23:39 -0700 Subject: [PATCH 180/202] go --- src/ir/constraint.cpp | 15 +++++++++++++++ src/ir/constraint.h | 7 +++++++ src/passes/ConstraintAnalysis.cpp | 28 ++++++++++++---------------- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 91f0a9bdf7f..b48cc776f75 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -215,4 +215,19 @@ std::optional LocalConstraint::parse(Expression* curr) { return {}; } +void LocalConstraintMap::approximateOr(const LocalConstraintMap& other) { + // Find things in both, and OR them. + for (auto& [local, constraints] : other) { + if (auto iter = find(local); iter != end()) { + iter->second.approximateOr(constraints); + } + } + + // Remove things only in us. + std::erase_if(*this, [&](const auto& item) { + const auto& [local, constraints] = item; + return !other.contains(local); + }); +} + } // namespace wasm::constraint diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 47ecd315ebb..d61777318a4 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -137,6 +137,13 @@ struct LocalConstraint { static std::optional parse(Expression* curr); }; +// A map of locals and their constraints. +struct LocalConstraintMap : public std::unordered_map { + // Perform an OR as above on each local that appears in both maps. If a local + // appears only in one, we can infer nothing, and drop it. + void approximateOr(const LocalConstraintMap& other); +}; + } // namespace wasm::constraint #endif // wasm_ir_constraint_h diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 1c7b16df4c8..05bd66d3183 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -40,8 +40,6 @@ using namespace wasm::constraint; namespace { -using LocalConstraintMap = std::unordered_map; - // In each basic block we will store the relevant operations, which are all // local gets and sets, branches, and uses of them. struct Info { @@ -164,11 +162,15 @@ struct ConstraintAnalysis LocalConstraintMap mergeIncoming(BasicBlock* block) { LocalConstraintMap constraints; - // For each relevant local, merge its constraints. - for (auto local : relevantLocals) { XXX - AndedConstraintSet& merged = constraints[local]; - for (auto* pred : block->in) { - merged.approximateOr(getConstraintsFromPredToSucc(pred, block, local)); + // Merge all preds. + for (auto* pred : block->in) { + auto& predConstraints = getConstraintsFromPredToSucc(pred, block); + if (pred == *block->in.begin()) { + // This is the first. Just copy. + constraints = predConstraints; + } else { + // Merge in subsequent ones. + constraints.approximateOr(predConstraints); } } @@ -194,16 +196,10 @@ struct ConstraintAnalysis // Given a source (predecessor) and a target (successor) block, find the span // of a particular local as it arrives to that target from that successor. - AndedConstraintSet getConstraintsFromPredToSucc(BasicBlock* pred, - BasicBlock* block, - Index local) { - auto iter = pred->contents.endConstraints.find(local); - if (iter == pred->contents.endConstraints.end()) { - return {}; - } - + const LocalConstraintMap& getConstraintsFromPredToSucc(BasicBlock* pred, + BasicBlock* block) { // TODO: use conditional branching to send different values along branches - return iter->second; + return pred->contents.endConstraints.find(local); } // Given an expression, apply it to the constraints. For example, a local.set From 97ec6b59627b19915d918e4bfbbeb2edd3abd2af Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 16 Jun 2026 16:24:10 -0700 Subject: [PATCH 181/202] fix --- 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 05bd66d3183..432030a3329 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -199,7 +199,7 @@ struct ConstraintAnalysis const LocalConstraintMap& getConstraintsFromPredToSucc(BasicBlock* pred, BasicBlock* block) { // TODO: use conditional branching to send different values along branches - return pred->contents.endConstraints.find(local); + return pred->contents.endConstraints; } // Given an expression, apply it to the constraints. For example, a local.set From 966676db6ae166c34c801eb2b33f1fb84348c47c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 16 Jun 2026 16:39:02 -0700 Subject: [PATCH 182/202] go --- src/passes/ConstraintAnalysis.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 432030a3329..537c9002608 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -17,8 +17,8 @@ // // Use mathematical constraint solving to optimize. For example: // -// if (x > 10) { -// assert(x > 0); // redundant and can be removed. +// if (x == 10) { +// assert(x != 0); // redundant and can be removed. // } // @@ -194,8 +194,8 @@ struct ConstraintAnalysis return constraints; } - // Given a source (predecessor) and a target (successor) block, find the span - // of a particular local as it arrives to that target from that successor. + // Given a source (predecessor) and a target (successor) block, find the + // constraints for locals as they arrive to that target from that successor. const LocalConstraintMap& getConstraintsFromPredToSucc(BasicBlock* pred, BasicBlock* block) { // TODO: use conditional branching to send different values along branches From 9645e714ed5ba43e3953ef76ae193ca10ca8f319 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 16 Jun 2026 16:48:39 -0700 Subject: [PATCH 183/202] Update src/ir/constraint.h Co-authored-by: Thomas Lively --- src/ir/constraint.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 4f0852d4dd1..d21966ff2d9 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -78,7 +78,7 @@ struct AndedConstraintSet : inplace_vector { // incorrectly). void approximateAnd(const Constraint& c); - // Merge constraints using OR. We cannot represent such a thing directly + // Merge constraints using OR. We cannot always represent such a thing directly // (we only use AND), so we approximate it in an approximate way. For example, // this would be valid: // From 64e0a86f74d932c5bbff6ae48ecb8c3997cba022 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 16 Jun 2026 16:48:54 -0700 Subject: [PATCH 184/202] Update src/ir/constraint.h Co-authored-by: Thomas Lively --- src/ir/constraint.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index d21966ff2d9..95703b83369 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -79,7 +79,7 @@ struct AndedConstraintSet : inplace_vector { void approximateAnd(const Constraint& c); // Merge constraints using OR. We cannot always represent such a thing directly - // (we only use AND), so we approximate it in an approximate way. For example, + // (we only use AND), so we approximate it. For example, // this would be valid: // // approximateOr({ x == 5 }, { x == 10 }) == { x >= 5 && x <= 10 } From 0e0bfb7730e65df5c2e318e569fd918c890d87f1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 16 Jun 2026 16:49:09 -0700 Subject: [PATCH 185/202] Update src/ir/constraint.h Co-authored-by: Thomas Lively --- src/ir/constraint.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 95703b83369..e566a2ea61a 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -89,7 +89,7 @@ struct AndedConstraintSet : inplace_vector { // // (X || Y) => approximateOr(X, Y) // - // That is, if X or Y is true, the result of fuzzOr is also true. But the + // That is, if X or Y is true, the result of approximateOr is also true. But the // reverse is not always so: approximateOr may be true without X || Y being // true (see the truth table linked above, and the value 8 in the example). // From 6947fc3378fcc197674768509c868259b32e54b4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 16 Jun 2026 16:49:21 -0700 Subject: [PATCH 186/202] Update src/ir/constraint.h Co-authored-by: Thomas Lively --- src/ir/constraint.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index e566a2ea61a..06c0a153af2 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -108,7 +108,7 @@ struct AndedConstraintSet : inplace_vector { // // I.e. the constraints imply the truth of the thing we are evaluating. // - // Note that the fuzziness here means that approximateOr() can do a better / + // Note that the approximation here means that approximateOr() can do a better / // worse job. It is always valid for approximateOr to return { } or any other // always-true thing (see the truth table linked above). But then: // From 2b30208c319893677577b236c4b276b146ae8346 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 16 Jun 2026 16:52:18 -0700 Subject: [PATCH 187/202] format --- src/ir/constraint.h | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 06c0a153af2..4a689ceec3a 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -78,9 +78,9 @@ struct AndedConstraintSet : inplace_vector { // incorrectly). void approximateAnd(const Constraint& c); - // Merge constraints using OR. We cannot always represent such a thing directly - // (we only use AND), so we approximate it. For example, - // this would be valid: + // Merge constraints using OR. We cannot always represent such a thing + // directly (we only use AND), so we approximate it. For example, this would + // be valid: // // approximateOr({ x == 5 }, { x == 10 }) == { x >= 5 && x <= 10 } // @@ -89,9 +89,10 @@ struct AndedConstraintSet : inplace_vector { // // (X || Y) => approximateOr(X, Y) // - // That is, if X or Y is true, the result of approximateOr is also true. But the - // reverse is not always so: approximateOr may be true without X || Y being - // true (see the truth table linked above, and the value 8 in the example). + // That is, if X or Y is true, the result of approximateOr is also true. But + // the reverse is not always so: approximateOr may be true without X || Y + // being true (see the truth table linked above, and the value 8 in the + // example). // // Returning to the example, we can use this to optimize as follows: if // two code paths reaching a location have x == 5 and x == 10, so the value in @@ -108,9 +109,9 @@ struct AndedConstraintSet : inplace_vector { // // I.e. the constraints imply the truth of the thing we are evaluating. // - // Note that the approximation here means that approximateOr() can do a better / - // worse job. It is always valid for approximateOr to return { } or any other - // always-true thing (see the truth table linked above). But then: + // Note that the approximation here means that approximateOr() can do a better + // / worse job. It is always valid for approximateOr to return { } or any + // other always-true thing (see the truth table linked above). But then: // // { x == 5 || x == 10 } => // { } =!!> From 835d863da5a46a3b072bb3769aab8f0d6e61b4cf Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 17 Jun 2026 10:47:46 -0700 Subject: [PATCH 188/202] clean --- src/ir/constraint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index b48cc776f75..a769fe15864 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -226,7 +226,7 @@ void LocalConstraintMap::approximateOr(const LocalConstraintMap& other) { // Remove things only in us. std::erase_if(*this, [&](const auto& item) { const auto& [local, constraints] = item; - return !other.contains(local); + return !other.contains(local); }); } From 6aec092b67f65c865acbb4653e5f8ec23d8576e9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 17 Jun 2026 10:51:44 -0700 Subject: [PATCH 189/202] fix --- test/lit/passes/constraint-analysis.wast | 70 ++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index b3a0b27a41b..26008fe08b2 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -503,6 +503,75 @@ ) ) + ;; CHECK: (func $multi-block-split-yes (type $0) (param $param i32) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $param) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $multi-block-split-yes (type $0) (param $param i32) + ;; OPTIN-NEXT: (local $x i32) + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (i32.const 10) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (if + ;; OPTIN-NEXT: (local.get $param) + ;; OPTIN-NEXT: (then + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (i32.const 10) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 0) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $multi-block-split-yes (param $param i32) + (local $x i32) + ;; As above, but now we set 10 again in the if arm. + (local.set $x + (i32.const 10) + ) + (if + (local.get $param) + (then + (local.set $x + (i32.const 10) + ) + ) + ) + ;; Now we can infer 1 and 0 here. + (drop + (i32.eq + (local.get $x) + (i32.const 10) + ) + ) + (drop + (i32.eq + (local.get $x) + (i32.const 20) + ) + ) + ) + ;; CHECK: (func $default-var (type $0) (param $param i32) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local $eq eqref) @@ -588,3 +657,4 @@ ) ) +;; dropAll From c0146332cb7877a8a452b780842b4620da5b1409 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 17 Jun 2026 10:53:02 -0700 Subject: [PATCH 190/202] nice --- test/lit/passes/constraint-analysis.wast | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index 26008fe08b2..89b567e393e 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -656,5 +656,3 @@ ) ) ) - -;; dropAll From 81f2421b5ee95ede8aa59e832c572874c4f74552 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 17 Jun 2026 10:53:13 -0700 Subject: [PATCH 191/202] form --- src/ir/constraint.h | 3 ++- src/passes/ConstraintAnalysis.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 0182891b906..73f80eafc65 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -139,7 +139,8 @@ struct LocalConstraint { }; // A map of locals and their constraints. -struct LocalConstraintMap : public std::unordered_map { +struct LocalConstraintMap + : public std::unordered_map { // Perform an OR as above on each local that appears in both maps. If a local // appears only in one, we can infer nothing, and drop it. void approximateOr(const LocalConstraintMap& other); diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 537c9002608..a615331aadd 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -51,7 +51,8 @@ struct Info { }; struct ConstraintAnalysis - : public WalkerPass, Info>> { + : public WalkerPass< + CFGWalker, Info>> { bool isFunctionParallel() override { return true; } // Locals are not modified here. From 4f9fb9db097ad832807bed46a322589a77af5a6c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 17 Jun 2026 10:55:39 -0700 Subject: [PATCH 192/202] help --- test/lit/help/wasm-metadce.test | 3 +++ test/lit/help/wasm-opt.test | 3 +++ test/lit/help/wasm2js.test | 3 +++ 3 files changed, 9 insertions(+) diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index aadf987cf33..c658c715677 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -109,6 +109,9 @@ ;; CHECK-NEXT: --const-hoisting hoist repeated constants to a ;; CHECK-NEXT: local ;; CHECK-NEXT: +;; CHECK-NEXT: --constraint-analysis finds and uses mathematical +;; CHECK-NEXT: constraints on locals +;; CHECK-NEXT: ;; CHECK-NEXT: --dae removes arguments to calls in an ;; CHECK-NEXT: lto-like manner ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 4dec96a4104..d7000a2cbb1 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -145,6 +145,9 @@ ;; CHECK-NEXT: --const-hoisting hoist repeated constants to a ;; CHECK-NEXT: local ;; CHECK-NEXT: +;; CHECK-NEXT: --constraint-analysis finds and uses mathematical +;; CHECK-NEXT: constraints on locals +;; CHECK-NEXT: ;; CHECK-NEXT: --dae removes arguments to calls in an ;; CHECK-NEXT: lto-like manner ;; CHECK-NEXT: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 550677a376f..4ef39e73fae 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -73,6 +73,9 @@ ;; CHECK-NEXT: --const-hoisting hoist repeated constants to a ;; CHECK-NEXT: local ;; CHECK-NEXT: +;; CHECK-NEXT: --constraint-analysis finds and uses mathematical +;; CHECK-NEXT: constraints on locals +;; CHECK-NEXT: ;; CHECK-NEXT: --dae removes arguments to calls in an ;; CHECK-NEXT: lto-like manner ;; CHECK-NEXT: From dab23fb7b59969b0b4f1e32e66ee0161a391633e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 17 Jun 2026 11:02:23 -0700 Subject: [PATCH 193/202] avoid warning --- src/ir/constraint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index a769fe15864..08fce63d065 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -158,7 +158,7 @@ std::optional LocalConstraint::parse(Expression* curr) { 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}}; + return LocalConstraint{get->index, Constraint{Abstract::Eq, {value}}}; } return {}; }; From 26b1b623011efad021cc1a00c0e5ea96f458f4b9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 17 Jun 2026 11:05:56 -0700 Subject: [PATCH 194/202] avoid warning --- 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 a615331aadd..3011147aabe 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -187,7 +187,7 @@ struct ConstraintAnalysis // TODO: support tuples if (type.size() == 1 && LiteralUtils::canMakeZero(type)) { auto value = Literal::makeZero(type); - constraints[i].approximateAnd(Constraint{Abstract::Eq, value}); + constraints[i].approximateAnd(Constraint{Abstract::Eq, {value}}); } } } @@ -211,7 +211,7 @@ struct ConstraintAnalysis localConstraints.clear(); if (Properties::isSingleConstantExpression(set->value)) { auto value = Properties::getLiteral(set->value); - localConstraints.approximateAnd(Constraint{Abstract::Eq, value}); + localConstraints.approximateAnd(Constraint{Abstract::Eq, {value}}); } } } From bda6665be7f367ac2868d67b459fd55b3b6c56a0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 17 Jun 2026 11:07:19 -0700 Subject: [PATCH 195/202] avoid warning --- src/ir/constraint.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 08fce63d065..3bfab8e2f3b 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -177,10 +177,10 @@ std::optional LocalConstraint::parse(Expression* curr) { // Parse a get or a constant. auto parseTerm = [&](Expression* expr) -> std::optional { if (auto* get = expr->dynCast()) { - return Term(get->index); + return Term{get->index}; } if (Properties::isSingleConstantExpression(expr)) { - return Term(Properties::getLiteral(expr)); + return Term{Properties::getLiteral(expr)}; } return {}; }; From 9dbf1d9400c020f4434e365a552dcecd8239bded Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 17 Jun 2026 13:09:20 -0700 Subject: [PATCH 196/202] add todo --- src/ir/constraint.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 3bfab8e2f3b..1049e8623ff 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -160,6 +160,7 @@ std::optional LocalConstraint::parse(Expression* curr) { auto value = Literal::makeZero(get->type); return LocalConstraint{get->index, Constraint{Abstract::Eq, {value}}}; } + // TODO: Recursively parse and reverse a constraint return {}; }; From 1981731a9d674abb74cce2e5e66632c4caca77c1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 18 Jun 2026 08:39:57 -0700 Subject: [PATCH 197/202] rename --- src/ir/constraint.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 1049e8623ff..a9ea18c1e16 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -154,7 +154,7 @@ void AndedConstraintSet::approximateOr(const AndedConstraintSet& other) { } std::optional LocalConstraint::parse(Expression* curr) { - auto parseEqZ = [&](Expression* value) -> std::optional { + auto parseEqZArgument = [&](Expression* value) -> std::optional { if (auto* get = value->dynCast()) { // Canonicalize EqZ to Eq of 0. auto value = Literal::makeZero(get->type); @@ -166,13 +166,13 @@ std::optional LocalConstraint::parse(Expression* curr) { if (auto* unary = curr->dynCast()) { if (Abstract::getUnary(unary->type, Abstract::EqZ) == unary->op) { - return parseEqZ(unary->value); + return parseEqZArgument(unary->value); } return {}; } if (auto* refIsNull = curr->dynCast()) { - return parseEqZ(refIsNull->value); + return parseEqZArgument(refIsNull->value); } // Parse a get or a constant. @@ -186,7 +186,7 @@ std::optional LocalConstraint::parse(Expression* curr) { return {}; }; - auto parseBinary = [&](Abstract::Op op, + auto parseBinaryArguments = [&](Abstract::Op op, Expression* left, Expression* right) -> std::optional { // The left must be a get. @@ -203,14 +203,14 @@ std::optional LocalConstraint::parse(Expression* curr) { // The operation must be one we recognize. for (auto op : {Abstract::Eq, Abstract::Ne}) { if (Abstract::getBinary(binary->type, op) == binary->op) { - return parseBinary(op, binary->left, binary->right); + return parseBinaryArguments(op, binary->left, binary->right); } } return {}; } if (auto* refEq = curr->dynCast()) { - return parseBinary(Abstract::Eq, refEq->left, refEq->right); + return parseBinaryArguments(Abstract::Eq, refEq->left, refEq->right); } return {}; From ec2c0d5b6089aa7f3edc0e6d5b12990d46f8e018 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 18 Jun 2026 08:40:03 -0700 Subject: [PATCH 198/202] format --- src/ir/constraint.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index a9ea18c1e16..723fb0c2002 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -154,7 +154,8 @@ void AndedConstraintSet::approximateOr(const AndedConstraintSet& other) { } std::optional LocalConstraint::parse(Expression* curr) { - auto parseEqZArgument = [&](Expression* value) -> std::optional { + auto parseEqZArgument = + [&](Expression* value) -> std::optional { if (auto* get = value->dynCast()) { // Canonicalize EqZ to Eq of 0. auto value = Literal::makeZero(get->type); @@ -186,9 +187,10 @@ std::optional LocalConstraint::parse(Expression* curr) { return {}; }; - auto parseBinaryArguments = [&](Abstract::Op op, - Expression* left, - Expression* right) -> std::optional { + 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. From 53974d7bc9c4b7adf5f122431423cb58fdbaddda Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 18 Jun 2026 11:47:08 -0700 Subject: [PATCH 199/202] test loops, showing the problem --- test/lit/passes/constraint-analysis.wast | 149 +++++++++++++++++++++++ 1 file changed, 149 insertions(+) diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index 89b567e393e..1e8b434a47f 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -572,6 +572,155 @@ ) ) + ;; CHECK: (func $loop (type $0) (param $param i32) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $loop + ;; CHECK-NEXT: (local.get $param) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $loop (type $0) (param $param i32) + ;; OPTIN-NEXT: (local $x i32) + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (i32.const 10) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (loop $loop + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (i32.const 10) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (br_if $loop + ;; OPTIN-NEXT: (local.get $param) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $loop (param $param i32) + (local $x i32) + ;; Set $x to 10 before the loop. + (local.set $x + (i32.const 10) + ) + (loop $loop + ;; Despite the backedges, we can infer x is 10 and not 20. + (drop + (i32.eq + (local.get $x) + (i32.const 10) + ) + ) + (drop + (i32.ne + (local.get $x) + (i32.const 20) + ) + ) + (br_if $loop + (local.get $param) + ) + ) + ) + + ;; CHECK: (func $loop-no (type $0) (param $param i32) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $loop + ;; CHECK-NEXT: (local.get $param) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $loop-no (type $0) (param $param i32) + ;; OPTIN-NEXT: (local $x i32) + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (i32.const 10) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (loop $loop + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (i32.const 10) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.ne + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (i32.const 20) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (i32.const 20) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (br_if $loop + ;; OPTIN-NEXT: (local.get $param) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $loop-no (param $param i32) + (local $x i32) + ;; As above, but now with another value set in the loop. We cannot infer. + (local.set $x + (i32.const 10) + ) + (loop $loop + (drop + (i32.eq + (local.get $x) + (i32.const 10) + ) + ) + (drop + (i32.ne + (local.get $x) + (i32.const 20) + ) + ) + (local.set $x + (i32.const 20) + ) + (br_if $loop + (local.get $param) + ) + ) + ) + ;; CHECK: (func $default-var (type $0) (param $param i32) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local $eq eqref) From e575105e87d3a430b83a94f9495b43d97514e862 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 18 Jun 2026 13:06:54 -0700 Subject: [PATCH 200/202] reverse --- src/ir/constraint.cpp | 35 +++++-- src/ir/constraint.h | 3 + src/passes/ConstraintAnalysis.cpp | 118 +++++++++++------------ test/lit/passes/constraint-analysis.wast | 15 +-- 4 files changed, 90 insertions(+), 81 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 723fb0c2002..a4de708338e 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -126,12 +126,9 @@ void AndedConstraintSet::approximateAnd(const Constraint& c) { void AndedConstraintSet::approximateOr(const AndedConstraintSet& other) { // If one is empty (no constraints, everything is true, and we can prove - // nothing useful) then it does not add anything to the other. - if (empty()) { - *this = other; - return; - } - if (other.empty()) { + // nothing useful) then we can prove nothing after the OR. + if (empty() || other.empty()) { + clear(); return; } @@ -233,4 +230,30 @@ void LocalConstraintMap::approximateOr(const LocalConstraintMap& other) { }); } +std::ostream& operator<<(std::ostream& o, const Constraint& constraint) { + o << "Constraint{" << /*constraint.op <<*/ ", "; + if (auto* c = std::get_if(&constraint.term)) { + o << *c; + } else if (auto* i = std::get_if(&constraint.term)) { + o << "Index(" << *i << ')'; + } + o << '}'; + return o; +} + +std::ostream& operator<<(std::ostream& o, const AndedConstraintSet& constraints) { + o << "AndedConstraintSet{"; + bool first = true; + for (auto& constraint : constraints) { + if (first) { + first = false; + } else { + o << ", "; + } + o << constraint; + } + o << '}'; + return o; +} + } // namespace wasm::constraint diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 73f80eafc65..01f9f55fded 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -146,6 +146,9 @@ struct LocalConstraintMap void approximateOr(const LocalConstraintMap& other); }; +std::ostream& operator<<(std::ostream& o, const Constraint& constraint); +std::ostream& operator<<(std::ostream& o, const AndedConstraintSet& constraints); + } // namespace wasm::constraint #endif // wasm_ir_constraint_h diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 3011147aabe..71eaafc9a7f 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -46,8 +46,12 @@ struct Info { std::vector actions; // For each local index, we track the constraints we know about it. We only do - // so at the end of each block, which is enough for the analysis below. - LocalConstraintMap endConstraints; + // so at the start of each block, which is enough for the analysis below. + // + // We use an optional here to represent the "null" state before any + // information arrives. (From the perspective of set theory, nullopt can be + // taken to mean the empty set is the set of values possible for each local.) + std::optional startConstraints; }; struct ConstraintAnalysis @@ -80,6 +84,10 @@ struct ConstraintAnalysis void visitRefIsNull(RefIsNull* curr) { addAction(); } void visitFunction(Function* curr) { + if (!entry) { + // Body is unreachable, no entry block. + return; + } // TODO: optimize for speed, find relevant locals etc. flow(); optimize(); @@ -88,27 +96,51 @@ struct ConstraintAnalysis // Flow infos around until we have inferred all we can about the constraints // in each location. void flow() { - // Start from all the blocks, and keep going while we find something new. - UniqueDeferredQueue work; - for (auto& block : basicBlocks) { - work.push(block.get()); + // Start from the entry. That block has incoming values - defaults - for + // each var. + auto& entryConstraints = entry->contents.startConstraints; + entryConstraints.emplace(); + auto* func = getFunction(); + auto numLocals = func->getNumLocals(); + for (Index i = 0; i < numLocals; i++) { + if (!func->isVar(i)) { + continue; + } + auto type = func->getLocalType(i); + // TODO: support tuples + if (type.size() == 1 && LiteralUtils::canMakeZero(type)) { + auto value = Literal::makeZero(type); + (*entryConstraints)[i].approximateAnd(Constraint{Abstract::Eq, {value}}); + } } + + // Starting from the entry, keep going while we find something new. + UniqueDeferredQueue work; + work.push(entry); while (!work.empty()) { auto* block = work.pop(); - // Merge incoming data to get the status at the start of the block. - LocalConstraintMap constraints = mergeIncoming(block); - - // Go through the block, applying things. + // Start at the top of the block, then go through, applying things. + LocalConstraintMap constraints = *block->contents.startConstraints; for (auto** currp : block->contents.actions) { applyToConstraints(*currp, constraints); } - // We now know the values at the end of the block. If something changed, - // flow it onward. - if (constraints != block->contents.endConstraints) { - block->contents.endConstraints = std::move(constraints); - for (auto* out : block->out) { + // We now know the values at the end of the block. Flow it onward, and + // where it causes changes, queue more work. + for (auto* out : block->out) { + auto& outConstraints = out->contents.startConstraints; + if (!outConstraints) { + // This is the first data arriving. + outConstraints.emplace(constraints); + work.push(out); + continue; + } + + // This is later data, which may or may not cause changes. + auto old = outConstraints; + outConstraints->approximateOr(constraints); + if (*outConstraints != old) { work.push(out); } } @@ -121,10 +153,15 @@ struct ConstraintAnalysis // Follow the general shape of flow(): we need to see what the state is // at each intermediate point inside the block. (Flowing between blocks is // of course not needed at this stage.) - LocalConstraintMap constraints = mergeIncoming(block.get()); + auto& constraints = block->contents.startConstraints; + if (!constraints) { + // Unreachable. + continue; + } + for (auto** currp : block->contents.actions) { - applyToConstraints(*currp, constraints); - optimizeExpression(currp, constraints); + applyToConstraints(*currp, *constraints); + optimizeExpression(currp, *constraints); } } } @@ -158,51 +195,6 @@ struct ConstraintAnalysis curr, wasm, getPassOptions(), value, DropMode::IgnoreParentEffects); } - // Merge incoming data to a block, by looking at the data arriving from each - // of the predecessor blocks. - LocalConstraintMap mergeIncoming(BasicBlock* block) { - LocalConstraintMap constraints; - - // Merge all preds. - for (auto* pred : block->in) { - auto& predConstraints = getConstraintsFromPredToSucc(pred, block); - if (pred == *block->in.begin()) { - // This is the first. Just copy. - constraints = predConstraints; - } else { - // Merge in subsequent ones. - constraints.approximateOr(predConstraints); - } - } - - // The entry block has incoming values - defaults - for each var. - if (block == entry) { - auto* func = getFunction(); - auto numLocals = func->getNumLocals(); - for (Index i = 0; i < numLocals; i++) { - if (!func->isVar(i)) { - continue; - } - auto type = func->getLocalType(i); - // TODO: support tuples - if (type.size() == 1 && LiteralUtils::canMakeZero(type)) { - auto value = Literal::makeZero(type); - constraints[i].approximateAnd(Constraint{Abstract::Eq, {value}}); - } - } - } - - return constraints; - } - - // Given a source (predecessor) and a target (successor) block, find the - // constraints for locals as they arrive to that target from that successor. - const LocalConstraintMap& getConstraintsFromPredToSucc(BasicBlock* pred, - BasicBlock* block) { - // TODO: use conditional branching to send different values along branches - return pred->contents.endConstraints; - } - // Given an expression, apply it to the constraints. For example, a local.set // sets the value for that local. void applyToConstraints(Expression* curr, LocalConstraintMap& constraints) { diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index 1e8b434a47f..cac599c30be 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -579,16 +579,10 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (loop $loop ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.eq - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 10) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.ne - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 20) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br_if $loop ;; CHECK-NEXT: (local.get $param) @@ -602,10 +596,7 @@ ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (loop $loop ;; OPTIN-NEXT: (drop - ;; OPTIN-NEXT: (i32.eq - ;; OPTIN-NEXT: (local.get $x) - ;; OPTIN-NEXT: (i32.const 10) - ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (i32.const 1) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (drop ;; OPTIN-NEXT: (i32.const 1) From aec95428b183d80677df08df8798e0b27da94eb2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 18 Jun 2026 13:07:00 -0700 Subject: [PATCH 201/202] format --- src/ir/constraint.cpp | 3 ++- src/ir/constraint.h | 3 ++- src/passes/ConstraintAnalysis.cpp | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index a4de708338e..84d3d257512 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -241,7 +241,8 @@ std::ostream& operator<<(std::ostream& o, const Constraint& constraint) { return o; } -std::ostream& operator<<(std::ostream& o, const AndedConstraintSet& constraints) { +std::ostream& operator<<(std::ostream& o, + const AndedConstraintSet& constraints) { o << "AndedConstraintSet{"; bool first = true; for (auto& constraint : constraints) { diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 01f9f55fded..a9bcded5cff 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -147,7 +147,8 @@ struct LocalConstraintMap }; std::ostream& operator<<(std::ostream& o, const Constraint& constraint); -std::ostream& operator<<(std::ostream& o, const AndedConstraintSet& constraints); +std::ostream& operator<<(std::ostream& o, + const AndedConstraintSet& constraints); } // namespace wasm::constraint diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 71eaafc9a7f..74f1f3e87ff 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -110,7 +110,8 @@ struct ConstraintAnalysis // TODO: support tuples if (type.size() == 1 && LiteralUtils::canMakeZero(type)) { auto value = Literal::makeZero(type); - (*entryConstraints)[i].approximateAnd(Constraint{Abstract::Eq, {value}}); + (*entryConstraints)[i].approximateAnd( + Constraint{Abstract::Eq, {value}}); } } From 92e5efdb0a049aa0c8ca3ecafb72545edf5e389b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 18 Jun 2026 13:08:58 -0700 Subject: [PATCH 202/202] fix.gtest --- test/gtest/constraint.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index a8bb66f75d6..71ade020c02 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -139,15 +139,16 @@ TEST(ConstraintTest, TestOrTrivial) { // { } AndedConstraintSet empty; - // Anything ORed with the empty set is unchanged. + // Anything ORed with the empty set becomes the empty set: if one side can + // prove nothing, neither can the result. auto t = s; t.approximateOr(empty); - EXPECT_EQ(t, s); + EXPECT_EQ(t, empty); // Flipped. t = empty; t.approximateOr(s); - EXPECT_EQ(t, s); + EXPECT_EQ(t, empty); // ORing with oneself changes nothing t = s;