Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions crates/synth-cli/tests/cabi_arena_realloc_linkability_418.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//! synth#418 — embedder import `cabi-arena-realloc` binds to the native TCB
//! arena allocator via synth's relocatable host-link path (#197).
//!
//! In the BYO-OS lean-MCU dissolve (gale#89), grow-free components route
//! `cabi_realloc` to an embedder-provided `env::__cabi_arena_realloc` import
//! (wit-bindgen `cabi-realloc-extern`). synth operates on the CORE module, so it
//! sees the verbatim core field name `__cabi_arena_realloc` (the kebab
//! `cabi-arena-realloc` is a component-surface concern, never reaching synth),
//! and its `--relocatable` import-call path (#197) lowers the call to a direct
//! `R_ARM_THM_CALL` relocation against an UNDEFINED `__cabi_arena_realloc`
//! symbol — which the native link binds to the TCB's arena allocator. synth
//! references the symbol; the TCB provides it; the linker binds it (the correct
//! layering — synth never implements the allocator or the realloc contract).
//!
//! This locks that mechanism. The full end-to-end check on gale's real dissolved
//! component (out of the wit-bindgen#4 → wasm-tools#2 → meld#301 pipeline) lands
//! when that fixture arrives; this proves the synth-layer contract synthetically.

use std::path::PathBuf;
use std::process::Command;

use object::read::elf::ElfFile32;
use object::{Object, ObjectSection, ObjectSymbol, RelocationTarget};

fn synth() -> &'static str {
env!("CARGO_BIN_EXE_synth")
}

fn fixture() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join("scripts/repro/cabi_arena_realloc.wat")
}

#[test]
fn cabi_arena_realloc_emits_undefined_symbol_and_reloc_418() {
let out = "/tmp/cabi_arena_realloc_418.o";
let status = Command::new(synth())
.args([
"compile",
fixture().to_str().unwrap(),
"--target",
"cortex-m4",
"--native-pointer-abi",
"--all-exports",
"--relocatable",
"-o",
out,
])
.output()
.expect("run synth");
assert!(
status.status.success(),
"compile failed: {}",
String::from_utf8_lossy(&status.stderr)
);

let data = std::fs::read(out).expect("read .o");
let obj = ElfFile32::<object::Endianness>::parse(&*data).expect("parse ELF");

// (1) `__cabi_arena_realloc` is present as an UNDEFINED symbol — left for the
// native link / TCB to satisfy, not implemented by synth.
let sym = obj
.symbols()
.find(|s| s.name() == Ok("__cabi_arena_realloc"))
.expect("__cabi_arena_realloc symbol present");
assert!(
sym.is_undefined(),
"__cabi_arena_realloc must be UNDEFINED (embedder-bound), not defined in synth's output"
);

// (2) a relocation targets that symbol at the call site — so the native
// linker rewrites the `bl` to the TCB's arena allocator.
let mut reloc_targets_arena = false;
for section in obj.sections() {
for (_off, reloc) in section.relocations() {
if let RelocationTarget::Symbol(idx) = reloc.target()
&& let Ok(s) = obj.symbol_by_index(idx)
&& s.name() == Ok("__cabi_arena_realloc")
{
reloc_targets_arena = true;
}
}
}
assert!(
reloc_targets_arena,
"a relocation must target __cabi_arena_realloc (the import call site), \
so the native link binds it to the TCB allocator"
);
}
6 changes: 6 additions & 0 deletions scripts/repro/cabi_arena_realloc.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(module
(import "env" "__cabi_arena_realloc"
(func $arena (param i32 i32 i32 i32) (result i32)))
(memory 1)
(func (export "do_realloc") (param i32 i32) (result i32)
(call $arena (local.get 0) (i32.const 0) (i32.const 8) (local.get 1))))
Loading