subagent transcript
session 18e25685 · workflow wf_23df5b1c
delegated task
You are an adversarial verifier for a FreeCAD-wasm code review. A reviewer claims: SEVERITY: critical CATEGORY: do-first-blocker FILE: freecad-port/re
▸ Magik
You are an adversarial verifier for a FreeCAD-wasm code review. A reviewer claims:
SEVERITY: critical
CATEGORY: do-first-blocker
FILE: freecad-port/research/R8-jspi-migration-plan.md
SUMMARY: Migrate the GUI build from ASYNCIFY to JSPI FIRST — it is the single highest-leverage step; it is the only thing that lets Python drive the live GUI (the very act of creating/rendering a 3D document), and it may auto-resolve part of the Coin indirect-call problem, so doing it first prevents wasted GL wrapper work.
DETAIL/EVIDENCE: STATUS.md Stage-4 'Blocker A' + research/R8-jspi-migration-plan.md §1: with a 3D view active, Python->C calls made during the Qt event loop crash in `_PyEM_TrampolineCall` ('unreachable'). Two incompatible stack-switching mechanisms coexist: Qt's `-sASYNCIFY` and CPython's `PY_CALL_TRAMPOLINE` (JSPI/externref). The headless T1 kernel renders fine precisely because it has no asyncify. This means the render path cannot be reached *stably* today regardless of GL correctness — creating a Part::Box view provider and painting it drives Python inside the event loop and traps. R9 §3.1's explicit re-test note also says: 'Do the JSPI migration (R8) first, re-measure, then do the wrapper work' because JSPI removes asyncify's function-table rewrite, which is itself a source of the Blocker-B signature traps. R8 §2-3: JSPI forces `-fwasm-exceptions`, so the exception ABI must be uniform — rebuild the C++ deps (OCCT, Coin, Boost compiled libs, xerces-c, ICU, fmt, yaml-cpp) with wasm-EH; CPython + its C deps are exception-neutral and are kept. Artifacts already exist: `toolchain/rebuild-qt-jspi.sh`. Concretely: (1) run rebuild-qt-jspi.sh to get qt-jspi, (2) rebuild the 7 C++ deps with `-fwasm-exceptions`, (3) switch FreeCAD link flags from `-sASYNCIFY`/`-fexceptions` to `-sJSPI`/`-fwasm-exceptions`, (4) re-run makeBox-in-GUI and confirm no trampoline trap, then re-measure Blocker B.
RECOMMENDATION: Execute R8 (rebuild-qt-jspi.sh + rebuild C++ deps with -fwasm-exceptions + relink with -sJSPI). Gate everything below on this; do NOT invest in the getprocaddress wrappers until you have re-measured Blocker B on the JSPI binary.
Independently verify by reading the actual file(s) and running git/grep as needed (repo at /home/magik6k/lcad-wasm/freecad-port/FreeCAD, toolchain at /opt/toolchains, deploy at /home/magik6k/lcad-wasm/freecad-port/deploy-gui). Default to REFUTED if you cannot confirm the evidence. Adjust severity to what the evidence actually supports.
Fable
thinking
I need to check whether this research document's claim about JSPI migration being a prerequisite is actually supported by the evidence.
Fable
⚙ Bash {"command": "cat /home/magik6k/lcad-wasm/freecad-port/research/R8-jspi-migration-plan.md 2>&1 | head -200", "description": "Read R8 migration plan"}
Fable
⚙ Bash {"command": "cat /home/magik6k/lcad-wasm/freecad-port/STATUS.md 2>&1 | head -250", "description": "Read STATUS.md"}
Fable
thinking
I'm noticing a potential contradiction here—the claim that R8 (JSPI migration) should be the first priority seems to conflict with what STATUS.md reveals about Session 2, where the core blocker around exceptions and stack-switching was actually solved through research in R10. This suggests the roadmap priorities might need reconsideration based on what was actually discovered. I need to verify that R10 actually exists and check the timeline to confirm it supersedes R8, then look at the git history to see when each was completed.
Fable
⚙ Bash {"command": "ls -la /home/magik6k/lcad-wasm/freecad-port/research/ 2>&1; echo \"---R10---\"; cat /home/magik6k/lcad-wasm/freecad-port/research/R10*.md 2>&1 | head -150", "description": "List research and read R10"}
Fable
thinking
Looking at the latest research findings, R10 directly contradicts the R8 migration strategy. The core assumption that JSPI serves as a universal bridge between Qt and CPython turns out to be false—empirical testing shows that combining native WebAssembly exception handling with JSPI produces invalid WebAssembly code, which undermines the entire foundation of the R8 plan.
More critically, R10 demonstrates that Blocker A (the trampoline crash) was already resolved through a different approach: ASYNCIFY combined with JavaScript exception handling and a forced JavaScript trampoline. This means the primary justification for executing R8 no longer holds. The reviewer's recommendation to gate ever …
Fable
⚙ Bash {"command": "echo \"===R9===\"; cat /home/magik6k/lcad-wasm/freecad-port/research/R9-coin-webgl-strategy.md 2>&1 | sed -n '1,120p'", "description": "Read R9 strategy"}
Fable
⚙ Bash {"command": "echo \"===toolchain scripts===\"; ls -la /home/magik6k/lcad-wasm/freecad-port/toolchain/ 2>&1; ls -la /opt/toolchains/ 2>&1; echo \"===qt dirs===\"; ls -d /opt/toolchains/qt-* 2>&1", "description": "Check toolchain artifacts"}
Fable
thinking
Now I'm mapping out the sequence of events—the initial planning documents came first, followed by the actual build scripts that executed the work, with timestamps showing when each component was compiled and ready.
R10 arrived after all that empirical testing was complete, documenting what actually happened: the JSPI-first approach was attempted but failed in two critical ways—native wasm-EH combined with JSPI produced invalid WebAssembly, and JS-EH with JSPI couldn't suspend properly. The real solution that emerged bypassed JSPI entirely, solving the trampoline crash through forced JS trampolines under ASYNCIFY instead.
This means the reviewer's core argument—that JSPI migration should be …
Fable
I have enough to render a verdict. The picture is decisive.
**Timeline of the artifacts (all Jul 4):**
- R8 (01:43) and R9 (01:44) authored — they propose the "do JSPI first" plan the reviewer is quoting.
- The plan was then *actually executed*: `rebuild-qt-jspi.sh` (02:41), `rebuild-deps-wasmeh.sh`, `rebuild-qt-jspi-wasmeh.sh` (05:43), `qt-jspi/` built (05:44), plus `occt-wasm/` and all `*-wasm` dep dirs rebuilt.
- R10 (`R10-eh-jspi-asyncife-findings.md`, 06:16) and the STATUS.md update (06:16) were written *after* that work, documenting its outcome.
**R10 empirically refutes the R8-first recommendation.** Both JSPI paths were built and measured, and both are dead ends:
- native wasm-EH + JSPI → **INVALID wasm** (LLVM miscompiles OCCT's `try_table`/`catch_all_ref`+rethrow into a `br_table` with inconsistent label arity; V8 rejects, Binaryen can't parse).
- JS-EH + JSPI → **cannot suspend** (`invoke_*` JS frames block JSPI suspension).
**Blocker A was already solved without JSPI.** The winning combination is ASYNCIFY + JS-EH + forcing the JS call-trampoline (`getPyEMTrampolinePtr → 0`). STATUS.md headline: Python drives OCCT from the live module, `Part.makeBox(10,10,10).Volume → 1000` with no trampoline crash.
The reviewer's evidence citations (R8 §1-5, R9 §3.1's "do JSPI first" note, STATUS Stage-4 Blocker A) are real but **stale** — they all predate R10. The reviewer's central claims are directly contradicted: JSPI is not "the single mechanism both speak" that works (it's a proven dead end), and it is not "the only thing that lets Python drive the live GUI" (that already works today without it). Executing R8 first would repeat work that was already done and empirically disproven.
Fable
⚙ StructuredOutput {"verdict": "REFUTED", "reason": "The recommendation to \"migrate to JSPI FIRST as the single highest-leverage do-first step\" is contradicted by the repo's own newest research and status. The reviewer quotes R8 (01:43) and R9 §3.1 (01:44),…