ir_lang/types.rs
1//! Value types, memory types, aggregate layout, and signatures.
2
3use alloc::vec::Vec;
4use core::fmt;
5
6use crate::entity::AggId;
7
8/// The type of an SSA value.
9///
10/// Integers are sized and carry their signedness, mirroring the value domains of the
11/// operation semantics (`specs/OPS.md` §1): every signedness-dependent operation
12/// (overflow detection, division, right shift, ordering, conversions) takes its
13/// signedness from its operand type, so an instruction cannot disagree with the
14/// domain it computes in. [`Ptr`](Type::Ptr) is an untraced address; [`Ref`](Type::Ref)
15/// is a garbage-collected reference that a moving collector may relocate at a
16/// safepoint (see the safepoint rules in `specs/IR.md` §9).
17///
18/// Aggregates are memory types only ([`MemType`]): they live in stack slots and
19/// globals and are reached through addresses, never held in an SSA value.
20/// Vector types are reserved; the enum is `#[non_exhaustive]` so they can be added
21/// in a minor release.
22///
23/// # Examples
24///
25/// ```
26/// use ir_lang::{PointerWidth, Type};
27///
28/// assert!(Type::I32.is_signed_int());
29/// assert!(Type::U8.is_unsigned_int());
30/// assert_eq!(Type::I16.int_bits(), Some(16));
31/// assert_eq!(Type::Ptr.size(PointerWidth::P64), 8);
32/// assert_eq!(Type::F32.to_string(), "f32");
33/// ```
34#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
35#[non_exhaustive]
36pub enum Type {
37 /// A boolean: `false` or `true` (one byte in memory, `0` or `1`).
38 Bool,
39 /// A signed 8-bit integer.
40 I8,
41 /// A signed 16-bit integer.
42 I16,
43 /// A signed 32-bit integer.
44 I32,
45 /// A signed 64-bit integer.
46 I64,
47 /// An unsigned 8-bit integer.
48 U8,
49 /// An unsigned 16-bit integer.
50 U16,
51 /// An unsigned 32-bit integer.
52 U32,
53 /// An unsigned 64-bit integer.
54 U64,
55 /// An IEEE 754 binary32 float.
56 F32,
57 /// An IEEE 754 binary64 float.
58 F64,
59 /// An untraced address, as wide as the module's [`PointerWidth`].
60 Ptr,
61 /// A garbage-collected reference, as wide as the module's [`PointerWidth`].
62 Ref,
63}
64
65impl Type {
66 /// Every value type, in a fixed order (useful for generators and tables).
67 pub const ALL: [Type; 13] = [
68 Type::Bool,
69 Type::I8,
70 Type::I16,
71 Type::I32,
72 Type::I64,
73 Type::U8,
74 Type::U16,
75 Type::U32,
76 Type::U64,
77 Type::F32,
78 Type::F64,
79 Type::Ptr,
80 Type::Ref,
81 ];
82
83 /// Whether this is an integer type (signed or unsigned).
84 ///
85 /// # Examples
86 ///
87 /// ```
88 /// use ir_lang::Type;
89 ///
90 /// assert!(Type::U64.is_int());
91 /// assert!(!Type::Bool.is_int());
92 /// ```
93 #[inline]
94 #[must_use]
95 pub const fn is_int(self) -> bool {
96 self.int_bits().is_some()
97 }
98
99 /// Whether this is a signed integer type.
100 ///
101 /// # Examples
102 ///
103 /// ```
104 /// use ir_lang::Type;
105 ///
106 /// assert!(Type::I8.is_signed_int());
107 /// assert!(!Type::U8.is_signed_int());
108 /// ```
109 #[inline]
110 #[must_use]
111 pub const fn is_signed_int(self) -> bool {
112 matches!(self, Type::I8 | Type::I16 | Type::I32 | Type::I64)
113 }
114
115 /// Whether this is an unsigned integer type.
116 ///
117 /// # Examples
118 ///
119 /// ```
120 /// use ir_lang::Type;
121 ///
122 /// assert!(Type::U16.is_unsigned_int());
123 /// assert!(!Type::I16.is_unsigned_int());
124 /// ```
125 #[inline]
126 #[must_use]
127 pub const fn is_unsigned_int(self) -> bool {
128 matches!(self, Type::U8 | Type::U16 | Type::U32 | Type::U64)
129 }
130
131 /// Whether this is a float type.
132 ///
133 /// # Examples
134 ///
135 /// ```
136 /// use ir_lang::Type;
137 ///
138 /// assert!(Type::F64.is_float());
139 /// assert!(!Type::I64.is_float());
140 /// ```
141 #[inline]
142 #[must_use]
143 pub const fn is_float(self) -> bool {
144 matches!(self, Type::F32 | Type::F64)
145 }
146
147 /// Whether this is an integer or float type.
148 ///
149 /// # Examples
150 ///
151 /// ```
152 /// use ir_lang::Type;
153 ///
154 /// assert!(Type::F32.is_numeric());
155 /// assert!(!Type::Ptr.is_numeric());
156 /// ```
157 #[inline]
158 #[must_use]
159 pub const fn is_numeric(self) -> bool {
160 self.is_int() || self.is_float()
161 }
162
163 /// The width of an integer type in bits, or `None` for other types.
164 ///
165 /// # Examples
166 ///
167 /// ```
168 /// use ir_lang::Type;
169 ///
170 /// assert_eq!(Type::U32.int_bits(), Some(32));
171 /// assert_eq!(Type::F32.int_bits(), None);
172 /// ```
173 #[inline]
174 #[must_use]
175 pub const fn int_bits(self) -> Option<u32> {
176 match self {
177 Type::I8 | Type::U8 => Some(8),
178 Type::I16 | Type::U16 => Some(16),
179 Type::I32 | Type::U32 => Some(32),
180 Type::I64 | Type::U64 => Some(64),
181 _ => None,
182 }
183 }
184
185 /// The width of the type's value in bits, for the given pointer width
186 /// (`Bool` is 1 bit).
187 ///
188 /// # Examples
189 ///
190 /// ```
191 /// use ir_lang::{PointerWidth, Type};
192 ///
193 /// assert_eq!(Type::Bool.bits(PointerWidth::P64), 1);
194 /// assert_eq!(Type::Ref.bits(PointerWidth::P32), 32);
195 /// ```
196 #[inline]
197 #[must_use]
198 pub const fn bits(self, ptr: PointerWidth) -> u32 {
199 match self {
200 Type::Bool => 1,
201 Type::F32 => 32,
202 Type::F64 => 64,
203 Type::Ptr | Type::Ref => ptr.bits(),
204 other => match other.int_bits() {
205 Some(b) => b,
206 None => 0,
207 },
208 }
209 }
210
211 /// The size of the type in memory, in bytes (also its natural alignment).
212 ///
213 /// # Examples
214 ///
215 /// ```
216 /// use ir_lang::{PointerWidth, Type};
217 ///
218 /// assert_eq!(Type::Bool.size(PointerWidth::P64), 1);
219 /// assert_eq!(Type::I16.size(PointerWidth::P64), 2);
220 /// assert_eq!(Type::Ptr.size(PointerWidth::P32), 4);
221 /// ```
222 #[inline]
223 #[must_use]
224 pub const fn size(self, ptr: PointerWidth) -> u32 {
225 match self {
226 Type::Bool => 1,
227 other => other.bits(ptr) / 8,
228 }
229 }
230
231 /// The integer type of the given width and signedness, if there is one.
232 ///
233 /// # Examples
234 ///
235 /// ```
236 /// use ir_lang::Type;
237 ///
238 /// assert_eq!(Type::int(32, true), Some(Type::I32));
239 /// assert_eq!(Type::int(12, false), None);
240 /// ```
241 #[must_use]
242 pub const fn int(bits: u32, signed: bool) -> Option<Type> {
243 Some(match (bits, signed) {
244 (8, true) => Type::I8,
245 (16, true) => Type::I16,
246 (32, true) => Type::I32,
247 (64, true) => Type::I64,
248 (8, false) => Type::U8,
249 (16, false) => Type::U16,
250 (32, false) => Type::U32,
251 (64, false) => Type::U64,
252 _ => return None,
253 })
254 }
255
256 /// The textual name of the type (`i32`, `ptr`, ...).
257 ///
258 /// # Examples
259 ///
260 /// ```
261 /// use ir_lang::Type;
262 ///
263 /// assert_eq!(Type::Ref.name(), "ref");
264 /// ```
265 #[must_use]
266 pub const fn name(self) -> &'static str {
267 match self {
268 Type::Bool => "bool",
269 Type::I8 => "i8",
270 Type::I16 => "i16",
271 Type::I32 => "i32",
272 Type::I64 => "i64",
273 Type::U8 => "u8",
274 Type::U16 => "u16",
275 Type::U32 => "u32",
276 Type::U64 => "u64",
277 Type::F32 => "f32",
278 Type::F64 => "f64",
279 Type::Ptr => "ptr",
280 Type::Ref => "ref",
281 }
282 }
283
284 /// Looks a type up by its textual name.
285 ///
286 /// # Examples
287 ///
288 /// ```
289 /// use ir_lang::Type;
290 ///
291 /// assert_eq!(Type::from_name("u16"), Some(Type::U16));
292 /// assert_eq!(Type::from_name("int"), None);
293 /// ```
294 #[must_use]
295 pub fn from_name(name: &str) -> Option<Type> {
296 Some(match name {
297 "bool" => Type::Bool,
298 "i8" => Type::I8,
299 "i16" => Type::I16,
300 "i32" => Type::I32,
301 "i64" => Type::I64,
302 "u8" => Type::U8,
303 "u16" => Type::U16,
304 "u32" => Type::U32,
305 "u64" => Type::U64,
306 "f32" => Type::F32,
307 "f64" => Type::F64,
308 "ptr" => Type::Ptr,
309 "ref" => Type::Ref,
310 _ => return None,
311 })
312 }
313}
314
315impl fmt::Display for Type {
316 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317 f.write_str(self.name())
318 }
319}
320
321/// The width of addresses (`ptr` and `ref`) in a module.
322///
323/// # Examples
324///
325/// ```
326/// use ir_lang::PointerWidth;
327///
328/// assert_eq!(PointerWidth::P64.bytes(), 8);
329/// assert_eq!(PointerWidth::P32.bits(), 32);
330/// ```
331#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
332#[non_exhaustive]
333pub enum PointerWidth {
334 /// 32-bit addresses (wasm32, 32-bit targets).
335 P32,
336 /// 64-bit addresses (x86-64, AArch64, riscv64). The default.
337 #[default]
338 P64,
339}
340
341impl PointerWidth {
342 /// The address width in bytes.
343 ///
344 /// # Examples
345 ///
346 /// ```
347 /// use ir_lang::PointerWidth;
348 ///
349 /// assert_eq!(PointerWidth::P32.bytes(), 4);
350 /// ```
351 #[inline]
352 #[must_use]
353 pub const fn bytes(self) -> u32 {
354 match self {
355 PointerWidth::P32 => 4,
356 PointerWidth::P64 => 8,
357 }
358 }
359
360 /// The address width in bits.
361 ///
362 /// # Examples
363 ///
364 /// ```
365 /// use ir_lang::PointerWidth;
366 ///
367 /// assert_eq!(PointerWidth::P64.bits(), 64);
368 /// ```
369 #[inline]
370 #[must_use]
371 pub const fn bits(self) -> u32 {
372 self.bytes() * 8
373 }
374
375 /// Whether `ty` is an integer exactly as wide as an address (the integer side
376 /// of `ptr_to_int` / `int_to_ptr`).
377 ///
378 /// # Examples
379 ///
380 /// ```
381 /// use ir_lang::{PointerWidth, Type};
382 ///
383 /// assert!(PointerWidth::P64.is_address_int(Type::U64));
384 /// assert!(!PointerWidth::P64.is_address_int(Type::U32));
385 /// ```
386 #[inline]
387 #[must_use]
388 pub const fn is_address_int(self, ty: Type) -> bool {
389 match ty.int_bits() {
390 Some(bits) => bits == self.bits(),
391 None => false,
392 }
393 }
394}
395
396/// A type as laid out in memory: a scalar value type or an aggregate.
397///
398/// # Examples
399///
400/// ```
401/// use ir_lang::{AggId, MemType, Type};
402///
403/// let m: MemType = Type::I32.into();
404/// assert_eq!(m, MemType::Scalar(Type::I32));
405/// assert_eq!(MemType::from(AggId::from_u32(0)).to_string(), "t0");
406/// ```
407#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
408#[non_exhaustive]
409pub enum MemType {
410 /// A scalar of the given value type.
411 Scalar(Type),
412 /// An aggregate declared in the module.
413 Agg(AggId),
414}
415
416impl From<Type> for MemType {
417 fn from(ty: Type) -> Self {
418 MemType::Scalar(ty)
419 }
420}
421
422impl From<AggId> for MemType {
423 fn from(agg: AggId) -> Self {
424 MemType::Agg(agg)
425 }
426}
427
428impl fmt::Display for MemType {
429 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430 match self {
431 MemType::Scalar(ty) => write!(f, "{ty}"),
432 MemType::Agg(agg) => write!(f, "{agg}"),
433 }
434 }
435}
436
437/// An aggregate memory type: a struct or a fixed-length array.
438///
439/// An aggregate may name only aggregates declared before it, so layouts are computed
440/// in declaration order and a recursive aggregate is impossible (recursion goes
441/// through `ptr`, which is opaque).
442///
443/// # Examples
444///
445/// ```
446/// use ir_lang::{Aggregate, MemType, Module, Type};
447///
448/// let mut m = Module::new("demo");
449/// let pair = m.declare_aggregate(Aggregate::structure(&[Type::U8.into(), Type::I64.into()]))?;
450/// let layout = m.layout(pair.into()).unwrap();
451/// assert_eq!((layout.size(), layout.align()), (16, 8));
452/// assert_eq!(m.field_offset(pair, 1), Some(8));
453/// # Ok::<(), ir_lang::ModuleError>(())
454/// ```
455#[derive(Clone, PartialEq, Eq, Hash, Debug)]
456#[non_exhaustive]
457pub enum Aggregate {
458 /// Fields laid out in order. Unless `packed`, each field is placed at the next
459 /// multiple of its alignment and the size is rounded up to the struct's
460 /// alignment (the C rule); a packed struct has alignment 1 and no padding.
461 Struct {
462 /// The field types, in order.
463 fields: Vec<MemType>,
464 /// Whether the struct is packed.
465 packed: bool,
466 },
467 /// `len` elements of `elem`, each at a stride of the element's size.
468 Array {
469 /// The element type.
470 elem: MemType,
471 /// The element count.
472 len: u64,
473 },
474}
475
476impl Aggregate {
477 /// A (non-packed) struct with the given fields.
478 ///
479 /// # Examples
480 ///
481 /// ```
482 /// use ir_lang::{Aggregate, Type};
483 ///
484 /// let s = Aggregate::structure(&[Type::I32.into()]);
485 /// assert!(matches!(s, Aggregate::Struct { packed: false, .. }));
486 /// ```
487 #[must_use]
488 pub fn structure(fields: &[MemType]) -> Aggregate {
489 Aggregate::Struct {
490 fields: fields.to_vec(),
491 packed: false,
492 }
493 }
494
495 /// A packed struct (alignment 1, no padding) with the given fields.
496 ///
497 /// # Examples
498 ///
499 /// ```
500 /// use ir_lang::{Aggregate, Type};
501 ///
502 /// let s = Aggregate::packed(&[Type::U8.into(), Type::U32.into()]);
503 /// assert!(matches!(s, Aggregate::Struct { packed: true, .. }));
504 /// ```
505 #[must_use]
506 pub fn packed(fields: &[MemType]) -> Aggregate {
507 Aggregate::Struct {
508 fields: fields.to_vec(),
509 packed: true,
510 }
511 }
512
513 /// An array of `len` elements.
514 ///
515 /// # Examples
516 ///
517 /// ```
518 /// use ir_lang::{Aggregate, Type};
519 ///
520 /// let a = Aggregate::array(Type::F64.into(), 4);
521 /// assert!(matches!(a, Aggregate::Array { len: 4, .. }));
522 /// ```
523 #[must_use]
524 pub fn array(elem: MemType, len: u64) -> Aggregate {
525 Aggregate::Array { elem, len }
526 }
527}
528
529/// The size and alignment of a memory type, in bytes.
530///
531/// # Examples
532///
533/// ```
534/// use ir_lang::{Module, Type};
535///
536/// let m = Module::new("demo");
537/// let l = m.layout(Type::I32.into()).unwrap();
538/// assert_eq!((l.size(), l.align()), (4, 4));
539/// ```
540#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
541pub struct Layout {
542 pub(crate) size: u64,
543 pub(crate) align: u32,
544}
545
546impl Layout {
547 /// The size in bytes (a multiple of the alignment).
548 ///
549 /// # Examples
550 ///
551 /// ```
552 /// use ir_lang::{Module, Type};
553 ///
554 /// assert_eq!(Module::new("m").layout(Type::U16.into()).unwrap().size(), 2);
555 /// ```
556 #[inline]
557 #[must_use]
558 pub const fn size(self) -> u64 {
559 self.size
560 }
561
562 /// The alignment in bytes (a power of two).
563 ///
564 /// # Examples
565 ///
566 /// ```
567 /// use ir_lang::{Module, Type};
568 ///
569 /// assert_eq!(Module::new("m").layout(Type::F64.into()).unwrap().align(), 8);
570 /// ```
571 #[inline]
572 #[must_use]
573 pub const fn align(self) -> u32 {
574 self.align
575 }
576}
577
578/// The largest size, in bytes, an aggregate may have (2^48).
579pub const MAX_AGGREGATE_SIZE: u64 = 1 << 48;
580
581/// A calling convention tag on a signature.
582///
583/// The IR records the convention; the backend implements it. Tail calls require the
584/// caller's and the callee's conventions to be equal.
585///
586/// # Examples
587///
588/// ```
589/// use ir_lang::CallConv;
590///
591/// assert_eq!(CallConv::default(), CallConv::Fast);
592/// assert_eq!(CallConv::C.name(), "c");
593/// ```
594#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
595#[non_exhaustive]
596pub enum CallConv {
597 /// The internal convention for code compiled together: the backend may choose
598 /// anything, and tail calls are guaranteed. The default.
599 #[default]
600 Fast,
601 /// The target platform's C convention (resolved by the backend per target).
602 C,
603 /// The System V AMD64 convention.
604 SysV64,
605 /// The Windows x64 convention.
606 Win64,
607 /// The AArch64 procedure-call standard.
608 Aapcs64,
609}
610
611impl CallConv {
612 /// Every convention, in a fixed order.
613 pub const ALL: [CallConv; 5] = [
614 CallConv::Fast,
615 CallConv::C,
616 CallConv::SysV64,
617 CallConv::Win64,
618 CallConv::Aapcs64,
619 ];
620
621 /// The textual name of the convention.
622 ///
623 /// # Examples
624 ///
625 /// ```
626 /// use ir_lang::CallConv;
627 ///
628 /// assert_eq!(CallConv::Win64.name(), "win64");
629 /// ```
630 #[must_use]
631 pub const fn name(self) -> &'static str {
632 match self {
633 CallConv::Fast => "fast",
634 CallConv::C => "c",
635 CallConv::SysV64 => "sysv64",
636 CallConv::Win64 => "win64",
637 CallConv::Aapcs64 => "aapcs64",
638 }
639 }
640
641 /// Looks a convention up by its textual name.
642 ///
643 /// # Examples
644 ///
645 /// ```
646 /// use ir_lang::CallConv;
647 ///
648 /// assert_eq!(CallConv::from_name("sysv64"), Some(CallConv::SysV64));
649 /// ```
650 #[must_use]
651 pub fn from_name(name: &str) -> Option<CallConv> {
652 CallConv::ALL.iter().copied().find(|c| c.name() == name)
653 }
654}
655
656impl fmt::Display for CallConv {
657 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
658 f.write_str(self.name())
659 }
660}
661
662/// A function signature: parameter types, return types, and a calling convention.
663///
664/// A function may return any number of values (zero for "returns nothing").
665/// Signatures are interned per module ([`Module::intern_signature`](crate::Module::intern_signature)),
666/// so two equal signatures share one [`SigId`](crate::SigId).
667///
668/// # Examples
669///
670/// ```
671/// use ir_lang::{CallConv, Signature, Type};
672///
673/// let sig = Signature::new(&[Type::I64, Type::Ptr], &[Type::Bool]).with_conv(CallConv::C);
674/// assert_eq!(sig.params, [Type::I64, Type::Ptr]);
675/// assert_eq!(sig.to_string(), "(i64, ptr) -> (bool) c");
676/// ```
677#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
678pub struct Signature {
679 /// Parameter types, in order.
680 pub params: Vec<Type>,
681 /// Return types, in order.
682 pub returns: Vec<Type>,
683 /// The calling convention.
684 pub conv: CallConv,
685}
686
687impl Signature {
688 /// A signature with the [`Fast`](CallConv::Fast) convention.
689 ///
690 /// # Examples
691 ///
692 /// ```
693 /// use ir_lang::{Signature, Type};
694 ///
695 /// let s = Signature::new(&[], &[Type::I32]);
696 /// assert!(s.params.is_empty());
697 /// ```
698 #[must_use]
699 pub fn new(params: &[Type], returns: &[Type]) -> Signature {
700 Signature {
701 params: params.to_vec(),
702 returns: returns.to_vec(),
703 conv: CallConv::Fast,
704 }
705 }
706
707 /// The same signature with a different calling convention.
708 ///
709 /// # Examples
710 ///
711 /// ```
712 /// use ir_lang::{CallConv, Signature};
713 ///
714 /// assert_eq!(Signature::new(&[], &[]).with_conv(CallConv::Win64).conv, CallConv::Win64);
715 /// ```
716 #[must_use]
717 pub fn with_conv(mut self, conv: CallConv) -> Signature {
718 self.conv = conv;
719 self
720 }
721}
722
723impl fmt::Display for Signature {
724 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
725 f.write_str("(")?;
726 for (i, ty) in self.params.iter().enumerate() {
727 if i > 0 {
728 f.write_str(", ")?;
729 }
730 write!(f, "{ty}")?;
731 }
732 f.write_str(") -> (")?;
733 for (i, ty) in self.returns.iter().enumerate() {
734 if i > 0 {
735 f.write_str(", ")?;
736 }
737 write!(f, "{ty}")?;
738 }
739 write!(f, ") {}", self.conv)
740 }
741}
742
743#[cfg(test)]
744mod tests {
745 use super::*;
746 use alloc::string::ToString;
747
748 #[test]
749 fn test_type_names_round_trip() {
750 for ty in Type::ALL {
751 assert_eq!(Type::from_name(ty.name()), Some(ty));
752 }
753 for conv in CallConv::ALL {
754 assert_eq!(CallConv::from_name(conv.name()), Some(conv));
755 }
756 }
757
758 #[test]
759 fn test_type_sizes_follow_pointer_width() {
760 assert_eq!(Type::Ptr.size(PointerWidth::P32), 4);
761 assert_eq!(Type::Ref.size(PointerWidth::P64), 8);
762 assert_eq!(Type::U64.size(PointerWidth::P32), 8);
763 assert_eq!(Type::Bool.size(PointerWidth::P32), 1);
764 }
765
766 #[test]
767 fn test_signature_display() {
768 let s = Signature::new(&[], &[]);
769 assert_eq!(s.to_string(), "() -> () fast");
770 }
771}