This patch introduces a new template class:
ana::concrete::range<ConcreteValueType>
for storing a unit-safe range of arbitrary-precision integer values,
such as:
- bits [0, 3)
- bytes [0, 1024)
- integers [5, 10) (unitless)
As in the previous patch, nothing uses this yet, but it is used in
my upcoming rewrite of the analyzer's state-tracking for ranges that
have concretely-known endpoints.
gcc/ChangeLog:
* Makefile.in (ANALYZER_OBJS): Add analyzer/concrete-ranges.o.
gcc/analyzer/ChangeLog:
* analyzer-selftests.cc (run_analyzer_selftests): Call
analyzer_concrete_ranges_cc_tests.
* analyzer-selftests.h (analyzer_concrete_ranges_cc_tests): New
decl.
* concrete-ranges.cc: New file, based in parts on material from
ranges.cc
* concrete-ranges.h: New file.
Signed-off-by: David Malcolm <[email protected]>
---
gcc/Makefile.in | 1 +
gcc/analyzer/analyzer-selftests.cc | 1 +
gcc/analyzer/analyzer-selftests.h | 1 +
gcc/analyzer/concrete-ranges.cc | 770 +++++++++++++++++++++++++++++
gcc/analyzer/concrete-ranges.h | 142 ++++++
5 files changed, 915 insertions(+)
create mode 100644 gcc/analyzer/concrete-ranges.cc
create mode 100644 gcc/analyzer/concrete-ranges.h
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 7f95cbb7cdf..962debed442 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1362,6 +1362,7 @@ ANALYZER_OBJS = \
analyzer/checker-event.o \
analyzer/checker-path.o \
analyzer/complexity.o \
+ analyzer/concrete-ranges.o \
analyzer/constraint-manager.o \
analyzer/diagnostic-manager.o \
analyzer/engine.o \
diff --git a/gcc/analyzer/analyzer-selftests.cc
b/gcc/analyzer/analyzer-selftests.cc
index 1cfb2007a63..a9dc6d9712d 100644
--- a/gcc/analyzer/analyzer-selftests.cc
+++ b/gcc/analyzer/analyzer-selftests.cc
@@ -49,6 +49,7 @@ run_analyzer_selftests ()
{
#if ENABLE_ANALYZER
analyzer_access_diagram_cc_tests ();
+ analyzer_concrete_ranges_cc_tests ();
analyzer_constraint_manager_cc_tests ();
analyzer_function_set_cc_tests ();
analyzer_integer_cc_tests ();
diff --git a/gcc/analyzer/analyzer-selftests.h
b/gcc/analyzer/analyzer-selftests.h
index f32846fdcf8..6976aab2294 100644
--- a/gcc/analyzer/analyzer-selftests.h
+++ b/gcc/analyzer/analyzer-selftests.h
@@ -34,6 +34,7 @@ extern void run_analyzer_selftests ();
/* Declarations for specific families of tests (by source file), in
alphabetical order. */
extern void analyzer_access_diagram_cc_tests ();
+extern void analyzer_concrete_ranges_cc_tests ();
extern void analyzer_constraint_manager_cc_tests ();
extern void analyzer_function_set_cc_tests ();
extern void analyzer_integer_cc_tests ();
diff --git a/gcc/analyzer/concrete-ranges.cc b/gcc/analyzer/concrete-ranges.cc
new file mode 100644
index 00000000000..788bd1bddc0
--- /dev/null
+++ b/gcc/analyzer/concrete-ranges.cc
@@ -0,0 +1,770 @@
+/* Unit-safe concrete ranges of values (e.g. "bits 16-31", "bytes 0-1023").
+ Copyright (C) 2019-2026 Free Software Foundation, Inc.
+ Contributed by David Malcolm <[email protected]>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "analyzer/common.h"
+
+#include "selftest.h"
+
+#include "diagnostics/markup.h"
+
+#include "analyzer/concrete-ranges.h"
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+template <typename ConcreteValueType>
+static void
+dump_any_singular_name (pretty_printer *);
+
+template <>
+void
+dump_any_singular_name<integer> (pretty_printer *)
+{
+ // no-op
+}
+
+template <>
+void
+dump_any_singular_name<integer_with_unit<units::bit>> (pretty_printer *pp)
+{
+ pp_string (pp, "bit ");
+}
+
+template <>
+void
+dump_any_singular_name<integer_with_unit<units::byte>> (pretty_printer *pp)
+{
+ pp_string (pp, "byte ");
+}
+
+template <typename ConcreteValueType>
+static void
+dump_any_plural_name (pretty_printer *);
+
+template <>
+void
+dump_any_plural_name<integer> (pretty_printer *)
+{
+ // no-op
+}
+
+template <>
+void
+dump_any_plural_name<integer_with_unit<units::bit>> (pretty_printer *pp)
+{
+ pp_string (pp, "bits ");
+}
+
+template <>
+void
+dump_any_plural_name<integer_with_unit<units::byte>> (pretty_printer *pp)
+{
+ pp_string (pp, "bytes ");
+}
+
+template<typename ConcreteValueType>
+void
+concrete::range<ConcreteValueType>::dump_to_pp (pretty_printer *pp) const
+{
+ pp_character (pp, '[');
+ if (m_size.get_integer () == 0)
+ {
+ pp_character (pp, ']');
+ return;
+ }
+ if (m_size.get_integer () == 1)
+ {
+ dump_any_singular_name<ConcreteValueType> (pp);
+ m_start_offset.dump_to_pp (pp);
+ pp_character (pp, ']');
+ return;
+ }
+ dump_any_plural_name<ConcreteValueType> (pp);
+ m_start_offset.dump_to_pp (pp);
+ pp_string (pp, "..");
+ get_next_offset ().dump_to_pp (pp);
+ pp_character (pp, ')');
+}
+
+/* "_pretty" variant. Same for bytes */
+
+template<>
+void
+concrete_byte_range::dump_to_pp_pretty (pretty_printer *pp) const
+{
+ dump_to_pp (pp);
+}
+
+// ...but for bits, try to express as bytes if possible:
+
+template<>
+void
+concrete_bit_range::dump_to_pp_pretty (pretty_printer *pp) const
+{
+ concrete::range<integer_with_unit<units::byte>> byte_range;
+ if (maybe_bytes_from_bits (*this, &byte_range))
+ byte_range.dump_to_pp (pp);
+ else
+ dump_to_pp (pp);
+}
+
+template<>
+DEBUG_FUNCTION void
+concrete_bit_range::dump () const
+{
+ pretty_printer pp;
+ pp.set_output_stream (stderr);
+ dump_to_pp_pretty (&pp);
+ pp_newline (&pp);
+ pp_flush (&pp);
+}
+
+template<>
+DEBUG_FUNCTION void
+concrete_byte_range::dump () const
+{
+ pretty_printer pp;
+ pp.set_output_stream (stderr);
+ dump_to_pp_pretty (&pp);
+ pp_newline (&pp);
+ pp_flush (&pp);
+}
+
+template<typename ConcreteValueType>
+int
+concrete::range<ConcreteValueType>::cmp (const range &lhs,
+ const range &rhs)
+{
+ if (int c = ConcreteValueType::cmp (lhs.get_start_offset (),
+ rhs.get_start_offset ()))
+ return c;
+ return ConcreteValueType::cmp (lhs.get_size (),
+ rhs.get_size ());
+}
+
+template<typename ConcreteValueType>
+bool
+concrete::range<ConcreteValueType>::intersects_p (const range &other,
+ range *out) const
+{
+ if (get_start_offset () >= other.get_next_offset ())
+ return false;
+ if (get_next_offset () <= other.get_start_offset ())
+ return false;
+ if (out)
+ {
+ ConcreteValueType overlap_start
+ = std::max (get_start_offset (),
+ other.get_start_offset ());
+ ConcreteValueType overlap_next
+ = std::min (get_next_offset (),
+ other.get_next_offset ());
+ gcc_assert (overlap_next > overlap_start);
+ *out = range<ConcreteValueType> (overlap_start, overlap_next -
overlap_start);
+ }
+ return true;
+}
+
+template
+bool concrete_bit_range::intersects_p (const concrete_bit_range &other,
+ concrete_bit_range *out) const;
+
+/* Return true if any part of THIS is >= OFFSET, and write the overhanging
+ range to *OUT. */
+
+template<typename ConcreteValueType>
+bool
+concrete::range<ConcreteValueType>::
+exceeds_p (ConcreteValueType offset,
+ range<ConcreteValueType> *out) const
+{
+ ConcreteValueType next_offset = get_next_offset ();
+ if (next_offset > offset)
+ {
+ /* THIS exceeds OFFSET. */
+ auto start = std::max (get_start_offset (), offset);
+ auto size = next_offset - start;
+ *out = range<ConcreteValueType> (start, size);
+ return true;
+ }
+ return false;
+}
+
+template
+bool concrete_bit_range::exceeds_p (integer_with_unit<units::bit>,
+ concrete_bit_range *) const;
+
+/* Return true if any part of this THIS < OFFSET and write the
+ bit range fallen short to *OUT. */
+
+template<typename ConcreteValueType>
+bool
+concrete::range<ConcreteValueType>::
+falls_short_of_p (ConcreteValueType offset,
+ range<ConcreteValueType> *out) const
+{
+ ConcreteValueType start_offset = get_start_offset ();
+ if (start_offset < offset)
+ {
+ /* THIS falls short of OFFSET. */
+ auto start = start_offset;
+ auto size = std::min (offset, get_next_offset ()) - start;
+ *out = range<ConcreteValueType> (start, size);
+ return true;
+ }
+ return false;
+}
+
+template
+bool concrete_bit_range::falls_short_of_p (integer_with_unit<units::bit>,
+ concrete_bit_range *) const;
+
+template<typename ConcreteValueType>
+std::unique_ptr<json::object>
+concrete::range<ConcreteValueType>::to_json () const
+{
+ auto obj = std::make_unique<json::object> ();
+ obj->set ("start_offset", m_start_offset.to_json ());
+ obj->set ("size", m_size.to_json ());
+ return obj;
+}
+
+template
+std::unique_ptr<json::object>
+concrete_bit_range::to_json () const;
+
+template<typename ConcreteValueType>
+std::unique_ptr<diagnostics::markup::node>
+concrete::range<ConcreteValueType>::
+make_markup_node () const
+{
+ return std::make_unique<diagnostics::markup::math::half_open_interval>
+ (m_start_offset.make_markup_node (),
+ get_next_offset ().make_markup_node ());
+}
+
+template
+std::unique_ptr<diagnostics::markup::node>
+concrete_bit_range::make_markup_node () const;
+
+bool
+maybe_bytes_from_bits (concrete::range<integer_with_unit<units::bit>> bits,
+ concrete::range<integer_with_unit<units::byte>> *out)
+{
+ integer_with_unit<units::byte> start_byte_offset;
+ if (!maybe_bytes_from_bits (bits.get_start_offset (),
+ &start_byte_offset))
+ return false;
+ integer_with_unit<units::byte> byte_size;
+ if (!maybe_bytes_from_bits (bits.get_size (),
+ &byte_size))
+ return false;
+ *out = concrete::range<integer_with_unit<units::byte>> (start_byte_offset,
+ byte_size);
+ return true;
+}
+
+/* If MASK is a contiguous range of set bits, write them
+ to *OUT and return true.
+ Otherwise return false. */
+
+bool
+concrete_bit_range_from_mask (unsigned HOST_WIDE_INT mask,
+ concrete_bit_range *out)
+{
+ concrete_bit_offset iter_bit_idx = 0_bits;
+ unsigned HOST_WIDE_INT iter_bit_mask = 1;
+
+ concrete_bit_size host_bits_per_wide_int (HOST_BITS_PER_WIDE_INT);
+
+ /* Find the first contiguous run of set bits in MASK. */
+
+ /* Find first set bit in MASK. */
+ while (iter_bit_idx < host_bits_per_wide_int)
+ {
+ if (mask & iter_bit_mask)
+ break;
+ iter_bit_idx += 1_bit;
+ iter_bit_mask <<= 1;
+ }
+ if (iter_bit_idx == host_bits_per_wide_int)
+ /* MASK is zero. */
+ return false;
+
+ concrete_bit_offset first_set_iter_bit_idx (iter_bit_idx);
+ concrete_bit_size num_set_bits = 1_bit;
+ iter_bit_idx += 1_bit;
+ iter_bit_mask <<= 1;
+
+ /* Find next unset bit in MASK. */
+ while (iter_bit_idx < host_bits_per_wide_int)
+ {
+ if (!(mask & iter_bit_mask))
+ break;
+ num_set_bits += 1_bit;
+ iter_bit_idx += 1_bit;
+ iter_bit_mask <<= 1;
+ }
+ if (iter_bit_idx == host_bits_per_wide_int)
+ {
+ *out = concrete_bit_range (first_set_iter_bit_idx, num_set_bits);
+ return true;
+ }
+
+ /* We now have the first contiguous run of set bits in MASK.
+ Fail if any other bits are set. */
+ while (iter_bit_idx < host_bits_per_wide_int)
+ {
+ if (mask & iter_bit_mask)
+ return false;
+ iter_bit_idx += 1_bit;
+ iter_bit_mask <<= 1;
+ }
+
+ *out = concrete_bit_range (first_set_iter_bit_idx, num_set_bits);
+ return true;
+}
+
+void
+get_smallest_enclosing_byte_range (const concrete_bit_range &in_bit_range,
+ concrete_byte_range *out_byte_range,
+ concrete_bit_offset *out_bit_offset)
+{
+ auto round_bit_to_lowest_byte = [](concrete_bit_offset bit_offset)
+ -> concrete_byte_offset
+ {
+ return concrete_byte_offset
+ (bit_offset.get_integer ().floor_div (BITS_PER_UNIT));
+ };
+ auto bit_within_byte = [](concrete_bit_offset bit_offset)
+ -> concrete_bit_offset
+ {
+ return concrete_bit_offset
+ (bit_offset.get_integer ().floor_mod (BITS_PER_UNIT));
+ };
+
+ *out_byte_range = concrete_byte_range
+ (round_bit_to_lowest_byte (in_bit_range.get_start_offset ()),
+ round_bit_to_lowest_byte (in_bit_range.get_size () + 7_bits));
+ *out_bit_offset = bit_within_byte (in_bit_range.get_start_offset ());
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Implementation detail of ASSERT_OVERLAP. */
+
+template<typename Unit>
+static void
+assert_overlap (const ::selftest::location &loc,
+ const concrete::range<Unit> &r1,
+ const concrete::range<Unit> &r2,
+ const concrete::range<Unit> &expected_overlap)
+{
+ // Test both ways round
+ {
+ concrete::range<Unit> actual_overlap;
+ ASSERT_TRUE_AT (loc, r1.intersects_p (r2, &actual_overlap));
+ ASSERT_EQ_AT (loc, actual_overlap, expected_overlap);
+ }
+ {
+ concrete::range<Unit> actual_overlap;
+ ASSERT_TRUE_AT (loc, r2.intersects_p (r1, &actual_overlap));
+ ASSERT_EQ_AT (loc, actual_overlap, expected_overlap);
+ }
+}
+
+/* Implementation detail of ASSERT_DISJOINT. */
+
+template<typename Unit>
+static void
+assert_disjoint (const ::selftest::location &loc,
+ const concrete::range<Unit> &r1,
+ const concrete::range<Unit> &r2)
+{
+ // Test both ways round
+ ASSERT_FALSE_AT (loc, r1.intersects_p (r2));
+ ASSERT_FALSE_AT (loc, r2.intersects_p (r1));
+}
+
+/* Assert that R1 and R2 overlap, checking both ways. */
+
+#define ASSERT_OVERLAP(R1, R2, EXPECTED_OVERLAP) \
+ SELFTEST_BEGIN_STMT \
+ assert_overlap (SELFTEST_LOCATION, (R1), (R2), (EXPECTED_OVERLAP)); \
+ SELFTEST_END_STMT
+
+/* Assert that R1 and R2 do not overlap, checking both ways. */
+
+#define ASSERT_DISJOINT(R1, R2) \
+ SELFTEST_BEGIN_STMT \
+ assert_disjoint (SELFTEST_LOCATION, (R1), (R2)); \
+ SELFTEST_END_STMT
+
+template<typename ConcreteValueType>
+static void
+test_intersections ()
+{
+ auto make_range = [] (int start, int size)
+ {
+ return concrete::range<ConcreteValueType> (ConcreteValueType(start),
+ ConcreteValueType(size));
+ };
+
+ /* Various size 8 ranges. */
+ concrete::range<ConcreteValueType> r0_7 = make_range (0, 8);
+ concrete::range<ConcreteValueType> r8_15 = make_range (8, 8);
+ concrete::range<ConcreteValueType> r16_23 = make_range (16, 8);
+ concrete::range<ConcreteValueType> r24_31 = make_range (24, 8);
+
+ /* Various size 16 ranges. */
+ concrete::range<ConcreteValueType> r0_15 = make_range (0, 16);
+ concrete::range<ConcreteValueType> r8_23 = make_range (8, 16);
+ concrete::range<ConcreteValueType> r16_31 = make_range (16, 16);
+
+ /* Size 32 range. */
+ concrete::range<ConcreteValueType> r0_31 = make_range (0, 32);
+
+ /* Everything should self-overlap. */
+ ASSERT_OVERLAP (r0_7, r0_7, r0_7);
+ ASSERT_OVERLAP (r8_15, r8_15, r8_15);
+ ASSERT_OVERLAP (r16_23, r16_23, r16_23);
+ ASSERT_OVERLAP (r24_31, r24_31, r24_31);
+ ASSERT_OVERLAP (r0_15, r0_15, r0_15);
+ ASSERT_OVERLAP (r8_23, r8_23, r8_23);
+ ASSERT_OVERLAP (r16_31, r16_31, r16_31);
+ ASSERT_OVERLAP (r0_31, r0_31, r0_31);
+
+ /* Verify the 8-bit bindings that don't overlap each other. */
+ ASSERT_DISJOINT (r0_7, r8_15);
+ ASSERT_DISJOINT (r8_15, r16_23);
+
+ /* Check for overlap of differently-sized bindings. */
+ ASSERT_OVERLAP (r0_7, r0_31, r0_7);
+ /* ...and with differing start points. */
+ ASSERT_OVERLAP (r8_15, r0_31, r8_15);
+ ASSERT_DISJOINT (r8_15, r16_31);
+ ASSERT_OVERLAP (r16_23, r0_31, r16_23);
+ ASSERT_OVERLAP (r16_31, r0_31, r16_31);
+
+ ASSERT_DISJOINT (r0_7, r8_23);
+ ASSERT_OVERLAP (r8_23, r16_23, r16_23);
+ ASSERT_OVERLAP (r8_23, r16_31, r16_23);
+ ASSERT_DISJOINT (r8_23, r24_31);
+}
+
+template<typename ConcreteValueType>
+static void
+test_falls_short_of_p ()
+{
+ using concrete_range_type = concrete::range<ConcreteValueType>;
+ auto make_range = [] (int start, int size)
+ {
+ return concrete_range_type (ConcreteValueType(start),
+ ConcreteValueType(size));
+ };
+
+ concrete_range_type rm4_m1 = make_range (-4, 4);
+ concrete_range_type r0_3 = make_range (0, 4);
+ concrete_range_type r10 = make_range (10, 1);
+
+ /* [0, 4) is fully after 0. */
+ {
+ concrete_range_type out;
+ ASSERT_FALSE (r0_3.falls_short_of_p (ConcreteValueType (0),
+ &out));
+ }
+
+ /* [0, 4) is not fully after 10; it's fully before. */
+ {
+ concrete_range_type out;
+ ASSERT_TRUE (r0_3.falls_short_of_p (ConcreteValueType (10),
+ &out));
+ ASSERT_EQ (out.get_start_offset (), ConcreteValueType (0));
+ ASSERT_EQ (out.get_size (), ConcreteValueType (4));
+ ASSERT_EQ (out.get_next_offset (), ConcreteValueType (4));
+ }
+
+ /* [0, 4) is not fully after 2. */
+ {
+ concrete_range_type out;
+ ASSERT_TRUE (r0_3.falls_short_of_p (ConcreteValueType (2),
+ &out));
+ ASSERT_EQ (out.get_start_offset (), ConcreteValueType (0));
+ ASSERT_EQ (out.get_size (), ConcreteValueType (2));
+ ASSERT_EQ (out.get_next_offset (), ConcreteValueType (2));
+ }
+
+ {
+ concrete_range_type out;
+ ASSERT_TRUE (rm4_m1.falls_short_of_p (ConcreteValueType (0),
+ &out));
+ ASSERT_EQ (out.get_start_offset (), ConcreteValueType (-4));
+ ASSERT_EQ (out.get_size (), ConcreteValueType (4));
+ ASSERT_EQ (out.get_next_offset (), ConcreteValueType (0));
+ }
+
+ {
+ concrete_range_type out;
+ ASSERT_TRUE (r10.falls_short_of_p (ConcreteValueType (15),
+ &out));
+ ASSERT_EQ (out.get_start_offset (), ConcreteValueType (10));
+ ASSERT_EQ (out.get_size (), ConcreteValueType (1));
+ ASSERT_EQ (out.get_next_offset (), ConcreteValueType (11));
+ }
+}
+
+template<typename ConcreteValueType>
+static void
+test_exceeds_p ()
+{
+ using concrete_range_type = concrete::range<ConcreteValueType>;
+ auto make_range = [] (int start, int size)
+ {
+ return concrete_range_type (ConcreteValueType(start),
+ ConcreteValueType(size));
+ };
+
+ concrete_range_type r0_3 = make_range (0, 4);
+ concrete_range_type r10 = make_range (10, 1);
+
+ /* [0, 4) is fully before 4. */
+ {
+ concrete_range_type out;
+ ASSERT_FALSE (r0_3.exceeds_p (ConcreteValueType (4),
+ &out));
+ }
+
+ /* [0, 4) is not fully before 3: the overlap is {3}. */
+ {
+ concrete_range_type out;
+ ASSERT_TRUE (r0_3.exceeds_p (ConcreteValueType (3),
+ &out));
+ ASSERT_EQ (out.get_start_offset (), ConcreteValueType (3));
+ ASSERT_EQ (out.get_size (), ConcreteValueType (1));
+ ASSERT_EQ (out.get_next_offset (), ConcreteValueType (4));
+ }
+
+ /* [0, 4) is not fully before 2: the overlap is {2, 3}. */
+ {
+ concrete_range_type out;
+ ASSERT_TRUE (r0_3.exceeds_p (ConcreteValueType (2),
+ &out));
+ ASSERT_EQ (out.get_start_offset (), ConcreteValueType (2));
+ ASSERT_EQ (out.get_size (), ConcreteValueType (2));
+ ASSERT_EQ (out.get_next_offset (), ConcreteValueType (4));
+ }
+
+ /* {10} is not fully before 5: the overlap is {10}. */
+ {
+ concrete_range_type out;
+ ASSERT_TRUE (r10.exceeds_p (ConcreteValueType (5),
+ &out));
+ ASSERT_EQ (out.get_start_offset (), ConcreteValueType (10));
+ ASSERT_EQ (out.get_size (), ConcreteValueType (1));
+ ASSERT_EQ (out.get_next_offset (), ConcreteValueType (11));
+ }
+}
+
+/* Implementation detail of ASSERT_BIT_RANGE_FROM_MASK_EQ. */
+
+static void
+assert_bit_range_from_mask_eq (const ::selftest::location &loc,
+ unsigned HOST_WIDE_INT mask,
+ const concrete_bit_range &expected)
+{
+ concrete_bit_range actual;
+ bool ok = concrete_bit_range_from_mask (mask, &actual);
+ ASSERT_TRUE_AT (loc, ok);
+ ASSERT_EQ_AT (loc, actual, expected);
+}
+
+/* Assert that concrete_bit_range_from_mask (MASK) returns true, and writes
+ out EXPECTED_BIT_RANGE. */
+
+#define ASSERT_BIT_RANGE_FROM_MASK_EQ(MASK, EXPECTED_BIT_RANGE) \
+ SELFTEST_BEGIN_STMT \
+ assert_bit_range_from_mask_eq (SELFTEST_LOCATION, MASK, \
+ EXPECTED_BIT_RANGE); \
+ SELFTEST_END_STMT
+
+/* Implementation detail of ASSERT_NO_BIT_RANGE_FROM_MASK. */
+
+static void
+assert_no_bit_range_from_mask_eq (const ::selftest::location &loc,
+ unsigned HOST_WIDE_INT mask)
+{
+ concrete_bit_range actual;
+ bool ok = concrete_bit_range_from_mask (mask, &actual);
+ ASSERT_FALSE_AT (loc, ok);
+}
+
+/* Assert that concrete_bit_range_from_mask (MASK) returns false. */
+
+#define ASSERT_NO_BIT_RANGE_FROM_MASK(MASK) \
+ SELFTEST_BEGIN_STMT \
+ assert_no_bit_range_from_mask_eq (SELFTEST_LOCATION, MASK); \
+ SELFTEST_END_STMT
+
+/* Verify that concrete_bit_range_from_mask works as expected. */
+
+static void
+test_bit_range_from_mask ()
+{
+ /* Should fail on zero. */
+ ASSERT_NO_BIT_RANGE_FROM_MASK (0);
+
+ /* Verify 1-bit masks. */
+ ASSERT_BIT_RANGE_FROM_MASK_EQ (1, concrete_bit_range (0_bits, 1_bit));
+ ASSERT_BIT_RANGE_FROM_MASK_EQ (2, concrete_bit_range (1_bit, 1_bit));
+ ASSERT_BIT_RANGE_FROM_MASK_EQ (4, concrete_bit_range (2_bits, 1_bit));
+ ASSERT_BIT_RANGE_FROM_MASK_EQ (8, concrete_bit_range (3_bits, 1_bit));
+ ASSERT_BIT_RANGE_FROM_MASK_EQ (16, concrete_bit_range (4_bits, 1_bit));
+ ASSERT_BIT_RANGE_FROM_MASK_EQ (32, concrete_bit_range (5_bits, 1_bit));
+ ASSERT_BIT_RANGE_FROM_MASK_EQ (64, concrete_bit_range (6_bits, 1_bit));
+ ASSERT_BIT_RANGE_FROM_MASK_EQ (128, concrete_bit_range (7_bits, 1_bit));
+
+ /* Verify N-bit masks starting at bit 0. */
+ ASSERT_BIT_RANGE_FROM_MASK_EQ (3, concrete_bit_range (0_bits, 2_bits));
+ ASSERT_BIT_RANGE_FROM_MASK_EQ (7, concrete_bit_range (0_bits, 3_bits));
+ ASSERT_BIT_RANGE_FROM_MASK_EQ (15, concrete_bit_range (0_bits, 4_bits));
+ ASSERT_BIT_RANGE_FROM_MASK_EQ (31, concrete_bit_range (0_bits, 5_bits));
+ ASSERT_BIT_RANGE_FROM_MASK_EQ (63, concrete_bit_range (0_bits, 6_bits));
+ ASSERT_BIT_RANGE_FROM_MASK_EQ (127, concrete_bit_range (0_bits, 7_bits));
+ ASSERT_BIT_RANGE_FROM_MASK_EQ (255, concrete_bit_range (0_bits, 8_bits));
+ ASSERT_BIT_RANGE_FROM_MASK_EQ (0xffff, concrete_bit_range (0_bits, 16_bits));
+
+ /* Various other tests. */
+ ASSERT_BIT_RANGE_FROM_MASK_EQ (0x30, concrete_bit_range (4_bits, 2_bits));
+ ASSERT_BIT_RANGE_FROM_MASK_EQ (0x700, concrete_bit_range (8_bits, 3_bits));
+ ASSERT_BIT_RANGE_FROM_MASK_EQ (0x600, concrete_bit_range (9_bits, 2_bits));
+
+ /* Multiple ranges of set bits should fail. */
+ ASSERT_NO_BIT_RANGE_FROM_MASK (0x101);
+ ASSERT_NO_BIT_RANGE_FROM_MASK (0xf0f0f0f0);
+}
+
+static void
+test_get_smallest_enclosing_byte_range ()
+{
+ // bit 0 should be bit 0 of byte 0
+ {
+ concrete_byte_range out_byte_range;
+ concrete_bit_offset out_bit_offset;
+ get_smallest_enclosing_byte_range
+ (concrete_bit_range (0_bits, 1_bit),
+ &out_byte_range,
+ &out_bit_offset);
+ ASSERT_EQ (out_byte_range, concrete_byte_range (0_bytes, 1_byte));
+ ASSERT_EQ (out_bit_offset, 0_bits);
+ }
+
+ // bits 1-3 should be bits 1-3 of byte 0
+ {
+ concrete_byte_range out_byte_range;
+ concrete_bit_offset out_bit_offset;
+ get_smallest_enclosing_byte_range
+ (concrete_bit_range (1_bit, 1_bit),
+ &out_byte_range,
+ &out_bit_offset);
+ ASSERT_EQ (out_byte_range, concrete_byte_range (0_bytes, 1_byte));
+ ASSERT_EQ (out_bit_offset, 1_bit);
+ }
+
+ // bits [16, 24) should be all of byte 2
+ {
+ concrete_byte_range out_byte_range;
+ concrete_bit_offset out_bit_offset;
+ get_smallest_enclosing_byte_range
+ (concrete_bit_range (16_bits, 8_bits),
+ &out_byte_range,
+ &out_bit_offset);
+ ASSERT_EQ (out_byte_range, concrete_byte_range (2_bytes, 1_byte));
+ ASSERT_EQ (out_bit_offset, 0_bits);
+ }
+
+ // bits 17-18 should be bits 1-2 of byte 2
+ {
+ concrete_byte_range out_byte_range;
+ concrete_bit_offset out_bit_offset;
+ get_smallest_enclosing_byte_range
+ (concrete_bit_range (17_bits, 2_bits),
+ &out_byte_range,
+ &out_bit_offset);
+ ASSERT_EQ (out_byte_range, concrete_byte_range (2_bytes, 1_byte));
+ ASSERT_EQ (out_bit_offset, 1_bit);
+ }
+}
+
+/* Run all of the selftests within this file. */
+
+void
+analyzer_concrete_ranges_cc_tests ()
+{
+ concrete_bit_range bits_16_to_23 (16_bits, 8_bits);
+ ASSERT_EQ (bits_16_to_23.get_start_offset (), 16_bits);
+ ASSERT_EQ (bits_16_to_23.get_size (), 8_bits);
+ ASSERT_EQ (bits_16_to_23.get_last_offset (), 23_bits);
+ ASSERT_EQ (bits_16_to_23.get_next_offset (), 24_bits);
+ bits_16_to_23.to_json ();
+ ASSERT_MARKUP_TEXT_EQ (*bits_16_to_23.make_markup_node (),
+ diagnostics::selftest::get_ascii_markup_opts (),
+ "[16, 24)");
+
+ concrete_byte_range bytes_16_to_23 (16_bytes, 8_bytes);
+ ASSERT_EQ (bytes_16_to_23.get_start_offset (), 16_bytes);
+ ASSERT_EQ (bytes_16_to_23.get_size (), 8_bytes);
+ ASSERT_EQ (bytes_16_to_23.get_last_offset (), 23_bytes);
+ ASSERT_EQ (bytes_16_to_23.get_next_offset (), 24_bytes);
+ bytes_16_to_23.to_json ();
+
+ test_intersections<integer_with_unit<units::bit>> ();
+ test_intersections<integer_with_unit<units::byte>> ();
+ test_falls_short_of_p<integer_with_unit<units::bit>> ();
+ test_exceeds_p<integer_with_unit<units::bit>> ();
+
+ test_bit_range_from_mask ();
+ test_get_smallest_enclosing_byte_range ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
+
+} // namespace ana
+
+/* Ensure we instantiate these. */
+
+template
+void
+ana::concrete_bit_range::dump_to_pp (pretty_printer *pp) const;
+
+template
+void
+ana::concrete_byte_range::dump_to_pp (pretty_printer *pp) const;
+
+template
+int
+ana::concrete_bit_range::cmp (const ana::concrete_bit_range &lhs,
+ const ana::concrete_bit_range &rhs);
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/concrete-ranges.h b/gcc/analyzer/concrete-ranges.h
new file mode 100644
index 00000000000..c0e3f860df9
--- /dev/null
+++ b/gcc/analyzer/concrete-ranges.h
@@ -0,0 +1,142 @@
+/* Unit-safe concrete ranges of values (e.g. "bits 16-31", "bytes 0-1023").
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ Contributed by David Malcolm <[email protected]>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_ANALYZER_CONCRETE_RANGES_H
+#define GCC_ANALYZER_CONCRETE_RANGES_H
+
+#include "analyzer/integer-with-unit.h"
+
+namespace ana {
+namespace concrete {
+
+/* Concrete ranges of bits or bytes (or unitless). */
+
+template<typename ConcreteValueType>
+class range
+{
+public:
+ range () = default;
+
+ range (ConcreteValueType start_offset,
+ ConcreteValueType size)
+ : m_start_offset (start_offset),
+ m_size (size)
+ {
+ }
+
+ void
+ dump () const;
+
+ void
+ dump_to_pp (pretty_printer *pp) const;
+
+ void
+ dump_to_pp_pretty (pretty_printer *pp) const;
+
+ ConcreteValueType
+ get_start_offset () const { return m_start_offset; };
+
+ ConcreteValueType
+ get_size () const { return m_size; }
+
+ ConcreteValueType
+ get_last_offset () const
+ {
+ return get_next_offset () - ConcreteValueType (1);
+ }
+
+ ConcreteValueType
+ get_next_offset () const { return m_start_offset + m_size; }
+
+ bool
+ contains_p (ConcreteValueType offset) const
+ {
+ return (offset >= get_start_offset ()
+ && offset < get_next_offset ());
+ }
+
+ bool
+ intersects_p (const range &other, range *out = nullptr) const;
+
+ bool
+ exceeds_p (ConcreteValueType offset,
+ range<ConcreteValueType> *out) const;
+
+ bool
+ falls_short_of_p (ConcreteValueType offset,
+ range<ConcreteValueType> *out) const;
+
+ bool
+ operator== (const range<ConcreteValueType> &other) const
+ {
+ return (m_start_offset == other.m_start_offset
+ && m_size == other.m_size);
+ }
+
+ static int
+ cmp (const range<ConcreteValueType> &lhs,
+ const range<ConcreteValueType> &rhs);
+
+ bool
+ operator< (const range<ConcreteValueType> &other) const
+ {
+ return cmp (*this, other) < 0;
+ }
+
+ std::unique_ptr<json::object>
+ to_json () const;
+
+
+ std::unique_ptr<diagnostics::markup::node>
+ make_markup_node () const;
+
+private:
+ ConcreteValueType m_start_offset;
+ ConcreteValueType m_size;
+};
+
+} // namespace ana::concrete
+
+using concrete_bit_range = concrete::range<integer_with_unit<units::bit>>;
+using concrete_byte_range = concrete::range<integer_with_unit<units::byte>>;
+
+inline concrete_bit_range
+bits_from_bytes (concrete_byte_range bytes)
+{
+ return concrete_bit_range (bits_from_bytes (bytes.get_start_offset ()),
+ bits_from_bytes (bytes.get_size ()));
+}
+
+bool
+maybe_bytes_from_bits (concrete_bit_range bits,
+ concrete_byte_range *out);
+
+bool
+concrete_bit_range_from_mask (unsigned HOST_WIDE_INT mask,
+ concrete_bit_range *out);
+
+void
+get_smallest_enclosing_byte_range (const concrete_bit_range &in_bit_range,
+ concrete_byte_range *out_byte_range,
+ concrete_bit_offset *out_bit_offset);
+
+} // namespace ana
+
+#endif /* GCC_ANALYZER_CONCRETE_RANGES_H */
--
2.56.0