From 90b3285e9515bcb4204f76e1b579f626a33c4f39 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Mon, 27 Apr 2026 22:24:36 +0200 Subject: [PATCH] feat(cli): add --relocatable flag to force ET_REL output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently synth produces a relocatable object (.o, ET_REL) only when the input wasm has imports — the relocations they generate trigger the relocatable code path. Self-contained wasm modules with no imports produce a complete ET_EXEC firmware with vector table, Reset_Handler, linear_memory section, etc. For linking into a host build system (e.g. integrating verified Rust kernel primitives compiled to wasm into a Zephyr build), the host expects a relocatable .o it can pull into its existing link step. Add a --relocatable CLI flag that forces ET_REL output regardless of whether the wasm has imports. The flag is additive — default behaviour is unchanged. Tested with gale-ffi.wasm (200 functions, 0 imports): output is now a 26645-byte ET_REL ARM EABI5 object with all gale_* symbols defined and no vector-table machinery. Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/synth-cli/src/main.rs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/crates/synth-cli/src/main.rs b/crates/synth-cli/src/main.rs index cfd4eea..af1d77d 100644 --- a/crates/synth-cli/src/main.rs +++ b/crates/synth-cli/src/main.rs @@ -170,6 +170,11 @@ enum Commands { /// Path to kiln-builtins object file (.o) for linking (used with --link) #[arg(long, value_name = "BUILTINS")] builtins: Option, + + /// Force relocatable object (.o, ET_REL) output even when wasm has no imports + /// — for linking into a host build system. + #[arg(long)] + relocatable: bool, }, /// Disassemble an ARM ELF file (e.g., synth disasm output.elf) @@ -248,6 +253,7 @@ fn main() -> Result<()> { verify, link, builtins, + relocatable, } => { // Resolve target spec: --target overrides, --cortex-m is backwards compat let target_spec = resolve_target_spec(target.as_deref(), cortex_m)?; @@ -272,6 +278,7 @@ fn main() -> Result<()> { &backend, verify, &target_spec, + relocatable, )?; // If --link requested, invoke the cross-linker @@ -553,6 +560,7 @@ fn compile_command( backend_name: &str, verify: bool, target_spec: &TargetSpec, + relocatable: bool, ) -> Result<()> { // Validate backend exists let registry = build_backend_registry(); @@ -595,6 +603,7 @@ fn compile_command( backend, verify, target_spec, + relocatable, ); } @@ -1222,6 +1231,7 @@ fn compile_all_exports( backend: &dyn Backend, verify: bool, target_spec: &TargetSpec, + relocatable: bool, ) -> Result<()> { let path = input.context("--all-exports requires an input file")?; @@ -1428,8 +1438,18 @@ fn compile_all_exports( // When there are relocations, produce a relocatable object (.o) instead of // an executable. This lets the output be linked with the Kiln bridge crate // (which provides __meld_dispatch_import and __meld_get_memory_base). - let elf_data = if has_relocations { - info!("Module has import calls — producing relocatable object (ET_REL)"); + // The --relocatable flag forces ET_REL output even when the wasm has no + // imports, for linking into a host build system (e.g. Zephyr). + let elf_data = if has_relocations || relocatable { + let total_relocs: usize = compiled_funcs.iter().map(|f| f.relocations.len()).sum(); + if has_relocations { + info!( + "Producing relocatable object (ET_REL): {} import call relocations", + total_relocs + ); + } else { + info!("Producing relocatable object (ET_REL): forced by --relocatable"); + } build_relocatable_elf(&compiled_funcs, &all_imports)? } else if cortex_m { build_multi_func_cortex_m_elf(&compiled_funcs, &all_memories, target_spec)?