lamina-mir 0.2.1

MIR (Machine Intermediate Representation) type definitions for Lamina
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
//! Type system for LUMIR.
//!
//! LUMIR types are lower-level than IR types, focused on machine representation
//! and efficient code generation. The type system supports:
//!
//! - **Scalar types**: Integer and floating-point types of various sizes
//! - **Vector types**: SIMD vector types for parallel operations
//! - **Pointer types**: Memory addresses
//!
//! Integer scalars carry width-specific bit patterns without signedness. Memory loads
//! zero-extend integer bits when host registers are wider than the access. Signed
//! interpretation belongs to signed operations and explicit `IntConvert` extension.
//!
//! ## Type Sizes
//!
//! All type sizes are fixed and known at compile time, enabling efficient
//! memory layout and register allocation decisions.
use std::fmt;

use lamina_platform::PointerWidth;

/// Which memory a pointer addresses. Only the gpu backends distinguish them; every
/// other target treats them all as `Generic`.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AddressSpace {
    #[default]
    Generic,
    Global,
    Region,
    Local,
    Constant,
    Private,
    Parameter,
}

/// Signedness of an integer crossing a function boundary. Instructions stay
/// signedness-free; this tells external call sites how to widen narrow arguments
/// and results to the width the C ABI expects.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub enum Signedness {
    #[default]
    Signed,
    Unsigned,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EightbyteClass {
    Integer,
    Sse,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FloatMember {
    F32,
    F64,
}

impl FloatMember {
    pub fn size_bytes(self) -> usize {
        match self {
            FloatMember::F32 => 4,
            FloatMember::F64 => 8,
        }
    }
}

/// One member an ABI hands a register of its own, with the byte offset it sits at in
/// the aggregate it was flattened out of.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FlatField {
    pub offset: u32,
    pub bytes: u32,
    /// The floating-point type this member is, or `None` when it is an integer that
    /// takes a general register.
    pub float: Option<FloatMember>,
}

/// Field makeup of an aggregate crossing a function boundary.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AggregateClass {
    Integer,
    Eightbytes([EightbyteClass; 2]),
    Memory,
    Homogeneous {
        member: FloatMember,
        count: u8,
    },
    /// One or two flattened members, each in a register of its own class. The RISC-V
    /// hard-float convention, and the single floating-point member that the
    /// z/Architecture ABI treats as that member.
    Flattened {
        first: FlatField,
        second: Option<FlatField>,
    },
}

impl AggregateClass {
    pub fn flattened(&self) -> Option<(FlatField, Option<FlatField>)> {
        match self {
            AggregateClass::Flattened { first, second } => Some((*first, *second)),
            _ => None,
        }
    }
}

/// What a value of aggregate shape actually is. A [`AggregateShape::Composite`] is a
/// struct or an array the author wrote; a [`AggregateShape::WideScalar`] is one
/// integer that legalization split across n registers because no register on the
/// target is wide enough to hold it.
///
/// The two reach a backend with the same size, alignment and class, and several ABIs
/// place them differently: AAPCS returns a composite over four bytes in memory while
/// a 64-bit integer comes back in `r0:r1`, and cdecl returns a struct through a
/// hidden pointer while a 64-bit integer comes back in `%edx:%eax`.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub enum AggregateShape {
    #[default]
    Composite,
    WideScalar,
}

/// An aggregate passed by value. The parameter register holds the address of the
/// callee's own copy; size and class tell a backend which registers carry it.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Aggregate {
    pub size: usize,
    pub align: usize,
    pub class: AggregateClass,
    pub shape: AggregateShape,
}

impl Aggregate {
    /// Whether this is a struct or an array rather than one legalized integer.
    pub fn is_composite(&self) -> bool {
        self.shape == AggregateShape::Composite
    }

    /// Whether this is one integer that legalization split across registers.
    pub fn is_wide_scalar(&self) -> bool {
        self.shape == AggregateShape::WideScalar
    }

    /// Eight-byte words the aggregate occupies in registers or on the stack.
    pub fn words(&self) -> usize {
        self.size.div_ceil(8)
    }

    pub fn eightbyte_class(&self, word: usize) -> EightbyteClass {
        match self.class {
            AggregateClass::Eightbytes(classes) => classes[word],
            AggregateClass::Homogeneous { .. } => EightbyteClass::Sse,
            AggregateClass::Integer | AggregateClass::Memory | AggregateClass::Flattened { .. } => {
                EightbyteClass::Integer
            }
        }
    }

    pub fn homogeneous_members(&self) -> Option<(FloatMember, usize)> {
        match self.class {
            AggregateClass::Homogeneous { member, count } => Some((member, count as usize)),
            AggregateClass::Integer
            | AggregateClass::Eightbytes(_)
            | AggregateClass::Memory
            | AggregateClass::Flattened { .. } => None,
        }
    }

    /// Register-sized words the aggregate occupies on a target of this pointer width.
    /// A 32-bit target holds twice as many of them as [`Aggregate::words`] counts.
    pub fn words_of(&self, pointer: PointerWidth) -> usize {
        self.size.div_ceil(pointer.bytes())
    }

    /// The largest aggregate a target of this pointer width passes in registers:
    /// two words, as every ABI in the table classifies it.
    pub fn max_in_registers(pointer: PointerWidth) -> u64 {
        2 * pointer.bytes() as u64
    }
}

/// A value that fits in one register. `I1` is a one-bit boolean, which the backends
/// still hold in a full register.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ScalarType {
    I8,
    I16,
    I32,
    I64,
    F32,
    F64,
    Ptr,
    PtrIn(AddressSpace),
    I1,
}

/// Element type of a vector.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VectorLane {
    I8,
    I16,
    I32,
    I64,
    F32,
    F64,
}

/// A vector, named by its total width and its lane type.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VectorType {
    V128(VectorLane),
    V256(VectorLane),
}

/// Every type MIR can name. MIR is a register machine, so an aggregate is not one of
/// them: an aggregate crossing a function boundary travels as a pointer plus an
/// [`Aggregate`] marker on the parameter.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MirType {
    Scalar(ScalarType),
    Vector(VectorType),
}

impl MirType {
    /// Reads `value` as this type's two's complement, sign-extended to 64 bits.
    pub fn signed_integer(self, value: i64, pointer: PointerWidth) -> i64 {
        let shift = 64 - self.size_bytes(pointer) * 8;
        (value << shift) >> shift
    }

    /// Reads `value` as this type's unsigned bits, zero-extended to 64 bits.
    pub fn unsigned_integer(self, value: i64, pointer: PointerWidth) -> u64 {
        let shift = 64 - self.size_bytes(pointer) * 8;
        ((value as u64) << shift) >> shift
    }

    /// Size of the type in bytes
    pub fn size_bytes(&self, pointer: PointerWidth) -> usize {
        match self {
            MirType::Scalar(s) => match s {
                ScalarType::I1 | ScalarType::I8 => 1,
                ScalarType::I16 => 2,
                ScalarType::I32 | ScalarType::F32 => 4,
                ScalarType::I64 | ScalarType::F64 => 8,
                ScalarType::Ptr | ScalarType::PtrIn(_) => pointer.bytes(),
            },
            MirType::Vector(v) => match v {
                VectorType::V128(_) => 16,
                VectorType::V256(_) => 32,
            },
        }
    }

    /// Alignment requirement in bytes
    pub fn alignment(&self, pointer: PointerWidth) -> usize {
        match self {
            MirType::Scalar(s) => match s {
                ScalarType::I1 | ScalarType::I8 => 1,
                ScalarType::I16 => 2,
                ScalarType::I32 | ScalarType::F32 => 4,
                ScalarType::I64 | ScalarType::F64 => 8,
                ScalarType::Ptr | ScalarType::PtrIn(_) => pointer.bytes(),
            },
            MirType::Vector(v) => match v {
                VectorType::V128(_) => 16,
                VectorType::V256(_) => 32,
            },
        }
    }

    /// Check if this is a floating-point type
    pub fn is_float(&self) -> bool {
        matches!(
            self,
            MirType::Scalar(ScalarType::F32 | ScalarType::F64)
                | MirType::Vector(VectorType::V128(VectorLane::F32 | VectorLane::F64))
                | MirType::Vector(VectorType::V256(VectorLane::F32 | VectorLane::F64))
        )
    }

    /// Check if this is a vector type
    pub fn is_vector(&self) -> bool {
        matches!(self, MirType::Vector(_))
    }

    /// A pointer into `space`.
    pub fn ptr_in(space: AddressSpace) -> Self {
        Self::Scalar(ScalarType::PtrIn(space))
    }
}

impl fmt::Display for ScalarType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ScalarType::I1 => write!(f, "i1"),
            ScalarType::I8 => write!(f, "i8"),
            ScalarType::I16 => write!(f, "i16"),
            ScalarType::I32 => write!(f, "i32"),
            ScalarType::I64 => write!(f, "i64"),
            ScalarType::F32 => write!(f, "f32"),
            ScalarType::F64 => write!(f, "f64"),
            ScalarType::Ptr => write!(f, "ptr"),
            ScalarType::PtrIn(space) => write!(f, "ptr<{space:?}>"),
        }
    }
}

impl fmt::Display for VectorLane {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            VectorLane::I8 => write!(f, "i8"),
            VectorLane::I16 => write!(f, "i16"),
            VectorLane::I32 => write!(f, "i32"),
            VectorLane::I64 => write!(f, "i64"),
            VectorLane::F32 => write!(f, "f32"),
            VectorLane::F64 => write!(f, "f64"),
        }
    }
}

impl fmt::Display for VectorType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            VectorType::V128(lane) => write!(f, "v128<{lane}>"),
            VectorType::V256(lane) => write!(f, "v256<{lane}>"),
        }
    }
}

impl fmt::Display for MirType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            MirType::Scalar(s) => write!(f, "{s}"),
            MirType::Vector(v) => write!(f, "{v}"),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn pointer_spaces_preserve_layout() {
        for space in [
            AddressSpace::Generic,
            AddressSpace::Global,
            AddressSpace::Region,
            AddressSpace::Local,
            AddressSpace::Constant,
            AddressSpace::Private,
            AddressSpace::Parameter,
        ] {
            let ty = MirType::ptr_in(space);
            assert_eq!(
                ty.size_bytes(PointerWidth::Bits64),
                MirType::Scalar(ScalarType::Ptr).size_bytes(PointerWidth::Bits64)
            );
            assert_eq!(
                ty.alignment(PointerWidth::Bits64),
                MirType::Scalar(ScalarType::Ptr).alignment(PointerWidth::Bits64)
            );
        }
        assert_ne!(
            MirType::ptr_in(AddressSpace::Global),
            MirType::ptr_in(AddressSpace::Local)
        );
    }

    #[test]
    fn test_scalar_sizes() {
        let w = PointerWidth::Bits64;
        assert_eq!(MirType::Scalar(ScalarType::I8).size_bytes(w), 1);
        assert_eq!(MirType::Scalar(ScalarType::I32).size_bytes(w), 4);
        assert_eq!(MirType::Scalar(ScalarType::I64).size_bytes(w), 8);
    }

    #[test]
    fn pointer_size_and_alignment_follow_pointer_width() {
        for ty in [
            MirType::Scalar(ScalarType::Ptr),
            MirType::ptr_in(AddressSpace::Local),
        ] {
            assert_eq!(ty.size_bytes(PointerWidth::Bits32), 4);
            assert_eq!(ty.alignment(PointerWidth::Bits32), 4);
            assert_eq!(ty.size_bytes(PointerWidth::Bits64), 8);
            assert_eq!(ty.alignment(PointerWidth::Bits64), 8);
        }
        assert_eq!(
            MirType::Scalar(ScalarType::I64).size_bytes(PointerWidth::Bits32),
            8
        );
        assert_eq!(
            MirType::Scalar(ScalarType::I64).alignment(PointerWidth::Bits32),
            8
        );
    }

    #[test]
    fn test_vector_sizes() {
        assert_eq!(
            MirType::Vector(VectorType::V128(VectorLane::I32)).size_bytes(PointerWidth::Bits64),
            16
        );
        assert_eq!(
            MirType::Vector(VectorType::V256(VectorLane::F64)).size_bytes(PointerWidth::Bits64),
            32
        );
    }

    #[test]
    fn test_float_detection() {
        assert!(MirType::Scalar(ScalarType::F32).is_float());
        assert!(MirType::Scalar(ScalarType::F64).is_float());
        assert!(!MirType::Scalar(ScalarType::I32).is_float());
    }
}