Skip to content

fix(decoder): float-typed global accesses loud-skip — the last silent float path (GI-FPU-001, #369)#648

Merged
avrabe merged 1 commit into
mainfrom
fix/369-float-loud-skip
Jul 8, 2026
Merged

fix(decoder): float-typed global accesses loud-skip — the last silent float path (GI-FPU-001, #369)#648
avrabe merged 1 commit into
mainfrom
fix/369-float-loud-skip

Conversation

@avrabe

@avrabe avrabe commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Closes the last silent-float gap of GI-FPU-001 (#369; the #619 frontier survey). jess tracks the arc as AFD-024.

Where GI-FPU-001 stood on main (verified per backend, v0.33.1)

PR #371 (decode loud-skip) + PR #556 (#554 single-function bail) already made every scalar float op loud. Verified on origin/main with a 12-module matrix (arith f32/f64, compares, trunc/convert, load/store, const, both reinterprets, float local, float global) × (ARM default, ARM --relocatable/direct, -b riscv, -b aarch64):

(module (global $g f32 (f32.const 2.5))
        (func (export "f") (result f32) (global.get $g)))
origin/main this PR
ARM default (-t cortex-m4f) rc=0, silent — 0x40200000 (2.5f) absent from the ELF, read returns 0.0; wasmtime: 2.5 loud-skip naming GlobalGet(0) … (GI-FPU-001)
ARM --relocatable (direct) rc=0, silent, same loud-skip, same diagnostic
-b riscv / -b aarch64 loud only by accident (GlobalGet itself is unsupported there) loud at decode, uniformly

Root cause: global.get/global.set are type-agnostic ops that decode fine, but init_i32 only captures a leading i32.const — an f32.const/f64.const initializer records None and the slot is zeroed (init_i32.unwrap_or(0)).

Fix

Both decode entry points (decode_wasm_module + the lighter decode_wasm_functions — the CLI compiles through both) track f32/f64-typed globals in the full index space (imported globals first) and flag any function whose GlobalGet/GlobalSet touches one, routing it through the existing #371/#556 loud-skip / honest-bail machinery. The two CLI diagnostics now also name GI-FPU-001 explicitly.

Oracles

  1. New decoder tests (red on origin/main): float-global get/set flags the function with a reason naming the op + GI-FPU-001; imported f64 global shifts the index space correctly; i32-global accesses stay unflagged. Existing arm backend uses soft-float for all f32/f64 — hardware FPU never used (cortex-m4f/m7/m7dp identical); blocks REQ-PIX-001 hard-float #369/-b aarch64 silently miscompiles f32 ops (emits 'mov w0,w1' / drops constants) instead of erroring like other unsupported milestone-1 ops #554 loud-skip tests untouched and green.
  2. Frozen anchors 10/10 byte-identical (frozen_codegen_bytes); the only float fixture in-tree is the -b aarch64 silently miscompiles f32 ops (emits 'mov w0,w1' / drops constants) instead of erroring like other unsupported milestone-1 ops #554 loud-skip repro.
  3. cargo test --workspace rc=0 (106 suites, 0 failures), cargo fmt --check clean, cargo clippy --workspace --all-targets -- -D warnings rc=0.

Sibling finding (out of scope, filing separately)

(global i64 (i64.const 4886718345)) + global.get is also silently zeroed — same init_i32-only mechanism, integer class (#643 landed pair get/set but not non-i32 initializers). Tracked in a follow-up issue.

What GI-FPU-002 (real VFP, #619) will need

Refs #369, #619, #554.

🤖 Generated with Claude Code

…inits were silently zeroed (GI-FPU-001, #369)

GI-FPU-001's decode-level loud-skip (PR #371) and the single-function
bail (#554, PR #556) cover every scalar float OP — but global.get/
global.set are type-agnostic ops that decode fine, and a float-typed
global's f32.const/f64.const initializer is never captured (init_i32
only decodes a leading i32.const), so its slot is silently zeroed.
Verified on main: (global f32 (f32.const 2.5)) + global.get compiled
rc=0 on BOTH ARM paths with the 0x40200000 bit pattern absent from the
output — the read returns 0.0, wasmtime returns 2.5. riscv/aarch64 were
already loud only by accident (GlobalGet itself is unsupported there).

Fix: both decode entry points (decode_wasm_module + the lighter
decode_wasm_functions) track f32/f64-typed globals in the FULL index
space (imported globals first) and flag any function whose GlobalGet/
GlobalSet touches one — routing it through the existing loud-skip /
honest-bail machinery. The two CLI diagnostics now also name GI-FPU-001.

Float locals/params remain compilable on purpose: without float ops
(all flagged) they are pure 4/8-byte bit moves — bit-correct, and loom
output may declare unused float locals.

Oracles: new decoder tests (float-global get/set flag + GI-FPU-001 in
the reason; imported-global index shift; i32 globals unflagged) — red
on origin/main; frozen anchors 10/10 byte-identical; workspace tests
rc=0; fmt + clippy -D warnings clean.

Refs #369, #619.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.18182% with 13 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/synth-core/src/wasm_decoder.rs 88.18% 13 Missing ⚠️

📢 Thoughts on this report? Let us know!

@avrabe avrabe merged commit b471c38 into main Jul 8, 2026
32 checks passed
@avrabe avrabe deleted the fix/369-float-loud-skip branch July 8, 2026 15:24
avrabe added a commit that referenced this pull request Jul 8, 2026
…age — decoder captures both words, startup materializes the R9 table (#652)

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>
avrabe added a commit that referenced this pull request Jul 8, 2026
…sound mask bounds (#648/#652/#653/#654) (#657)

Pin sweep 0.33.1 -> 0.34.0 + CHANGELOG.

Co-authored-by: Claude Fable 5 <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