Skip to content

POC: multi-threaded dispatch on wasm32-unknown-wasip1-threads - #6

Draft
scottmarchant wants to merge 4 commits into
feat/scottm/libDispatchWasmV2from
feat/scottm/libDispatchWasmThreads
Draft

POC: multi-threaded dispatch on wasm32-unknown-wasip1-threads#6
scottmarchant wants to merge 4 commits into
feat/scottm/libDispatchWasmV2from
feat/scottm/libDispatchWasmThreads

Conversation

@scottmarchant

Copy link
Copy Markdown
Collaborator

Fork-only proof of concept. This PR is a draft on purpose and is not meant to merge. It shows the multi-threaded research on top of #3 (the combined WASI port). The full findings live in tests/wasm/THREADS-RESEARCH.md on this branch. The series tracker is #3.

Summary

libdispatch becomes multi-threaded when compiled for wasm32-unknown-wasip1-threads, behind an experimental DISPATCH_WASI_THREADS=ON CMake knob. In that mode the library takes the generic POSIX shape: a pthread worker pool, real blocking waits on a wasm futex, POSIX semaphores, and a manager thread on a condition variable. The smoke test under wasmtime v24 (-S threads) prints:

PASS: async=8/8 on-worker-thread=8/8 sync=1 timer=1

Every dispatch_async block ran on a worker pthread, semaphores blocked and woke across threads, dispatch_sync funneled, and dispatch_after fired through the manager thread. The cooperative build is unchanged: 52 of 52 ctest cases still pass.

What this dictates for #3 and #5

Discoveries specific to threads

  • The module must import and export a capped shared memory (-Wl,--import-memory,--export-memory,--max-memory=N) or every pthread_create fails with EAGAIN.
  • wasi-libc deliberately omits pthread_exit: a thread ends only by returning from its start function. dispatch_main() therefore crashes with a named message until a dedicated park design lands.
  • __builtin_wasm_memory_atomic_wait32/notify is a true futex; _dispatch_wait_on_address uses it directly.
  • DISPATCH_HW_CONFIG_UP must stay cooperative-only: UP-sized continuations (32 bytes on wasm32) are smaller than struct dispatch_apply_s.
  • Not implemented in threads mode: fd and signal event sources (poll_oneoff has no cross-thread wakeup object), dispatch_main(), and the Swift overlay.

Commits

Four conventional commits: research notes, the CMake knob, the gate split plus the condvar manager backend (event_wasi_threads.c), and the smoke test.

scottmarchant and others added 4 commits August 19, 2026 13:52
Proven end to end on this machine: a 4-thread pthread program with
atomics, semaphores, and accurate timed waits compiles with both the
wasi-sdk 33 clang and the Swift 6.3.3 host clang against the
swift-wasm-6.3-RELEASE threads artifactbundle, and passes under
wasmtime v24 LTS with -S threads.

Key findings:
- _REENTRANT discriminates the threads triple at compile time.
- Memory must be imported and shared via explicit link flags, or
  pthread_create fails with EAGAIN at runtime.
- Current wasmtime (v47) has removed wasi-threads; v24 LTS is the
  one solid runtime today. The proposal is deprecated upstream, so
  threads mode should be an experimental knob, not the default.
- The slice A seams need no change for threads mode: the poke-defer
  hooks compile to no-ops there, and no API signature moves. This
  answers the #5 pre-upstream blocker.
- Full gate audit of every __wasi__ conditional with a KEEP / SPLIT /
  THREADS classification, and a threads-mode design sketch.

Co-authored-by: Krzysztof Rodak <krodak.konta@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
DISPATCH_WASI_THREADS=ON retargets the WASI toolchain file to
wasm32-unknown-wasip1-threads: it switches the C, C++, and Swift
target triples, adds -pthread, and links with
--import-memory,--export-memory,--max-memory=DISPATCH_WASI_MAX_MEMORY
(default 256 MiB, cache-overridable). wasi-threads hosts spawn every
thread against one imported shared memory; a module that owns a
private memory fails at the first pthread_create with EAGAIN, so the
import flags are not optional.

The knob defaults to OFF and the default configuration is unchanged:
the existing single-threaded build reconfigures and builds
identically. The threads bundle ships its builtins archive at the
same relative path, so the DISPATCH_WASI_BUILTINS default holds for
both SDK layouts (verified by linking and running a pthread smoke
test with the bundle builtins under wasmtime v24).

Co-authored-by: Krzysztof Rodak <krodak.konta@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eads

Split every single-thread assumption on a new DISPATCH_WASI_COOPERATIVE
discriminator (defined(__wasi__) && !defined(_REENTRANT)). The threads
triple now behaves like a generic POSIX platform:

- queue.c: the cooperative poke/drain arms compile only in cooperative
  mode; the pthread worker pool, root-queue drain, and worker threads
  compile in threads mode, the Linux shape. _gettid() derives a unique
  nonzero per-thread id from pthread_self() (4-aligned, so it survives
  the tid << 2 lock-owner encoding).
- shims/lock: threads mode gets a real futex via
  __builtin_wasm_memory_atomic_wait32/notify for
  _dispatch_wait_on_address(), sched_yield for _dispatch_thread_switch,
  and POSIX semaphores for sema4 (USE_POSIX_SEM=1 in the WASI CMake arm
  when DISPATCH_WASI_THREADS is on).
- event: a new DISPATCH_EVENT_BACKEND_WASI_THREADS backend
  (event_wasi_threads.c). The manager thread parks on a
  CLOCK_MONOTONIC condition variable and merges due timers; pokes
  signal the condvar. wasip1 has no poll wakeup object (no eventfd, no
  pipe creation), so fd and signal sources crash with a named message
  until a bounded-slice poller thread lands.
- dispatch_main() crashes with a named message in threads mode:
  wasi-libc has no pthread_exit (a wasi thread ends only by returning
  from its start function), so the generic park does not exist.
- DISPATCH_HW_CONFIG_UP stays cooperative-only. The threads target is
  not a uniprocessor, and UP also shrinks continuations below
  sizeof(struct dispatch_apply_s) on wasm32.

The library builds clean (-Werror) in both modes. The cooperative
build is unchanged: 52 of 52 ctest cases still pass.

Co-authored-by: Krzysztof Rodak <krodak.konta@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add tests/wasm/threads-dispatch-smoke.c: 8 dispatch_async blocks must
run on worker pthreads (checked against pthread_self of main),
dispatch_semaphore must block and wake across threads, dispatch_sync
must funnel, and dispatch_after must fire through the manager thread.

Under wasmtime v24 (-S threads) the test prints:

    PASS: async=8/8 on-worker-thread=8/8 sync=1 timer=1

Multi-threaded dispatch on wasm32-unknown-wasip1-threads is proven.
The research notes record the build and run commands, the
implementation results, and what stays unimplemented (fd/signal
sources, dispatch_main, the Swift overlay).

Co-authored-by: Krzysztof Rodak <krodak.konta@gmail.com>
Co-Authored-By: Claude Fable 5 <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