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 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 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); 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}