Prepare shared code for cooperative single-threaded event backends - #5
Prepare shared code for cooperative single-threaded event backends#5scottmarchant wants to merge 3 commits into
Conversation
3e67f91 to
d85fd0d
Compare
…ends Some platforms run libdispatch on one thread, with no worker threads. An event backend for such a platform cannot wake a worker when work is enqueued. Instead, a poke may run the enqueued work immediately, on the thread that enqueued it. This commit prepares the shared code for that execution model. It makes no functional change on any current platform. Part 1: poke-defer hooks. The macros _dispatch_cooperative_pokes_defer() and _dispatch_cooperative_pokes_undefer() compile to ((void)0) unless a cooperative event backend defines the real versions. The hooks bracket each critical section that can enqueue work while it holds an internal lock: - the dispatch_sync and dispatch_barrier_sync inline funnels - the dispatch_async_and_wait funnel - _dispatch_barrier_trysync_or_async_f - dispatch_once initializers (the once gate is held) - object dispose (destructor batches are enqueued during teardown) - the specifics-hash mutation in dispatch_queue_set_specific On 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 them, enqueued work would run under the caller's lock and deadlock. Part 2: a shared main-queue drain. _dispatch_main_queue_drain moves out of DISPATCH_COCOA_COMPAT into its own guard. The two runloop-only steps (the runloop-handle initialization and the thread-QoS override propagation) stay gated under DISPATCH_COCOA_COMPAT. A cooperative backend can then drain the thread-bound main queue through the same code the CFRunLoop callback uses, instead of a divergent copy. The compiled result on Darwin is unchanged. Platforms that define neither macro compile neither version, as before. Co-authored-by: Krzysztof Rodak <krodak.konta@gmail.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The private-data block entry that Swift's asyncAndWait(execute:) produces does not pass through _dispatch_async_and_wait_f. It funnels through _dispatch_async_and_wait_block_with_privdata and reaches _dispatch_async_and_wait_recurse directly. Bracket that path with the same poke-defer hooks the other funnels use. Without this, a cooperative backend would run enqueued work under the caller's lock on this one path. Co-authored-by: Scott Marchant <15382220+scottmarchant@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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 |
Trim each poke-defer site comment to one line; the per-site rationale moves to the PR description. Drop the WASI function declarations from event_internal.h: nothing in this change defines them, and the event backend change brings the complete declaration block together with its implementation. Linux container check after the edits: main and this branch still pass the identical 23 of 23 upstream tests. Co-authored-by: Krzysztof Rodak <krodak.konta@gmail.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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 |
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. 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.