Skip to main content

synth_backend/
linker_script.rs

1//! Linker Script Generator for ARM Cortex-M
2//!
3//! Generates GNU ld linker scripts (.ld files) for embedded ARM targets
4
5use synth_core::Result;
6
7/// Memory region definition
8#[derive(Debug, Clone)]
9pub struct MemoryRegion {
10    /// Region name (e.g., "FLASH", "RAM")
11    pub name: String,
12    /// Start address
13    pub origin: u32,
14    /// Size in bytes
15    pub length: u32,
16    /// Attributes (r=read, w=write, x=execute)
17    pub attributes: String,
18}
19
20/// Linker script generator
21pub struct LinkerScriptGenerator {
22    /// Memory regions
23    regions: Vec<MemoryRegion>,
24    /// Entry point symbol
25    entry_point: String,
26    /// Stack size
27    stack_size: u32,
28    /// Heap size
29    heap_size: u32,
30    /// WASM linear memory size (0 = disabled)
31    wasm_memory_size: u32,
32    /// Enable Meld runtime integration sections
33    meld_integration: bool,
34}
35
36impl Default for LinkerScriptGenerator {
37    fn default() -> Self {
38        Self::new()
39    }
40}
41
42impl LinkerScriptGenerator {
43    /// Create a new linker script generator with default STM32 memory layout
44    pub fn new_stm32() -> Self {
45        let regions = vec![
46            MemoryRegion {
47                name: "FLASH".to_string(),
48                origin: 0x08000000,
49                length: 512 * 1024, // 512KB
50                attributes: "rx".to_string(),
51            },
52            MemoryRegion {
53                name: "RAM".to_string(),
54                origin: 0x20000000,
55                length: 128 * 1024, // 128KB
56                attributes: "rwx".to_string(),
57            },
58        ];
59
60        Self {
61            regions,
62            entry_point: "Reset_Handler".to_string(),
63            stack_size: 4096, // 4KB stack
64            heap_size: 8192,  // 8KB heap
65            wasm_memory_size: 0,
66            meld_integration: false,
67        }
68    }
69
70    /// Create a custom linker script generator
71    pub fn new() -> Self {
72        Self {
73            regions: Vec::new(),
74            entry_point: "Reset_Handler".to_string(),
75            stack_size: 4096,
76            heap_size: 0,
77            wasm_memory_size: 0,
78            meld_integration: false,
79        }
80    }
81
82    /// Add a memory region
83    pub fn add_region(&mut self, region: MemoryRegion) -> &mut Self {
84        self.regions.push(region);
85        self
86    }
87
88    /// Set the entry point
89    pub fn with_entry_point(mut self, entry: String) -> Self {
90        self.entry_point = entry;
91        self
92    }
93
94    /// Set stack size
95    pub fn with_stack_size(mut self, size: u32) -> Self {
96        self.stack_size = size;
97        self
98    }
99
100    /// Set heap size
101    pub fn with_heap_size(mut self, size: u32) -> Self {
102        self.heap_size = size;
103        self
104    }
105
106    /// Set WASM linear memory size (adds a .wasm_linear_memory section)
107    pub fn with_wasm_memory(mut self, size: u32) -> Self {
108        self.wasm_memory_size = size;
109        self
110    }
111
112    /// Enable Meld runtime integration (adds import table and extern symbols)
113    pub fn with_meld_integration(mut self) -> Self {
114        self.meld_integration = true;
115        self
116    }
117
118    /// Generate the linker script
119    pub fn generate(&self) -> Result<String> {
120        let mut script = String::new();
121
122        // Header comment
123        script.push_str("/* Generated Linker Script for ARM Cortex-M */\n");
124        script.push_str("/* Generated by Synth */\n\n");
125
126        // Entry point
127        script.push_str(&format!("ENTRY({})\n\n", self.entry_point));
128
129        // Meld runtime external symbols
130        if self.meld_integration {
131            script.push_str("/* Meld runtime symbols (provided by meld static library) */\n");
132            script.push_str("EXTERN(__meld_dispatch_import)\n");
133            script.push_str("EXTERN(__meld_get_memory_base)\n\n");
134        }
135
136        // Stack and heap symbols
137        script.push_str(&format!("_stack_size = 0x{:X};\n", self.stack_size));
138        if self.heap_size > 0 {
139            script.push_str(&format!("_heap_size = 0x{:X};\n", self.heap_size));
140        }
141        script.push('\n');
142
143        // Memory regions
144        script.push_str("MEMORY\n{\n");
145        for region in &self.regions {
146            script.push_str(&format!(
147                "  {} ({}): ORIGIN = 0x{:08X}, LENGTH = 0x{:X}\n",
148                region.name, region.attributes, region.origin, region.length
149            ));
150        }
151        script.push_str("}\n\n");
152
153        // Sections
154        script.push_str("SECTIONS\n{\n");
155
156        // .isr_vector section (interrupt vector table)
157        script.push_str("  .isr_vector :\n");
158        script.push_str("  {\n");
159        script.push_str("    . = ALIGN(256);  /* Vector table must be aligned to next power-of-2 >= table size */\n");
160        script.push_str("    KEEP(*(.isr_vector))\n");
161        script.push_str("    . = ALIGN(4);\n");
162        script.push_str("  } >FLASH\n\n");
163
164        // .text section (code)
165        script.push_str("  .text :\n");
166        script.push_str("  {\n");
167        script.push_str("    . = ALIGN(4);\n");
168        script.push_str("    *(.text)\n");
169        script.push_str("    *(.text*)\n");
170        if self.meld_integration {
171            script.push_str("    *(.text.synth.*)   /* Synth-compiled component code */\n");
172            script.push_str("    *(.text.meld.*)    /* Meld runtime */\n");
173        }
174        script.push_str("    *(.glue_7)         /* ARM/Thumb interworking */\n");
175        script.push_str("    *(.glue_7t)\n");
176        script.push_str("    *(.eh_frame)\n");
177        script.push_str("    KEEP (*(.init))\n");
178        script.push_str("    KEEP (*(.fini))\n");
179        script.push_str("    . = ALIGN(4);\n");
180        script.push_str("    _etext = .;\n");
181        script.push_str("  } >FLASH\n\n");
182
183        // Meld import descriptor table (read-only, in FLASH)
184        if self.meld_integration {
185            script.push_str("  .meld_import_table :\n");
186            script.push_str("  {\n");
187            script.push_str("    . = ALIGN(4);\n");
188            script.push_str("    __meld_import_table_start = .;\n");
189            script.push_str("    KEEP(*(.meld.imports))\n");
190            script.push_str("    __meld_import_table_end = .;\n");
191            script.push_str("  } >FLASH\n\n");
192        }
193
194        // .rodata section (read-only data)
195        script.push_str("  .rodata :\n");
196        script.push_str("  {\n");
197        script.push_str("    . = ALIGN(4);\n");
198        script.push_str("    *(.rodata)\n");
199        script.push_str("    *(.rodata*)\n");
200        script.push_str("    . = ALIGN(4);\n");
201        script.push_str("  } >FLASH\n\n");
202
203        // .ARM.extab and .ARM.exidx sections (exception handling)
204        script.push_str("  .ARM.extab :\n");
205        script.push_str("  {\n");
206        script.push_str("    *(.ARM.extab* .gnu.linkonce.armextab.*)\n");
207        script.push_str("  } >FLASH\n\n");
208
209        script.push_str("  .ARM.exidx :\n");
210        script.push_str("  {\n");
211        script.push_str("    __exidx_start = .;\n");
212        script.push_str("    *(.ARM.exidx* .gnu.linkonce.armexidx.*)\n");
213        script.push_str("    __exidx_end = .;\n");
214        script.push_str("  } >FLASH\n\n");
215
216        // .preinit_array, .init_array, .fini_array (C++ constructors/destructors)
217        script.push_str("  .preinit_array :\n");
218        script.push_str("  {\n");
219        script.push_str("    PROVIDE_HIDDEN (__preinit_array_start = .);\n");
220        script.push_str("    KEEP (*(.preinit_array*))\n");
221        script.push_str("    PROVIDE_HIDDEN (__preinit_array_end = .);\n");
222        script.push_str("  } >FLASH\n\n");
223
224        script.push_str("  .init_array :\n");
225        script.push_str("  {\n");
226        script.push_str("    PROVIDE_HIDDEN (__init_array_start = .);\n");
227        script.push_str("    KEEP (*(SORT(.init_array.*)))\n");
228        script.push_str("    KEEP (*(.init_array*))\n");
229        script.push_str("    PROVIDE_HIDDEN (__init_array_end = .);\n");
230        script.push_str("  } >FLASH\n\n");
231
232        script.push_str("  .fini_array :\n");
233        script.push_str("  {\n");
234        script.push_str("    PROVIDE_HIDDEN (__fini_array_start = .);\n");
235        script.push_str("    KEEP (*(SORT(.fini_array.*)))\n");
236        script.push_str("    KEEP (*(.fini_array*))\n");
237        script.push_str("    PROVIDE_HIDDEN (__fini_array_end = .);\n");
238        script.push_str("  } >FLASH\n\n");
239
240        // Load address for .data initialization
241        script.push_str("  _sidata = LOADADDR(.data);\n\n");
242
243        // .data section (initialized data)
244        script.push_str("  .data :\n");
245        script.push_str("  {\n");
246        script.push_str("    . = ALIGN(4);\n");
247        script.push_str("    _sdata = .;        /* Start of data section */\n");
248        script.push_str("    *(.data)\n");
249        script.push_str("    *(.data*)\n");
250        script.push_str("    . = ALIGN(4);\n");
251        script.push_str("    _edata = .;        /* End of data section */\n");
252        script.push_str("  } >RAM AT> FLASH\n\n");
253
254        // .bss section (zero-initialized data)
255        script.push_str("  .bss :\n");
256        script.push_str("  {\n");
257        script.push_str("    . = ALIGN(4);\n");
258        script.push_str("    _sbss = .;         /* Start of BSS section */\n");
259        script.push_str("    __bss_start__ = _sbss;\n");
260        script.push_str("    *(.bss)\n");
261        script.push_str("    *(.bss*)\n");
262        script.push_str("    *(COMMON)\n");
263        script.push_str("    . = ALIGN(4);\n");
264        script.push_str("    _ebss = .;         /* End of BSS section */\n");
265        script.push_str("    __bss_end__ = _ebss;\n");
266        script.push_str("  } >RAM\n\n");
267
268        // WASM linear memory (if configured)
269        if self.wasm_memory_size > 0 {
270            script.push_str(&format!(
271                "  __WASM_MEMORY_SIZE = 0x{:X};\n",
272                self.wasm_memory_size
273            ));
274            script.push_str("  .wasm_linear_memory (NOLOAD) :\n");
275            script.push_str("  {\n");
276            script.push_str("    . = ALIGN(4);\n");
277            script.push_str("    __wasm_memory_start = .;\n");
278            script.push_str("    . = . + __WASM_MEMORY_SIZE;\n");
279            script.push_str("    __wasm_memory_end = .;\n");
280            script.push_str("  } >RAM\n\n");
281        }
282
283        // Heap section (if enabled)
284        if self.heap_size > 0 {
285            script.push_str("  .heap :\n");
286            script.push_str("  {\n");
287            script.push_str("    . = ALIGN(4);\n");
288            script.push_str("    _sheap = .;\n");
289            script.push_str("    . = . + _heap_size;\n");
290            script.push_str("    . = ALIGN(4);\n");
291            script.push_str("    _eheap = .;\n");
292            script.push_str("  } >RAM\n\n");
293        }
294
295        // Stack (grows downward from end of RAM)
296        script.push_str("  .stack :\n");
297        script.push_str("  {\n");
298        script.push_str("    . = ALIGN(8);\n");
299        script.push_str("    _sstack = .;\n");
300        script.push_str("    . = . + _stack_size;\n");
301        script.push_str("    . = ALIGN(8);\n");
302        script.push_str("    _estack = .;\n");
303        script.push_str("  } >RAM\n\n");
304
305        // Remove debugging symbols
306        script.push_str("  /DISCARD/ :\n");
307        script.push_str("  {\n");
308        script.push_str("    libc.a ( * )\n");
309        script.push_str("    libm.a ( * )\n");
310        script.push_str("    libgcc.a ( * )\n");
311        script.push_str("  }\n\n");
312
313        // Attributes
314        script.push_str("  .ARM.attributes 0 : { *(.ARM.attributes) }\n");
315
316        script.push_str("}\n");
317
318        Ok(script)
319    }
320
321    /// Generate and write to a file
322    pub fn generate_to_file(&self, path: &str) -> Result<()> {
323        let script = self.generate()?;
324        std::fs::write(path, script)?;
325        Ok(())
326    }
327}
328
329#[cfg(test)]
330mod tests {
331    use super::*;
332
333    #[test]
334    fn test_linker_script_generation() {
335        let generator = LinkerScriptGenerator::new_stm32();
336        let script = generator.generate().expect("Failed to generate");
337
338        assert!(script.contains("ENTRY(Reset_Handler)"));
339        assert!(script.contains("MEMORY"));
340        assert!(script.contains("FLASH"));
341        assert!(script.contains("RAM"));
342        assert!(script.contains("SECTIONS"));
343    }
344
345    #[test]
346    fn test_custom_memory_regions() {
347        let mut generator = LinkerScriptGenerator::new();
348        generator.add_region(MemoryRegion {
349            name: "FLASH".to_string(),
350            origin: 0x08000000,
351            length: 1024 * 1024,
352            attributes: "rx".to_string(),
353        });
354
355        let script = generator.generate().expect("Failed to generate");
356        assert!(script.contains("FLASH"));
357        assert!(script.contains("0x08000000"));
358    }
359
360    #[test]
361    fn test_entry_point() {
362        let generator = LinkerScriptGenerator::new_stm32().with_entry_point("main".to_string());
363
364        let script = generator.generate().expect("Failed to generate");
365        assert!(script.contains("ENTRY(main)"));
366    }
367
368    #[test]
369    fn test_stack_configuration() {
370        let generator = LinkerScriptGenerator::new_stm32().with_stack_size(8192);
371
372        let script = generator.generate().expect("Failed to generate");
373        assert!(script.contains("_stack_size = 0x2000")); // 8192 = 0x2000
374    }
375
376    #[test]
377    fn test_heap_configuration() {
378        let generator = LinkerScriptGenerator::new_stm32().with_heap_size(16384);
379
380        let script = generator.generate().expect("Failed to generate");
381        assert!(script.contains("_heap_size = 0x4000")); // 16384 = 0x4000
382        assert!(script.contains(".heap"));
383    }
384
385    #[test]
386    fn test_section_alignment() {
387        let generator = LinkerScriptGenerator::new_stm32();
388        let script = generator.generate().expect("Failed to generate");
389
390        // Vector table must be 256-byte aligned (power-of-2 >= table size)
391        assert!(script.contains("ALIGN(256)"));
392        // Other sections should be 4-byte aligned
393        assert!(script.contains("ALIGN(4)"));
394    }
395
396    #[test]
397    fn test_data_section_initialization() {
398        let generator = LinkerScriptGenerator::new_stm32();
399        let script = generator.generate().expect("Failed to generate");
400
401        // Data section should have load address
402        assert!(script.contains("_sidata"));
403        assert!(script.contains("_sdata"));
404        assert!(script.contains("_edata"));
405        assert!(script.contains(">RAM AT> FLASH"));
406    }
407
408    #[test]
409    fn test_bss_section() {
410        let generator = LinkerScriptGenerator::new_stm32();
411        let script = generator.generate().expect("Failed to generate");
412
413        assert!(script.contains(".bss"));
414        assert!(script.contains("_sbss"));
415        assert!(script.contains("_ebss"));
416    }
417
418    #[test]
419    fn test_meld_integration_sections() {
420        let generator = LinkerScriptGenerator::new_stm32()
421            .with_meld_integration()
422            .with_wasm_memory(64 * 1024); // 64KB WASM linear memory
423
424        let script = generator.generate().expect("Failed to generate");
425
426        // External symbols
427        assert!(script.contains("EXTERN(__meld_dispatch_import)"));
428        assert!(script.contains("EXTERN(__meld_get_memory_base)"));
429
430        // Import table section
431        assert!(script.contains(".meld_import_table"));
432        assert!(script.contains("__meld_import_table_start"));
433        assert!(script.contains("__meld_import_table_end"));
434
435        // Synth/Meld code sections
436        assert!(script.contains("*(.text.synth.*)"));
437        assert!(script.contains("*(.text.meld.*)"));
438
439        // WASM linear memory
440        assert!(script.contains(".wasm_linear_memory"));
441        assert!(script.contains("__wasm_memory_start"));
442        assert!(script.contains("__wasm_memory_end"));
443        assert!(script.contains("__WASM_MEMORY_SIZE = 0x10000")); // 64KB
444    }
445
446    #[test]
447    fn test_isr_vector_section() {
448        let generator = LinkerScriptGenerator::new_stm32();
449        let script = generator.generate().expect("Failed to generate");
450
451        assert!(script.contains(".isr_vector"));
452        assert!(script.contains("KEEP(*(.isr_vector))"));
453    }
454}