Skip to main content

shape_value/v2/
struct_layout.rs

1/// StructLayout engine: computes C-compatible field layouts from type definitions.
2///
3/// Every typed struct starts with HeapHeader (8 bytes) then fields in declaration
4/// order with natural alignment padding.
5
6/// Field type for v2 struct layouts.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum FieldKind {
9    F64,  // 8 bytes, 8-align
10    I64,  // 8 bytes, 8-align
11    I32,  // 4 bytes, 4-align
12    I16,  // 2 bytes, 2-align
13    I8,   // 1 byte, 1-align
14    U64,  // 8 bytes, 8-align
15    U32,  // 4 bytes, 4-align
16    U16,  // 2 bytes, 2-align
17    U8,   // 1 byte, 1-align
18    Bool, // 1 byte, 1-align
19    Ptr,  // 8 bytes, 8-align — typed pointer to heap object
20}
21
22impl FieldKind {
23    pub fn size(&self) -> usize {
24        match self {
25            FieldKind::F64 | FieldKind::I64 | FieldKind::U64 | FieldKind::Ptr => 8,
26            FieldKind::I32 | FieldKind::U32 => 4,
27            FieldKind::I16 | FieldKind::U16 => 2,
28            FieldKind::I8 | FieldKind::U8 | FieldKind::Bool => 1,
29        }
30    }
31
32    pub fn alignment(&self) -> usize {
33        self.size()
34    }
35}
36
37/// Information about a single field in a struct layout.
38#[derive(Debug, Clone)]
39pub struct FieldInfo {
40    pub name: String,
41    pub kind: FieldKind,
42    pub offset: usize, // byte offset from START of struct (including header)
43    pub size: usize,
44}
45
46/// Computed C-compatible layout for a typed struct.
47///
48/// The layout starts with an 8-byte HeapHeader at offset 0. Fields follow
49/// at offset 8+ with natural alignment padding between them. The total size
50/// is rounded up to 8-byte alignment.
51#[derive(Debug)]
52pub struct StructLayout {
53    pub fields: Vec<FieldInfo>,
54    pub total_size: usize,        // including HeapHeader (8 bytes)
55    pub heap_field_mask: u64,     // bitmap: bit N = field N is Ptr type
56}
57
58impl StructLayout {
59    /// Compute layout from field definitions. HeapHeader is at offset 0 (8 bytes).
60    /// Fields start at offset 8, with natural alignment padding.
61    pub fn new(fields: &[(impl AsRef<str>, FieldKind)]) -> Self {
62        let mut current_offset = 8; // after HeapHeader
63        let mut field_infos = Vec::new();
64        let mut heap_mask: u64 = 0;
65
66        for (i, (name, kind)) in fields.iter().enumerate() {
67            let align = kind.alignment();
68            let size = kind.size();
69            // Align current_offset to field's natural alignment
70            current_offset = (current_offset + align - 1) & !(align - 1);
71            field_infos.push(FieldInfo {
72                name: name.as_ref().to_string(),
73                kind: *kind,
74                offset: current_offset,
75                size,
76            });
77            if *kind == FieldKind::Ptr {
78                heap_mask |= 1u64 << i;
79            }
80            current_offset += size;
81        }
82        // Final size: align to 8 bytes
83        let total_size = (current_offset + 7) & !7;
84
85        StructLayout {
86            fields: field_infos,
87            total_size,
88            heap_field_mask: heap_mask,
89        }
90    }
91
92    pub fn field_offset(&self, idx: usize) -> usize {
93        self.fields[idx].offset
94    }
95
96    pub fn field_kind(&self, idx: usize) -> FieldKind {
97        self.fields[idx].kind
98    }
99
100    pub fn total_size(&self) -> usize {
101        self.total_size
102    }
103
104    pub fn field_count(&self) -> usize {
105        self.fields.len()
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_point_f64_f64() {
115        // Point { x: F64, y: F64 }
116        let layout = StructLayout::new(&[
117            ("x", FieldKind::F64),
118            ("y", FieldKind::F64),
119        ]);
120        assert_eq!(layout.field_count(), 2);
121        assert_eq!(layout.field_offset(0), 8);  // x at offset 8
122        assert_eq!(layout.field_offset(1), 16); // y at offset 16
123        assert_eq!(layout.total_size(), 24);
124        assert_eq!(layout.heap_field_mask, 0);
125    }
126
127    #[test]
128    fn test_mixed_alignment_padding() {
129        // Mixed { a: I32, b: F64, c: I8 }
130        // a at offset 8 (4 bytes, 4-align: 8 is 4-aligned)
131        // b at offset 16 (8 bytes, 8-align: 12 not 8-aligned, pad to 16)
132        // c at offset 24 (1 byte, 1-align)
133        // total: round 25 up to 32
134        let layout = StructLayout::new(&[
135            ("a", FieldKind::I32),
136            ("b", FieldKind::F64),
137            ("c", FieldKind::I8),
138        ]);
139        assert_eq!(layout.field_count(), 3);
140        assert_eq!(layout.field_offset(0), 8);   // a: I32 at 8
141        assert_eq!(layout.field_kind(0), FieldKind::I32);
142        assert_eq!(layout.fields[0].size, 4);
143
144        assert_eq!(layout.field_offset(1), 16);  // b: F64 at 16 (padded from 12)
145        assert_eq!(layout.field_kind(1), FieldKind::F64);
146        assert_eq!(layout.fields[1].size, 8);
147
148        assert_eq!(layout.field_offset(2), 24);  // c: I8 at 24
149        assert_eq!(layout.field_kind(2), FieldKind::I8);
150        assert_eq!(layout.fields[2].size, 1);
151
152        assert_eq!(layout.total_size(), 32);     // 25 rounded up to 32
153    }
154
155    #[test]
156    fn test_all_field_kinds() {
157        let layout = StructLayout::new(&[
158            ("f0", FieldKind::F64),   // 8, size 8
159            ("f1", FieldKind::I64),   // 16, size 8
160            ("f2", FieldKind::I32),   // 24, size 4
161            ("f3", FieldKind::I16),   // 28, size 2
162            ("f4", FieldKind::I8),    // 30, size 1
163            ("f5", FieldKind::U64),   // 32, size 8 (pad from 31 to 32)
164            ("f6", FieldKind::U32),   // 40, size 4
165            ("f7", FieldKind::U16),   // 44, size 2
166            ("f8", FieldKind::U8),    // 46, size 1
167            ("f9", FieldKind::Bool),  // 47, size 1
168            ("f10", FieldKind::Ptr),  // 48, size 8
169        ]);
170        assert_eq!(layout.field_count(), 11);
171
172        // F64 at 8
173        assert_eq!(layout.field_offset(0), 8);
174        // I64 at 16
175        assert_eq!(layout.field_offset(1), 16);
176        // I32 at 24
177        assert_eq!(layout.field_offset(2), 24);
178        // I16 at 28
179        assert_eq!(layout.field_offset(3), 28);
180        // I8 at 30
181        assert_eq!(layout.field_offset(4), 30);
182        // U64 at 32 (31 padded to 32)
183        assert_eq!(layout.field_offset(5), 32);
184        // U32 at 40
185        assert_eq!(layout.field_offset(6), 40);
186        // U16 at 44
187        assert_eq!(layout.field_offset(7), 44);
188        // U8 at 46
189        assert_eq!(layout.field_offset(8), 46);
190        // Bool at 47
191        assert_eq!(layout.field_offset(9), 47);
192        // Ptr at 48
193        assert_eq!(layout.field_offset(10), 48);
194
195        assert_eq!(layout.total_size(), 56); // 48 + 8 = 56
196        // Only field 10 (Ptr) is heap
197        assert_eq!(layout.heap_field_mask, 1u64 << 10);
198    }
199
200    #[test]
201    fn test_heap_field_mask_positions_1_and_3() {
202        // Struct with Ptr fields at positions 1 and 3
203        let layout = StructLayout::new(&[
204            ("a", FieldKind::I32),   // pos 0: not Ptr
205            ("b", FieldKind::Ptr),   // pos 1: Ptr
206            ("c", FieldKind::F64),   // pos 2: not Ptr
207            ("d", FieldKind::Ptr),   // pos 3: Ptr
208        ]);
209        assert_eq!(layout.heap_field_mask, 0b1010);
210    }
211
212    #[test]
213    fn test_empty_struct() {
214        let layout = StructLayout::new(&[] as &[(&str, FieldKind)]);
215        assert_eq!(layout.field_count(), 0);
216        assert_eq!(layout.total_size(), 8); // header only
217        assert_eq!(layout.heap_field_mask, 0);
218    }
219
220    #[test]
221    fn test_single_bool_field() {
222        // Struct with a single bool: header (8) + bool at 8 (1 byte) = 9 → round to 16
223        let layout = StructLayout::new(&[("flag", FieldKind::Bool)]);
224        assert_eq!(layout.field_count(), 1);
225        assert_eq!(layout.field_offset(0), 8);
226        assert_eq!(layout.fields[0].size, 1);
227        assert_eq!(layout.total_size(), 16);
228        assert_eq!(layout.heap_field_mask, 0);
229    }
230
231    #[test]
232    fn test_all_ptr_fields() {
233        let layout = StructLayout::new(&[
234            ("a", FieldKind::Ptr),
235            ("b", FieldKind::Ptr),
236            ("c", FieldKind::Ptr),
237        ]);
238        assert_eq!(layout.field_offset(0), 8);
239        assert_eq!(layout.field_offset(1), 16);
240        assert_eq!(layout.field_offset(2), 24);
241        assert_eq!(layout.total_size(), 32);
242        assert_eq!(layout.heap_field_mask, 0b111);
243    }
244
245    #[test]
246    fn test_small_fields_packing() {
247        // Multiple small fields pack tightly
248        let layout = StructLayout::new(&[
249            ("a", FieldKind::I8),   // 8
250            ("b", FieldKind::I8),   // 9
251            ("c", FieldKind::I8),   // 10
252            ("d", FieldKind::I8),   // 11
253        ]);
254        assert_eq!(layout.field_offset(0), 8);
255        assert_eq!(layout.field_offset(1), 9);
256        assert_eq!(layout.field_offset(2), 10);
257        assert_eq!(layout.field_offset(3), 11);
258        assert_eq!(layout.total_size(), 16); // 12 rounded to 16
259    }
260
261    #[test]
262    fn test_field_names_preserved() {
263        let layout = StructLayout::new(&[
264            ("x_coord", FieldKind::F64),
265            ("y_coord", FieldKind::F64),
266        ]);
267        assert_eq!(layout.fields[0].name, "x_coord");
268        assert_eq!(layout.fields[1].name, "y_coord");
269    }
270
271    #[test]
272    fn test_field_kind_size_and_alignment() {
273        // Verify all FieldKind sizes
274        assert_eq!(FieldKind::F64.size(), 8);
275        assert_eq!(FieldKind::I64.size(), 8);
276        assert_eq!(FieldKind::I32.size(), 4);
277        assert_eq!(FieldKind::I16.size(), 2);
278        assert_eq!(FieldKind::I8.size(), 1);
279        assert_eq!(FieldKind::U64.size(), 8);
280        assert_eq!(FieldKind::U32.size(), 4);
281        assert_eq!(FieldKind::U16.size(), 2);
282        assert_eq!(FieldKind::U8.size(), 1);
283        assert_eq!(FieldKind::Bool.size(), 1);
284        assert_eq!(FieldKind::Ptr.size(), 8);
285
286        // Alignment equals size for natural alignment
287        assert_eq!(FieldKind::F64.alignment(), 8);
288        assert_eq!(FieldKind::I32.alignment(), 4);
289        assert_eq!(FieldKind::I16.alignment(), 2);
290        assert_eq!(FieldKind::Bool.alignment(), 1);
291        assert_eq!(FieldKind::Ptr.alignment(), 8);
292    }
293}