Skip to main content

rucc_base/
index.rs

1//! Typed 32-bit indices.
2//!
3//! The compiler stores its trees, its IR and its machine code in flat vectors and refers to
4//! elements by index rather than by pointer. A `u32` index is half the size of a pointer,
5//! it is stable across a reallocation of the backing vector, and it serialises without
6//! fixups, all of which matter at the sizes a translation unit reaches.
7//!
8//! The cost of a bare `u32` is that every index in the program has the same type, so a block
9//! number can be passed where an instruction number was wanted and the compiler will not
10//! object. `Idx<T>` is the fix: it is still a `u32` at runtime and it is a distinct type at
11//! compile time.
12
13use std::fmt;
14use std::marker::PhantomData;
15use std::num::NonZeroU32;
16
17/// A 32-bit index into a flat table of `T`.
18///
19/// `Idx<T>` is `Copy`, is exactly four bytes, and has a niche, so `Option<Idx<T>>` is also
20/// four bytes. Optional indices are everywhere in a compiler (no successor, no parent, no
21/// spill slot) and paying eight bytes for each of them adds up, so the value is stored
22/// biased by one over a `NonZeroU32`. That is where the limit of one below `u32::MAX` on the
23/// largest representable index comes from.
24pub struct Idx<T> {
25    raw: NonZeroU32,
26    _marker: PhantomData<fn() -> T>,
27}
28
29impl<T> Idx<T> {
30    /// The largest index that can be represented.
31    pub const MAX: u32 = u32::MAX - 1;
32
33    /// Wraps a raw index.
34    ///
35    /// # Panics
36    ///
37    /// Panics if `raw` exceeds [`Idx::MAX`]. A translation unit with four billion of
38    /// anything is not a translation unit we intend to compile, and the alternative to
39    /// panicking is silently truncating, which is worse.
40    #[inline]
41    pub const fn new(raw: u32) -> Self {
42        assert!(raw <= Self::MAX, "index out of range");
43        match NonZeroU32::new(raw + 1) {
44            Some(raw) => Self { raw, _marker: PhantomData },
45            None => unreachable!(),
46        }
47    }
48
49    /// Wraps a `usize`, which is what indexing a `Vec` gives back.
50    ///
51    /// # Panics
52    ///
53    /// Panics if the value exceeds [`Idx::MAX`].
54    #[inline]
55    pub fn from_usize(raw: usize) -> Self {
56        Self::new(u32::try_from(raw).expect("index out of range"))
57    }
58
59    /// The underlying `u32`.
60    #[inline]
61    pub const fn raw(self) -> u32 {
62        self.raw.get() - 1
63    }
64
65    /// The index as a `usize`, for slicing.
66    #[inline]
67    pub const fn index(self) -> usize {
68        self.raw() as usize
69    }
70}
71
72// The derives would all demand `T: Trait`, which is wrong here: an index is four bytes of
73// integer no matter what it points at, and requiring `T: Clone` to copy an index is a papercut
74// that shows up in every signature. So they are written out.
75impl<T> Clone for Idx<T> {
76    #[inline]
77    fn clone(&self) -> Self {
78        *self
79    }
80}
81
82impl<T> Copy for Idx<T> {}
83
84impl<T> PartialEq for Idx<T> {
85    #[inline]
86    fn eq(&self, other: &Self) -> bool {
87        self.raw == other.raw
88    }
89}
90
91impl<T> Eq for Idx<T> {}
92
93impl<T> PartialOrd for Idx<T> {
94    #[inline]
95    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
96        Some(self.cmp(other))
97    }
98}
99
100impl<T> Ord for Idx<T> {
101    #[inline]
102    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
103        self.raw.cmp(&other.raw)
104    }
105}
106
107impl<T> std::hash::Hash for Idx<T> {
108    #[inline]
109    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
110        self.raw.hash(state);
111    }
112}
113
114impl<T> fmt::Debug for Idx<T> {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        // The type name is worth the width: a dump full of bare integers is unreadable, and
117        // dumps are the primary debugging tool for a compiler.
118        let name = std::any::type_name::<T>();
119        let short = name.rsplit("::").next().unwrap_or(name);
120        write!(f, "{short}#{}", self.raw())
121    }
122}
123
124/// A contiguous half-open run of indices, `start .. end`.
125///
126/// Children of an AST node, arguments of a call and parameters of a block are all stored as
127/// runs in one flat vector, so the parent holds eight bytes instead of a `Vec`.
128pub struct IdxRange<T> {
129    start: u32,
130    end: u32,
131    _marker: PhantomData<fn() -> T>,
132}
133
134// Written out for the same reason as the ones on `Idx`: a range of indices is eight bytes of
135// integer whatever it points at, and a derive would demand `T: Clone` to copy one.
136impl<T> Clone for IdxRange<T> {
137    #[inline]
138    fn clone(&self) -> Self {
139        *self
140    }
141}
142
143impl<T> Copy for IdxRange<T> {}
144
145impl<T> PartialEq for IdxRange<T> {
146    #[inline]
147    fn eq(&self, other: &Self) -> bool {
148        self.start == other.start && self.end == other.end
149    }
150}
151
152impl<T> Eq for IdxRange<T> {}
153
154impl<T> fmt::Debug for IdxRange<T> {
155    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156        let name = std::any::type_name::<T>();
157        let short = name.rsplit("::").next().unwrap_or(name);
158        write!(f, "{short}#{}..{}", self.start, self.end)
159    }
160}
161
162impl<T> IdxRange<T> {
163    /// Builds a range.
164    ///
165    /// # Panics
166    ///
167    /// Panics if `end` is before `start`.
168    #[inline]
169    pub fn new(start: Idx<T>, end: Idx<T>) -> Self {
170        assert!(start.raw() <= end.raw(), "reversed index range");
171        Self { start: start.raw(), end: end.raw(), _marker: PhantomData }
172    }
173
174    /// The empty range at `at`.
175    #[inline]
176    pub fn empty_at(at: Idx<T>) -> Self {
177        Self { start: at.raw(), end: at.raw(), _marker: PhantomData }
178    }
179
180    /// How many indices the range covers.
181    #[inline]
182    pub const fn len(self) -> usize {
183        (self.end - self.start) as usize
184    }
185
186    /// Whether the range covers nothing.
187    #[inline]
188    pub const fn is_empty(self) -> bool {
189        self.start == self.end
190    }
191
192    /// The indices in the range, in order.
193    pub fn iter(self) -> impl Iterator<Item = Idx<T>> {
194        (self.start..self.end).map(Idx::new)
195    }
196
197    /// The range as a `usize` range, for slicing the backing vector.
198    #[inline]
199    pub const fn as_usize_range(self) -> std::ops::Range<usize> {
200        self.start as usize..self.end as usize
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207
208    struct Block;
209    struct Inst;
210
211    #[test]
212    fn an_index_is_four_bytes_and_so_is_an_optional_one() {
213        assert_eq!(size_of::<Idx<Block>>(), 4);
214        assert_eq!(size_of::<Option<Idx<Block>>>(), 4);
215    }
216
217    #[test]
218    fn round_trips_through_usize() {
219        let i = Idx::<Inst>::from_usize(7);
220        assert_eq!(i.index(), 7);
221        assert_eq!(i.raw(), 7);
222    }
223
224    #[test]
225    fn debug_names_the_table() {
226        assert_eq!(format!("{:?}", Idx::<Block>::new(3)), "Block#3");
227    }
228
229    #[test]
230    fn a_range_iterates_half_open() {
231        let r = IdxRange::new(Idx::<Inst>::new(2), Idx::<Inst>::new(5));
232        let got: Vec<u32> = r.iter().map(Idx::raw).collect();
233        assert_eq!(got, vec![2, 3, 4]);
234        assert_eq!(r.len(), 3);
235        assert_eq!(r.as_usize_range(), 2..5);
236    }
237
238    #[test]
239    fn an_empty_range_is_empty() {
240        let r = IdxRange::empty_at(Idx::<Inst>::new(9));
241        assert!(r.is_empty());
242        assert_eq!(r.iter().count(), 0);
243    }
244
245    #[test]
246    #[should_panic(expected = "reversed index range")]
247    fn a_reversed_range_is_rejected() {
248        let _ = IdxRange::new(Idx::<Inst>::new(5), Idx::<Inst>::new(2));
249    }
250
251    #[test]
252    #[should_panic(expected = "index out of range")]
253    fn the_niche_value_is_rejected() {
254        let _ = Idx::<Inst>::new(u32::MAX);
255    }
256}