solverforge_core/wasm/
memory.rs

1use crate::domain::{DomainClass, FieldType, PrimitiveType};
2use indexmap::IndexMap;
3
4#[derive(Debug, Clone)]
5pub struct MemoryLayout {
6    pub total_size: u32,
7    pub alignment: u32,
8    pub field_offsets: IndexMap<String, FieldLayout>,
9}
10
11#[derive(Debug, Clone)]
12pub struct FieldLayout {
13    pub offset: u32,
14    pub size: u32,
15    pub wasm_type: WasmMemoryType,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum WasmMemoryType {
20    I32,
21    I64,
22    F32,
23    F64,
24    Pointer,
25    ArrayPointer,
26}
27
28impl WasmMemoryType {
29    pub fn size(&self) -> u32 {
30        match self {
31            WasmMemoryType::I32 => 4,
32            WasmMemoryType::I64 => 8,
33            WasmMemoryType::F32 => 4,
34            WasmMemoryType::F64 => 8,
35            WasmMemoryType::Pointer => 4,
36            // Arrays are host-managed references (pointers) in WASM32 - 4 bytes
37            WasmMemoryType::ArrayPointer => 4,
38        }
39    }
40
41    pub fn alignment(&self) -> u32 {
42        match self {
43            WasmMemoryType::I32 => 4,
44            WasmMemoryType::I64 => 8,
45            WasmMemoryType::F32 => 4,
46            WasmMemoryType::F64 => 8,
47            WasmMemoryType::Pointer => 4,
48            WasmMemoryType::ArrayPointer => 4,
49        }
50    }
51}
52
53#[derive(Debug, Default)]
54pub struct LayoutCalculator {
55    class_layouts: IndexMap<String, MemoryLayout>,
56}
57
58impl LayoutCalculator {
59    pub fn new() -> Self {
60        Self::default()
61    }
62
63    pub fn calculate_layout(&mut self, class: &DomainClass) -> MemoryLayout {
64        if let Some(layout) = self.class_layouts.get(&class.name) {
65            return layout.clone();
66        }
67
68        let mut field_offsets = IndexMap::new();
69        let mut current_offset: u32 = 0;
70        let mut max_alignment: u32 = 4;
71
72        for field in &class.fields {
73            let wasm_type = field_type_to_wasm(&field.field_type);
74            let field_alignment = wasm_type.alignment();
75            let field_size = wasm_type.size();
76
77            if !current_offset.is_multiple_of(field_alignment) {
78                current_offset = (current_offset / field_alignment + 1) * field_alignment;
79            }
80
81            field_offsets.insert(
82                field.name.clone(),
83                FieldLayout {
84                    offset: current_offset,
85                    size: field_size,
86                    wasm_type,
87                },
88            );
89
90            current_offset += field_size;
91            max_alignment = max_alignment.max(field_alignment);
92        }
93
94        if !current_offset.is_multiple_of(max_alignment) {
95            current_offset = (current_offset / max_alignment + 1) * max_alignment;
96        }
97
98        let layout = MemoryLayout {
99            total_size: current_offset.max(4),
100            field_offsets,
101            alignment: max_alignment,
102        };
103
104        self.class_layouts
105            .insert(class.name.clone(), layout.clone());
106        layout
107    }
108
109    pub fn get_layout(&self, class_name: &str) -> Option<&MemoryLayout> {
110        self.class_layouts.get(class_name)
111    }
112}
113
114fn field_type_to_wasm(field_type: &FieldType) -> WasmMemoryType {
115    match field_type {
116        FieldType::Primitive(prim) => match prim {
117            PrimitiveType::Bool => WasmMemoryType::I32,
118            PrimitiveType::Int => WasmMemoryType::I32,
119            PrimitiveType::Long => WasmMemoryType::I64,
120            PrimitiveType::Float => WasmMemoryType::F32,
121            PrimitiveType::Double => WasmMemoryType::F64,
122            PrimitiveType::String => WasmMemoryType::Pointer,
123            PrimitiveType::Date => WasmMemoryType::I64,
124            PrimitiveType::DateTime => WasmMemoryType::I64,
125        },
126        FieldType::Object { .. } => WasmMemoryType::Pointer,
127        FieldType::Array { .. } | FieldType::List { .. } | FieldType::Set { .. } => {
128            WasmMemoryType::ArrayPointer
129        }
130        FieldType::Map { .. } => WasmMemoryType::Pointer,
131        FieldType::Score(_) => WasmMemoryType::Pointer,
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138    use crate::domain::{FieldDescriptor, PlanningAnnotation};
139
140    fn create_test_class() -> DomainClass {
141        DomainClass::new("Lesson")
142            .with_annotation(PlanningAnnotation::PlanningEntity)
143            .with_field(FieldDescriptor::new(
144                "id",
145                FieldType::Primitive(PrimitiveType::Long),
146            ))
147            .with_field(FieldDescriptor::new("room", FieldType::object("Room")))
148            .with_field(FieldDescriptor::new(
149                "timeslot",
150                FieldType::object("Timeslot"),
151            ))
152    }
153
154    #[test]
155    fn test_layout_calculation() {
156        let mut calc = LayoutCalculator::new();
157        let class = create_test_class();
158        let layout = calc.calculate_layout(&class);
159
160        // id (i64) + room (pointer) + timeslot (pointer) = 8 + 4 + 4 = 16 bytes
161        assert!(layout.total_size >= 16);
162        assert!(layout.field_offsets.contains_key("id"));
163        assert!(layout.field_offsets.contains_key("room"));
164        assert!(layout.field_offsets.contains_key("timeslot"));
165    }
166
167    #[test]
168    fn test_field_type_mapping() {
169        assert_eq!(
170            field_type_to_wasm(&FieldType::Primitive(PrimitiveType::Int)),
171            WasmMemoryType::I32
172        );
173        assert_eq!(
174            field_type_to_wasm(&FieldType::Primitive(PrimitiveType::Long)),
175            WasmMemoryType::I64
176        );
177        assert_eq!(
178            field_type_to_wasm(&FieldType::Primitive(PrimitiveType::Double)),
179            WasmMemoryType::F64
180        );
181        assert_eq!(
182            field_type_to_wasm(&FieldType::object("Room")),
183            WasmMemoryType::Pointer
184        );
185        assert_eq!(
186            field_type_to_wasm(&FieldType::list(FieldType::object("Room"))),
187            WasmMemoryType::ArrayPointer
188        );
189    }
190
191    #[test]
192    fn test_alignment() {
193        let mut calc = LayoutCalculator::new();
194        let class = DomainClass::new("Test")
195            .with_field(FieldDescriptor::new(
196                "a",
197                FieldType::Primitive(PrimitiveType::Int),
198            ))
199            .with_field(FieldDescriptor::new(
200                "b",
201                FieldType::Primitive(PrimitiveType::Long),
202            ));
203
204        let layout = calc.calculate_layout(&class);
205        let b_layout = layout.field_offsets.get("b").unwrap();
206
207        assert_eq!(b_layout.offset % 8, 0);
208    }
209
210    #[test]
211    fn test_cached_layout() {
212        let mut calc = LayoutCalculator::new();
213        let class = create_test_class();
214
215        let layout1 = calc.calculate_layout(&class);
216        let layout2 = calc.calculate_layout(&class);
217
218        assert_eq!(layout1.total_size, layout2.total_size);
219        assert!(calc.get_layout("Lesson").is_some());
220        assert!(calc.get_layout("Unknown").is_none());
221    }
222
223    #[test]
224    fn test_wasm_memory_type_sizes() {
225        assert_eq!(WasmMemoryType::I32.size(), 4);
226        assert_eq!(WasmMemoryType::I64.size(), 8);
227        assert_eq!(WasmMemoryType::F32.size(), 4);
228        assert_eq!(WasmMemoryType::F64.size(), 8);
229        assert_eq!(WasmMemoryType::Pointer.size(), 4);
230        assert_eq!(WasmMemoryType::ArrayPointer.size(), 4);
231    }
232
233    #[test]
234    fn test_wasm_memory_type_alignment() {
235        assert_eq!(WasmMemoryType::I32.alignment(), 4);
236        assert_eq!(WasmMemoryType::I64.alignment(), 8);
237        assert_eq!(WasmMemoryType::F32.alignment(), 4);
238        assert_eq!(WasmMemoryType::F64.alignment(), 8);
239        assert_eq!(WasmMemoryType::Pointer.alignment(), 4);
240        assert_eq!(WasmMemoryType::ArrayPointer.alignment(), 4);
241    }
242
243    #[test]
244    fn test_empty_class_layout() {
245        let mut calc = LayoutCalculator::new();
246        let class = DomainClass::new("Empty");
247        let layout = calc.calculate_layout(&class);
248
249        assert_eq!(layout.total_size, 4); // Minimum 4 bytes
250        assert_eq!(layout.alignment, 4);
251        assert!(layout.field_offsets.is_empty());
252    }
253
254    #[test]
255    fn test_bool_field() {
256        let mut calc = LayoutCalculator::new();
257        let class = DomainClass::new("Test").with_field(FieldDescriptor::new(
258            "active",
259            FieldType::Primitive(PrimitiveType::Bool),
260        ));
261
262        let layout = calc.calculate_layout(&class);
263        let field = layout.field_offsets.get("active").unwrap();
264
265        assert_eq!(field.wasm_type, WasmMemoryType::I32);
266    }
267
268    #[test]
269    fn test_score_field() {
270        use crate::domain::ScoreType;
271
272        let mut calc = LayoutCalculator::new();
273        let class = DomainClass::new("Test").with_field(FieldDescriptor::new(
274            "score",
275            FieldType::Score(ScoreType::HardSoft),
276        ));
277
278        let layout = calc.calculate_layout(&class);
279        let field = layout.field_offsets.get("score").unwrap();
280
281        assert_eq!(field.wasm_type, WasmMemoryType::Pointer);
282    }
283
284    #[test]
285    fn test_map_field() {
286        let mut calc = LayoutCalculator::new();
287        let class = DomainClass::new("Test").with_field(FieldDescriptor::new(
288            "data",
289            FieldType::map(
290                FieldType::Primitive(PrimitiveType::String),
291                FieldType::object("Value"),
292            ),
293        ));
294
295        let layout = calc.calculate_layout(&class);
296        let field = layout.field_offsets.get("data").unwrap();
297
298        assert_eq!(field.wasm_type, WasmMemoryType::Pointer);
299    }
300}