feat(334): MCU-dissolve fixups — coalesce __stack_pointer globals + DCE dead import shim#343
feat(334): MCU-dissolve fixups — coalesce __stack_pointer globals + DCE dead import shim#343avrabe wants to merge 2 commits into
Conversation
…able Plan (traceability leads) for #334: coalesce the N per-component __stack_pointer globals into one shared shadow stack (separate SPs clobber frames — unsound; blocks synth#707), and DCE the dead wac lowered-import shim trampoline (exported '0', mis-typed call_indirect over an import table — synth loud-declines it). release v0.41.0, derives-from SYS-2/SYS-8. rivet validate PASS. Refs #334, SR-49. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d-shim DCE) A post-merge pass (mcu_dissolve.rs, gated on MemoryStrategy::SharedMemory, run before encoding so DWARF/attestation stay consistent) makes a wac-composed multi-provider node dissolve cleanly on an MCU (SR-49): 1. Coalesce the N per-component __stack_pointer globals into one shared shadow stack. Leaving them separate is UNSOUND (providers descending from the shared memory top clobber each other's frames) and blocks synth's shadow-stack rebasing (synth#707). Keep one survivor, redirect global.get/set of the duplicates via the existing rewriter+IndexMaps.globals machinery, renumber survivors down, drop the extras. 2. Drop the dead lowered-import shim trampoline's keep-alive export (a numeric "0" export over an unreachable, import-dispatched call_indirect) so synth DCEs it instead of loud-declining (synth#642/#676). Detection is CONSERVATIVE — SP coalescing uses ONLY the authoritative `name` section signal (`__stack_pointer`). The init-value fallback heuristic was REJECTED (Mythos #334 review, this branch): two unrelated mutable-i32 globals in different components sharing an init would be fused → silent cross-component clobber. Absent the name we do not guess. Trampoline drop requires numeric name + strict body shape + unreachable + import dispatch table. Tests (mcu_dissolve.rs): stack_pointer_globals_coalesced (3→2 globals, redirect verified), shared_stack_pointer_runtime_non_clobber (wasmtime: two providers' frames don't alias post-coalesce), dead_import_trampoline_export_dropped (dead "0" dropped; real/vtable/non-trampoline exports kept), conservative_no_coalesce_when_unsure (single-SP, differing-init, AND unnamed same-init → 0 coalesced). 457 lib + 4 integration green; clippy + fmt clean. Refs #334, SR-49, SYS-2, SYS-8. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mythos discover pass (manual) — done, one finding fixedAdversarial pass over Finding [MEDIUM] — fixed in this PR. The original SP-detection had an init-value fallback (when no module names Lower items noted (not blocking): mixed named/unnamed inputs under-coalesce (fails safe, no corruption); the whole-body re-rewrite couples to the rewriter's MVP-opcode whitelist (latent — no synthetic body emits a non-whitelisted op today). Both are fail-safe and can be follow-ups if they ever bite. 457 lib + 4 integration green post-fix; clippy + fmt clean. |
LS-N verification gate✅ 59/59 approved LS entries verified
Approved Failed LS entries(none) Missing regression tests(none) Updated automatically by |
Makes a
meld fuse --memory sharedwac-composed multi-provider node dissolve cleanly on an MCU (SR-49) — the "ergonomics gate" gale currently hand-works-around with WAT surgery, and the companion to #326.A post-merge pass (
mcu_dissolve.rs, gated onSharedMemory, run before encoding so DWARF/attestation stay consistent) does two fixups:__stack_pointerglobals into one shared shadow stack. Leaving them separate is unsound (providers descending from the shared-memory top clobber each other's frames on a cross-component call) and blocks synth's shadow-stack rebasing (synth#707). Keep one survivor, redirectglobal.get/global.setof the duplicates via the existing rewriter machinery, renumber survivors down, drop extras."0"export over an unreachable, import-dispatchedcall_indirect) so synth DCEs it instead of loud-declining (synth#642/#676).Conservative detection (the risk area)
SP coalescing uses only the authoritative
name-section signal (__stack_pointer). An init-value fallback heuristic was rejected during this branch's Mythos pass — it could fuse two unrelated mutable-i32 globals in different components that merely share an init value, silently clobbering one component's writes. Absent the name, meld does not guess. Trampoline drop requires numeric name + strict body shape + unreachable + import dispatch table.Tests (457 lib + 4 integration green; clippy + fmt clean)
stack_pointer_globals_coalesced— 3→2 globals, exactly one SP remains, redirect + renumber verified.shared_stack_pointer_runtime_non_clobber— wasmtime: pre-coalesce the nested provider call clobbers the caller's frame; post-coalesce it doesn't.dead_import_trampoline_export_dropped— dead"0"dropped; real/vtable/non-trampoline exports kept.conservative_no_coalesce_when_unsure— single-SP, differing-init, AND unnamed same-init all → 0 coalesced.Falsification
If SP coalescing regressed,
shared_stack_pointer_runtime_non_clobber(frames alias) fails. If detection became unsafe,conservative_no_coalesce_when_unsure(unnamed same-init coalesced) fails. If the trampoline drop hit a live export,dead_import_trampoline_export_dropped(kept exports) fails.Refs #334, SR-49, SYS-2, SYS-8, synth#707.
🤖 Generated with Claude Code