feat: Temporary PR to review changes to compile libDispatch to Wasm - #2
feat: Temporary PR to review changes to compile libDispatch to Wasm#2krodak wants to merge 4 commits into
Conversation
Register the single-thread-compatible subset of the upstream bsdtests suite for WASI instead of relying only on the bespoke tests/wasm suite. Test binaries are executed by a new WASI_TEST_RUNNER cache variable (default wasmtime); when the runner is absent the tests are registered but disabled so configuration still succeeds. bsdtestharness is not built for WASI because posix_spawn does not exist there; the runner propagates the guest exit code instead. Compiling bsdtests and the tests for wasm32-wasip1 needs __wasi__ arms next to the existing __unix__ guards (WASI clang does not define __unix__), the generic_unix_port.h shims, a WASI-safe failure exit status (WASI rejects 0xff), and stubs for the large-file helpers since wasi-libc has no mkstemp. 11 of the 20 default DISPATCH_C_TESTS plus dispatch_c99 and dispatch_plusplus pass under wasmtime. The remaining 9 need concurrent worker threads or file-descriptor sources and are excluded with the reason documented in tests/CMakeLists.txt.
21f3798 to
7474fff
Compare
Comparison: PR #1 vs PR #2 vs PR #3 (libdispatch → Wasm/WASI)(Updated after two review rounds on #3: a full-branch review with fixes, then an adversarial probe round from the #2 side with a hardening series. Every row is verified empirically — built and executed. Cross-posted on #1, #2, and #3.) All three branches were built for Branch summary
Dispatch API supportLegend: ✅ works · Queues & submission
Synchronization
Sources
Data & I/O
Test coverage
Harness note (#3): Node is now optional — without it (or below 19.8) everything still builds and all tests register as visible-but-DISABLED, the same shape as the missing- Runtime support
Review round (what changed since the previous version of this comment)Two rounds. First, a high-effort review of #3's full diff vs Second, an adversarial probe round from the #2 side found one bypassed funnel in the poke-defer fix ( Bottom line#1 is the minimal seed but breaks contracts on APIs it nominally supports. #2 made the supported set behave per spec and the unsupported set fail loudly; the review round found its one systemic gap — eager drains running client code beneath caller-held locks — plus a silent-spin path and the signal/cap issues, all inherited by and now fixed in #3. #3 is #2's design carried to completion: fd and signal sources work, the re-entrancy divergence from threaded platforms is confined to documented, pinned-by-test semantics, and every failure mode is a named crash. The remaining unsupported surface (process/vnode/memory-pressure/Mach, cross-process signals, true parallelism) is bounded by WASI itself. 🤖 Generated with Claude Code |
|
Closing: this port's runtime was adopted as the foundation of the combined branch (#3), which stacks the event sources, review-round fixes, and hardening series on top and now shows the full change set against main. #3 is the single source of truth going forward; upstream-facing slices will be cut from it one at a time. See the comparison comment above for the full evaluation record. |
Add cooperative libdispatch support for single-threaded WASI
Problem
Single-threaded
wasm32-unknown-wasip1has no worker threads, manager thread, or blocking synchronization primitive. libdispatch therefore cannot use its existing pthread or event-loop backends, which leaves Swift programs targeting this ABI without the Dispatch module and its queue, timer, group, semaphore, and overlay APIs.This change adds a WASI-specific cooperative backend while leaving native platforms unchanged behind
__wasi__and CMake platform gates.Architecture
The new
event_wasi.cbackend replaces worker and manager threads with an explicit pending-work drain on the sole WASI thread. Queue and event-loop pokes mark root, main, or manager work pending. When no drain is active, the backend drains eagerly. Work enqueued by a running item is deferred until the outer drain regains control.Each drain step processes one category in this order:
Root turns are bounded so a queue that continually refills itself cannot keep lower-QoS work
from running. A newly formed pending set still starts at the highest QoS.
Timers continue to use libdispatch's generic timer heap. The WASI backend tracks the nearest armed deadline so waits and
dispatch_main()can sleep until useful work is due.Wall-clock deadlines are converted to uptime deadlines when armed. They fire normally under WASI, but a later host wall-clock adjustment does not reposition an already armed timer.
The lock and semaphore shims pump runnable work and timers while waiting. They fail loudly when an infinite wait cannot make progress instead of hanging the WebAssembly instance.
The CMake toolchain requires CMake 3.31 or newer, builds against a Swift WASI SDK, selects static libraries, configures the WASI event backend, and propagates the required signal, memory-mapping, and process-ID emulation libraries to consumers. A WASI-specific module map supplies the same transitive dependencies to ClangImporter and Swift clients. The Swift Dispatch overlay is built as a static WASI module.
Semantics And Limitations
wasm32-unknown-wasip1. Configurations with WebAssembly atomics or_REENTRANTare rejected at compile time.dispatch_once,dispatch_apply, timers,dispatch_after, and finite semaphore waits are supported within that cooperative model.DISPATCH_SOURCE_TYPE_PROCdeclaration has no linkable WASI definition because the process source type is implemented only by the kevent backend.DispatchIOremain visible but fail loudly when they reach unsupported file-descriptor registration.dispatch_main()owns the sole thread and drains pending work and timers. Once fully idle, it traps withdispatch_main(): no runnable work on single-threaded WASIinstead of blocking forever.Test Plan
Prerequisites are a matching Swift 6.3.2 release toolchain and
wasm32-unknown-wasip1SDK, CMake 3.31 or newer, Ninja 1.10 or newer, and Node.js 19.8 or newer.Result with Swift 6.3.2: 28/28 tests passed. This includes thirteen existing libdispatch tests that are compatible with the single-threaded target. WASI-specific coverage checks queue behavior, a deferred concurrent-queue barrier backlog, initial QoS ordering, fairness across self-replenishing roots, uptime and wall-clock timers, synchronization policy, file-descriptor and signal source failures, useful and immediately idle
dispatch_main()paths, a clean C consumer, and negative runner checks for wrong mode, a missing marker, and a missing diagnostic.Result with Swift 6.3.2: 30/30 tests passed. The Swift-enabled run adds clean CMake and autolink-only Swift consumers.
Proposed Clean Commit Series
dispatch_main(), runner, and clean-consumer coverage.Alternatives Considered
Wait For
wasip1-threadsThe threaded ABI would preserve libdispatch's normal execution model, but it is a different deployment target and is not available in every WASI runtime. Waiting also leaves current single-threaded WASI programs without Dispatch. This backend rejects threaded builds so a future threads port can use the existing worker model rather than inheriting cooperative semantics.
DispatchAsync
DispatchAsync can provide a Swift-level scheduling API, but it does not supply the C libdispatch ABI or the Dispatch overlay expected by existing Swift code. Adopting it would require application and dependency changes instead of making the standard module available for this target.
Separate Shim Package
A compatibility package could implement a subset of Dispatch outside this repository. It would duplicate API and behavioral contracts, complicate ClangImporter integration, and make transitive static linking the responsibility of each consumer. Keeping the platform backend in libdispatch lets the existing queue, object, timer, and overlay implementations remain the source of truth.
Review Questions