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 Layout for () {
102    fn head_size() -> usize {
103        0
104    }
105}
106
107/// Computes layout of a type, iff all inner types have their layout computed.
108// Does not recurse.
109pub fn compute(ty: &ir::Ty) -> Option<ir::TyLayout> {
110    Some(match &ty.kind {
111        ir::TyKind::Array(_) | ir::TyKind::Primitive(ir::TyPrimitive::text) => ir::TyLayout {
112            head_size: 64,
113            body_ptrs: vec![0],
114        },
115
116        ir::TyKind::Primitive(prim) => {
117            let head_size = match prim {
118                ir::TyPrimitive::int8 => 8,
119                ir::TyPrimitive::int16 => 16,
120                ir::TyPrimitive::int32 => 32,
121                ir::TyPrimitive::int64 => 64,
122                ir::TyPrimitive::uint8 => 8,
123                ir::TyPrimitive::uint16 => 16,
124                ir::TyPrimitive::uint32 => 32,
125                ir::TyPrimitive::uint64 => 64,
126                ir::TyPrimitive::float32 => 32,
127                ir::TyPrimitive::float64 => 64,
128                ir::TyPrimitive::bool => 8,
129                ir::TyPrimitive::text => unreachable!(),
130            };
131            ir::TyLayout {
132                head_size,
133                body_ptrs: vec![],
134            }
135        }
136
137        ir::TyKind::Tuple(fields) => {
138            let mut head_size: u32 = 0;
139            let mut body_ptrs = vec::Vec::new();
140            for f in fields {
141                let Some(layout) = &f.ty.layout else {
142                    return None;
143                };
144
145                let field_offset = head_size.div_ceil(8);
146                head_size += layout.head_size;
147                body_ptrs.extend(layout.body_ptrs.iter().map(|p| p + field_offset));
148            }
149
150            ir::TyLayout {
151                head_size,
152                body_ptrs,
153            }
154        }
155
156        ir::TyKind::Enum(variants) => {
157            let head = enum_head_format(variants, &ty.variants_recursive);
158
159            let head_size = (head.tag_bytes + head.inner_bytes) * 8;
160
161            let body_ptrs = if head.has_ptr {
162                vec![head.tag_bytes]
163            } else {
164                // here we should include body_ptrs in the head of inner type,
165                // but no type with a ptr fits into 4 bytes, so we can just skip
166                // that and assume there are no body_ptrs
167                vec![]
168            };
169
170            ir::TyLayout {
171                head_size,
172                body_ptrs,
173            }
174        }
175        _ => return None,
176    })
177}
178
179pub fn tuple_field_offsets(ty: &ir::Ty) -> vec::Vec<u32> {
180    let ir::TyKind::Tuple(ty_fields) = &ty.kind else {
181        panic!("got: {:?}", ty.kind)
182    };
183
184    let mut field_offsets = vec::Vec::with_capacity(ty_fields.len());
185    let mut offset = 0_u32;
186    for field in ty_fields {
187        field_offsets.push(offset);
188
189        let layout = field.ty.layout.as_ref().unwrap();
190        offset += (layout.head_size).div_ceil(8);
191    }
192    field_offsets
193}
194
195pub fn tuple_field_offset(ty: &ir::Ty, position: u16) -> u32 {
196    *tuple_field_offsets(ty).get(position as usize).unwrap()
197}
198
199pub fn does_enum_variant_contain_recursive(enum_ty: &ir::Ty, variant_index: u16) -> bool {
200    enum_ty.variants_recursive.contains(&variant_index)
201}
202
203pub use crate::generated::layout::EnumFormat;
204
205pub fn enum_format(variants: &[ir::TyEnumVariant], variants_recursive: &[u16]) -> EnumFormat {
206    let head = enum_head_format(variants, variants_recursive);
207    let variants = variants
208        .iter()
209        .map(|v| enum_variant_format(&head, &v.ty))
210        .collect();
211    EnumFormat {
212        tag_bytes: head.tag_bytes as u8,
213        inner_bytes: head.inner_bytes as u8,
214        has_ptr: head.has_ptr,
215        variants,
216    }
217}
218
219#[derive(Debug)]
220pub struct EnumHeadFormat {
221    pub tag_bytes: u32,
222    pub inner_bytes: u32,
223    pub has_ptr: bool,
224}
225
226pub fn enum_head_format(
227    variants: &[ir::TyEnumVariant],
228    variants_recursive: &[u16],
229) -> EnumHeadFormat {
230    let t = enum_tag_size(variants.len());
231
232    let force_ptr = !variants_recursive.is_empty();
233
234    let max_head = if force_ptr {
235        u32::MAX
236    } else {
237        enum_max_variant_head_size(variants)
238    };
239
240    let mut has_ptr = false;
241    let inner = if max_head > 32 {
242        // insert a pointer
243        has_ptr = true;
244        32
245    } else {
246        max_head
247    };
248
249    EnumHeadFormat {
250        tag_bytes: t.div_ceil(8),
251        inner_bytes: inner.div_ceil(8),
252        has_ptr,
253    }
254}
255
256pub use crate::generated::layout::EnumVariantFormat;
257
258pub fn enum_variant_format(head: &EnumHeadFormat, variant_ty: &ir::Ty) -> EnumVariantFormat {
259    let inner_head_size = variant_ty.layout.as_ref().unwrap().head_size;
260    let inner_head_bytes = inner_head_size.div_ceil(8);
261
262    let is_unit = inner_head_bytes == 0;
263
264    let padding_bytes = if head.has_ptr {
265        if is_unit {
266            // unit variant does not have a pointer, it is all padding
267            4
268        } else {
269            0
270        }
271    } else {
272        head.inner_bytes.saturating_sub(inner_head_bytes) as u8
273    };
274
275    EnumVariantFormat {
276        padding_bytes,
277        is_unit,
278    }
279}
280
281fn enum_max_variant_head_size(variants: &[ir::TyEnumVariant]) -> u32 {
282    let mut i = 0;
283    for v in variants {
284        let Some(ty_layout) = &v.ty.layout else {
285            panic!("missing layout: {:?}", v.ty)
286        };
287        let size = ty_layout.head_size;
288        i = i.max(size);
289    }
290    i
291}
292
293fn enum_tag_size(variants_len: usize) -> u32 {
294    // TODO: when bool-sub-byte packing is implemented, remove function in favor of enum_tag_size_used
295    enum_tag_size_used(variants_len).div_ceil(8) * 8
296}
297
298fn enum_tag_size_used(variants_len: usize) -> u32 {
299    variants_len
300        .saturating_sub(1)
301        .checked_ilog2()
302        .map(|x| x + 1)
303        .unwrap_or_default()
304}
305
306#[test]
307fn test_enum_tag_size() {
308    assert_eq!(0, enum_tag_size_used(0));
309    assert_eq!(0, enum_tag_size_used(1));
310    assert_eq!(1, enum_tag_size_used(2));
311    assert_eq!(2, enum_tag_size_used(3));
312    assert_eq!(2, enum_tag_size_used(4));
313    assert_eq!(3, enum_tag_size_used(5));
314    assert_eq!(3, enum_tag_size_used(6));
315    assert_eq!(3, enum_tag_size_used(7));
316    assert_eq!(3, enum_tag_size_used(8));
317    assert_eq!(4, enum_tag_size_used(9));
318    assert_eq!(4, enum_tag_size_used(10));
319    assert_eq!(4, enum_tag_size_used(11));
320    assert_eq!(4, enum_tag_size_used(12));
321    assert_eq!(4, enum_tag_size_used(13));
322    assert_eq!(4, enum_tag_size_used(14));
323    assert_eq!(4, enum_tag_size_used(15));
324    assert_eq!(4, enum_tag_size_used(16));
325    assert_eq!(5, enum_tag_size_used(17));
326    assert_eq!(5, enum_tag_size_used(18));
327    assert_eq!(5, enum_tag_size_used(19));
328    assert_eq!(5, enum_tag_size_used(20));
329    assert_eq!(5, enum_tag_size_used(21));
330    assert_eq!(5, enum_tag_size_used(22));
331    assert_eq!(8, enum_tag_size_used(256));
332    assert_eq!(9, enum_tag_size_used(257));
333
334    assert_eq!(0, enum_tag_size(0));
335    assert_eq!(0, enum_tag_size(1));
336    assert_eq!(8, enum_tag_size(2));
337    assert_eq!(8, enum_tag_size(3));
338    assert_eq!(8, enum_tag_size(4));
339    assert_eq!(8, enum_tag_size(5));
340    assert_eq!(8, enum_tag_size(6));
341    assert_eq!(8, enum_tag_size(7));
342    assert_eq!(8, enum_tag_size(8));
343    assert_eq!(8, enum_tag_size(9));
344    assert_eq!(8, enum_tag_size(10));
345    assert_eq!(8, enum_tag_size(11));
346    assert_eq!(8, enum_tag_size(12));
347    assert_eq!(8, enum_tag_size(13));
348    assert_eq!(8, enum_tag_size(14));
349    assert_eq!(8, enum_tag_size(15));
350    assert_eq!(8, enum_tag_size(16));
351    assert_eq!(8, enum_tag_size(17));
352    assert_eq!(8, enum_tag_size(18));
353    assert_eq!(8, enum_tag_size(19));
354    assert_eq!(8, enum_tag_size(20));
355    assert_eq!(8, enum_tag_size(21));
356    assert_eq!(8, enum_tag_size(22));
357    assert_eq!(8, enum_tag_size(256));
358    assert_eq!(16, enum_tag_size(257));
359}