Skip to main content

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}