From c66b1bd7fb6ef27fd0f21e45ea781add8d4c23c3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 18 Aug 2026 08:23:17 -0700 Subject: [PATCH 01/32] go --- src/support/i65.h | 33 ++++++++++++++++++ src/support/span.h | 85 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 src/support/i65.h create mode 100644 src/support/span.h diff --git a/src/support/i65.h b/src/support/i65.h new file mode 100644 index 00000000000..07340c4ff6e --- /dev/null +++ b/src/support/i65.h @@ -0,0 +1,33 @@ +/* + * 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. + */ + +#ifndef wasm_support_i65_h +#define wasm_support_i65_h + +namespace wasm { + +// A 65-bit integer, capable of representing numbers in the range +// +// std::numeric_limits::min() .. std::numeric_limits::max() +// +// This allows an I65 to represent any 32 or 64-bit number, signed *or* +// unsigned. +struct I65 { +}; + +} // namespace wasm + +#endif // wasm_support_i65_h diff --git a/src/support/span.h b/src/support/span.h new file mode 100644 index 00000000000..e48f67696d2 --- /dev/null +++ b/src/support/span.h @@ -0,0 +1,85 @@ +/* + * 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. + */ + +#ifndef wasm_support_span_h +#define wasm_support_span_h + +#include "src/support/i65.h" + +namespace wasm { + +// A span of values. +// +// Span{min, max} means [min, max], inclusive of both sides. To represent an +// empty span, we use min > max. +template +struct Span { + T min, max; + + // Set a single value as possible. + void set(T value) { + min = max = value; + } + + // To represent an empty span, we use min > max, an impossible span. + void setEmpty() { + min = 1; + max = 0; + } + + bool isEmpty() const { return min > max; } + + static Span empty() { + Span ret; + ret.setEmpty(); + return ret; + } + + void setFull() { + *this = Span(); + assert(isFull()); + } + + bool isFull() const { return min == Min && max == Max; } + + static Span full() { + Span ret; + ret.setFull(); + return ret; + } + + // Intersect this with another span, returning a (possibly empty) span. + Span intersection(const Span& other) const { + if (isEmpty() || other.isEmpty()) { + return empty(); + } + return Span{std::max(min, other.min), std::min(max, other.max)}; + } + + // Checks whether two spans have any overlap at all. + bool hasOverlap(const Span& other) { + return !intersection(other).isEmpty(); + } + + // Check whether we contain another span (possibly being equal). + bool contains(const Span& other) const { + return intersection(other) == other; + } +}; + +} // namespace wasm + +#endif // wasm_support_span_h From b74ca8ef88d629b032caf80bf6f3ff89b2312ab5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 18 Aug 2026 08:23:23 -0700 Subject: [PATCH 02/32] go --- src/support/i65.h | 3 +-- src/support/span.h | 11 +++-------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/support/i65.h b/src/support/i65.h index 07340c4ff6e..41b7bebe509 100644 --- a/src/support/i65.h +++ b/src/support/i65.h @@ -25,8 +25,7 @@ namespace wasm { // // This allows an I65 to represent any 32 or 64-bit number, signed *or* // unsigned. -struct I65 { -}; +struct I65 {}; } // namespace wasm diff --git a/src/support/span.h b/src/support/span.h index e48f67696d2..28ada114aba 100644 --- a/src/support/span.h +++ b/src/support/span.h @@ -25,14 +25,11 @@ namespace wasm { // // Span{min, max} means [min, max], inclusive of both sides. To represent an // empty span, we use min > max. -template -struct Span { +template struct Span { T min, max; // Set a single value as possible. - void set(T value) { - min = max = value; - } + void set(T value) { min = max = value; } // To represent an empty span, we use min > max, an impossible span. void setEmpty() { @@ -70,9 +67,7 @@ struct Span { } // Checks whether two spans have any overlap at all. - bool hasOverlap(const Span& other) { - return !intersection(other).isEmpty(); - } + bool hasOverlap(const Span& other) { return !intersection(other).isEmpty(); } // Check whether we contain another span (possibly being equal). bool contains(const Span& other) const { From 01d20c4e8ffd3df71ee036de153f1ac9fc4cc6fe Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 18 Aug 2026 08:35:02 -0700 Subject: [PATCH 03/32] work --- src/support/i65.h | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/support/i65.h b/src/support/i65.h index 41b7bebe509..ddf3c22076b 100644 --- a/src/support/i65.h +++ b/src/support/i65.h @@ -17,6 +17,8 @@ #ifndef wasm_support_i65_h #define wasm_support_i65_h +#include + namespace wasm { // A 65-bit integer, capable of representing numbers in the range @@ -25,7 +27,38 @@ namespace wasm { // // This allows an I65 to represent any 32 or 64-bit number, signed *or* // unsigned. -struct I65 {}; +struct I65 { + // A 64-bit payload with an extra 65th sign bit. + uint64_t value = 0; + bool negative = false; + + // Unsigned values are simple. + I65(uint32_t x) : value(x) {} + I65(uint64_t x) : value(x) {} + + // Signed values need to be checked for being negative. + I65(int32_t x) { + if (x >= 0) { + value = x; + } else { + negative = true; + value = -int64_t(x); + } + } + I65(int64_t x) { + if (x >= 0) { + value = x; + } else { + negative = true; + + // We cannot simply negate MIN_INT64. + if (x == std::numeric_limits::min()) { + value = uint64_t(1) << 31; + } else { + value = -int64_t(x); + } + } + } } // namespace wasm From 71a0a255fc5021976c9568316697ee4f4af2c5f0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 18 Aug 2026 08:38:28 -0700 Subject: [PATCH 04/32] work --- src/support/i65.h | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/support/i65.h b/src/support/i65.h index ddf3c22076b..452b63c65c6 100644 --- a/src/support/i65.h +++ b/src/support/i65.h @@ -60,6 +60,32 @@ struct I65 { } } + bool operator==(const I65& other) const { + return value == other.value && negative == other.negative; + } + bool operator!=(const I65& other) const { + return !(*this == other); + } + + bool operator<(const I65& other) const { + if (negative) { + if (other.negative) { + // Both negative; we are smaller if absolute value is larger. + return value > other.value; + } else { + // Only we are negative, so we are smaller. + return true; + } + } else { + if (other.negative) { + // Only the other is negative, so we are larger. + return false; + } else { + // Both positive; we are smaller if absolute value is smaller. + return value < other.value; + } + } + } } // namespace wasm #endif // wasm_support_i65_h From 553a48171b6b5fc82c165b6efd92dae8bc0f9d60 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 18 Aug 2026 08:39:26 -0700 Subject: [PATCH 05/32] work --- src/support/i65.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/support/i65.h b/src/support/i65.h index 452b63c65c6..8a35819a1ca 100644 --- a/src/support/i65.h +++ b/src/support/i65.h @@ -86,6 +86,15 @@ struct I65 { } } } + bool operator<=(const I65& other) const { + return *this < other || *this == other; + } + bool operator>(const I65& other) const { + return !(*this <= other); + } + bool operator>=(const I65& other) const { + return !(*this < other); + } } // namespace wasm #endif // wasm_support_i65_h From df402c9a03a6c742e9fc1eb97ffdd80a84d9d5e5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 18 Aug 2026 08:40:42 -0700 Subject: [PATCH 06/32] work --- src/support/i65.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/support/i65.h b/src/support/i65.h index 8a35819a1ca..a7c1d065c50 100644 --- a/src/support/i65.h +++ b/src/support/i65.h @@ -51,7 +51,7 @@ struct I65 { } else { negative = true; - // We cannot simply negate MIN_INT64. + // One does not simply negate MIN_INT64. if (x == std::numeric_limits::min()) { value = uint64_t(1) << 31; } else { From 19fc7087c26a253da8df7e355e3918b8f77f6a8b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 18 Aug 2026 09:12:54 -0700 Subject: [PATCH 07/32] changes so far --- src/support/i65.h | 76 ++++++-- src/support/span.h | 38 +++- test/gtest/CMakeLists.txt | 2 + test/gtest/i65.cpp | 259 +++++++++++++++++++++++++++ test/gtest/span.cpp | 361 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 721 insertions(+), 15 deletions(-) create mode 100644 test/gtest/i65.cpp create mode 100644 test/gtest/span.cpp diff --git a/src/support/i65.h b/src/support/i65.h index a7c1d065c50..53953da0032 100644 --- a/src/support/i65.h +++ b/src/support/i65.h @@ -17,6 +17,8 @@ #ifndef wasm_support_i65_h #define wasm_support_i65_h +#include +#include #include namespace wasm { @@ -32,12 +34,14 @@ struct I65 { uint64_t value = 0; bool negative = false; + constexpr I65() = default; + // Unsigned values are simple. - I65(uint32_t x) : value(x) {} - I65(uint64_t x) : value(x) {} + constexpr I65(uint32_t x) : value(x) {} + constexpr I65(uint64_t x) : value(x) {} // Signed values need to be checked for being negative. - I65(int32_t x) { + constexpr I65(int32_t x) { if (x >= 0) { value = x; } else { @@ -45,7 +49,7 @@ struct I65 { value = -int64_t(x); } } - I65(int64_t x) { + constexpr I65(int64_t x) { if (x >= 0) { value = x; } else { @@ -53,21 +57,21 @@ struct I65 { // One does not simply negate MIN_INT64. if (x == std::numeric_limits::min()) { - value = uint64_t(1) << 31; + value = uint64_t(1) << 63; } else { value = -int64_t(x); } } } - bool operator==(const I65& other) const { + constexpr bool operator==(const I65& other) const { return value == other.value && negative == other.negative; } - bool operator!=(const I65& other) const { + constexpr bool operator!=(const I65& other) const { return !(*this == other); } - bool operator<(const I65& other) const { + constexpr bool operator<(const I65& other) const { if (negative) { if (other.negative) { // Both negative; we are smaller if absolute value is larger. @@ -86,15 +90,65 @@ struct I65 { } } } - bool operator<=(const I65& other) const { + constexpr bool operator<=(const I65& other) const { return *this < other || *this == other; } - bool operator>(const I65& other) const { + constexpr bool operator>(const I65& other) const { return !(*this <= other); } - bool operator>=(const I65& other) const { + constexpr bool operator>=(const I65& other) const { return !(*this < other); } +}; + +inline std::ostream& operator<<(std::ostream& os, const I65& x) { + if (x.negative) { + os << '-'; + } + return os << x.value; +} + } // namespace wasm +namespace std { + +template<> class numeric_limits { +public: + static constexpr bool is_specialized = true; + static constexpr bool is_signed = true; + static constexpr bool is_integer = true; + static constexpr bool is_exact = true; + static constexpr bool has_infinity = false; + static constexpr bool has_quiet_NaN = false; + static constexpr bool has_signaling_NaN = false; + static constexpr float_denorm_style has_denorm = denorm_absent; + static constexpr bool has_denorm_loss = false; + static constexpr float_round_style round_style = round_toward_zero; + static constexpr bool is_iec559 = false; + static constexpr bool is_bounded = true; + static constexpr bool is_modulo = false; + static constexpr int digits = 65; + static constexpr int digits10 = 19; + static constexpr int max_digits10 = 0; + static constexpr int radix = 2; + static constexpr int min_exponent = 0; + static constexpr int min_exponent10 = 0; + static constexpr int max_exponent = 0; + static constexpr int max_exponent10 = 0; + static constexpr bool traps = false; + static constexpr bool tinyness_before = false; + + static constexpr wasm::I65 min() noexcept { + return wasm::I65(std::numeric_limits::min()); + } + static constexpr wasm::I65 lowest() noexcept { + return min(); + } + static constexpr wasm::I65 max() noexcept { + return wasm::I65(std::numeric_limits::max()); + } +}; + +} // namespace std + #endif // wasm_support_i65_h diff --git a/src/support/span.h b/src/support/span.h index 28ada114aba..6c55950a16f 100644 --- a/src/support/span.h +++ b/src/support/span.h @@ -17,16 +17,28 @@ #ifndef wasm_support_span_h #define wasm_support_span_h -#include "src/support/i65.h" +#include +#include +#include +#include + +#include "support/i65.h" namespace wasm { -// A span of values. +// A span of values. // // Span{min, max} means [min, max], inclusive of both sides. To represent an // empty span, we use min > max. template struct Span { - T min, max; + static constexpr T Min = std::numeric_limits::lowest(); + static constexpr T Max = std::numeric_limits::max(); + + T min = Min; + T max = Max; + + constexpr Span() = default; + constexpr Span(T min, T max) : min(min), max(max) {} // Set a single value as possible. void set(T value) { min = max = value; } @@ -67,14 +79,32 @@ template struct Span { } // Checks whether two spans have any overlap at all. - bool hasOverlap(const Span& other) { return !intersection(other).isEmpty(); } + bool hasOverlap(const Span& other) const { return !intersection(other).isEmpty(); } // Check whether we contain another span (possibly being equal). bool contains(const Span& other) const { return intersection(other) == other; } + + bool operator==(const Span& other) const { + if (isEmpty()) { + return other.isEmpty(); + } + return !other.isEmpty() && min == other.min && max == other.max; + } + bool operator!=(const Span& other) const { + return !(*this == other); + } }; +template +inline std::ostream& operator<<(std::ostream& os, const Span& span) { + if (span.isEmpty()) { + return os << "[empty]"; + } + return os << '[' << span.min << ", " << span.max << ']'; +} + } // namespace wasm #endif // wasm_support_span_h diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index ab49c49f438..13b304b5671 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -17,6 +17,7 @@ set(unittest_SOURCES disjoint_sets.cpp graph.cpp int128.cpp + i65.cpp leaves.cpp glbs.cpp inplace_vector.cpp @@ -31,6 +32,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/i65.cpp b/test/gtest/i65.cpp new file mode 100644 index 00000000000..cb4874775bd --- /dev/null +++ b/test/gtest/i65.cpp @@ -0,0 +1,259 @@ +/* + * 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 +#include +#include + +#include "support/i65.h" +#include "gtest/gtest.h" + +using namespace wasm; + +TEST(I65Test, DefaultConstruct) { + I65 x; + EXPECT_EQ(x.value, 0u); + EXPECT_FALSE(x.negative); + EXPECT_EQ(x, I65(0)); +} + +TEST(I65Test, ConstructFromUnsigned32) { + uint32_t zero = 0; + uint32_t one = 1; + uint32_t mid = 12345678; + uint32_t maxU32 = std::numeric_limits::max(); + + I65 iZero(zero); + EXPECT_EQ(iZero.value, 0u); + EXPECT_FALSE(iZero.negative); + + I65 iOne(one); + EXPECT_EQ(iOne.value, 1u); + EXPECT_FALSE(iOne.negative); + + I65 iMid(mid); + EXPECT_EQ(iMid.value, mid); + EXPECT_FALSE(iMid.negative); + + I65 iMax(maxU32); + EXPECT_EQ(iMax.value, uint64_t(maxU32)); + EXPECT_FALSE(iMax.negative); +} + +TEST(I65Test, ConstructFromSigned32) { + int32_t zero = 0; + int32_t one = 1; + int32_t maxI32 = std::numeric_limits::max(); + int32_t negOne = -1; + int32_t negMid = -12345678; + int32_t minI32 = std::numeric_limits::min(); + + I65 iZero(zero); + EXPECT_EQ(iZero.value, 0u); + EXPECT_FALSE(iZero.negative); + + I65 iOne(one); + EXPECT_EQ(iOne.value, 1u); + EXPECT_FALSE(iOne.negative); + + I65 iMax(maxI32); + EXPECT_EQ(iMax.value, uint64_t(maxI32)); + EXPECT_FALSE(iMax.negative); + + I65 iNegOne(negOne); + EXPECT_EQ(iNegOne.value, 1u); + EXPECT_TRUE(iNegOne.negative); + + I65 iNegMid(negMid); + EXPECT_EQ(iNegMid.value, 12345678u); + EXPECT_TRUE(iNegMid.negative); + + I65 iMin(minI32); + EXPECT_EQ(iMin.value, 2147483648ULL); + EXPECT_TRUE(iMin.negative); +} + +TEST(I65Test, ConstructFromUnsigned64) { + uint64_t zero = 0; + uint64_t one = 1; + uint64_t maxU32 = std::numeric_limits::max(); + uint64_t maxI64 = std::numeric_limits::max(); + uint64_t highBitOnly = uint64_t(1) << 63; + uint64_t maxU64 = std::numeric_limits::max(); + + I65 iZero(zero); + EXPECT_EQ(iZero.value, 0u); + EXPECT_FALSE(iZero.negative); + + I65 iOne(one); + EXPECT_EQ(iOne.value, 1u); + EXPECT_FALSE(iOne.negative); + + I65 iMaxU32(maxU32); + EXPECT_EQ(iMaxU32.value, maxU32); + EXPECT_FALSE(iMaxU32.negative); + + I65 iMaxI64(maxI64); + EXPECT_EQ(iMaxI64.value, maxI64); + EXPECT_FALSE(iMaxI64.negative); + + I65 iHighBit(highBitOnly); + EXPECT_EQ(iHighBit.value, highBitOnly); + EXPECT_FALSE(iHighBit.negative); + + I65 iMaxU64(maxU64); + EXPECT_EQ(iMaxU64.value, maxU64); + EXPECT_FALSE(iMaxU64.negative); +} + +TEST(I65Test, ConstructFromSigned64) { + int64_t zero = 0; + int64_t one = 1; + int64_t maxI64 = std::numeric_limits::max(); + int64_t negOne = -1; + int64_t minI64 = std::numeric_limits::min(); + int64_t minI64PlusOne = std::numeric_limits::min() + 1; + + I65 iZero(zero); + EXPECT_EQ(iZero.value, 0u); + EXPECT_FALSE(iZero.negative); + + I65 iOne(one); + EXPECT_EQ(iOne.value, 1u); + EXPECT_FALSE(iOne.negative); + + I65 iMax(maxI64); + EXPECT_EQ(iMax.value, uint64_t(maxI64)); + EXPECT_FALSE(iMax.negative); + + I65 iNegOne(negOne); + EXPECT_EQ(iNegOne.value, 1u); + EXPECT_TRUE(iNegOne.negative); + + I65 iMin(minI64); + EXPECT_EQ(iMin.value, uint64_t(1) << 63); + EXPECT_TRUE(iMin.negative); + + I65 iMinPlusOne(minI64PlusOne); + EXPECT_EQ(iMinPlusOne.value, uint64_t(std::numeric_limits::max())); + EXPECT_TRUE(iMinPlusOne.negative); +} + +TEST(I65Test, EqualityAndInequality) { + EXPECT_EQ(I65(int32_t(0)), I65(uint32_t(0))); + EXPECT_EQ(I65(int32_t(0)), I65(int64_t(0))); + EXPECT_EQ(I65(int32_t(0)), I65(uint64_t(0))); + + EXPECT_EQ(I65(int32_t(42)), I65(uint32_t(42))); + EXPECT_EQ(I65(int32_t(42)), I65(int64_t(42))); + EXPECT_EQ(I65(int32_t(42)), I65(uint64_t(42))); + + EXPECT_EQ(I65(int32_t(-42)), I65(int64_t(-42))); + EXPECT_EQ(I65(std::numeric_limits::min()), + I65(int64_t(std::numeric_limits::min()))); + + EXPECT_NE(I65(int32_t(1)), I65(int32_t(-1))); + EXPECT_NE(I65(uint64_t(0xffffffffffffffffULL)), I65(int64_t(-1))); + EXPECT_NE(I65(std::numeric_limits::min()), + I65(uint64_t(1) << 63)); +} + +TEST(I65Test, TotalOrdering) { + std::vector sortedValues = { + I65(std::numeric_limits::min()), + I65(std::numeric_limits::min() + 1), + I65(int64_t(-0x100000000LL)), + I65(std::numeric_limits::min()), + I65(int32_t(-12345)), + I65(int64_t(-2)), + I65(int64_t(-1)), + I65(0), + I65(1), + I65(2), + I65(int32_t(12345)), + I65(std::numeric_limits::max()), + I65(uint64_t(std::numeric_limits::max()) + 1), + I65(std::numeric_limits::max()), + I65(uint64_t(std::numeric_limits::max()) + 1), + I65(std::numeric_limits::max() - 1), + I65(std::numeric_limits::max()), + I65(uint64_t(std::numeric_limits::max()) + 1), + I65(std::numeric_limits::max() - 1), + I65(std::numeric_limits::max()), + }; + + for (size_t i = 0; i < sortedValues.size(); ++i) { + for (size_t j = 0; j < sortedValues.size(); ++j) { + const auto& a = sortedValues[i]; + const auto& b = sortedValues[j]; + + if (i < j) { + EXPECT_LT(a, b); + EXPECT_LE(a, b); + EXPECT_GT(b, a); + EXPECT_GE(b, a); + EXPECT_NE(a, b); + EXPECT_FALSE(a == b); + EXPECT_FALSE(b < a); + } else if (i == j) { + EXPECT_EQ(a, b); + EXPECT_LE(a, b); + EXPECT_GE(a, b); + EXPECT_FALSE(a < b); + EXPECT_FALSE(a > b); + EXPECT_FALSE(a != b); + } else { + EXPECT_GT(a, b); + EXPECT_GE(a, b); + EXPECT_LT(b, a); + EXPECT_LE(b, a); + EXPECT_NE(a, b); + EXPECT_FALSE(a == b); + EXPECT_FALSE(a < b); + } + } + } +} + +TEST(I65Test, NumericLimits) { + EXPECT_TRUE(std::numeric_limits::is_specialized); + EXPECT_TRUE(std::numeric_limits::is_signed); + EXPECT_TRUE(std::numeric_limits::is_integer); + + EXPECT_EQ(std::numeric_limits::min(), + I65(std::numeric_limits::min())); + EXPECT_EQ(std::numeric_limits::lowest(), + I65(std::numeric_limits::min())); + EXPECT_EQ(std::numeric_limits::max(), + I65(std::numeric_limits::max())); +} + +TEST(I65Test, StreamOutput) { + auto toString = [](const I65& x) { + std::ostringstream ss; + ss << x; + return ss.str(); + }; + + EXPECT_EQ(toString(I65(0)), "0"); + EXPECT_EQ(toString(I65(42)), "42"); + EXPECT_EQ(toString(I65(-42)), "-42"); + EXPECT_EQ(toString(I65(std::numeric_limits::min())), + "-9223372036854775808"); + EXPECT_EQ(toString(I65(std::numeric_limits::max())), + "18446744073709551615"); +} diff --git a/test/gtest/span.cpp b/test/gtest/span.cpp new file mode 100644 index 00000000000..02613dde08f --- /dev/null +++ b/test/gtest/span.cpp @@ -0,0 +1,361 @@ +/* + * 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 +#include + +#include "support/i65.h" +#include "support/span.h" +#include "gtest/gtest.h" + +using namespace wasm; + +// ============================================================================ +// Generic Span tests +// ============================================================================ + +TEST(SpanTest, EmptySpanInt) { + Span empty = Span::empty(); + EXPECT_TRUE(empty.isEmpty()); + EXPECT_FALSE(empty.isFull()); + + Span invalid(10, 5); + EXPECT_TRUE(invalid.isEmpty()); + EXPECT_FALSE(invalid.isFull()); + + EXPECT_EQ(empty, invalid); + + Span s; + EXPECT_FALSE(s.isEmpty()); + s.setEmpty(); + EXPECT_TRUE(s.isEmpty()); + EXPECT_EQ(s, empty); +} + +TEST(SpanTest, FullSpanIntTypes) { + // Signed 32-bit + Span fullI32 = Span::full(); + EXPECT_TRUE(fullI32.isFull()); + EXPECT_FALSE(fullI32.isEmpty()); + EXPECT_EQ(fullI32.min, std::numeric_limits::min()); + EXPECT_EQ(fullI32.max, std::numeric_limits::max()); + + Span defI32; + EXPECT_TRUE(defI32.isFull()); + EXPECT_EQ(defI32, fullI32); + + // Unsigned 32-bit + Span fullU32 = Span::full(); + EXPECT_TRUE(fullU32.isFull()); + EXPECT_FALSE(fullU32.isEmpty()); + EXPECT_EQ(fullU32.min, 0u); + EXPECT_EQ(fullU32.max, std::numeric_limits::max()); + + // Signed 64-bit + Span fullI64 = Span::full(); + EXPECT_TRUE(fullI64.isFull()); + EXPECT_FALSE(fullI64.isEmpty()); + EXPECT_EQ(fullI64.min, std::numeric_limits::min()); + EXPECT_EQ(fullI64.max, std::numeric_limits::max()); + + // Unsigned 64-bit + Span fullU64 = Span::full(); + EXPECT_TRUE(fullU64.isFull()); + EXPECT_FALSE(fullU64.isEmpty()); + EXPECT_EQ(fullU64.min, 0ull); + EXPECT_EQ(fullU64.max, std::numeric_limits::max()); +} + +TEST(SpanTest, SetSingleValue) { + Span s; + s.set(42); + EXPECT_EQ(s.min, 42); + EXPECT_EQ(s.max, 42); + EXPECT_FALSE(s.isEmpty()); + EXPECT_FALSE(s.isFull()); + EXPECT_EQ(s, Span(42, 42)); +} + +TEST(SpanTest, SetFull) { + Span s(10, 20); + EXPECT_FALSE(s.isFull()); + s.setFull(); + EXPECT_TRUE(s.isFull()); + EXPECT_EQ(s.min, std::numeric_limits::min()); + EXPECT_EQ(s.max, std::numeric_limits::max()); +} + +TEST(SpanTest, IntersectionInt) { + Span a(1, 10); + Span b(5, 15); + Span ab = a.intersection(b); + EXPECT_EQ(ab, Span(5, 10)); + + // Commutativity + EXPECT_EQ(b.intersection(a), Span(5, 10)); + + // Touching at a single point + Span c(10, 20); + EXPECT_EQ(a.intersection(c), Span(10, 10)); + + // Disjoint + Span d(11, 20); + EXPECT_TRUE(a.intersection(d).isEmpty()); + EXPECT_EQ(a.intersection(d), Span::empty()); + + // Contained + Span e(3, 7); + EXPECT_EQ(a.intersection(e), Span(3, 7)); + + // Identical + EXPECT_EQ(a.intersection(a), a); + + // With empty + EXPECT_TRUE(a.intersection(Span::empty()).isEmpty()); + EXPECT_TRUE(Span::empty().intersection(a).isEmpty()); + + // With full + EXPECT_EQ(a.intersection(Span::full()), a); + EXPECT_EQ(Span::full().intersection(a), a); +} + +TEST(SpanTest, HasOverlapInt) { + Span a(1, 10); + Span b(5, 15); + Span c(10, 20); + Span d(11, 20); + + EXPECT_TRUE(a.hasOverlap(b)); + EXPECT_TRUE(b.hasOverlap(a)); + EXPECT_TRUE(a.hasOverlap(c)); + EXPECT_FALSE(a.hasOverlap(d)); + EXPECT_FALSE(d.hasOverlap(a)); + + EXPECT_FALSE(a.hasOverlap(Span::empty())); + EXPECT_TRUE(a.hasOverlap(Span::full())); + EXPECT_FALSE(Span::empty().hasOverlap(Span::full())); +} + +TEST(SpanTest, ContainsInt) { + Span a(1, 10); + Span b(3, 7); + Span c(5, 15); + Span d(11, 20); + + EXPECT_TRUE(a.contains(b)); + EXPECT_FALSE(b.contains(a)); + + EXPECT_TRUE(a.contains(a)); + EXPECT_FALSE(a.contains(c)); + EXPECT_FALSE(a.contains(d)); + + EXPECT_TRUE(a.contains(Span::empty())); + EXPECT_TRUE(Span::empty().contains(Span::empty())); + EXPECT_FALSE(Span::empty().contains(a)); + + EXPECT_TRUE(Span::full().contains(a)); + EXPECT_TRUE(Span::full().contains(Span::empty())); + EXPECT_FALSE(a.contains(Span::full())); +} + +TEST(SpanTest, StreamOutput) { + auto toString = [](const auto& span) { + std::ostringstream ss; + ss << span; + return ss.str(); + }; + + EXPECT_EQ(toString(Span(1, 10)), "[1, 10]"); + EXPECT_EQ(toString(Span::empty()), "[empty]"); + EXPECT_EQ(toString(Span(10, 5)), "[empty]"); +} + +// ============================================================================ +// Span tests (corner cases, sign mixing, large range) +// ============================================================================ + +TEST(SpanI65Test, FullAndLimits) { + EXPECT_EQ(Span::Min, I65(std::numeric_limits::min())); + EXPECT_EQ(Span::Max, I65(std::numeric_limits::max())); + + Span full = Span::full(); + EXPECT_TRUE(full.isFull()); + EXPECT_FALSE(full.isEmpty()); + EXPECT_EQ(full.min, I65(std::numeric_limits::min())); + EXPECT_EQ(full.max, I65(std::numeric_limits::max())); + + // Default constructed span is full + Span def; + EXPECT_TRUE(def.isFull()); + EXPECT_FALSE(def.isEmpty()); + EXPECT_EQ(def, full); +} + +TEST(SpanI65Test, Empty) { + Span empty = Span::empty(); + EXPECT_TRUE(empty.isEmpty()); + EXPECT_FALSE(empty.isFull()); + + Span empty2{I65(100), I65(-100)}; + EXPECT_TRUE(empty2.isEmpty()); + EXPECT_FALSE(empty2.isFull()); + EXPECT_EQ(empty, empty2); + + Span empty3{I65(uint64_t(1)), I65(int64_t(-1))}; + EXPECT_TRUE(empty3.isEmpty()); + EXPECT_EQ(empty, empty3); +} + +TEST(SpanI65Test, SingletonsAtExtremes) { + // Min int64 singleton + Span minI64{I65(std::numeric_limits::min()), + I65(std::numeric_limits::min())}; + EXPECT_FALSE(minI64.isEmpty()); + EXPECT_FALSE(minI64.isFull()); + EXPECT_EQ(minI64.min, I65(std::numeric_limits::min())); + EXPECT_EQ(minI64.max, I65(std::numeric_limits::min())); + + // -1 singleton + Span negOne{I65(-1), I65(-1)}; + EXPECT_FALSE(negOne.isEmpty()); + + // 0 singleton + Span zero{I65(0), I65(0)}; + EXPECT_FALSE(zero.isEmpty()); + + // 1 singleton + Span one{I65(1), I65(1)}; + EXPECT_FALSE(one.isEmpty()); + + // Max int64 singleton + Span maxI64{I65(std::numeric_limits::max()), + I65(std::numeric_limits::max())}; + EXPECT_FALSE(maxI64.isEmpty()); + + // 2^63 singleton (above int64_t max, into uint64_t territory) + Span highBit{I65(uint64_t(1) << 63), I65(uint64_t(1) << 63)}; + EXPECT_FALSE(highBit.isEmpty()); + + // Max uint64 singleton + Span maxU64{I65(std::numeric_limits::max()), + I65(std::numeric_limits::max())}; + EXPECT_FALSE(maxU64.isEmpty()); +} + +TEST(SpanI65Test, CrossingZero) { + Span span{I65(-10), I65(10)}; + EXPECT_FALSE(span.isEmpty()); + EXPECT_FALSE(span.isFull()); + + // Contains points inside + EXPECT_TRUE(span.contains(Span(I65(-10), I65(-10)))); + EXPECT_TRUE(span.contains(Span(I65(-5), I65(5)))); + EXPECT_TRUE(span.contains(Span(I65(0), I65(0)))); + EXPECT_TRUE(span.contains(Span(I65(10), I65(10)))); + + // Does not contain points outside + EXPECT_FALSE(span.contains(Span(I65(-11), I65(-11)))); + EXPECT_FALSE(span.contains(Span(I65(11), I65(11)))); + EXPECT_FALSE(span.contains(Span(I65(-15), I65(5)))); + EXPECT_FALSE(span.contains(Span(I65(-5), I65(15)))); +} + +TEST(SpanI65Test, NegativeAndPositiveIntersections) { + Span neg{I65(-100), I65(-10)}; + Span pos{I65(10), I65(100)}; + + EXPECT_FALSE(neg.hasOverlap(pos)); + EXPECT_FALSE(pos.hasOverlap(neg)); + EXPECT_TRUE(neg.intersection(pos).isEmpty()); + EXPECT_TRUE(pos.intersection(neg).isEmpty()); + + Span touchNegZero{I65(-10), I65(0)}; + Span touchZeroPos{I65(0), I65(10)}; + EXPECT_TRUE(touchNegZero.hasOverlap(touchZeroPos)); + EXPECT_EQ(touchNegZero.intersection(touchZeroPos), Span(I65(0), I65(0))); + + Span overlap{I65(-50), I65(50)}; + EXPECT_EQ(neg.intersection(overlap), Span(I65(-50), I65(-10))); + EXPECT_EQ(pos.intersection(overlap), Span(I65(10), I65(50))); +} + +TEST(SpanI65Test, SignedUnsignedBoundary) { + // Test around INT64_MAX and 2^63 + int64_t maxI64 = std::numeric_limits::max(); + uint64_t highBit = uint64_t(maxI64) + 1; // 0x8000000000000000ULL + + Span s1(I65(maxI64 - 100), I65(highBit + 50)); + Span s2(I65(highBit), I65(highBit + 100)); + + EXPECT_TRUE(s1.hasOverlap(s2)); + EXPECT_EQ(s1.intersection(s2), Span(I65(highBit), I65(highBit + 50))); + + // Disjoint near 2^63 boundary + Span s3{I65(maxI64 - 200), I65(maxI64)}; + Span s4{I65(highBit + 1), I65(highBit + 100)}; + EXPECT_FALSE(s3.hasOverlap(s4)); + EXPECT_TRUE(s3.intersection(s4).isEmpty()); + + // Adjacent touching at 2^63 + Span s5{I65(maxI64), I65(highBit)}; + Span s6{I65(highBit), I65(highBit + 10)}; + EXPECT_TRUE(s5.hasOverlap(s6)); + EXPECT_EQ(s5.intersection(s6), Span(I65(highBit), I65(highBit))); +} + +TEST(SpanI65Test, ExtremeBoundaries) { + Span minPart(I65(std::numeric_limits::min()), + I65(std::numeric_limits::min() + 100)); + Span maxPart(I65(std::numeric_limits::max() - 100), + I65(std::numeric_limits::max())); + + EXPECT_FALSE(minPart.hasOverlap(maxPart)); + EXPECT_TRUE(minPart.intersection(maxPart).isEmpty()); + + Span full = Span::full(); + EXPECT_TRUE(full.contains(minPart)); + EXPECT_TRUE(full.contains(maxPart)); + EXPECT_EQ(full.intersection(minPart), minPart); + EXPECT_EQ(full.intersection(maxPart), maxPart); + EXPECT_TRUE(full.hasOverlap(minPart)); + EXPECT_TRUE(full.hasOverlap(maxPart)); + + Span allNeg(I65(std::numeric_limits::min()), I65(-1)); + Span allNonNeg(I65(0), I65(std::numeric_limits::max())); + + EXPECT_FALSE(allNeg.hasOverlap(allNonNeg)); + EXPECT_TRUE(allNeg.intersection(allNonNeg).isEmpty()); + EXPECT_TRUE(full.contains(allNeg)); + EXPECT_TRUE(full.contains(allNonNeg)); +} + +TEST(SpanI65Test, SetAndMutate) { + Span s; + EXPECT_TRUE(s.isFull()); + + s.set(I65(-12345)); + EXPECT_FALSE(s.isFull()); + EXPECT_FALSE(s.isEmpty()); + EXPECT_EQ(s.min, I65(-12345)); + EXPECT_EQ(s.max, I65(-12345)); + + s.setEmpty(); + EXPECT_TRUE(s.isEmpty()); + + s.setFull(); + EXPECT_TRUE(s.isFull()); +} From b645fabb33b2a6814bd91dbc6ba713a7076d7890 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 18 Aug 2026 09:13:16 -0700 Subject: [PATCH 08/32] format --- src/support/i65.h | 12 +++--------- src/support/span.h | 8 ++++---- test/gtest/i65.cpp | 3 +-- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/support/i65.h b/src/support/i65.h index 53953da0032..305f4e44c22 100644 --- a/src/support/i65.h +++ b/src/support/i65.h @@ -93,12 +93,8 @@ struct I65 { constexpr bool operator<=(const I65& other) const { return *this < other || *this == other; } - constexpr bool operator>(const I65& other) const { - return !(*this <= other); - } - constexpr bool operator>=(const I65& other) const { - return !(*this < other); - } + constexpr bool operator>(const I65& other) const { return !(*this <= other); } + constexpr bool operator>=(const I65& other) const { return !(*this < other); } }; inline std::ostream& operator<<(std::ostream& os, const I65& x) { @@ -141,9 +137,7 @@ template<> class numeric_limits { static constexpr wasm::I65 min() noexcept { return wasm::I65(std::numeric_limits::min()); } - static constexpr wasm::I65 lowest() noexcept { - return min(); - } + static constexpr wasm::I65 lowest() noexcept { return min(); } static constexpr wasm::I65 max() noexcept { return wasm::I65(std::numeric_limits::max()); } diff --git a/src/support/span.h b/src/support/span.h index 6c55950a16f..3b8ea83a8f7 100644 --- a/src/support/span.h +++ b/src/support/span.h @@ -79,7 +79,9 @@ template struct Span { } // Checks whether two spans have any overlap at all. - bool hasOverlap(const Span& other) const { return !intersection(other).isEmpty(); } + bool hasOverlap(const Span& other) const { + return !intersection(other).isEmpty(); + } // Check whether we contain another span (possibly being equal). bool contains(const Span& other) const { @@ -92,9 +94,7 @@ template struct Span { } return !other.isEmpty() && min == other.min && max == other.max; } - bool operator!=(const Span& other) const { - return !(*this == other); - } + bool operator!=(const Span& other) const { return !(*this == other); } }; template diff --git a/test/gtest/i65.cpp b/test/gtest/i65.cpp index cb4874775bd..dd50840d02d 100644 --- a/test/gtest/i65.cpp +++ b/test/gtest/i65.cpp @@ -168,8 +168,7 @@ TEST(I65Test, EqualityAndInequality) { EXPECT_NE(I65(int32_t(1)), I65(int32_t(-1))); EXPECT_NE(I65(uint64_t(0xffffffffffffffffULL)), I65(int64_t(-1))); - EXPECT_NE(I65(std::numeric_limits::min()), - I65(uint64_t(1) << 63)); + EXPECT_NE(I65(std::numeric_limits::min()), I65(uint64_t(1) << 63)); } TEST(I65Test, TotalOrdering) { From b3184eda4d827891dbada0d9fdc8522e4a7a5bba Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 Aug 2026 11:05:09 -0700 Subject: [PATCH 09/32] rename --- src/support/{i65.h => iu64.h} | 44 ++++----- test/gtest/{i65.cpp => iu64.cpp} | 164 +++++++++++++++---------------- test/gtest/span.cpp | 134 ++++++++++++------------- 3 files changed, 171 insertions(+), 171 deletions(-) rename src/support/{i65.h => iu64.h} (73%) rename test/gtest/{i65.cpp => iu64.cpp} (57%) diff --git a/src/support/i65.h b/src/support/iu64.h similarity index 73% rename from src/support/i65.h rename to src/support/iu64.h index 305f4e44c22..a6b0bbb980a 100644 --- a/src/support/i65.h +++ b/src/support/iu64.h @@ -23,25 +23,25 @@ namespace wasm { -// A 65-bit integer, capable of representing numbers in the range +// An integer capable of representing numbers in the combined range of 32 and +// 64-bit integers, both signed and unsigned. That is, in the range // // std::numeric_limits::min() .. std::numeric_limits::max() // -// This allows an I65 to represent any 32 or 64-bit number, signed *or* -// unsigned. -struct I65 { +// This is basically an i64 combined with a u64 in terms of range, hence "IU64". +struct IU64 { // A 64-bit payload with an extra 65th sign bit. uint64_t value = 0; bool negative = false; - constexpr I65() = default; + constexpr IU64() = default; // Unsigned values are simple. - constexpr I65(uint32_t x) : value(x) {} - constexpr I65(uint64_t x) : value(x) {} + constexpr IU64(uint32_t x) : value(x) {} + constexpr IU64(uint64_t x) : value(x) {} // Signed values need to be checked for being negative. - constexpr I65(int32_t x) { + constexpr IU64(int32_t x) { if (x >= 0) { value = x; } else { @@ -49,7 +49,7 @@ struct I65 { value = -int64_t(x); } } - constexpr I65(int64_t x) { + constexpr IU64(int64_t x) { if (x >= 0) { value = x; } else { @@ -64,14 +64,14 @@ struct I65 { } } - constexpr bool operator==(const I65& other) const { + constexpr bool operator==(const IU64& other) const { return value == other.value && negative == other.negative; } - constexpr bool operator!=(const I65& other) const { + constexpr bool operator!=(const IU64& other) const { return !(*this == other); } - constexpr bool operator<(const I65& other) const { + constexpr bool operator<(const IU64& other) const { if (negative) { if (other.negative) { // Both negative; we are smaller if absolute value is larger. @@ -90,14 +90,14 @@ struct I65 { } } } - constexpr bool operator<=(const I65& other) const { + constexpr bool operator<=(const IU64& other) const { return *this < other || *this == other; } - constexpr bool operator>(const I65& other) const { return !(*this <= other); } - constexpr bool operator>=(const I65& other) const { return !(*this < other); } + constexpr bool operator>(const IU64& other) const { return !(*this <= other); } + constexpr bool operator>=(const IU64& other) const { return !(*this < other); } }; -inline std::ostream& operator<<(std::ostream& os, const I65& x) { +inline std::ostream& operator<<(std::ostream& os, const IU64& x) { if (x.negative) { os << '-'; } @@ -108,7 +108,7 @@ inline std::ostream& operator<<(std::ostream& os, const I65& x) { namespace std { -template<> class numeric_limits { +template<> class numeric_limits { public: static constexpr bool is_specialized = true; static constexpr bool is_signed = true; @@ -134,12 +134,12 @@ template<> class numeric_limits { static constexpr bool traps = false; static constexpr bool tinyness_before = false; - static constexpr wasm::I65 min() noexcept { - return wasm::I65(std::numeric_limits::min()); + static constexpr wasm::IU64 min() noexcept { + return wasm::IU64(std::numeric_limits::min()); } - static constexpr wasm::I65 lowest() noexcept { return min(); } - static constexpr wasm::I65 max() noexcept { - return wasm::I65(std::numeric_limits::max()); + static constexpr wasm::IU64 lowest() noexcept { return min(); } + static constexpr wasm::IU64 max() noexcept { + return wasm::IU64(std::numeric_limits::max()); } }; diff --git a/test/gtest/i65.cpp b/test/gtest/iu64.cpp similarity index 57% rename from test/gtest/i65.cpp rename to test/gtest/iu64.cpp index dd50840d02d..0c38fcbe799 100644 --- a/test/gtest/i65.cpp +++ b/test/gtest/iu64.cpp @@ -24,37 +24,37 @@ using namespace wasm; -TEST(I65Test, DefaultConstruct) { - I65 x; +TEST(IU64Test, DefaultConstruct) { + IU64 x; EXPECT_EQ(x.value, 0u); EXPECT_FALSE(x.negative); - EXPECT_EQ(x, I65(0)); + EXPECT_EQ(x, IU64(0)); } -TEST(I65Test, ConstructFromUnsigned32) { +TEST(IU64Test, ConstructFromUnsigned32) { uint32_t zero = 0; uint32_t one = 1; uint32_t mid = 12345678; uint32_t maxU32 = std::numeric_limits::max(); - I65 iZero(zero); + IU64 iZero(zero); EXPECT_EQ(iZero.value, 0u); EXPECT_FALSE(iZero.negative); - I65 iOne(one); + IU64 iOne(one); EXPECT_EQ(iOne.value, 1u); EXPECT_FALSE(iOne.negative); - I65 iMid(mid); + IU64 iMid(mid); EXPECT_EQ(iMid.value, mid); EXPECT_FALSE(iMid.negative); - I65 iMax(maxU32); + IU64 iMax(maxU32); EXPECT_EQ(iMax.value, uint64_t(maxU32)); EXPECT_FALSE(iMax.negative); } -TEST(I65Test, ConstructFromSigned32) { +TEST(IU64Test, ConstructFromSigned32) { int32_t zero = 0; int32_t one = 1; int32_t maxI32 = std::numeric_limits::max(); @@ -62,32 +62,32 @@ TEST(I65Test, ConstructFromSigned32) { int32_t negMid = -12345678; int32_t minI32 = std::numeric_limits::min(); - I65 iZero(zero); + IU64 iZero(zero); EXPECT_EQ(iZero.value, 0u); EXPECT_FALSE(iZero.negative); - I65 iOne(one); + IU64 iOne(one); EXPECT_EQ(iOne.value, 1u); EXPECT_FALSE(iOne.negative); - I65 iMax(maxI32); + IU64 iMax(maxI32); EXPECT_EQ(iMax.value, uint64_t(maxI32)); EXPECT_FALSE(iMax.negative); - I65 iNegOne(negOne); + IU64 iNegOne(negOne); EXPECT_EQ(iNegOne.value, 1u); EXPECT_TRUE(iNegOne.negative); - I65 iNegMid(negMid); + IU64 iNegMid(negMid); EXPECT_EQ(iNegMid.value, 12345678u); EXPECT_TRUE(iNegMid.negative); - I65 iMin(minI32); + IU64 iMin(minI32); EXPECT_EQ(iMin.value, 2147483648ULL); EXPECT_TRUE(iMin.negative); } -TEST(I65Test, ConstructFromUnsigned64) { +TEST(IU64Test, ConstructFromUnsigned64) { uint64_t zero = 0; uint64_t one = 1; uint64_t maxU32 = std::numeric_limits::max(); @@ -95,32 +95,32 @@ TEST(I65Test, ConstructFromUnsigned64) { uint64_t highBitOnly = uint64_t(1) << 63; uint64_t maxU64 = std::numeric_limits::max(); - I65 iZero(zero); + IU64 iZero(zero); EXPECT_EQ(iZero.value, 0u); EXPECT_FALSE(iZero.negative); - I65 iOne(one); + IU64 iOne(one); EXPECT_EQ(iOne.value, 1u); EXPECT_FALSE(iOne.negative); - I65 iMaxU32(maxU32); + IU64 iMaxU32(maxU32); EXPECT_EQ(iMaxU32.value, maxU32); EXPECT_FALSE(iMaxU32.negative); - I65 iMaxI64(maxI64); + IU64 iMaxI64(maxI64); EXPECT_EQ(iMaxI64.value, maxI64); EXPECT_FALSE(iMaxI64.negative); - I65 iHighBit(highBitOnly); + IU64 iHighBit(highBitOnly); EXPECT_EQ(iHighBit.value, highBitOnly); EXPECT_FALSE(iHighBit.negative); - I65 iMaxU64(maxU64); + IU64 iMaxU64(maxU64); EXPECT_EQ(iMaxU64.value, maxU64); EXPECT_FALSE(iMaxU64.negative); } -TEST(I65Test, ConstructFromSigned64) { +TEST(IU64Test, ConstructFromSigned64) { int64_t zero = 0; int64_t one = 1; int64_t maxI64 = std::numeric_limits::max(); @@ -128,71 +128,71 @@ TEST(I65Test, ConstructFromSigned64) { int64_t minI64 = std::numeric_limits::min(); int64_t minI64PlusOne = std::numeric_limits::min() + 1; - I65 iZero(zero); + IU64 iZero(zero); EXPECT_EQ(iZero.value, 0u); EXPECT_FALSE(iZero.negative); - I65 iOne(one); + IU64 iOne(one); EXPECT_EQ(iOne.value, 1u); EXPECT_FALSE(iOne.negative); - I65 iMax(maxI64); + IU64 iMax(maxI64); EXPECT_EQ(iMax.value, uint64_t(maxI64)); EXPECT_FALSE(iMax.negative); - I65 iNegOne(negOne); + IU64 iNegOne(negOne); EXPECT_EQ(iNegOne.value, 1u); EXPECT_TRUE(iNegOne.negative); - I65 iMin(minI64); + IU64 iMin(minI64); EXPECT_EQ(iMin.value, uint64_t(1) << 63); EXPECT_TRUE(iMin.negative); - I65 iMinPlusOne(minI64PlusOne); + IU64 iMinPlusOne(minI64PlusOne); EXPECT_EQ(iMinPlusOne.value, uint64_t(std::numeric_limits::max())); EXPECT_TRUE(iMinPlusOne.negative); } -TEST(I65Test, EqualityAndInequality) { - EXPECT_EQ(I65(int32_t(0)), I65(uint32_t(0))); - EXPECT_EQ(I65(int32_t(0)), I65(int64_t(0))); - EXPECT_EQ(I65(int32_t(0)), I65(uint64_t(0))); +TEST(IU64Test, EqualityAndInequality) { + EXPECT_EQ(IU64(int32_t(0)), IU64(uint32_t(0))); + EXPECT_EQ(IU64(int32_t(0)), IU64(int64_t(0))); + EXPECT_EQ(IU64(int32_t(0)), IU64(uint64_t(0))); - EXPECT_EQ(I65(int32_t(42)), I65(uint32_t(42))); - EXPECT_EQ(I65(int32_t(42)), I65(int64_t(42))); - EXPECT_EQ(I65(int32_t(42)), I65(uint64_t(42))); + EXPECT_EQ(IU64(int32_t(42)), IU64(uint32_t(42))); + EXPECT_EQ(IU64(int32_t(42)), IU64(int64_t(42))); + EXPECT_EQ(IU64(int32_t(42)), IU64(uint64_t(42))); - EXPECT_EQ(I65(int32_t(-42)), I65(int64_t(-42))); - EXPECT_EQ(I65(std::numeric_limits::min()), - I65(int64_t(std::numeric_limits::min()))); + EXPECT_EQ(IU64(int32_t(-42)), IU64(int64_t(-42))); + EXPECT_EQ(IU64(std::numeric_limits::min()), + IU64(int64_t(std::numeric_limits::min()))); - EXPECT_NE(I65(int32_t(1)), I65(int32_t(-1))); - EXPECT_NE(I65(uint64_t(0xffffffffffffffffULL)), I65(int64_t(-1))); - EXPECT_NE(I65(std::numeric_limits::min()), I65(uint64_t(1) << 63)); + EXPECT_NE(IU64(int32_t(1)), IU64(int32_t(-1))); + EXPECT_NE(IU64(uint64_t(0xffffffffffffffffULL)), IU64(int64_t(-1))); + EXPECT_NE(IU64(std::numeric_limits::min()), IU64(uint64_t(1) << 63)); } -TEST(I65Test, TotalOrdering) { - std::vector sortedValues = { - I65(std::numeric_limits::min()), - I65(std::numeric_limits::min() + 1), - I65(int64_t(-0x100000000LL)), - I65(std::numeric_limits::min()), - I65(int32_t(-12345)), - I65(int64_t(-2)), - I65(int64_t(-1)), - I65(0), - I65(1), - I65(2), - I65(int32_t(12345)), - I65(std::numeric_limits::max()), - I65(uint64_t(std::numeric_limits::max()) + 1), - I65(std::numeric_limits::max()), - I65(uint64_t(std::numeric_limits::max()) + 1), - I65(std::numeric_limits::max() - 1), - I65(std::numeric_limits::max()), - I65(uint64_t(std::numeric_limits::max()) + 1), - I65(std::numeric_limits::max() - 1), - I65(std::numeric_limits::max()), +TEST(IU64Test, TotalOrdering) { + std::vector sortedValues = { + IU64(std::numeric_limits::min()), + IU64(std::numeric_limits::min() + 1), + IU64(int64_t(-0x100000000LL)), + IU64(std::numeric_limits::min()), + IU64(int32_t(-12345)), + IU64(int64_t(-2)), + IU64(int64_t(-1)), + IU64(0), + IU64(1), + IU64(2), + IU64(int32_t(12345)), + IU64(std::numeric_limits::max()), + IU64(uint64_t(std::numeric_limits::max()) + 1), + IU64(std::numeric_limits::max()), + IU64(uint64_t(std::numeric_limits::max()) + 1), + IU64(std::numeric_limits::max() - 1), + IU64(std::numeric_limits::max()), + IU64(uint64_t(std::numeric_limits::max()) + 1), + IU64(std::numeric_limits::max() - 1), + IU64(std::numeric_limits::max()), }; for (size_t i = 0; i < sortedValues.size(); ++i) { @@ -228,31 +228,31 @@ TEST(I65Test, TotalOrdering) { } } -TEST(I65Test, NumericLimits) { - EXPECT_TRUE(std::numeric_limits::is_specialized); - EXPECT_TRUE(std::numeric_limits::is_signed); - EXPECT_TRUE(std::numeric_limits::is_integer); - - EXPECT_EQ(std::numeric_limits::min(), - I65(std::numeric_limits::min())); - EXPECT_EQ(std::numeric_limits::lowest(), - I65(std::numeric_limits::min())); - EXPECT_EQ(std::numeric_limits::max(), - I65(std::numeric_limits::max())); +TEST(IU64Test, NumericLimits) { + EXPECT_TRUE(std::numeric_limits::is_specialized); + EXPECT_TRUE(std::numeric_limits::is_signed); + EXPECT_TRUE(std::numeric_limits::is_integer); + + EXPECT_EQ(std::numeric_limits::min(), + IU64(std::numeric_limits::min())); + EXPECT_EQ(std::numeric_limits::lowest(), + IU64(std::numeric_limits::min())); + EXPECT_EQ(std::numeric_limits::max(), + IU64(std::numeric_limits::max())); } -TEST(I65Test, StreamOutput) { - auto toString = [](const I65& x) { +TEST(IU64Test, StreamOutput) { + auto toString = [](const IU64& x) { std::ostringstream ss; ss << x; return ss.str(); }; - EXPECT_EQ(toString(I65(0)), "0"); - EXPECT_EQ(toString(I65(42)), "42"); - EXPECT_EQ(toString(I65(-42)), "-42"); - EXPECT_EQ(toString(I65(std::numeric_limits::min())), + EXPECT_EQ(toString(IU64(0)), "0"); + EXPECT_EQ(toString(IU64(42)), "42"); + EXPECT_EQ(toString(IU64(-42)), "-42"); + EXPECT_EQ(toString(IU64(std::numeric_limits::min())), "-9223372036854775808"); - EXPECT_EQ(toString(I65(std::numeric_limits::max())), + EXPECT_EQ(toString(IU64(std::numeric_limits::max())), "18446744073709551615"); } diff --git a/test/gtest/span.cpp b/test/gtest/span.cpp index 02613dde08f..72ff18d2704 100644 --- a/test/gtest/span.cpp +++ b/test/gtest/span.cpp @@ -18,7 +18,7 @@ #include #include -#include "support/i65.h" +#include "support/iu64.h" #include "support/span.h" #include "gtest/gtest.h" @@ -185,148 +185,148 @@ TEST(SpanTest, StreamOutput) { } // ============================================================================ -// Span tests (corner cases, sign mixing, large range) +// Span tests (corner cases, sign mixing, large range) // ============================================================================ -TEST(SpanI65Test, FullAndLimits) { - EXPECT_EQ(Span::Min, I65(std::numeric_limits::min())); - EXPECT_EQ(Span::Max, I65(std::numeric_limits::max())); +TEST(SpanIU64Test, FullAndLimits) { + EXPECT_EQ(Span::Min, IU64(std::numeric_limits::min())); + EXPECT_EQ(Span::Max, IU64(std::numeric_limits::max())); - Span full = Span::full(); + Span full = Span::full(); EXPECT_TRUE(full.isFull()); EXPECT_FALSE(full.isEmpty()); - EXPECT_EQ(full.min, I65(std::numeric_limits::min())); - EXPECT_EQ(full.max, I65(std::numeric_limits::max())); + EXPECT_EQ(full.min, IU64(std::numeric_limits::min())); + EXPECT_EQ(full.max, IU64(std::numeric_limits::max())); // Default constructed span is full - Span def; + Span def; EXPECT_TRUE(def.isFull()); EXPECT_FALSE(def.isEmpty()); EXPECT_EQ(def, full); } -TEST(SpanI65Test, Empty) { - Span empty = Span::empty(); +TEST(SpanIU64Test, Empty) { + Span empty = Span::empty(); EXPECT_TRUE(empty.isEmpty()); EXPECT_FALSE(empty.isFull()); - Span empty2{I65(100), I65(-100)}; + Span empty2{IU64(100), IU64(-100)}; EXPECT_TRUE(empty2.isEmpty()); EXPECT_FALSE(empty2.isFull()); EXPECT_EQ(empty, empty2); - Span empty3{I65(uint64_t(1)), I65(int64_t(-1))}; + Span empty3{IU64(uint64_t(1)), IU64(int64_t(-1))}; EXPECT_TRUE(empty3.isEmpty()); EXPECT_EQ(empty, empty3); } -TEST(SpanI65Test, SingletonsAtExtremes) { +TEST(SpanIU64Test, SingletonsAtExtremes) { // Min int64 singleton - Span minI64{I65(std::numeric_limits::min()), - I65(std::numeric_limits::min())}; + Span minI64{IU64(std::numeric_limits::min()), + IU64(std::numeric_limits::min())}; EXPECT_FALSE(minI64.isEmpty()); EXPECT_FALSE(minI64.isFull()); - EXPECT_EQ(minI64.min, I65(std::numeric_limits::min())); - EXPECT_EQ(minI64.max, I65(std::numeric_limits::min())); + EXPECT_EQ(minI64.min, IU64(std::numeric_limits::min())); + EXPECT_EQ(minI64.max, IU64(std::numeric_limits::min())); // -1 singleton - Span negOne{I65(-1), I65(-1)}; + Span negOne{IU64(-1), IU64(-1)}; EXPECT_FALSE(negOne.isEmpty()); // 0 singleton - Span zero{I65(0), I65(0)}; + Span zero{IU64(0), IU64(0)}; EXPECT_FALSE(zero.isEmpty()); // 1 singleton - Span one{I65(1), I65(1)}; + Span one{IU64(1), IU64(1)}; EXPECT_FALSE(one.isEmpty()); // Max int64 singleton - Span maxI64{I65(std::numeric_limits::max()), - I65(std::numeric_limits::max())}; + Span maxI64{IU64(std::numeric_limits::max()), + IU64(std::numeric_limits::max())}; EXPECT_FALSE(maxI64.isEmpty()); // 2^63 singleton (above int64_t max, into uint64_t territory) - Span highBit{I65(uint64_t(1) << 63), I65(uint64_t(1) << 63)}; + Span highBit{IU64(uint64_t(1) << 63), IU64(uint64_t(1) << 63)}; EXPECT_FALSE(highBit.isEmpty()); // Max uint64 singleton - Span maxU64{I65(std::numeric_limits::max()), - I65(std::numeric_limits::max())}; + Span maxU64{IU64(std::numeric_limits::max()), + IU64(std::numeric_limits::max())}; EXPECT_FALSE(maxU64.isEmpty()); } -TEST(SpanI65Test, CrossingZero) { - Span span{I65(-10), I65(10)}; +TEST(SpanIU64Test, CrossingZero) { + Span span{IU64(-10), IU64(10)}; EXPECT_FALSE(span.isEmpty()); EXPECT_FALSE(span.isFull()); // Contains points inside - EXPECT_TRUE(span.contains(Span(I65(-10), I65(-10)))); - EXPECT_TRUE(span.contains(Span(I65(-5), I65(5)))); - EXPECT_TRUE(span.contains(Span(I65(0), I65(0)))); - EXPECT_TRUE(span.contains(Span(I65(10), I65(10)))); + EXPECT_TRUE(span.contains(Span(IU64(-10), IU64(-10)))); + EXPECT_TRUE(span.contains(Span(IU64(-5), IU64(5)))); + EXPECT_TRUE(span.contains(Span(IU64(0), IU64(0)))); + EXPECT_TRUE(span.contains(Span(IU64(10), IU64(10)))); // Does not contain points outside - EXPECT_FALSE(span.contains(Span(I65(-11), I65(-11)))); - EXPECT_FALSE(span.contains(Span(I65(11), I65(11)))); - EXPECT_FALSE(span.contains(Span(I65(-15), I65(5)))); - EXPECT_FALSE(span.contains(Span(I65(-5), I65(15)))); + EXPECT_FALSE(span.contains(Span(IU64(-11), IU64(-11)))); + EXPECT_FALSE(span.contains(Span(IU64(11), IU64(11)))); + EXPECT_FALSE(span.contains(Span(IU64(-15), IU64(5)))); + EXPECT_FALSE(span.contains(Span(IU64(-5), IU64(15)))); } -TEST(SpanI65Test, NegativeAndPositiveIntersections) { - Span neg{I65(-100), I65(-10)}; - Span pos{I65(10), I65(100)}; +TEST(SpanIU64Test, NegativeAndPositiveIntersections) { + Span neg{IU64(-100), IU64(-10)}; + Span pos{IU64(10), IU64(100)}; EXPECT_FALSE(neg.hasOverlap(pos)); EXPECT_FALSE(pos.hasOverlap(neg)); EXPECT_TRUE(neg.intersection(pos).isEmpty()); EXPECT_TRUE(pos.intersection(neg).isEmpty()); - Span touchNegZero{I65(-10), I65(0)}; - Span touchZeroPos{I65(0), I65(10)}; + Span touchNegZero{IU64(-10), IU64(0)}; + Span touchZeroPos{IU64(0), IU64(10)}; EXPECT_TRUE(touchNegZero.hasOverlap(touchZeroPos)); - EXPECT_EQ(touchNegZero.intersection(touchZeroPos), Span(I65(0), I65(0))); + EXPECT_EQ(touchNegZero.intersection(touchZeroPos), Span(IU64(0), IU64(0))); - Span overlap{I65(-50), I65(50)}; - EXPECT_EQ(neg.intersection(overlap), Span(I65(-50), I65(-10))); - EXPECT_EQ(pos.intersection(overlap), Span(I65(10), I65(50))); + Span overlap{IU64(-50), IU64(50)}; + EXPECT_EQ(neg.intersection(overlap), Span(IU64(-50), IU64(-10))); + EXPECT_EQ(pos.intersection(overlap), Span(IU64(10), IU64(50))); } -TEST(SpanI65Test, SignedUnsignedBoundary) { +TEST(SpanIU64Test, SignedUnsignedBoundary) { // Test around INT64_MAX and 2^63 int64_t maxI64 = std::numeric_limits::max(); uint64_t highBit = uint64_t(maxI64) + 1; // 0x8000000000000000ULL - Span s1(I65(maxI64 - 100), I65(highBit + 50)); - Span s2(I65(highBit), I65(highBit + 100)); + Span s1(IU64(maxI64 - 100), IU64(highBit + 50)); + Span s2(IU64(highBit), IU64(highBit + 100)); EXPECT_TRUE(s1.hasOverlap(s2)); - EXPECT_EQ(s1.intersection(s2), Span(I65(highBit), I65(highBit + 50))); + EXPECT_EQ(s1.intersection(s2), Span(IU64(highBit), IU64(highBit + 50))); // Disjoint near 2^63 boundary - Span s3{I65(maxI64 - 200), I65(maxI64)}; - Span s4{I65(highBit + 1), I65(highBit + 100)}; + Span s3{IU64(maxI64 - 200), IU64(maxI64)}; + Span s4{IU64(highBit + 1), IU64(highBit + 100)}; EXPECT_FALSE(s3.hasOverlap(s4)); EXPECT_TRUE(s3.intersection(s4).isEmpty()); // Adjacent touching at 2^63 - Span s5{I65(maxI64), I65(highBit)}; - Span s6{I65(highBit), I65(highBit + 10)}; + Span s5{IU64(maxI64), IU64(highBit)}; + Span s6{IU64(highBit), IU64(highBit + 10)}; EXPECT_TRUE(s5.hasOverlap(s6)); - EXPECT_EQ(s5.intersection(s6), Span(I65(highBit), I65(highBit))); + EXPECT_EQ(s5.intersection(s6), Span(IU64(highBit), IU64(highBit))); } -TEST(SpanI65Test, ExtremeBoundaries) { - Span minPart(I65(std::numeric_limits::min()), - I65(std::numeric_limits::min() + 100)); - Span maxPart(I65(std::numeric_limits::max() - 100), - I65(std::numeric_limits::max())); +TEST(SpanIU64Test, ExtremeBoundaries) { + Span minPart(IU64(std::numeric_limits::min()), + IU64(std::numeric_limits::min() + 100)); + Span maxPart(IU64(std::numeric_limits::max() - 100), + IU64(std::numeric_limits::max())); EXPECT_FALSE(minPart.hasOverlap(maxPart)); EXPECT_TRUE(minPart.intersection(maxPart).isEmpty()); - Span full = Span::full(); + Span full = Span::full(); EXPECT_TRUE(full.contains(minPart)); EXPECT_TRUE(full.contains(maxPart)); EXPECT_EQ(full.intersection(minPart), minPart); @@ -334,8 +334,8 @@ TEST(SpanI65Test, ExtremeBoundaries) { EXPECT_TRUE(full.hasOverlap(minPart)); EXPECT_TRUE(full.hasOverlap(maxPart)); - Span allNeg(I65(std::numeric_limits::min()), I65(-1)); - Span allNonNeg(I65(0), I65(std::numeric_limits::max())); + Span allNeg(IU64(std::numeric_limits::min()), IU64(-1)); + Span allNonNeg(IU64(0), IU64(std::numeric_limits::max())); EXPECT_FALSE(allNeg.hasOverlap(allNonNeg)); EXPECT_TRUE(allNeg.intersection(allNonNeg).isEmpty()); @@ -343,15 +343,15 @@ TEST(SpanI65Test, ExtremeBoundaries) { EXPECT_TRUE(full.contains(allNonNeg)); } -TEST(SpanI65Test, SetAndMutate) { - Span s; +TEST(SpanIU64Test, SetAndMutate) { + Span s; EXPECT_TRUE(s.isFull()); - s.set(I65(-12345)); + s.set(IU64(-12345)); EXPECT_FALSE(s.isFull()); EXPECT_FALSE(s.isEmpty()); - EXPECT_EQ(s.min, I65(-12345)); - EXPECT_EQ(s.max, I65(-12345)); + EXPECT_EQ(s.min, IU64(-12345)); + EXPECT_EQ(s.max, IU64(-12345)); s.setEmpty(); EXPECT_TRUE(s.isEmpty()); From c317fb8d2ad25e73bb5f5b8e45e1a4b6dd1cc75d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 Aug 2026 11:05:18 -0700 Subject: [PATCH 10/32] format --- src/support/iu64.h | 8 ++++++-- test/gtest/span.cpp | 13 +++++++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/support/iu64.h b/src/support/iu64.h index a6b0bbb980a..b91666560c8 100644 --- a/src/support/iu64.h +++ b/src/support/iu64.h @@ -93,8 +93,12 @@ struct IU64 { constexpr bool operator<=(const IU64& other) const { return *this < other || *this == other; } - constexpr bool operator>(const IU64& other) const { return !(*this <= other); } - constexpr bool operator>=(const IU64& other) const { return !(*this < other); } + constexpr bool operator>(const IU64& other) const { + return !(*this <= other); + } + constexpr bool operator>=(const IU64& other) const { + return !(*this < other); + } }; inline std::ostream& operator<<(std::ostream& os, const IU64& x) { diff --git a/test/gtest/span.cpp b/test/gtest/span.cpp index 72ff18d2704..f05fd80e2ff 100644 --- a/test/gtest/span.cpp +++ b/test/gtest/span.cpp @@ -223,7 +223,7 @@ TEST(SpanIU64Test, Empty) { TEST(SpanIU64Test, SingletonsAtExtremes) { // Min int64 singleton Span minI64{IU64(std::numeric_limits::min()), - IU64(std::numeric_limits::min())}; + IU64(std::numeric_limits::min())}; EXPECT_FALSE(minI64.isEmpty()); EXPECT_FALSE(minI64.isFull()); EXPECT_EQ(minI64.min, IU64(std::numeric_limits::min())); @@ -243,7 +243,7 @@ TEST(SpanIU64Test, SingletonsAtExtremes) { // Max int64 singleton Span maxI64{IU64(std::numeric_limits::max()), - IU64(std::numeric_limits::max())}; + IU64(std::numeric_limits::max())}; EXPECT_FALSE(maxI64.isEmpty()); // 2^63 singleton (above int64_t max, into uint64_t territory) @@ -252,7 +252,7 @@ TEST(SpanIU64Test, SingletonsAtExtremes) { // Max uint64 singleton Span maxU64{IU64(std::numeric_limits::max()), - IU64(std::numeric_limits::max())}; + IU64(std::numeric_limits::max())}; EXPECT_FALSE(maxU64.isEmpty()); } @@ -286,7 +286,8 @@ TEST(SpanIU64Test, NegativeAndPositiveIntersections) { Span touchNegZero{IU64(-10), IU64(0)}; Span touchZeroPos{IU64(0), IU64(10)}; EXPECT_TRUE(touchNegZero.hasOverlap(touchZeroPos)); - EXPECT_EQ(touchNegZero.intersection(touchZeroPos), Span(IU64(0), IU64(0))); + EXPECT_EQ(touchNegZero.intersection(touchZeroPos), + Span(IU64(0), IU64(0))); Span overlap{IU64(-50), IU64(50)}; EXPECT_EQ(neg.intersection(overlap), Span(IU64(-50), IU64(-10))); @@ -319,9 +320,9 @@ TEST(SpanIU64Test, SignedUnsignedBoundary) { TEST(SpanIU64Test, ExtremeBoundaries) { Span minPart(IU64(std::numeric_limits::min()), - IU64(std::numeric_limits::min() + 100)); + IU64(std::numeric_limits::min() + 100)); Span maxPart(IU64(std::numeric_limits::max() - 100), - IU64(std::numeric_limits::max())); + IU64(std::numeric_limits::max())); EXPECT_FALSE(minPart.hasOverlap(maxPart)); EXPECT_TRUE(minPart.intersection(maxPart).isEmpty()); From 8763274b628a40218196260e266d933dcd4b125c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 Aug 2026 11:06:27 -0700 Subject: [PATCH 11/32] builds --- src/support/span.h | 2 -- test/gtest/CMakeLists.txt | 2 +- test/gtest/iu64.cpp | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/support/span.h b/src/support/span.h index 3b8ea83a8f7..dbd301089cd 100644 --- a/src/support/span.h +++ b/src/support/span.h @@ -22,8 +22,6 @@ #include #include -#include "support/i65.h" - namespace wasm { // A span of values. diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index 13b304b5671..925880bd40a 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -17,7 +17,7 @@ set(unittest_SOURCES disjoint_sets.cpp graph.cpp int128.cpp - i65.cpp + iu64.cpp leaves.cpp glbs.cpp inplace_vector.cpp diff --git a/test/gtest/iu64.cpp b/test/gtest/iu64.cpp index 0c38fcbe799..1f812e9f8a3 100644 --- a/test/gtest/iu64.cpp +++ b/test/gtest/iu64.cpp @@ -19,7 +19,7 @@ #include #include -#include "support/i65.h" +#include "support/iu64.h" #include "gtest/gtest.h" using namespace wasm; From 0e493293722733d8cd79eeeff2ab888475547d99 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 Aug 2026 11:23:32 -0700 Subject: [PATCH 12/32] work --- src/ir/constraint.cpp | 68 +++++++++++++++++++++++++++++++++++++++++++ src/ir/constraint.h | 6 ++++ 2 files changed, 74 insertions(+) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index a1516280deb..9851b46c4d2 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -22,6 +22,74 @@ namespace wasm::constraint { +std::optional> Constraint::makeSpan() const { + auto* c = std::get_if(&term); + if (!c) { + // Not comparing to a constant, so cannot be a constant span. + return {}; + } + + switch (op) { + case Eq: { + auto x = c->getUnsigned(); + if (x <= std::numeric_limits::max()) { + // This is in the range of both signed and unsigned values, so there is + // no ambiguity. That is, we cannot convert the bit pattern + // 0xffffffffffffffff into a Span, as it might be either uint64_t(-1) + // or actually negative (but a bit pattern like 0x0000000000000001 is + // always fine as it can only ever be "1"). + return Span{x, x}; + } + break; + } + + case LtS: + if (c->getInteger() == std::numeric_limits::min()) { + // Less than the lowest possible number is an empty span. + return Span::empty(); + } else { + return Span{std::numeric_limits::min(), c->getInteger() - 1}; + } + break; + case LtU: + if (c->getInteger() == 0) { + // Less than the lowest possible number is an empty span. + return Span::empty(); + } else { + return Span{0, c->getUnsigned() - 1}; + } + break; + case LeS: + return Span{std::numeric_limits::min(), c->getInteger()}; + case LeU: + return Span{0, c->getUnsigned()}; + + case GtS: + if (c->getInteger() == std::numeric_limits::max()) { + // Greater than the highest possible number is an empty span. + return Span::empty(); + } else { + return Span{c->getInteger() + 1, std::numeric_limits::max}; + } + break; + case GtU: + if (c->getInteger() == 0) { + // Greater than the highest possible number is an empty span. + return Span::empty(); + } else { + return Span{0, c->getUnsigned() - 1}; + } + break; + case GeS: + return Span{std::numeric_limits::min(), c->getInteger()}; + case GeU: + return Span{0, c->getUnsigned()}; + + default: {} + } + + return {}; +} namespace { Result TrueFalse(bool x) { return x ? True : False; } diff --git a/src/ir/constraint.h b/src/ir/constraint.h index c7814effea5..fb37d20633e 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -27,6 +27,8 @@ #include "ir/abstract.h" #include "support/inplace_vector.h" +#include "support/iu64.h" +#include "support/span.h" #include "support/utilities.h" #include "wasm.h" @@ -63,6 +65,10 @@ struct Constraint { Constraint negate() const { return Constraint{Abstract::negateRelational(op), term}; } + + // Convert the constraint into a constant span, if possible. For example, + // "< 100 (unsigned)" turns into the span [0, 100]. + std::optional> makeSpan() const; }; // We limit constraints to a low number to ensure good performance even with From 4bfa03228de70e08d749824b52b3508a39bb1da7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 Aug 2026 11:25:32 -0700 Subject: [PATCH 13/32] work --- 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 9851b46c4d2..331eb15bf77 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -73,23 +73,24 @@ std::optional> Constraint::makeSpan() const { } break; case GtU: - if (c->getInteger() == 0) { + if (c->getInteger() == std::numeric_limits::max()) { // Greater than the highest possible number is an empty span. return Span::empty(); } else { - return Span{0, c->getUnsigned() - 1}; + return Span{c->getUnsigned() + 1, std::numeric_limits::max}; } break; case GeS: - return Span{std::numeric_limits::min(), c->getInteger()}; + return Span{c->getInteger(), std::numeric_limits::max()}; case GeU: - return Span{0, c->getUnsigned()}; + return Span{c->getUnsigned(), std::numeric_limits::max()}; default: {} } return {}; } + namespace { Result TrueFalse(bool x) { return x ? True : False; } From af0bc975e95467bd06899e0b1bf802a7b5c3d895 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 Aug 2026 11:25:45 -0700 Subject: [PATCH 14/32] work --- src/ir/constraint.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 331eb15bf77..e456adb4400 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -48,7 +48,8 @@ std::optional> Constraint::makeSpan() const { // Less than the lowest possible number is an empty span. return Span::empty(); } else { - return Span{std::numeric_limits::min(), c->getInteger() - 1}; + return Span{std::numeric_limits::min(), + c->getInteger() - 1}; } break; case LtU: @@ -69,7 +70,8 @@ std::optional> Constraint::makeSpan() const { // Greater than the highest possible number is an empty span. return Span::empty(); } else { - return Span{c->getInteger() + 1, std::numeric_limits::max}; + return Span{c->getInteger() + 1, + std::numeric_limits::max}; } break; case GtU: @@ -77,7 +79,8 @@ std::optional> Constraint::makeSpan() const { // Greater than the highest possible number is an empty span. return Span::empty(); } else { - return Span{c->getUnsigned() + 1, std::numeric_limits::max}; + return Span{c->getUnsigned() + 1, + std::numeric_limits::max}; } break; case GeS: @@ -85,7 +88,8 @@ std::optional> Constraint::makeSpan() const { case GeU: return Span{c->getUnsigned(), std::numeric_limits::max()}; - default: {} + default: { + } } return {}; From 32cc04b0f77774d3c2b563d1e88e4cecfb4fd4ef Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 Aug 2026 11:32:05 -0700 Subject: [PATCH 15/32] work --- src/ir/constraint.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index e456adb4400..0208c7f09b6 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -151,6 +151,22 @@ Result provesConstantPair(Abstract::Op aOp, } } + // If we can represent both as spans, we can calculate that way. + if (auto aSpan = Constraint{aOp, aConstant}.getSpan()) { + if (auto bSpan = Constraint{bOp, bConstant}.getSpan()) { + if (bSpan->contains(*aSpan)) { + // b's values contains a's, e.g., b = { 0 < x < 10 } and + // a = { 3 < x < 7 }, so a => b. + return True; + } + if (!bSpan->hasOverlap(*aSpan)) { + // There is no overlap at all, e.g., { 0 < x < 10 } vs { 20 < x < 30 }, + // both cannot be true and each proves the other false. + return False; + } + } + } + if (!recursing) { // The flipped operation may tell us something: y ==> !x implies // x ==> y is false (because if not, then x would prove y, and y would From 3b59e5dc821b5cc50dd2506894dc4add13e7826d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 Aug 2026 11:34:35 -0700 Subject: [PATCH 16/32] work --- src/ir/constraint.cpp | 10 ++++++---- src/ir/constraint.h | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 0208c7f09b6..ee4847de6fd 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -22,7 +22,9 @@ namespace wasm::constraint { -std::optional> Constraint::makeSpan() const { +std::optional> Constraint::getSpan() const { + using namespace Abstract; + auto* c = std::get_if(&term); if (!c) { // Not comparing to a constant, so cannot be a constant span. @@ -71,16 +73,16 @@ std::optional> Constraint::makeSpan() const { return Span::empty(); } else { return Span{c->getInteger() + 1, - std::numeric_limits::max}; + int64_t(std::numeric_limits::max)}; } break; case GtU: - if (c->getInteger() == std::numeric_limits::max()) { + if (c->getUnsigned() == std::numeric_limits::max()) { // Greater than the highest possible number is an empty span. return Span::empty(); } else { return Span{c->getUnsigned() + 1, - std::numeric_limits::max}; + uint64_t(std::numeric_limits::max)}; } break; case GeS: diff --git a/src/ir/constraint.h b/src/ir/constraint.h index fb37d20633e..1bfea0ad8ef 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -68,7 +68,7 @@ struct Constraint { // Convert the constraint into a constant span, if possible. For example, // "< 100 (unsigned)" turns into the span [0, 100]. - std::optional> makeSpan() const; + std::optional> getSpan() const; }; // We limit constraints to a low number to ensure good performance even with From 69a97dea1dae5cccd7e516afa047404148b83a9b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 Aug 2026 11:49:16 -0700 Subject: [PATCH 17/32] testt --- src/ir/constraint.cpp | 4 +- test/gtest/constraint.cpp | 203 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 199 insertions(+), 8 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index ee4847de6fd..c8a2a77aaa5 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -73,7 +73,7 @@ std::optional> Constraint::getSpan() const { return Span::empty(); } else { return Span{c->getInteger() + 1, - int64_t(std::numeric_limits::max)}; + std::numeric_limits::max()}; } break; case GtU: @@ -82,7 +82,7 @@ std::optional> Constraint::getSpan() const { return Span::empty(); } else { return Span{c->getUnsigned() + 1, - uint64_t(std::numeric_limits::max)}; + std::numeric_limits::max()}; } break; case GeS: diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 36a52220125..ea58167e004 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -1,3 +1,5 @@ +#include + #include "ir/constraint.h" #include "ir/abstract.h" #include "gtest/gtest.h" @@ -275,8 +277,14 @@ TEST(ConstraintTest, TestOrInequality) { auto empty = AndedConstraintSet::makeProvesNothing(); checkOr(eq5, ges7, empty); - // x > 5 || x >= 6 => x > 5 - checkOr(gts5, ges6, gts5); + // x > 5 and x >= 6 are equivalent, so ORing them does not change either. + auto ored1 = gts5; + ored1.approximateOr(ges6); + EXPECT_EQ(ored1, gts5); + + auto ored2 = ges6; + ored2.approximateOr(gts5); + EXPECT_EQ(ored2, ges6); // x > 5 || x >= 5 => x >= 5 checkOr(gts5, ges5, ges5); @@ -294,8 +302,8 @@ TEST(ConstraintTest, TestOrInequality) { // x > signed_max || x >= (signed_max + 1 === signed_min) != x > signed_max AndedConstraintSet gtsMax{ {GtS, {Literal(std::numeric_limits::max())}}}; - // TODO: x > signed_max is always false, so this could return a contradiction - checkOr(gtsMax, gesMin, empty); + // x > signed_max implies x >= signed_min, so ORing them yields x >= signed_min. XXX fix! + checkOr(gtsMax, gesMin, gesMin); } TEST(ConstraintTest, TestOrLoop) { @@ -484,9 +492,16 @@ TEST(ConstraintTest, TestAndLoop) { // inputs). checkAnd(le5, lt5U, AndedConstraintSet{le5[0], lt5U[0]}); - // Different constants do not optimize, but could TODO + // Different constants optimize when one implies the other (x <= 5 and x < 6 + // are equivalent). AndedConstraintSet lt6{{LtS, {Literal(int32_t(6))}}}; - checkAnd(le5, lt6, AndedConstraintSet{le5[0], lt6[0]}); + auto anded1 = le5; + anded1.approximateAnd(lt6[0]); + EXPECT_EQ(anded1, le5); + + auto anded2 = lt6; + anded2.approximateAnd(le5[0]); + EXPECT_EQ(anded2, lt6); // A non-constant. // x <= y && x < y => x < y @@ -660,3 +675,179 @@ TEST(ConstraintTest, TestEqConstraints) { // having $1 > $0 and needing to look $0 up. check(map.get(1), {GtS, {Literal(int32_t(42))}}); } + +TEST(ConstraintTest, GetSpan) { + const IU64 minI64(std::numeric_limits::min()); + const IU64 maxI64(std::numeric_limits::max()); + const IU64 maxU64(std::numeric_limits::max()); + + // Non-literal terms have no constant span. + EXPECT_EQ((Constraint{Eq, {Index(0)}}.getSpan()), std::nullopt); + EXPECT_EQ((Constraint{LtS, {Index(1)}}.getSpan()), std::nullopt); + EXPECT_EQ((Constraint{GeU, {Index(2)}}.getSpan()), std::nullopt); + + // Unsupported operations (e.g. Ne) have no constant span. + EXPECT_EQ((Constraint{Ne, {Literal(int32_t(5))}}.getSpan()), std::nullopt); + EXPECT_EQ((Constraint{Ne, {Literal(int32_t(0))}}.getSpan()), std::nullopt); + + // Eq: only non-negative values up to int32_t max have an unambiguous span. + EXPECT_EQ((Constraint{Eq, {Literal(int32_t(0))}}.getSpan()), + (Span{0, 0})); + EXPECT_EQ((Constraint{Eq, {Literal(int32_t(1))}}.getSpan()), + (Span{1, 1})); + EXPECT_EQ((Constraint{Eq, {Literal(int32_t(42))}}.getSpan()), + (Span{42, 42})); + EXPECT_EQ((Constraint{Eq, {Literal(int64_t(42))}}.getSpan()), + (Span{42, 42})); + EXPECT_EQ( + (Constraint{Eq, {Literal(std::numeric_limits::max())}}.getSpan()), + (Span{uint32_t(std::numeric_limits::max()), + uint32_t(std::numeric_limits::max())})); + + // Eq with negative or large values returns nullopt due to signed/unsigned + // ambiguity. + EXPECT_EQ((Constraint{Eq, {Literal(int32_t(-1))}}.getSpan()), std::nullopt); + EXPECT_EQ((Constraint{Eq, {Literal(int32_t(-42))}}.getSpan()), std::nullopt); + EXPECT_EQ( + (Constraint{Eq, {Literal(std::numeric_limits::min())}}.getSpan()), + std::nullopt); + EXPECT_EQ((Constraint{Eq, {Literal(int64_t(-1))}}.getSpan()), std::nullopt); + EXPECT_EQ( + (Constraint{Eq, {Literal(std::numeric_limits::min())}}.getSpan()), + std::nullopt); + EXPECT_EQ( + (Constraint{Eq, {Literal(int64_t(std::numeric_limits::max()) + 1)}} + .getSpan()), + std::nullopt); + EXPECT_EQ( + (Constraint{Eq, {Literal(std::numeric_limits::max())}}.getSpan()), + std::nullopt); + EXPECT_EQ((Constraint{Eq, {Literal(uint32_t(0x80000000u))}}.getSpan()), + std::nullopt); + EXPECT_EQ( + (Constraint{Eq, {Literal(std::numeric_limits::max())}}.getSpan()), + std::nullopt); + + // LtS: [minI64, C - 1] + EXPECT_EQ((Constraint{LtS, {Literal(int32_t(10))}}.getSpan()), + (Span{minI64, IU64(9)})); + EXPECT_EQ((Constraint{LtS, {Literal(int32_t(0))}}.getSpan()), + (Span{minI64, IU64(-1)})); + EXPECT_EQ((Constraint{LtS, {Literal(int32_t(-5))}}.getSpan()), + (Span{minI64, IU64(-6)})); + EXPECT_EQ((Constraint{LtS, {Literal(int64_t(100))}}.getSpan()), + (Span{minI64, IU64(99)})); + EXPECT_EQ( + (Constraint{LtS, {Literal(std::numeric_limits::max())}}.getSpan()), + (Span{minI64, IU64(std::numeric_limits::max() - 1)})); + // LtS min signed: empty span + auto ltsMin = + Constraint{LtS, {Literal(std::numeric_limits::min())}}.getSpan(); + ASSERT_TRUE(ltsMin.has_value()); + EXPECT_TRUE(ltsMin->isEmpty()); + EXPECT_EQ(ltsMin, Span::empty()); + + // LtU: [0, C - 1] + EXPECT_EQ((Constraint{LtU, {Literal(uint32_t(10))}}.getSpan()), + (Span{IU64(0), IU64(9)})); + EXPECT_EQ((Constraint{LtU, {Literal(uint32_t(1))}}.getSpan()), + (Span{IU64(0), IU64(0)})); + EXPECT_EQ((Constraint{LtU, {Literal(uint64_t(100))}}.getSpan()), + (Span{IU64(0), IU64(99)})); + EXPECT_EQ( + (Constraint{LtU, {Literal(std::numeric_limits::max())}}.getSpan()), + (Span{IU64(0), IU64(std::numeric_limits::max() - 1)})); + // LtU 0: empty span + auto ltuZero32 = Constraint{LtU, {Literal(uint32_t(0))}}.getSpan(); + ASSERT_TRUE(ltuZero32.has_value()); + EXPECT_TRUE(ltuZero32->isEmpty()); + EXPECT_EQ(ltuZero32, Span::empty()); + auto ltuZero64 = Constraint{LtU, {Literal(uint64_t(0))}}.getSpan(); + ASSERT_TRUE(ltuZero64.has_value()); + EXPECT_TRUE(ltuZero64->isEmpty()); + EXPECT_EQ(ltuZero64, Span::empty()); + + // LeS: [minI64, C] + EXPECT_EQ((Constraint{LeS, {Literal(int32_t(10))}}.getSpan()), + (Span{minI64, IU64(10)})); + EXPECT_EQ((Constraint{LeS, {Literal(int32_t(0))}}.getSpan()), + (Span{minI64, IU64(0)})); + EXPECT_EQ((Constraint{LeS, {Literal(int32_t(-5))}}.getSpan()), + (Span{minI64, IU64(-5)})); + EXPECT_EQ( + (Constraint{LeS, {Literal(std::numeric_limits::min())}}.getSpan()), + (Span{minI64, minI64})); + EXPECT_EQ( + (Constraint{LeS, {Literal(std::numeric_limits::max())}}.getSpan()), + (Span{minI64, maxI64})); + + // LeU: [0, C] + EXPECT_EQ((Constraint{LeU, {Literal(uint32_t(0))}}.getSpan()), + (Span{IU64(0), IU64(0)})); + EXPECT_EQ((Constraint{LeU, {Literal(uint32_t(10))}}.getSpan()), + (Span{IU64(0), IU64(10)})); + EXPECT_EQ( + (Constraint{LeU, {Literal(std::numeric_limits::max())}}.getSpan()), + (Span{IU64(0), maxU64})); + + // GtS: [C + 1, maxI64] + EXPECT_EQ((Constraint{GtS, {Literal(int32_t(10))}}.getSpan()), + (Span{IU64(11), maxI64})); + EXPECT_EQ((Constraint{GtS, {Literal(int32_t(0))}}.getSpan()), + (Span{IU64(1), maxI64})); + EXPECT_EQ((Constraint{GtS, {Literal(int32_t(-5))}}.getSpan()), + (Span{IU64(-4), maxI64})); + EXPECT_EQ( + (Constraint{GtS, {Literal(std::numeric_limits::min())}}.getSpan()), + (Span{IU64(std::numeric_limits::min() + 1), maxI64})); + EXPECT_EQ( + (Constraint{GtS, {Literal(std::numeric_limits::max() - 1)}} + .getSpan()), + (Span{maxI64, maxI64})); + // GtS max signed: empty span + auto gtsMax = + Constraint{GtS, {Literal(std::numeric_limits::max())}}.getSpan(); + ASSERT_TRUE(gtsMax.has_value()); + EXPECT_TRUE(gtsMax->isEmpty()); + EXPECT_EQ(gtsMax, Span::empty()); + + // GtU: [C + 1, maxU64] + EXPECT_EQ((Constraint{GtU, {Literal(uint32_t(0))}}.getSpan()), + (Span{IU64(1), maxU64})); + EXPECT_EQ((Constraint{GtU, {Literal(uint32_t(10))}}.getSpan()), + (Span{IU64(11), maxU64})); + EXPECT_EQ( + (Constraint{GtU, {Literal(std::numeric_limits::max() - 1)}} + .getSpan()), + (Span{maxU64, maxU64})); + // GtU max unsigned: empty span + auto gtuMax = + Constraint{GtU, {Literal(std::numeric_limits::max())}}.getSpan(); + ASSERT_TRUE(gtuMax.has_value()); + EXPECT_TRUE(gtuMax->isEmpty()); + EXPECT_EQ(gtuMax, Span::empty()); + + // GeS: [C, maxI64] + EXPECT_EQ((Constraint{GeS, {Literal(int32_t(10))}}.getSpan()), + (Span{IU64(10), maxI64})); + EXPECT_EQ((Constraint{GeS, {Literal(int32_t(0))}}.getSpan()), + (Span{IU64(0), maxI64})); + EXPECT_EQ((Constraint{GeS, {Literal(int32_t(-5))}}.getSpan()), + (Span{IU64(-5), maxI64})); + EXPECT_EQ( + (Constraint{GeS, {Literal(std::numeric_limits::min())}}.getSpan()), + (Span{minI64, maxI64})); + EXPECT_EQ( + (Constraint{GeS, {Literal(std::numeric_limits::max())}}.getSpan()), + (Span{maxI64, maxI64})); + + // GeU: [C, maxU64] + EXPECT_EQ((Constraint{GeU, {Literal(uint32_t(0))}}.getSpan()), + (Span{IU64(0), maxU64})); + EXPECT_EQ((Constraint{GeU, {Literal(uint32_t(10))}}.getSpan()), + (Span{IU64(10), maxU64})); + EXPECT_EQ( + (Constraint{GeU, {Literal(std::numeric_limits::max())}}.getSpan()), + (Span{maxU64, maxU64})); +} + From 424c1ab7b27d727aaf53d0fc4bf6a2001199534c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 Aug 2026 11:57:32 -0700 Subject: [PATCH 18/32] work --- test/gtest/constraint.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index ea58167e004..744f76b3ac7 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -302,7 +302,6 @@ TEST(ConstraintTest, TestOrInequality) { // x > signed_max || x >= (signed_max + 1 === signed_min) != x > signed_max AndedConstraintSet gtsMax{ {GtS, {Literal(std::numeric_limits::max())}}}; - // x > signed_max implies x >= signed_min, so ORing them yields x >= signed_min. XXX fix! checkOr(gtsMax, gesMin, gesMin); } From c05fba8a1f42bad02d3aa2647c5b4ee69d97e144 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 Aug 2026 12:01:00 -0700 Subject: [PATCH 19/32] changes --- src/ir/constraint.cpp | 37 +++++++++++++++++++++++++------------ test/gtest/constraint.cpp | 2 +- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index c8a2a77aaa5..48821b465f1 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -31,14 +31,18 @@ std::optional> Constraint::getSpan() const { return {}; } + auto minSigned = c->type == Type::i32 ? std::numeric_limits::min() : std::numeric_limits::min(); + auto maxSigned = c->type == Type::i32 ? std::numeric_limits::max() : std::numeric_limits::max(); + auto maxUnsigned = c->type == Type::i32 ? std::numeric_limits::max() : std::numeric_limits::max(); + switch (op) { case Eq: { auto x = c->getUnsigned(); - if (x <= std::numeric_limits::max()) { + if (x <= uint64_(maxSigned)) { // This is in the range of both signed and unsigned values, so there is // no ambiguity. That is, we cannot convert the bit pattern - // 0xffffffffffffffff into a Span, as it might be either uint64_t(-1) - // or actually negative (but a bit pattern like 0x0000000000000001 is + // 0xffffffff into a Span, as it might be either uint32_t(-1) + // or actually negative (but a bit pattern like 0x00000001 is // always fine as it can only ever be "1"). return Span{x, x}; } @@ -46,11 +50,11 @@ std::optional> Constraint::getSpan() const { } case LtS: - if (c->getInteger() == std::numeric_limits::min()) { + if (c->getInteger() == minSigned) { // Less than the lowest possible number is an empty span. return Span::empty(); } else { - return Span{std::numeric_limits::min(), + return Span{minSigned, c->getInteger() - 1}; } break; @@ -63,32 +67,32 @@ std::optional> Constraint::getSpan() const { } break; case LeS: - return Span{std::numeric_limits::min(), c->getInteger()}; + return Span{minSigned, c->getInteger()}; case LeU: return Span{0, c->getUnsigned()}; case GtS: - if (c->getInteger() == std::numeric_limits::max()) { + if (c->getInteger() == maxSigned) { // Greater than the highest possible number is an empty span. return Span::empty(); } else { return Span{c->getInteger() + 1, - std::numeric_limits::max()}; + maxSigned}; } break; case GtU: - if (c->getUnsigned() == std::numeric_limits::max()) { + if (c->getUnsigned() == maxUnsigned) { // Greater than the highest possible number is an empty span. return Span::empty(); } else { return Span{c->getUnsigned() + 1, - std::numeric_limits::max()}; + maxUnsigned}; } break; case GeS: - return Span{c->getInteger(), std::numeric_limits::max()}; + return Span{c->getInteger(), maxSigned}; case GeU: - return Span{c->getUnsigned(), std::numeric_limits::max()}; + return Span{c->getUnsigned(), maxUnsigned}; default: { } @@ -156,6 +160,15 @@ Result provesConstantPair(Abstract::Op aOp, // If we can represent both as spans, we can calculate that way. if (auto aSpan = Constraint{aOp, aConstant}.getSpan()) { if (auto bSpan = Constraint{bOp, bConstant}.getSpan()) { + if (aSpan->isEmpty()) { + // An empty span implies a contradiction (e.g. x > MAX_INT), as it means + // no possible number can apply. And contradictions prove anything. + return True; + } + if (bSpan->isEmpty()) { + // Nothing that is not a contradiction can prove a contradiction. + return False; + } if (bSpan->contains(*aSpan)) { // b's values contains a's, e.g., b = { 0 < x < 10 } and // a = { 3 < x < 7 }, so a => b. diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 744f76b3ac7..bb6d8e357a8 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -302,7 +302,7 @@ TEST(ConstraintTest, TestOrInequality) { // x > signed_max || x >= (signed_max + 1 === signed_min) != x > signed_max AndedConstraintSet gtsMax{ {GtS, {Literal(std::numeric_limits::max())}}}; - checkOr(gtsMax, gesMin, gesMin); + checkOr(gtsMax, gesMin, gesMin); // TODO: x > signed_max is a contradiction } TEST(ConstraintTest, TestOrLoop) { From b5ea854b97e6af5cd35e69076eae686129b351b1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 Aug 2026 14:29:15 -0700 Subject: [PATCH 20/32] work --- src/ir/constraint.cpp | 2 +- test/gtest/constraint.cpp | 275 ++++++++++++++++++++++++++++++-------- 2 files changed, 223 insertions(+), 54 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 48821b465f1..1cf03da7d11 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -38,7 +38,7 @@ std::optional> Constraint::getSpan() const { switch (op) { case Eq: { auto x = c->getUnsigned(); - if (x <= uint64_(maxSigned)) { + if (x <= uint64_t(maxSigned)) { // This is in the range of both signed and unsigned values, so there is // no ambiguity. That is, we cannot convert the bit pattern // 0xffffffff into a Span, as it might be either uint32_t(-1) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index bb6d8e357a8..4b0dd84b113 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -676,6 +676,9 @@ TEST(ConstraintTest, TestEqConstraints) { } TEST(ConstraintTest, GetSpan) { + const IU64 minI32(std::numeric_limits::min()); + const IU64 maxI32(std::numeric_limits::max()); + const IU64 maxU32(std::numeric_limits::max()); const IU64 minI64(std::numeric_limits::min()); const IU64 maxI64(std::numeric_limits::max()); const IU64 maxU64(std::numeric_limits::max()); @@ -689,90 +692,130 @@ TEST(ConstraintTest, GetSpan) { EXPECT_EQ((Constraint{Ne, {Literal(int32_t(5))}}.getSpan()), std::nullopt); EXPECT_EQ((Constraint{Ne, {Literal(int32_t(0))}}.getSpan()), std::nullopt); - // Eq: only non-negative values up to int32_t max have an unambiguous span. + // Eq (i32): non-negative values up to int32_t max have an unambiguous span. EXPECT_EQ((Constraint{Eq, {Literal(int32_t(0))}}.getSpan()), (Span{0, 0})); EXPECT_EQ((Constraint{Eq, {Literal(int32_t(1))}}.getSpan()), (Span{1, 1})); EXPECT_EQ((Constraint{Eq, {Literal(int32_t(42))}}.getSpan()), (Span{42, 42})); - EXPECT_EQ((Constraint{Eq, {Literal(int64_t(42))}}.getSpan()), - (Span{42, 42})); EXPECT_EQ( (Constraint{Eq, {Literal(std::numeric_limits::max())}}.getSpan()), - (Span{uint32_t(std::numeric_limits::max()), - uint32_t(std::numeric_limits::max())})); + (Span{maxI32, maxI32})); - // Eq with negative or large values returns nullopt due to signed/unsigned - // ambiguity. + // Eq (i32) with negative or large unsigned values returns nullopt due to + // signed/unsigned ambiguity. EXPECT_EQ((Constraint{Eq, {Literal(int32_t(-1))}}.getSpan()), std::nullopt); EXPECT_EQ((Constraint{Eq, {Literal(int32_t(-42))}}.getSpan()), std::nullopt); EXPECT_EQ( (Constraint{Eq, {Literal(std::numeric_limits::min())}}.getSpan()), std::nullopt); - EXPECT_EQ((Constraint{Eq, {Literal(int64_t(-1))}}.getSpan()), std::nullopt); + EXPECT_EQ((Constraint{Eq, {Literal(uint32_t(0x80000000u))}}.getSpan()), + std::nullopt); EXPECT_EQ( - (Constraint{Eq, {Literal(std::numeric_limits::min())}}.getSpan()), + (Constraint{Eq, {Literal(std::numeric_limits::max())}}.getSpan()), std::nullopt); + + // Eq (i64): non-negative values up to int64_t max have an unambiguous span. + EXPECT_EQ((Constraint{Eq, {Literal(int64_t(0))}}.getSpan()), + (Span{0, 0})); + EXPECT_EQ((Constraint{Eq, {Literal(int64_t(42))}}.getSpan()), + (Span{42, 42})); EXPECT_EQ( (Constraint{Eq, {Literal(int64_t(std::numeric_limits::max()) + 1)}} .getSpan()), - std::nullopt); + (Span{uint64_t(std::numeric_limits::max()) + 1, + uint64_t(std::numeric_limits::max()) + 1})); EXPECT_EQ( (Constraint{Eq, {Literal(std::numeric_limits::max())}}.getSpan()), + (Span{maxI64, maxI64})); + + // Eq (i64) with negative or large unsigned values returns nullopt. + EXPECT_EQ((Constraint{Eq, {Literal(int64_t(-1))}}.getSpan()), std::nullopt); + EXPECT_EQ( + (Constraint{Eq, {Literal(std::numeric_limits::min())}}.getSpan()), std::nullopt); - EXPECT_EQ((Constraint{Eq, {Literal(uint32_t(0x80000000u))}}.getSpan()), + EXPECT_EQ((Constraint{Eq, {Literal(uint64_t(uint64_t(1) << 63))}}.getSpan()), std::nullopt); EXPECT_EQ( (Constraint{Eq, {Literal(std::numeric_limits::max())}}.getSpan()), std::nullopt); - // LtS: [minI64, C - 1] + // LtS (i32): [minI32, C - 1] EXPECT_EQ((Constraint{LtS, {Literal(int32_t(10))}}.getSpan()), - (Span{minI64, IU64(9)})); + (Span{minI32, IU64(9)})); EXPECT_EQ((Constraint{LtS, {Literal(int32_t(0))}}.getSpan()), - (Span{minI64, IU64(-1)})); + (Span{minI32, IU64(-1)})); EXPECT_EQ((Constraint{LtS, {Literal(int32_t(-5))}}.getSpan()), - (Span{minI64, IU64(-6)})); + (Span{minI32, IU64(-6)})); + EXPECT_EQ( + (Constraint{LtS, {Literal(std::numeric_limits::max())}}.getSpan()), + (Span{minI32, IU64(std::numeric_limits::max() - 1)})); + // LtS min signed (i32): empty span + auto ltsMin32 = + Constraint{LtS, {Literal(std::numeric_limits::min())}}.getSpan(); + ASSERT_TRUE(ltsMin32.has_value()); + EXPECT_TRUE(ltsMin32->isEmpty()); + EXPECT_EQ(ltsMin32, Span::empty()); + + // LtS (i64): [minI64, C - 1] EXPECT_EQ((Constraint{LtS, {Literal(int64_t(100))}}.getSpan()), (Span{minI64, IU64(99)})); + EXPECT_EQ((Constraint{LtS, {Literal(int64_t(0))}}.getSpan()), + (Span{minI64, IU64(-1)})); EXPECT_EQ( (Constraint{LtS, {Literal(std::numeric_limits::max())}}.getSpan()), (Span{minI64, IU64(std::numeric_limits::max() - 1)})); - // LtS min signed: empty span - auto ltsMin = + // LtS min signed (i64): empty span + auto ltsMin64 = Constraint{LtS, {Literal(std::numeric_limits::min())}}.getSpan(); - ASSERT_TRUE(ltsMin.has_value()); - EXPECT_TRUE(ltsMin->isEmpty()); - EXPECT_EQ(ltsMin, Span::empty()); + ASSERT_TRUE(ltsMin64.has_value()); + EXPECT_TRUE(ltsMin64->isEmpty()); + EXPECT_EQ(ltsMin64, Span::empty()); - // LtU: [0, C - 1] + // LtU (i32): [0, C - 1] EXPECT_EQ((Constraint{LtU, {Literal(uint32_t(10))}}.getSpan()), (Span{IU64(0), IU64(9)})); EXPECT_EQ((Constraint{LtU, {Literal(uint32_t(1))}}.getSpan()), (Span{IU64(0), IU64(0)})); - EXPECT_EQ((Constraint{LtU, {Literal(uint64_t(100))}}.getSpan()), - (Span{IU64(0), IU64(99)})); EXPECT_EQ( - (Constraint{LtU, {Literal(std::numeric_limits::max())}}.getSpan()), - (Span{IU64(0), IU64(std::numeric_limits::max() - 1)})); - // LtU 0: empty span + (Constraint{LtU, {Literal(std::numeric_limits::max())}}.getSpan()), + (Span{IU64(0), IU64(uint64_t(std::numeric_limits::max()) - 1)})); + // LtU 0 (i32): empty span auto ltuZero32 = Constraint{LtU, {Literal(uint32_t(0))}}.getSpan(); ASSERT_TRUE(ltuZero32.has_value()); EXPECT_TRUE(ltuZero32->isEmpty()); EXPECT_EQ(ltuZero32, Span::empty()); + + // LtU (i64): [0, C - 1] + EXPECT_EQ((Constraint{LtU, {Literal(uint64_t(100))}}.getSpan()), + (Span{IU64(0), IU64(99)})); + EXPECT_EQ( + (Constraint{LtU, {Literal(std::numeric_limits::max())}}.getSpan()), + (Span{IU64(0), IU64(std::numeric_limits::max() - 1)})); + // LtU 0 (i64): empty span auto ltuZero64 = Constraint{LtU, {Literal(uint64_t(0))}}.getSpan(); ASSERT_TRUE(ltuZero64.has_value()); EXPECT_TRUE(ltuZero64->isEmpty()); EXPECT_EQ(ltuZero64, Span::empty()); - // LeS: [minI64, C] + // LeS (i32): [minI32, C] EXPECT_EQ((Constraint{LeS, {Literal(int32_t(10))}}.getSpan()), - (Span{minI64, IU64(10)})); + (Span{minI32, IU64(10)})); EXPECT_EQ((Constraint{LeS, {Literal(int32_t(0))}}.getSpan()), - (Span{minI64, IU64(0)})); + (Span{minI32, IU64(0)})); EXPECT_EQ((Constraint{LeS, {Literal(int32_t(-5))}}.getSpan()), - (Span{minI64, IU64(-5)})); + (Span{minI32, IU64(-5)})); + EXPECT_EQ( + (Constraint{LeS, {Literal(std::numeric_limits::min())}}.getSpan()), + (Span{minI32, minI32})); + EXPECT_EQ( + (Constraint{LeS, {Literal(std::numeric_limits::max())}}.getSpan()), + (Span{minI32, maxI32})); + + // LeS (i64): [minI64, C] + EXPECT_EQ((Constraint{LeS, {Literal(int64_t(10))}}.getSpan()), + (Span{minI64, IU64(10)})); EXPECT_EQ( (Constraint{LeS, {Literal(std::numeric_limits::min())}}.getSpan()), (Span{minI64, minI64})); @@ -780,22 +823,50 @@ TEST(ConstraintTest, GetSpan) { (Constraint{LeS, {Literal(std::numeric_limits::max())}}.getSpan()), (Span{minI64, maxI64})); - // LeU: [0, C] + // LeU (i32): [0, C] EXPECT_EQ((Constraint{LeU, {Literal(uint32_t(0))}}.getSpan()), (Span{IU64(0), IU64(0)})); EXPECT_EQ((Constraint{LeU, {Literal(uint32_t(10))}}.getSpan()), (Span{IU64(0), IU64(10)})); + EXPECT_EQ( + (Constraint{LeU, {Literal(std::numeric_limits::max())}}.getSpan()), + (Span{IU64(0), maxU32})); + + // LeU (i64): [0, C] + EXPECT_EQ((Constraint{LeU, {Literal(uint64_t(0))}}.getSpan()), + (Span{IU64(0), IU64(0)})); + EXPECT_EQ((Constraint{LeU, {Literal(uint64_t(10))}}.getSpan()), + (Span{IU64(0), IU64(10)})); EXPECT_EQ( (Constraint{LeU, {Literal(std::numeric_limits::max())}}.getSpan()), (Span{IU64(0), maxU64})); - // GtS: [C + 1, maxI64] + // GtS (i32): [C + 1, maxI32] EXPECT_EQ((Constraint{GtS, {Literal(int32_t(10))}}.getSpan()), - (Span{IU64(11), maxI64})); + (Span{IU64(11), maxI32})); EXPECT_EQ((Constraint{GtS, {Literal(int32_t(0))}}.getSpan()), - (Span{IU64(1), maxI64})); + (Span{IU64(1), maxI32})); EXPECT_EQ((Constraint{GtS, {Literal(int32_t(-5))}}.getSpan()), - (Span{IU64(-4), maxI64})); + (Span{IU64(-4), maxI32})); + EXPECT_EQ( + (Constraint{GtS, {Literal(std::numeric_limits::min())}}.getSpan()), + (Span{IU64(std::numeric_limits::min() + 1), maxI32})); + EXPECT_EQ( + (Constraint{GtS, {Literal(std::numeric_limits::max() - 1)}} + .getSpan()), + (Span{maxI32, maxI32})); + // GtS max signed (i32): empty span + auto gtsMax32 = + Constraint{GtS, {Literal(std::numeric_limits::max())}}.getSpan(); + ASSERT_TRUE(gtsMax32.has_value()); + EXPECT_TRUE(gtsMax32->isEmpty()); + EXPECT_EQ(gtsMax32, Span::empty()); + + // GtS (i64): [C + 1, maxI64] + EXPECT_EQ((Constraint{GtS, {Literal(int64_t(10))}}.getSpan()), + (Span{IU64(11), maxI64})); + EXPECT_EQ((Constraint{GtS, {Literal(int64_t(0))}}.getSpan()), + (Span{IU64(1), maxI64})); EXPECT_EQ( (Constraint{GtS, {Literal(std::numeric_limits::min())}}.getSpan()), (Span{IU64(std::numeric_limits::min() + 1), maxI64})); @@ -803,36 +874,64 @@ TEST(ConstraintTest, GetSpan) { (Constraint{GtS, {Literal(std::numeric_limits::max() - 1)}} .getSpan()), (Span{maxI64, maxI64})); - // GtS max signed: empty span - auto gtsMax = + // GtS max signed (i64): empty span + auto gtsMax64 = Constraint{GtS, {Literal(std::numeric_limits::max())}}.getSpan(); - ASSERT_TRUE(gtsMax.has_value()); - EXPECT_TRUE(gtsMax->isEmpty()); - EXPECT_EQ(gtsMax, Span::empty()); + ASSERT_TRUE(gtsMax64.has_value()); + EXPECT_TRUE(gtsMax64->isEmpty()); + EXPECT_EQ(gtsMax64, Span::empty()); - // GtU: [C + 1, maxU64] + // GtU (i32): [C + 1, maxU32] EXPECT_EQ((Constraint{GtU, {Literal(uint32_t(0))}}.getSpan()), - (Span{IU64(1), maxU64})); + (Span{IU64(1), maxU32})); EXPECT_EQ((Constraint{GtU, {Literal(uint32_t(10))}}.getSpan()), + (Span{IU64(11), maxU32})); + EXPECT_EQ( + (Constraint{GtU, {Literal(std::numeric_limits::max() - 1)}} + .getSpan()), + (Span{maxU32, maxU32})); + // GtU max unsigned (i32): empty span + auto gtuMax32 = + Constraint{GtU, {Literal(std::numeric_limits::max())}}.getSpan(); + ASSERT_TRUE(gtuMax32.has_value()); + EXPECT_TRUE(gtuMax32->isEmpty()); + EXPECT_EQ(gtuMax32, Span::empty()); + + // GtU (i64): [C + 1, maxU64] + EXPECT_EQ((Constraint{GtU, {Literal(uint64_t(0))}}.getSpan()), + (Span{IU64(1), maxU64})); + EXPECT_EQ((Constraint{GtU, {Literal(uint64_t(10))}}.getSpan()), (Span{IU64(11), maxU64})); EXPECT_EQ( (Constraint{GtU, {Literal(std::numeric_limits::max() - 1)}} .getSpan()), (Span{maxU64, maxU64})); - // GtU max unsigned: empty span - auto gtuMax = + // GtU max unsigned (i64): empty span + auto gtuMax64 = Constraint{GtU, {Literal(std::numeric_limits::max())}}.getSpan(); - ASSERT_TRUE(gtuMax.has_value()); - EXPECT_TRUE(gtuMax->isEmpty()); - EXPECT_EQ(gtuMax, Span::empty()); + ASSERT_TRUE(gtuMax64.has_value()); + EXPECT_TRUE(gtuMax64->isEmpty()); + EXPECT_EQ(gtuMax64, Span::empty()); - // GeS: [C, maxI64] + // GeS (i32): [C, maxI32] EXPECT_EQ((Constraint{GeS, {Literal(int32_t(10))}}.getSpan()), - (Span{IU64(10), maxI64})); + (Span{IU64(10), maxI32})); EXPECT_EQ((Constraint{GeS, {Literal(int32_t(0))}}.getSpan()), - (Span{IU64(0), maxI64})); + (Span{IU64(0), maxI32})); EXPECT_EQ((Constraint{GeS, {Literal(int32_t(-5))}}.getSpan()), - (Span{IU64(-5), maxI64})); + (Span{IU64(-5), maxI32})); + EXPECT_EQ( + (Constraint{GeS, {Literal(std::numeric_limits::min())}}.getSpan()), + (Span{minI32, maxI32})); + EXPECT_EQ( + (Constraint{GeS, {Literal(std::numeric_limits::max())}}.getSpan()), + (Span{maxI32, maxI32})); + + // GeS (i64): [C, maxI64] + EXPECT_EQ((Constraint{GeS, {Literal(int64_t(10))}}.getSpan()), + (Span{IU64(10), maxI64})); + EXPECT_EQ((Constraint{GeS, {Literal(int64_t(0))}}.getSpan()), + (Span{IU64(0), maxI64})); EXPECT_EQ( (Constraint{GeS, {Literal(std::numeric_limits::min())}}.getSpan()), (Span{minI64, maxI64})); @@ -840,13 +939,83 @@ TEST(ConstraintTest, GetSpan) { (Constraint{GeS, {Literal(std::numeric_limits::max())}}.getSpan()), (Span{maxI64, maxI64})); - // GeU: [C, maxU64] + // GeU (i32): [C, maxU32] EXPECT_EQ((Constraint{GeU, {Literal(uint32_t(0))}}.getSpan()), - (Span{IU64(0), maxU64})); + (Span{IU64(0), maxU32})); EXPECT_EQ((Constraint{GeU, {Literal(uint32_t(10))}}.getSpan()), + (Span{IU64(10), maxU32})); + EXPECT_EQ( + (Constraint{GeU, {Literal(std::numeric_limits::max())}}.getSpan()), + (Span{maxU32, maxU32})); + + // GeU (i64): [C, maxU64] + EXPECT_EQ((Constraint{GeU, {Literal(uint64_t(0))}}.getSpan()), + (Span{IU64(0), maxU64})); + EXPECT_EQ((Constraint{GeU, {Literal(uint64_t(10))}}.getSpan()), (Span{IU64(10), maxU64})); EXPECT_EQ( (Constraint{GeU, {Literal(std::numeric_limits::max())}}.getSpan()), (Span{maxU64, maxU64})); } +TEST(ConstraintTest, EmptySpanContradiction) { + // Impossible constraints produce empty spans. + Constraint gtsMax32{GtS, {Literal(std::numeric_limits::max())}}; + Constraint ltsMin32{LtS, {Literal(std::numeric_limits::min())}}; + Constraint ltuZero32{LtU, {Literal(uint32_t(0))}}; + Constraint gtuMax32{GtU, {Literal(std::numeric_limits::max())}}; + + Constraint gtsMax64{GtS, {Literal(std::numeric_limits::max())}}; + Constraint ltsMin64{LtS, {Literal(std::numeric_limits::min())}}; + Constraint ltuZero64{LtU, {Literal(uint64_t(0))}}; + Constraint gtuMax64{GtU, {Literal(std::numeric_limits::max())}}; + + Constraint eq5{Eq, {Literal(int32_t(5))}}; + Constraint ge0{GeS, {Literal(int32_t(0))}}; + Constraint eq100_64{Eq, {Literal(int64_t(100))}}; + + // An impossible constraint proves anything is True. + EXPECT_EQ(AndedConstraintSet{gtsMax32}.proves(eq5), True); + EXPECT_EQ(AndedConstraintSet{ltsMin32}.proves(ge0), True); + EXPECT_EQ(AndedConstraintSet{ltuZero32}.proves(eq5), True); + EXPECT_EQ(AndedConstraintSet{gtuMax32}.proves(ge0), True); + + EXPECT_EQ(AndedConstraintSet{gtsMax64}.proves(eq100_64), True); + EXPECT_EQ(AndedConstraintSet{ltsMin64}.proves(eq100_64), True); + EXPECT_EQ(AndedConstraintSet{ltuZero64}.proves(eq100_64), True); + EXPECT_EQ(AndedConstraintSet{gtuMax64}.proves(eq100_64), True); + + // Impossible constraint proves another impossible constraint is True. + EXPECT_EQ(AndedConstraintSet{gtsMax32}.proves(ltsMin32), True); + EXPECT_EQ(AndedConstraintSet{ltuZero32}.proves(gtuMax32), True); + + // A normal constraint proves an impossible constraint is False. + EXPECT_EQ(AndedConstraintSet{eq5}.proves(gtsMax32), False); + EXPECT_EQ(AndedConstraintSet{eq5}.proves(ltsMin32), False); + EXPECT_EQ(AndedConstraintSet{eq5}.proves(ltuZero32), False); + EXPECT_EQ(AndedConstraintSet{eq5}.proves(gtuMax32), False); + + EXPECT_EQ(AndedConstraintSet{eq100_64}.proves(gtsMax64), False); + EXPECT_EQ(AndedConstraintSet{eq100_64}.proves(ltsMin64), False); + EXPECT_EQ(AndedConstraintSet{eq100_64}.proves(ltuZero64), False); + EXPECT_EQ(AndedConstraintSet{eq100_64}.proves(gtuMax64), False); + + // An impossible constraint in a set proves any condition. + AndedConstraintSet s{gtsMax32}; + EXPECT_EQ(s.proves(eq5), True); + EXPECT_EQ(s.proves(ge0), True); + + // Adding an impossible constraint to a non-empty set proves False and turns + // the set into an explicit contradiction (provesEverything() == true). + AndedConstraintSet s2; + s2.set(eq5); + s2.approximateAnd(ltuZero32); + EXPECT_TRUE(s2.provesEverything()); + + // ORing an impossible constraint (which has no models) with a valid set + // leaves the valid set. + AndedConstraintSet valid{{Eq, {Literal(int32_t(42))}}}; + AndedConstraintSet impossible{gtsMax32}; + checkOr(valid, impossible, valid); +} + From 7048dd2498f06eecb6fb027b025090a8ef216b0b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 Aug 2026 14:29:24 -0700 Subject: [PATCH 21/32] work --- src/ir/constraint.cpp | 19 ++++++++-------- test/gtest/constraint.cpp | 48 +++++++++++++++++++-------------------- 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 1cf03da7d11..681d4cb0ee4 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -31,9 +31,13 @@ std::optional> Constraint::getSpan() const { return {}; } - auto minSigned = c->type == Type::i32 ? std::numeric_limits::min() : std::numeric_limits::min(); - auto maxSigned = c->type == Type::i32 ? std::numeric_limits::max() : std::numeric_limits::max(); - auto maxUnsigned = c->type == Type::i32 ? std::numeric_limits::max() : std::numeric_limits::max(); + auto minSigned = c->type == Type::i32 ? std::numeric_limits::min() + : std::numeric_limits::min(); + auto maxSigned = c->type == Type::i32 ? std::numeric_limits::max() + : std::numeric_limits::max(); + auto maxUnsigned = c->type == Type::i32 + ? std::numeric_limits::max() + : std::numeric_limits::max(); switch (op) { case Eq: { @@ -54,8 +58,7 @@ std::optional> Constraint::getSpan() const { // Less than the lowest possible number is an empty span. return Span::empty(); } else { - return Span{minSigned, - c->getInteger() - 1}; + return Span{minSigned, c->getInteger() - 1}; } break; case LtU: @@ -76,8 +79,7 @@ std::optional> Constraint::getSpan() const { // Greater than the highest possible number is an empty span. return Span::empty(); } else { - return Span{c->getInteger() + 1, - maxSigned}; + return Span{c->getInteger() + 1, maxSigned}; } break; case GtU: @@ -85,8 +87,7 @@ std::optional> Constraint::getSpan() const { // Greater than the highest possible number is an empty span. return Span::empty(); } else { - return Span{c->getUnsigned() + 1, - maxUnsigned}; + return Span{c->getUnsigned() + 1, maxUnsigned}; } break; case GeS: diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 4b0dd84b113..6ba0672d8a7 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -779,8 +779,10 @@ TEST(ConstraintTest, GetSpan) { EXPECT_EQ((Constraint{LtU, {Literal(uint32_t(1))}}.getSpan()), (Span{IU64(0), IU64(0)})); EXPECT_EQ( - (Constraint{LtU, {Literal(std::numeric_limits::max())}}.getSpan()), - (Span{IU64(0), IU64(uint64_t(std::numeric_limits::max()) - 1)})); + (Constraint{LtU, {Literal(std::numeric_limits::max())}} + .getSpan()), + (Span{IU64(0), + IU64(uint64_t(std::numeric_limits::max()) - 1)})); // LtU 0 (i32): empty span auto ltuZero32 = Constraint{LtU, {Literal(uint32_t(0))}}.getSpan(); ASSERT_TRUE(ltuZero32.has_value()); @@ -791,7 +793,8 @@ TEST(ConstraintTest, GetSpan) { EXPECT_EQ((Constraint{LtU, {Literal(uint64_t(100))}}.getSpan()), (Span{IU64(0), IU64(99)})); EXPECT_EQ( - (Constraint{LtU, {Literal(std::numeric_limits::max())}}.getSpan()), + (Constraint{LtU, {Literal(std::numeric_limits::max())}} + .getSpan()), (Span{IU64(0), IU64(std::numeric_limits::max() - 1)})); // LtU 0 (i64): empty span auto ltuZero64 = Constraint{LtU, {Literal(uint64_t(0))}}.getSpan(); @@ -828,18 +831,18 @@ TEST(ConstraintTest, GetSpan) { (Span{IU64(0), IU64(0)})); EXPECT_EQ((Constraint{LeU, {Literal(uint32_t(10))}}.getSpan()), (Span{IU64(0), IU64(10)})); - EXPECT_EQ( - (Constraint{LeU, {Literal(std::numeric_limits::max())}}.getSpan()), - (Span{IU64(0), maxU32})); + EXPECT_EQ((Constraint{LeU, {Literal(std::numeric_limits::max())}} + .getSpan()), + (Span{IU64(0), maxU32})); // LeU (i64): [0, C] EXPECT_EQ((Constraint{LeU, {Literal(uint64_t(0))}}.getSpan()), (Span{IU64(0), IU64(0)})); EXPECT_EQ((Constraint{LeU, {Literal(uint64_t(10))}}.getSpan()), (Span{IU64(0), IU64(10)})); - EXPECT_EQ( - (Constraint{LeU, {Literal(std::numeric_limits::max())}}.getSpan()), - (Span{IU64(0), maxU64})); + EXPECT_EQ((Constraint{LeU, {Literal(std::numeric_limits::max())}} + .getSpan()), + (Span{IU64(0), maxU64})); // GtS (i32): [C + 1, maxI32] EXPECT_EQ((Constraint{GtS, {Literal(int32_t(10))}}.getSpan()), @@ -851,10 +854,9 @@ TEST(ConstraintTest, GetSpan) { EXPECT_EQ( (Constraint{GtS, {Literal(std::numeric_limits::min())}}.getSpan()), (Span{IU64(std::numeric_limits::min() + 1), maxI32})); - EXPECT_EQ( - (Constraint{GtS, {Literal(std::numeric_limits::max() - 1)}} - .getSpan()), - (Span{maxI32, maxI32})); + EXPECT_EQ((Constraint{GtS, {Literal(std::numeric_limits::max() - 1)}} + .getSpan()), + (Span{maxI32, maxI32})); // GtS max signed (i32): empty span auto gtsMax32 = Constraint{GtS, {Literal(std::numeric_limits::max())}}.getSpan(); @@ -870,10 +872,9 @@ TEST(ConstraintTest, GetSpan) { EXPECT_EQ( (Constraint{GtS, {Literal(std::numeric_limits::min())}}.getSpan()), (Span{IU64(std::numeric_limits::min() + 1), maxI64})); - EXPECT_EQ( - (Constraint{GtS, {Literal(std::numeric_limits::max() - 1)}} - .getSpan()), - (Span{maxI64, maxI64})); + EXPECT_EQ((Constraint{GtS, {Literal(std::numeric_limits::max() - 1)}} + .getSpan()), + (Span{maxI64, maxI64})); // GtS max signed (i64): empty span auto gtsMax64 = Constraint{GtS, {Literal(std::numeric_limits::max())}}.getSpan(); @@ -944,18 +945,18 @@ TEST(ConstraintTest, GetSpan) { (Span{IU64(0), maxU32})); EXPECT_EQ((Constraint{GeU, {Literal(uint32_t(10))}}.getSpan()), (Span{IU64(10), maxU32})); - EXPECT_EQ( - (Constraint{GeU, {Literal(std::numeric_limits::max())}}.getSpan()), - (Span{maxU32, maxU32})); + EXPECT_EQ((Constraint{GeU, {Literal(std::numeric_limits::max())}} + .getSpan()), + (Span{maxU32, maxU32})); // GeU (i64): [C, maxU64] EXPECT_EQ((Constraint{GeU, {Literal(uint64_t(0))}}.getSpan()), (Span{IU64(0), maxU64})); EXPECT_EQ((Constraint{GeU, {Literal(uint64_t(10))}}.getSpan()), (Span{IU64(10), maxU64})); - EXPECT_EQ( - (Constraint{GeU, {Literal(std::numeric_limits::max())}}.getSpan()), - (Span{maxU64, maxU64})); + EXPECT_EQ((Constraint{GeU, {Literal(std::numeric_limits::max())}} + .getSpan()), + (Span{maxU64, maxU64})); } TEST(ConstraintTest, EmptySpanContradiction) { @@ -1018,4 +1019,3 @@ TEST(ConstraintTest, EmptySpanContradiction) { AndedConstraintSet impossible{gtsMax32}; checkOr(valid, impossible, valid); } - From 9e42b9d4aae7ff714255aec43056e49a9434e4a9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 Aug 2026 14:32:17 -0700 Subject: [PATCH 22/32] work --- test/gtest/constraint.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 6ba0672d8a7..2cbd6439bce 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -302,7 +302,8 @@ TEST(ConstraintTest, TestOrInequality) { // x > signed_max || x >= (signed_max + 1 === signed_min) != x > signed_max AndedConstraintSet gtsMax{ {GtS, {Literal(std::numeric_limits::max())}}}; - checkOr(gtsMax, gesMin, gesMin); // TODO: x > signed_max is a contradiction + // x > signed_max is impossible, so it vanishes in the OR. + checkOr(gtsMax, gesMin, gesMin); } TEST(ConstraintTest, TestOrLoop) { From d80eae557a9a35c381069cc3e151f537c1681bfa Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 Aug 2026 14:39:20 -0700 Subject: [PATCH 23/32] work --- src/ir/constraint.cpp | 2 +- test/gtest/constraint.cpp | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 681d4cb0ee4..b096194324f 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -167,7 +167,7 @@ Result provesConstantPair(Abstract::Op aOp, return True; } if (bSpan->isEmpty()) { - // Nothing that is not a contradiction can prove a contradiction. + // Anything that is not a contradiction can prove a contradiction. return False; } if (bSpan->contains(*aSpan)) { diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 2cbd6439bce..4c107087094 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -960,6 +960,13 @@ TEST(ConstraintTest, GetSpan) { (Span{maxU64, maxU64})); } +TEST(ConstraintTest, SpanOptimizations) { + // Using spans, we can optimize things like {x < 100} => {x < 200}. + Constraint lts100{LtS, {Literal(int32_t(100))}}; + Constraint lts200{LtS, {Literal(int32_t(200))}}; + EXPECT_EQ(AndedConstraintSet{lts100}.proves(lts200), True); +} + TEST(ConstraintTest, EmptySpanContradiction) { // Impossible constraints produce empty spans. Constraint gtsMax32{GtS, {Literal(std::numeric_limits::max())}}; From 2960da75eb645b5aeb16d519a399e9381e490cb0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 Aug 2026 14:42:38 -0700 Subject: [PATCH 24/32] work --- test/gtest/constraint.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 4c107087094..751bd2f7f6d 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -965,6 +965,15 @@ TEST(ConstraintTest, SpanOptimizations) { Constraint lts100{LtS, {Literal(int32_t(100))}}; Constraint lts200{LtS, {Literal(int32_t(200))}}; EXPECT_EQ(AndedConstraintSet{lts100}.proves(lts200), True); + + // Mixing signed and unsigned works fine: x in [0, 100] (x <= 100 unsigned) + // proves x in [-MIN_INT, 200] (x < 200 signed) is true. + Constraint leu100{LtU, {Literal(int32_t(100))}}; + EXPECT_EQ(AndedConstraintSet{leu100}.proves(lts200), True); + + // Replacing 100 with 500, we can no longer prove anything. + Constraint leu500{LtU, {Literal(int32_t(500))}}; + EXPECT_EQ(AndedConstraintSet{leu500}.proves(lts200), Unknown); } TEST(ConstraintTest, EmptySpanContradiction) { From 2188b3720e06681a45c196d6c3ec21109fbc0116 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 Aug 2026 16:58:12 -0700 Subject: [PATCH 25/32] feedback --- src/support/span.h | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/support/span.h b/src/support/span.h index dbd301089cd..56f070c06c2 100644 --- a/src/support/span.h +++ b/src/support/span.h @@ -43,16 +43,14 @@ template struct Span { // To represent an empty span, we use min > max, an impossible span. void setEmpty() { - min = 1; - max = 0; + *this = empty(); + assert(isEmpty()); } bool isEmpty() const { return min > max; } static Span empty() { - Span ret; - ret.setEmpty(); - return ret; + return Span{Max, Min}; } void setFull() { @@ -63,9 +61,7 @@ template struct Span { bool isFull() const { return min == Min && max == Max; } static Span full() { - Span ret; - ret.setFull(); - return ret; + return Span{}; } // Intersect this with another span, returning a (possibly empty) span. From 073fbaf77727eeb8baedde156056a930ca362dd8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 19 Aug 2026 16:58:18 -0700 Subject: [PATCH 26/32] format --- src/support/span.h | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/support/span.h b/src/support/span.h index 56f070c06c2..05cdbce65a9 100644 --- a/src/support/span.h +++ b/src/support/span.h @@ -49,9 +49,7 @@ template struct Span { bool isEmpty() const { return min > max; } - static Span empty() { - return Span{Max, Min}; - } + static Span empty() { return Span{Max, Min}; } void setFull() { *this = Span(); @@ -60,9 +58,7 @@ template struct Span { bool isFull() const { return min == Min && max == Max; } - static Span full() { - return Span{}; - } + static Span full() { return Span{}; } // Intersect this with another span, returning a (possibly empty) span. Span intersection(const Span& other) const { From e14eaf0081727df6db85759905d84a0e180fb3d8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 20 Aug 2026 09:02:06 -0700 Subject: [PATCH 27/32] todos --- src/ir/constraint.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index b096194324f..c64c7313a52 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -344,6 +344,8 @@ void AndedConstraintSet::approximateAnd(const Constraint& c) { } } + // TODO: use Spans here when possible + if (size() < MaxConstraints) { // Insert into the right place, keeping us sorted. insert(std::upper_bound(begin(), end(), c), c); @@ -505,6 +507,8 @@ bool AndedConstraintSet::approximateOr(const AndedConstraintSet& other) { return true; } + // TODO: use Spans here when possible + // For more complex cases, do a detailed analysis. auto result = detailedApproximateOr(*this, other); auto changed = (result != *this); From d332f0f166959596ffad066dbf9cd508da1a695e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 20 Aug 2026 09:31:13 -0700 Subject: [PATCH 28/32] fix --- src/ir/constraint.cpp | 45 ++++++++++++++----- .../lit/passes/constraint-analysis-loops.wast | 2 +- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index c64c7313a52..bfd2eb7a60d 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -644,37 +644,50 @@ void BasicBlockConstraintMap::set(Index index, Expression* value) { // Apply a constraint to a value, x = C. if (Properties::isSingleConstantExpression(value)) { auto c = Properties::getLiteral(value); - set(index, Constraint{Abstract::Eq, {c}}); + set(index, Constraint{Eq, {c}}); return; } // Apply a constraint to a local, x = y. if (auto* get = value->dynCast()) { - set(index, Constraint{Abstract::Eq, {get->index}}); + set(index, Constraint{Eq, {get->index}}); return; } if (auto* tee = value->dynCast()) { - set(index, Constraint{Abstract::Eq, {tee->index}}); + set(index, Constraint{Eq, {tee->index}}); return; } // Apply an increment of a local, x = y + 1. Index y; - if (matches(value, binary(Abstract::Add, local(&y), ival(1)))) { - // The local y must have old constraints that we know how to increment. - auto old = get(y); + if (matches(value, binary(Add, local(&y), ival(1)))) { + // The local y must have old constraints that we know how to increment and + // transform into new ones. + const auto old = get(y); + auto new_ = old; + + // If we see an unsigned upper bound but not a lower one, we can add a + // lower one (if we do not overflow). That is, if we see x < 100, x++, then + // we can not only update x < 100 to x <= 100, but also add x > 0 (since 0 + // is impossible after the ++). This is not possible for signed operations, + // since x++ does not prove x > 0 there (0 is not the only value that is + // <= 0). + bool hasUnsignedUpperBound = false; + bool hasUnsignedLowerBound = false; + Type type; // Iterate over the old constraints and increment each one. - for (auto iter = old.begin(); iter != old.end();) { + for (auto iter = new_.begin(); iter != new_.end();) { auto& c = *iter; auto* N = std::get_if(&c.term); if (!N) { // A non-constant term, which we don't know how to increment. Simply // remove it: we are losing proving power here, but doing so is never // invalid. - iter = old.erase(iter); + iter = new_.erase(iter); continue; } + type = N->type; switch (c.op) { // x == N, x++ => x == N+1. @@ -694,32 +707,40 @@ void BasicBlockConstraintMap::set(Index index, Expression* value) { break; case LtU: c.op = LeU; + hasUnsignedUpperBound = true; break; // x <= N, x++ => x <= N+1 if no overflow case LeS: if (N->isSignedMax()) { - iter = old.erase(iter); + iter = new_.erase(iter); continue; } *N = N->add(Literal::makeFromInt32(1, N->type)); break; case LeU: if (N->isUnsignedMax()) { - iter = old.erase(iter); + iter = new_.erase(iter); continue; } *N = N->add(Literal::makeFromInt32(1, N->type)); + hasUnsignedUpperBound = true; break; default: // Something we don't recognize. - iter = old.erase(iter); + iter = new_.erase(iter); continue; } ++iter; } - set(index, old); + if (hasUnsignedUpperBound && !hasUnsignedLowerBound) { + // We know we did not overflow (we are bounded from above), and don't have + // any lower bound, so add x > 0. + new_.approximateAnd({GtU, Literal::makeFromInt32(0, type)}); + } + + set(index, new_); return; } diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 9e503b01e95..5d7f122a69a 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -665,7 +665,7 @@ (br $out) ) ) - ;; x > 0 && x <= 100 here (but we need loops mode to get both). + ;; x > 0 && x <= 100 here. (drop (i32.gt_u (local.get $x) From 818b9c565d6687728517770da43a13fdafc122f2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 20 Aug 2026 09:48:54 -0700 Subject: [PATCH 29/32] fix --- test/gtest/constraint.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 751bd2f7f6d..47039f1ac0b 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -607,10 +607,13 @@ TEST(ConstraintTest, TestIncrement) { map.set(0, &add); check(map.get(0), {LeS, {Literal(int32_t(5))}}); - // Ditto, unsigned + // Ditto, unsigned. We also add a lower bound here, as after $0++, $0 > 0 + // (due to no overflow, proven by the upper bound). + Constraint gtu0{GtU, {Literal(int32_t(0))}}; map.set(0, {LtU, {Literal(int32_t(5))}}); map.set(0, &add); - check(map.get(0), {LeU, {Literal(int32_t(5))}}); + EXPECT_EQ(map.get(0), + (AndedConstraintSet{{LeU, {Literal(int32_t(5))}}, gtu0})); // $0 <= 5, $0++ => $0 <= 6 (signed) map.set(0, {LeS, {Literal(int32_t(5))}}); @@ -620,7 +623,8 @@ TEST(ConstraintTest, TestIncrement) { // Ditto, unsigned map.set(0, {LeU, {Literal(int32_t(5))}}); map.set(0, &add); - check(map.get(0), {LeU, {Literal(int32_t(6))}}); + EXPECT_EQ(map.get(0), + (AndedConstraintSet{{LeU, {Literal(int32_t(6))}}, gtu0})); // $0 <= max_signed, $0++ => nothing, because it would overflow map.set(0, {LeS, {Literal::makeSignedMax(Type::i32)}}); @@ -636,7 +640,9 @@ TEST(ConstraintTest, TestIncrement) { map.set(0, {LeU, {Literal::makeSignedMax(Type::i32)}}); map.set(0, &add); auto one = Literal::makeFromInt32(1, Type::i32); - check(map.get(0), {LeU, {Literal::makeSignedMax(Type::i32).add(one)}}); + EXPECT_EQ(map.get(0), + (AndedConstraintSet{ + {LeU, {Literal::makeSignedMax(Type::i32).add(one)}}, gtu0})); // Multiple constraints at once: // $0 >= 10 && $0 < 20, $0++ => $0 > 10 && $0 <= 20 From e8bec8cddb39d80a9d628ccfaa52b2832d907797 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 20 Aug 2026 09:57:21 -0700 Subject: [PATCH 30/32] 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 1bfea0ad8ef..85280dcbd1e 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -67,7 +67,7 @@ struct Constraint { } // Convert the constraint into a constant span, if possible. For example, - // "< 100 (unsigned)" turns into the span [0, 100]. + // "<= 100 (unsigned)" turns into the span [0, 100]. std::optional> getSpan() const; }; From 2f06af40a65ddebae6fb52ab5967e3ecb07d4a2f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 20 Aug 2026 11:06:04 -0700 Subject: [PATCH 31/32] braces --- 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 bfd2eb7a60d..7d02345ac1a 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -159,8 +159,8 @@ Result provesConstantPair(Abstract::Op aOp, } // If we can represent both as spans, we can calculate that way. - if (auto aSpan = Constraint{aOp, aConstant}.getSpan()) { - if (auto bSpan = Constraint{bOp, bConstant}.getSpan()) { + if (auto aSpan = Constraint{aOp, {aConstant}}.getSpan()) { + if (auto bSpan = Constraint{bOp, {bConstant}}.getSpan()) { if (aSpan->isEmpty()) { // An empty span implies a contradiction (e.g. x > MAX_INT), as it means // no possible number can apply. And contradictions prove anything. @@ -737,7 +737,7 @@ void BasicBlockConstraintMap::set(Index index, Expression* value) { if (hasUnsignedUpperBound && !hasUnsignedLowerBound) { // We know we did not overflow (we are bounded from above), and don't have // any lower bound, so add x > 0. - new_.approximateAnd({GtU, Literal::makeFromInt32(0, type)}); + new_.approximateAnd({GtU, {Literal::makeFromInt32(0, type)}}); } set(index, new_); From 7659e7c72404465d3082a406a46e524392cb2509 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 20 Aug 2026 16:51:39 -0700 Subject: [PATCH 32/32] simpl --- src/ir/constraint.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 7d02345ac1a..44009e02ec3 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -673,7 +673,6 @@ void BasicBlockConstraintMap::set(Index index, Expression* value) { // since x++ does not prove x > 0 there (0 is not the only value that is // <= 0). bool hasUnsignedUpperBound = false; - bool hasUnsignedLowerBound = false; Type type; // Iterate over the old constraints and increment each one. @@ -734,9 +733,8 @@ void BasicBlockConstraintMap::set(Index index, Expression* value) { ++iter; } - if (hasUnsignedUpperBound && !hasUnsignedLowerBound) { - // We know we did not overflow (we are bounded from above), and don't have - // any lower bound, so add x > 0. + if (hasUnsignedUpperBound) { + // We know we did not overflow (we are bounded from above), so add x > 0. new_.approximateAnd({GtU, {Literal::makeFromInt32(0, type)}}); }