docs(wasm): E9 static analysis - found UNWINDING/DONE_FLAG bug; reframe root cause to register-value codegen

This commit is contained in:
2026-07-22 08:00:39 -04:00
parent 8e664f99a0
commit e0d94c24d2
2 changed files with 36 additions and 1 deletions
+35
View File
@@ -204,6 +204,41 @@ remaining paths are generated-wasm inspection of the BLOCK_PTR/register restart
sequence, or building per-op TCI. A deterministic reproducer exists (remove
`wasm_tci_only_tb`, open example.txt, faults at MEPC 0x4201e210, gen 16).
### E9 — static analysis of generated wasm (dump + wasm2wat)
Added a node-only dump hook (`wasm_dump_module`, tcg.c finalize) that writes the
first compiled store-TB and load-TB modules; `_scratch/dump-tbs.mjs` boots the
firmware under node far enough to emit them. `wasm2wat --enable-threads` gives
readable IR.
**Finding 1 (real bug, fixed-then-shelved):** the ld/st slow paths detect an
asyncify yield with the thread-global `DONE_FLAG` (ctx+20), while the general
call path uses `UNWINDING` (ctx+28), which is reset before each call and set
*only* by the coroutine switch. `DONE_FLAG` is set by *any* nested ld/st helper,
so a nested guest access inside a store/load helper that then yields makes the
outer op falsely read "completed" and swallow the unwind. Converting ld/st to
the `UNWINDING` mechanism is the correct fix. **But** rebuilding with compiled
stores + the UNWINDING fix still crashes identically (gen 16, same MEPC), so the
store helpers on this path never actually yield — this bug is latent here. Change
shelved (not shipped) to avoid touching the working compiled-load path without a
green test; recorded for a future upstream fix.
**Finding 2 (reframes the root cause):** combine E5 with the load result. E5
routes *every* store through the C softmmu helper (correct memory write) and
still crashes; loads share the identical restart/block structure and work. So
the corruption is **not** in the store data codegen, the restart machinery, or
the yield handling. What remains is that a store *writes* guest memory using an
address/value taken from guest registers (wasm globals). A compiled TB that
computes a wrong register value corrupts memory only through a store — a wrong
address fed to a *load* just reads harmless garbage. This matches every prior
result: emergent (depends which ops precede a store), deterministic, and
non-localizable by store region or width. The remaining hunt is a
register-producing op whose compiled codegen diverges from TCI, exposed
destructively via stores — which needs a compiled-vs-TCI per-TB register
differential, not more whole-TB gating.
Tooling retained: `wasm_dump_module` + `wasm_tb_had_store/load` tags +
`_scratch/dump-tbs.mjs`.
### Next options
1. **Ship E1 as a guarded workaround** — reader works now; measure real
navigation/page-turn perf before judging it unacceptable.