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
56use rucc_base::float::Format;
57
58/// An IR type.
59///
60/// Four bytes, packed, because a type sits on every value in a function and a function has a
61/// great many values. The alternative, an enum holding a lane type and a lane count, comes out
62/// at twelve bytes for the same information, and the tables this goes in are walked often
63/// enough for that to show.
64///
65/// The packing is the low sixteen bits for the width in bits, the next thirteen for the lane
66/// count biased by one, and the top three for which of the six kinds it is. That gives a
67/// largest integer of [`Type::MAX_BITS`] and a widest vector of [`Type::MAX_LANES`], both of
68/// which are past anything a target has.
69///
70/// The kind field took a bit off the lane count when `mem` was added, which halved
71/// [`Type::MAX_LANES`] from sixteen thousand to eight. The widest vector register anybody ships
72/// is 2048 bits, so the widest useful vector is 2048 lanes of `i1`, and the number this leaves
73/// is four times that. Adding `cap` cost nothing further, since three bits hold eight kinds.
74///
75/// ```
76/// use rucc_ir::{Float, Type};
77///
78/// assert_eq!(Type::int(32).to_string(), "i32");
79/// assert_eq!(Type::float(Float::F64).to_string(), "f64");
80/// assert_eq!(Type::PTR.to_string(), "ptr");
81/// assert_eq!(Type::CAP.to_string(), "cap");
82/// assert_eq!(Type::vector(Type::int(8), 16).to_string(), "i8x16");
83/// assert_eq!(size_of::<Type>(), 4);
84/// ```
85#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
86pub struct Type(u32);
87
88/// Which of the six kinds a [`Type`] is.
89///
90/// This is the discriminant on its own, for matching. It says nothing about the width or the
91/// lane count, which is why it is separate from the type rather than being the type.
92#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
93pub enum Kind {
94    /// No value. The result type of a `store`, of a `call` to a `void` function, and of every
95    /// terminator.
96    Void,
97    /// An integer of some width, with no signedness.
98    Int,
99    /// A floating point value in one of the formats of [`Float`].
100    Float,
101    /// An address, with no pointee.
102    Ptr,
103    /// The state of memory, which only exists while memory SSA does.
104    Mem,
105    /// A capability, with no representation the IR knows about.
106    ///
107    /// Only the memory safety instructions produce or consume one. A function with none of them
108    /// has no value of this kind anywhere in it.
109    Cap,
110}
111
112/// A floating point format, named by its width in bits.
113///
114/// The names are the widths because that is what the textual form uses, and a reader who sees
115/// `f80` should not have to know that it occupies sixteen bytes on the stack. That is a layout
116/// question and it belongs to the target, not to the type.
117#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
118pub enum Float {
119    /// IEEE binary16, which is `_Float16` and `__fp16`.
120    F16,
121    /// IEEE binary32, which is `float` everywhere we care about.
122    F32,
123    /// IEEE binary64, which is `double`.
124    F64,
125    /// The x87 80-bit extended format, which is `long double` on x86 SysV.
126    F80,
127    /// IEEE binary128, which is `_Float128`, and `long double` on AArch64 Linux.
128    F128,
129    /// IEEE decimal32 in the binary integer encoding, which is `_Decimal32`.
130    D32,
131    /// IEEE decimal64 in the binary integer encoding, which is `_Decimal64`.
132    D64,
133    /// IEEE decimal128 in the binary integer encoding, which is `_Decimal128`.
134    D128,
135}
136
137impl Float {
138    /// The width of the format in bits.
139    ///
140    /// This is the width of the format and not the size of the object. `F80` is eighty bits of
141    /// format in a ten, twelve or sixteen byte object depending on the target.
142    #[must_use]
143    pub const fn bits(self) -> u32 {
144        match self {
145            Self::F16 => 16,
146            Self::F32 => 32,
147            Self::F64 => 64,
148            Self::F80 => 80,
149            Self::F128 | Self::D128 => 128,
150            Self::D32 => 32,
151            Self::D64 => 64,
152        }
153    }
154
155    /// Whether this is one of the three decimal formats.
156    ///
157    /// A decimal shares its width with a binary format, and it travels in the same registers as
158    /// the binary format of that width does, which is why most of the back end never has to ask.
159    /// What must ask is anything that computes with the value or reads its bits as a number,
160    /// because the same sixty four bits are a different number in the two encodings.
161    #[must_use]
162    pub const fn is_decimal(self) -> bool {
163        matches!(self, Self::D32 | Self::D64 | Self::D128)
164    }
165
166    /// The binary format of that width, if there is one.
167    ///
168    /// Never a decimal one, because a width alone does not say decimal, and a caller that has a
169    /// width and wants a type has a binary type in mind.
170    #[must_use]
171    pub const fn from_bits(bits: u32) -> Option<Self> {
172        match bits {
173            16 => Some(Self::F16),
174            32 => Some(Self::F32),
175            64 => Some(Self::F64),
176            80 => Some(Self::F80),
177            128 => Some(Self::F128),
178            _ => None,
179        }
180    }
181
182    /// The encoding this is, as `rucc_base::float` spells it.
183    ///
184    /// The inverse of the map `rucc-lower` keeps in the other direction, and total where that one
185    /// is not. Every format the IR has a type for is an IEEE encoding, so the two that map to
186    /// nothing there, the brain float and the double-double, are not among these and there is no
187    /// case here to return nothing for.
188    ///
189    /// It is on the type rather than in the crate that wants it because which encoding an `f80` is
190    /// is a fact about `f80` and not about whoever is asking. Anything that has to interpret the
191    /// bits of an `fconst` needs it, and a copy of the table in each of them is a table that can
192    /// disagree with itself.
193    ///
194    /// ```
195    /// use rucc_base::float::Format;
196    /// use rucc_ir::Float;
197    ///
198    /// assert_eq!(Float::F64.encoding(), Format::Double);
199    /// assert_eq!(Float::F80.encoding(), Format::X87Extended);
200    /// ```
201    #[must_use]
202    pub const fn encoding(self) -> Format {
203        match self {
204            Self::F16 => Format::Half,
205            Self::F32 => Format::Single,
206            Self::F64 => Format::Double,
207            Self::F80 => Format::X87Extended,
208            Self::F128 => Format::Quad,
209            Self::D32 => Format::Decimal32,
210            Self::D64 => Format::Decimal64,
211            Self::D128 => Format::Decimal128,
212        }
213    }
214
215    /// What goes in the width field of a packed type, which is the width with a flag for decimal.
216    const fn code(self) -> u32 {
217        if self.is_decimal() { self.bits() | DECIMAL } else { self.bits() }
218    }
219
220    /// The inverse of [`Float::code`].
221    const fn from_code(code: u32) -> Option<Self> {
222        match code {
223            c if c == DECIMAL | 32 => Some(Self::D32),
224            c if c == DECIMAL | 64 => Some(Self::D64),
225            c if c == DECIMAL | 128 => Some(Self::D128),
226            _ => Self::from_bits(code),
227        }
228    }
229}
230
231impl fmt::Display for Float {
232    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233        let letter = if self.is_decimal() { 'd' } else { 'f' };
234        write!(f, "{letter}{}", self.bits())
235    }
236}
237
238// Where the packing lives. Changing any of these changes the meaning of every `Type` in a
239// serialised module, which is why the textual form carries a version.
240const BITS_SHIFT: u32 = 0;
241const BITS_MASK: u32 = 0xffff;
242const LANES_SHIFT: u32 = 16;
243const LANES_MASK: u32 = 0x1fff;
244const KIND_SHIFT: u32 = 29;
245
246// The top bit of the width field on a float says the format is decimal. No binary float is
247// anywhere near that wide, so the flag costs no width, and it is on the float kind only, since
248// an integer does use the whole field.
249const DECIMAL: u32 = 1 << 15;
250
251impl Type {
252    /// The widest integer that can be represented, which is what limits `_BitInt`.
253    ///
254    /// Sixteen bits of width is more than any target's `BITINT_MAXWIDTH` and more than any
255    /// vector register, and it leaves room in the same four bytes for the lane count.
256    pub const MAX_BITS: u32 = BITS_MASK;
257
258    /// The most lanes a vector can have.
259    pub const MAX_LANES: u32 = LANES_MASK + 1;
260
261    /// No value.
262    pub const VOID: Self = Self::pack(Kind::Void, 0, 1);
263    /// An address.
264    pub const PTR: Self = Self::pack(Kind::Ptr, 0, 1);
265    /// The state of memory. See the note at the top of this module.
266    pub const MEM: Self = Self::pack(Kind::Mem, 0, 1);
267    /// A capability. See the note at the top of this module.
268    pub const CAP: Self = Self::pack(Kind::Cap, 0, 1);
269    /// The one-bit integer every comparison produces.
270    pub const I1: Self = Self::pack(Kind::Int, 1, 1);
271
272    /// Builds a type from its parts, with no checking. Every public constructor checks first.
273    const fn pack(kind: Kind, bits: u32, lanes: u32) -> Self {
274        Self((kind as u32) << KIND_SHIFT | (lanes - 1) << LANES_SHIFT | bits << BITS_SHIFT)
275    }
276
277    /// An integer `bits` wide.
278    ///
279    /// # Panics
280    ///
281    /// Panics if `bits` is zero or above [`Type::MAX_BITS`]. A zero-width integer is not a
282    /// thing the IR has, and a caller that computed one has a bug that gets much harder to
283    /// find if it is allowed to travel.
284    #[must_use]
285    pub const fn int(bits: u32) -> Self {
286        assert!(bits > 0 && bits <= Self::MAX_BITS, "integer width out of range");
287        Self::pack(Kind::Int, bits, 1)
288    }
289
290    /// A floating point value in the given format.
291    #[must_use]
292    pub const fn float(format: Float) -> Self {
293        Self::pack(Kind::Float, format.code(), 1)
294    }
295
296    /// A vector of `lanes` copies of `lane`.
297    ///
298    /// # Panics
299    ///
300    /// Panics if `lane` is not an integer or a floating point type, if it is itself a vector,
301    /// or if `lanes` is zero or above [`Type::MAX_LANES`]. A vector of pointers is not in the
302    /// instruction set, so admitting the type would mean admitting a value nothing can be done
303    /// with.
304    #[must_use]
305    pub const fn vector(lane: Self, lanes: u32) -> Self {
306        assert!(lanes > 0 && lanes <= Self::MAX_LANES, "lane count out of range");
307        assert!(lane.is_scalar(), "a vector's lane is a scalar");
308        assert!(
309            matches!(lane.kind(), Kind::Int | Kind::Float),
310            "a vector's lane is an integer or a floating point value"
311        );
312        Self::pack(lane.kind(), lane.field(), lanes)
313    }
314
315    /// Which of the six kinds this is.
316    #[must_use]
317    pub const fn kind(self) -> Kind {
318        match self.0 >> KIND_SHIFT {
319            0 => Kind::Void,
320            1 => Kind::Int,
321            2 => Kind::Float,
322            3 => Kind::Ptr,
323            4 => Kind::Mem,
324            _ => Kind::Cap,
325        }
326    }
327
328    /// The width of one lane in bits, which for a scalar is the width of the type.
329    ///
330    /// Zero for `void`, for `ptr` and for `cap`, since the width of an address is a property of
331    /// the target and not of the type, and a capability has no width in the IR at all. Ask the
332    /// target for the first and `spec/safe-memory/05-representation.md` for the second.
333    #[must_use]
334    pub const fn bits(self) -> u32 {
335        match self.kind() {
336            Kind::Float => self.field() & !DECIMAL,
337            _ => self.field(),
338        }
339    }
340
341    /// The width field as it is packed, which on a float carries the decimal flag as well.
342    const fn field(self) -> u32 {
343        self.0 >> BITS_SHIFT & BITS_MASK
344    }
345
346    /// How many lanes this has, which is one unless it is a vector.
347    #[must_use]
348    pub const fn lanes(self) -> u32 {
349        (self.0 >> LANES_SHIFT & LANES_MASK) + 1
350    }
351
352    /// Whether this has exactly one lane.
353    #[must_use]
354    pub const fn is_scalar(self) -> bool {
355        self.lanes() == 1
356    }
357
358    /// Whether this has more than one lane.
359    #[must_use]
360    pub const fn is_vector(self) -> bool {
361        self.lanes() > 1
362    }
363
364    /// The type of one lane, which for a scalar is the type itself.
365    #[must_use]
366    pub const fn lane(self) -> Self {
367        Self::pack(self.kind(), self.field(), 1)
368    }
369
370    /// The same shape as this, with the lane type replaced.
371    ///
372    /// This is what a comparison does: `icmp` over `i32x4` produces `i1x4`, and the rule that
373    /// the lane count is carried across is easier to get right in one place than at every
374    /// instruction that needs it.
375    ///
376    /// # Panics
377    ///
378    /// Panics under the same conditions as [`Type::vector`].
379    #[must_use]
380    pub const fn with_lane(self, lane: Self) -> Self {
381        Self::vector(lane, self.lanes())
382    }
383
384    /// Whether this is an integer, of any width, scalar or vector.
385    #[must_use]
386    pub const fn is_int(self) -> bool {
387        matches!(self.kind(), Kind::Int)
388    }
389
390    /// Whether this is a floating point value, scalar or vector.
391    #[must_use]
392    pub const fn is_float(self) -> bool {
393        matches!(self.kind(), Kind::Float)
394    }
395
396    /// Whether this is an address. A vector of pointers cannot be built, so this is scalar.
397    #[must_use]
398    pub const fn is_ptr(self) -> bool {
399        matches!(self.kind(), Kind::Ptr)
400    }
401
402    /// Whether this is the absence of a value.
403    #[must_use]
404    pub const fn is_void(self) -> bool {
405        matches!(self.kind(), Kind::Void)
406    }
407
408    /// Whether this is the state of memory.
409    #[must_use]
410    pub const fn is_mem(self) -> bool {
411        matches!(self.kind(), Kind::Mem)
412    }
413
414    /// Whether this is a capability. A vector of capabilities cannot be built, so this is scalar.
415    #[must_use]
416    pub const fn is_cap(self) -> bool {
417        matches!(self.kind(), Kind::Cap)
418    }
419
420    /// The floating point format, if this is one.
421    #[must_use]
422    pub const fn format(self) -> Option<Float> {
423        match self.kind() {
424            Kind::Float => Float::from_code(self.field()),
425            _ => None,
426        }
427    }
428
429    /// Parses the textual form, which is what the printer writes.
430    ///
431    /// ```
432    /// use rucc_ir::Type;
433    ///
434    /// assert_eq!(Type::parse("i32"), Some(Type::int(32)));
435    /// assert_eq!(Type::parse("f32x4"), Some(Type::vector(Type::float(rucc_ir::Float::F32), 4)));
436    /// assert_eq!(Type::parse("i0"), None);
437    /// assert_eq!(Type::parse("i32 "), None);
438    /// ```
439    #[must_use]
440    pub fn parse(text: &str) -> Option<Self> {
441        if text == "void" {
442            return Some(Self::VOID);
443        }
444        if text == "ptr" {
445            return Some(Self::PTR);
446        }
447        if text == "mem" {
448            return Some(Self::MEM);
449        }
450        if text == "cap" {
451            return Some(Self::CAP);
452        }
453        let (head, lanes) = match text.split_once('x') {
454            // A lane count of one is not written, so `i8x1` is not a spelling of anything and
455            // accepting it would give two texts for one type and break the round trip.
456            Some((head, lanes)) => (head, parse_u32(lanes).filter(|&n| n > 1)?),
457            None => (text, 1),
458        };
459        let bits = parse_u32(head.strip_prefix(['i', 'f', 'd'])?)?;
460        let lane = match head.as_bytes()[0] {
461            b'i' if bits > 0 && bits <= Self::MAX_BITS => Self::int(bits),
462            b'f' => Self::float(Float::from_bits(bits)?),
463            b'd' if bits < DECIMAL => Self::float(Float::from_code(bits | DECIMAL)?),
464            _ => return None,
465        };
466        if lanes > Self::MAX_LANES {
467            return None;
468        }
469        Some(if lanes == 1 { lane } else { Self::vector(lane, lanes) })
470    }
471}
472
473/// A decimal `u32` with no sign, no underscores, and no leading zero on a non-zero number.
474///
475/// `str::parse` would take `+4` and `0004`, and either one would be a second spelling of a
476/// type that already has one, which is what breaks a byte for byte round trip.
477fn parse_u32(text: &str) -> Option<u32> {
478    if text.is_empty() || (text.starts_with('0') && text.len() > 1) {
479        return None;
480    }
481    text.bytes().all(|b| b.is_ascii_digit()).then(|| text.parse().ok())?
482}
483
484impl fmt::Display for Type {
485    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
486        match self.kind() {
487            Kind::Void => return f.write_str("void"),
488            Kind::Ptr => return f.write_str("ptr"),
489            Kind::Mem => return f.write_str("mem"),
490            Kind::Cap => return f.write_str("cap"),
491            Kind::Int => write!(f, "i{}", self.bits())?,
492            Kind::Float => match self.format() {
493                Some(format) => write!(f, "{format}")?,
494                None => write!(f, "f{}", self.bits())?,
495            },
496        }
497        if self.is_vector() {
498            write!(f, "x{}", self.lanes())?;
499        }
500        Ok(())
501    }
502}
503
504impl fmt::Debug for Type {
505    // The `Display` form is the one anybody wants to read, and a derived `Debug` would print
506    // the packed integer, which is not information anybody can use.
507    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
508        fmt::Display::fmt(self, f)
509    }
510}
511
512#[cfg(test)]
513mod tests {
514    use super::*;
515
516    #[test]
517    fn a_type_is_four_bytes() {
518        assert_eq!(size_of::<Type>(), 4);
519    }
520
521    #[test]
522    fn memory_is_its_own_kind_and_nothing_else_answers_to_it() {
523        assert_eq!(Type::MEM.kind(), Kind::Mem);
524        assert!(Type::MEM.is_mem());
525        assert_eq!(Type::MEM.to_string(), "mem");
526        assert_eq!(Type::parse("mem"), Some(Type::MEM));
527        // It is not void, which is what a reader who skimmed the packing might expect, and the
528        // difference matters because a store produces no value and may still define memory.
529        for other in [Type::VOID, Type::PTR, Type::int(64), Type::float(Float::F64)] {
530            assert!(!other.is_mem(), "{other} answered to being memory");
531            assert_ne!(other, Type::MEM);
532        }
533        assert!(!Type::MEM.is_void() && !Type::MEM.is_ptr() && !Type::MEM.is_int());
534    }
535
536    #[test]
537    fn the_widest_vector_still_packs_beside_the_new_kind() {
538        // The kind took a bit off the lane count. Both ends of the range have to survive that,
539        // because getting the mask wrong reads back as a vector of a different width rather
540        // than as anything that fails.
541        let widest = Type::vector(Type::int(8), Type::MAX_LANES);
542        assert_eq!(widest.lanes(), Type::MAX_LANES);
543        assert_eq!(widest.lane(), Type::int(8));
544        let widest_int = Type::int(Type::MAX_BITS);
545        assert_eq!(widest_int.bits(), Type::MAX_BITS);
546        assert_eq!(widest_int.lanes(), 1);
547        assert_eq!(Type::vector(widest_int, Type::MAX_LANES).bits(), Type::MAX_BITS);
548    }
549
550    #[test]
551    fn the_parts_come_back_out() {
552        let v = Type::vector(Type::int(8), 16);
553        assert_eq!(v.kind(), Kind::Int);
554        assert_eq!(v.bits(), 8);
555        assert_eq!(v.lanes(), 16);
556        assert_eq!(v.lane(), Type::int(8));
557        assert!(v.is_vector());
558        assert!(!v.is_scalar());
559    }
560
561    #[test]
562    fn a_scalar_has_one_lane_and_is_its_own_lane() {
563        let i32_ = Type::int(32);
564        assert_eq!(i32_.lanes(), 1);
565        assert_eq!(i32_.lane(), i32_);
566        assert!(i32_.is_scalar());
567    }
568
569    #[test]
570    fn void_and_ptr_and_cap_have_no_width_of_their_own() {
571        assert_eq!(Type::VOID.bits(), 0);
572        assert_eq!(Type::PTR.bits(), 0);
573        assert_eq!(Type::CAP.bits(), 0);
574        assert!(Type::VOID.is_void());
575        assert!(Type::PTR.is_ptr());
576        assert!(Type::CAP.is_cap());
577    }
578
579    #[test]
580    fn a_capability_is_none_of_the_other_kinds() {
581        assert_eq!(Type::CAP.kind(), Kind::Cap);
582        assert_eq!(Type::CAP.to_string(), "cap");
583        assert_eq!(Type::parse("cap"), Some(Type::CAP));
584        // A pointer is the one it would be mistaken for, since the instrumentation keeps the two
585        // side by side, and the whole point of the type is that they are not interchangeable.
586        for other in [Type::VOID, Type::PTR, Type::MEM, Type::int(64), Type::float(Float::F64)] {
587            assert!(!other.is_cap(), "{other} answered to being a capability");
588            assert_ne!(other, Type::CAP);
589        }
590        assert!(!Type::CAP.is_ptr() && !Type::CAP.is_void() && !Type::CAP.is_mem());
591        assert!(Type::CAP.is_scalar());
592    }
593
594    #[test]
595    fn a_comparison_keeps_the_lane_count() {
596        assert_eq!(Type::vector(Type::int(32), 4).with_lane(Type::I1), Type::vector(Type::I1, 4));
597        assert_eq!(Type::int(32).with_lane(Type::I1), Type::I1);
598    }
599
600    #[test]
601    fn the_extremes_are_representable() {
602        let widest = Type::int(Type::MAX_BITS);
603        assert_eq!(widest.bits(), Type::MAX_BITS);
604        let longest = Type::vector(Type::I1, Type::MAX_LANES);
605        assert_eq!(longest.lanes(), Type::MAX_LANES);
606        assert_eq!(longest.lane(), Type::I1);
607    }
608
609    #[test]
610    fn every_type_round_trips_through_its_text() {
611        let mut types = vec![Type::VOID, Type::PTR, Type::MEM, Type::CAP];
612        for bits in [1, 8, 16, 32, 64, 128, 3, 12, Type::MAX_BITS] {
613            types.push(Type::int(bits));
614        }
615        for format in [Float::F16, Float::F32, Float::F64, Float::F80, Float::F128] {
616            types.push(Type::float(format));
617        }
618        for lanes in [2, 4, 16, Type::MAX_LANES] {
619            types.push(Type::vector(Type::int(8), lanes));
620            types.push(Type::vector(Type::float(Float::F32), lanes));
621        }
622        for ty in types {
623            let text = ty.to_string();
624            assert_eq!(Type::parse(&text), Some(ty), "{text}");
625        }
626    }
627
628    #[test]
629    fn the_texts_that_are_not_types_are_refused() {
630        for text in [
631            "",
632            "i",
633            "f",
634            "i0",
635            "i8x0",
636            "i8x1",
637            "f24",
638            "f0",
639            "i-1",
640            "i+1",
641            "i08",
642            "i8x01",
643            "int",
644            "i32 ",
645            " i32",
646            "i8x",
647            "x4",
648            "i8x4x4",
649            "i65536",
650            "i8x8193",
651            "voidx2",
652            "ptrx2",
653            "capx2",
654            "cap ",
655            "Cap",
656            "capability",
657        ] {
658            assert_eq!(Type::parse(text), None, "{text}");
659        }
660    }
661
662    #[test]
663    fn a_format_knows_its_width_both_ways() {
664        for format in [Float::F16, Float::F32, Float::F64, Float::F80, Float::F128] {
665            assert_eq!(Float::from_bits(format.bits()), Some(format));
666            assert_eq!(Type::float(format).format(), Some(format));
667        }
668        assert_eq!(Float::from_bits(24), None);
669        assert_eq!(Type::int(32).format(), None);
670    }
671
672    #[test]
673    fn a_decimal_is_its_own_type_at_a_width_a_binary_format_has() {
674        for (decimal, binary) in
675            [(Float::D32, Float::F32), (Float::D64, Float::F64), (Float::D128, Float::F128)]
676        {
677            let ty = Type::float(decimal);
678            assert_ne!(ty, Type::float(binary));
679            assert_eq!(ty.bits(), binary.bits());
680            assert_eq!(ty.format(), Some(decimal));
681            assert!(ty.is_float() && decimal.is_decimal() && !binary.is_decimal());
682            assert_eq!(Type::parse(&ty.to_string()), Some(ty));
683            let vector = Type::vector(ty, 4);
684            assert_eq!(vector.lane(), ty);
685            assert_eq!(Type::parse(&vector.to_string()), Some(vector));
686        }
687        assert_eq!(Type::float(Float::D64).to_string(), "d64");
688        assert_eq!(Float::from_bits(64), Some(Float::F64));
689        assert_eq!(Type::parse("d16"), None);
690        assert_eq!(Type::parse("d80"), None);
691    }
692
693    #[test]
694    #[should_panic(expected = "integer width out of range")]
695    fn a_zero_width_integer_is_refused() {
696        let _ = Type::int(0);
697    }
698
699    #[test]
700    #[should_panic(expected = "integer width out of range")]
701    fn an_integer_wider_than_the_packing_is_refused() {
702        let _ = Type::int(Type::MAX_BITS + 1);
703    }
704
705    #[test]
706    #[should_panic(expected = "lane count out of range")]
707    fn a_vector_with_no_lanes_is_refused() {
708        let _ = Type::vector(Type::int(8), 0);
709    }
710
711    #[test]
712    #[should_panic(expected = "a vector's lane is a scalar")]
713    fn a_vector_of_vectors_is_refused() {
714        let _ = Type::vector(Type::vector(Type::int(8), 2), 2);
715    }
716
717    #[test]
718    #[should_panic(expected = "an integer or a floating point value")]
719    fn a_vector_of_pointers_is_refused() {
720        let _ = Type::vector(Type::PTR, 2);
721    }
722
723    #[test]
724    #[should_panic(expected = "an integer or a floating point value")]
725    fn a_vector_of_capabilities_is_refused() {
726        let _ = Type::vector(Type::CAP, 2);
727    }
728}