This patch introduces a template class ana::integer_with_unit<Unit>
wrapping the arbitrary-precision ana::integer introduced in the previous
patch, capturing the units of the integer in the type system: bits vs
bytes vs unitless. The idea is that if we mess up bits vs bytes in the
analyzer implementation, we get a compile-time type error. The class
also supports user-defined literals, so we can write e.g.
8_bits
in the code, rather than
integer_with_unit<units::bit> (8)
As in the previous patch, nothing uses this yet, but it is fundamental
to my upcoming rewrite of the analyzer's state-tracking.
gcc/ChangeLog:
* Makefile.in (ANALYZER_OBJS): Add analyzer/integer-with-unit.o.
gcc/analyzer/ChangeLog:
* analyzer-selftests.cc (run_analyzer_selftests): Call
analyzer_integer_with_unit_cc_tests.
* analyzer-selftests.h (analyzer_integer_with_unit_cc_tests): New
decl.
* integer-with-unit.cc: New file.
* integer-with-unit.h: New file.
* units.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/integer-with-unit.cc | 580 +++++++++++++++++++++++++++++
gcc/analyzer/integer-with-unit.h | 403 ++++++++++++++++++++
gcc/analyzer/units.h | 69 ++++
6 files changed, 1055 insertions(+)
create mode 100644 gcc/analyzer/integer-with-unit.cc
create mode 100644 gcc/analyzer/integer-with-unit.h
create mode 100644 gcc/analyzer/units.h
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 5bb019c2343..7f95cbb7cdf 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1371,6 +1371,7 @@ ANALYZER_OBJS = \
analyzer/infinite-loop.o \
analyzer/infinite-recursion.o \
analyzer/integer.o \
+ analyzer/integer-with-unit.o \
analyzer/kf.o \
analyzer/kf-analyzer.o \
analyzer/kf-lang-cp.o \
diff --git a/gcc/analyzer/analyzer-selftests.cc
b/gcc/analyzer/analyzer-selftests.cc
index cf5af41c8d7..1cfb2007a63 100644
--- a/gcc/analyzer/analyzer-selftests.cc
+++ b/gcc/analyzer/analyzer-selftests.cc
@@ -52,6 +52,7 @@ run_analyzer_selftests ()
analyzer_constraint_manager_cc_tests ();
analyzer_function_set_cc_tests ();
analyzer_integer_cc_tests ();
+ analyzer_integer_with_unit_cc_tests ();
analyzer_program_point_cc_tests ();
analyzer_program_state_cc_tests ();
analyzer_ranges_cc_tests ();
diff --git a/gcc/analyzer/analyzer-selftests.h
b/gcc/analyzer/analyzer-selftests.h
index f3af1115f07..f32846fdcf8 100644
--- a/gcc/analyzer/analyzer-selftests.h
+++ b/gcc/analyzer/analyzer-selftests.h
@@ -37,6 +37,7 @@ extern void analyzer_access_diagram_cc_tests ();
extern void analyzer_constraint_manager_cc_tests ();
extern void analyzer_function_set_cc_tests ();
extern void analyzer_integer_cc_tests ();
+extern void analyzer_integer_with_unit_cc_tests ();
extern void analyzer_program_point_cc_tests ();
extern void analyzer_program_state_cc_tests ();
extern void analyzer_ranges_cc_tests ();
diff --git a/gcc/analyzer/integer-with-unit.cc
b/gcc/analyzer/integer-with-unit.cc
new file mode 100644
index 00000000000..a10b0b940d7
--- /dev/null
+++ b/gcc/analyzer/integer-with-unit.cc
@@ -0,0 +1,580 @@
+/* Unit-safe arbitrary precision integers (e.g. "32 bytes").
+ 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/integer-with-unit.h"
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+template<>
+void
+integer_with_unit<units::byte>::dump_to_pp (pretty_printer *pp,
+ units::role role) const
+{
+ switch (role)
+ {
+ default:
+ gcc_unreachable ();
+ case units::role::generic:
+ m_integer.dump_to_pp (pp);
+ break;
+ case units::role::offset:
+ pp_string (pp, "byte ");
+ m_integer.dump_to_pp (pp);
+ break;
+ case units::role::size:
+ if (m_integer == 1)
+ pp_printf (pp, "1 byte");
+ else
+ {
+ m_integer.dump_to_pp (pp);
+ pp_string (pp, " bytes");
+ }
+ break;
+ }
+}
+
+template<>
+void
+integer_with_unit<units::bit>::dump_to_pp (pretty_printer *pp,
+ units::role role) const
+{
+ if (role != units::role::generic)
+ {
+ // Attempt to print as bytes
+ concrete_byte_size bytes;
+ if (maybe_bytes_from_bits (*this, &bytes))
+ {
+ bytes.dump_to_pp (pp, role);
+ return;
+ }
+ }
+ switch (role)
+ {
+ default:
+ gcc_unreachable ();
+ case units::role::generic:
+ m_integer.dump_to_pp (pp);
+ break;
+ case units::role::offset:
+ pp_string (pp, "bit ");
+ m_integer.dump_to_pp (pp);
+ break;
+ case units::role::size:
+ if (m_integer == 1)
+ pp_printf (pp, "1 bit");
+ else
+ {
+ m_integer.dump_to_pp (pp);
+ pp_string (pp, " bits");
+ }
+ break;
+ }
+}
+
+template<>
+DEBUG_FUNCTION void
+integer_with_unit<units::bit>::dump () const
+{
+ pretty_printer pp;
+ pp.set_output_stream (stderr);
+ dump_to_pp (&pp, units::role::size);
+ pp_newline (&pp);
+ pp_flush (&pp);
+}
+
+template<>
+DEBUG_FUNCTION void
+integer_with_unit<units::byte>::dump () const
+{
+ pretty_printer pp;
+ pp.set_output_stream (stderr);
+ dump_to_pp (&pp, units::role::size);
+ pp_newline (&pp);
+ pp_flush (&pp);
+}
+
+template<>
+std::unique_ptr<json::object>
+integer_with_unit<units::bit>::to_json () const
+{
+ auto obj = std::make_unique<json::object> ();
+ obj->set ("integer", m_integer.to_json ());
+ obj->set_string ("unit", "bits");
+ return obj;
+}
+
+template<>
+std::unique_ptr<json::object>
+integer_with_unit<units::byte>::to_json () const
+{
+ auto obj = std::make_unique<json::object> ();
+ obj->set ("integer", m_integer.to_json ());
+ obj->set_string ("unit", "bytes");
+ return obj;
+}
+
+template<typename Unit>
+std::unique_ptr<diagnostics::markup::node>
+integer_with_unit<Unit>::
+make_markup_node () const
+{
+ return m_integer.make_markup_node ();
+}
+
+template
+std::unique_ptr<diagnostics::markup::node>
+integer_with_unit<units::bit>::
+make_markup_node () const;
+
+template
+std::unique_ptr<diagnostics::markup::node>
+integer_with_unit<units::byte>::
+make_markup_node () const;
+
+bool
+maybe_bytes_from_bits (integer_with_unit<units::bit> bits,
+ integer_with_unit<units::byte> *out)
+{
+ if (bits.floor_mod (integer_with_unit<units::bit> (BITS_PER_UNIT))
+ != 0_bits)
+ return false;
+ *out = integer_with_unit<units::byte>
+ (bits.get_integer ().floor_div (BITS_PER_UNIT));
+ return true;
+}
+
+integer_with_unit<units::bit>
+convert_from_bit_offset_t (bit_offset_t bits)
+{
+ unsigned HOST_WIDE_INT uhwi = bits.to_uhwi ();
+ return integer_with_unit<units::bit> (uhwi);
+}
+
+integer_with_unit<units::bit>
+convert_from_bit_size_t (bit_size_t bits)
+{
+ unsigned HOST_WIDE_INT uhwi = bits.to_uhwi ();
+ return integer_with_unit<units::bit> (uhwi);
+}
+
+integer_with_unit<units::byte>
+convert_from_byte_offset_t (byte_offset_t bytes)
+{
+ unsigned HOST_WIDE_INT uhwi = bytes.to_uhwi ();
+ return integer_with_unit<units::byte> (uhwi);
+}
+
+integer_with_unit<units::byte>
+convert_from_byte_size_t (byte_size_t bytes)
+{
+ unsigned HOST_WIDE_INT uhwi = bytes.to_uhwi ();
+ return integer_with_unit<units::byte> (uhwi);
+}
+
+bit_offset_t
+convert_to_bit_offset_t (integer_with_unit<units::bit> new_val)
+{
+ return bit_offset_t (new_val.as_long ());
+}
+
+bit_size_t
+convert_to_bit_size_t (integer_with_unit<units::bit> new_val)
+{
+ return bit_offset_t (new_val.as_unsigned_long ());
+}
+
+byte_offset_t
+convert_to_byte_offset_t (integer_with_unit<units::byte> new_val)
+{
+ gcc_assert (new_val >= 0_bytes);
+ return byte_offset_t (new_val.get_integer ().as_unsigned_long ());
+}
+
+byte_size_t
+convert_to_byte_size_t (integer_with_unit<units::byte> new_val)
+{
+ gcc_assert (new_val >= 0_bytes);
+ return byte_offset_t (new_val.get_integer ().as_unsigned_long ());
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Unit-independent tests. */
+
+template<typename Unit>
+static void
+test_rule_of_5 ()
+{
+ integer_with_unit<Unit> a;
+ ASSERT_EQ (a.get_integer (), 0);
+
+ integer_with_unit<Unit> b (42);
+ ASSERT_EQ (b.get_integer (), 42);
+
+ integer_with_unit<Unit> c (b);
+ ASSERT_EQ (c.get_integer (), 42);
+ // Check that writes to b don't affect c after the copy:
+ b = integer_with_unit<Unit> (43);
+ ASSERT_EQ (b.get_integer (), 43);
+ ASSERT_EQ (c.get_integer (), 42);
+
+ integer_with_unit<Unit> d (std::move (c));
+ ASSERT_EQ (d.get_integer (), 42);
+ ASSERT_EQ (c.get_integer (), 0);
+
+ c = integer_with_unit<Unit> (1066);
+ ASSERT_EQ (c.get_integer (), 1066);
+
+ a = std::move (d);
+ ASSERT_EQ (a.get_integer (), 42);
+}
+
+/* Test addition, for scalars of the given unit. */
+
+template<typename Unit>
+static void
+test_addition ()
+{
+ ASSERT_EQ (integer_with_unit<Unit> (1), integer_with_unit<Unit> (1));
+ ASSERT_NE (integer_with_unit<Unit> (1), integer_with_unit<Unit> (2));
+
+ /* Addition. */
+ ASSERT_EQ (integer_with_unit<Unit> (4) + integer_with_unit<Unit> (3),
+ integer_with_unit<Unit> (7));
+
+ /* Subtraction. */
+ ASSERT_EQ (integer_with_unit<Unit> (4) - integer_with_unit<Unit> (3),
+ integer_with_unit<Unit> (1));
+
+ /* Additive identity. */
+ ASSERT_EQ (integer_with_unit<Unit> (3) + integer_with_unit<Unit> (0),
+ integer_with_unit<Unit> (3));
+ ASSERT_EQ (integer_with_unit<Unit> (2) - integer_with_unit<Unit> (0),
+ integer_with_unit<Unit> (2));
+
+ /* Negative values. */
+ ASSERT_EQ (integer_with_unit<Unit> (4) - integer_with_unit<Unit> (5),
+ integer_with_unit<Unit> (-1));
+ ASSERT_EQ (integer_with_unit<Unit> (-1) - integer_with_unit<Unit> (-3),
+ integer_with_unit<Unit> (2));
+}
+
+/* Test operator++, for scalars of the given unit. */
+
+template<typename Unit>
+static void
+test_auto_inc ()
+{
+ integer_with_unit<Unit> i (0);
+ ASSERT_EQ (i, integer_with_unit<Unit> (0));
+
+ ++i;
+ ASSERT_EQ (i, integer_with_unit<Unit> (1));
+
+ ++i;
+ ASSERT_EQ (i, integer_with_unit<Unit> (2));
+}
+
+
+/* Test multiplication, for scalars of the given unit by unitless integers. */
+
+template<typename Unit>
+static void
+test_scaling ()
+{
+ ASSERT_EQ (integer_with_unit<Unit> (5) * 2,
+ integer_with_unit<Unit> (10));
+
+ /* Identity. */
+ ASSERT_EQ (integer_with_unit<Unit> (7) * 1,
+ integer_with_unit<Unit> (7));
+
+ /* Zero. */
+ ASSERT_EQ (integer_with_unit<Unit> (4) * 0,
+ integer_with_unit<Unit> (0));
+}
+
+template<typename Unit>
+static void
+test_division ()
+{
+ ASSERT_EQ (integer_with_unit<Unit> (0)
+ .floor_div (integer_with_unit<Unit> (10)),
+ integer (0));
+ ASSERT_EQ (integer_with_unit<Unit> (24)
+ .floor_div (integer_with_unit<Unit> (10)),
+ integer (2));
+ ASSERT_EQ (integer_with_unit<Unit> (3)
+ .floor_div (integer_with_unit<Unit> (1)),
+ integer (3));
+ ASSERT_EQ (integer_with_unit<Unit> (-8)
+ .floor_div (integer_with_unit<Unit> (10)),
+ integer (-1));
+}
+
+template<typename Unit>
+static void
+test_modulus ()
+{
+ ASSERT_EQ (integer_with_unit<Unit> (0)
+ .floor_mod (integer_with_unit<Unit> (10)),
+ integer_with_unit<Unit> (0));
+ ASSERT_EQ (integer_with_unit<Unit> (24)
+ .floor_mod (integer_with_unit<Unit> (10)),
+ integer_with_unit<Unit> (4));
+ ASSERT_EQ (integer_with_unit<Unit> (3)
+ .floor_mod (integer_with_unit<Unit> (1)),
+ integer_with_unit<Unit> (0));
+ ASSERT_EQ (integer_with_unit<Unit> (-8)
+ .floor_mod (integer_with_unit<Unit> (10)),
+ integer_with_unit<Unit> (2));
+}
+
+template<typename Unit>
+static void
+test_dumping ();
+
+template<>
+void
+test_dumping<units::bit> ()
+{
+ {
+ auto seven_bits = 7_bits;
+ {
+ pretty_printer pp;
+ seven_bits.dump_to_pp (&pp, units::role::offset);
+ ASSERT_STREQ (pp_formatted_text (&pp), "bit 7");
+ }
+ {
+ pretty_printer pp;
+ seven_bits.dump_to_pp (&pp, units::role::size);
+ ASSERT_STREQ (pp_formatted_text (&pp), "7 bits");
+ }
+ }
+
+ {
+ auto one_bit = 1_bit;
+ {
+ pretty_printer pp;
+ one_bit.dump_to_pp (&pp, units::role::offset);
+ ASSERT_STREQ (pp_formatted_text (&pp), "bit 1");
+ }
+ {
+ pretty_printer pp;
+ one_bit.dump_to_pp (&pp, units::role::size);
+ ASSERT_STREQ (pp_formatted_text (&pp), "1 bit");
+ }
+ }
+}
+
+template<>
+void
+test_dumping<units::byte> ()
+{
+ {
+ auto seven_bytes = 7_bytes;
+ {
+ pretty_printer pp;
+ seven_bytes.dump_to_pp (&pp, units::role::offset);
+ ASSERT_STREQ (pp_formatted_text (&pp), "byte 7");
+ }
+ {
+ pretty_printer pp;
+ seven_bytes.dump_to_pp (&pp, units::role::size);
+ ASSERT_STREQ (pp_formatted_text (&pp), "7 bytes");
+ }
+ }
+
+ {
+ auto one_byte = 1_byte;
+ {
+ pretty_printer pp;
+ one_byte.dump_to_pp (&pp, units::role::offset);
+ ASSERT_STREQ (pp_formatted_text (&pp), "byte 1");
+ }
+ {
+ pretty_printer pp;
+ one_byte.dump_to_pp (&pp, units::role::size);
+ ASSERT_STREQ (pp_formatted_text (&pp), "1 byte");
+ }
+ }
+}
+
+/* Run all per-unit tests, for Unit. */
+
+template<typename Unit>
+static void
+per_unit_scalar_tests ()
+{
+ test_rule_of_5<Unit> ();
+ test_addition<Unit> ();
+ test_auto_inc<Unit> ();
+ test_scaling<Unit> ();
+ test_division<Unit> ();
+ test_modulus<Unit> ();
+ test_dumping<Unit> ();
+}
+
+static void
+test_to_json ()
+{
+ ASSERT_JSON_PRINT_EQ (*(32_bits).to_json (), true,
+ ("{\"integer\": \"32\",\n"
+ " \"unit\": \"bits\"}"));
+ ASSERT_JSON_PRINT_EQ (*(1024_bytes).to_json (), true,
+ ("{\"integer\": \"1024\",\n"
+ " \"unit\": \"bytes\"}"));
+}
+
+static void
+test_comparison_of_compatible_mixed_units ()
+{
+ ASSERT_EQ (integer_with_unit<units::bit> (8),
+ integer_with_unit<units::byte> (1));
+ ASSERT_NE (integer_with_unit<units::bit> (7),
+ integer_with_unit<units::byte> (1));
+ ASSERT_NE (integer_with_unit<units::bit> (9),
+ integer_with_unit<units::byte> (1));
+}
+
+static void
+test_addition_of_compatible_mixed_units ()
+{
+ ASSERT_EQ ((integer_with_unit<units::bit> (8)
+ + integer_with_unit<units::byte> (1)),
+ integer_with_unit<units::bit> (16));
+
+ ASSERT_EQ ((integer_with_unit<units::byte> (1)
+ - integer_with_unit<units::bit> (2)),
+ integer_with_unit<units::bit> (6));
+}
+
+/* Unit-dependent tests. */
+
+static void
+test_udls ()
+{
+ ASSERT_EQ (8_bits, 1_byte);
+ ASSERT_NE (7_bits, 1_byte);
+ ASSERT_NE (9_bits, 1_byte);
+ ASSERT_EQ (1_byte + 1_bit, 9_bits);
+ ASSERT_EQ (1_byte - 2_bits, 6_bits);
+ ASSERT_EQ ((5_bits * 4) - 2_bytes, 4_bits);
+}
+
+/* Consider:
+ |
+ | struct coord { uint32_t x; uint32_t y; };
+ | struct triangle { coord m_vertices[3]; };
+ |
+ and computing sizeof (triangle). */
+
+static void
+test_sizeof_triangle ()
+{
+ /* In bytes. */
+ {
+ auto sizeof_x = 32_bits;
+ auto sizeof_y = 32_bits;
+
+ auto sizeof_coord = sizeof_x + sizeof_y;
+ ASSERT_EQ (sizeof_coord, 64_bits);
+
+ auto sizeof_triangle = sizeof_coord * 3;
+ ASSERT_EQ (sizeof_triangle, 192_bits);
+ }
+
+ /* In bits. */
+ {
+ auto sizeof_x = 4_bytes;
+ auto sizeof_y = 4_bytes;
+
+ auto sizeof_coord = sizeof_x + sizeof_y;
+ ASSERT_EQ (sizeof_coord, 8_bytes);
+
+ auto sizeof_triangle = sizeof_coord * 3;
+ ASSERT_EQ (sizeof_triangle, 24_bytes);
+ }
+}
+
+static void
+test_round_bit_to_lowest_byte ()
+{
+ ASSERT_EQ (round_bit_to_lowest_byte (0_bits), 0_bytes);
+ ASSERT_EQ (round_bit_to_lowest_byte (1_bit), 0_bytes);
+ ASSERT_EQ (round_bit_to_lowest_byte (7_bits), 0_bytes);
+ ASSERT_EQ (round_bit_to_lowest_byte (8_bits), 1_byte);
+ ASSERT_EQ (round_bit_to_lowest_byte (15_bits), 1_byte);
+ ASSERT_EQ (round_bit_to_lowest_byte (16_bits), 2_bytes);
+ ASSERT_EQ (round_bit_to_lowest_byte (23_bits), 2_bytes);
+ ASSERT_EQ (round_bit_to_lowest_byte (24_bits), 3_bytes);
+}
+
+static void
+test_bit_within_byte ()
+{
+ ASSERT_EQ (bit_within_byte (0_bits), 0_bits);
+ ASSERT_EQ (bit_within_byte (1_bit), 1_bit);
+ ASSERT_EQ (bit_within_byte (7_bits), 7_bits);
+ ASSERT_EQ (bit_within_byte (8_bits), 0_bits);
+ ASSERT_EQ (bit_within_byte (15_bits), 7_bits);
+ ASSERT_EQ (bit_within_byte (16_bits), 0_bits);
+ ASSERT_EQ (bit_within_byte (23_bits), 7_bits);
+ ASSERT_EQ (bit_within_byte (24_bits), 0_bits);
+}
+
+/* Run all of the selftests within this file. */
+
+void
+analyzer_integer_with_unit_cc_tests ()
+{
+ per_unit_scalar_tests<units::bit> ();
+ per_unit_scalar_tests<units::byte> ();
+
+ test_to_json ();
+
+ test_comparison_of_compatible_mixed_units ();
+ test_addition_of_compatible_mixed_units ();
+
+ test_udls ();
+
+ test_sizeof_triangle ();
+
+ test_round_bit_to_lowest_byte ();
+ test_bit_within_byte ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/integer-with-unit.h b/gcc/analyzer/integer-with-unit.h
new file mode 100644
index 00000000000..daf59537294
--- /dev/null
+++ b/gcc/analyzer/integer-with-unit.h
@@ -0,0 +1,403 @@
+/* Unit-safe arbitrary precision integers (e.g. "32 bytes").
+ 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_INTEGER_WITH_UNIT_H
+#define GCC_ANALYZER_INTEGER_WITH_UNIT_H
+
+#include "analyzer/integer.h"
+#include "analyzer/units.h"
+
+namespace ana {
+
+/* A unit-safe arbitrary precision integer e.g. "32_bytes" or "5_bits". */
+
+template<typename Unit>
+class integer_with_unit
+{
+public:
+ static constexpr const char *dump_type_name = Unit::dump_type_name;
+
+ integer_with_unit () : m_integer () {}
+
+ explicit integer_with_unit (int i) : m_integer (i) {}
+
+ explicit integer_with_unit (integer i) : m_integer (std::move (i)) {}
+
+ const integer &get_integer () const { return m_integer; }
+
+ void
+ dump_to_pp (pretty_printer *pp,
+ units::role role = units::role::generic) const;
+ void
+ dump () const;
+
+ std::unique_ptr<json::object>
+ to_json () const;
+
+ std::unique_ptr<diagnostics::markup::node>
+ make_markup_node () const;
+
+ bool
+ is_zero_p () const { return m_integer.is_zero_p (); }
+
+ bool
+ is_one_p () const { return m_integer.is_one_p (); }
+
+ static int
+ cmp (const integer_with_unit<Unit> &lhs,
+ const integer_with_unit<Unit> &rhs)
+ {
+ return integer::cmp (lhs.m_integer, rhs.m_integer);
+ }
+
+ integer_with_unit<Unit>&
+ operator+= (const integer_with_unit<Unit> &rhs)
+ {
+ m_integer += rhs.m_integer;
+ return *this;
+ }
+
+ integer_with_unit<Unit>&
+ operator-= (const integer_with_unit<Unit> &rhs)
+ {
+ m_integer -= rhs.m_integer;
+ return *this;
+ }
+
+ integer_with_unit<Unit>&
+ operator++ ()
+ {
+ m_integer += 1;
+ return *this;
+ }
+
+ bool
+ operator< (const integer_with_unit<Unit> &rhs) const
+ {
+ return m_integer < rhs.m_integer;
+ }
+
+ bool
+ operator<= (const integer_with_unit<Unit> &rhs) const
+ {
+ return m_integer <= rhs.m_integer;
+ }
+
+ bool
+ operator> (const integer_with_unit<Unit> &rhs) const
+ {
+ return m_integer > rhs.m_integer;
+ }
+
+ bool
+ operator>= (const integer_with_unit<Unit> &rhs) const
+ {
+ return m_integer >= rhs.m_integer;
+ }
+
+ integer_with_unit<Unit>
+ operator- () const
+ {
+ return integer_with_unit<Unit> (-m_integer);
+ }
+
+ /* Division. */
+
+ integer
+ floor_div (const integer_with_unit<Unit> &other) const
+ {
+ return get_integer ().floor_div (other.get_integer ());
+ }
+
+ /* Modulus. */
+
+ integer_with_unit<Unit>
+ floor_mod (const integer_with_unit<Unit> &other) const
+ {
+ return integer_with_unit<Unit>
+ (get_integer ().floor_mod (other.get_integer ()));
+ }
+
+ long
+ as_long () const
+ {
+ return m_integer.as_long ();
+ }
+
+ unsigned long
+ as_unsigned_long () const
+ {
+ return m_integer.as_unsigned_long ();
+ }
+
+private:
+ integer m_integer;
+};
+
+/* User-defined literal operators. */
+
+inline integer_with_unit<units::bit>
+operator ""_bit (unsigned long long v)
+{
+ gcc_checking_assert (v == 1);
+ return integer_with_unit<units::bit> (v);
+}
+
+inline integer_with_unit<units::bit>
+operator ""_bits (unsigned long long v)
+{
+ gcc_checking_assert (v != 1);
+ return integer_with_unit<units::bit> (v);
+}
+
+inline integer_with_unit<units::byte>
+operator ""_byte (unsigned long long v)
+{
+ gcc_checking_assert (v == 1);
+ return integer_with_unit<units::byte> (v);
+}
+
+inline integer_with_unit<units::byte>
+operator ""_bytes (unsigned long long v)
+{
+ gcc_checking_assert (v != 1);
+ return integer_with_unit<units::byte> (v);
+}
+
+/* Explicit conversions. */
+
+inline integer_with_unit<units::bit>
+bits_from_bytes (integer_with_unit<units::byte> bytes)
+{
+ return integer_with_unit<units::bit> (bytes.get_integer () * BITS_PER_UNIT);
+}
+
+bool
+maybe_bytes_from_bits (integer_with_unit<units::bit> bits,
+ integer_with_unit<units::byte> *out);
+
+/* Overloaded operators. */
+
+/* Comparisons of like units. */
+
+template<typename Unit>
+inline bool
+operator== (const integer_with_unit<Unit> &lhs,
+ const integer_with_unit<Unit> &rhs)
+{
+ return lhs.get_integer () == rhs.get_integer ();
+}
+
+template<typename Unit>
+inline bool
+operator!= (const integer_with_unit<Unit> &lhs,
+ const integer_with_unit<Unit> &rhs)
+{
+ return lhs.get_integer () != rhs.get_integer ();
+}
+
+/* Comparisons of compatible units, implicitly converting to bits. */
+
+inline bool
+operator== (const integer_with_unit<units::bit> &lhs,
+ const integer_with_unit<units::byte> &rhs)
+{
+ return lhs == bits_from_bytes (rhs);
+}
+
+inline bool
+operator== (const integer_with_unit<units::byte> &lhs,
+ const integer_with_unit<units::bit> &rhs)
+{
+ return bits_from_bytes (lhs) == rhs;
+}
+
+inline bool
+operator!= (const integer_with_unit<units::bit> &lhs,
+ const integer_with_unit<units::byte> &rhs)
+{
+ return lhs != bits_from_bytes (rhs);
+}
+
+inline bool
+operator!= (const integer_with_unit<units::byte> &lhs,
+ const integer_with_unit<units::bit> &rhs)
+{
+ return bits_from_bytes (lhs) != rhs;
+}
+
+/* Addition of integer_with_unit of same unit. */
+
+template<typename Unit>
+inline integer_with_unit<Unit>
+operator+ (const integer_with_unit<Unit> &lhs,
+ const integer_with_unit<Unit> &rhs)
+{
+ return integer_with_unit<Unit> (lhs.get_integer () + rhs.get_integer ());
+}
+
+/* Addition of integer_with_unit of compatible types, implicitly converting to
bits. */
+
+inline integer_with_unit<units::bit>
+operator+ (const integer_with_unit<units::bit> &lhs,
+ const integer_with_unit<units::byte> &rhs)
+{
+ return lhs + bits_from_bytes (rhs);
+}
+
+inline integer_with_unit<units::bit>
+operator+ (const integer_with_unit<units::byte> &lhs,
+ const integer_with_unit<units::bit> &rhs)
+{
+ return bits_from_bytes (lhs) + rhs;
+}
+
+/* Negation. */
+
+template<typename Unit>
+inline integer_with_unit<Unit>
+operator- (const integer_with_unit<Unit> &i)
+{
+ return integer_with_unit<Unit> (-(i.get_integer ()));
+}
+
+/* Subtraction of integer_with_unit of same kind. */
+
+template<typename Unit>
+inline integer_with_unit<Unit>
+operator- (const integer_with_unit<Unit> &lhs,
+ const integer_with_unit<Unit> &rhs)
+{
+ return integer_with_unit<Unit> (lhs.get_integer () - rhs.get_integer ());
+}
+
+/* Subtraction of integer_with_unit of compatible types,
+ implicitly converting to bits. */
+
+integer_with_unit<units::bit>
+inline operator- (const integer_with_unit<units::bit> &lhs,
+ const integer_with_unit<units::byte> &rhs)
+{
+ return lhs - bits_from_bytes (rhs);
+}
+
+inline integer_with_unit<units::bit>
+operator- (const integer_with_unit<units::byte> &lhs,
+ const integer_with_unit<units::bit> &rhs)
+{
+ return bits_from_bytes (lhs) - rhs;
+}
+
+/* Multiplication of integer_with_unit by a unitless value. */
+
+template<typename Unit>
+inline integer_with_unit<Unit>
+operator* (const integer_with_unit<Unit> &lhs,
+ const integer &rhs)
+{
+ return integer_with_unit<Unit> (lhs.get_integer () * rhs);
+}
+
+/* Multiplication of unitless value by a integer_with_unit. */
+
+template<typename Unit>
+inline integer_with_unit<Unit>
+operator* (const integer &lhs,
+ const integer_with_unit<Unit> &rhs)
+{
+ return integer_with_unit<Unit> (lhs * rhs.get_integer ());
+}
+
+using concrete_bit_size = integer_with_unit<units::bit>;
+using concrete_bit_offset = integer_with_unit<units::bit>;
+using concrete_byte_size = integer_with_unit<units::byte>;
+using concrete_byte_offset = integer_with_unit<units::byte>;
+
+inline concrete_byte_offset
+round_bit_to_lowest_byte (concrete_bit_offset bit_offset)
+{
+ return concrete_byte_offset
+ (bit_offset.get_integer ().floor_div (BITS_PER_UNIT));
+}
+
+inline concrete_bit_offset
+bit_within_byte (concrete_bit_offset bit_offset)
+{
+ return concrete_bit_offset
+ (bit_offset.get_integer ().floor_mod (BITS_PER_UNIT));
+}
+
+extern bool
+get_type_size (const_tree type, concrete_bit_size *out);
+
+/* Functions for converting from older representation.
+
+ Ultimately we'll want {bit,byte}_{offset,size}_t to be
+ integer_with_unit<units::{bit,byte}>
+ but for now these should help migrate the code. */
+
+concrete_bit_offset
+convert_from_bit_offset_t (bit_offset_t);
+
+concrete_bit_size
+convert_from_bit_size_t (bit_size_t);
+
+integer_with_unit<units::byte>
+convert_from_byte_offset_t (byte_offset_t);
+
+integer_with_unit<units::byte>
+convert_from_byte_size_t (byte_size_t);
+
+/* Functions for converting to older representation. */
+
+bit_offset_t
+convert_to_bit_offset_t (concrete_bit_offset);
+
+bit_size_t
+convert_to_bit_size_t (concrete_bit_size);
+
+byte_offset_t
+convert_to_byte_offset_t (concrete_byte_offset);
+
+byte_size_t
+convert_to_byte_size_t (concrete_byte_size);
+
+template <typename ConcreteValueType>
+inline bool
+eval_comparison_p (ConcreteValueType a,
+ enum tree_code op,
+ ConcreteValueType b)
+{
+ switch (op)
+ {
+ default:
+ gcc_unreachable ();
+ case EQ_EXPR: return a == b;
+ case NE_EXPR: return a != b;
+ case GE_EXPR: return a >= b;
+ case LE_EXPR: return a <= b;
+ case GT_EXPR: return a > b;
+ case LT_EXPR: return a < b;
+ }
+}
+
+} // namespace ana
+
+#endif /* GCC_ANALYZER_INTEGER_WITH_UNIT_H */
diff --git a/gcc/analyzer/units.h b/gcc/analyzer/units.h
new file mode 100644
index 00000000000..a0117323fe3
--- /dev/null
+++ b/gcc/analyzer/units.h
@@ -0,0 +1,69 @@
+/* Traits for expressing units and dimensions in the type system.
+ 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_UNITS_H
+#define GCC_ANALYZER_UNITS_H
+
+/* Traits types for distinguishing different kinds of scalars
+ and ranges, so that we get a type error if we mess up our units. */
+
+namespace ana {
+namespace units {
+
+/* Traits type representing the size of something in memory,
+ measured in *bits*. */
+
+struct bit
+{
+ // Forbid creating instances: this is just a trait
+ bit () = delete;
+
+ static constexpr const char *dump_type_name = "bits";
+ static constexpr const char *singular_dump_name = "bit";
+ static constexpr const char *plural_dump_name = "bit";
+};
+
+/* Traits type representing the size of something in memory,
+ measured in *bytes*. */
+
+struct byte
+{
+ // Forbid creating instances: this is just a trait
+ byte () = delete;
+
+ static constexpr const char *dump_type_name = "bytes";
+ static constexpr const char *singular_dump_name = "byte";
+ static constexpr const char *plural_dump_name = "bytes";
+};
+
+/* Enum for distinguishing different roles of a scalar
+ so e.g. "10" vs "byte 10" for an offset vs "10 bytes" for a size. */
+
+enum class role
+{
+ generic,
+ offset,
+ size
+};
+
+} // namespace ana::units
+} // namespace ana
+
+#endif /* GCC_ANALYZER_UNITS_H */
--
2.56.0