subagent transcript

session 18e25685 · workflow wf_23df5b1c
delegated task
You are an adversarial verifier for a FreeCAD-wasm code review. A reviewer claims: SEVERITY: high CATEGORY: reproducibility-trampoline-patch FILE: /ho
▸ Magik
You are an adversarial verifier for a FreeCAD-wasm code review. A reviewer claims: SEVERITY: high CATEGORY: reproducibility-trampoline-patch FILE: /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Main/CMakeLists.txt SUMMARY: The load-bearing getPyEMTrampolinePtr->0 fix is a manual hand-edit of the GENERATED FreeCAD.js, not baked into the build; a clean relink silently reintroduces the Python->OCCT trampoline crash. DETAIL/EVIDENCE: src/Main/CMakeLists.txt:83 and STATUS.md:22 both claim the JS-trampoline force is 'applied post-link'/'baked post-link', but there is NO automation anywhere that applies it. Source /opt/toolchains/src/Python-3.14.4/Python/emscripten_trampoline.c:57 is UNPATCHED (`function getPyEMTrampolinePtr() {` with the original iOS/try body; first `return 0` is only on the error/iOS paths at lines 69,77). Yet /opt/toolchains/src/freecad-gui-build/bin/FreeCAD.js and freecad-port/deploy-gui/FreeCAD.js both begin `function getPyEMTrampolinePtr(){return 0;let isIOS=...` — i.e. `return 0;` was hand-injected into the emscripten-generated JS after link. grep across all *.sh/*.py/*.md finds zero sed/patch step. Consequence: re-running `ninja FreeCADMain` regenerates FreeCAD.js from the unpatched EM_JS and drops the forced-JS-trampoline, so `_PyEM_TrampolineCall` crashes ('unreachable') under ASYNCIFY exactly as R10 describes — the entire 'winning' config silently regresses on any rebuild. RECOMMENDATION: Make the patch part of the build: either patch emscripten_trampoline.c in the CPython source tree (add a patch file under toolchain/patches/ and apply it during the python build) so the EM_JS emits `return 0;` at compile time, or add a deterministic post-link step (e.g. a --post-js override of setEmscriptenTrampoline, or a checked-in sed run as a CMake POST_BUILD command) so relinking cannot lose it. 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 verify whether the getPyEMTrampolinePtr->0 fix is actually part of the build process or just a manual edit to the generated FreeCAD.js file, so I'm going to look at the relevant source files to check.
Fable
⚙ Bash {"command": "sed -n '75,95p' /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Main/CMakeLists.txt 2>/dev/null; echo \"---STATUS---\"; sed -n '15,30p' /home/magik6k/lcad-wasm/freecad-port/STATUS.md 2>/dev/null; echo \"---TRAMPOLINE SRC---\";…
tool result
        # ASYNCIFY + JS-EH build (see research/R10). This is the only combination
        # that yields a VALID module, a rendering GUI, and working Python->OCCT:
        #  * wasm-EH is out: LLVM mis-compiles OCCT's try_table/catch_all_ref to an
        #    invalid br_table (V8 rejects it).
        #  * JSPI is out: JS-EH's invoke_* frames can't be suspended across; and
        #    wasm-EH is invalid. ASYNCIFY instruments the whole module so any
        #    function can suspend — Qt's synchr  …[truncated]
Fable
⚙ Bash {"command": "echo \"===build FreeCAD.js head===\"; head -c 300 /opt/toolchains/src/freecad-gui-build/bin/FreeCAD.js 2>/dev/null | grep -o 'getPyEMTrampolinePtr(){[^}]*' | head -1; echo; grep -o 'function getPyEMTrampolinePtr(){return 0' /op…
tool result
===build FreeCAD.js head===

function getPyEMTrampolinePtr(){return 0
===deploy FreeCAD.js===
function getPyEMTrampolinePtr(){return 0
Fable
thinking
I've confirmed the evidence: the source file is unpatched with the full iOS logic intact, both generated FreeCAD.js files have the injected `getPyEMTrampolinePtr` returning 0, and the build claims it was applied post-link. Now I'm searching through all shell, Python, and markdown files to check if there's any automated process actually applying this transformation.
Fable
⚙ Bash {"command": "echo \"=== grep getPyEMTrampolinePtr in scripts ===\"; grep -rn \"getPyEMTrampolinePtr\" /home/magik6k/lcad-wasm --include=*.sh --include=*.py --include=*.md --include=*.txt --include=*.cmake 2>/dev/null; echo \"=== grep sed/pa…
tool result
=== grep getPyEMTrampolinePtr in scripts ===
/home/magik6k/lcad-wasm/freecad-port/STATUS.md:22:  (`getPyEMTrampolinePtr→0`, baked post-link). Valid module, GUI renders,
/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Main/CMakeLists.txt:83:        #    JS trampoline (getPyEMTrampolinePtr->0, applied post-link). It's
/home/magik6k/lcad-wasm/freecad-port/research/R10-eh-jspi-asyncify-findings.md:20:     `getPyEMTrampolinePtr()` returns 0 (iOS path, or any thrown error).
/home/magik6k/lcad-wasm/fr  …[truncated]
Fable
⚙ Bash {"command": "echo \"=== toolchain patches dir ===\"; ls -la /opt/toolchains/patches/ /home/magik6k/lcad-wasm/toolchain/patches/ 2>/dev/null; find /home/magik6k/lcad-wasm /opt/toolchains -name \"*.patch\" 2>/dev/null | head; echo \"=== grep …
tool result
=== toolchain patches dir ===
/home/magik6k/lcad-wasm/freecad-port/toolchain/patches/cpython-3.14.4-wasm-static-embed.patch
/home/magik6k/lcad-wasm/freecad-port/FreeCAD/contrib/clion/attach_pydevd.py.patch
/opt/toolchains/src/Python-3.14.4/Mac/BuildScript/backport_gh71383_fix.patch
/opt/toolchains/src/Python-3.14.4/Mac/BuildScript/backport_gh110950_fix.patch
/opt/toolchains/src/Python-3.14.4/Mac/BuildScript/tk868_on_10_8_10_9.patch
/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/3rdParty/salome  …[truncated]
Fable
thinking
I need to verify that the CPython patch doesn't modify the emscripten_trampoline.c file, and double-check whether the source code actually lacks a return statement at the top.
Fable
⚙ Bash {"command": "echo \"=== does cpython patch touch trampoline? ===\"; grep -l \"trampoline\\|TrampolinePtr\" /home/magik6k/lcad-wasm/freecad-port/toolchain/patches/cpython-3.14.4-wasm-static-embed.patch 2>/dev/null && echo \"YES\" || echo \"N…
tool result
=== does cpython patch touch trampoline? ===
NO
=== first line of source function ===
57:function getPyEMTrampolinePtr() {
57:function getPyEMTrampolinePtr() {
91:    const ptr = getPyEMTrampolinePtr();
Fable
thinking
I've confirmed the core issue: the emscripten trampoline source file remains unpatched with its full iOS implementation, the cpython toolchain patches don't address it, yet both FreeCAD.js builds have been manually modified to return 0 on the trampoline function. The CMakeLists configuration appears to be part of this puzzle too.

The documentation claims the fix is applied post-link, but there's no automation—no sed scripts, patches, or post-build steps actually implementing this. This means a clean rebuild would regenerate FreeCAD.js from the unpatched source, losing the injected `return 0` and reintroducing the crash. The dead code after the return statement in the generated form confirms  …
Fable
All evidence verified independently: 1. **Source unpatched**: `/opt/toolchains/src/Python-3.14.4/Python/emscripten_trampoline.c:57` reads `function getPyEMTrampolinePtr() {` with the full iOS/try-fallback body; `return 0` appears only on the iOS path (line 69) and the catch fallback (line 77). No leading `return 0;`. 2. **Generated JS hand-injected**: Both `/opt/toolchains/src/freecad-gui-build/bin/FreeCAD.js` and `freecad-port/deploy-gui/FreeCAD.js` begin `function getPyEMTrampolinePtr(){return 0` — with the original iOS body following as dead code after the return, proving post-generation injection. 3. **No automation exists**: grep across all `.sh`/`.py`/`.md`/`.cmake` finds zero sed/patch/POST_BUILD/--post-js step touching the trampoline or FreeCAD.js. The only hits for `getPyEMTrampolinePtr` are the two prose claims (CMakeLists.txt:83 "applied post-link", STATUS.md:22 "baked post-link") plus R10 research notes. The one CPython patch under `toolchain/patches/` (cpython-3.14.4-wasm-static-embed.patch) does NOT touch the trampoline. The claim is fully substantiated: the "applied/baked post-link" wording is aspirational — nothing automates it, so a clean `ninja` relink regenerates FreeCAD.js from the unpatched EM_JS and drops the forced JS trampoline, reintroducing the ASYNCIFY crash. Severity high is appropriate: the patch is load-bearing (its loss = total failure of the documented winning config) and the falsely-documented automation makes the regression silent, though it is a reproducibility/rebuild landmine rather than a defect in the currently-shipped artifact.
Fable
⚙ StructuredOutput {"verdict": "CONFIRMED", "reason": "Verified all evidence: emscripten_trampoline.c:57 in the CPython source tree is unpatched (`function getPyEMTrampolinePtr() {` with full iOS/try body; `return 0` only on iOS path line 69 and catch fallbac…
tool result
Structured output provided successfully