/* * Copyright 2026 The Emscripten Authors. All rights reserved. * Emscripten is available under two separate licenses, the MIT license and the * University of Illinois/NCSA Open Source License. Both these licenses can be * found in the LICENSE file. * * Low level threading primitives for lock, condvar and mutex, build on the * emscripten atomics API. */ #include #include "emscripten_internal.h" void emscripten_lock_init(emscripten_lock_t *lock) { emscripten_atomic_store_u32((void*)lock, EMSCRIPTEN_LOCK_T_STATIC_INITIALIZER); } bool emscripten_lock_wait_acquire(emscripten_lock_t *lock, int64_t maxWaitNanoseconds) { emscripten_lock_t val = emscripten_atomic_cas_u32((void*)lock, 0, 1); if (!val) return true; int64_t waitEnd = (int64_t)(emscripten_performance_now() * 1e6) + maxWaitNanoseconds; while (maxWaitNanoseconds > 0) { emscripten_atomic_wait_u32((void*)lock, val, maxWaitNanoseconds); val = emscripten_atomic_cas_u32((void*)lock, 0, 1); if (!val) return true; maxWaitNanoseconds = waitEnd - (int64_t)(emscripten_performance_now() * 1e6); } return false; } void emscripten_lock_waitinf_acquire(emscripten_lock_t *lock) { emscripten_lock_t val; do { val = emscripten_atomic_cas_u32((void*)lock, 0, 1); if (val) { emscripten_atomic_wait_u32((void*)lock, val, ATOMICS_WAIT_DURATION_INFINITE); } } while (val); } bool emscripten_lock_busyspin_wait_acquire(emscripten_lock_t *lock, double maxWaitMilliseconds) { // TODO: we changed the performance_now calls to get_now, which can be applied // to the remaining code (since all calls defer to the best internal option). emscripten_lock_t val = emscripten_atomic_cas_u32((void*)lock, 0, 1); if (!val) return true; double t = emscripten_get_now(); double waitEnd = t + maxWaitMilliseconds; while (t < waitEnd) { val = emscripten_atomic_cas_u32((void*)lock, 0, 1); if (!val) return true; t = emscripten_get_now(); } return false; } void emscripten_lock_busyspin_waitinf_acquire(emscripten_lock_t *lock) { emscripten_lock_t val; do { val = emscripten_atomic_cas_u32((void*)lock, 0, 1); } while (val); } bool emscripten_lock_try_acquire(emscripten_lock_t *lock) { emscripten_lock_t val = emscripten_atomic_cas_u32((void*)lock, 0, 1); return !val; } void emscripten_lock_release(emscripten_lock_t *lock) { emscripten_atomic_store_u32((void*)lock, 0); emscripten_atomic_notify((void*)lock, 1); } void emscripten_semaphore_init(emscripten_semaphore_t *sem, int num) { emscripten_atomic_store_u32((void*)sem, num); } int emscripten_semaphore_try_acquire(emscripten_semaphore_t *sem, int num) { uint32_t val = num; for (;;) { uint32_t ret = emscripten_atomic_cas_u32((void*)sem, val, val - num); if (ret == val) return val - num; if (ret < num) return -1; val = ret; } } int emscripten_semaphore_wait_acquire(emscripten_semaphore_t *sem, int num, int64_t maxWaitNanoseconds) { int val = emscripten_atomic_load_u32((void*)sem); int64_t waitEnd = 0; for (;;) { while (val < num && maxWaitNanoseconds > 0) { // Deley initialization of waitEnd until we know we are going to need to // wait (since emscripten_performance_now is not cheap). if (!waitEnd) waitEnd = (int64_t)(emscripten_performance_now() * 1e6) + maxWaitNanoseconds; emscripten_atomic_wait_u32((void*)sem, val, maxWaitNanoseconds); val = emscripten_atomic_load_u32((void*)sem); maxWaitNanoseconds = waitEnd - (int64_t)(emscripten_performance_now() * 1e6); } // If we exited the loop and still don't have enough, it means we timed out. if (val < num) return -1; int ret = (int)emscripten_atomic_cas_u32((void*)sem, val, val - num); if (ret == val) return val - num; val = ret; } } int emscripten_semaphore_waitinf_acquire(emscripten_semaphore_t *sem, int num) { int val = emscripten_atomic_load_u32((void*)sem); for (;;) { while (val < num) { emscripten_atomic_wait_u32((void*)sem, val, ATOMICS_WAIT_DURATION_INFINITE); val = emscripten_atomic_load_u32((void*)sem); } int ret = (int)emscripten_atomic_cas_u32((void*)sem, val, val - num); if (ret == val) return val - num; val = ret; } } uint32_t emscripten_semaphore_release(emscripten_semaphore_t *sem, int num) { uint32_t ret = emscripten_atomic_add_u32((void*)sem, num); emscripten_atomic_notify((void*)sem, num); return ret; } void emscripten_condvar_init(emscripten_condvar_t *condvar) { *condvar = EMSCRIPTEN_CONDVAR_T_STATIC_INITIALIZER; } void emscripten_condvar_waitinf(emscripten_condvar_t *condvar, emscripten_lock_t *lock) { int val = emscripten_atomic_load_u32((void*)condvar); emscripten_lock_release(lock); emscripten_atomic_wait_u32((void*)condvar, val, ATOMICS_WAIT_DURATION_INFINITE); emscripten_lock_waitinf_acquire(lock); } bool emscripten_condvar_wait(emscripten_condvar_t* condvar, emscripten_lock_t* lock, int64_t maxWaitNanoseconds) { int val = emscripten_atomic_load_u32((void*)condvar); emscripten_lock_release(lock); int waitValue = emscripten_atomic_wait_u32((void*)condvar, val, maxWaitNanoseconds); if (waitValue == ATOMICS_WAIT_TIMED_OUT) { return false; } return emscripten_lock_wait_acquire(lock, maxWaitNanoseconds); } ATOMICS_WAIT_TOKEN_T emscripten_condvar_wait_async(emscripten_condvar_t *condvar, emscripten_lock_t *lock, emscripten_async_wait_callback_t asyncWaitFinished, void *userData, double maxWaitMilliseconds) { int val = emscripten_atomic_load_u32((void*)condvar); emscripten_lock_release(lock); return emscripten_atomic_wait_async((void*)condvar, val, asyncWaitFinished, userData, maxWaitMilliseconds); } void emscripten_condvar_signal(emscripten_condvar_t *condvar, uint32_t numWaitersToSignal) { emscripten_atomic_add_u32((void*)condvar, 1); emscripten_atomic_notify((void*)condvar, numWaitersToSignal); }