diff --git a/ChangeLog.md b/ChangeLog.md index ef9d4d15d45ba..d7cd9b0f2b54f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -30,6 +30,8 @@ See docs/process.md for more on how version tagging works. This is an extension of #26336 which removed many of them. These APIs were not previously functional under Wasm Workers, but if there is strong use case it may be possible to enable them in future. (#26487) +- Pthread mutex/cond/rwlock primitives now work from with Wasm Workers (and + between Wasm Workers and pthreads). (#26510) 5.0.3 - 03/14/26 ---------------- diff --git a/site/source/docs/api_reference/wasm_workers.rst b/site/source/docs/api_reference/wasm_workers.rst index 409b39ba78bd1..4cc38617a1344 100644 --- a/site/source/docs/api_reference/wasm_workers.rst +++ b/site/source/docs/api_reference/wasm_workers.rst @@ -89,13 +89,15 @@ Pthreads and Wasm Workers share several similarities: * Both can use emscripten_futex_wait/wake API, * Both can use GCC __sync_* Atomics API, * Both can use C11 and C++11 Atomics APIs, + * Both can use pthread_mutex/pthread_cond/pthread_rwlock/pthread_once APIs. * Both types of threads have a local stack. * Both types of threads have thread-local storage (TLS) support via ``thread_local`` (C++11), ``_Thread_local`` (C11) and ``__thread`` (GNU11) keywords. * Both types of threads support TLS via explicitly linked in Wasm globals (see ``test/wasm_worker/wasm_worker_tls_wasm_assembly.c/.S`` for example code) * Both types of threads have a concept of a thread ID (``pthread_self()`` for pthreads, - ``emscripten_wasm_worker_self_id()`` for Wasm Workers) + ``emscripten_wasm_worker_self_id()`` for Wasm Workers). `gettid()` works in + both contexts so is more portable. * Both types of threads can perform an event-based and an infinite loop programming model. * Both can use ``EM_ASM`` and ``EM_JS`` API to execute JS code on the calling thread. * Both can call out to JS library functions (linked in with ``--js-library`` directive) to diff --git a/src/lib/libatomic.js b/src/lib/libatomic.js index c30dff83323cd..8e4662dc78e87 100644 --- a/src/lib/libatomic.js +++ b/src/lib/libatomic.js @@ -161,4 +161,23 @@ addToLibrary({ navigator['hardwareConcurrency'], emscripten_atomics_is_lock_free: (width) => Atomics.isLockFree(width), + +#if (ASSERTIONS || !ALLOW_BLOCKING_ON_MAIN_THREAD) && !MINIMAL_RUNTIME + emscripten_check_blocking_allowed__deps: ['$warnOnce'], +#endif + emscripten_check_blocking_allowed: () => { +#if (ASSERTIONS || !ALLOW_BLOCKING_ON_MAIN_THREAD) && !MINIMAL_RUNTIME +#if ENVIRONMENT_MAY_BE_NODE + if (ENVIRONMENT_IS_NODE) return; +#endif + + if (ENVIRONMENT_IS_WORKER) return; // Blocking in a worker/pthread is fine. + + warnOnce('Blocking on the main thread is very dangerous, see https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread'); +#if !ALLOW_BLOCKING_ON_MAIN_THREAD + abort('Blocking on the main thread is not allowed by default. See https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread'); +#endif + +#endif + }, }); diff --git a/src/lib/libpthread.js b/src/lib/libpthread.js index 0ad90f9cd68dc..791a55e1ec462 100644 --- a/src/lib/libpthread.js +++ b/src/lib/libpthread.js @@ -904,25 +904,6 @@ var LibraryPThread = { return spawnThread(threadParams); }, -#if (ASSERTIONS || !ALLOW_BLOCKING_ON_MAIN_THREAD) && !MINIMAL_RUNTIME - emscripten_check_blocking_allowed__deps: ['$warnOnce'], -#endif - emscripten_check_blocking_allowed: () => { -#if (ASSERTIONS || !ALLOW_BLOCKING_ON_MAIN_THREAD) && !MINIMAL_RUNTIME -#if ENVIRONMENT_MAY_BE_NODE - if (ENVIRONMENT_IS_NODE) return; -#endif - - if (ENVIRONMENT_IS_WORKER) return; // Blocking in a worker/pthread is fine. - - warnOnce('Blocking on the main thread is very dangerous, see https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread'); -#if !ALLOW_BLOCKING_ON_MAIN_THREAD - abort('Blocking on the main thread is not allowed by default. See https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread'); -#endif - -#endif - }, - // This function is called by a pthread to signal that exit() was called and // that the entire process should exit. // This function is always called from a pthread, but is executed on the diff --git a/system/lib/libc/musl/src/temp/__randname.c b/system/lib/libc/musl/src/temp/__randname.c index 62152de05b5ed..ce92d71fd53c6 100644 --- a/system/lib/libc/musl/src/temp/__randname.c +++ b/system/lib/libc/musl/src/temp/__randname.c @@ -11,7 +11,7 @@ char *__randname(char *template) unsigned long r; __clock_gettime(CLOCK_REALTIME, &ts); - r = ts.tv_sec + ts.tv_nsec + __pthread_self()->tid * 65537UL; + r = ts.tv_sec + ts.tv_nsec + CURRENT_THREAD_ID * 65537UL; /* XXX EMSCRIPTEN: avoid repeating the same result when __clock_gettime does not change between calls. */ static unsigned int counter = 0; diff --git a/system/lib/libc/musl/src/thread/pthread_cond_timedwait.c b/system/lib/libc/musl/src/thread/pthread_cond_timedwait.c index edd5a571a4585..49472e7b6140d 100644 --- a/system/lib/libc/musl/src/thread/pthread_cond_timedwait.c +++ b/system/lib/libc/musl/src/thread/pthread_cond_timedwait.c @@ -82,7 +82,7 @@ int __pthread_cond_timedwait(pthread_cond_t *restrict c, pthread_mutex_t *restri } #endif - if ((m->_m_type&15) && (m->_m_lock&INT_MAX) != __pthread_self()->tid) + if ((m->_m_type&15) && (m->_m_lock&INT_MAX) != CURRENT_THREAD_ID) return EPERM; if (ts && ts->tv_nsec >= 1000000000UL) diff --git a/system/lib/libc/musl/src/thread/pthread_mutex_consistent.c b/system/lib/libc/musl/src/thread/pthread_mutex_consistent.c index 27c74e5b6a010..1620bd40efb1e 100644 --- a/system/lib/libc/musl/src/thread/pthread_mutex_consistent.c +++ b/system/lib/libc/musl/src/thread/pthread_mutex_consistent.c @@ -7,7 +7,7 @@ int pthread_mutex_consistent(pthread_mutex_t *m) int own = old & 0x3fffffff; if (!(m->_m_type & 4) || !own || !(old & 0x40000000)) return EINVAL; - if (own != __pthread_self()->tid) + if (own != CURRENT_THREAD_ID) return EPERM; a_and(&m->_m_lock, ~0x40000000); return 0; diff --git a/system/lib/libc/musl/src/thread/pthread_mutex_timedlock.c b/system/lib/libc/musl/src/thread/pthread_mutex_timedlock.c index 232c9b321a8e2..16133852842dc 100644 --- a/system/lib/libc/musl/src/thread/pthread_mutex_timedlock.c +++ b/system/lib/libc/musl/src/thread/pthread_mutex_timedlock.c @@ -87,12 +87,12 @@ int __pthread_mutex_timedlock(pthread_mutex_t *restrict m, const struct timespec if (!own && (!r || (type&4))) continue; if ((type&3) == PTHREAD_MUTEX_ERRORCHECK - && own == __pthread_self()->tid) + && own == CURRENT_THREAD_ID) return EDEADLK; #if defined(__EMSCRIPTEN__) && !defined(NDEBUG) // Extra check for deadlock in debug builds, but only if we would block // forever (at == NULL). - assert(at || own != __pthread_self()->tid && "pthread mutex deadlock detected"); + assert(at || own != CURRENT_THREAD_ID && "pthread mutex deadlock detected"); #endif a_inc(&m->_m_waiters); diff --git a/system/lib/libc/musl/src/thread/pthread_rwlock_timedwrlock.c b/system/lib/libc/musl/src/thread/pthread_rwlock_timedwrlock.c index b147cb92ea9f5..b54936c660a19 100644 --- a/system/lib/libc/musl/src/thread/pthread_rwlock_timedwrlock.c +++ b/system/lib/libc/musl/src/thread/pthread_rwlock_timedwrlock.c @@ -5,7 +5,7 @@ int __pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rw, const struct tim #ifdef __EMSCRIPTEN__ /// XXX Emscripten: The spec allows detecting when multiple write locks would deadlock, which we do here to avoid hangs. /// If attempting to lock the write lock that we already own, error out. - if (rw->_rw_wr_owner == __pthread_self()->tid) return EDEADLK; + if (rw->_rw_wr_owner == CURRENT_THREAD_ID) return EDEADLK; #endif int r, t; @@ -27,7 +27,7 @@ int __pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rw, const struct tim #ifdef __EMSCRIPTEN__ /// XXX Emscripten: The spec allows detecting when multiple write locks would deadlock, which we do here to avoid hangs. /// Mark this thread as the owner of this write lock. - rw->_rw_wr_owner = __pthread_self()->tid; + rw->_rw_wr_owner = CURRENT_THREAD_ID; #endif return r; } diff --git a/system/lib/libc/musl/src/thread/pthread_rwlock_trywrlock.c b/system/lib/libc/musl/src/thread/pthread_rwlock_trywrlock.c index e275bdaf1a1f1..a1a4a55078795 100644 --- a/system/lib/libc/musl/src/thread/pthread_rwlock_trywrlock.c +++ b/system/lib/libc/musl/src/thread/pthread_rwlock_trywrlock.c @@ -6,7 +6,7 @@ int __pthread_rwlock_trywrlock(pthread_rwlock_t *rw) #ifdef __EMSCRIPTEN__ /// XXX Emscripten: The spec allows detecting when multiple write locks would deadlock, which we do here to avoid hangs. /// Mark this thread to own the write lock, to ignore multiple attempts to lock. - rw->_rw_wr_owner = __pthread_self()->tid; + rw->_rw_wr_owner = CURRENT_THREAD_ID; #endif return 0; } diff --git a/system/lib/libc/musl/src/thread/pthread_rwlock_unlock.c b/system/lib/libc/musl/src/thread/pthread_rwlock_unlock.c index 7a55a085a0288..9c3b6cebf56c7 100644 --- a/system/lib/libc/musl/src/thread/pthread_rwlock_unlock.c +++ b/system/lib/libc/musl/src/thread/pthread_rwlock_unlock.c @@ -7,7 +7,7 @@ int __pthread_rwlock_unlock(pthread_rwlock_t *rw) #ifdef __EMSCRIPTEN__ /// XXX Emscripten: The spec allows detecting when multiple write locks would deadlock, which we do here to avoid hangs. /// Mark this thread to not own the write lock anymore. - if (rw->_rw_wr_owner == __pthread_self()->tid) rw->_rw_wr_owner = 0; + if (rw->_rw_wr_owner == CURRENT_THREAD_ID) rw->_rw_wr_owner = 0; #endif do { diff --git a/system/lib/libcxxabi/src/cxa_guard_impl.h b/system/lib/libcxxabi/src/cxa_guard_impl.h index 72efcd1e83c90..191a589176b1a 100644 --- a/system/lib/libcxxabi/src/cxa_guard_impl.h +++ b/system/lib/libcxxabi/src/cxa_guard_impl.h @@ -64,11 +64,6 @@ #include #include -#ifdef __EMSCRIPTEN__ -#include -#include -#endif - #ifndef _LIBCXXABI_HAS_NO_THREADS # if defined(__ELF__) && defined(_LIBCXXABI_LINK_PTHREAD_LIB) # pragma comment(lib, "pthread") @@ -430,13 +425,6 @@ void PlatformFutexWake(int* addr) { __tsan_release(addr); futex(reinterpret_cast(addr), WAKE, INT_MAX, NULL, NULL); } -#elif defined(__EMSCRIPTEN__) -void PlatformFutexWait(int* addr, int expect) { - emscripten_futex_wait(addr, expect, INFINITY); -} -void PlatformFutexWake(int* addr) { - emscripten_futex_wake(addr, INT_MAX); -} #elif defined(SYS_futex) void PlatformFutexWait(int* addr, int expect) { constexpr int WAIT = 0; diff --git a/test/codesize/test_codesize_minimal_pthreads_memgrowth.json b/test/codesize/test_codesize_minimal_pthreads_memgrowth.json index a96e1eea00ba9..246b58694730c 100644 --- a/test/codesize/test_codesize_minimal_pthreads_memgrowth.json +++ b/test/codesize/test_codesize_minimal_pthreads_memgrowth.json @@ -2,9 +2,9 @@ "a.out.js": 7766, "a.out.js.gz": 3812, "a.out.nodebug.wasm": 19266, - "a.out.nodebug.wasm.gz": 8935, + "a.out.nodebug.wasm.gz": 8934, "total": 27032, - "total_gz": 12747, + "total_gz": 12746, "sent": [ "a (memory)", "b (emscripten_get_now)", diff --git a/test/test_other.py b/test/test_other.py index 936dff4783e1c..2e89d058c374d 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -13513,7 +13513,7 @@ def test_wasm_worker_preprocessor_flags(self): @also_with_minimal_runtime def test_wasm_worker_pthread_api_usage(self): - self.assert_fail([EMCC, test_file('wasm_worker/wasm_worker_pthread_api_usage.c'), '-sWASM_WORKERS'], 'undefined symbol: pthread_mutex_lock') + self.do_runf('wasm_worker/wasm_worker_pthread_api_usage.c', cflags=['-sWASM_WORKERS']) @also_with_minimal_runtime def test_wasm_worker_cxx_init(self): diff --git a/tools/system_libs.py b/tools/system_libs.py index b41ae353d04f1..6d2d4b81e4afd 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -1188,19 +1188,6 @@ def get_files(self): ignore += LIBC_SOCKETS if self.is_mt: - ignore += [ - 'clone.c', - 'pthread_create.c', - 'pthread_kill.c', 'pthread_sigmask.c', - '__set_thread_area.c', 'synccall.c', - '__syscall_cp.c', '__tls_get_addr.c', - '__unmapself.c', - # Empty files, simply ignore them. - 'syscall_cp.c', 'tls.c', - # TODO: Support these. See #12216. - 'pthread_setname_np.c', - 'pthread_getname_np.c', - ] libc_files += files_in_path( path='system/lib/pthread', filenames=[ @@ -1215,80 +1202,89 @@ def get_files(self): 'emscripten_yield.c', 'thread_profiler.c', ]) + elif self.is_ww: + ignore += ['pthread_self.c'] + libc_files += files_in_path( + path='system/lib/libc', + filenames=['emscripten_yield_stub.c']) else: ignore += ['thread'] libc_files += files_in_path( path='system/lib/libc', filenames=['emscripten_yield_stub.c']) - if self.is_ww: - libc_files += files_in_path( - path='system/lib/libc/musl/src/thread', - filenames=[ - '__lock.c', - '__wait.c', - 'lock_ptc.c', - ]) - else: - # Include stub version of thread functions when building - # in single theaded mode. - # Note: We do *not* include these stubs in the Wasm Workers build since it would - # never be safe to call these from a Wasm Worker. - libc_files += files_in_path( - path='system/lib/pthread', - filenames=[ - 'library_pthread_stub.c', - 'pthread_self_stub.c', - 'proxying_stub.c', - ]) - libc_files += files_in_path( - path='system/lib/libc/musl/src/thread', - filenames=[ - 'pthread_self.c', - 'pthread_cleanup_push.c', - 'pthread_attr_init.c', - 'pthread_attr_destroy.c', - 'pthread_attr_get.c', - 'pthread_attr_setdetachstate.c', - 'pthread_attr_setguardsize.c', - 'pthread_attr_setinheritsched.c', - 'pthread_attr_setschedparam.c', - 'pthread_attr_setschedpolicy.c', - 'pthread_attr_setscope.c', - 'pthread_attr_setstack.c', - 'pthread_attr_setstacksize.c', - 'pthread_getattr_np.c', - 'pthread_getconcurrency.c', - 'pthread_getcpuclockid.c', - 'pthread_getschedparam.c', - 'pthread_setschedprio.c', - 'pthread_setconcurrency.c', - 'default_attr.c', - # C11 thread library functions - 'call_once.c', - 'tss_create.c', - 'tss_delete.c', - 'tss_set.c', - 'cnd_broadcast.c', - 'cnd_destroy.c', - 'cnd_init.c', - 'cnd_signal.c', - 'cnd_timedwait.c', - 'cnd_wait.c', - 'mtx_destroy.c', - 'mtx_init.c', - 'mtx_lock.c', - 'mtx_timedlock.c', - 'mtx_trylock.c', - 'mtx_unlock.c', - 'thrd_create.c', - 'thrd_exit.c', - 'thrd_join.c', - 'thrd_sleep.c', - 'thrd_yield.c', - ]) + # Include stub version of thread functions when building + # in single theaded mode. + # Note: We do *not* include these stubs in the Wasm Workers build since it would + # never be safe to call these from a Wasm Worker. + libc_files += files_in_path( + path='system/lib/pthread', + filenames=[ + 'library_pthread_stub.c', + 'pthread_self_stub.c', + 'proxying_stub.c', + ]) + libc_files += files_in_path( + path='system/lib/libc/musl/src/thread', + filenames=[ + 'pthread_self.c', + 'pthread_cleanup_push.c', + 'pthread_attr_init.c', + 'pthread_attr_destroy.c', + 'pthread_attr_get.c', + 'pthread_attr_setdetachstate.c', + 'pthread_attr_setguardsize.c', + 'pthread_attr_setinheritsched.c', + 'pthread_attr_setschedparam.c', + 'pthread_attr_setschedpolicy.c', + 'pthread_attr_setscope.c', + 'pthread_attr_setstack.c', + 'pthread_attr_setstacksize.c', + 'pthread_getattr_np.c', + 'pthread_getconcurrency.c', + 'pthread_getcpuclockid.c', + 'pthread_getschedparam.c', + 'pthread_setschedprio.c', + 'pthread_setconcurrency.c', + 'default_attr.c', + # C11 thread library functions + 'call_once.c', + 'tss_create.c', + 'tss_delete.c', + 'tss_set.c', + 'cnd_broadcast.c', + 'cnd_destroy.c', + 'cnd_init.c', + 'cnd_signal.c', + 'cnd_timedwait.c', + 'cnd_wait.c', + 'mtx_destroy.c', + 'mtx_init.c', + 'mtx_lock.c', + 'mtx_timedlock.c', + 'mtx_trylock.c', + 'mtx_unlock.c', + 'thrd_create.c', + 'thrd_exit.c', + 'thrd_join.c', + 'thrd_sleep.c', + 'thrd_yield.c', + ]) if self.is_mt or self.is_ww: + ignore += [ + 'clone.c', + 'pthread_create.c', + 'pthread_kill.c', 'pthread_sigmask.c', + '__set_thread_area.c', 'synccall.c', + '__syscall_cp.c', '__tls_get_addr.c', + '__unmapself.c', + # Empty files, simply ignore them. + 'syscall_cp.c', 'tls.c', + # TODO: Support these. See #12216. + 'pthread_setname_np.c', + 'pthread_getname_np.c', + ] # Low level thread primitives available in both pthreads and wasm workers builds. libc_files += files_in_path( path='system/lib/pthread', @@ -1303,6 +1299,7 @@ def get_files(self): ignore += ['pow_small.c', 'log_small.c', 'log2_small.c'] ignore = set(ignore) + print(ignore) for dirpath, dirnames, filenames in os.walk(musl_srcdir): # Don't recurse into ignored directories remove = [d for d in dirnames if d in ignore] @@ -1616,7 +1613,6 @@ class libcxxabi(ExceptionLibrary, MTLibrary, DebugLibrary): name = 'libc++abi' cflags = [ '-Oz', - '-D_LIBCXXABI_USE_FUTEX', '-D_LIBCPP_BUILDING_LIBRARY', '-D_LIBCXXABI_BUILDING_LIBRARY', '-DLIBCXXABI_NON_DEMANGLING_TERMINATE',