Commit graph pcbjam/scripts/common/inject-dyncall-shims.sh
Author SHA1 Message Date
Viktor Vaczi
14ca16cbd3 test(asyncify): red-green race harness + ablation flags, unwind-catch shim, spec tightening, decisions docs
The asyncify single-slot work, executed red-green (full ledger:
docs/features/asyncify-arbiter/redgreen.md; decisions record:
docs/features/async/07-decisions-and-outcome.md):

- tests/apps/standalone/asyncify-races/ + tests/asyncify/ + dedicated
  playwright config: 8 scenarios reproducing the KiCad asyncify failure
  family with the kicad-faithful startup topology (pre-park fiber swap →
  park throw through the live trampoline). Built in 3 variants; the
  SHIM_DISABLE_TRAMPOLINE_HEAL / SHIM_DISABLE_HANDLESLEEP ablation builds
  keep the historical hang and index-out-of-bounds crash reproducible
  forever (mutation-style pins for the existing shims).
- scripts/common/shims/handlesleep.js: catch the "unwind" park sentinel
  in the wakeUp path — when main's last pre-park suspension was a sleep,
  the main-loop park throw escaped through that sleep's promise reaction
  as an uncaught rejection (the calculator/gerbview console errors).
- scripts/common/inject-dyncall-shims.sh: SHIM_DISABLE_* ablation knobs.
- Spec tightening (the acceptance bar): 'uncaught exception: unwind'
  tolerance DELETED from pcbnew/eeschema specs; load-pcb gained a hard
  clean-console gate over 5 asyncify corruption signatures.
- wxwidgets pointer bump: modal LIFO resolvers, pump resolve-on-error,
  sync clipboard IsSupported (014f67e6c1).

Final state: asyncify suite 7/7, wx e2e 291/292 (1 skip), KiCad e2e 40
passed / 2 skipped with ZERO corruption signatures in any log across all
six apps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 17:02:59 +02:00
Istvan Matejcsok
680eb9dc15 perf(build): 4h05m -> 1h15m full CI build — docker caps, pipelined wasm-opt, Binaryen 130
Three orchestration fixes, validated end-to-end on the Hetzner ccx53
(run 27280051992, 1h14m41s vs 4h05m baseline, e2e identical):

- docker-compose CPU/memory caps were hardcoded to dev-Mac defaults
  (10 CPUs / 32G); now env-tunable via KICAD_DOCKER_CPUS/KICAD_DOCKER_MEM
  (CI exports nproc/110G — the 32-core runner was compiling on 10 cores).
- docker/build.sh: split build_app into compile_app (container) +
  postprocess_app (host-side dyncall shims + finalize + asyncify + -O2)
  and added KICAD_PIPELINE=1 mode that overlaps each tool's host-side
  wasm-opt with the next tool's container compile (max 2 concurrent
  postprocesses — pcbnew -O2 peaks ~34G RSS). Also: comma-separated app
  lists for cheap pipeline repros.
- get-wasm-opt.sh: Binaryen default 121 -> 130 (fixes the v121 -O2 lock
  convoy, ~9x) and BINARYEN_BUILD_FROM_SOURCE=1 support: the official
  x86_64-linux release tarballs (Alpine/musl, no LTO, assertions on) run
  asyncify 4x slower than a stock gcc -O3+LTO build with sha256-identical
  output; self-build takes ~5 min and is instantly repaid.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 07:48:46 +02:00
Gergő Törcsvári
b5b1a0ac05
fix(wasm): dynCall signature-mismatch fallback + eeschema collab wire converters
Two things, both verified in the real web app (two-tab eeschema collab).

1. dynCall crash fix (all apps) — scripts/common/shims/dyncall-binding.js.tmpl.
   Programmatic editor edits trapped with 'indirect call signature mismatch': the
   asyncify-instrumented wasmExports[dynCall_<sig>] trampoline does call_indirect with a
   stale type for some table indices (post-asyncify+O2) even though the table entry is
   valid. Proven by patching the built js: at the trap getWasmTableEntry(index) SUCCEEDS
   where the trampoline fails. Fix: the shim now catches the 'signature mismatch'
   RuntimeError and falls back to getWasmTableEntry; the Asyncify unwind sentinel and real
   exceptions re-throw, so instrumentation/unwind is untouched for normal calls. This
   unblocks ALL programmatic edits, not just collab (e.g. eeschema SCH_ITEM::Move).

2. eeschema collab apply converters (wasm/bindings/eeschema_embind.cpp).
   doApply now handles added-item construction (build the SCH_ITEM with the delta's uuid
   via const_cast — as the s-expr parser does — + commit.Add) and richer SCH_LINE
   serialization (start/end/layer) so wire edits reconstruct on the peer. Implemented for
   SCH_LINE (wires) + SCH_JUNCTION; other types log 'no converter for added type' and are
   skipped (next batch). eeschema re-enabled in the web app collab gate.

Tests: eeschema-collab.spec snapshot (green); apply/two-tab skipped — they no-op headless
because the e2e harness's kicadOpenFile returns false (OpenProjectFiles bails before
building the connectivity graph), so SCH_COMMIT::Push doesn't persist. Verified in-app.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:56:42 +02:00
Gergő Törcsvári
18a9de0449
fix(wasm): self-heal fiber trampoline so schematic load doesn't hang
The emscripten fiber glue gates Fibers.trampoline() on Fibers.trampolineRunning
and resets it at the end of its loop. At startup emscripten_set_main_loop(...,1)
throws "unwind" to establish the main loop, and KiCad does so from inside a tool
coroutine, so the throw propagates THROUGH the trampoline and skips the reset —
leaving the flag stuck true. Every fiber swap after startup then becomes a silent
no-op, so opening a schematic (SetScreen -> RunAction(selectionClear) -> fiber
swap) hangs forever with the editor stuck on "untitled".

Wrap the trampoline loop in try/finally (inject-dyncall-shims.sh section "3c") so
the flag is always reset. Add tests/kicad/eeschema-load.spec.ts, which opens a
small wires/junctions schematic via Module.kicadOpenFile and asserts the editor
title switches away from "untitled": it times out (RED) without the shim and
passes (GREEN) with it. Also add features/web-init/0002-url-regex-modal-followup.md
capturing the unrelated URL-detection wxRegEx modal surfaced once loading works.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 20:32:25 +02:00
Gergő Törcsvári
ddd959fbc2
fix(web): eeschema schematic open path — Asyncify/dynCall fixes + bump submodules
- inject-dyncall-shims.sh: add a dynCallLegacy -> wasmExports fallback so embind's
  generic dynCall path finds the DYNCALLS=1 trampolines. Without it, an Asyncify
  unwind/rewind through an embind call (kicadOpenFile -> OpenProjectFiles) died
  with "f is not a function".
- open-flow.ts: kicadOpenFile runs OpenProjectFiles under Asyncify, so its sync
  return is a falsy placeholder. Invoke it and poll the frame title for the load
  instead of trusting the return value, and never fall back to UI automation
  while the hook is in flight (it would re-enter the suspended Asyncify call).
- .gitignore: ignore .playwright-mcp scratch.
- bump kicad + wxwidgets submodules to the wasm schematic-open fixes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 20:32:24 +02:00
Viktor Vaczi
a4ad69412e fix(wasm): bind dynCall_* to real DYNCALLS=1 exports; refactor shim into files
The shim bound bare dynCall_* names to JS getWasmTableEntry() calls, bypassing
the asyncify-instrumented dynCall_* wasm trampolines that -sDYNCALLS=1 provides.
That broke Asyncify unwind/rewind through indirect calls -> "indirect call
signature mismatch" (caught every frame in Firefox; fatal renderer crash in
Chrome). Bind the bare names to wasmExports["dynCall_<sig>"] instead.

Result: the PCBnew "select draw lines" e2e is green in Firefox (tool selects and
draws, zero page errors). Dropped the fiber-stabilization block, the shipped
diagnostic block, and the exportCallStack JS hack (all compensated for the wrong
binding); shim shrank 521 -> ~250 lines.

- scripts/common/inject-dyncall-shims.sh: orchestrator only; injected JS extracted
  to scripts/common/shims/
- scripts/common/shims/dyncall-binding.js.tmpl: per-signature binding template
- scripts/common/shims/handlesleep.js: nested-Asyncify handleSleep fix (#9153)
- scripts/common/shims/diagnostics.js: logging-only, opt-in via SHIM_DIAGNOSTICS=1
- tests/package.json: add test:kicad:firefox / test:kicad:chrome scripts

Known issue (tracked separately): Chrome still renderer-crashes on the first
coroutine resume. Asyncify.doRewind replays the deep main-context call stack and
exceeds V8's execution-stack limit (Firefox tolerates the same wasm). Proper fix
is JSPI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 15:07:10 +02:00
Viktor Vaczi
9a04217788 wip: nested-asyncify fix, wxAuiToolBar registration, tests, research docs
Main-repo side of a multi-part WIP covering the KiCad WASM tool-selection
and nested-Asyncify work. Submodule commits are in kicad@f6e9239aaa
(libcontext hygiene) and wxwidgets@bb80f91e8b (auibar registration +
dialog diagnostics).

## scripts/common/inject-dyncall-shims.sh

Wrap Asyncify.handleSleep / allocateData to save-and-restore Asyncify.currData
around each EM_ASYNC_JS sleep. This fixes the nested Asyncify collision where
a fiber swap that fired during a modal's event loop clobbered currData, and
the modal's later doRewind used the fiber's buffer and hit "RuntimeError:
index out of bounds". Root cause documented as Emscripten Issue #9153
(wontfix upstream).

Diagnostic-rewind logging (forcedBottomOfCallStack, callStack traces) is
retained to help future debugging of Asyncify state corruption.

## tests/

- tests/playwright-kicad.config.ts: add `channel: 'chrome'` for the
  chromium project so --project=chromium --headed uses system Chrome
  (real GPU) instead of SwiftShader on ARM Mac. Also switch trace to
  retain-on-failure + screenshot on-failure for easier E2E debugging.
- tests/kicad/pcbnew.spec.ts: replace `tool.checked` assertions with a
  label-suffix check (`[checked]`) since our auibar registration encodes
  checked state in the label (no schema change to the registry).
- tests/apps/Makefile.wasm: add `coroutine-nested` build target + include
  it in the all: list.
- tests/apps/standalone/coroutine/: kicad_coroutine_harness.h + test app
  reproducing KiCad COROUTINE semantics against real libcontext.
- tests/apps/standalone/coroutine-nested/: nested_test.cpp reproduces the
  EM_ASYNC_JS-modal + fiber-swap nesting bug in isolation. 8 scenarios
  from baseline_modal_alone through nested_fibers_inside_modal.
- tests/e2e/coroutine.spec.ts + coroutine-nested.spec.ts: Playwright specs
  that load the standalone apps and assert all case cases pass via
  [COROUTINE_TEST] SUMMARY log parsing.

## research/ and features/browser-tools/

Three background docs capturing the investigation trajectory:

- features/browser-tools/0001-kicad-wasm-tool-activation-investigation.md
  Early investigation: why tools don't activate; initial dynCall-empty-
  callback hypothesis.
- features/browser-tools/0002-wasm-coroutine-deep-dive.md
  Deep dive on Asyncify internals, fiber API, QEMU's coroutine-wasm
  reference implementation.
- features/browser-tools/0003-wxauitoolbar-registration-fix.md
  The narrow fix: why wxAuiToolBar needs a registration block, where to
  add it, what the fallback plan is.
- research/threading_1.md: corrected root-cause analysis after reading
  runtime logs — nested-Asyncify currData collision, Emscripten #9153.
- research/threading_2.md: extended research on alternative approaches
  (JSPI/WasmFX/state-machines) and why they don't help here.

## Submodule pointer updates

kicad: f6e9239aaa (wip: libcontext WASM hygiene cleanup)
wxwidgets: bb80f91e8b (wip: wxAuiToolBar element-registry registration +
            dialog diagnostics)

## Open threads not yet in scope

- Firefox/Chrome divergent behavior: "indirect call signature mismatch"
  traps in Firefox vs renderer crash in system Chrome (tracked in
  plans/peaceful-hugging-pnueli.md and the research docs).
- E2E pixel-diff for Draw Lines fails because the test's diff region does
  not cover where the line is actually drawn; tool activation works, the
  line is visible in test-results/pcbnew-draw-lines-02-after-drawing.png.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 13:58:16 +02:00
Viktor Vaczi
b46b4b69f1 fix(wasm): Fix asyncify rewind with asyncify-aware dynCall shims
Emscripten 4.x removed dynCall_* WASM exports, breaking asyncify
rewind through indirect calls (modal dialogs, event handlers).
Generate JS shims that track Asyncify.exportCallStack and register
in wasmExports so doRewind can find them.

Also fixes empty callback functions ((() => {})) generated by
Emscripten 4.x + pthreads for HTML5 events, pthread entry,
sighandler, async timer, and main loop callbacks.

Build pipeline improvements:
- Stub wasm-opt/finalize in Docker (RAM limits), run on host
- Add setup-emsdk.sh for reproducible Emscripten setup
- Simplify env.sh and version management

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 12:26:48 +01:00
Viktor Vaczi
014816b7a5 fix(wasm): Fix Emscripten empty callback functions for pthreads builds
Emscripten with pthreads generates empty arrow functions `{}` for callback
paths it assumes won't be used. However, when registering HTML5 events from
the main browser thread, targetThread is 0 and the direct call path IS taken.

This fix post-processes the generated JS to replace empty callbacks with
actual dynCall invocations for:
- HTML5 event callbacks (dynCall_iiii) - 7 instances
- pthread entry points (dynCall_ii) - 1 instance
- Signal handlers (dynCall_vi) - 1 instance
- Async timers (dynCall_vi) - 1 instance

Also improves build logging to show completion status and exit code.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 14:19:13 +01:00
Viktor Vaczi
9c271996c2 Fix KiCad WASM build and runtime errors
Build improvements:
- Change -O0 to -O1 to fix "local count too large" asyncify error
- Add wasm-emscripten-finalize to host (Docker OOMs on large WASM)
- Use -gseparate-dwarf for smaller main binary with debug info
- Build native protoc for code generation
- Add more functions to asyncify removelist

Runtime fixes:
- Add inject-dyncall-shims.sh to fix "dynCall_* is not defined" in Emscripten 4.x
- Update wxwidgets with browserInfo.name fix

New stubs and bindings:
- Add Embind bindings for JavaScript interop
- Add stubs for scripting, API plugin, PCB frame, navlib

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-22 16:30:53 +01:00