Skip to content

Commit cc9fb50

Browse files
authored
Move low level lock/condvar/semaphore form libwasm_worker to libc. (#26433)
I've moved the declarations of these functions to a new header (`threading_primitives.h`) which is then included in both `wasm_workers.h` and in `threading.h`. Add a some basic testing that these function are available under pthreads as well as wasm workers. This is step towards using these primitives to make implement libc++'s mutex/cond helpers needed for `call_once`. See #26375. All the declaration and implementations here are unchanged: This is a pure move.
1 parent ed06789 commit cc9fb50

8 files changed

Lines changed: 379 additions & 324 deletions

File tree

system/include/emscripten/threading.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@
1111
#include <pthread.h>
1212
#include <stdbool.h>
1313

14-
#include <emscripten/html5.h> // for EMSCRIPTEN_RESULT
1514
#include <emscripten/atomic.h>
15+
#include <emscripten/threading_primitives.h>
1616

1717
// Legacy proxying functions. See proxying.h for the new proxying system.
18-
#include "threading_legacy.h"
18+
#include <emscripten/threading_legacy.h>
1919

2020
#ifdef __cplusplus
2121
extern "C" {
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/*
2+
* Copyright 2026 The Emscripten Authors. All rights reserved.
3+
* Emscripten is available under two separate licenses, the MIT license and the
4+
* University of Illinois/NCSA Open Source License. Both these licenses can be
5+
* found in the LICENSE file.
6+
*/
7+
8+
#include <emscripten/atomic.h>
9+
10+
// Similar to emscripten_async_wait_callback_t but with a volatile first
11+
// argument.
12+
typedef void (*emscripten_async_wait_volatile_callback_t)(volatile void* address, uint32_t value, ATOMICS_WAIT_RESULT_T waitResult, void* userData);
13+
14+
#define emscripten_lock_t volatile uint32_t
15+
16+
// Use with syntax "emscripten_lock_t l = EMSCRIPTEN_LOCK_T_STATIC_INITIALIZER;"
17+
#define EMSCRIPTEN_LOCK_T_STATIC_INITIALIZER 0
18+
19+
void emscripten_lock_init(emscripten_lock_t * _Nonnull lock);
20+
21+
// Attempts to acquire the specified lock. If the lock is free, then this
22+
// function acquires the lock and immediately returns true. If the lock is
23+
// not free at the time of the call, the calling thread is set to synchronously
24+
// sleep for at most maxWaitNanoseconds long, until another thread releases the
25+
// lock. If the lock is acquired within that period, the function returns
26+
// true. If the lock is not acquired within the specified period, then the
27+
// wait times out and false is returned.
28+
// NOTE: This function can be only called in a Worker, and not on the main
29+
// browser thread, because the main browser thread cannot synchronously
30+
// sleep to wait for locks.
31+
32+
bool emscripten_lock_wait_acquire(emscripten_lock_t * _Nonnull lock, int64_t maxWaitNanoseconds);
33+
34+
// Similar to emscripten_lock_wait_acquire(), but instead of waiting for at most
35+
// a specified timeout value, the thread will wait indefinitely long until the
36+
// lock can be acquired.
37+
// NOTE: The only way to abort this wait is to call
38+
// emscripten_terminate_wasm_worker() on the Worker.
39+
// NOTE: This function can be only called in a Worker, and not on the main
40+
// browser thread, because the main browser thread cannot synchronously
41+
// sleep to wait for locks.
42+
void emscripten_lock_waitinf_acquire(emscripten_lock_t * _Nonnull lock);
43+
44+
// Similar to emscripten_lock_wait_acquire(), but instead of placing the calling
45+
// thread to sleep until the lock can be acquired, this function will burn CPU
46+
// cycles attempting to acquire the lock, until the given timeout is met.
47+
// This function can be called in both main thread and in Workers.
48+
// NOTE: The wait period used for this function is specified in milliseconds
49+
// instead of nanoseconds, see
50+
// https://github.com/WebAssembly/threads/issues/175 for details.
51+
// NOTE: If this function is called on the main thread, be sure to use a
52+
// reasonable max wait value, or otherwise a "slow script dialog"
53+
// notification can pop up, and can cause the browser to stop executing
54+
// the page.
55+
bool emscripten_lock_busyspin_wait_acquire(emscripten_lock_t * _Nonnull lock, double maxWaitMilliseconds);
56+
57+
// Similar to emscripten_lock_wait_acquire(), but instead of placing the calling
58+
// thread to sleep until the lock can be acquired, this function will burn CPU
59+
// cycles indefinitely until the given lock can be acquired.
60+
// This function can be called in both main thread and in Workers.
61+
// NOTE: The only way to abort this wait is to call
62+
// emscripten_terminate_wasm_worker() on the Worker. If called on the main
63+
// thread, and the lock cannot be acquired within a reasonable time
64+
// period, this function will *HANG* the browser page content process, and
65+
// show up a "slow script dialog", and/or cause the browser to stop the
66+
// page. If you call this function on the main browser thread, be extra
67+
// careful to analyze that the given lock will be extremely fast to
68+
// acquire without contention from other threads.
69+
void emscripten_lock_busyspin_waitinf_acquire(emscripten_lock_t * _Nonnull lock);
70+
71+
// Registers an *asynchronous* lock acquire operation. The calling thread will
72+
// asynchronously try to obtain the given lock after the calling thread yields
73+
// back to the event loop. If the attempt is successful within
74+
// maxWaitMilliseconds period, then the given callback asyncWaitFinished is
75+
// called with waitResult == ATOMICS_WAIT_OK. If the lock is not acquired within
76+
// the timeout period, then the callback asyncWaitFinished is called with
77+
// waitResult == ATOMICS_WAIT_TIMED_OUT.
78+
// NOTE: Unlike function emscripten_lock_wait_acquire() which takes in the wait
79+
// timeout parameter as int64 nanosecond units, this function takes in the wait
80+
// timeout parameter as double millisecond units. See
81+
// https://github.com/WebAssembly/threads/issues/175 for more information.
82+
// NOTE: This function can be called in both main thread and in Workers.
83+
// NOTE 2: This function will always acquire the lock asynchronously. That is,
84+
// the lock will only be attempted to acquire after current control flow
85+
// yields back to the browser, so that the Wasm call stack is empty.
86+
// This is to guarantee a uniform control flow. If you use this API in
87+
// a Worker, you cannot utilise an infinite loop programming model.
88+
void emscripten_lock_async_acquire(emscripten_lock_t * _Nonnull lock,
89+
emscripten_async_wait_volatile_callback_t _Nonnull asyncWaitFinished,
90+
void *userData,
91+
double maxWaitMilliseconds);
92+
93+
// Attempts to acquire a lock, returning true if successful. If the lock is
94+
// already held, this function will not sleep to wait until the lock is
95+
// released, but immediately returns false.
96+
// This function can be called on both main thread and in Workers.
97+
bool emscripten_lock_try_acquire(emscripten_lock_t * _Nonnull lock);
98+
99+
// Unlocks the specified lock for another thread to access. Note that locks are
100+
// extremely lightweight, there is no "lock owner" tracking: this function does
101+
// not actually check whether the calling thread owns the specified lock, but
102+
// any thread can call this function to release a lock on behalf of whichever
103+
// thread owns it. This function can be called on both main thread and in
104+
// Workers.
105+
void emscripten_lock_release(emscripten_lock_t * _Nonnull lock);
106+
107+
#define emscripten_semaphore_t volatile uint32_t
108+
109+
// Use with syntax emscripten_semaphore_t s = EMSCRIPTEN_SEMAPHORE_T_STATIC_INITIALIZER(num);
110+
#define EMSCRIPTEN_SEMAPHORE_T_STATIC_INITIALIZER(num) ((int)(num))
111+
112+
void emscripten_semaphore_init(emscripten_semaphore_t * _Nonnull sem, int num);
113+
114+
// main thread, try acquire num instances, but do not sleep to wait if not
115+
// available.
116+
// Returns idx that was acquired or -1 if acquire failed.
117+
int emscripten_semaphore_try_acquire(emscripten_semaphore_t * _Nonnull sem, int num);
118+
119+
// main thread, poll to try acquire num instances. Returns idx that was
120+
// acquired. If you use this API in Worker, you cannot run an infinite loop.
121+
void emscripten_semaphore_async_acquire(emscripten_semaphore_t * _Nonnull sem,
122+
int num,
123+
emscripten_async_wait_volatile_callback_t _Nonnull asyncWaitFinished,
124+
void *userData,
125+
double maxWaitMilliseconds);
126+
127+
// worker, sleep to acquire num instances. Returns idx that was acquired, or -1
128+
// if timed out unable to acquire.
129+
int emscripten_semaphore_wait_acquire(emscripten_semaphore_t * _Nonnull sem, int num, int64_t maxWaitNanoseconds);
130+
131+
// worker, sleep infinitely long to acquire num instances. Returns idx that was
132+
// acquired.
133+
int emscripten_semaphore_waitinf_acquire(emscripten_semaphore_t * _Nonnull sem, int num);
134+
135+
// Releases the given number of resources back to the semaphore. Note that the
136+
// ownership of resources is completely conceptual - there is no actual checking
137+
// that the calling thread had previously acquired that many resources, so
138+
// programs need to keep check of their semaphore usage consistency themselves.
139+
// Returns how many resources were available in the semaphore before the new
140+
// resources were released back to the semaphore. (i.e. the index where the
141+
// resource was put back to)
142+
// [main thread or worker]
143+
uint32_t emscripten_semaphore_release(emscripten_semaphore_t * _Nonnull sem, int num);
144+
145+
// Condition variable is an object that can be waited on, and another thread can
146+
// signal, while coordinating an access to a related mutex.
147+
#define emscripten_condvar_t volatile uint32_t
148+
149+
// Use with syntax emscripten_condvar_t cv = EMSCRIPTEN_CONDVAR_T_STATIC_INITIALIZER;
150+
#define EMSCRIPTEN_CONDVAR_T_STATIC_INITIALIZER ((int)(0))
151+
152+
// Creates a new condition variable to the given memory location.
153+
void emscripten_condvar_init(emscripten_condvar_t * _Nonnull condvar);
154+
155+
// Atomically performs the following:
156+
// 1. releases the given lock. The lock should (but does not strictly need to)
157+
// be held by the calling thread prior to this call.
158+
// 2. sleep the calling thread to wait for the specified condition variable to
159+
// be signaled.
160+
// 3. once the sleep has finished (another thread has signaled the condition
161+
// variable), the calling thread wakes up and reacquires the lock prior to
162+
// returning from this function.
163+
void emscripten_condvar_waitinf(emscripten_condvar_t * _Nonnull condvar, emscripten_lock_t * _Nonnull lock);
164+
165+
// Same as the above, except that an attempt to wait for the condition variable
166+
// to become true is only performed for a maximum duration.
167+
// On success (no timeout), this function will return true. If the wait times
168+
// out, this function will return false. In this case,
169+
// the calling thread will not try to reacquire the lock.
170+
bool emscripten_condvar_wait(emscripten_condvar_t * _Nonnull condvar, emscripten_lock_t * _Nonnull lock, int64_t maxWaitNanoseconds);
171+
172+
// Asynchronously wait for the given condition variable to signal.
173+
ATOMICS_WAIT_TOKEN_T emscripten_condvar_wait_async(emscripten_condvar_t * _Nonnull condvar,
174+
emscripten_lock_t * _Nonnull lock,
175+
emscripten_async_wait_callback_t _Nonnull asyncWaitFinished,
176+
void *userData,
177+
double maxWaitMilliseconds);
178+
179+
// Signals the given number of waiters on the specified condition variable.
180+
// Pass numWaitersToSignal == EMSCRIPTEN_NOTIFY_ALL_WAITERS to wake all waiters
181+
// ("broadcast" operation).
182+
void emscripten_condvar_signal(emscripten_condvar_t * _Nonnull condvar, int64_t numWaitersToSignal);

0 commit comments

Comments
 (0)