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}
130
131impl Float {
132    /// The width of the format in bits.
133    ///
134    /// This is the width of the format and not the size of the object. `F80` is eighty bits of
135    /// format in a ten, twelve or sixteen byte object depending on the target.
136    #[must_use]
137    pub const fn bits(self) -> u32 {
138        match self {
139            Self::F16 => 16,
140            Self::F32 => 32,
141            Self::F64 => 64,
142            Self::F80 => 80,
143            Self::F128 => 128,
144        }
145    }
146
147    /// The format of that width, if there is one.
148    #[must_use]
149    pub const fn from_bits(bits: u32) -> Option<Self> {
150        match bits {
151            16 => Some(Self::F16),
152            32 => Some(Self::F32),
153            64 => Some(Self::F64),
154            80 => Some(Self::F80),
155            128 => Some(Self::F128),
156            _ => None,
157        }
158    }
159
160    /// The encoding this is, as `rucc_base::float` spells it.
161    ///
162    /// The inverse of the map `rucc-lower` keeps in the other direction, and total where that one
163    /// is not. Every format the IR has a type for is an IEEE encoding, so the two that map to
164    /// nothing there, the brain float and the double-double, are not among these and there is no
165    /// case here to return nothing for.
166    ///
167    /// It is on the type rather than in the crate that wants it because which encoding an `f80` is
168    /// is a fact about `f80` and not about whoever is asking. Anything that has to interpret the
169    /// bits of an `fconst` needs it, and a copy of the table in each of them is a table that can
170    /// disagree with itself.
171    ///
172    /// ```
173    /// use rucc_base::float::Format;
174    /// use rucc_ir::Float;
175    ///
176    /// assert_eq!(Float::F64.encoding(), Format::Double);
177    /// assert_eq!(Float::F80.encoding(), Format::X87Extended);
178    /// ```
179    #[must_use]
180    pub const fn encoding(self) -> Format {
181        match self {
182            Self::F16 => Format::Half,
183            Self::F32 => Format::Single,
184            Self::F64 => Format::Double,
185            Self::F80 => Format::X87Extended,
186            Self::F128 => Format::Quad,
187        }
188    }
189}
190
191impl fmt::Display for Float {
192    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193        write!(f, "f{}", self.bits())
194    }
195}
196
197// Where the packing lives. Changing any of these changes the meaning of every `Type` in a
198// serialised module, which is why the textual form carries a version.
199const BITS_SHIFT: u32 = 0;
200const BITS_MASK: u32 = 0xffff;
201const LANES_SHIFT: u32 = 16;
202const LANES_MASK: u32 = 0x1fff;
203const KIND_SHIFT: u32 = 29;
204
205impl Type {
206    /// The widest integer that can be represented, which is what limits `_BitInt`.
207    ///
208    /// Sixteen bits of width is more than any target's `BITINT_MAXWIDTH` and more than any
209    /// vector register, and it leaves room in the same four bytes for the lane count.
210    pub const MAX_BITS: u32 = BITS_MASK;
211
212    /// The most lanes a vector can have.
213    pub const MAX_LANES: u32 = LANES_MASK + 1;
214
215    /// No value.
216    pub const VOID: Self = Self::pack(Kind::Void, 0, 1);
217    /// An address.
218    pub const PTR: Self = Self::pack(Kind::Ptr, 0, 1);
219    /// The state of memory. See the note at the top of this module.
220    pub const MEM: Self = Self::pack(Kind::Mem, 0, 1);
221    /// A capability. See the note at the top of this module.
222    pub const CAP: Self = Self::pack(Kind::Cap, 0, 1);
223    /// The one-bit integer every comparison produces.
224    pub const I1: Self = Self::pack(Kind::Int, 1, 1);
225
226    /// Builds a type from its parts, with no checking. Every public constructor checks first.
227    const fn pack(kind: Kind, bits: u32, lanes: u32) -> Self {
228        Self((kind as u32) << KIND_SHIFT | (lanes - 1) << LANES_SHIFT | bits << BITS_SHIFT)
229    }
230
231    /// An integer `bits` wide.
232    ///
233    /// # Panics
234    ///
235    /// Panics if `bits` is zero or above [`Type::MAX_BITS`]. A zero-width integer is not a
236    /// thing the IR has, and a caller that computed one has a bug that gets much harder to
237    /// find if it is allowed to travel.
238    #[must_use]
239    pub const fn int(bits: u32) -> Self {
240        assert!(bits > 0 && bits <= Self::MAX_BITS, "integer width out of range");
241        Self::pack(Kind::Int, bits, 1)
242    }
243
244    /// A floating point value in the given format.
245    #[must_use]
246    pub const fn float(format: Float) -> Self {
247        Self::pack(Kind::Float, format.bits(), 1)
248    }
249
250    /// A vector of `lanes` copies of `lane`.
251    ///
252    /// # Panics
253    ///
254    /// Panics if `lane` is not an integer or a floating point type, if it is itself a vector,
255    /// or if `lanes` is zero or above [`Type::MAX_LANES`]. A vector of pointers is not in the
256    /// instruction set, so admitting the type would mean admitting a value nothing can be done
257    /// with.
258    #[must_use]
259    pub const fn vector(lane: Self, lanes: u32) -> Self {
260        assert!(lanes > 0 && lanes <= Self::MAX_LANES, "lane count out of range");
261        assert!(lane.is_scalar(), "a vector's lane is a scalar");
262        assert!(
263            matches!(lane.kind(), Kind::Int | Kind::Float),
264            "a vector's lane is an integer or a floating point value"
265        );
266        Self::pack(lane.kind(), lane.bits(), lanes)
267    }
268
269    /// Which of the six kinds this is.
270    #[must_use]
271    pub const fn kind(self) -> Kind {
272        match self.0 >> KIND_SHIFT {
273            0 => Kind::Void,
274            1 => Kind::Int,
275            2 => Kind::Float,
276            3 => Kind::Ptr,
277            4 => Kind::Mem,
278            _ => Kind::Cap,
279        }
280    }
281
282    /// The width of one lane in bits, which for a scalar is the width of the type.
283    ///
284    /// Zero for `void`, for `ptr` and for `cap`, since the width of an address is a property of
285    /// the target and not of the type, and a capability has no width in the IR at all. Ask the
286    /// target for the first and `spec/safe-memory/05-representation.md` for the second.
287    #[must_use]
288    pub const fn bits(self) -> u32 {
289        self.0 >> BITS_SHIFT & BITS_MASK
290    }
291
292    /// How many lanes this has, which is one unless it is a vector.
293    #[must_use]
294    pub const fn lanes(self) -> u32 {
295        (self.0 >> LANES_SHIFT & LANES_MASK) + 1
296    }
297
298    /// Whether this has exactly one lane.
299    #[must_use]
300    pub const fn is_scalar(self) -> bool {
301        self.lanes() == 1
302    }
303
304    /// Whether this has more than one lane.
305    #[must_use]
306    pub const fn is_vector(self) -> bool {
307        self.lanes() > 1
308    }
309
310    /// The type of one lane, which for a scalar is the type itself.
311    #[must_use]
312    pub const fn lane(self) -> Self {
313        Self::pack(self.kind(), self.bits(), 1)
314    }
315
316    /// The same shape as this, with the lane type replaced.
317    ///
318    /// This is what a comparison does: `icmp` over `i32x4` produces `i1x4`, and the rule that
319    /// the lane count is carried across is easier to get right in one place than at every
320    /// instruction that needs it.
321    ///
322    /// # Panics
323    ///
324    /// Panics under the same conditions as [`Type::vector`].
325    #[must_use]
326    pub const fn with_lane(self, lane: Self) -> Self {
327        Self::vector(lane, self.lanes())
328    }
329
330    /// Whether this is an integer, of any width, scalar or vector.
331    #[must_use]
332    pub const fn is_int(self) -> bool {
333        matches!(self.kind(), Kind::Int)
334    }
335
336    /// Whether this is a floating point value, scalar or vector.
337    #[must_use]
338    pub const fn is_float(self) -> bool {
339        matches!(self.kind(), Kind::Float)
340    }
341
342    /// Whether this is an address. A vector of pointers cannot be built, so this is scalar.
343    #[must_use]
344    pub const fn is_ptr(self) -> bool {
345        matches!(self.kind(), Kind::Ptr)
346    }
347
348    /// Whether this is the absence of a value.
349    #[must_use]
350    pub const fn is_void(self) -> bool {
351        matches!(self.kind(), Kind::Void)
352    }
353
354    /// Whether this is the state of memory.
355    #[must_use]
356    pub const fn is_mem(self) -> bool {
357        matches!(self.kind(), Kind::Mem)
358    }
359
360    /// Whether this is a capability. A vector of capabilities cannot be built, so this is scalar.
361    #[must_use]
362    pub const fn is_cap(self) -> bool {
363        matches!(self.kind(), Kind::Cap)
364    }
365
366    /// The floating point format, if this is one.
367    #[must_use]
368    pub const fn format(self) -> Option<Float> {
369        match self.kind() {
370            Kind::Float => Float::from_bits(self.bits()),
371            _ => None,
372        }
373    }
374
375    /// Parses the textual form, which is what the printer writes.
376    ///
377    /// ```
378    /// use rucc_ir::Type;
379    ///
380    /// assert_eq!(Type::parse("i32"), Some(Type::int(32)));
381    /// assert_eq!(Type::parse("f32x4"), Some(Type::vector(Type::float(rucc_ir::Float::F32), 4)));
382    /// assert_eq!(Type::parse("i0"), None);
383    /// assert_eq!(Type::parse("i32 "), None);
384    /// ```
385    #[must_use]
386    pub fn parse(text: &str) -> Option<Self> {
387        if text == "void" {
388            return Some(Self::VOID);
389        }
390        if text == "ptr" {
391            return Some(Self::PTR);
392        }
393        if text == "mem" {
394            return Some(Self::MEM);
395        }
396        if text == "cap" {
397            return Some(Self::CAP);
398        }
399        let (head, lanes) = match text.split_once('x') {
400            // A lane count of one is not written, so `i8x1` is not a spelling of anything and
401            // accepting it would give two texts for one type and break the round trip.
402            Some((head, lanes)) => (head, parse_u32(lanes).filter(|&n| n > 1)?),
403            None => (text, 1),
404        };
405        let bits = parse_u32(head.strip_prefix(['i', 'f'])?)?;
406        let lane = match head.as_bytes()[0] {
407            b'i' if bits > 0 && bits <= Self::MAX_BITS => Self::int(bits),
408            b'f' => Self::float(Float::from_bits(bits)?),
409            _ => return None,
410        };
411        if lanes > Self::MAX_LANES {
412            return None;
413        }
414        Some(if lanes == 1 { lane } else { Self::vector(lane, lanes) })
415    }
416}
417
418/// A decimal `u32` with no sign, no underscores, and no leading zero on a non-zero number.
419///
420/// `str::parse` would take `+4` and `0004`, and either one would be a second spelling of a
421/// type that already has one, which is what breaks a byte for byte round trip.
422fn parse_u32(text: &str) -> Option<u32> {
423    if text.is_empty() || (text.starts_with('0') && text.len() > 1) {
424        return None;
425    }
426    text.bytes().all(|b| b.is_ascii_digit()).then(|| text.parse().ok())?
427}
428
429impl fmt::Display for Type {
430    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
431        match self.kind() {
432            Kind::Void => return f.write_str("void"),
433            Kind::Ptr => return f.write_str("ptr"),
434            Kind::Mem => return f.write_str("mem"),
435            Kind::Cap => return f.write_str("cap"),
436            Kind::Int => write!(f, "i{}", self.bits())?,
437            Kind::Float => write!(f, "f{}", self.bits())?,
438        }
439        if self.is_vector() {
440            write!(f, "x{}", self.lanes())?;
441        }
442        Ok(())
443    }
444}
445
446impl fmt::Debug for Type {
447    // The `Display` form is the one anybody wants to read, and a derived `Debug` would print
448    // the packed integer, which is not information anybody can use.
449    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
450        fmt::Display::fmt(self, f)
451    }
452}
453
454#[cfg(test)]
455mod tests {
456    use super::*;
457
458    #[test]
459    fn a_type_is_four_bytes() {
460        assert_eq!(size_of::<Type>(), 4);
461    }
462
463    #[test]
464    fn memory_is_its_own_kind_and_nothing_else_answers_to_it() {
465        assert_eq!(Type::MEM.kind(), Kind::Mem);
466        assert!(Type::MEM.is_mem());
467        assert_eq!(Type::MEM.to_string(), "mem");
468        assert_eq!(Type::parse("mem"), Some(Type::MEM));
469        // It is not void, which is what a reader who skimmed the packing might expect, and the
470        // difference matters because a store produces no value and may still define memory.
471        for other in [Type::VOID, Type::PTR, Type::int(64), Type::float(Float::F64)] {
472            assert!(!other.is_mem(), "{other} answered to being memory");
473            assert_ne!(other, Type::MEM);
474        }
475        assert!(!Type::MEM.is_void() && !Type::MEM.is_ptr() && !Type::MEM.is_int());
476    }
477
478    #[test]
479    fn the_widest_vector_still_packs_beside_the_new_kind() {
480        // The kind took a bit off the lane count. Both ends of the range have to survive that,
481        // because getting the mask wrong reads back as a vector of a different width rather
482        // than as anything that fails.
483        let widest = Type::vector(Type::int(8), Type::MAX_LANES);
484        assert_eq!(widest.lanes(), Type::MAX_LANES);
485        assert_eq!(widest.lane(), Type::int(8));
486        let widest_int = Type::int(Type::MAX_BITS);
487        assert_eq!(widest_int.bits(), Type::MAX_BITS);
488        assert_eq!(widest_int.lanes(), 1);
489        assert_eq!(Type::vector(widest_int, Type::MAX_LANES).bits(), Type::MAX_BITS);
490    }
491
492    #[test]
493    fn the_parts_come_back_out() {
494        let v = Type::vector(Type::int(8), 16);
495        assert_eq!(v.kind(), Kind::Int);
496        assert_eq!(v.bits(), 8);
497        assert_eq!(v.lanes(), 16);
498        assert_eq!(v.lane(), Type::int(8));
499        assert!(v.is_vector());
500        assert!(!v.is_scalar());
501    }
502
503    #[test]
504    fn a_scalar_has_one_lane_and_is_its_own_lane() {
505        let i32_ = Type::int(32);
506        assert_eq!(i32_.lanes(), 1);
507        assert_eq!(i32_.lane(), i32_);
508        assert!(i32_.is_scalar());
509    }
510
511    #[test]
512    fn void_and_ptr_and_cap_have_no_width_of_their_own() {
513        assert_eq!(Type::VOID.bits(), 0);
514        assert_eq!(Type::PTR.bits(), 0);
515        assert_eq!(Type::CAP.bits(), 0);
516        assert!(Type::VOID.is_void());
517        assert!(Type::PTR.is_ptr());
518        assert!(Type::CAP.is_cap());
519    }
520
521    #[test]
522    fn a_capability_is_none_of_the_other_kinds() {
523        assert_eq!(Type::CAP.kind(), Kind::Cap);
524        assert_eq!(Type::CAP.to_string(), "cap");
525        assert_eq!(Type::parse("cap"), Some(Type::CAP));
526        // A pointer is the one it would be mistaken for, since the instrumentation keeps the two
527        // side by side, and the whole point of the type is that they are not interchangeable.
528        for other in [Type::VOID, Type::PTR, Type::MEM, Type::int(64), Type::float(Float::F64)] {
529            assert!(!other.is_cap(), "{other} answered to being a capability");
530            assert_ne!(other, Type::CAP);
531        }
532        assert!(!Type::CAP.is_ptr() && !Type::CAP.is_void() && !Type::CAP.is_mem());
533        assert!(Type::CAP.is_scalar());
534    }
535
536    #[test]
537    fn a_comparison_keeps_the_lane_count() {
538        assert_eq!(Type::vector(Type::int(32), 4).with_lane(Type::I1), Type::vector(Type::I1, 4));
539        assert_eq!(Type::int(32).with_lane(Type::I1), Type::I1);
540    }
541
542    #[test]
543    fn the_extremes_are_representable() {
544        let widest = Type::int(Type::MAX_BITS);
545        assert_eq!(widest.bits(), Type::MAX_BITS);
546        let longest = Type::vector(Type::I1, Type::MAX_LANES);
547        assert_eq!(longest.lanes(), Type::MAX_LANES);
548        assert_eq!(longest.lane(), Type::I1);
549    }
550
551    #[test]
552    fn every_type_round_trips_through_its_text() {
553        let mut types = vec![Type::VOID, Type::PTR, Type::MEM, Type::CAP];
554        for bits in [1, 8, 16, 32, 64, 128, 3, 12, Type::MAX_BITS] {
555            types.push(Type::int(bits));
556        }
557        for format in [Float::F16, Float::F32, Float::F64, Float::F80, Float::F128] {
558            types.push(Type::float(format));
559        }
560        for lanes in [2, 4, 16, Type::MAX_LANES] {
561            types.push(Type::vector(Type::int(8), lanes));
562            types.push(Type::vector(Type::float(Float::F32), lanes));
563        }
564        for ty in types {
565            let text = ty.to_string();
566            assert_eq!(Type::parse(&text), Some(ty), "{text}");
567        }
568    }
569
570    #[test]
571    fn the_texts_that_are_not_types_are_refused() {
572        for text in [
573            "",
574            "i",
575            "f",
576            "i0",
577            "i8x0",
578            "i8x1",
579            "f24",
580            "f0",
581            "i-1",
582            "i+1",
583            "i08",
584            "i8x01",
585            "int",
586            "i32 ",
587            " i32",
588            "i8x",
589            "x4",
590            "i8x4x4",
591            "i65536",
592            "i8x8193",
593            "voidx2",
594            "ptrx2",
595            "capx2",
596            "cap ",
597            "Cap",
598            "capability",
599        ] {
600            assert_eq!(Type::parse(text), None, "{text}");
601        }
602    }
603
604    #[test]
605    fn a_format_knows_its_width_both_ways() {
606        for format in [Float::F16, Float::F32, Float::F64, Float::F80, Float::F128] {
607            assert_eq!(Float::from_bits(format.bits()), Some(format));
608            assert_eq!(Type::float(format).format(), Some(format));
609        }
610        assert_eq!(Float::from_bits(24), None);
611        assert_eq!(Type::int(32).format(), None);
612    }
613
614    #[test]
615    #[should_panic(expected = "integer width out of range")]
616    fn a_zero_width_integer_is_refused() {
617        let _ = Type::int(0);
618    }
619
620    #[test]
621    #[should_panic(expected = "integer width out of range")]
622    fn an_integer_wider_than_the_packing_is_refused() {
623        let _ = Type::int(Type::MAX_BITS + 1);
624    }
625
626    #[test]
627    #[should_panic(expected = "lane count out of range")]
628    fn a_vector_with_no_lanes_is_refused() {
629        let _ = Type::vector(Type::int(8), 0);
630    }
631
632    #[test]
633    #[should_panic(expected = "a vector's lane is a scalar")]
634    fn a_vector_of_vectors_is_refused() {
635        let _ = Type::vector(Type::vector(Type::int(8), 2), 2);
636    }
637
638    #[test]
639    #[should_panic(expected = "an integer or a floating point value")]
640    fn a_vector_of_pointers_is_refused() {
641        let _ = Type::vector(Type::PTR, 2);
642    }
643
644    #[test]
645    #[should_panic(expected = "an integer or a floating point value")]
646    fn a_vector_of_capabilities_is_refused() {
647        let _ = Type::vector(Type::CAP, 2);
648    }
649}