From c608b65ef5e8bf036524c56cebdb22256d64a3f2 Mon Sep 17 00:00:00 2001 From: Evan Reichard Date: Wed, 22 Jul 2026 21:43:12 -0400 Subject: [PATCH] wasm-emu: rule out store redispatch path; document data-race conclusion (E17) ?yieldtrace shows the crash occurs with zero compiled-TB DONE_FLAG resume re-entries, so it is not the store block-replay path. Combined with nondeterminism and identity-independence, the corruption is a guest-RAM data race with concurrent device/timer callbacks. Blanket store-TCI stays default. --- docs/wasm-inflate-panic.md | 36 ++++++++++++++++++++++++++++++++++++ third_party/qemu | 2 +- web/app.js | 1 + 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/docs/wasm-inflate-panic.md b/docs/wasm-inflate-panic.md index 4b4e967..a2302f4 100644 --- a/docs/wasm-inflate-panic.md +++ b/docs/wasm-inflate-panic.md @@ -501,3 +501,39 @@ consistent with a torn/stale store of a pointer-sized value under a race. `?sthash` and `?stcw/stcmask/stcval` are opt-in debug tools; the default build path is unchanged (blanket store-TCI). The narrowed 32-bit-only fallback remains rejected as a default because it only lowers, not removes, the race probability. + +## E17: not the store block-replay/redispatch path + +Added opt-in `?yieldtrace`: the dispatch loop counts compiled-TB "yield/resume" +re-entries, defined as a compiled TB returning non-zero with the *same* `tb_ptr` +and `do_init == 0` (the `DONE_FLAG`-driven redispatch/resume path; chains set +`do_init = 1`, completion sets `tb_ptr = 0`). + +Result over three crashing `?nostoretci` runs: **all crashed at `0x4201e210`, +zero yield re-entries**. So the store-specific block-replay-via-redispatch +mechanism (upstream's stated reason for blanket store-TCI) does not fire during +this workload. That path is not the trigger here. + +Caveat: Asyncify-based unwinds (coroutine yields) restore the whole C stack and +are transparent to this counter, so a coroutine-mediated replay is not excluded. +But the store `DONE_FLAG` redispatch path is definitively ruled out. + +### Consolidated conclusion +The `0x4201e210` corruption is: +- nondeterministic (same config crashes/passes across runs), +- independent of which specific store TBs are compiled, +- gated only by having enough compiled stores of both widths (a timing knob), +- not the store `DONE_FLAG` redispatch/replay path. + +The consistent remaining explanation is a **data race on guest RAM** between the +CPU worker (compiled `i64.storeN`) and another agent that runs concurrently — +main-thread `QEMUTimer`/device-model callbacks (SPI/SD/GDMA/cache) reading or +writing guest DRAM. TCI's C store path masks it by changing timing/serialization, +not by being semantically different. This matches upstream qemu-wasm shipping +blanket store-TCI rather than a targeted fix. + +A real fix is a threading-correctness project: ensure guest-RAM accesses from +device/timer callbacks are properly serialized against vCPU stores (BQL/memory +barriers), or run device memory access on the vCPU thread. Opt-in repro/debug +controls now available: `?nostoretci`, `?sthash`, `?stcw/stcmask/stcval`, +`?yieldtrace`. Production default remains blanket store-TCI. diff --git a/third_party/qemu b/third_party/qemu index a18a299..9e91849 160000 --- a/third_party/qemu +++ b/third_party/qemu @@ -1 +1 @@ -Subproject commit a18a2991c681fb967b096b895c51f0eff4da1ddc +Subproject commit 9e9184949d44669a52307e28641c8a6af3c7cbe5 diff --git a/web/app.js b/web/app.js index 566c297..fb42ab9 100644 --- a/web/app.js +++ b/web/app.js @@ -493,6 +493,7 @@ async function boot(file, variant, setStatus) { } else if (params.has('tcgdiff2')) options._wasm_diff_enable2(); else if (params.has('tcgdiff')) options._wasm_diff_enable(); if (params.has('nostoretci')) options._wasm_disable_store_tci(); + if (params.has('yieldtrace')) options._wasm_enable_yield_trace(); 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');