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.
This commit is contained in:
@@ -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.
|
||||
|
||||
Vendored
+1
-1
Submodule third_party/qemu updated: a18a2991c6...9e9184949d
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user