From 223500ab09f79a68eeff71594c54302b66715e2b Mon Sep 17 00:00:00 2001 From: Evan Reichard Date: Tue, 21 Jul 2026 16:14:22 -0400 Subject: [PATCH] test(web): seed SD before autoboot; document upstream store-TCI root cause The file browser no longer auto-seeds, so the headless test seeds Test/example.txt into OPFS before autoboot. Docs corrected: store-containing-TB TCI is upstream qemu-wasm design (compiled store path is not rewind-safe), not a port bug. --- ISSUES.md | 8 +++++--- docs/wasm-inflate-panic.md | 23 +++++++++++++++++++++++ tests/browser_raw_sd.py | 16 ++++++++++++++++ third_party/qemu | 2 +- 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/ISSUES.md b/ISSUES.md index febe249..cc5d3e5 100644 --- a/ISSUES.md +++ b/ISSUES.md @@ -8,11 +8,13 @@ Opening `/Test/example.txt` through QEMU `vvfat` could corrupt coroutine/guest s `ssi-sd` consumed a write-data token while leaving response mode, then parsed payload bytes as commands. The adapter now enters token mode immediately after the final R1 byte. SPI CMD13 also uses the upstream-correct `sd_r1` internal response so status polling no longer loops on `Unexpected response to cmd 13`. -## Worked around: WASM reader panic in uzlib font decompression +## Upstream limitation: compiled wasm32 store path (perf ceiling) -Opening a book panicked **only in WASM** (`Invalid read ... reason: rejected` → Load access fault) inside `uzlib_uncompress` during the reader's font `prewarmCache`. Native QEMU from the same submodule decompresses the identical static flash font and renders text, so it is a wasm32 TCG backend miscompile, not firmware/SD. +Opening a book panics **only in WASM** (`Invalid read ... reason: rejected` → Load access fault) inside `uzlib_uncompress` during the reader's font `prewarmCache` **if** compiled guest stores are enabled. Native QEMU from the same submodule renders fine. -Worked around by forcing store-containing TBs to TCI (`wasm_tci_only_tb = true` in `tcg_out_qemu_st`). Reader now opens and paints in the browser. This is a workaround with a perf ceiling (store-containing TBs run interpreted); the precise miscompiled op is not yet localized. Full investigation and next steps: `docs/wasm-inflate-panic.md`. +Root cause (confirmed by full diff against pristine upstream): `wasm_tci_only_tb = true` in `tcg_out_qemu_st` is **upstream qemu-wasm's own design**. Upstream forces every store-containing TB to the TCI interpreter because its compiled restartable-block store path is not rewind-safe (a yielded softmmu helper replays the block; safe for loads, corrupting for stores). This is the dominant cost of book open (font inflate is store-heavy, so the hot loop never JIT-promotes). + +The real fix is to make compiled stores rewind-safe and drop the blanket TCI — a substantial TCG-backend task, ~15 min per browser iteration, no native repro. Full analysis: `docs/wasm-inflate-panic.md`. ## Resolved: reader grayscale rendered inverted and muddled diff --git a/docs/wasm-inflate-panic.md b/docs/wasm-inflate-panic.md index 00169ed..a73a506 100644 --- a/docs/wasm-inflate-panic.md +++ b/docs/wasm-inflate-panic.md @@ -14,6 +14,29 @@ Guru Meditation Error: Core 0 panic'ed (Load access fault). Exception was unhand MEPC : 0x4201e210 RA : 0x4201e18c SP : 0x3fcb3810 ``` +## Root cause (established) + +`wasm_tci_only_tb = true` in `tcg_out_qemu_st` is **upstream qemu-wasm's own +design**, not a workaround this project invented. A full diff of our +`tcg/wasm32/tcg-target.c.inc` against pristine upstream qemu-wasm shows the two +files are identical except for that one line. Upstream unconditionally forces +every store-containing TB to the TCI interpreter, so upstream's *compiled* +(JIT) store path is emitted but never executed — it is effectively unfinished, +untested dead code, and it corrupts guest state the moment it runs. + +Mechanism: the wasm32 backend compiles a TB as a chain of wasm blocks guarded +by `if (BLOCK_PTR <= block_idx)`. When a softmmu slow-path helper yields +(asyncify unwind for I/O / interrupt), the wasm function returns and re-enters, +skipping already-executed blocks via `BLOCK_PTR`. This restart is safe to +replay for loads but not for a compiled store, so upstream punts all store TBs +to TCI (which has precise instruction-level checkpointing). + +The real fix is therefore not a port bug hunt but completing upstream's +compiled store path to be rewind-safe (e.g. defer the memory commit past the +TB's yield points / buffer stores until block commit), then dropping the +blanket TCI. Large TCG-backend task; ~15 min per browser iteration; no native +repro. + ## Established facts - **Fault site (via same-version local ELF addr2line, not byte-identical):** diff --git a/tests/browser_raw_sd.py b/tests/browser_raw_sd.py index 894b6d2..fece0e9 100644 --- a/tests/browser_raw_sd.py +++ b/tests/browser_raw_sd.py @@ -115,6 +115,22 @@ try: '?firmware=/_scratch/crosspoint-reader-1.4.1-release-firmware.bin' f'&variant=x3{suffix}', ) + # Seed the SD card in OPFS before autoboot; the app no longer auto-seeds. + base = url.split('?')[0] + driver.get(base) + seed = driver.execute_async_script(''' + const done = arguments[arguments.length - 1]; + import('./sdstore.js').then(async m => { + const lorem = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'; + const nl = String.fromCharCode(10, 10); + const text = Array(10).fill(lorem).join(nl); + await m.clearSdFiles().catch(() => {}); + await m.writeSdFile('Test/example.txt', text); + done('ok'); + }).catch(e => done('err: ' + e)); + ''') + if seed != 'ok': + raise RuntimeError(f'failed to seed SD card: {seed}') driver.get(url) confirm = driver.find_element(By.CSS_SELECTOR, '[data-button="confirm"]') diff --git a/third_party/qemu b/third_party/qemu index 962fc6e..7cab93e 160000 --- a/third_party/qemu +++ b/third_party/qemu @@ -1 +1 @@ -Subproject commit 962fc6e71d8e33706a5e29c97f75bc5065362ebf +Subproject commit 7cab93ede7ade918756adb014464507ec9241169