Prepare shared code for cooperative single-threaded event backends - #5
scottmarchant wants to merge 1 commit into
Conversation
d85fd0d to
4677b9c
Compare
268b9d1 to
4677b9c
Compare
|
A few small things for the cherry-pick, none of them code:
|
|
Threads research result. The branch The changes in this PR need no modification to support it. In threads mode the poke-defer hooks compile to Full findings (toolchain facts, the gate audit, runtime support, what stays unimplemented) are in |
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>
|
Note, my research found some concerns with |
A single-threaded WASI backend drains the main queue when the host event loop calls back into libdispatch. _dispatch_main_queue_drain compiles only under DISPATCH_COCOA_COMPAT, next to the CFRunLoop callback. Move _dispatch_main_queue_drain under a separate guard. The two runloop-only steps stay under DISPATCH_COCOA_COMPAT: the runloop-handle initialization and the thread-QoS override propagation. The guard also names __wasi__. That arm compiles nothing here. Darwin builds the same code as before. Platforms that define neither macro compile neither version, as before. Co-authored-by: Krzysztof Rodak <krodak.konta@gmail.com>
d352c83 to
ba10c29
Compare
Re-scoped 2026-09-04. Upstream review on swiftlang#953 asked for a host-loop model instead of drain on poke. #8 built it, and #3 now carries it. This slice is now the shared main-queue drain hoist only: 14 lines added, 1 removed, in one file. The poke-defer hooks and the sync funnel split are gone. The text below predates the re-scope where it describes those hooks. This branch mirrors the upstream PR commit for commit.
Staging note: this draft lives in the PassiveLogic fork for internal review. Progress for the full series is tracked in #3. When this draft is ready, we will cherry-pick its commits onto a new branch named
feat/prepare-cooperative-event-backendand open the upstream PR from that branch. The separate branch lets us keep changing and pre-testing this draft here, independent of the upstream PR. This staging note is the only fork-specific content; the rest of this description is written for the upstream PR.Summary
This PR prepares shared code for platforms that run libdispatch on one thread, with no worker threads. It adds two things: no-op hook macros around specific critical sections, and one shared main-queue drain function. It makes no functional change on any current platform. Linux builds and test results are identical before and after this change.
Context
WebAssembly (
wasm32-wasip1) runs libdispatch on a single thread. That platform has no way to create a worker thread. A port for it therefore uses a cooperative event backend: when work is enqueued and nothing is running, the backend runs the work immediately, on the thread that enqueued it.We built and tested that port as a series of small, independent changes. This PR is the first change in the series. Later changes add the WASI build system, the cooperative event backend, the Swift overlay, and file-descriptor and signal event sources. The later changes touch shared files only inside wasm-only blocks; they do not change code that other platforms compile.
You do not need the later changes to review this one. The two edits here are self-contained. Each is safe on its own, and each is required before a cooperative backend can exist.
What changes
1. Poke-defer hooks. The macros
_dispatch_cooperative_pokes_defer()and_dispatch_cooperative_pokes_undefer()compile to((void)0)on every current platform. A cooperative event backend defines the real versions and declares its functions when it arrives; this PR pre-stages no declarations. The hooks bracket each critical section that can enqueue work while it holds an internal lock. Each site carries a one-line comment; this list is the long form:dispatch_syncanddispatch_barrier_syncinline funnels: on the inline paths, the client callout runs with the queue's barrier lock helddispatch_async_and_waitfunnels, including the private-data block entry that Swift'sasyncAndWait(execute:)produces: the invoke can run inline with the acquired width or barrier held_dispatch_barrier_trysync_or_async_f: the invoke runs with the barrier lock helddispatch_onceinitializers: the initializer runs with the once gate held, and a drained item that re-enters the samedispatch_oncewould crash where threaded platforms simply waitdispatch_queue_set_specific: the destructor push must not run the client destructor underdqsh_lockOn a threaded platform, a poke only wakes another worker, so the hooks change nothing. On a cooperative backend, the hooks defer the inline execution until the outermost section exits. Without the hooks, the enqueued work would run under the caller's lock. A program that is correct on every threaded platform would then deadlock.
2. A shared main-queue drain.
_dispatch_main_queue_drainmoves out ofDISPATCH_COCOA_COMPATinto its own guard. The two runloop-only steps stay gated underDISPATCH_COCOA_COMPAT: the runloop-handle initialization, and the thread-QoS override propagation. A cooperative backend can then drain the thread-bound main queue through the same code the CFRunLoop callback uses. The alternative is a 63-line divergent copy, which we had, and which drifts.Why this is safe
_implfunction plus a wrapper) is always-inline. The compiler folds it back together.mainand this branch produce identical results: both pass the same 23 of 23 upstream tests.What we ask from reviewers
The public macOS CMake build of this repository does not currently configure, so we cannot compile the
DISPATCH_COCOA_COMPATpath ourselves. We ask for one Apple CI run to confirm the Darwin build is unchanged. That is the main risk this PR carries, and it is the reason we send this small change first.Before we upstream
This section is fork-only, like the staging note at the top. It lists the tasks that block the upstream submission of this PR. The full series tracker lives in #3.
wasm32-wasip1-threads) and prove the concept. DONE on branchfeat/scottm/libDispatchWasmThreads: libdispatch becomes multi-threaded (dispatch_async on real worker pthreads under wasmtime v24), and the seams in this PR need no change and no API signature moves. This PR is unblocked.feat/prepare-cooperative-event-backend, open the upstream PR, and raise the Apple CI question. DONE 2026-08-20: one squashed commit, upstream draft PR feat: prepare shared code for cooperative single-threaded event backends swiftlang/swift-corelibs-libdispatch#953. The CI ask sits in the PR body; it goes live when the draft is marked ready. Cherry-pick notes from review: squash to one commit; say "funnels" in the message (it covers bothdispatch_async_and_waitfunnels); spell out fork links asPassiveLogic/swift-corelibs-libdispatch#N; no tool trailer on the upstream commit.#if canImport(Dispatch)is mis-used: find the WASI code paths that become wrong, or break, when real Dispatch support for wasm rolls out.