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}