Summary
The #746 fix (0.43.0) stopped synth from skipping wide static-region loads under --native-pointer-abi, but the relocated copy path now silently miscompiles: it runs and produces WRONG bytes for part of a static buffer copy. This is a regression from loud skip → silent wrong data, which for a safety-relevant backend is the worse failure mode.
Found dissolving gale's v0.4.0 fused gust:os {time, log} node (the buffer-carrying syscall seam #746 was meant to unblock). The node now dissolves cleanly (13 functions, 0 skipped, only read32/write32 undefined) — but the emitted object copies the wrong bytes.
Symptom (deterministic, qemu cortex-m3)
The app copies the static string "gust:os up\n" into a Vec<u8> and the provider writes it byte-by-byte to an mmio sink. Captured on the sink:
got = [2, 0, 0, 0, 1, 0, 0, 32, 117, 112, 10]
expected = [103,117,115,116,58,111,115, 32, 117, 112, 10] ("gust:os up\n")
Bytes 7–10 (" up\n") are correct; bytes 0–6 ("gust:os") are garbage — and the garbage is the .data content immediately AFTER the string:
arm-none-eabi-objdump -s -j .data:
0020 67757374 3a6f7320 75700a00 02000000 gust:os up......
^^^^^^^^ the 02 00 00 ... that leaked into bytes 0-6
So the copy's source address is miscomputed (offset past the string) for the head chunk; the tail chunk reads correctly. run()'s unrelated return value and the byte count (11) are both correct — only the copied content is wrong.
Isolation (synth ARM backend, nothing upstream)
- The pre-synth wasm is correct. Running
fused.wasm AND loom.wasm (i.e. immediately before synth compile) under a wasmtime host that stubs gust:hal/mmio returns byte-exact "gust:os up\n". So component-new ×3 → wac plug → meld 0.41 fuse → loom 1.2 inline all produce functionally correct wasm; only synth compile corrupts it.
- The
.data is placed correctly (string is byte-exact in the object). The bug is in the CODE that copies it, traced to the inlined Vec::extend_from_slice/memmove copy loop (func_17, reached run → func_20 → func_16 RawVec-grow → func_17), or in the relocated static source address feeding it.
- Not ABI/r11: verified an r11=0 (
fp) trampoline compiles correctly; failure is byte-identical with and without it.
- A direct single
i64.load from a static (a 12-line repro) codegens correctly under 0.43 — so this is specific to the multi-chunk copy path, not every wide static load.
Repro
gale benches/gust/drivers/build-os-tl.sh (synth 0.43.0, meld 0.41.0, loom 1.2.0; S1/S2 surgeries removed) produces os-tl-cm3.o; benches/gust/src/bin/gust_os_tl_probe.rs is the qemu oracle. Committed at gale feat/gust-v0.4.0-syscall-seam @ ef68c59. Happy to help reduce to a standalone minimal case — the direct-i64.load toy doesn't trigger it, so it needs the copy-loop shape (static source ≥ ~9 bytes, chunked head+tail copy above wasm_data_base).
Impact
v0.4.0 buffer-carrying syscall seam remains blocked — #746 changed the failure mode from skip to silent-wrong, but the node still doesn't run correctly. Blocks every buffer-carrying capability (log/channel/io). The stateless gust:os/time node (no buffer copy) is unaffected and passes.
Summary
The #746 fix (0.43.0) stopped synth from skipping wide static-region loads under
--native-pointer-abi, but the relocated copy path now silently miscompiles: it runs and produces WRONG bytes for part of a static buffer copy. This is a regression from loud skip → silent wrong data, which for a safety-relevant backend is the worse failure mode.Found dissolving gale's v0.4.0 fused
gust:os {time, log}node (the buffer-carrying syscall seam #746 was meant to unblock). The node now dissolves cleanly (13 functions, 0 skipped, onlyread32/write32undefined) — but the emitted object copies the wrong bytes.Symptom (deterministic, qemu cortex-m3)
The app copies the static string
"gust:os up\n"into aVec<u8>and the provider writes it byte-by-byte to an mmio sink. Captured on the sink:Bytes 7–10 (
" up\n") are correct; bytes 0–6 ("gust:os") are garbage — and the garbage is the.datacontent immediately AFTER the string:So the copy's source address is miscomputed (offset past the string) for the head chunk; the tail chunk reads correctly.
run()'s unrelated return value and the byte count (11) are both correct — only the copied content is wrong.Isolation (synth ARM backend, nothing upstream)
fused.wasmANDloom.wasm(i.e. immediately beforesynth compile) under a wasmtime host that stubsgust:hal/mmioreturns byte-exact"gust:os up\n". So component-new ×3 → wac plug → meld 0.41 fuse → loom 1.2 inline all produce functionally correct wasm; onlysynth compilecorrupts it..datais placed correctly (string is byte-exact in the object). The bug is in the CODE that copies it, traced to the inlinedVec::extend_from_slice/memmove copy loop (func_17, reachedrun → func_20 → func_16RawVec-grow→ func_17), or in the relocated static source address feeding it.fp) trampoline compiles correctly; failure is byte-identical with and without it.i64.loadfrom a static (a 12-line repro) codegens correctly under 0.43 — so this is specific to the multi-chunk copy path, not every wide static load.Repro
gale
benches/gust/drivers/build-os-tl.sh(synth 0.43.0, meld 0.41.0, loom 1.2.0; S1/S2 surgeries removed) producesos-tl-cm3.o;benches/gust/src/bin/gust_os_tl_probe.rsis the qemu oracle. Committed at galefeat/gust-v0.4.0-syscall-seam@ ef68c59. Happy to help reduce to a standalone minimal case — the direct-i64.loadtoy doesn't trigger it, so it needs the copy-loop shape (static source ≥ ~9 bytes, chunked head+tail copy abovewasm_data_base).Impact
v0.4.0 buffer-carrying syscall seam remains blocked — #746 changed the failure mode from skip to silent-wrong, but the node still doesn't run correctly. Blocks every buffer-carrying capability (log/channel/io). The stateless
gust:os/timenode (no buffer copy) is unaffected and passes.