Skip to content

fix(elf): #354 — per-region .bss/.data split for high-offset init segments - #356

Merged
avrabe merged 1 commit into
mainfrom
fix/synth-354-per-region-bss-split
Jun 14, 2026
Merged

fix(elf): #354 — per-region .bss/.data split for high-offset init segments#356
avrabe merged 1 commit into
mainfrom
fix/synth-354-per-region-bss-split

Conversation

@avrabe

@avrabe avrabe commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

What

Fixes gale #354: under --native-pointer-abi --relocatable, an initialized (data) segment at a high linmem offset defeated the #345 .bss split, 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:

  • 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).

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_data reloc is the retargetable Abs32 form. Else → fall back to one-PROGBITS (fat but always correct). Selector untouched.

Verification (all grounded in tool output)

  • Kill-criterion: 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 + objdump -s). New unit test mixed_high_offset_segment_splits_per_region_354.
  • End-to-end: 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).
  • Byte-IDENTICAL to main: control_step (non-native) + mutex_pressure (dissolved --relocatable objects carry full wasm linmem (64KB .data) + absolute MOVW relocs — MCU-unshippable + link-fragile (gale mutex silicon fault) #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.

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 .data bounded + 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

…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>
@avrabe

avrabe commented Jun 14, 2026

Copy link
Copy Markdown
Contributor Author

Correction (independent clean-room verification, run before merge): the PR body mislabels one regression fixture. scripts/repro/mutex_pressure.wat is the #326 register-exhaustion (arg-move cycle) repro — it has no (memory)/(data), so it is not "the #345 all-zero split." Its byte-identity result still stands (it's unchanged — it has no linmem to affect), but the label was wrong.

The #345 all-zero (NOBITS .bss, no mixed-split) regression is correctly verified on the proper repro scripts/repro/native_pointer_bss.wat: byte-identical branch vs main, .bss NOBITS 4 B / .data 0 B — confirmed independently, plus the passing native_pointer_zero_linmem_lands_in_nobits_bss_345 unit test.

All substantive claims hold (clean-room CONFIRMED claims 1–4, 6, 7): .data 65552→16 B, .bss NOBITS; reloc→__synth_wasm_seg_0; in-place addend rewritten to 8; native_pointer_shadow_stack differential ORACLE PASS end-to-end. The fix does not regress the all-zero path.

@avrabe
avrabe merged commit ea5ee43 into main Jun 14, 2026
13 of 14 checks passed
@avrabe
avrabe deleted the fix/synth-354-per-region-bss-split branch June 14, 2026 17:36
@codecov

codecov Bot commented Jun 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.79006% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/synth-cli/src/main.rs 97.79% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

avrabe added a commit that referenced this pull request Jun 14, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant