From 4426a55c7913903557b2d87db3db8ece42873822 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 24 Sep 2026 15:01:37 -0700 Subject: [PATCH 01/12] start --- src/cfg/liveness-traversal.h | 2 +- src/ir/constraint.cpp | 77 +++++++++++----------- src/ir/constraint.h | 33 +++++++--- src/passes/DeadArgumentElimination.cpp | 2 +- src/passes/SignaturePruning.cpp | 2 +- src/passes/param-utils.cpp | 16 ++--- src/passes/param-utils.h | 12 ++-- src/support/sorted_vector.h | 90 +++++++++++++++++++++----- 8 files changed, 155 insertions(+), 79 deletions(-) diff --git a/src/cfg/liveness-traversal.h b/src/cfg/liveness-traversal.h index 7bae2cbaf48..ac15f545a1e 100644 --- a/src/cfg/liveness-traversal.h +++ b/src/cfg/liveness-traversal.h @@ -37,7 +37,7 @@ namespace wasm { // may be a great many potential elements but actual sets // may be fairly small. Specifically, we use a sorted // vector. -using SetOfLocals = SortedVector; +using SetOfLocals = SortedVector; // A liveness-relevant action. Supports a get, a set, or an // "other" which can be used for other purposes, to mark diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 3e15f560311..478d1afd8ba 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -1051,14 +1051,12 @@ void BasicBlockConstraintMap::set(Index index, // We should not set values in unreachable code. assert(!unreachable); - // Clear the old state. + // Clear the old state, making us prove nothing. eraseStaleRefs(index); map.erase(index); // Apply the constraints, if there are any. - if (constraints.provesNothing()) { - setProvesNothing(index); - } else { + if (!constraints.provesNothing()) { for (auto& c : constraints) { approximateAnd(index, c); } @@ -1199,24 +1197,22 @@ bool BasicBlockConstraintMap::approximateOr( return true; } - // We only need to loop on our locals, as any local that is missing in us is - // one that would end up proving nothing (and get removed). + // Both maps are sorted by local Index. Intersect/merge in place: any local + // missing in |other| (or whose merged constraint set proves nothing) is + // dropped. bool changed = false; - for (auto& [local, constraints] : map) { - changed |= constraints.approximateOr(other.get(local)); - } - - // Anything that became trivial after the OR must be removed. - std::erase_if(map, [&](const auto& item) { - const auto& [local, constraints] = item; - // We do not store contradictions. - assert(!constraints.provesEverything()); - if (constraints.provesNothing()) { - changed = true; - return true; - } - return false; + auto oldSize = map.size(); + map.intersect(other.map, [&](auto& self, const auto& other) { + changed |= self.value.approximateOr(other.value); + assert(!self.value.provesEverything()); + return !self.value.provesNothing(); }); + if (map.size() != oldSize) { + changed = true; + } + if (map.empty()) { + refs.clear(); + } return changed; } @@ -1246,18 +1242,19 @@ void BasicBlockConstraintMap::approximateAndInternal(Index index, // If we are applying a constraint to another local, and we know that // local's value, propagate it. That is, if x == 42, then if we try to apply // y < x we instead apply y < 42, which is better. - auto otherConstraints = get(*other); - if (auto lit = otherConstraints.getLiteral()) { - actual.term = Term{*lit}; + if (auto* otherConstraints = map.find(*other)) { + if (auto lit = otherConstraints->value.getLiteral()) { + actual.term = Term{*lit}; + } } } // Refer to the constraints for this index. If this is the first access of // the local, then we insert a new item into the map, which has a default of - // proxesEverything, which we need to flip (provesEverything cannot otherwise + // provesNothing, which we need to populate (provesNothing cannot otherwise // be found in the map, as we never store it). - auto [iter, _] = map.insert({index, AndedConstraintSet::makeProvesNothing()}); - auto& indexConstraints = iter->second; + auto& indexConstraints = + map.insert({index, AndedConstraintSet::makeProvesNothing()}).value; // As in ::set(), this makes the map temporarily invalid until the // approximateAnd, as we don't store proves-nothing in the map, normally. @@ -1267,6 +1264,7 @@ void BasicBlockConstraintMap::approximateAndInternal(Index index, // We just proved we are in unreachable code. unreachable = true; map.clear(); + refs.clear(); return; } @@ -1310,32 +1308,37 @@ Result BasicBlockConstraintMap::proves(LocalConstraint condition) const { // about, propagate it. TODO: even without equality, we can add more // constraints here (e.g. x < y and y < 10 can lead to proving x < 10) if (auto* other = std::get_if(&condition.constraint.term)) { - auto otherConstraints = get(*other); - if (auto lit = otherConstraints.getLiteral()) { - condition.constraint.term = Term{*lit}; + if (auto* otherConstraints = map.find(*other)) { + if (auto lit = otherConstraints->value.getLiteral()) { + condition.constraint.term = Term{*lit}; + } } } - return get(condition.local).proves(condition.constraint); + if (auto* constraints = map.find(condition.local)) { + return constraints->value.proves(condition.constraint); + } + return Unknown; } void BasicBlockConstraintMap::noteRefs(Index index, const Constraint& c) { if (auto* i = std::get_if(&c.term)) { - refs[*i].insert(index); + refs.insert({*i, {}}).value.insert(index); } } void BasicBlockConstraintMap::eraseStaleRefs(Index index) { - auto iter = refs.find(index); - if (iter == refs.end()) { + auto* entry = refs.find(index); + if (!entry) { return; } - auto& refIndexes = iter->second; + auto refIndexes = std::move(entry->value); + refs.erase(index); for (auto refIndex : refIndexes) { - if (auto iter = map.find(refIndex); iter != map.end()) { - auto& refConstraints = iter->second; + if (auto* target = map.find(refIndex)) { + auto& refConstraints = target->value; std::erase_if(refConstraints, [&](const auto& c) { if (auto* i = std::get_if(&c.term)) { if (*i == index) { @@ -1346,7 +1349,7 @@ void BasicBlockConstraintMap::eraseStaleRefs(Index index) { }); if (refConstraints.empty()) { // This became trivial. - map.erase(iter); + map.erase(refIndex); } } } diff --git a/src/ir/constraint.h b/src/ir/constraint.h index f1b6f200aed..82f70e7beba 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -33,6 +33,7 @@ #include "ir/abstract.h" #include "support/inplace_vector.h" #include "support/small_vector.h" +#include "support/sorted_vector.h" #include "support/span.h" #include "support/utilities.h" #include "wasm.h" @@ -341,8 +342,8 @@ struct BasicBlockConstraintMap { // We should not be called in unreachable code. assert(!unreachable); - if (auto iter = map.find(index); iter != map.end()) { - auto& constraints = iter->second; + if (auto* entry = map.find(index)) { + auto& constraints = entry->value; // If we can prove nothing, we should have removed it from the map. assert(!constraints.provesNothing()); // If we can prove everything, we should be entirely unreachable. @@ -367,7 +368,7 @@ struct BasicBlockConstraintMap { // Check a condition on a local, given all we know about all other locals. Result proves(LocalConstraint condition) const; - bool operator!=(const BasicBlockConstraintMap& other) { + bool operator!=(const BasicBlockConstraintMap& other) const { return unreachable != other.unreachable || map != other.map; } @@ -375,17 +376,33 @@ struct BasicBlockConstraintMap { const BasicBlockConstraintMap& map); private: - std::unordered_map map; + template struct Indexed { + Index index; + T value; + + bool operator<(const Indexed& other) const { return index < other.index; } + bool operator<(Index otherIndex) const { return index < otherIndex; } + friend bool operator<(Index otherIndex, const Indexed& self) { + return otherIndex < self.index; + } + bool operator==(const Indexed& other) const { + return index == other.index && value == other.value; + } + }; + + // Sorted by local Index for fast contiguous copying and linear-time merge in + // approximateOr. + SortedVector> map; - // Maps an index to the locals that have constraints referring to it. When a - // local is modified, we need to wipe all those constraints, which become - // stale. + // Maps an index to the locals that have constraints referring to it, sorted + // by index. When a local is modified, we need to wipe all those constraints, + // which become stale. // // It is ok (but unoptimal in efficiency) if we have stale refs here, e.g. due // to approximation removing a constraint. Whenever there is a reference, // however, it must be noted here, so that when things get stale we can remove // them. - std::unordered_map> refs; + SortedVector>> refs; // Given a constraint on a local, note refs. void noteRefs(Index index, const Constraint& c); diff --git a/src/passes/DeadArgumentElimination.cpp b/src/passes/DeadArgumentElimination.cpp index 949c52073fb..895b10cbc17 100644 --- a/src/passes/DeadArgumentElimination.cpp +++ b/src/passes/DeadArgumentElimination.cpp @@ -71,7 +71,7 @@ struct DAEFunctionInfo { // computation, and we reset it every time we touch the function. bool stale = true; // The unused parameters, if any. - SortedVector unusedParams; + SortedVector unusedParams; // Maps a function name to the calls going to it. std::unordered_map> calls; // Map of all calls that are dropped, to their drops' locations (so that diff --git a/src/passes/SignaturePruning.cpp b/src/passes/SignaturePruning.cpp index fc95a66bad8..31b35278f61 100644 --- a/src/passes/SignaturePruning.cpp +++ b/src/passes/SignaturePruning.cpp @@ -276,7 +276,7 @@ struct SignaturePruning : public Pass { // We found possible work! Find the specific params that are unused & try // to prune them. - SortedVector unusedParams; + SortedVector unusedParams; for (Index i = 0; i < numParams; i++) { if (!usedParams.contains(i)) { unusedParams.insert(i); diff --git a/src/passes/param-utils.cpp b/src/passes/param-utils.cpp index 6861756c475..bbfbf02134d 100644 --- a/src/passes/param-utils.cpp +++ b/src/passes/param-utils.cpp @@ -188,9 +188,9 @@ RemovalOutcome removeParameter(const std::vector& funcs, return Success; } -std::pair +std::pair, RemovalOutcome> removeParameters(const std::vector& funcs, - SortedVector indexes, + SortedVector indexes, const std::vector& calls, const std::vector& callRefs, Module* module, @@ -210,7 +210,7 @@ removeParameters(const std::vector& funcs, // Iterate downwards, as we may remove more than one, and going forwards would // alter the indexes after us. Index i = first->getNumParams() - 1; - SortedVector removed; + SortedVector removed; while (1) { if (indexes.has(i)) { auto outcome = removeParameter(funcs, i, calls, callRefs, module, runner); @@ -230,10 +230,10 @@ removeParameters(const std::vector& funcs, return {removed, finalOutcome}; } -SortedVector applyConstantValues(const std::vector& funcs, - const std::vector& calls, - const std::vector& callRefs, - Module* module) { +SortedVector applyConstantValues(const std::vector& funcs, + const std::vector& calls, + const std::vector& callRefs, + Module* module) { assert(funcs.size() > 0); auto* first = funcs[0]; #ifndef NDEBUG @@ -242,7 +242,7 @@ SortedVector applyConstantValues(const std::vector& funcs, } #endif - SortedVector optimized; + SortedVector optimized; auto numParams = first->getNumParams(); for (Index i = 0; i < numParams; i++) { PossibleConstantValues value; diff --git a/src/passes/param-utils.h b/src/passes/param-utils.h index c5c52f4cea5..65f59a85fb4 100644 --- a/src/passes/param-utils.h +++ b/src/passes/param-utils.h @@ -87,9 +87,9 @@ RemovalOutcome removeParameter(const std::vector& funcs, // we return Success if we removed any index, Failure if we removed none, and // FailureDueToEffects if at least one index could have been removed but for // effects). -std::pair +std::pair, RemovalOutcome> removeParameters(const std::vector& funcs, - SortedVector indexes, + SortedVector indexes, const std::vector& calls, const std::vector& callRefs, Module* module, @@ -102,10 +102,10 @@ removeParameters(const std::vector& funcs, // which allows other optimizations to remove it. // // Returns the indexes that were optimized. -SortedVector applyConstantValues(const std::vector& funcs, - const std::vector& calls, - const std::vector& callRefs, - Module* module); +SortedVector applyConstantValues(const std::vector& funcs, + const std::vector& calls, + const std::vector& callRefs, + Module* module); // Helper that localizes all calls to a set of targets, in an entire module. // This basically calls ChildLocalizer in each function, on the relevant calls. diff --git a/src/support/sorted_vector.h b/src/support/sorted_vector.h index 234d4da7e6b..dc77a4124a2 100644 --- a/src/support/sorted_vector.h +++ b/src/support/sorted_vector.h @@ -26,7 +26,18 @@ namespace wasm { -struct SortedVector : public std::vector { +template struct SortedVector : public std::vector { + using Base = std::vector; + using Base::back; + using Base::begin; + using Base::clear; + using Base::empty; + using Base::end; + using Base::erase; + using Base::push_back; + using Base::resize; + using Base::size; + SortedVector() = default; SortedVector merge(const SortedVector& other) const { @@ -34,12 +45,12 @@ struct SortedVector : public std::vector { ret.resize(size() + other.size()); Index i = 0, j = 0, t = 0; while (i < size() && j < other.size()) { - auto left = (*this)[i]; - auto right = other[j]; + const auto& left = (*this)[i]; + const auto& right = other[j]; if (left < right) { ret[t++] = left; i++; - } else if (left > right) { + } else if (right < left) { ret[t++] = right; j++; } else { @@ -60,21 +71,41 @@ struct SortedVector : public std::vector { return ret; } - void insert(Index x) { + T& insert(T x) { + if (empty() || back() < x) { + push_back(std::move(x)); + return back(); + } auto it = std::lower_bound(begin(), end(), x); - if (it == end()) { - push_back(x); - } else if (*it > x) { + if (x < *it) { Index i = it - begin(); resize(size() + 1); std::move_backward(begin() + i, begin() + size() - 1, end()); - (*this)[i] = x; + (*this)[i] = std::move(x); + return (*this)[i]; + } + return *it; + } + + template T* find(const K& x) { + auto it = std::lower_bound(begin(), end(), x); + if (it != end() && !(x < *it)) { + return &*it; } + return nullptr; } - bool erase(Index x) { + template const T* find(const K& x) const { auto it = std::lower_bound(begin(), end(), x); - if (it != end() && *it == x) { + if (it != end() && !(x < *it)) { + return &*it; + } + return nullptr; + } + + template bool erase(const K& x) { + auto it = std::lower_bound(begin(), end(), x); + if (it != end() && !(x < *it)) { std::move(it + 1, end(), it); resize(size() - 1); return true; @@ -82,16 +113,17 @@ struct SortedVector : public std::vector { return false; } - bool has(Index x) const { - auto it = std::lower_bound(begin(), end(), x); - return it != end() && *it == x; + template bool has(const K& x) const { + return find(x) != nullptr; } - template SortedVector& filter(T keep) { + template SortedVector& filter(F keep) { size_t skip = 0; for (size_t i = 0; i < size(); i++) { if (keep((*this)[i])) { - (*this)[i - skip] = (*this)[i]; + if (skip > 0) { + (*this)[i - skip] = std::move((*this)[i]); + } } else { skip++; } @@ -100,6 +132,30 @@ struct SortedVector : public std::vector { return *this; } + // Intersect this vector in place with |other|, keeping only elements present + // in both for which |keep(selfElem, otherElem)| returns true. + template void intersect(const SortedVector& other, F keep) { + size_t write = 0; + size_t i = 0, j = 0; + while (i < size() && j < other.size()) { + if ((*this)[i] < other[j]) { + i++; + } else if (other[j] < (*this)[i]) { + j++; + } else { + if (keep((*this)[i], other[j])) { + if (write != i) { + (*this)[write] = std::move((*this)[i]); + } + write++; + } + i++; + j++; + } + } + resize(write); + } + void verify() const { for (Index i = 1; i < size(); i++) { assert((*this)[i - 1] < (*this)[i]); @@ -108,7 +164,7 @@ struct SortedVector : public std::vector { void dump(const char* str = nullptr) const { std::cout << "SortedVector " << (str ? str : "") << ": "; - for (auto x : *this) { + for (const auto& x : *this) { std::cout << x << " "; } std::cout << '\n'; From 74ad440eeaa37b3a472bbb8b7ada711576e5fce1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 24 Sep 2026 15:25:24 -0700 Subject: [PATCH 02/12] fix --- src/ir/constraint.cpp | 2 +- src/ir/constraint.h | 1 + src/support/sorted_vector.h | 8 +++++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 478d1afd8ba..20b4a001ab4 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -1202,7 +1202,7 @@ bool BasicBlockConstraintMap::approximateOr( // dropped. bool changed = false; auto oldSize = map.size(); - map.intersect(other.map, [&](auto& self, const auto& other) { + map.intersectAndFilter(other.map, [&](auto& self, const auto& other) { changed |= self.value.approximateOr(other.value); assert(!self.value.provesEverything()); return !self.value.provesNothing(); diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 82f70e7beba..eeff01adb95 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -376,6 +376,7 @@ struct BasicBlockConstraintMap { const BasicBlockConstraintMap& map); private: + // Wrap a combination of an index and a value, and sort using only the index. template struct Indexed { Index index; T value; diff --git a/src/support/sorted_vector.h b/src/support/sorted_vector.h index dc77a4124a2..538408138fd 100644 --- a/src/support/sorted_vector.h +++ b/src/support/sorted_vector.h @@ -132,9 +132,11 @@ template struct SortedVector : public std::vector { return *this; } - // Intersect this vector in place with |other|, keeping only elements present - // in both for which |keep(selfElem, otherElem)| returns true. - template void intersect(const SortedVector& other, F keep) { + // Intersect this vector in place with |other|, and filtering so elements + // present in both are only kept when |keep(selfElem, otherElem)| returns + // true (this can be useful when the items contain more than they key being + // sorted on). + template void intersectAndFilter(const SortedVector& other, F keep) { size_t write = 0; size_t i = 0, j = 0; while (i < size() && j < other.size()) { From 7e3db7c69689732f243ff787fba2f15e4966a875 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 24 Sep 2026 15:58:26 -0700 Subject: [PATCH 03/12] clean --- src/support/sorted_vector.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/support/sorted_vector.h b/src/support/sorted_vector.h index 538408138fd..452ff19b12d 100644 --- a/src/support/sorted_vector.h +++ b/src/support/sorted_vector.h @@ -87,30 +87,30 @@ template struct SortedVector : public std::vector { return *it; } - template T* find(const K& x) { + template bool erase(const K& x) { auto it = std::lower_bound(begin(), end(), x); - if (it != end() && !(x < *it)) { - return &*it; + if (it != end() && *it == x) { + std::move(it + 1, end(), it); + resize(size() - 1); + return true; } - return nullptr; + return false; } - template const T* find(const K& x) const { + template T* find(const K& x) { auto it = std::lower_bound(begin(), end(), x); - if (it != end() && !(x < *it)) { + if (it != end() && *it == x) { return &*it; } return nullptr; } - template bool erase(const K& x) { + template const T* find(const K& x) const { auto it = std::lower_bound(begin(), end(), x); - if (it != end() && !(x < *it)) { - std::move(it + 1, end(), it); - resize(size() - 1); - return true; + if (it != end() && *it == x) { + return &*it; } - return false; + return nullptr; } template bool has(const K& x) const { From 31de9639eade0440bd79fbfe4dfb5ffe2b7f9d05 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 24 Sep 2026 16:00:28 -0700 Subject: [PATCH 04/12] fix --- src/ir/constraint.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index eeff01adb95..bd091c0d565 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -389,6 +389,9 @@ struct BasicBlockConstraintMap { bool operator==(const Indexed& other) const { return index == other.index && value == other.value; } + bool operator==(const Index& otherIndex) const { + return index == otherIndex; + } }; // Sorted by local Index for fast contiguous copying and linear-time merge in From 426def0ae52f0c3445fe03e0590be09bd34d62d4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 24 Sep 2026 16:08:50 -0700 Subject: [PATCH 05/12] fix --- src/support/sorted_vector.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/support/sorted_vector.h b/src/support/sorted_vector.h index 452ff19b12d..a3026075d99 100644 --- a/src/support/sorted_vector.h +++ b/src/support/sorted_vector.h @@ -26,7 +26,7 @@ namespace wasm { -template struct SortedVector : public std::vector { +template struct SortedVector : public std::vector { using Base = std::vector; using Base::back; using Base::begin; @@ -50,7 +50,7 @@ template struct SortedVector : public std::vector { if (left < right) { ret[t++] = left; i++; - } else if (right < left) { + } else if (left > right) { ret[t++] = right; j++; } else { From 73254aea1dbb5ff412043b7cd571a7bbf6476222 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 24 Sep 2026 16:28:36 -0700 Subject: [PATCH 06/12] fix --- src/ir/constraint.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 20b4a001ab4..d9194371e9d 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -1197,9 +1197,9 @@ bool BasicBlockConstraintMap::approximateOr( return true; } - // Both maps are sorted by local Index. Intersect/merge in place: any local - // missing in |other| (or whose merged constraint set proves nothing) is - // dropped. + // Both maps are sorted by local Index. Intersect in place: for us to be able + // to prove something (for us to have an entry in the ORed map), there must + // have been an entry in both original maps. bool changed = false; auto oldSize = map.size(); map.intersectAndFilter(other.map, [&](auto& self, const auto& other) { @@ -1210,6 +1210,10 @@ bool BasicBlockConstraintMap::approximateOr( if (map.size() != oldSize) { changed = true; } + + // We could more precisely find which locals were removed from the map, but + // stale refs has low overhead and no correctness cost, so just handle the + // common, simple case of nothing remaining, so no refs are needed. if (map.empty()) { refs.clear(); } From 8e065f9ae7cce77f48af80fc8541b7be704de800 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 24 Sep 2026 16:28:43 -0700 Subject: [PATCH 07/12] format --- src/support/sorted_vector.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/support/sorted_vector.h b/src/support/sorted_vector.h index a3026075d99..10b7537587b 100644 --- a/src/support/sorted_vector.h +++ b/src/support/sorted_vector.h @@ -136,7 +136,8 @@ template struct SortedVector : public std::vector { // present in both are only kept when |keep(selfElem, otherElem)| returns // true (this can be useful when the items contain more than they key being // sorted on). - template void intersectAndFilter(const SortedVector& other, F keep) { + template + void intersectAndFilter(const SortedVector& other, F keep) { size_t write = 0; size_t i = 0, j = 0; while (i < size() && j < other.size()) { From 96f774e44bcbd65604417c537df8f9595af6a31d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Sep 2026 09:01:43 -0700 Subject: [PATCH 08/12] comments --- src/ir/constraint.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index d9194371e9d..688143fd70a 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -1205,6 +1205,7 @@ bool BasicBlockConstraintMap::approximateOr( map.intersectAndFilter(other.map, [&](auto& self, const auto& other) { changed |= self.value.approximateOr(other.value); assert(!self.value.provesEverything()); + // Keep only entries that prove things, as others should not be in the map. return !self.value.provesNothing(); }); if (map.size() != oldSize) { @@ -1212,7 +1213,7 @@ bool BasicBlockConstraintMap::approximateOr( } // We could more precisely find which locals were removed from the map, but - // stale refs has low overhead and no correctness cost, so just handle the + // stale refs have low overhead and no correctness cost, so just handle the // common, simple case of nothing remaining, so no refs are needed. if (map.empty()) { refs.clear(); From 8836348af232d51a9baeee2f03447b8cc32061c7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Sep 2026 09:09:39 -0700 Subject: [PATCH 09/12] slim --- src/ir/constraint.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index bd091c0d565..816ecd91eb7 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -383,9 +383,6 @@ struct BasicBlockConstraintMap { bool operator<(const Indexed& other) const { return index < other.index; } bool operator<(Index otherIndex) const { return index < otherIndex; } - friend bool operator<(Index otherIndex, const Indexed& self) { - return otherIndex < self.index; - } bool operator==(const Indexed& other) const { return index == other.index && value == other.value; } From b6df2c04a5acefd2ef085842b5ee0786a0413455 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Sep 2026 10:08:26 -0700 Subject: [PATCH 10/12] iter --- src/ir/constraint.cpp | 22 +++++++++++----------- src/ir/constraint.h | 4 ++-- src/support/sorted_vector.h | 20 +++++++++++--------- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 688143fd70a..f12b57d2005 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -1247,8 +1247,8 @@ void BasicBlockConstraintMap::approximateAndInternal(Index index, // If we are applying a constraint to another local, and we know that // local's value, propagate it. That is, if x == 42, then if we try to apply // y < x we instead apply y < 42, which is better. - if (auto* otherConstraints = map.find(*other)) { - if (auto lit = otherConstraints->value.getLiteral()) { + if (auto iter = map.find(*other); iter != map.end()) { + if (auto lit = iter->value.getLiteral()) { actual.term = Term{*lit}; } } @@ -1313,15 +1313,15 @@ Result BasicBlockConstraintMap::proves(LocalConstraint condition) const { // about, propagate it. TODO: even without equality, we can add more // constraints here (e.g. x < y and y < 10 can lead to proving x < 10) if (auto* other = std::get_if(&condition.constraint.term)) { - if (auto* otherConstraints = map.find(*other)) { - if (auto lit = otherConstraints->value.getLiteral()) { + if (auto iter = map.find(*other); iter != map.end()) { + if (auto lit = iter->value.getLiteral()) { condition.constraint.term = Term{*lit}; } } } - if (auto* constraints = map.find(condition.local)) { - return constraints->value.proves(condition.constraint); + if (auto iter = map.find(condition.local); iter != map.end()) { + return iter->value.proves(condition.constraint); } return Unknown; } @@ -1333,17 +1333,17 @@ void BasicBlockConstraintMap::noteRefs(Index index, const Constraint& c) { } void BasicBlockConstraintMap::eraseStaleRefs(Index index) { - auto* entry = refs.find(index); - if (!entry) { + auto iter = refs.find(index); + if (iter == refs.end()) { return; } - auto refIndexes = std::move(entry->value); + auto refIndexes = std::move(iter->value); refs.erase(index); for (auto refIndex : refIndexes) { - if (auto* target = map.find(refIndex)) { - auto& refConstraints = target->value; + if (auto iter = map.find(refIndex); iter != map.end()) { + auto& refConstraints = iter->value; std::erase_if(refConstraints, [&](const auto& c) { if (auto* i = std::get_if(&c.term)) { if (*i == index) { diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 816ecd91eb7..34e8b708466 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -342,8 +342,8 @@ struct BasicBlockConstraintMap { // We should not be called in unreachable code. assert(!unreachable); - if (auto* entry = map.find(index)) { - auto& constraints = entry->value; + if (auto iter = map.find(index); iter != map.end()) { + auto& constraints = iter->value; // If we can prove nothing, we should have removed it from the map. assert(!constraints.provesNothing()); // If we can prove everything, we should be entirely unreachable. diff --git a/src/support/sorted_vector.h b/src/support/sorted_vector.h index 10b7537587b..85824a77961 100644 --- a/src/support/sorted_vector.h +++ b/src/support/sorted_vector.h @@ -37,6 +37,8 @@ template struct SortedVector : public std::vector { using Base::push_back; using Base::resize; using Base::size; + using typename Base::const_iterator; + using typename Base::iterator; SortedVector() = default; @@ -88,8 +90,8 @@ template struct SortedVector : public std::vector { } template bool erase(const K& x) { - auto it = std::lower_bound(begin(), end(), x); - if (it != end() && *it == x) { + auto it = find(x); + if (it != end()) { std::move(it + 1, end(), it); resize(size() - 1); return true; @@ -97,24 +99,24 @@ template struct SortedVector : public std::vector { return false; } - template T* find(const K& x) { + template iterator find(const K& x) { auto it = std::lower_bound(begin(), end(), x); if (it != end() && *it == x) { - return &*it; + return it; } - return nullptr; + return end(); } - template const T* find(const K& x) const { + template const_iterator find(const K& x) const { auto it = std::lower_bound(begin(), end(), x); if (it != end() && *it == x) { - return &*it; + return it; } - return nullptr; + return end(); } template bool has(const K& x) const { - return find(x) != nullptr; + return find(x) != end(); } template SortedVector& filter(F keep) { From 7257132cba39760a7e2d53f8b6a214e0118e8e51 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Sep 2026 11:37:23 -0700 Subject: [PATCH 11/12] iters --- src/ir/constraint.cpp | 4 ++-- src/support/sorted_vector.h | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index f12b57d2005..ab7302ef3b9 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -1339,7 +1339,7 @@ void BasicBlockConstraintMap::eraseStaleRefs(Index index) { } auto refIndexes = std::move(iter->value); - refs.erase(index); + refs.erase(iter); for (auto refIndex : refIndexes) { if (auto iter = map.find(refIndex); iter != map.end()) { @@ -1354,7 +1354,7 @@ void BasicBlockConstraintMap::eraseStaleRefs(Index index) { }); if (refConstraints.empty()) { // This became trivial. - map.erase(refIndex); + map.erase(iter); } } } diff --git a/src/support/sorted_vector.h b/src/support/sorted_vector.h index 85824a77961..11e6173c64e 100644 --- a/src/support/sorted_vector.h +++ b/src/support/sorted_vector.h @@ -89,11 +89,12 @@ template struct SortedVector : public std::vector { return *it; } + iterator erase(iterator it) { return Base::erase(it); } + template bool erase(const K& x) { auto it = find(x); if (it != end()) { - std::move(it + 1, end(), it); - resize(size() - 1); + erase(it); return true; } return false; From 6b8400b4fd66a9f6fd410fa366d5f83d1544c5a8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 25 Sep 2026 12:06:13 -0700 Subject: [PATCH 12/12] comment --- src/ir/constraint.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 34e8b708466..7fffd3f71ec 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -408,7 +408,9 @@ struct BasicBlockConstraintMap { // Given a constraint on a local, note refs. void noteRefs(Index index, const Constraint& c); - // Given an index, erase constraints referring to it. + // Given an index, erase constraints referring to it. This is called when the + // information for this index is wiped out, so we clear the refs and the + // constraints referred to. void eraseStaleRefs(Index index); // Internal version, with a flag to flip the constraint. Whenever we apply