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//! cap                           opaque, a capability, only under -fsafety
14//! i8x16 f32x4                   fixed vectors
15//! void
16//! mem                           the state of memory, at -O2 and above
17//! ```
18//!
19//! Four decisions are worth restating because the rest of the crate depends on them.
20//!
21//! **Integers are signless.** There is no `u32` beside `i32`. The operation carries the
22//! signedness, so `sdiv` and `udiv` are different opcodes over the same type. That halves the
23//! type space and removes the family of bugs where the type says one thing and the operation
24//! does another.
25//!
26//! **Pointers are opaque.** A `ptr` has no pointee. The size of an access belongs to the
27//! `load` or the `store`, and the aliasing information belongs to the metadata on it, where
28//! the effective-type rules can be applied precisely rather than guessed at from a static
29//! pointee type that C does not license conclusions from anyway.
30//!
31//! **A capability is opaque for the same reason a pointer is.** `cap` is what the memory safety
32//! instrumentation moves around, per `spec/safe-memory/06-instrumentation.md` section 6.2.1, and
33//! how wide it is and what is in it belong to `spec/safe-memory/05-representation.md`. Nothing in
34//! the optimizer may depend on either. There is no load and no store of one: a capability reaches
35//! a register through `cap.of`, `cap.load`, `cap.null`, `cap.narrow` or `cap.recover` and leaves
36//! through `cap.store` or a check, and that closed set is what lets the representation change
37//! without anything downstream noticing. A module that uses none of them contains no `cap` and is
38//! byte for byte what it was before this type existed.
39//!
40//! **Aggregates are not values.** There is no struct type and no array type. Structs and
41//! arrays live in memory, a struct assignment is a `memcpy`, and a struct passed by value has
42//! been taken apart by the ABI rules before it reaches the IR.
43//!
44//! `mem` is the odd one and document 09 of `spec/optimizer` is why it exists. Memory SSA works
45//! by pretending the whole of memory is one variable, so that the machinery that already puts
46//! block parameters where two definitions meet does it for memory too. That pretence needs a
47//! type for the variable to have. Nothing computes with a `mem` and nothing stores one: it is
48//! threaded from the instruction that wrote memory to the instruction that reads it, and the
49//! back end never sees one, because memory SSA is built inside the optimizer and taken off
50//! again before anything lowers. A function that does not carry it is a function where every
51//! memory operation is unordered with respect to every other and the alias analysis is asked
52//! directly, which is what `-O0` and `-O1` do.
53
54use std::fmt;
55
56/// An IR type.
57///
58/// Four bytes, packed, because a type sits on every value in a function and a function has a
59/// great many values. The alternative, an enum holding a lane type and a lane count, comes out
60/// at twelve bytes for the same information, and the tables this goes in are walked often
61/// enough for that to show.
62///
63/// The packing is the low sixteen bits for the width in bits, the next thirteen for the lane
64/// count biased by one, and the top three for which of the six kinds it is. That gives a
65/// largest integer of [`Type::MAX_BITS`] and a widest vector of [`Type::MAX_LANES`], both of
66/// which are past anything a target has.
67///
68/// The kind field took a bit off the lane count when `mem` was added, which halved
69/// [`Type::MAX_LANES`] from sixteen thousand to eight. The widest vector register anybody ships
70/// is 2048 bits, so the widest useful vector is 2048 lanes of `i1`, and the number this leaves
71/// is four times that. Adding `cap` cost nothing further, since three bits hold eight kinds.
72///
73/// ```
74/// use rucc_ir::{Float, Type};
75///
76/// assert_eq!(Type::int(32).to_string(), "i32");
77/// assert_eq!(Type::float(Float::F64).to_string(), "f64");
78/// assert_eq!(Type::PTR.to_string(), "ptr");
79/// assert_eq!(Type::CAP.to_string(), "cap");
80/// assert_eq!(Type::vector(Type::int(8), 16).to_string(), "i8x16");
81/// assert_eq!(size_of::<Type>(), 4);
82/// ```
83#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
84pub struct Type(u32);
85
86/// Which of the six kinds a [`Type`] is.
87///
88/// This is the discriminant on its own, for matching. It says nothing about the width or the
89/// lane count, which is why it is separate from the type rather than being the type.
90#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
91pub enum Kind {
92    /// No value. The result type of a `store`, of a `call` to a `void` function, and of every
93    /// terminator.
94    Void,
95    /// An integer of some width, with no signedness.
96    Int,
97    /// A floating point value in one of the formats of [`Float`].
98    Float,
99    /// An address, with no pointee.
100    Ptr,
101    /// The state of memory, which only exists while memory SSA does.
102    Mem,
103    /// A capability, with no representation the IR knows about.
104    ///
105    /// Only the memory safety instructions produce or consume one. A function with none of them
106    /// has no value of this kind anywhere in it.
107    Cap,
108}
109
110/// A floating point format, named by its width in bits.
111///
112/// The names are the widths because that is what the textual form uses, and a reader who sees
113/// `f80` should not have to know that it occupies sixteen bytes on the stack. That is a layout
114/// question and it belongs to the target, not to the type.
115#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
116pub enum Float {
117    /// IEEE binary16, which is `_Float16` and `__fp16`.
118    F16,
119    /// IEEE binary32, which is `float` everywhere we care about.
120    F32,
121    /// IEEE binary64, which is `double`.
122    F64,
123    /// The x87 80-bit extended format, which is `long double` on x86 SysV.
124    F80,
125    /// IEEE binary128, which is `_Float128`, and `long double` on AArch64 Linux.
126    F128,
127}
128
129impl Float {
130    /// The width of the format in bits.
131    ///
132    /// This is the width of the format and not the size of the object. `F80` is eighty bits of
133    /// format in a ten, twelve or sixteen byte object depending on the target.
134    #[must_use]
135    pub const fn bits(self) -> u32 {
136        match self {
137            Self::F16 => 16,
138            Self::F32 => 32,
139            Self::F64 => 64,
140            Self::F80 => 80,
141            Self::F128 => 128,
142        }
143    }
144
145    /// The format of that width, if there is one.
146    #[must_use]
147    pub const fn from_bits(bits: u32) -> Option<Self> {
148        match bits {
149            16 => Some(Self::F16),
150            32 => Some(Self::F32),
151            64 => Some(Self::F64),
152            80 => Some(Self::F80),
153            128 => Some(Self::F128),
154            _ => None,
155        }
156    }
157}
158
159impl fmt::Display for Float {
160    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161        write!(f, "f{}", self.bits())
162    }
163}
164
165// Where the packing lives. Changing any of these changes the meaning of every `Type` in a
166// serialised module, which is why the textual form carries a version.
167const BITS_SHIFT: u32 = 0;
168const BITS_MASK: u32 = 0xffff;
169const LANES_SHIFT: u32 = 16;
170const LANES_MASK: u32 = 0x1fff;
171const KIND_SHIFT: u32 = 29;
172
173impl Type {
174    /// The widest integer that can be represented, which is what limits `_BitInt`.
175    ///
176    /// Sixteen bits of width is more than any target's `BITINT_MAXWIDTH` and more than any
177    /// vector register, and it leaves room in the same four bytes for the lane count.
178    pub const MAX_BITS: u32 = BITS_MASK;
179
180    /// The most lanes a vector can have.
181    pub const MAX_LANES: u32 = LANES_MASK + 1;
182
183    /// No value.
184    pub const VOID: Self = Self::pack(Kind::Void, 0, 1);
185    /// An address.
186    pub const PTR: Self = Self::pack(Kind::Ptr, 0, 1);
187    /// The state of memory. See the note at the top of this module.
188    pub const MEM: Self = Self::pack(Kind::Mem, 0, 1);
189    /// A capability. See the note at the top of this module.
190    pub const CAP: Self = Self::pack(Kind::Cap, 0, 1);
191    /// The one-bit integer every comparison produces.
192    pub const I1: Self = Self::pack(Kind::Int, 1, 1);
193
194    /// Builds a type from its parts, with no checking. Every public constructor checks first.
195    const fn pack(kind: Kind, bits: u32, lanes: u32) -> Self {
196        Self((kind as u32) << KIND_SHIFT | (lanes - 1) << LANES_SHIFT | bits << BITS_SHIFT)
197    }
198
199    /// An integer `bits` wide.
200    ///
201    /// # Panics
202    ///
203    /// Panics if `bits` is zero or above [`Type::MAX_BITS`]. A zero-width integer is not a
204    /// thing the IR has, and a caller that computed one has a bug that gets much harder to
205    /// find if it is allowed to travel.
206    #[must_use]
207    pub const fn int(bits: u32) -> Self {
208        assert!(bits > 0 && bits <= Self::MAX_BITS, "integer width out of range");
209        Self::pack(Kind::Int, bits, 1)
210    }
211
212    /// A floating point value in the given format.
213    #[must_use]
214    pub const fn float(format: Float) -> Self {
215        Self::pack(Kind::Float, format.bits(), 1)
216    }
217
218    /// A vector of `lanes` copies of `lane`.
219    ///
220    /// # Panics
221    ///
222    /// Panics if `lane` is not an integer or a floating point type, if it is itself a vector,
223    /// or if `lanes` is zero or above [`Type::MAX_LANES`]. A vector of pointers is not in the
224    /// instruction set, so admitting the type would mean admitting a value nothing can be done
225    /// with.
226    #[must_use]
227    pub const fn vector(lane: Self, lanes: u32) -> Self {
228        assert!(lanes > 0 && lanes <= Self::MAX_LANES, "lane count out of range");
229        assert!(lane.is_scalar(), "a vector's lane is a scalar");
230        assert!(
231            matches!(lane.kind(), Kind::Int | Kind::Float),
232            "a vector's lane is an integer or a floating point value"
233        );
234        Self::pack(lane.kind(), lane.bits(), lanes)
235    }
236
237    /// Which of the six kinds this is.
238    #[must_use]
239    pub const fn kind(self) -> Kind {
240        match self.0 >> KIND_SHIFT {
241            0 => Kind::Void,
242            1 => Kind::Int,
243            2 => Kind::Float,
244            3 => Kind::Ptr,
245            4 => Kind::Mem,
246            _ => Kind::Cap,
247        }
248    }
249
250    /// The width of one lane in bits, which for a scalar is the width of the type.
251    ///
252    /// Zero for `void`, for `ptr` and for `cap`, since the width of an address is a property of
253    /// the target and not of the type, and a capability has no width in the IR at all. Ask the
254    /// target for the first and `spec/safe-memory/05-representation.md` for the second.
255    #[must_use]
256    pub const fn bits(self) -> u32 {
257        self.0 >> BITS_SHIFT & BITS_MASK
258    }
259
260    /// How many lanes this has, which is one unless it is a vector.
261    #[must_use]
262    pub const fn lanes(self) -> u32 {
263        (self.0 >> LANES_SHIFT & LANES_MASK) + 1
264    }
265
266    /// Whether this has exactly one lane.
267    #[must_use]
268    pub const fn is_scalar(self) -> bool {
269        self.lanes() == 1
270    }
271
272    /// Whether this has more than one lane.
273    #[must_use]
274    pub const fn is_vector(self) -> bool {
275        self.lanes() > 1
276    }
277
278    /// The type of one lane, which for a scalar is the type itself.
279    #[must_use]
280    pub const fn lane(self) -> Self {
281        Self::pack(self.kind(), self.bits(), 1)
282    }
283
284    /// The same shape as this, with the lane type replaced.
285    ///
286    /// This is what a comparison does: `icmp` over `i32x4` produces `i1x4`, and the rule that
287    /// the lane count is carried across is easier to get right in one place than at every
288    /// instruction that needs it.
289    ///
290    /// # Panics
291    ///
292    /// Panics under the same conditions as [`Type::vector`].
293    #[must_use]
294    pub const fn with_lane(self, lane: Self) -> Self {
295        Self::vector(lane, self.lanes())
296    }
297
298    /// Whether this is an integer, of any width, scalar or vector.
299    #[must_use]
300    pub const fn is_int(self) -> bool {
301        matches!(self.kind(), Kind::Int)
302    }
303
304    /// Whether this is a floating point value, scalar or vector.
305    #[must_use]
306    pub const fn is_float(self) -> bool {
307        matches!(self.kind(), Kind::Float)
308    }
309
310    /// Whether this is an address. A vector of pointers cannot be built, so this is scalar.
311    #[must_use]
312    pub const fn is_ptr(self) -> bool {
313        matches!(self.kind(), Kind::Ptr)
314    }
315
316    /// Whether this is the absence of a value.
317    #[must_use]
318    pub const fn is_void(self) -> bool {
319        matches!(self.kind(), Kind::Void)
320    }
321
322    /// Whether this is the state of memory.
323    #[must_use]
324    pub const fn is_mem(self) -> bool {
325        matches!(self.kind(), Kind::Mem)
326    }
327
328    /// Whether this is a capability. A vector of capabilities cannot be built, so this is scalar.
329    #[must_use]
330    pub const fn is_cap(self) -> bool {
331        matches!(self.kind(), Kind::Cap)
332    }
333
334    /// The floating point format, if this is one.
335    #[must_use]
336    pub const fn format(self) -> Option<Float> {
337        match self.kind() {
338            Kind::Float => Float::from_bits(self.bits()),
339            _ => None,
340        }
341    }
342
343    /// Parses the textual form, which is what the printer writes.
344    ///
345    /// ```
346    /// use rucc_ir::Type;
347    ///
348    /// assert_eq!(Type::parse("i32"), Some(Type::int(32)));
349    /// assert_eq!(Type::parse("f32x4"), Some(Type::vector(Type::float(rucc_ir::Float::F32), 4)));
350    /// assert_eq!(Type::parse("i0"), None);
351    /// assert_eq!(Type::parse("i32 "), None);
352    /// ```
353    #[must_use]
354    pub fn parse(text: &str) -> Option<Self> {
355        if text == "void" {
356            return Some(Self::VOID);
357        }
358        if text == "ptr" {
359            return Some(Self::PTR);
360        }
361        if text == "mem" {
362            return Some(Self::MEM);
363        }
364        if text == "cap" {
365            return Some(Self::CAP);
366        }
367        let (head, lanes) = match text.split_once('x') {
368            // A lane count of one is not written, so `i8x1` is not a spelling of anything and
369            // accepting it would give two texts for one type and break the round trip.
370            Some((head, lanes)) => (head, parse_u32(lanes).filter(|&n| n > 1)?),
371            None => (text, 1),
372        };
373        let bits = parse_u32(head.strip_prefix(['i', 'f'])?)?;
374        let lane = match head.as_bytes()[0] {
375            b'i' if bits > 0 && bits <= Self::MAX_BITS => Self::int(bits),
376            b'f' => Self::float(Float::from_bits(bits)?),
377            _ => return None,
378        };
379        if lanes > Self::MAX_LANES {
380            return None;
381        }
382        Some(if lanes == 1 { lane } else { Self::vector(lane, lanes) })
383    }
384}
385
386/// A decimal `u32` with no sign, no underscores, and no leading zero on a non-zero number.
387///
388/// `str::parse` would take `+4` and `0004`, and either one would be a second spelling of a
389/// type that already has one, which is what breaks a byte for byte round trip.
390fn parse_u32(text: &str) -> Option<u32> {
391    if text.is_empty() || (text.starts_with('0') && text.len() > 1) {
392        return None;
393    }
394    text.bytes().all(|b| b.is_ascii_digit()).then(|| text.parse().ok())?
395}
396
397impl fmt::Display for Type {
398    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
399        match self.kind() {
400            Kind::Void => return f.write_str("void"),
401            Kind::Ptr => return f.write_str("ptr"),
402            Kind::Mem => return f.write_str("mem"),
403            Kind::Cap => return f.write_str("cap"),
404            Kind::Int => write!(f, "i{}", self.bits())?,
405            Kind::Float => write!(f, "f{}", self.bits())?,
406        }
407        if self.is_vector() {
408            write!(f, "x{}", self.lanes())?;
409        }
410        Ok(())
411    }
412}
413
414impl fmt::Debug for Type {
415    // The `Display` form is the one anybody wants to read, and a derived `Debug` would print
416    // the packed integer, which is not information anybody can use.
417    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
418        fmt::Display::fmt(self, f)
419    }
420}
421
422#[cfg(test)]
423mod tests {
424    use super::*;
425
426    #[test]
427    fn a_type_is_four_bytes() {
428        assert_eq!(size_of::<Type>(), 4);
429    }
430
431    #[test]
432    fn memory_is_its_own_kind_and_nothing_else_answers_to_it() {
433        assert_eq!(Type::MEM.kind(), Kind::Mem);
434        assert!(Type::MEM.is_mem());
435        assert_eq!(Type::MEM.to_string(), "mem");
436        assert_eq!(Type::parse("mem"), Some(Type::MEM));
437        // It is not void, which is what a reader who skimmed the packing might expect, and the
438        // difference matters because a store produces no value and may still define memory.
439        for other in [Type::VOID, Type::PTR, Type::int(64), Type::float(Float::F64)] {
440            assert!(!other.is_mem(), "{other} answered to being memory");
441            assert_ne!(other, Type::MEM);
442        }
443        assert!(!Type::MEM.is_void() && !Type::MEM.is_ptr() && !Type::MEM.is_int());
444    }
445
446    #[test]
447    fn the_widest_vector_still_packs_beside_the_new_kind() {
448        // The kind took a bit off the lane count. Both ends of the range have to survive that,
449        // because getting the mask wrong reads back as a vector of a different width rather
450        // than as anything that fails.
451        let widest = Type::vector(Type::int(8), Type::MAX_LANES);
452        assert_eq!(widest.lanes(), Type::MAX_LANES);
453        assert_eq!(widest.lane(), Type::int(8));
454        let widest_int = Type::int(Type::MAX_BITS);
455        assert_eq!(widest_int.bits(), Type::MAX_BITS);
456        assert_eq!(widest_int.lanes(), 1);
457        assert_eq!(Type::vector(widest_int, Type::MAX_LANES).bits(), Type::MAX_BITS);
458    }
459
460    #[test]
461    fn the_parts_come_back_out() {
462        let v = Type::vector(Type::int(8), 16);
463        assert_eq!(v.kind(), Kind::Int);
464        assert_eq!(v.bits(), 8);
465        assert_eq!(v.lanes(), 16);
466        assert_eq!(v.lane(), Type::int(8));
467        assert!(v.is_vector());
468        assert!(!v.is_scalar());
469    }
470
471    #[test]
472    fn a_scalar_has_one_lane_and_is_its_own_lane() {
473        let i32_ = Type::int(32);
474        assert_eq!(i32_.lanes(), 1);
475        assert_eq!(i32_.lane(), i32_);
476        assert!(i32_.is_scalar());
477    }
478
479    #[test]
480    fn void_and_ptr_and_cap_have_no_width_of_their_own() {
481        assert_eq!(Type::VOID.bits(), 0);
482        assert_eq!(Type::PTR.bits(), 0);
483        assert_eq!(Type::CAP.bits(), 0);
484        assert!(Type::VOID.is_void());
485        assert!(Type::PTR.is_ptr());
486        assert!(Type::CAP.is_cap());
487    }
488
489    #[test]
490    fn a_capability_is_none_of_the_other_kinds() {
491        assert_eq!(Type::CAP.kind(), Kind::Cap);
492        assert_eq!(Type::CAP.to_string(), "cap");
493        assert_eq!(Type::parse("cap"), Some(Type::CAP));
494        // A pointer is the one it would be mistaken for, since the instrumentation keeps the two
495        // side by side, and the whole point of the type is that they are not interchangeable.
496        for other in [Type::VOID, Type::PTR, Type::MEM, Type::int(64), Type::float(Float::F64)] {
497            assert!(!other.is_cap(), "{other} answered to being a capability");
498            assert_ne!(other, Type::CAP);
499        }
500        assert!(!Type::CAP.is_ptr() && !Type::CAP.is_void() && !Type::CAP.is_mem());
501        assert!(Type::CAP.is_scalar());
502    }
503
504    #[test]
505    fn a_comparison_keeps_the_lane_count() {
506        assert_eq!(Type::vector(Type::int(32), 4).with_lane(Type::I1), Type::vector(Type::I1, 4));
507        assert_eq!(Type::int(32).with_lane(Type::I1), Type::I1);
508    }
509
510    #[test]
511    fn the_extremes_are_representable() {
512        let widest = Type::int(Type::MAX_BITS);
513        assert_eq!(widest.bits(), Type::MAX_BITS);
514        let longest = Type::vector(Type::I1, Type::MAX_LANES);
515        assert_eq!(longest.lanes(), Type::MAX_LANES);
516        assert_eq!(longest.lane(), Type::I1);
517    }
518
519    #[test]
520    fn every_type_round_trips_through_its_text() {
521        let mut types = vec![Type::VOID, Type::PTR, Type::MEM, Type::CAP];
522        for bits in [1, 8, 16, 32, 64, 128, 3, 12, Type::MAX_BITS] {
523            types.push(Type::int(bits));
524        }
525        for format in [Float::F16, Float::F32, Float::F64, Float::F80, Float::F128] {
526            types.push(Type::float(format));
527        }
528        for lanes in [2, 4, 16, Type::MAX_LANES] {
529            types.push(Type::vector(Type::int(8), lanes));
530            types.push(Type::vector(Type::float(Float::F32), lanes));
531        }
532        for ty in types {
533            let text = ty.to_string();
534            assert_eq!(Type::parse(&text), Some(ty), "{text}");
535        }
536    }
537
538    #[test]
539    fn the_texts_that_are_not_types_are_refused() {
540        for text in [
541            "",
542            "i",
543            "f",
544            "i0",
545            "i8x0",
546            "i8x1",
547            "f24",
548            "f0",
549            "i-1",
550            "i+1",
551            "i08",
552            "i8x01",
553            "int",
554            "i32 ",
555            " i32",
556            "i8x",
557            "x4",
558            "i8x4x4",
559            "i65536",
560            "i8x8193",
561            "voidx2",
562            "ptrx2",
563            "capx2",
564            "cap ",
565            "Cap",
566            "capability",
567        ] {
568            assert_eq!(Type::parse(text), None, "{text}");
569        }
570    }
571
572    #[test]
573    fn a_format_knows_its_width_both_ways() {
574        for format in [Float::F16, Float::F32, Float::F64, Float::F80, Float::F128] {
575            assert_eq!(Float::from_bits(format.bits()), Some(format));
576            assert_eq!(Type::float(format).format(), Some(format));
577        }
578        assert_eq!(Float::from_bits(24), None);
579        assert_eq!(Type::int(32).format(), None);
580    }
581
582    #[test]
583    #[should_panic(expected = "integer width out of range")]
584    fn a_zero_width_integer_is_refused() {
585        let _ = Type::int(0);
586    }
587
588    #[test]
589    #[should_panic(expected = "integer width out of range")]
590    fn an_integer_wider_than_the_packing_is_refused() {
591        let _ = Type::int(Type::MAX_BITS + 1);
592    }
593
594    #[test]
595    #[should_panic(expected = "lane count out of range")]
596    fn a_vector_with_no_lanes_is_refused() {
597        let _ = Type::vector(Type::int(8), 0);
598    }
599
600    #[test]
601    #[should_panic(expected = "a vector's lane is a scalar")]
602    fn a_vector_of_vectors_is_refused() {
603        let _ = Type::vector(Type::vector(Type::int(8), 2), 2);
604    }
605
606    #[test]
607    #[should_panic(expected = "an integer or a floating point value")]
608    fn a_vector_of_pointers_is_refused() {
609        let _ = Type::vector(Type::PTR, 2);
610    }
611
612    #[test]
613    #[should_panic(expected = "an integer or a floating point value")]
614    fn a_vector_of_capabilities_is_refused() {
615        let _ = Type::vector(Type::CAP, 2);
616    }
617}