Skip to main content

lutra_bin/
layout.rs

1use crate::string;
2use crate::vec;
3
4use crate::ir;
5
6// TODO: conceptually, this shouldn't exist
7pub trait Layout {
8    /// Returns the size of the head in bits for a given type.
9    fn head_size() -> usize;
10}
11
12impl<T> Layout for &T
13where
14    T: Layout,
15{
16    fn head_size() -> usize {
17        T::head_size()
18    }
19}
20
21impl Layout for bool {
22    fn head_size() -> usize {
23        8
24    }
25}
26
27impl Layout for i8 {
28    fn head_size() -> usize {
29        8
30    }
31}
32impl Layout for i16 {
33    fn head_size() -> usize {
34        16
35    }
36}
37impl Layout for i32 {
38    fn head_size() -> usize {
39        32
40    }
41}
42impl Layout for i64 {
43    fn head_size() -> usize {
44        64
45    }
46}
47impl Layout for u8 {
48    fn head_size() -> usize {
49        8
50    }
51}
52impl Layout for u16 {
53    fn head_size() -> usize {
54        16
55    }
56}
57impl Layout for u32 {
58    fn head_size() -> usize {
59        32
60    }
61}
62impl Layout for u64 {
63    fn head_size() -> usize {
64        64
65    }
66}
67impl Layout for f32 {
68    fn head_size() -> usize {
69        32
70    }
71}
72impl Layout for f64 {
73    fn head_size() -> usize {
74        64
75    }
76}
77
78impl Layout for str {
79    fn head_size() -> usize {
80        64
81    }
82}
83impl Layout for string::String {
84    fn head_size() -> usize {
85        64
86    }
87}
88
89impl<I> Layout for vec::Vec<I> {
90    fn head_size() -> usize {
91        64
92    }
93}
94
95impl<I: Layout> Layout for Option<I> {
96    fn head_size() -> usize {
97        if I::head_size() == 0 { 8 } else { 40 }
98    }
99}
100
101impl<T: Layout, E: Layout> Layout for core::result::Result<T, E> {
102    fn head_size() -> usize {
103        // 1 byte tag + inline inner (capped at 32 bits → 4 bytes when a pointer is needed)
104        let max_bits = T::head_size().max(E::head_size());
105        8 + max_bits.min(32)
106    }
107}
108
109impl Layout for () {
110    fn head_size() -> usize {
111        0
112    }
113}
114
115/// Computes layout of a type, iff all inner types have their layout computed.
116// Does not recurse.
117pub fn compute(ty: &ir::Ty) -> Option<ir::TyLayout> {
118    Some(match &ty.kind {
119        ir::TyKind::Array(_) | ir::TyKind::Primitive(ir::TyPrimitive::text) => ir::TyLayout {
120            head_size: 64,
121            body_ptrs: vec![0],
122        },
123
124        ir::TyKind::Primitive(prim) => {
125            let head_size = match prim {
126                ir::TyPrimitive::int8 => 8,
127                ir::TyPrimitive::int16 => 16,
128                ir::TyPrimitive::int32 => 32,
129                ir::TyPrimitive::int64 => 64,
130                ir::TyPrimitive::uint8 => 8,
131                ir::TyPrimitive::uint16 => 16,
132                ir::TyPrimitive::uint32 => 32,
133                ir::TyPrimitive::uint64 => 64,
134                ir::TyPrimitive::float32 => 32,
135                ir::TyPrimitive::float64 => 64,
136                ir::TyPrimitive::bool => 8,
137                ir::TyPrimitive::text => unreachable!(),
138            };
139            ir::TyLayout {
140                head_size,
141                body_ptrs: vec![],
142            }
143        }
144
145        ir::TyKind::Tuple(fields) => {
146            let mut head_size: u32 = 0;
147            let mut body_ptrs = vec::Vec::new();
148            for f in fields {
149                let Some(layout) = &f.ty.layout else {
150                    return None;
151                };
152
153                let field_offset = head_size.div_ceil(8);
154                head_size += layout.head_size;
155                body_ptrs.extend(layout.body_ptrs.iter().map(|p| p + field_offset));
156            }
157
158            ir::TyLayout {
159                head_size,
160                body_ptrs,
161            }
162        }
163
164        ir::TyKind::Enum(variants) => {
165            let head = enum_head_format(variants, &ty.variants_recursive);
166
167            let head_size = (head.tag_bytes + head.inner_bytes) * 8;
168
169            let body_ptrs = if head.has_ptr {
170                vec![head.tag_bytes]
171            } else {
172                // here we should include body_ptrs in the head of inner type,
173                // but no type with a ptr fits into 4 bytes, so we can just skip
174                // that and assume there are no body_ptrs
175                vec![]
176            };
177
178            ir::TyLayout {
179                head_size,
180                body_ptrs,
181            }
182        }
183        _ => return None,
184    })
185}
186
187pub fn tuple_field_offsets(ty: &ir::Ty) -> vec::Vec<u32> {
188    let ir::TyKind::Tuple(ty_fields) = &ty.kind else {
189        panic!("got: {:?}", ty.kind)
190    };
191
192    let mut field_offsets = vec::Vec::with_capacity(ty_fields.len());
193    let mut offset = 0_u32;
194    for field in ty_fields {
195        field_offsets.push(offset);
196
197        let layout = field.ty.layout.as_ref().unwrap();
198        offset += (layout.head_size).div_ceil(8);
199    }
200    field_offsets
201}
202
203pub fn tuple_field_offset(ty: &ir::Ty, position: u16) -> u32 {
204    *tuple_field_offsets(ty).get(position as usize).unwrap()
205}
206
207pub fn does_enum_variant_contain_recursive(enum_ty: &ir::Ty, variant_index: u16) -> bool {
208    enum_ty.variants_recursive.contains(&variant_index)
209}
210
211pub use crate::generated::layout::EnumFormat;
212
213pub fn enum_format(variants: &[ir::TyEnumVariant], variants_recursive: &[u16]) -> EnumFormat {
214    let head = enum_head_format(variants, variants_recursive);
215    let variants = variants
216        .iter()
217        .map(|v| enum_variant_format(&head, &v.ty))
218        .collect();
219    EnumFormat {
220        tag_bytes: head.tag_bytes as u8,
221        inner_bytes: head.inner_bytes as u8,
222        has_ptr: head.has_ptr,
223        variants,
224    }
225}
226
227#[derive(Debug)]
228pub struct EnumHeadFormat {
229    pub tag_bytes: u32,
230    pub inner_bytes: u32,
231    pub has_ptr: bool,
232}
233
234pub fn enum_head_format(
235    variants: &[ir::TyEnumVariant],
236    variants_recursive: &[u16],
237) -> EnumHeadFormat {
238    let t = enum_tag_size(variants.len());
239
240    let force_ptr = !variants_recursive.is_empty();
241
242    let max_head = if force_ptr {
243        u32::MAX
244    } else {
245        enum_max_variant_head_size(variants)
246    };
247
248    let mut has_ptr = false;
249    let inner = if max_head > 32 {
250        // insert a pointer
251        has_ptr = true;
252        32
253    } else {
254        max_head
255    };
256
257    EnumHeadFormat {
258        tag_bytes: t.div_ceil(8),
259        inner_bytes: inner.div_ceil(8),
260        has_ptr,
261    }
262}
263
264pub use crate::generated::layout::EnumVariantFormat;
265
266pub fn enum_variant_format(head: &EnumHeadFormat, variant_ty: &ir::Ty) -> EnumVariantFormat {
267    let inner_head_size = variant_ty.layout.as_ref().unwrap().head_size;
268    let inner_head_bytes = inner_head_size.div_ceil(8);
269
270    let is_unit = inner_head_bytes == 0;
271
272    let padding_bytes = if head.has_ptr {
273        if is_unit {
274            // unit variant does not have a pointer, it is all padding
275            4
276        } else {
277            0
278        }
279    } else {
280        head.inner_bytes.saturating_sub(inner_head_bytes) as u8
281    };
282
283    EnumVariantFormat {
284        padding_bytes,
285        is_unit,
286    }
287}
288
289fn enum_max_variant_head_size(variants: &[ir::TyEnumVariant]) -> u32 {
290    let mut i = 0;
291    for v in variants {
292        let Some(ty_layout) = &v.ty.layout else {
293            panic!("missing layout: {:?}", v.ty)
294        };
295        let size = ty_layout.head_size;
296        i = i.max(size);
297    }
298    i
299}
300
301fn enum_tag_size(variants_len: usize) -> u32 {
302    // TODO: when bool-sub-byte packing is implemented, remove function in favor of enum_tag_size_used
303    enum_tag_size_used(variants_len).div_ceil(8) * 8
304}
305
306fn enum_tag_size_used(variants_len: usize) -> u32 {
307    variants_len
308        .saturating_sub(1)
309        .checked_ilog2()
310        .map(|x| x + 1)
311        .unwrap_or_default()
312}
313
314#[test]
315fn test_enum_tag_size() {
316    assert_eq!(0, enum_tag_size_used(0));
317    assert_eq!(0, enum_tag_size_used(1));
318    assert_eq!(1, enum_tag_size_used(2));
319    assert_eq!(2, enum_tag_size_used(3));
320    assert_eq!(2, enum_tag_size_used(4));
321    assert_eq!(3, enum_tag_size_used(5));
322    assert_eq!(3, enum_tag_size_used(6));
323    assert_eq!(3, enum_tag_size_used(7));
324    assert_eq!(3, enum_tag_size_used(8));
325    assert_eq!(4, enum_tag_size_used(9));
326    assert_eq!(4, enum_tag_size_used(10));
327    assert_eq!(4, enum_tag_size_used(11));
328    assert_eq!(4, enum_tag_size_used(12));
329    assert_eq!(4, enum_tag_size_used(13));
330    assert_eq!(4, enum_tag_size_used(14));
331    assert_eq!(4, enum_tag_size_used(15));
332    assert_eq!(4, enum_tag_size_used(16));
333    assert_eq!(5, enum_tag_size_used(17));
334    assert_eq!(5, enum_tag_size_used(18));
335    assert_eq!(5, enum_tag_size_used(19));
336    assert_eq!(5, enum_tag_size_used(20));
337    assert_eq!(5, enum_tag_size_used(21));
338    assert_eq!(5, enum_tag_size_used(22));
339    assert_eq!(8, enum_tag_size_used(256));
340    assert_eq!(9, enum_tag_size_used(257));
341
342    assert_eq!(0, enum_tag_size(0));
343    assert_eq!(0, enum_tag_size(1));
344    assert_eq!(8, enum_tag_size(2));
345    assert_eq!(8, enum_tag_size(3));
346    assert_eq!(8, enum_tag_size(4));
347    assert_eq!(8, enum_tag_size(5));
348    assert_eq!(8, enum_tag_size(6));
349    assert_eq!(8, enum_tag_size(7));
350    assert_eq!(8, enum_tag_size(8));
351    assert_eq!(8, enum_tag_size(9));
352    assert_eq!(8, enum_tag_size(10));
353    assert_eq!(8, enum_tag_size(11));
354    assert_eq!(8, enum_tag_size(12));
355    assert_eq!(8, enum_tag_size(13));
356    assert_eq!(8, enum_tag_size(14));
357    assert_eq!(8, enum_tag_size(15));
358    assert_eq!(8, enum_tag_size(16));
359    assert_eq!(8, enum_tag_size(17));
360    assert_eq!(8, enum_tag_size(18));
361    assert_eq!(8, enum_tag_size(19));
362    assert_eq!(8, enum_tag_size(20));
363    assert_eq!(8, enum_tag_size(21));
364    assert_eq!(8, enum_tag_size(22));
365    assert_eq!(8, enum_tag_size(256));
366    assert_eq!(16, enum_tag_size(257));
367}