Skip to content

Add wasm32-unknown-emscripten target support - #1969

Draft
guybedford wants to merge 4 commits into
tokio-rs:masterfrom
guybedford:emscripten
Draft

Add wasm32-unknown-emscripten target support#1969
guybedford wants to merge 4 commits into
tokio-rs:masterfrom
guybedford:emscripten

Conversation

@guybedford

@guybedford guybedford commented Jul 11, 2026

Copy link
Copy Markdown

Draft PR for #642.

Adds wasm32-unknown-emscripten as a target for mio, with a CI job running the suite under Node. Opening as a draft for now since it depends on unreleased patchsets for Emscripten and Rust's libc.

This lays the groundwork for Tokio support for Emscripten. Initially I did not plan to PR Mio, but in discussion with @Darksonn and @Noah-Kennedy it was suggested to use Mio if possible, and this has worked out well in my opinion.

Once emscripten-core/emscripten#27207 lands, Emscripten exposes a real epoll, so the existing Linux epoll selector can reused rather than adding a new backend.

Patch Sets

This PR will remain a draft while it relies on the following upstream patches:

In addition Rust nightly is needed for two additional patches only landed recently.

Test status

Tests run directly on the Emscripten Node.js build with NODERAWSOCKETS and NODERAWFS to provide a transparent runner without further harness configuration being necessary.

Tests use JSPI to be able to support the harness integration, but can work without JSPI when integrating with the callback-based hosted event loop model, which will also be supported on the Tokio side. Initially the callback mode was also tested here, but this was removed based on initial PR feedback.

Early feedback on the approach very much welcome.

Comment thread src/poll.rs Outdated
@guybedford

guybedford commented Jul 12, 2026 via email

Copy link
Copy Markdown
Author

@Darksonn

Copy link
Copy Markdown
Member

Why do you want the callback mechanism on the Tokio side rather than just storing them in this buffer until the next time Tokio calls poll? I'm not sure we can safely invoke wakers from the callback anyway.

@Darksonn Darksonn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like you were able to reduce this down to merely adjusting cfgs, which is great, thanks!

Comment thread README.md Outdated

@Thomasdezeeuw Thomasdezeeuw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what to do about the missing public api (UnixStream::pair and UnixDatagram). Removing them, as this API does, is one option, we can also include them, but always return an unsupported error.

What do you think @Darksonn?

Comment thread src/lib.rs Outdated
Comment thread tests/tcp_stream.rs Outdated
Comment thread tests/udp_socket.rs

#[cfg_attr(
target_os = "emscripten",
ignore = "libuv does not re-associate a connected UDP socket on reconnect"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this related to libuv?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test runner here is Node.js. See libuv/libuv#5193 for the upstream PR (which it seems will likely not be approved).

@Darksonn

Copy link
Copy Markdown
Member

My suggestion would be to remove the unsupported APIs. IMO it's no different from the fact that UnixDatagram also does not exist on the windows target.

@guybedford

Copy link
Copy Markdown
Author

My suggestion would be to remove the unsupported APIs. IMO it's no different from the fact that UnixDatagram also does not exist on the windows target.

I've gone ahead and implemented this.

guybedford and others added 3 commits July 24, 2026 00:05
Adds `wasm32-unknown-emscripten` as a target for mio, plus a CI job that runs
the suite under Node. Resolves tokio-rs#642.

Emscripten exposes a real epoll backed by its runtime event loop, so the
existing Linux epoll selector is reused rather than adding a new backend. The
wasm `compile_error!` guard is relaxed to let emscripten through, and the
`epoll`/`eventfd`/pipe-waker cfg lists gain emscripten.

Because emscripten cannot block in `epoll_wait` without JSPI/ASYNCIFY, the
epoll fd is exposed via `AsRawFd` on `Poll`/`Registry` so the runtime driving
mio can arm emscripten's `emscripten_epoll_set_callback` on it directly,
delivering ready events on a fresh host tick whenever the set makes progress. A
zero-timeout `Poll::poll` remains available as a synchronous readiness probe
where a blocking wait is impossible.

AF_UNIX support is stream-only: emscripten's node-backed sockets have no
datagram primitive, so `UnixDatagram` and the `socketpair`-based helpers are
not compiled there. Sockets set `O_NONBLOCK` via `fcntl` since emscripten's
`socket(2)` silently strips `SOCK_NONBLOCK`/`SOCK_CLOEXEC`.

The suite spawns OS threads (socket-peer test harnesses), so std is rebuilt
with atomics via -Zbuild-std and linked -pthread with -sPROXY_TO_PTHREAD so the
main thread can block; JSPI (-sJSPI) provides the return-to-host suspension for
blocking reads/writes, and NODERAWFS/NODERAWSOCKETS back the filesystem and
sockets with node's. This needs nightly + rust-src and a JSPI-capable Node
(26+, or 22 with --experimental-wasm-jspi). No custom target spec is required:
nightly now emits the __main_argc_argv entry point (rust-lang/rust#158937).
Doctests are skipped on this target: rustdoc does not apply the emcc link args,
so the examples cannot be linked with the socket/thread runtime.

Temporary, until the dependencies land upstream:

* Cargo.toml patches libc to guybedford/libc#emscripten for the emscripten
  epoll/pthread externs (rust-lang/libc#5270).
* the CI job builds against the guybedford/emscripten `cf` fork, which carries
  AF_UNIX pathname stream sockets and multicast getsockopt patches this target
  depends on.

The two `reconnect_udp_socket_*` tests are ignored on emscripten: libuv does
not re-associate a connected UDP socket when it is reconnected to a new peer,
so no readiness is delivered for the new peer.

Suite result: 144 passed, 0 failed, 5 ignored under Node - green on CI.
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.

3 participants