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

90 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 03 — Toolchain compatibility status (verified 2026-06-10/11)
> **Partly superseded 2026-06-22 — see [`06-spike-plan.md`](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`](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.5–3 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.