fix(elf): #354 — per-region .bss/.data split for high-offset init segments - #356
Conversation
…ments The #345 zero-init split was binary (split_linmem_bss = native_layout && data_segments.is_empty()): ANY initialized (data) segment fell to the one-PROGBITS arm, so a small .rodata const at a HIGH linmem offset (gale's stack_push: a 12-byte -ENOMEM const at 65536, above the 64 KiB shadow stack) dragged the whole zero gap into a 65552-byte PROGBITS .data — MCU-unshippable. Add a third "mixed-split" case in build_relocatable_elf using per-region SYMBOLS (link-survivable, unlike a naive per-section split — a single __synth_wasm_data base + selector-baked addends can't span independently-placed sections): - zero reservation -> NOBITS .bss (__synth_wasm_data = 0) - each init segment packed into a small PROGBITS .data under its own __synth_wasm_seg_K symbol - every __synth_wasm_data + C static reloc whose addend C lands in segment K is retargeted to __synth_wasm_seg_K + (C - seg_off_K): both the symbol AND the in-place REL addend word in .text (R_ARM_ABS32 is S+A, A in the word). SAFE-BY-CONSTRUCTION GATE: fire only when every init segment sits at offset >= wasm_data_base (the SP-global init — the same boundary the selector uses for static-vs-frame) AND every __synth_wasm_data reloc is the retargetable Abs32 form. The shadow stack is reached only via the SP register value (dynamic, never a static reloc with an addend in that range), so per-region symbols can't mis-address anything the selector doesn't already assume separable. If the gate fails, fall back to the existing one-PROGBITS arm (fat but always correct). This is the mixed-case deferred in the #345 code comment coming due — NOT a v0.11.44 regression; #350 just made stack_push compile far enough to expose it. Verification: - scripts/repro/high_offset_init_segment_354.wat: .data 65552 -> 16 B; .bss 65548 NOBITS. Reloc retargets to __synth_wasm_seg_0; in-place addend rewritten 65544 -> 8 (readelf -r/-s/-x confirmed). New unit test mixed_high_offset_segment_splits_per_region_354. - native_pointer_shadow_stack (a 2nd bug instance): 4104 B PROGBITS .data -> .bss=4100 NOBITS + .data=8; differential ORACLE PASS (const 42 loads via the retargeted symbol). - Byte-IDENTICAL to main: control_step (non-native) + mutex_pressure (#345 all-zero split). Four frozen differentials PASS (control_step 0x00210A55, flight_seam 0x07FDF307, div_const 338/338, mutex_pressure). - synth-cli suite 27+32 green; fmt + clippy -D warnings clean. On-target gate (gale): stack/msgq .data bounded + stack_pop shim+silicon, to run when v0.11.45 tags. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Correction (independent clean-room verification, run before merge): the PR body mislabels one regression fixture. The #345 all-zero (NOBITS All substantive claims hold (clean-room CONFIRMED claims 1–4, 6, 7): |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
…p + changelog) (#357) Feature release bundling gale #354 (#356): native-pointer mixed-memory modules (stack/msgq decides) now ship a bounded .data instead of a 64 KiB PROGBITS blob — per-region .bss/.data split with per-region symbols + in-place REL addend retargeting in build_relocatable_elf (selector untouched; safe-by-construction gate on offset >= wasm_data_base + Abs32-only static relocs). Pin sweep: workspace.package 0.11.44 -> 0.11.45 across all 11 publishable crate manifests + path-dep pins + MODULE.bazel + Cargo.lock. rivet GI-NPA-004 -> implemented. Non-mixed paths byte-identical (control_step, native_pointer_bss); four frozen differentials green. On-target gate is gale's silicon run. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
What
Fixes gale #354: under
--native-pointer-abi --relocatable, an initialized(data)segment at a high linmem offset defeated the #345.bsssplit, shipping the whole 64 KiB zero gap as PROGBITS.data(MCU-unshippable). gale: stack_push.data=65552, msgq.data=65556.Root cause: the #345 split was binary —
split_linmem_bss = native_layout.is_some() && data_segments.is_empty()— so any init segment fell to the one-PROGBITS arm. This is the mixed-case the #345 code comment explicitly deferred coming due; not a v0.11.44 regression (#350 just made stack_push compile far enough to expose it).Fix — per-region symbols (link-survivable)
A third "mixed-split" case in
build_relocatable_elf:.bss(__synth_wasm_data=0).dataunder its own__synth_wasm_seg_Ksymbol__synth_wasm_data + Cstatic reloc whose addendClands in segment K is retargeted to__synth_wasm_seg_K + (C − seg_off_K)— both the symbol and the in-place REL addend word in.text(R_ARM_ABS32isS + A, A in the word).Per-region symbols (not just sections) make the sections independently placeable — a single base symbol + selector-baked addends can't span the link gap.
Safe-by-construction gate: fires only when every init segment sits at offset ≥
wasm_data_base(the SP-global init — the exact static-vs-frame boundary the selector already uses) and every__synth_wasm_datareloc is the retargetableAbs32form. Else → fall back to one-PROGBITS (fat but always correct). Selector untouched.Verification (all grounded in tool output)
scripts/repro/high_offset_init_segment_354.wat→.data65552 → 16 B,.bss65548 NOBITS. Reloc retargets to__synth_wasm_seg_0; in-place addend rewritten 65544 → 8 (readelf -r/-s+objdump -s). New unit testmixed_high_offset_segment_splits_per_region_354.native_pointer_shadow_stack(a 2nd bug instance: 4104 B PROGBITS →.bss=4100 NOBITS +.data=8) — differential ORACLE PASS (const 42 loads via the retargeted symbol).0x00210A55, flight_seam0x07FDF307, div_const 338/338, mutex_pressure).-D warningsclean.Falsification: wrong if a native-pointer mixed module still emits a 64 KiB PROGBITS
.data, or the retargeted addend/symbol mis-resolves the const, or any frozen differential changes. Closing gate is gale's on-target run (stack/msgq.databounded + stack_pop shim+silicon) when v0.11.45 tags — synth-side is not marked verified alone.Tracks rivet GI-NPA-004. Target release: v0.11.45.
🤖 Generated with Claude Code