Skip to main content

rucc_ir/
ty.rs

1//! The IR type system.
2//!
3//! Design: `spec/08-ir.md` section 8.2.
4//!
5//! Much smaller than C's, and deliberately so. Everything C-specific has been resolved by the
6//! time lowering runs, and re-deriving any of it here would mean two answers to the same
7//! question with nothing keeping them in step.
8//!
9//! ```text
10//! i1 i8 i16 i32 i64 i128 iN     integers, by width, signless
11//! f16 f32 f64 f80 f128          floating point, by width
12//! ptr                           opaque, no pointee
13//! i8x16 f32x4                   fixed vectors
14//! void
15//! ```
16//!
17//! Three decisions are worth restating because the rest of the crate depends on them.
18//!
19//! **Integers are signless.** There is no `u32` beside `i32`. The operation carries the
20//! signedness, so `sdiv` and `udiv` are different opcodes over the same type. That halves the
21//! type space and removes the family of bugs where the type says one thing and the operation
22//! does another.
23//!
24//! **Pointers are opaque.** A `ptr` has no pointee. The size of an access belongs to the
25//! `load` or the `store`, and the aliasing information belongs to the metadata on it, where
26//! the effective-type rules can be applied precisely rather than guessed at from a static
27//! pointee type that C does not license conclusions from anyway.
28//!
29//! **Aggregates are not values.** There is no struct type and no array type. Structs and
30//! arrays live in memory, a struct assignment is a `memcpy`, and a struct passed by value has
31//! been taken apart by the ABI rules before it reaches the IR.
32
33use std::fmt;
34
35/// An IR type.
36///
37/// Four bytes, packed, because a type sits on every value in a function and a function has a
38/// great many values. The alternative, an enum holding a lane type and a lane count, comes out
39/// at twelve bytes for the same information, and the tables this goes in are walked often
40/// enough for that to show.
41///
42/// The packing is the low sixteen bits for the width in bits, the next fourteen for the lane
43/// count biased by one, and the top two for which of the four kinds it is. That gives a
44/// largest integer of [`Type::MAX_BITS`] and a widest vector of [`Type::MAX_LANES`], both of
45/// which are past anything a target has.
46///
47/// ```
48/// use rucc_ir::{Float, Type};
49///
50/// assert_eq!(Type::int(32).to_string(), "i32");
51/// assert_eq!(Type::float(Float::F64).to_string(), "f64");
52/// assert_eq!(Type::PTR.to_string(), "ptr");
53/// assert_eq!(Type::vector(Type::int(8), 16).to_string(), "i8x16");
54/// assert_eq!(size_of::<Type>(), 4);
55/// ```
56#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
57pub struct Type(u32);
58
59/// Which of the four kinds a [`Type`] is.
60///
61/// This is the discriminant on its own, for matching. It says nothing about the width or the
62/// lane count, which is why it is separate from the type rather than being the type.
63#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
64pub enum Kind {
65    /// No value. The result type of a `store`, of a `call` to a `void` function, and of every
66    /// terminator.
67    Void,
68    /// An integer of some width, with no signedness.
69    Int,
70    /// A floating point value in one of the formats of [`Float`].
71    Float,
72    /// An address, with no pointee.
73    Ptr,
74}
75
76/// A floating point format, named by its width in bits.
77///
78/// The names are the widths because that is what the textual form uses, and a reader who sees
79/// `f80` should not have to know that it occupies sixteen bytes on the stack. That is a layout
80/// question and it belongs to the target, not to the type.
81#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
82pub enum Float {
83    /// IEEE binary16, which is `_Float16` and `__fp16`.
84    F16,
85    /// IEEE binary32, which is `float` everywhere we care about.
86    F32,
87    /// IEEE binary64, which is `double`.
88    F64,
89    /// The x87 80-bit extended format, which is `long double` on x86 SysV.
90    F80,
91    /// IEEE binary128, which is `_Float128`, and `long double` on AArch64 Linux.
92    F128,
93}
94
95impl Float {
96    /// The width of the format in bits.
97    ///
98    /// This is the width of the format and not the size of the object. `F80` is eighty bits of
99    /// format in a ten, twelve or sixteen byte object depending on the target.
100    #[must_use]
101    pub const fn bits(self) -> u32 {
102        match self {
103            Self::F16 => 16,
104            Self::F32 => 32,
105            Self::F64 => 64,
106            Self::F80 => 80,
107            Self::F128 => 128,
108        }
109    }
110
111    /// The format of that width, if there is one.
112    #[must_use]
113    pub const fn from_bits(bits: u32) -> Option<Self> {
114        match bits {
115            16 => Some(Self::F16),
116            32 => Some(Self::F32),
117            64 => Some(Self::F64),
118            80 => Some(Self::F80),
119            128 => Some(Self::F128),
120            _ => None,
121        }
122    }
123}
124
125impl fmt::Display for Float {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        write!(f, "f{}", self.bits())
128    }
129}
130
131// Where the packing lives. Changing any of these changes the meaning of every `Type` in a
132// serialised module, which is why the textual form carries a version.
133const BITS_SHIFT: u32 = 0;
134const BITS_MASK: u32 = 0xffff;
135const LANES_SHIFT: u32 = 16;
136const LANES_MASK: u32 = 0x3fff;
137const KIND_SHIFT: u32 = 30;
138
139impl Type {
140    /// The widest integer that can be represented, which is what limits `_BitInt`.
141    ///
142    /// Sixteen bits of width is more than any target's `BITINT_MAXWIDTH` and more than any
143    /// vector register, and it leaves room in the same four bytes for the lane count.
144    pub const MAX_BITS: u32 = BITS_MASK;
145
146    /// The most lanes a vector can have.
147    pub const MAX_LANES: u32 = LANES_MASK + 1;
148
149    /// No value.
150    pub const VOID: Self = Self::pack(Kind::Void, 0, 1);
151    /// An address.
152    pub const PTR: Self = Self::pack(Kind::Ptr, 0, 1);
153    /// The one-bit integer every comparison produces.
154    pub const I1: Self = Self::pack(Kind::Int, 1, 1);
155
156    /// Builds a type from its parts, with no checking. Every public constructor checks first.
157    const fn pack(kind: Kind, bits: u32, lanes: u32) -> Self {
158        Self((kind as u32) << KIND_SHIFT | (lanes - 1) << LANES_SHIFT | bits << BITS_SHIFT)
159    }
160
161    /// An integer `bits` wide.
162    ///
163    /// # Panics
164    ///
165    /// Panics if `bits` is zero or above [`Type::MAX_BITS`]. A zero-width integer is not a
166    /// thing the IR has, and a caller that computed one has a bug that gets much harder to
167    /// find if it is allowed to travel.
168    #[must_use]
169    pub const fn int(bits: u32) -> Self {
170        assert!(bits > 0 && bits <= Self::MAX_BITS, "integer width out of range");
171        Self::pack(Kind::Int, bits, 1)
172    }
173
174    /// A floating point value in the given format.
175    #[must_use]
176    pub const fn float(format: Float) -> Self {
177        Self::pack(Kind::Float, format.bits(), 1)
178    }
179
180    /// A vector of `lanes` copies of `lane`.
181    ///
182    /// # Panics
183    ///
184    /// Panics if `lane` is not an integer or a floating point type, if it is itself a vector,
185    /// or if `lanes` is zero or above [`Type::MAX_LANES`]. A vector of pointers is not in the
186    /// instruction set, so admitting the type would mean admitting a value nothing can be done
187    /// with.
188    #[must_use]
189    pub const fn vector(lane: Self, lanes: u32) -> Self {
190        assert!(lanes > 0 && lanes <= Self::MAX_LANES, "lane count out of range");
191        assert!(lane.is_scalar(), "a vector's lane is a scalar");
192        assert!(
193            matches!(lane.kind(), Kind::Int | Kind::Float),
194            "a vector's lane is an integer or a floating point value"
195        );
196        Self::pack(lane.kind(), lane.bits(), lanes)
197    }
198
199    /// Which of the four kinds this is.
200    #[must_use]
201    pub const fn kind(self) -> Kind {
202        match self.0 >> KIND_SHIFT {
203            0 => Kind::Void,
204            1 => Kind::Int,
205            2 => Kind::Float,
206            _ => Kind::Ptr,
207        }
208    }
209
210    /// The width of one lane in bits, which for a scalar is the width of the type.
211    ///
212    /// Zero for `void` and for `ptr`, since the width of an address is a property of the
213    /// target and not of the type. Ask the target for it.
214    #[must_use]
215    pub const fn bits(self) -> u32 {
216        self.0 >> BITS_SHIFT & BITS_MASK
217    }
218
219    /// How many lanes this has, which is one unless it is a vector.
220    #[must_use]
221    pub const fn lanes(self) -> u32 {
222        (self.0 >> LANES_SHIFT & LANES_MASK) + 1
223    }
224
225    /// Whether this has exactly one lane.
226    #[must_use]
227    pub const fn is_scalar(self) -> bool {
228        self.lanes() == 1
229    }
230
231    /// Whether this has more than one lane.
232    #[must_use]
233    pub const fn is_vector(self) -> bool {
234        self.lanes() > 1
235    }
236
237    /// The type of one lane, which for a scalar is the type itself.
238    #[must_use]
239    pub const fn lane(self) -> Self {
240        Self::pack(self.kind(), self.bits(), 1)
241    }
242
243    /// The same shape as this, with the lane type replaced.
244    ///
245    /// This is what a comparison does: `icmp` over `i32x4` produces `i1x4`, and the rule that
246    /// the lane count is carried across is easier to get right in one place than at every
247    /// instruction that needs it.
248    ///
249    /// # Panics
250    ///
251    /// Panics under the same conditions as [`Type::vector`].
252    #[must_use]
253    pub const fn with_lane(self, lane: Self) -> Self {
254        Self::vector(lane, self.lanes())
255    }
256
257    /// Whether this is an integer, of any width, scalar or vector.
258    #[must_use]
259    pub const fn is_int(self) -> bool {
260        matches!(self.kind(), Kind::Int)
261    }
262
263    /// Whether this is a floating point value, scalar or vector.
264    #[must_use]
265    pub const fn is_float(self) -> bool {
266        matches!(self.kind(), Kind::Float)
267    }
268
269    /// Whether this is an address. A vector of pointers cannot be built, so this is scalar.
270    #[must_use]
271    pub const fn is_ptr(self) -> bool {
272        matches!(self.kind(), Kind::Ptr)
273    }
274
275    /// Whether this is the absence of a value.
276    #[must_use]
277    pub const fn is_void(self) -> bool {
278        matches!(self.kind(), Kind::Void)
279    }
280
281    /// The floating point format, if this is one.
282    #[must_use]
283    pub const fn format(self) -> Option<Float> {
284        match self.kind() {
285            Kind::Float => Float::from_bits(self.bits()),
286            _ => None,
287        }
288    }
289
290    /// Parses the textual form, which is what the printer writes.
291    ///
292    /// ```
293    /// use rucc_ir::Type;
294    ///
295    /// assert_eq!(Type::parse("i32"), Some(Type::int(32)));
296    /// assert_eq!(Type::parse("f32x4"), Some(Type::vector(Type::float(rucc_ir::Float::F32), 4)));
297    /// assert_eq!(Type::parse("i0"), None);
298    /// assert_eq!(Type::parse("i32 "), None);
299    /// ```
300    #[must_use]
301    pub fn parse(text: &str) -> Option<Self> {
302        if text == "void" {
303            return Some(Self::VOID);
304        }
305        if text == "ptr" {
306            return Some(Self::PTR);
307        }
308        let (head, lanes) = match text.split_once('x') {
309            // A lane count of one is not written, so `i8x1` is not a spelling of anything and
310            // accepting it would give two texts for one type and break the round trip.
311            Some((head, lanes)) => (head, parse_u32(lanes).filter(|&n| n > 1)?),
312            None => (text, 1),
313        };
314        let bits = parse_u32(head.strip_prefix(['i', 'f'])?)?;
315        let lane = match head.as_bytes()[0] {
316            b'i' if bits > 0 && bits <= Self::MAX_BITS => Self::int(bits),
317            b'f' => Self::float(Float::from_bits(bits)?),
318            _ => return None,
319        };
320        if lanes > Self::MAX_LANES {
321            return None;
322        }
323        Some(if lanes == 1 { lane } else { Self::vector(lane, lanes) })
324    }
325}
326
327/// A decimal `u32` with no sign, no underscores, and no leading zero on a non-zero number.
328///
329/// `str::parse` would take `+4` and `0004`, and either one would be a second spelling of a
330/// type that already has one, which is what breaks a byte for byte round trip.
331fn parse_u32(text: &str) -> Option<u32> {
332    if text.is_empty() || (text.starts_with('0') && text.len() > 1) {
333        return None;
334    }
335    text.bytes().all(|b| b.is_ascii_digit()).then(|| text.parse().ok())?
336}
337
338impl fmt::Display for Type {
339    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
340        match self.kind() {
341            Kind::Void => return f.write_str("void"),
342            Kind::Ptr => return f.write_str("ptr"),
343            Kind::Int => write!(f, "i{}", self.bits())?,
344            Kind::Float => write!(f, "f{}", self.bits())?,
345        }
346        if self.is_vector() {
347            write!(f, "x{}", self.lanes())?;
348        }
349        Ok(())
350    }
351}
352
353impl fmt::Debug for Type {
354    // The `Display` form is the one anybody wants to read, and a derived `Debug` would print
355    // the packed integer, which is not information anybody can use.
356    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
357        fmt::Display::fmt(self, f)
358    }
359}
360
361#[cfg(test)]
362mod tests {
363    use super::*;
364
365    #[test]
366    fn a_type_is_four_bytes() {
367        assert_eq!(size_of::<Type>(), 4);
368    }
369
370    #[test]
371    fn the_parts_come_back_out() {
372        let v = Type::vector(Type::int(8), 16);
373        assert_eq!(v.kind(), Kind::Int);
374        assert_eq!(v.bits(), 8);
375        assert_eq!(v.lanes(), 16);
376        assert_eq!(v.lane(), Type::int(8));
377        assert!(v.is_vector());
378        assert!(!v.is_scalar());
379    }
380
381    #[test]
382    fn a_scalar_has_one_lane_and_is_its_own_lane() {
383        let i32_ = Type::int(32);
384        assert_eq!(i32_.lanes(), 1);
385        assert_eq!(i32_.lane(), i32_);
386        assert!(i32_.is_scalar());
387    }
388
389    #[test]
390    fn void_and_ptr_have_no_width_of_their_own() {
391        assert_eq!(Type::VOID.bits(), 0);
392        assert_eq!(Type::PTR.bits(), 0);
393        assert!(Type::VOID.is_void());
394        assert!(Type::PTR.is_ptr());
395    }
396
397    #[test]
398    fn a_comparison_keeps_the_lane_count() {
399        assert_eq!(Type::vector(Type::int(32), 4).with_lane(Type::I1), Type::vector(Type::I1, 4));
400        assert_eq!(Type::int(32).with_lane(Type::I1), Type::I1);
401    }
402
403    #[test]
404    fn the_extremes_are_representable() {
405        let widest = Type::int(Type::MAX_BITS);
406        assert_eq!(widest.bits(), Type::MAX_BITS);
407        let longest = Type::vector(Type::I1, Type::MAX_LANES);
408        assert_eq!(longest.lanes(), Type::MAX_LANES);
409        assert_eq!(longest.lane(), Type::I1);
410    }
411
412    #[test]
413    fn every_type_round_trips_through_its_text() {
414        let mut types = vec![Type::VOID, Type::PTR];
415        for bits in [1, 8, 16, 32, 64, 128, 3, 12, Type::MAX_BITS] {
416            types.push(Type::int(bits));
417        }
418        for format in [Float::F16, Float::F32, Float::F64, Float::F80, Float::F128] {
419            types.push(Type::float(format));
420        }
421        for lanes in [2, 4, 16, Type::MAX_LANES] {
422            types.push(Type::vector(Type::int(8), lanes));
423            types.push(Type::vector(Type::float(Float::F32), lanes));
424        }
425        for ty in types {
426            let text = ty.to_string();
427            assert_eq!(Type::parse(&text), Some(ty), "{text}");
428        }
429    }
430
431    #[test]
432    fn the_texts_that_are_not_types_are_refused() {
433        for text in [
434            "", "i", "f", "i0", "i8x0", "i8x1", "f24", "f0", "i-1", "i+1", "i08", "i8x01", "int",
435            "i32 ", " i32", "i8x", "x4", "i8x4x4", "i65536", "i8x16385", "voidx2", "ptrx2",
436        ] {
437            assert_eq!(Type::parse(text), None, "{text}");
438        }
439    }
440
441    #[test]
442    fn a_format_knows_its_width_both_ways() {
443        for format in [Float::F16, Float::F32, Float::F64, Float::F80, Float::F128] {
444            assert_eq!(Float::from_bits(format.bits()), Some(format));
445            assert_eq!(Type::float(format).format(), Some(format));
446        }
447        assert_eq!(Float::from_bits(24), None);
448        assert_eq!(Type::int(32).format(), None);
449    }
450
451    #[test]
452    #[should_panic(expected = "integer width out of range")]
453    fn a_zero_width_integer_is_refused() {
454        let _ = Type::int(0);
455    }
456
457    #[test]
458    #[should_panic(expected = "integer width out of range")]
459    fn an_integer_wider_than_the_packing_is_refused() {
460        let _ = Type::int(Type::MAX_BITS + 1);
461    }
462
463    #[test]
464    #[should_panic(expected = "lane count out of range")]
465    fn a_vector_with_no_lanes_is_refused() {
466        let _ = Type::vector(Type::int(8), 0);
467    }
468
469    #[test]
470    #[should_panic(expected = "a vector's lane is a scalar")]
471    fn a_vector_of_vectors_is_refused() {
472        let _ = Type::vector(Type::vector(Type::int(8), 2), 2);
473    }
474
475    #[test]
476    #[should_panic(expected = "an integer or a floating point value")]
477    fn a_vector_of_pointers_is_refused() {
478        let _ = Type::vector(Type::PTR, 2);
479    }
480}