Skip to main content

synth_backend/
memory_layout.rs

1//! Memory Layout Analyzer
2//!
3//! Analyzes WebAssembly modules and generates memory layouts for embedded targets
4
5use synth_core::{Component, Error, HardwareCapabilities, Result};
6
7/// Memory section type
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum SectionType {
10    /// Executable code (.text)
11    Text,
12    /// Read-only data (.rodata)
13    ReadOnlyData,
14    /// Initialized data (.data)
15    Data,
16    /// Uninitialized data (.bss)
17    Bss,
18    /// Stack
19    Stack,
20    /// Heap
21    Heap,
22}
23
24/// Memory section in the layout
25#[derive(Debug, Clone)]
26pub struct MemorySection {
27    /// Section type
28    pub section_type: SectionType,
29
30    /// Section name
31    pub name: String,
32
33    /// Base address
34    pub base_address: u32,
35
36    /// Size in bytes
37    pub size: u32,
38
39    /// Alignment requirement
40    pub alignment: u32,
41
42    /// Whether section is in flash (XIP) or RAM
43    pub in_flash: bool,
44}
45
46impl MemorySection {
47    /// Get end address (exclusive)
48    pub fn end_address(&self) -> u32 {
49        self.base_address + self.size
50    }
51
52    /// Check if this section overlaps with another
53    pub fn overlaps(&self, other: &MemorySection) -> bool {
54        let self_end = self.end_address();
55        let other_end = other.end_address();
56
57        !(self_end <= other.base_address || other_end <= self.base_address)
58    }
59}
60
61/// Memory layout for a WebAssembly module
62#[derive(Debug, Clone)]
63pub struct MemoryLayout {
64    /// Hardware capabilities
65    hw_caps: HardwareCapabilities,
66
67    /// Sections in the layout
68    sections: Vec<MemorySection>,
69
70    /// Total flash usage
71    flash_usage: u32,
72
73    /// Total RAM usage
74    ram_usage: u32,
75}
76
77impl MemoryLayout {
78    /// Create a new memory layout
79    pub fn new(hw_caps: HardwareCapabilities) -> Self {
80        Self {
81            hw_caps,
82            sections: Vec::new(),
83            flash_usage: 0,
84            ram_usage: 0,
85        }
86    }
87
88    /// Add a section to the layout
89    pub fn add_section(&mut self, section: MemorySection) -> Result<()> {
90        // Check for overlaps
91        for existing in &self.sections {
92            if section.overlaps(existing) {
93                return Err(Error::MemoryLayoutError(format!(
94                    "Section '{}' at 0x{:08X} overlaps with '{}' at 0x{:08X}",
95                    section.name, section.base_address, existing.name, existing.base_address
96                )));
97            }
98        }
99
100        // Update usage counters
101        if section.in_flash {
102            self.flash_usage += section.size;
103        } else {
104            self.ram_usage += section.size;
105        }
106
107        self.sections.push(section);
108        Ok(())
109    }
110
111    /// Get all sections
112    pub fn sections(&self) -> &[MemorySection] {
113        &self.sections
114    }
115
116    /// Get flash usage
117    pub fn flash_usage(&self) -> u32 {
118        self.flash_usage
119    }
120
121    /// Get RAM usage
122    pub fn ram_usage(&self) -> u32 {
123        self.ram_usage
124    }
125
126    /// Validate layout against hardware capabilities
127    pub fn validate(&self) -> Result<()> {
128        // Check flash capacity
129        if self.flash_usage as u64 > self.hw_caps.flash_size {
130            return Err(Error::MemoryLayoutError(format!(
131                "Flash usage {} bytes exceeds capacity {} bytes",
132                self.flash_usage, self.hw_caps.flash_size
133            )));
134        }
135
136        // Check RAM capacity
137        if self.ram_usage as u64 > self.hw_caps.ram_size {
138            return Err(Error::MemoryLayoutError(format!(
139                "RAM usage {} bytes exceeds capacity {} bytes",
140                self.ram_usage, self.hw_caps.ram_size
141            )));
142        }
143
144        Ok(())
145    }
146
147    /// Get section by type
148    pub fn get_section(&self, section_type: SectionType) -> Option<&MemorySection> {
149        self.sections
150            .iter()
151            .find(|s| s.section_type == section_type)
152    }
153
154    /// Generate GNU LD linker script for ARM Cortex-M
155    pub fn generate_linker_script(&self) -> String {
156        let mut script = String::new();
157
158        script.push_str("/* Linker script for ARM Cortex-M */\n");
159        script.push_str("/* Generated by Synth WebAssembly Component Synthesizer */\n\n");
160
161        // Memory regions
162        script.push_str("MEMORY\n{\n");
163        script.push_str(&format!(
164            "  FLASH (rx) : ORIGIN = 0x00000000, LENGTH = {}K\n",
165            self.hw_caps.flash_size / 1024
166        ));
167        script.push_str(&format!(
168            "  RAM (rwx)  : ORIGIN = 0x20000000, LENGTH = {}K\n",
169            self.hw_caps.ram_size / 1024
170        ));
171        script.push_str("}\n\n");
172
173        // Entry point
174        script.push_str("ENTRY(Reset_Handler)\n\n");
175
176        // Stack size
177        let stack_section = self.get_section(SectionType::Stack);
178        let stack_size = stack_section.map(|s| s.size).unwrap_or(4096);
179        script.push_str(&format!("_stack_size = {};\n\n", stack_size));
180
181        // Heap size
182        let heap_section = self.get_section(SectionType::Heap);
183        let heap_size = heap_section.map(|s| s.size).unwrap_or(8192);
184        script.push_str(&format!("_heap_size = {};\n\n", heap_size));
185
186        // Sections
187        script.push_str("SECTIONS\n{\n");
188
189        // .text section
190        script.push_str("  .text :\n");
191        script.push_str("  {\n");
192        script.push_str("    KEEP(*(.isr_vector))\n");
193        script.push_str("    *(.text*)\n");
194        script.push_str("    *(.rodata*)\n");
195        script.push_str("    KEEP(*(.init))\n");
196        script.push_str("    KEEP(*(.fini))\n");
197        script.push_str("    . = ALIGN(4);\n");
198        script.push_str("    _etext = .;\n");
199        script.push_str("  } > FLASH\n\n");
200
201        // .ARM.exidx section (for exception handling)
202        script.push_str("  .ARM.exidx :\n");
203        script.push_str("  {\n");
204        script.push_str("    __exidx_start = .;\n");
205        script.push_str("    *(.ARM.exidx* .gnu.linkonce.armexidx.*)\n");
206        script.push_str("    __exidx_end = .;\n");
207        script.push_str("  } > FLASH\n\n");
208
209        // .data section
210        script.push_str("  .data :\n");
211        script.push_str("  {\n");
212        script.push_str("    _sdata = .;\n");
213        script.push_str("    *(.data*)\n");
214        script.push_str("    . = ALIGN(4);\n");
215        script.push_str("    _edata = .;\n");
216        script.push_str("  } > RAM AT> FLASH\n\n");
217        script.push_str("  _sidata = LOADADDR(.data);\n\n");
218
219        // .bss section
220        script.push_str("  .bss :\n");
221        script.push_str("  {\n");
222        script.push_str("    _sbss = .;\n");
223        script.push_str("    *(.bss*)\n");
224        script.push_str("    *(COMMON)\n");
225        script.push_str("    . = ALIGN(4);\n");
226        script.push_str("    _ebss = .;\n");
227        script.push_str("  } > RAM\n\n");
228
229        // .heap section
230        script.push_str("  .heap :\n");
231        script.push_str("  {\n");
232        script.push_str("    _heap_start = .;\n");
233        script.push_str("    . = . + _heap_size;\n");
234        script.push_str("    _heap_end = .;\n");
235        script.push_str("  } > RAM\n\n");
236
237        // .stack section
238        script.push_str("  .stack :\n");
239        script.push_str("  {\n");
240        script.push_str("    . = . + _stack_size;\n");
241        script.push_str("    _stack_top = .;\n");
242        script.push_str("  } > RAM\n\n");
243
244        script.push_str("  /* Remove information from the standard libraries */\n");
245        script.push_str("  /DISCARD/ :\n");
246        script.push_str("  {\n");
247        script.push_str("    libc.a ( * )\n");
248        script.push_str("    libm.a ( * )\n");
249        script.push_str("    libgcc.a ( * )\n");
250        script.push_str("  }\n");
251
252        script.push_str("}\n");
253
254        script
255    }
256}
257
258/// Memory layout analyzer
259pub struct MemoryLayoutAnalyzer {
260    hw_caps: HardwareCapabilities,
261}
262
263impl MemoryLayoutAnalyzer {
264    /// Create a new analyzer
265    pub fn new(hw_caps: HardwareCapabilities) -> Self {
266        Self { hw_caps }
267    }
268
269    /// Analyze a component and generate memory layout
270    pub fn analyze(&self, component: &Component) -> Result<MemoryLayout> {
271        let mut layout = MemoryLayout::new(self.hw_caps.clone());
272
273        // Calculate sizes for each section
274        let text_size = self.estimate_text_size(component);
275        let rodata_size = self.estimate_rodata_size(component);
276        let data_size = self.estimate_data_size(component);
277        let bss_size = self.estimate_bss_size(component);
278        let stack_size = self.estimate_stack_size(component);
279        let heap_size = self.estimate_heap_size(component);
280
281        // Allocate sections
282        // For XIP (Execute In Place), put .text and .rodata in flash
283        let flash_base = 0x00000000;
284        let ram_base = 0x20000000;
285
286        let mut current_flash = flash_base;
287        let mut current_ram = ram_base;
288
289        // .text section in flash
290        if text_size > 0 {
291            let text_section = MemorySection {
292                section_type: SectionType::Text,
293                name: ".text".to_string(),
294                base_address: current_flash,
295                size: text_size,
296                alignment: 4,
297                in_flash: true,
298            };
299            current_flash = align_up(text_section.end_address(), 4);
300            layout.add_section(text_section)?;
301        }
302
303        // .rodata section in flash
304        if rodata_size > 0 {
305            let rodata_section = MemorySection {
306                section_type: SectionType::ReadOnlyData,
307                name: ".rodata".to_string(),
308                base_address: current_flash,
309                size: rodata_size,
310                alignment: 4,
311                in_flash: true,
312            };
313            layout.add_section(rodata_section)?;
314        }
315
316        // .data section in RAM (but stored in flash, copied at startup)
317        if data_size > 0 {
318            let data_section = MemorySection {
319                section_type: SectionType::Data,
320                name: ".data".to_string(),
321                base_address: current_ram,
322                size: data_size,
323                alignment: 4,
324                in_flash: false,
325            };
326            current_ram = align_up(data_section.end_address(), 4);
327            layout.add_section(data_section)?;
328        }
329
330        // .bss section in RAM
331        if bss_size > 0 {
332            let bss_section = MemorySection {
333                section_type: SectionType::Bss,
334                name: ".bss".to_string(),
335                base_address: current_ram,
336                size: bss_size,
337                alignment: 4,
338                in_flash: false,
339            };
340            current_ram = align_up(bss_section.end_address(), 4);
341            layout.add_section(bss_section)?;
342        }
343
344        // Heap in RAM
345        if heap_size > 0 {
346            let heap_section = MemorySection {
347                section_type: SectionType::Heap,
348                name: ".heap".to_string(),
349                base_address: current_ram,
350                size: heap_size,
351                alignment: 8,
352                in_flash: false,
353            };
354            layout.add_section(heap_section)?;
355        }
356
357        // Stack in RAM (grows downward, so we place it at the end)
358        if stack_size > 0 {
359            let stack_base = self.hw_caps.ram_size as u32 - stack_size;
360            let stack_section = MemorySection {
361                section_type: SectionType::Stack,
362                name: ".stack".to_string(),
363                base_address: stack_base,
364                size: stack_size,
365                alignment: 8,
366                in_flash: false,
367            };
368            layout.add_section(stack_section)?;
369        }
370
371        // Validate the layout
372        layout.validate()?;
373
374        Ok(layout)
375    }
376
377    /// Estimate .text section size
378    fn estimate_text_size(&self, component: &Component) -> u32 {
379        // Rough estimate: assume 1 WASM instruction = 2-4 ARM instructions
380        // and each ARM instruction is 2 or 4 bytes (Thumb-2)
381        // For now, use a conservative estimate of 8 bytes per function
382        let mut size = 0u32;
383
384        for module in &component.modules {
385            // Estimate based on number of functions
386            size += (module.functions.len() as u32) * 128; // 128 bytes per function average
387        }
388
389        // Round up to alignment
390        align_up(size, 4)
391    }
392
393    /// Estimate .rodata section size
394    fn estimate_rodata_size(&self, component: &Component) -> u32 {
395        let mut size = 0u32;
396
397        for module in &component.modules {
398            // Estimate from global data
399            size += (module.globals.len() as u32) * 4;
400        }
401
402        align_up(size, 4)
403    }
404
405    /// Estimate .data section size
406    fn estimate_data_size(&self, component: &Component) -> u32 {
407        let mut size = 0u32;
408
409        for module in &component.modules {
410            // Data segments from WebAssembly linear memory
411            for memory in &module.memories {
412                size += memory.initial * 65536; // Pages to bytes
413            }
414        }
415
416        align_up(size, 4)
417    }
418
419    /// Estimate .bss section size
420    fn estimate_bss_size(&self, _component: &Component) -> u32 {
421        // For now, allocate a fixed amount for uninitialized data
422        align_up(4096, 4)
423    }
424
425    /// Estimate stack size
426    fn estimate_stack_size(&self, _component: &Component) -> u32 {
427        // Conservative estimate: 256 bytes per stack frame, max depth of 16
428        let stack_size = 256 * 16;
429
430        align_up(stack_size, 8)
431    }
432
433    /// Estimate heap size
434    fn estimate_heap_size(&self, _component: &Component) -> u32 {
435        // Allocate remaining RAM after other sections
436        // For now, use a conservative 8KB
437        align_up(8192, 8)
438    }
439}
440
441/// Align value up to alignment
442fn align_up(value: u32, alignment: u32) -> u32 {
443    (value + alignment - 1) & !(alignment - 1)
444}
445
446#[cfg(test)]
447mod tests {
448    use super::*;
449    use std::collections::HashMap;
450    use synth_core::{CoreModule, Function, FunctionSignature, Global, Memory, ValueType};
451
452    fn test_component() -> Component {
453        Component {
454            name: "test".to_string(),
455            modules: vec![CoreModule {
456                id: "test_module".to_string(),
457                binary: vec![],
458                functions: vec![Function {
459                    index: 0,
460                    name: Some("add".to_string()),
461                    signature: FunctionSignature {
462                        params: vec![ValueType::I32, ValueType::I32],
463                        results: vec![ValueType::I32],
464                    },
465                    exported: true,
466                    imported: false,
467                }],
468                memories: vec![Memory {
469                    index: 0,
470                    initial: 1, // 64KB
471                    maximum: None,
472                    shared: false,
473                    memory64: false,
474                }],
475                tables: vec![],
476                globals: vec![Global {
477                    index: 0,
478                    value_type: ValueType::I32,
479                    mutable: false,
480                }],
481            }],
482            components: vec![],
483            instances: vec![],
484            interfaces: HashMap::new(),
485            imports: vec![],
486            exports: vec![],
487        }
488    }
489
490    #[test]
491    fn test_memory_section_overlap() {
492        let section1 = MemorySection {
493            section_type: SectionType::Text,
494            name: ".text".to_string(),
495            base_address: 0x00000000,
496            size: 1024,
497            alignment: 4,
498            in_flash: true,
499        };
500
501        let section2 = MemorySection {
502            section_type: SectionType::ReadOnlyData,
503            name: ".rodata".to_string(),
504            base_address: 0x00000400,
505            size: 512,
506            alignment: 4,
507            in_flash: true,
508        };
509
510        let section3 = MemorySection {
511            section_type: SectionType::Data,
512            name: ".data".to_string(),
513            base_address: 0x00000200, // Overlaps with section1
514            size: 512,
515            alignment: 4,
516            in_flash: false,
517        };
518
519        assert!(!section1.overlaps(&section2));
520        assert!(section1.overlaps(&section3));
521    }
522
523    #[test]
524    fn test_memory_layout_creation() {
525        let hw_caps = HardwareCapabilities::nrf52840();
526        let analyzer = MemoryLayoutAnalyzer::new(hw_caps);
527        let component = test_component();
528
529        let layout = analyzer.analyze(&component).unwrap();
530
531        // Should have sections allocated
532        assert!(!layout.sections().is_empty());
533
534        // Should have flash usage (text + rodata)
535        assert!(layout.flash_usage() > 0);
536
537        // Should have RAM usage (data + bss + stack + heap)
538        assert!(layout.ram_usage() > 0);
539
540        // Validate against hardware
541        assert!(layout.validate().is_ok());
542    }
543
544    #[test]
545    fn test_memory_layout_validation() {
546        let hw_caps = HardwareCapabilities::nrf52840();
547        let analyzer = MemoryLayoutAnalyzer::new(hw_caps);
548        let component = test_component();
549
550        let layout = analyzer.analyze(&component).unwrap();
551
552        // Print layout for inspection
553        println!("\nMemory Layout:");
554        println!(
555            "Flash usage: {} / {} bytes",
556            layout.flash_usage(),
557            layout.flash_usage
558        );
559        println!(
560            "RAM usage: {} / {} bytes",
561            layout.ram_usage(),
562            layout.ram_usage
563        );
564        println!("\nSections:");
565        for section in layout.sections() {
566            println!(
567                "  {} ({:?}): 0x{:08X} - 0x{:08X} ({} bytes, {})",
568                section.name,
569                section.section_type,
570                section.base_address,
571                section.end_address(),
572                section.size,
573                if section.in_flash { "flash" } else { "RAM" }
574            );
575        }
576
577        assert!(layout.validate().is_ok());
578    }
579
580    #[test]
581    fn test_linker_script_generation() {
582        let hw_caps = HardwareCapabilities::nrf52840();
583        let analyzer = MemoryLayoutAnalyzer::new(hw_caps);
584        let component = test_component();
585
586        let layout = analyzer.analyze(&component).unwrap();
587        let linker_script = layout.generate_linker_script();
588
589        // Print linker script for inspection
590        println!("\nGenerated Linker Script:");
591        println!("{}", linker_script);
592
593        // Verify key elements are present
594        assert!(linker_script.contains("MEMORY"));
595        assert!(linker_script.contains("FLASH"));
596        assert!(linker_script.contains("RAM"));
597        assert!(linker_script.contains("ENTRY(Reset_Handler)"));
598        assert!(linker_script.contains(".text"));
599        assert!(linker_script.contains(".data"));
600        assert!(linker_script.contains(".bss"));
601        assert!(linker_script.contains(".heap"));
602        assert!(linker_script.contains(".stack"));
603        assert!(linker_script.contains("_sdata"));
604        assert!(linker_script.contains("_edata"));
605        assert!(linker_script.contains("_sbss"));
606        assert!(linker_script.contains("_ebss"));
607        assert!(linker_script.contains("_stack_top"));
608    }
609}