Skip to content

WASM: free-threaded CPython 3.15t with pthreads, cross-origin isolation by default #175

Open

Description

@b-re-w

User decision (2026-10-04), following #158 (free-threaded 3.15t is the only build):

  • The wasm interpreter is also 3.15t, built by us (tools/wasm/build-cpython.sh) with pthreads; not Pyodide's GIL build. Unverified so far: whether --disable-gil builds for emscripten (CPython's bundled mimalloc has no emscripten-specific path); first task is that build.
  • Cross-origin isolation (COOP: same-origin, COEP: require-corp or credentialless) is the default: toolchain generates the headers for the dev server and hosting config, and ships a service-worker fallback (coi-serviceworker style) for hosts that cannot set headers. The runtime checks crossOriginIsolated at start and fails with an explanation if it is false.
  • Documented limits: embedding in third-party iframes, popup-based OAuth/payment (opener severed), cross-origin resources without CORP/CORS.
  • Cost accepted: Pyodide's emscripten wheels do not load; compiled dependencies are built by pypackpack for wasm.
    Update docs/platforms/wasm-design.md and SPEC accordingly. Related: pypackpack Android release java resources consume generatePythonProperties without a task dependency #72, toolchain (header generation), pythonx-concurrent §3/§7 (wasm dispatchers become threads).

Activity

  1. b-re-w commented on Nov 12, 2026

    @b-re-w
    MemberAuthor

    Source-only investigation, 2026-11-13 (agent; nothing was built, so none of this proves a build links or runs).

    • --disable-gil on emscripten: CPython 3.15.0's configure accepts it, with no platform guard (configure.ac:1757-1768). mimalloc is not disabled for emscripten; the vendored mimalloc 2.1.2 treats emscripten as WASI and uses sbrk, which is atomic with shared memory. Upstream gh-158996 / PR gh-158997 (no pthreads) built a t ABI and ran the test suite: 372 test files passed and 9 failed, mostly "can't start new thread".
    • Prior art: no free-threaded build with pthreads for emscripten was found. Pyodide Klib walker: bind member functions of classes and interfaces (the klib side of #215) #237 reports a GIL 3.14 build with pthreads working on Emscripten 5.0.3.
    • Expected failure points (inferred):
      • The link fails unless libffi and mpdec are rebuilt with -pthread.
      • The call trampoline imports non-shared memory (Makefile.pre.in:3214-3216).
      • In this library: Kotlin/Wasm's memory import is not shared, which revives the .wasm patch wasm-design.md calls obsolete.
      • Thread.start() waits for the new thread, so the first start can deadlock without a pre-created worker pool.
      • Python threads calling back into Kotlin need the function table on every Worker.
    • Flags to try first (agent proposal, not confirmed): Platforms/emscripten with --host-runner node --disable-gil --enable-wasm-pthreads; -pthread in CFLAGS/LDFLAGS; -sPTHREAD_POOL_SIZE=8 -sMAXIMUM_MEMORY=2GB.
    • Open: which Emscripten to pin. CPython 3.15.0 pins 4.0.19; our script pins 5.0.3.

    Running the build experiment needs a CI job, and none exists for it. That job is waiting on a decision (asked through the boost coordinator).

  2. added a commit that references this issue on Nov 12, 2026
  3. b-re-w commented on Nov 12, 2026

    @b-re-w
    MemberAuthor

    Step 1 landed in develop via #397. The free-threaded 3.15t wasm build with pthreads builds and runs.

    • Run: build-ft-wasm.yml run 43301962045.
    • Build: Emscripten 4.0.19, --disable-gil --enable-wasm-pthreads, libffi and mpdec with atomics, Py_GIL_DISABLED 1, ABIFLAGS=t, stdlib lib/python3.15t.
    • Smoke test under node: FT-WASM-SMOKE-OK gil=False threads=4 abiflags=t version=3.15.0, both in the build tree and from the unpacked archive. The artifact python-3.15.0t-wasm32-emscripten is 9.1 MB.
    • Patch: one small patch, so the call trampoline falls back to the JS trampoline under shared memory.

    Not verified: browsers (only node ran), the cost of the JS trampoline, and thread safety beyond the smoke test.

    Left for later steps:

    • Kotlin/Wasm's shared memory import.
    • A worker pool for the first Thread.start from Kotlin.
    • Function tables on every Worker for upcalls.
    • Staging this archive as the wasmJs runtime.
    • Cross-origin isolation headers, the service-worker fallback, and the crossOriginIsolated start check.
    • The Emscripten pin: 4.0.19, CPython's own, versus 5.0.3 in the existing runtime.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions