pcbjam/docs/features/wasm-exceptions/03-toolchain-status.md
Viktor Vaczi c1ef489cfa feat(wasm-eh): migrate the WASM build to native wasm exceptions (+ 3D viewer default-on)
Replace the legacy Emscripten JS-exceptions model with native wasm-EH (legacy
encoding) across the whole build, keeping Asyncify coroutines working via a
from-source Binaryen --hoist-cpp-catches pre-pass. Net result: native-EH is the
only build mode, the 3D viewer is on by default, and pcbnew shrinks substantially.

Highlights:
- Binaryen submodule everywhere + --hoist-cpp-catches integration in apply-asyncify;
  post-link Asyncify covers every app wasm (not just standalone test wasm).
- Build deps (incl. OpenCASCADE without OCC_CONVERT_SIGNALS) and all KiCad apps
  with -fwasm-exceptions; emscripten_sleep added to the post-link asyncify-imports.
- libcontext fiber entry wired under native exceptions; while-loop main loop +
  currData shim injected into all wx apps.
- Native-EH collab apply fixed: DEBUG-define the embind TU + match all out-of-CMake
  C++ TUs' ABI flags to the core, fixing the vtable-layout skew / mis-dispatch.
- 3D viewer enabled by default (real raytracer linked, not the stub).
- Retire the EH-spike scaffolding; flip the asyncify-races ablation pins to
  shim-redundancy pins (native-EH stays clean with the legacy shims ablated).
- Fix the asyncify-races quiescence check to not require Asyncify.currData==0:
  under the native-EH per-frame-yield top loop the main stack is asyncify-suspended
  every frame, so currData legitimately churns (a freed-but-not-yet-nulled buffer,
  not a leak). Refresh the pcbnew toolbar screenshot baseline for the new kicad.
- CI: drop the obsolete binaryen_version input/env (the build uses the binaryen
  submodule fork's wasm-opt, not a version download); key the wasm-output cache on
  the binaryen submodule SHA instead.

Bumps the wxwidgets + binaryen submodules to their squashed feature commits.

Validated green: all 7 apps native-EH (real 3D in pcbnew); KiCad e2e 63/63
Firefox + Chromium (3D viewer renders); wx 336; coroutine 34/34 both engines;
asyncify 7/7 both engines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 09:40:26 +02:00

6.4 KiB
Raw Permalink Blame History

03 — Toolchain compatibility status (verified 2026-06-10/11)

Partly superseded 2026-06-22 — see 06-spike-plan.md. Corrections: (a) the host-side --asyncify already runs Binaryen v130 in CI/publish (BINARYEN_VERSION=130), so "we don't have the partial support locally" understates it — v121 is only the finalize/in-link copy. (b) --pass-arg=asyncify-ignore-unwind-from-catch is implemented (shipped v125), but it silently drops the suspend — a tripwire-silencer, not a fix. (c) The encoding is resolved to legacy (exnref + Asyncify is unsupported in every released Binaryen incl. v130, no roadmap), so the "encoding decision forks the asyncify work" framing in §experiment is closed: legacy + the catch-arm-hoisting pre-pass is the only path.

The compatibility matrix

Combination Status
ASYNCIFY=1 + -fexceptions (ours today) Supported; the expensive-but-working baseline
ASYNCIFY=1 + -fwasm-exceptions emcc emits a warning, not an error ("Parts of the program that mix ASYNCIFY and exceptions will not compile"); Binaryen support is partial — everything except unwinding from inside a catch arm (see 05)
SUPPORT_LONGJMP=emscripten + -fwasm-exceptions Hard error in emcc; wasm-EH pairs with SUPPORT_LONGJMP=wasm (automatic default)
JSPI (-sJSPI) + -fwasm-exceptions Compatible (JSPI doesn't transform the wasm) — but fibers do not exist under JSPI (Asyncify.setDataHeader is not a function, emscripten #18180), and KiCad tools are fibers → JSPI stays closed for us
Mixing -fexceptions and -fwasm-exceptions objects at link Not supported; produces inconsistent internal state (emscripten #20165, #18500) — flag flip must be uniform across wx + kicad + test apps

Binaryen history (the part that moved since our earlier research)

  • Partial asyncify+wasm-EH support merged upstream 2025-11-19, commit ad13362b "Add partial support for -fwasm-exceptions in Asyncify (#5343) (#5475)" — these are the caiiiycuk PRs from 2022/2023 (the author previously maintained the caiiiycuk/binaryen-fwasm-exceptions fork). Shipped in binaryen v125 (released the same day). Upstream is at v130 (2026-06-01).
  • The merge is +114 lines in src/passes/Asyncify.cpp plus 1,139 lines of lit tests (asyncify_pass-arg=asyncify-eh*.wast): Try-body traversal + an asserts-mode tripwire. Details and the remaining hole in 05-asyncify-fork-design.md.
  • Notable gaps in current main: the documented asyncify-ignore-unwind-from-catch pass-arg is not consumed anywhere in the code (dead docs); TryTable (standardized exnref EH encoding) is entirely unsupported by the pass; tail calls remain fatal.
  • Tracking issue for full support: WebAssembly/binaryen #4470 (open).

Our local toolchain

  • emsdk-bundled wasm-opt: v121 (version_121-72-g7353da707) — verified 612 commits behind the EH merge. We do not have even the partial support locally.
  • This only matters for the post-link step: scripts/common/apply-asyncify.sh resolves its binary via scripts/common/get-wasm-opt.sh, so a newer/patched wasm-opt can be slotted in for asyncify alone, without touching the emsdk compiler side. (apply-finalize.sh keeps using the emsdk wasm-emscripten-finalize; cross-version binary compatibility at the .wasm level is fine.)
  • The compiler side (LLVM emitting wasm-EH instructions) has been mature for years; the emcc warning about ASYNCIFY=1 is expected and survivable once the Binaryen side is fixed.

setjmp/longjmp inventory (often raised as a blocker — it is not)

  • KiCad: zero direct setjmp/libpng usage outside thirdparty/ (and thirdparty/ does not bundle libpng). All image I/O goes through wxImage.
  • wxWidgets bundles png, jpeg, tiff, zlib. Both libpng's pngerror.c and wx's own PNG handler use the classic pattern (src/common/imagpng.cpp:319/:527 setjmp, :204 longjmp); libjpeg's error manager likewise.
  • Today this rides JS-based SjLj (default SUPPORT_LONGJMP=emscripten, invoke machinery). Under wasm-EH it becomes SUPPORT_LONGJMP=wasm automatically, no source changes. Constraints don't bite: setjmp is not called from C++ catch clauses there, and the PNG/JPEG decode paths never suspend (pure computation → never asyncify-instrumented → the unwind-from-catch limitation cannot arise in them).

The parked end-to-end experiment (2026-06-11) — what it taught us

A parallel session actually built pcbnew with wasm-EH end-to-end (docs/wasm-exceptions-experiment.md; full plumbing patch in its appendix, gated behind KICAD_WASM_EH=1). Findings that supersede/extend the matrix above:

  • Working flag set: -fwasm-exceptions -sSUPPORT_LONGJMP=wasm -sWASM_LEGACY_EXCEPTIONS=0, uniformly on every compile and link (deps incl. boost/cairo/harfbuzz, wx, kicad).
  • Result: links clean, raw pcbnew 92 MB (vs 338 MB post-asyncify pre-O2 today), zero invoke_*/dynCall — corroborates this dossier's measurements from the compiler side.
  • Blocker #1 (before the catch issue ever arises): emscripten 4.0.2's LLVM emits an invalid br_table (label arity mismatch) in OpenCASCADE (ShapeUpgrade_SplitSurface::Build) under wasm-EH — module malformed at the source; no Binaryen can fix it. Resume = emsdk bump (5.0.7+), then full clean rebuild (~2.53 h).
  • Encoding wrinkle: 4.0.2's legacy encoding output failed Binaryen parsing (popping from empty stack), forcing WASM_LEGACY_EXCEPTIONS=0 (exnref). But the asyncify pass has zero TryTable support (05 §2) — so after the emsdk bump, the encoding decision forks the asyncify work: legacy → v125 partial support + the catch-arm-hoisting pre-pass; exnref → TryTable support + exnref spilling (05 §new-EH).
  • Ops gotchas recorded there: deps scripts skip-if-stamped (flag changes don't trigger rebuilds — undefined symbol: emscripten_longjmp from stale libcairo.a); CI already self-builds Binaryen v130 for the post-link chain; build-time payoff is also large (the ~52 min -O2 pass shrinks with the module).

Browser support notes

wasm-EH (legacy encoding) is supported across modern Chrome/Firefox/Safari. One watch item from research: a Safari 26.0 regression crashing -fwasm-exceptions apps at startup was reported September 2025 (emscripten #25365) — re-check status at migration time.