| Filename | Latest commit message | Latest commit date |
|---|---|---|
Two research dossiers under docs/features: - async/: Asyncify currData contention — the clipboard crash and post-idle tool hang as one disease (single global suspension slot, three producers), asyncify internals walkthroughs, prior art, arbiter designs A/B. - wasm-exceptions/: -fexceptions vs -fwasm-exceptions — measured invoke-driven share of the asyncify tax on pcbnew (59% raw / 64% gzip; download 64.5->36 MB if migrated), KiCad dialog-in-catch audit (85 direct / 93 review of 636, catch_audit.py included), binaryen toolchain status (partial EH support since v125; TryTable unsupported), and a catch-arm-hoisting pre-pass design that would obsolete the KiCad-side refactor. Cross-linked with the parked KICAD_WASM_EH end-to-end experiment (docs/wasm-exceptions-experiment.md). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
| .. | ||
| 01-background-two-eh-models.md | ||
| 02-measurements.md | ||
| 03-toolchain-status.md | ||
| 04-kicad-audit.md | ||
| 05-asyncify-fork-design.md | ||
| audit-results.txt | ||
| catch_audit.py | ||
| README.md | ||
-fexceptions vs -fwasm-exceptions in KiCad-WASM — research dossier
Status: research / decision record. A parallel session attempted the migration end-to-end and parked it on an emscripten-4.0.2 LLVM codegen bug — see
docs/wasm-exceptions-experiment.md(full build-plumbing patch preserved in its appendix; flags-fwasm-exceptions -sSUPPORT_LONGJMP=wasm -sWASM_LEGACY_EXCEPTIONS=0, raw linked pcbnew 92 MB and zeroinvoke_*/dynCall). This dossier is the research companion: mechanism, measurements, audit, and the asyncify catch-block design. Authored 2026-06-11/12. Companion todocs/features/async/(the AsyncifycurrDatacontention dossier) — this dossier covers the exception-handling axis of the same machine.
Why this exists
The whole build is on -fexceptions (Emscripten's JavaScript-based exception
handling). That choice is not cosmetic — it is the single largest driver of our Asyncify
cost: it forces env.invoke_* into ASYNCIFY_IMPORTS, which makes nearly the whole call
graph "suspension-capable" and therefore instrumented. We measured the consequence on
our own binary: 59% of the raw asyncify tax (64% of the gzipped tax) on pcbnew exists only
because of the invoke machinery. Migrating to native -fwasm-exceptions would cut the
shipped pcbnew download from 64.5 MB to ~36 MB (−44%) and the module from
187 MB to ~122 MB (−35%), plus an unmeasured-but-real runtime win on every try-region
hot path.
The migration is currently blocked by one upstream limitation — Binaryen's Asyncify pass cannot handle a suspension inside a catch handler — and KiCad triggers exactly that pattern (modal error dialogs from catch blocks) in 85 audited places. This dossier records the mechanism, the measurements, the toolchain status, the audit, and a concrete fork design (catch-arm hoisting) that would remove the blocker without refactoring KiCad at all.
TL;DR / decision
- Stay on
-fexceptionsfor now. Asyncify stays under any design (fibers + EM_ASYNC_JS have no wasm-EH replacement); this is purely about how much it must instrument. - The prize is measured, not estimated: −44% download, −35% module size (see 02).
- Binaryen merged partial asyncify+wasm-EH support in v125 (2025-11-19). Our emsdk bundles v121 — we don't even have the partial support locally.
- The remaining hole — unwind-from-catch — is fixable with a bounded new Binaryen pass
(catch-arm hoisting, ~400–800 lines, 1–2 weeks, genuinely upstreamable; see 05). It is a
pre-pass before stock
--asyncify—Asyncify.cppitself needs zero changes, so "fork" overstates it (one added file;get-wasm-opt.shalready has the build-from-source deployment path). It obsoletes the 85-site KiCad refactor entirely. - Blocker ordering (learned from the parked experiment): the catch-block limitation
is blocker #2. Blocker #1 is an LLVM codegen bug in emscripten 4.0.2 (invalid
br_tablearity in OpenCASCADE code under wasm-EH) — needs an emsdk bump first. And the experiment had to force the exnref encoding (WASM_LEGACY_EXCEPTIONS=0, because 4.0.2's legacy encoding output doesn't even parse in Binaryen), which collides with the fact that Binaryen's asyncify has zeroTryTablesupport: the encoding choice after the emsdk bump decides which variant of the catch fix applies (see 03 §experiment, 05). - Trigger to act: when we are ready to invest ~2 weeks of toolchain work, or if upstream lands full support on binaryen #4470 first. Until then the Asyncify arbiter work (docs/features/async/) is the priority — it fixes shipping bugs and is needed either way.
Document index
| File | Contents |
|---|---|
01-background-two-eh-models.md |
How JS-EH (invoke_*) and wasm-EH actually work, and the three concrete couplings into our Asyncify machine. |
02-measurements.md |
Our controlled size experiment on pcbnew (methodology + numbers) and the published third-party benchmarks. |
03-toolchain-status.md |
Compatibility matrix: emcc checks, binaryen history (what merged in v125, what didn't), JSPI/fibers, setjmp/longjmp, mixing modes. |
04-kicad-audit.md |
The brace-matching catch-block audit: 85 direct / 93 review / 458 trivial of 636; libpng/libjpeg setjmp story; refactor effort if done by hand. |
05-asyncify-fork-design.md |
Asyncify.cpp internals, why catch arms are structurally hard, and the catch-arm-hoisting fork design with limits and effort. |
catch_audit.py |
The audit tool (re-runnable; suitable as a CI gate on the kicad submodule). |
audit-results.txt |
Full audit output incl. all 85 direct-suspend sites. |
Relationship to docs/features/async/
Independent axes of the same machine. The async dossier is about correctness (one global
Asyncify.currData shared by overlapping suspensions → crash/hang); this dossier is about
cost (how much code Asyncify instruments). Fixing one does not fix the other. Sequencing:
async arbiter first (shipping bugs), wasm-EH migration second (size/speed), and the
migration plan below assumes the arbiter exists.
Migration plan (when triggered)
- Resume the parked experiment (
docs/wasm-exceptions-experiment.md): bump emsdk past the 4.0.2 LLVMbr_tablebug,git applyits appendix patch (KICAD_WASM_EH=1gated), full clean deps rebuild (stamp-skip gotcha: stale sjlj objects in cairo etc.). Then decide the EH encoding: if newer LLVM emits parseable legacy encoding, the catch-arm-hoisting pre-pass (05) applies; if exnref stays forced, asyncify needsTryTablesupport + exnref spilling instead (05 §new-EH variant). - Newer Binaryen for the post-link step only:
scripts/common/get-wasm-opt.shalready abstracts the binary — point it at a ≥ v125 build carrying the hoisting patch (05). - Validate partial support first: rebuild one app
-fwasm-exceptions+ asyncify-asserts, run the e2e suites; the asserts tripwire makes any missed unwind-from-catch a deterministic trap. - Uniform flag flip:
-fexceptions→-fwasm-exceptionsinscripts/build-wxuniversal-wasm.sh:141-142,scripts/kicad/build-kicad-target.sh(lines ~203/207/211/214),tests/apps/Makefile.wasm(all occurrences) — plus-sSUPPORT_LONGJMP=wasm(default with wasm-EH; theemscriptenflavor is a hard error). - Drop
env.invoke_*fromASYNCIFY_IMPORTSinscripts/common/apply-asyncify.sh:33. - Expect to delete/shrink shim machinery that exists only for the invoke world
(
inject-dyncall-shims.shphases 1–2) — verify, don't assume. - Keep
catch_audit.pyas a CI gate only if shipping without the fork (i.e., the hand-refactor path); with the fork it is informational.