While looking at something else, I decided to write some self-tests for
the bound-snapping changes.
Along the way, I discovered a couple of things.
This patch has the self tests, and they tripped over an issue with
get_bitmask (). get_bitmask () takes the current mask, and intersect
it with a mask derived from the lower and upper bounds, giving us useful
results. However, when the 2 masks are incompatible, it was returning
bitmask_unknown, which is akin to a VARYING result. It has no way of
communicating an UNDEFINED result, which would be more appropriate.
Instead, it should just return the original mask. Any undefined results
will show up eventually when set_range_from_bitmask () is called.
This patch provides the updated get_bitmask as well as all the self tests.
Bootstraps on x86_64-pc-linux-gnu with no regressions. Pushed.
Andrew
From 5ae33c8f44f0112644b561dfc549c1dc2c679b6f Mon Sep 17 00:00:00 2001
From: Andrew MacLeod <amacl...@redhat.com>
Date: Tue, 24 Jun 2025 13:10:56 -0400
Subject: [PATCH 1/3] get_bitmask is sometimes less refined.
get_bitmask intersects the current mask with a mask generated from the
range. If the 2 masks are incompatible, it currently returns UNKNOWN.
Instead, ti should return the original mask or information is lost.
* value-range.cc (irange::get_bitmask): Return original mask if
result is unknown.
(assert_snap_result): New.
(test_irange_snap_bounds): New.
(range_tests_misc): Call test_irange_snap_bounds.
---
gcc/value-range.cc | 117 ++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 116 insertions(+), 1 deletion(-)
diff --git a/gcc/value-range.cc b/gcc/value-range.cc
index 23a5c66ed5e..85c1e26287e 100644
--- a/gcc/value-range.cc
+++ b/gcc/value-range.cc
@@ -2513,7 +2513,13 @@ irange::get_bitmask () const
// See also the note in irange_bitmask::intersect.
irange_bitmask bm (type (), lower_bound (), upper_bound ());
if (!m_bitmask.unknown_p ())
- bm.intersect (m_bitmask);
+ {
+ bm.intersect (m_bitmask);
+ // If the new intersection is unknown, it means there are inconstent
+ // bits, so simply return the original bitmask.
+ if (bm.unknown_p ())
+ return m_bitmask;
+ }
return bm;
}
@@ -2879,6 +2885,112 @@ range_tests_strict_enum ()
ASSERT_FALSE (ir1.varying_p ());
}
+// Test that range bounds are "snapped" to where they are expected to be.
+
+static void
+assert_snap_result (int lb_val, int ub_val,
+ int expected_lb, int expected_ub,
+ unsigned mask_val, unsigned value_val,
+ tree type)
+{
+ wide_int lb = wi::shwi (lb_val, TYPE_PRECISION (type));
+ wide_int ub = wi::shwi (ub_val, TYPE_PRECISION (type));
+ wide_int new_lb, new_ub;
+
+ irange_bitmask bm (wi::uhwi (value_val, TYPE_PRECISION (type)),
+ wi::uhwi (mask_val, TYPE_PRECISION (type)));
+
+ int_range_max r (type);
+ r.set (type, lb, ub);
+ r.update_bitmask (bm);
+
+ if (TYPE_SIGN (type) == SIGNED && expected_ub < expected_lb)
+ gcc_checking_assert (r.undefined_p ());
+ else if (TYPE_SIGN (type) == UNSIGNED
+ && ((unsigned)expected_ub < (unsigned)expected_lb))
+ gcc_checking_assert (r.undefined_p ());
+ else
+ {
+ gcc_checking_assert (wi::eq_p (r.lower_bound (),
+ wi::shwi (expected_lb,
+ TYPE_PRECISION (type))));
+ gcc_checking_assert (wi::eq_p (r.upper_bound (),
+ wi::shwi (expected_ub,
+ TYPE_PRECISION (type))));
+ }
+}
+
+
+// Run a selection of tests that confirm, bounds are snapped as expected.
+// We only test individual pairs, multiple pairs use the same snapping
+// routine as single pairs.
+
+static void
+test_irange_snap_bounds ()
+{
+ tree u32 = unsigned_type_node;
+ tree s32 = integer_type_node;
+ tree s8 = build_nonstandard_integer_type (8, /*unsigned=*/ 0);
+ tree s1 = build_nonstandard_integer_type (1, /*unsigned=*/ 0);
+ tree u1 = build_nonstandard_integer_type (1, /*unsigned=*/ 1);
+
+ // Basic aligned range: even-only
+ assert_snap_result (5, 15, 6, 14, 0xFFFFFFFE, 0x0, u32);
+ // Singleton that doesn't match mask: undefined.
+ assert_snap_result (7, 7, 1, 0, 0xFFFFFFFE, 0x0, u32);
+ // 8-bit signed char, mask 0xF0 (i.e. step of 16).
+ assert_snap_result (-100, 100, -96, 96, 0xF0, 0x00, s8);
+ // Already aligned range: no change.
+ assert_snap_result (0, 240, 0, 240, 0xF0, 0x00, u32);
+ // Negative range, step 16 alignment (s32).
+ assert_snap_result (-123, -17, -112, -32, 0xFFFFFFF0, 0x00, s32);
+ // Negative range, step 16 alignment (trailing-zero aligned mask).
+ assert_snap_result (-123, -17, -112, -32, 0xFFFFFFF0, 0x00, s32);
+ // s8, 16-alignment mask, value = 0 (valid).
+ assert_snap_result (-50, 10, -48, 0, 0xF0, 0x00, s8);
+ // No values in range [-3,2] match alignment except 0.
+ assert_snap_result (-3, 2, 0, 0, 0xF8, 0x00, s8);
+ // No values in range [-3,2] match alignment — undefined.
+ assert_snap_result (-3, 2, 1, 0, 0xF8, 0x04, s8);
+ // Already aligned range: no change.
+ assert_snap_result (0, 240, 0, 240, 0xF0, 0x00, s32);
+ // 1-bit signed: only -1 allowed (0b1).
+ assert_snap_result (-1, 0, -1, -1, 0x00, 0x01, s1);
+ // 1-bit signed: only 0 allowed (0b0).
+ assert_snap_result (-1, 0, 0, 0, 0x00, 0x00, s1);
+ // 1-bit signed: no match (invalid case).
+ assert_snap_result (-1, -1, 1, 0, 0x00, 0x00, s1);
+ // 1-bit signed: no match (invalid case).
+ assert_snap_result (0, 0, 1, 0, 0x00, 0x01, s1);
+ // 1-bit unsigned: only 1 allowed.
+ assert_snap_result (0, 1, 1, 1, 0x00, 0x01, u1);
+ // 1-bit unsigned: only 0 allowed.
+ assert_snap_result (0, 1, 0, 0, 0x00, 0x00, u1);
+ // 1-bit unsigned: no match (invalid case).
+ assert_snap_result (1, 1, 1, 0, 0x00, 0x00, u1);
+ // 1-bit unsigned: no match (invalid case).
+ assert_snap_result (0, 0, 1, 0, 0x00, 0x01, u1);
+ // Unsigned: Near overflow, even alignment.
+ assert_snap_result (UINT_MAX - 6, UINT_MAX, UINT_MAX - 5, UINT_MAX - 1,
+ 0xFFFFFFFE, 0x00, u32);
+ // Unsigned: Wraparound-like range — no valid snapped values.
+ assert_snap_result (UINT_MAX - 5, UINT_MAX, 1, 0, 0xFFFFFFF0, 0x00, u32);
+ // Signed: Near INT_MAX, 8-aligned.
+ assert_snap_result (INT_MAX - 18, INT_MAX, INT_MAX - 15, INT_MAX - 7,
+ 0xFFFFFFF8, 0x00, s32);
+ // Signed: Near INT_MIN, 16-aligned.
+ assert_snap_result (INT_MIN, INT_MIN + 30, INT_MIN, INT_MIN + 16,
+ 0xFFFFFFF0, 0x00, s32);
+ // Signed: Full domain, 4-aligned.
+ assert_snap_result (-128, 127, -128, 124, 0xFC, 0x00, s8);
+ // Singleton at INT_MIN that doesn’t match alignment — undefined
+ assert_snap_result (INT_MIN, INT_MIN, 1, 0, 0xFFFFFFFE, 0x01, s32);
+ // Range at INT_MIN that doesn’t match alignment — undefined.
+ assert_snap_result (INT_MIN, INT_MIN + 10, 1, 0, 0xFFFFFFF0, 0x0F, s32);
+ // Unsigned: Full domain, 256-aligned.
+ assert_snap_result (0, UINT_MAX, 0, UINT_MAX & ~255, 0xFFFFFF00, 0x00, u32);
+}
+
static void
range_tests_misc ()
{
@@ -3150,6 +3262,9 @@ range_tests_nonzero_bits ()
r0.set_zero (integer_type_node);
r0.set_nonzero_bits (INT (1));
ASSERT_TRUE (r0.zero_p ());
+
+ // Now test that range bounds are snapped to match bitmask alignments.
+ test_irange_snap_bounds ();
}
// Build an frange from string endpoints.
--
2.45.0