Skip to content

host: call fpcast-emu'd pthread entry functions with the correct ABI#841

Open
brandonpayton wants to merge 1 commit into
mainfrom
fix/pthread-fpcast-emu-abi
Open

host: call fpcast-emu'd pthread entry functions with the correct ABI#841
brandonpayton wants to merge 1 commit into
mainfrom
fix/pthread-fpcast-emu-abi

Conversation

@brandonpayton

Copy link
Copy Markdown
Member

Problem

Kandelo user programs are post-processed with binaryen's --fpcast-emu (see optimize_wasm in scripts/ports/*), which rewrites every indirectly-called function to one uniform trampoline signature: N i64 parameters and an i64 result. pthread entry points are reached by the host through table.get(fnPtr) — an indirect reference — so they get the emulated signature too.

centralizedThreadWorkerMain called the entry as threadFn(argPtr) with a plain JS number, which throws TypeError: Cannot convert <ptr> to a BigInt because the trampoline's first parameter is i64.

This surfaced as the first program that is both fork-instrumented and multi-threaded (pcmanfm, in the LXDE port) crashing on its first gio/glib worker thread:

Thread worker failed: Cannot convert 6749556 to a BigInt

Single-axis programs never hit it — openbox is fork-instrumented but single-threaded; non-fpcast programs (nginx, cpython) keep the plain (i32)->i32 entry.

Fix

Add buildThreadEntryArgs(), which uses the function wrapper's parameter count (fn.length) to pick the ABI:

  • plain C entry — exactly one pointer parameter (i32 on wasm32, i64 on wasm64): pass the arg as before.
  • fpcast-emu trampoline — N all-i64 parameters: pass the pointer arg first as a BigInt and zero-fill the remaining slots.

Applied at all three entry call sites (the main run loop, the non-instrumented path, and the fork-from-thread child). Programs with a 1-param entry reproduce the previous behaviour exactly, so nginx/cpython/etc. are unaffected.

Verification

Built pcmanfm (GTK2 + libfm, fork-instrumented + threaded) and ran it under Xfbdev in the browser. Before: crashes on the first worker thread. After: pcmanfm runs and renders a full file-manager window browsing the guest VFS, with no thread-worker errors. tsc --noEmit clean for worker-main.ts.

🤖 Generated with Claude Code

Kandelo user programs are post-processed with binaryen's `--fpcast-emu` (see
optimize_wasm in scripts/ports/*), which rewrites every indirectly-called
function to one uniform trampoline signature: N i64 parameters and an i64
result. pthread entry points are reached by the host through
`table.get(fnPtr)` — an indirect reference — so they too get the emulated
signature.

centralizedThreadWorkerMain called the entry as `threadFn(argPtr)` with a plain
JS number, which throws `TypeError: Cannot convert <ptr> to a BigInt` because
the trampoline's first parameter is i64. This surfaced as the first program that
is BOTH fork-instrumented and multi-threaded (pcmanfm) crashing on its first
gio/glib worker thread; single-axis programs (leafpad is threaded but not
fpcast-relevant here, openbox is fork-instrumented but single-threaded) never
hit it, and non-fpcast programs (nginx, cpython) keep the plain (i32)->i32 entry.

Add buildThreadEntryArgs(), which uses the function wrapper's parameter count to
pick the ABI: a plain C entry has exactly one pointer parameter (i32 on wasm32,
i64 on wasm64); an fpcast-emu trampoline has N all-i64 parameters, so pass the
pointer arg first as a BigInt and zero-fill the rest. Applied at all three entry
call sites (the main run loop, the non-instrumented path, and the
fork-from-thread child). Programs with a 1-param entry are unaffected — that
branch reproduces the previous behaviour exactly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant