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.
This commit is contained in:
2026-07-21 16:14:22 -04:00
parent 78f181e924
commit 223500ab09
4 changed files with 45 additions and 4 deletions
+5 -3
View File
@@ -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
+23
View File
@@ -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):**
+16
View File
@@ -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"]')