Skip to content

feat: JSPI support for wasm32-unknown-emscripten - #1

Open
guybedford wants to merge 3 commits into
emscripten-targetfrom
emscripten-jspi
Open

feat: JSPI support for wasm32-unknown-emscripten#1
guybedford wants to merge 3 commits into
emscripten-targetfrom
emscripten-jspi

Conversation

@guybedford

@guybedford guybedford commented Jul 15, 2026

Copy link
Copy Markdown
Owner

Motivation

Supporting the wasm32-unknown-emscripten platform.

Solution

This extends and is based to tokio-rs#8281, the single-threaded wasm32-unknown-emscripten target, to support JavaScript Promise Integration (JSPI)-based suspension.

Under a single-threaded build, Emscripten shares the host event loop with JavaScript, throwing on any blocking operations.

With JSPI, operations may suspend the Wasm execution on a JS promise allowing the runtime to be suspended but reentrant. For this reason, this is not like a wake, but instead multiple of these parked JSPI blocks can coexist and be woken in any order. When JSPI is enabled, and a block_on future would have to suspend (a timer deadline, an external wake, or a deferred yield_now's host turn), the calling Wasm stack parks on the host loop via JSPI-specific parking and resumes on the wake. Runtime::block_on, Handle::block_on, and the blocking_* sync APIs all behave as on native. The scheduler, task system, and time driver are all core Tokio. The Emscripten-specific aspect is just the hosted event loop drive loop and the JSPI park primitive over Emscripten's promise API, along with timer/keepalive glue. The parked stacks live in an Emscripten member of the existing runtime::context thread-local and no new public API is added.

spawn_blocking remains unsupported. Since tokio::fs and the stdio types do rely on it, the internal blocking shim completes their std calls inline, which is possible since Emscripten's filesystem syscalls are synchronous and there is nothing to offload. net is currently not included as it depends on unreleased Emscripten epoll work. When that is ready this PR can either be updated with that.

This PR does not include net functionality yet, as that depends on the Mio PR in tokio-rs/mio#1969. But extending this approach to full socket epolls is straightforward on top of this base as the next follow-on once the Mio PR lands.

Tests run in Node.js with two modes -

  1. The JSPI suite tests include features rt, time, sync, macros, fs, io-util, io-std, test-util as some emscripten-specific tests. There are only a couple of documented ignores (dup(), link() MEMFS limits, on_thread_park). - 744 passed, 0 failed, 12 ignored

  2. The non-JSPI tests remain limited pending deeper host event loop runtime work, a separate follow-on. - 4 passing, 0 failed.

@guybedford
guybedford force-pushed the emscripten-jspi branch 4 times, most recently from 6121ff9 to 3258304 Compare July 18, 2026 03:34
Integrates the #[tokio::test] suite for wasm32-unknown-emscripten
using the canonical current_thread scheduler, drivers, and task system
on top of JSPI, including the native block_on drive loop. The only
platform-specific code is the JSPI park, where inside a JSPI promising
wrapper (entered by the #[tokio::test] expansion via the jspi crate's
enter_promising) a timed park suspends the whole stack on a host
timer while the host event loop runs, so that waits are real. This JSPI
suspension path is guarded by an activation wrapper which is only enabled
by the test suite currently. Outside an
activation, or for a wait nothing could ever wake (no reactor here,
and host timers are the only mid-park wake source), the leaf panics
with a targeted error instead of deadlocking the host loop.

spawn_blocking remains unsupported (no threads). CI runs the contract
tests in two lanes: -sJSPI (waits suspend) and without (waits panic).
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