Skip to main content

lamina_mir/
types.rs

1//! Type system for LUMIR.
2//!
3//! LUMIR types are lower-level than IR types, focused on machine representation
4//! and efficient code generation. The type system supports:
5//!
6//! - **Scalar types**: Integer and floating-point types of various sizes
7//! - **Vector types**: SIMD vector types for parallel operations
8//! - **Pointer types**: Memory addresses
9//!
10//! Integer scalars carry width-specific bit patterns without signedness. Memory loads
11//! zero-extend integer bits when host registers are wider than the access. Signed
12//! interpretation belongs to signed operations and explicit `IntConvert` extension.
13//!
14//! ## Type Sizes
15//!
16//! All type sizes are fixed and known at compile time, enabling efficient
17//! memory layout and register allocation decisions.
18use std::fmt;
19
20/// Which memory a pointer addresses. Only the gpu backends distinguish them; every
21/// other target treats them all as `Generic`.
22#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
23pub enum AddressSpace {
24    #[default]
25    Generic,
26    Global,
27    Region,
28    Local,
29    Constant,
30    Private,
31    Parameter,
32}
33
34/// Signedness of an integer crossing a function boundary. Instructions stay
35/// signedness-free; this tells external call sites how to widen narrow arguments
36/// and results to the width the C ABI expects.
37#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
38pub enum Signedness {
39    #[default]
40    Signed,
41    Unsigned,
42}
43
44/// Field makeup of an aggregate crossing a function boundary. Backends classify from
45/// this; lowering only produces `Integer` today and rejects the rest.
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
47pub enum AggregateClass {
48    Integer,
49}
50
51/// An aggregate passed by value. The parameter register holds the address of the
52/// callee's own copy; size and class tell a backend which registers carry it.
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
54pub struct Aggregate {
55    pub size: usize,
56    pub align: usize,
57    pub class: AggregateClass,
58}
59
60impl Aggregate {
61    /// Eight-byte words the aggregate occupies in registers or on the stack.
62    pub fn words(&self) -> usize {
63        self.size.div_ceil(8)
64    }
65}
66
67/// A value that fits in one register. `I1` is a one-bit boolean, which the backends
68/// still hold in a full register.
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
70pub enum ScalarType {
71    I8,
72    I16,
73    I32,
74    I64,
75    F32,
76    F64,
77    Ptr,
78    PtrIn(AddressSpace),
79    I1,
80}
81
82/// Element type of a vector.
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
84pub enum VectorLane {
85    I8,
86    I16,
87    I32,
88    I64,
89    F32,
90    F64,
91}
92
93/// A vector, named by its total width and its lane type.
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
95pub enum VectorType {
96    V128(VectorLane),
97    V256(VectorLane),
98}
99
100/// Every type MIR can name. MIR is a register machine, so an aggregate is not one of
101/// them: an aggregate crossing a function boundary travels as a pointer plus an
102/// [`Aggregate`] marker on the parameter.
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
104pub enum MirType {
105    Scalar(ScalarType),
106    Vector(VectorType),
107}
108
109impl MirType {
110    /// Reads `value` as this type's two's complement, sign-extended to 64 bits.
111    pub fn signed_integer(self, value: i64) -> i64 {
112        let shift = 64 - self.size_bytes() * 8;
113        (value << shift) >> shift
114    }
115
116    /// Reads `value` as this type's unsigned bits, zero-extended to 64 bits.
117    pub fn unsigned_integer(self, value: i64) -> u64 {
118        let shift = 64 - self.size_bytes() * 8;
119        ((value as u64) << shift) >> shift
120    }
121
122    /// Size of the type in bytes
123    pub fn size_bytes(&self) -> usize {
124        match self {
125            MirType::Scalar(s) => match s {
126                ScalarType::I1 | ScalarType::I8 => 1,
127                ScalarType::I16 => 2,
128                ScalarType::I32 | ScalarType::F32 => 4,
129                ScalarType::I64 | ScalarType::F64 | ScalarType::Ptr | ScalarType::PtrIn(_) => 8,
130            },
131            MirType::Vector(v) => match v {
132                VectorType::V128(_) => 16,
133                VectorType::V256(_) => 32,
134            },
135        }
136    }
137
138    /// Alignment requirement in bytes
139    pub fn alignment(&self) -> usize {
140        match self {
141            MirType::Scalar(s) => match s {
142                ScalarType::I1 | ScalarType::I8 => 1,
143                ScalarType::I16 => 2,
144                ScalarType::I32 | ScalarType::F32 => 4,
145                ScalarType::I64 | ScalarType::F64 | ScalarType::Ptr | ScalarType::PtrIn(_) => 8,
146            },
147            MirType::Vector(v) => match v {
148                VectorType::V128(_) => 16,
149                VectorType::V256(_) => 32,
150            },
151        }
152    }
153
154    /// Check if this is a floating-point type
155    pub fn is_float(&self) -> bool {
156        matches!(
157            self,
158            MirType::Scalar(ScalarType::F32 | ScalarType::F64)
159                | MirType::Vector(VectorType::V128(VectorLane::F32 | VectorLane::F64))
160                | MirType::Vector(VectorType::V256(VectorLane::F32 | VectorLane::F64))
161        )
162    }
163
164    /// Check if this is a vector type
165    pub fn is_vector(&self) -> bool {
166        matches!(self, MirType::Vector(_))
167    }
168
169    /// Shorthand for `MirType::Scalar(ScalarType::I1)`.
170    pub fn i1() -> Self {
171        MirType::Scalar(ScalarType::I1)
172    }
173
174    /// Shorthand for `MirType::Scalar(ScalarType::I8)`.
175    pub fn i8() -> Self {
176        MirType::Scalar(ScalarType::I8)
177    }
178
179    /// Shorthand for `MirType::Scalar(ScalarType::I16)`.
180    pub fn i16() -> Self {
181        MirType::Scalar(ScalarType::I16)
182    }
183
184    /// Shorthand for `MirType::Scalar(ScalarType::I32)`.
185    pub fn i32() -> Self {
186        MirType::Scalar(ScalarType::I32)
187    }
188
189    /// Shorthand for `MirType::Scalar(ScalarType::I64)`.
190    pub fn i64() -> Self {
191        MirType::Scalar(ScalarType::I64)
192    }
193
194    /// Shorthand for `MirType::Scalar(ScalarType::F32)`.
195    pub fn f32() -> Self {
196        MirType::Scalar(ScalarType::F32)
197    }
198
199    /// Shorthand for `MirType::Scalar(ScalarType::F64)`.
200    pub fn f64() -> Self {
201        MirType::Scalar(ScalarType::F64)
202    }
203
204    /// A pointer into `space`.
205    pub fn ptr_in(space: AddressSpace) -> Self {
206        Self::Scalar(ScalarType::PtrIn(space))
207    }
208
209    /// Shorthand for `MirType::Scalar(ScalarType::Ptr)`.
210    pub fn ptr() -> Self {
211        MirType::Scalar(ScalarType::Ptr)
212    }
213}
214
215impl fmt::Display for ScalarType {
216    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217        match self {
218            ScalarType::I1 => write!(f, "i1"),
219            ScalarType::I8 => write!(f, "i8"),
220            ScalarType::I16 => write!(f, "i16"),
221            ScalarType::I32 => write!(f, "i32"),
222            ScalarType::I64 => write!(f, "i64"),
223            ScalarType::F32 => write!(f, "f32"),
224            ScalarType::F64 => write!(f, "f64"),
225            ScalarType::Ptr => write!(f, "ptr"),
226            ScalarType::PtrIn(space) => write!(f, "ptr<{space:?}>"),
227        }
228    }
229}
230
231impl fmt::Display for VectorLane {
232    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233        match self {
234            VectorLane::I8 => write!(f, "i8"),
235            VectorLane::I16 => write!(f, "i16"),
236            VectorLane::I32 => write!(f, "i32"),
237            VectorLane::I64 => write!(f, "i64"),
238            VectorLane::F32 => write!(f, "f32"),
239            VectorLane::F64 => write!(f, "f64"),
240        }
241    }
242}
243
244impl fmt::Display for VectorType {
245    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
246        match self {
247            VectorType::V128(lane) => write!(f, "v128<{lane}>"),
248            VectorType::V256(lane) => write!(f, "v256<{lane}>"),
249        }
250    }
251}
252
253impl fmt::Display for MirType {
254    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255        match self {
256            MirType::Scalar(s) => write!(f, "{s}"),
257            MirType::Vector(v) => write!(f, "{v}"),
258        }
259    }
260}
261
262#[cfg(test)]
263mod tests {
264    use super::*;
265
266    #[test]
267    fn pointer_spaces_preserve_layout() {
268        for space in [
269            AddressSpace::Generic,
270            AddressSpace::Global,
271            AddressSpace::Region,
272            AddressSpace::Local,
273            AddressSpace::Constant,
274            AddressSpace::Private,
275            AddressSpace::Parameter,
276        ] {
277            let ty = MirType::ptr_in(space);
278            assert_eq!(ty.size_bytes(), MirType::ptr().size_bytes());
279            assert_eq!(ty.alignment(), MirType::ptr().alignment());
280        }
281        assert_ne!(
282            MirType::ptr_in(AddressSpace::Global),
283            MirType::ptr_in(AddressSpace::Local)
284        );
285    }
286
287    #[test]
288    fn test_scalar_sizes() {
289        assert_eq!(MirType::Scalar(ScalarType::I8).size_bytes(), 1);
290        assert_eq!(MirType::Scalar(ScalarType::I32).size_bytes(), 4);
291        assert_eq!(MirType::Scalar(ScalarType::I64).size_bytes(), 8);
292    }
293
294    #[test]
295    fn test_vector_sizes() {
296        assert_eq!(
297            MirType::Vector(VectorType::V128(VectorLane::I32)).size_bytes(),
298            16
299        );
300        assert_eq!(
301            MirType::Vector(VectorType::V256(VectorLane::F64)).size_bytes(),
302            32
303        );
304    }
305
306    #[test]
307    fn test_float_detection() {
308        assert!(MirType::Scalar(ScalarType::F32).is_float());
309        assert!(MirType::Scalar(ScalarType::F64).is_float());
310        assert!(!MirType::Scalar(ScalarType::I32).is_float());
311    }
312}