use synth_core::Result;
#[derive(Debug, Clone)]
pub struct MemoryRegion {
pub name: String,
pub origin: u32,
pub length: u32,
pub attributes: String,
}
pub struct LinkerScriptGenerator {
regions: Vec<MemoryRegion>,
entry_point: String,
stack_size: u32,
heap_size: u32,
wasm_memory_size: u32,
meld_integration: bool,
}
impl Default for LinkerScriptGenerator {
fn default() -> Self {
Self::new()
}
}
impl LinkerScriptGenerator {
pub fn new_stm32() -> Self {
let regions = vec![
MemoryRegion {
name: "FLASH".to_string(),
origin: 0x08000000,
length: 512 * 1024, attributes: "rx".to_string(),
},
MemoryRegion {
name: "RAM".to_string(),
origin: 0x20000000,
length: 128 * 1024, attributes: "rwx".to_string(),
},
];
Self {
regions,
entry_point: "Reset_Handler".to_string(),
stack_size: 4096, heap_size: 8192, wasm_memory_size: 0,
meld_integration: false,
}
}
pub fn new() -> Self {
Self {
regions: Vec::new(),
entry_point: "Reset_Handler".to_string(),
stack_size: 4096,
heap_size: 0,
wasm_memory_size: 0,
meld_integration: false,
}
}
pub fn add_region(&mut self, region: MemoryRegion) -> &mut Self {
self.regions.push(region);
self
}
pub fn with_entry_point(mut self, entry: String) -> Self {
self.entry_point = entry;
self
}
pub fn with_stack_size(mut self, size: u32) -> Self {
self.stack_size = size;
self
}
pub fn with_heap_size(mut self, size: u32) -> Self {
self.heap_size = size;
self
}
pub fn with_wasm_memory(mut self, size: u32) -> Self {
self.wasm_memory_size = size;
self
}
pub fn with_meld_integration(mut self) -> Self {
self.meld_integration = true;
self
}
pub fn generate(&self) -> Result<String> {
let mut script = String::new();
script.push_str("/* Generated Linker Script for ARM Cortex-M */\n");
script.push_str("/* Generated by Synth */\n\n");
script.push_str(&format!("ENTRY({})\n\n", self.entry_point));
if self.meld_integration {
script.push_str("/* Meld runtime symbols (provided by meld static library) */\n");
script.push_str("EXTERN(__meld_dispatch_import)\n");
script.push_str("EXTERN(__meld_get_memory_base)\n\n");
}
script.push_str(&format!("_stack_size = 0x{:X};\n", self.stack_size));
if self.heap_size > 0 {
script.push_str(&format!("_heap_size = 0x{:X};\n", self.heap_size));
}
script.push('\n');
script.push_str("MEMORY\n{\n");
for region in &self.regions {
script.push_str(&format!(
" {} ({}): ORIGIN = 0x{:08X}, LENGTH = 0x{:X}\n",
region.name, region.attributes, region.origin, region.length
));
}
script.push_str("}\n\n");
script.push_str("SECTIONS\n{\n");
script.push_str(" .isr_vector :\n");
script.push_str(" {\n");
script.push_str(" . = ALIGN(256); /* Vector table must be aligned to next power-of-2 >= table size */\n");
script.push_str(" KEEP(*(.isr_vector))\n");
script.push_str(" . = ALIGN(4);\n");
script.push_str(" } >FLASH\n\n");
script.push_str(" .text :\n");
script.push_str(" {\n");
script.push_str(" . = ALIGN(4);\n");
script.push_str(" *(.text)\n");
script.push_str(" *(.text*)\n");
if self.meld_integration {
script.push_str(" *(.text.synth.*) /* Synth-compiled component code */\n");
script.push_str(" *(.text.meld.*) /* Meld runtime */\n");
}
script.push_str(" *(.glue_7) /* ARM/Thumb interworking */\n");
script.push_str(" *(.glue_7t)\n");
script.push_str(" *(.eh_frame)\n");
script.push_str(" KEEP (*(.init))\n");
script.push_str(" KEEP (*(.fini))\n");
script.push_str(" . = ALIGN(4);\n");
script.push_str(" _etext = .;\n");
script.push_str(" } >FLASH\n\n");
if self.meld_integration {
script.push_str(" .meld_import_table :\n");
script.push_str(" {\n");
script.push_str(" . = ALIGN(4);\n");
script.push_str(" __meld_import_table_start = .;\n");
script.push_str(" KEEP(*(.meld.imports))\n");
script.push_str(" __meld_import_table_end = .;\n");
script.push_str(" } >FLASH\n\n");
}
script.push_str(" .rodata :\n");
script.push_str(" {\n");
script.push_str(" . = ALIGN(4);\n");
script.push_str(" *(.rodata)\n");
script.push_str(" *(.rodata*)\n");
script.push_str(" . = ALIGN(4);\n");
script.push_str(" } >FLASH\n\n");
script.push_str(" .ARM.extab :\n");
script.push_str(" {\n");
script.push_str(" *(.ARM.extab* .gnu.linkonce.armextab.*)\n");
script.push_str(" } >FLASH\n\n");
script.push_str(" .ARM.exidx :\n");
script.push_str(" {\n");
script.push_str(" __exidx_start = .;\n");
script.push_str(" *(.ARM.exidx* .gnu.linkonce.armexidx.*)\n");
script.push_str(" __exidx_end = .;\n");
script.push_str(" } >FLASH\n\n");
script.push_str(" .preinit_array :\n");
script.push_str(" {\n");
script.push_str(" PROVIDE_HIDDEN (__preinit_array_start = .);\n");
script.push_str(" KEEP (*(.preinit_array*))\n");
script.push_str(" PROVIDE_HIDDEN (__preinit_array_end = .);\n");
script.push_str(" } >FLASH\n\n");
script.push_str(" .init_array :\n");
script.push_str(" {\n");
script.push_str(" PROVIDE_HIDDEN (__init_array_start = .);\n");
script.push_str(" KEEP (*(SORT(.init_array.*)))\n");
script.push_str(" KEEP (*(.init_array*))\n");
script.push_str(" PROVIDE_HIDDEN (__init_array_end = .);\n");
script.push_str(" } >FLASH\n\n");
script.push_str(" .fini_array :\n");
script.push_str(" {\n");
script.push_str(" PROVIDE_HIDDEN (__fini_array_start = .);\n");
script.push_str(" KEEP (*(SORT(.fini_array.*)))\n");
script.push_str(" KEEP (*(.fini_array*))\n");
script.push_str(" PROVIDE_HIDDEN (__fini_array_end = .);\n");
script.push_str(" } >FLASH\n\n");
script.push_str(" _sidata = LOADADDR(.data);\n\n");
script.push_str(" .data :\n");
script.push_str(" {\n");
script.push_str(" . = ALIGN(4);\n");
script.push_str(" _sdata = .; /* Start of data section */\n");
script.push_str(" *(.data)\n");
script.push_str(" *(.data*)\n");
script.push_str(" . = ALIGN(4);\n");
script.push_str(" _edata = .; /* End of data section */\n");
script.push_str(" } >RAM AT> FLASH\n\n");
script.push_str(" .bss :\n");
script.push_str(" {\n");
script.push_str(" . = ALIGN(4);\n");
script.push_str(" _sbss = .; /* Start of BSS section */\n");
script.push_str(" __bss_start__ = _sbss;\n");
script.push_str(" *(.bss)\n");
script.push_str(" *(.bss*)\n");
script.push_str(" *(COMMON)\n");
script.push_str(" . = ALIGN(4);\n");
script.push_str(" _ebss = .; /* End of BSS section */\n");
script.push_str(" __bss_end__ = _ebss;\n");
script.push_str(" } >RAM\n\n");
if self.wasm_memory_size > 0 {
script.push_str(&format!(
" __WASM_MEMORY_SIZE = 0x{:X};\n",
self.wasm_memory_size
));
script.push_str(" .wasm_linear_memory (NOLOAD) :\n");
script.push_str(" {\n");
script.push_str(" . = ALIGN(4);\n");
script.push_str(" __wasm_memory_start = .;\n");
script.push_str(" . = . + __WASM_MEMORY_SIZE;\n");
script.push_str(" __wasm_memory_end = .;\n");
script.push_str(" } >RAM\n\n");
}
if self.heap_size > 0 {
script.push_str(" .heap :\n");
script.push_str(" {\n");
script.push_str(" . = ALIGN(4);\n");
script.push_str(" _sheap = .;\n");
script.push_str(" . = . + _heap_size;\n");
script.push_str(" . = ALIGN(4);\n");
script.push_str(" _eheap = .;\n");
script.push_str(" } >RAM\n\n");
}
script.push_str(" .stack :\n");
script.push_str(" {\n");
script.push_str(" . = ALIGN(8);\n");
script.push_str(" _sstack = .;\n");
script.push_str(" . = . + _stack_size;\n");
script.push_str(" . = ALIGN(8);\n");
script.push_str(" _estack = .;\n");
script.push_str(" } >RAM\n\n");
script.push_str(" /DISCARD/ :\n");
script.push_str(" {\n");
script.push_str(" libc.a ( * )\n");
script.push_str(" libm.a ( * )\n");
script.push_str(" libgcc.a ( * )\n");
script.push_str(" }\n\n");
script.push_str(" .ARM.attributes 0 : { *(.ARM.attributes) }\n");
script.push_str("}\n");
Ok(script)
}
pub fn generate_to_file(&self, path: &str) -> Result<()> {
let script = self.generate()?;
std::fs::write(path, script)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_linker_script_generation() {
let generator = LinkerScriptGenerator::new_stm32();
let script = generator.generate().expect("Failed to generate");
assert!(script.contains("ENTRY(Reset_Handler)"));
assert!(script.contains("MEMORY"));
assert!(script.contains("FLASH"));
assert!(script.contains("RAM"));
assert!(script.contains("SECTIONS"));
}
#[test]
fn test_custom_memory_regions() {
let mut generator = LinkerScriptGenerator::new();
generator.add_region(MemoryRegion {
name: "FLASH".to_string(),
origin: 0x08000000,
length: 1024 * 1024,
attributes: "rx".to_string(),
});
let script = generator.generate().expect("Failed to generate");
assert!(script.contains("FLASH"));
assert!(script.contains("0x08000000"));
}
#[test]
fn test_entry_point() {
let generator = LinkerScriptGenerator::new_stm32().with_entry_point("main".to_string());
let script = generator.generate().expect("Failed to generate");
assert!(script.contains("ENTRY(main)"));
}
#[test]
fn test_stack_configuration() {
let generator = LinkerScriptGenerator::new_stm32().with_stack_size(8192);
let script = generator.generate().expect("Failed to generate");
assert!(script.contains("_stack_size = 0x2000")); }
#[test]
fn test_heap_configuration() {
let generator = LinkerScriptGenerator::new_stm32().with_heap_size(16384);
let script = generator.generate().expect("Failed to generate");
assert!(script.contains("_heap_size = 0x4000")); assert!(script.contains(".heap"));
}
#[test]
fn test_section_alignment() {
let generator = LinkerScriptGenerator::new_stm32();
let script = generator.generate().expect("Failed to generate");
assert!(script.contains("ALIGN(256)"));
assert!(script.contains("ALIGN(4)"));
}
#[test]
fn test_data_section_initialization() {
let generator = LinkerScriptGenerator::new_stm32();
let script = generator.generate().expect("Failed to generate");
assert!(script.contains("_sidata"));
assert!(script.contains("_sdata"));
assert!(script.contains("_edata"));
assert!(script.contains(">RAM AT> FLASH"));
}
#[test]
fn test_bss_section() {
let generator = LinkerScriptGenerator::new_stm32();
let script = generator.generate().expect("Failed to generate");
assert!(script.contains(".bss"));
assert!(script.contains("_sbss"));
assert!(script.contains("_ebss"));
}
#[test]
fn test_meld_integration_sections() {
let generator = LinkerScriptGenerator::new_stm32()
.with_meld_integration()
.with_wasm_memory(64 * 1024);
let script = generator.generate().expect("Failed to generate");
assert!(script.contains("EXTERN(__meld_dispatch_import)"));
assert!(script.contains("EXTERN(__meld_get_memory_base)"));
assert!(script.contains(".meld_import_table"));
assert!(script.contains("__meld_import_table_start"));
assert!(script.contains("__meld_import_table_end"));
assert!(script.contains("*(.text.synth.*)"));
assert!(script.contains("*(.text.meld.*)"));
assert!(script.contains(".wasm_linear_memory"));
assert!(script.contains("__wasm_memory_start"));
assert!(script.contains("__wasm_memory_end"));
assert!(script.contains("__WASM_MEMORY_SIZE = 0x10000")); }
#[test]
fn test_isr_vector_section() {
let generator = LinkerScriptGenerator::new_stm32();
let script = generator.generate().expect("Failed to generate");
assert!(script.contains(".isr_vector"));
assert!(script.contains("KEEP(*(.isr_vector))"));
}
}