From 1406ab6d26b019f40e04268b252549db5882a066 Mon Sep 17 00:00:00 2001 From: Evan Reichard Date: Thu, 23 Jul 2026 06:59:21 -0400 Subject: [PATCH] wasm-emu: document state materialization probes (E20-E22) --- docs/wasm-inflate-panic.md | 58 ++++++++++++++++++++++++++++++++++++++ tests/browser_raw_sd.py | 2 ++ third_party/qemu | 2 +- web/app.js | 3 ++ 4 files changed, 64 insertions(+), 1 deletion(-) diff --git a/docs/wasm-inflate-panic.md b/docs/wasm-inflate-panic.md index 1204520..835293a 100644 --- a/docs/wasm-inflate-panic.md +++ b/docs/wasm-inflate-panic.md @@ -616,3 +616,61 @@ vCPU execution state/translation, not an unmapped legitimate pointer. Production default remains blanket store-TCI. The next useful probe is not more IO-thread DRAM logging; it is targeted vCPU-state/TLB-entry instrumentation at the failing translated block or around async exit handling. + + +## E20-E22: state materialization probes + +E11 had already identified the first live divergence after arming at open: +`pc=0x4201d6ea` in `utf8NextCodepoint()`, where TCI had applied the prologue +`sp -= 16` to `CPUArchState` but compiled-live still had stale `env->gpr[2]`. +The disassembly confirms `0x4201d6d6..0x4201d6e8` is exactly the function +prologue and first branch: + +```text +4201d6d6: addi sp,sp,-16 +4201d6d8: sw s2,0(sp) +... +4201d6e4: lbu s1,0(s2) +4201d6e8: beqz s1,4201d7a8 +4201d6ea: mv s0,a0 +``` + +### E20: no-chain boundary probe + +Added opt-in `?stateflush=open`, armed by the browser harness immediately before +opening `example.txt`. It reuses the existing `wasm_diff_needs_nochain_at(pc)` +app-flash window to force app TBs to return to C without running the expensive +compiled-vs-TCI differential shadow. + +Result with `?nostoretci&stateflush=open`: original corruption still reproduced +(`Invalid read 0x3FCB80A6`, `MEPC 0x4201e210`). Returning to C between app TBs +is not enough; the wasm backend does not materialize `CPUArchState` merely by +leaving a compiled TB. + +### E21/E22: blind helper spill attempts are invalid + +Tested a direct backend fix candidate: spill TCG global-mem temps before C helper +calls so slow-path load/store helpers and exceptions see coherent architectural +state. + +- spilling all global-mem temps before+after helpers caused an earlier invalid + read around `0x07585d82`; +- spilling before helpers only caused the same failure; +- narrowing to RISC-V integer GPR globals (`xN/...`) still caused immediate + invalid writes around `0x075f....`. + +Conclusion: helper-boundary materialization is still the right suspect, but it +must respect TCG liveness/state. Blind backend spills at helper emission can +write stale globals back into `CPUArchState` and make corruption worse. The fix +belongs in the wasm backend's handling of TCG sync/liveness around calls (or in +targeted generated `sync_i32/i64` ops), not in an unconditional helper wrapper +spill. + +### Revised next step + +Trace the TCG liveness/sync ops for the divergent prologue TB and the later +slow-path helper TB: verify whether `la_global_sync()` marks `x2/sp` `TS_MEM` +across the relevant call/branch, and whether the wasm backend emits the +corresponding store. The observed bug is now specifically: **compiled globals and +`CPUArchState` are allowed to diverge, and the wasm backend exposes stale env to +helper/exception-visible code when store TBs stay compiled.** diff --git a/tests/browser_raw_sd.py b/tests/browser_raw_sd.py index 1c90501..3be1c18 100644 --- a/tests/browser_raw_sd.py +++ b/tests/browser_raw_sd.py @@ -169,6 +169,8 @@ try: driver.execute_script('window.xteinkArmTcgDiff2 && window.xteinkArmTcgDiff2()') if 'ramrace=open' in url: driver.execute_script('window.xteinkEnableRamrace && window.xteinkEnableRamrace()') + if 'stateflush=open' in url: + driver.execute_script('window.xteinkEnableStateflush && window.xteinkEnableStateflush()') open_start = time.monotonic() press(confirm, 'opening example.txt') open_seconds = time.monotonic() - open_start diff --git a/third_party/qemu b/third_party/qemu index ab9c582..346c789 160000 --- a/third_party/qemu +++ b/third_party/qemu @@ -1 +1 @@ -Subproject commit ab9c5823eecc988fa9bb1cc0fe1a6a08dd6e9e86 +Subproject commit 346c7898514487a3ad5ecee2f9d4d04b97069af0 diff --git a/web/app.js b/web/app.js index ef4936c..0483695 100644 --- a/web/app.js +++ b/web/app.js @@ -498,6 +498,9 @@ async function boot(file, variant, setStatus) { if (params.get('ramrace') === 'open') { window.xteinkEnableRamrace = () => options._wasm_enable_ramrace_trace(); } else if (params.has('ramrace')) options._wasm_enable_ramrace_trace(); + if (params.get('stateflush') === 'open') { + window.xteinkEnableStateflush = () => options._wasm_stateflush_enable(); + } else if (params.has('stateflush')) options._wasm_stateflush_enable(); if (params.has('sthash')) options._wasm_set_store_tci_hash(Number(params.get('sthash')) || 1); if (params.has('stcmask')) options._wasm_set_store_bisect(Number(params.get('stcw')) || 0, Number(params.get('stcmask')) >>> 0, (Number(params.get('stcval')) || 0) >>> 0); options.FS.mkdirTree('/bios');