fix(globals): #649 i64.const global initializers reach the emitted image — decoder captures both words, startup materializes the R9 table#652
Merged
Conversation
…age — decoder captures both words, startup materializes the R9 table The decoder's `init_i32` captured only a leading `i32.const`; an i64 global's init decoded to `None` and every consumer's `unwrap_or(0)` silently ZEROED the slot. #645 fixed the get/set PAIR lowering and the width-aware slot layout, but a nonzero `(global (mut i64) (i64.const X))` read before any set still returned garbage — the init existed nowhere in the output. Decoder: `WasmGlobal.init_i32` is RENAMED to `init: Option<GlobalInit>` (I32/I64) so no consumer survives by wildcard. f32/f64 inits stay `None` on purpose — float global access is the GI-FPU-001 (#369/#648) loud-skip lane and capturing bits here must not quietly unskip it. Consumers audited (all of them, post-rename): - `identify_stack_pointer_global`: matches `GlobalInit::I32` only (an SP is an i32 address; i64 inits are data, never promoted). - `--native-pointer-abi` `__synth_globals` region: stays 4-byte i32 slots; wide globals are already REFUSED up front (#643 bail — the decline-loudly arm), so only I32 inits can land. Byte-identical for i32-global modules (verified against origin/main). - Self-contained Cortex-M image (the #649 repro shape): globals were never materialized AT ALL — R9 was never even initialized. Now `generate_minimal_startup` gets the table words (#643 summed-width layout, i64 = two words lo-first), points R9 at `0x2000_0000 + linmem_size`, and stores every word before calling the user function; RAM sizing + the NoBits region cover the table. Both the all-exports and single-function builders. Tables past the STR.W #imm12 range decline loudly. No globals => the historical 28-byte startup blob and the whole ELF stay BYTE-IDENTICAL (verified). Red -> green: scripts/repro/i64_global_init_649_differential.py runs the default cortex-m4f image's REAL reset path in unicorn (artifact-derived, nothing fabricated), then the stateful export sequence vs one wasmtime instance. origin/main: FAIL (4 divergences — init reads return vector- table garbage). This branch: PASS (init reads, i32-after-i64 offset canary, #645 set-then-get pin, control). Gates: frozen anchors 10/10, #643 oracle PASS, cargo test --workspace green, fmt + clippy --workspace -D warnings clean. Fixes #649. Refs #645, #643, #648, #369. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
avrabe
added a commit
that referenced
this pull request
Jul 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #649. Lineage: #645 (i64 global get/set pair lowering + width-aware slot layout), #643, #648/#369 (GI-FPU-001 float loud-skip lane).
Mechanism
WasmGlobal.init_i32only decoded a leadingi32.const; ani64.constinit decoded toNoneand every consumer'sunwrap_or(0)silently ZEROED the slot. #645 made the accesses width-correct but a nonzero i64 init still never reached the output — worse, the default self-contained image never materialized a globals table at all (R9 was never initialized; reads hit whatever R9=0 pointed at).Fix
Decoder (
synth-core/wasm_decoder.rs):init_i32: Option<i32>→init: Option<GlobalInit>withI32(i32)/I64(i64)— a rename, so the compiler finds every consumer (no wildcard survives). f32/f64 inits deliberately stayNone: float global access is the GI-FPU-001 loud-skip lane (#648) and capturing bit patterns here must not quietly unskip it.Consumers audited (all of them, forced by the rename):
identify_stack_pointer_global(#237 SP promotion)GlobalInit::I32only — an SP is an i32 address; i64 inits are data, never promoted--native-pointer-abi__synth_globalsslots (NativeGlobalsLayout, all data/bss write sites inbuild_relocatable_elf)build_multi_func_cortex_m_elf+ single-functionbuild_cortex_m_elf)generate_minimal_startupreceives the table words (#643 summed-width layout, i64 = two words lo-first), setsR9 = 0x2000_0000 + linmem_size, and stores every captured init word (via the R12 scratch — reserved, and no user code has run yet) beforeBLX; RAM sizing + the NoBits phdr/section cover the table; tables past theSTR.W #imm12range decline loudly--relocatable(host-owned R9 table)No globals ⇒ the historical 28-byte startup blob and the whole ELF stay byte-identical (
cmpvs an origin/main build: identical; frozen anchors 10/10).Red → green
scripts/repro/i64_global_init_649_differential.py+i64_global_init_649.wat— the DEFAULTcortex-m4fimage (the exact #649 verification shape), fully artifact-derived: unicorn executes the image's real reset path up to the call scaffold (the harness fabricates nothing), then runs the stateful export sequence against one wasmtime instance. Symbols from the ELF symtab, never disasm text.get_lo→0x20020000,get_hi→0x81(vector-table garbage through R9=0), i32 canary wrong0x20010000; init reads0x9abcdef0/0x12345678/0xc0ffee1before any set; i32-declared-after-i64 offset canary at the summed-width slot; fix(globals): #643 thumb-2 i64 global.set/get — register-pair lowering + type-aware slot layout #645 set-then-get pin still greenGates
--native-pointer-abii32 output byte-identical, wide-global refusal still loudcargo test --workspacegreen (new: decoder i64-init capture test, startup-materializer byte test, table-layout test)cargo fmt --check+cargo clippy --workspace --all-targets -- -D warningsclean🤖 Generated with Claude Code