Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------------
Expand Down
4 changes: 3 additions & 1 deletion site/source/docs/api_reference/wasm_workers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions src/lib/libatomic.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
});
19 changes: 0 additions & 19 deletions src/lib/libpthread.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion system/lib/libc/musl/src/temp/__randname.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion system/lib/libc/musl/src/thread/pthread_cond_timedwait.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion system/lib/libc/musl/src/thread/pthread_mutex_consistent.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions system/lib/libc/musl/src/thread/pthread_mutex_timedlock.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions system/lib/libc/musl/src/thread/pthread_rwlock_timedwrlock.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion system/lib/libc/musl/src/thread/pthread_rwlock_trywrlock.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion system/lib/libc/musl/src/thread/pthread_rwlock_unlock.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
12 changes: 0 additions & 12 deletions system/lib/libcxxabi/src/cxa_guard_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,6 @@
#include <limits.h>
#include <stdlib.h>

#ifdef __EMSCRIPTEN__
#include <emscripten/threading.h>
#include <math.h>
#endif

#ifndef _LIBCXXABI_HAS_NO_THREADS
# if defined(__ELF__) && defined(_LIBCXXABI_LINK_PTHREAD_LIB)
# pragma comment(lib, "pthread")
Expand Down Expand Up @@ -430,13 +425,6 @@ void PlatformFutexWake(int* addr) {
__tsan_release(addr);
futex(reinterpret_cast<volatile uint32_t*>(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;
Expand Down
4 changes: 2 additions & 2 deletions test/codesize/test_codesize_minimal_pthreads_memgrowth.json
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
Expand Down
2 changes: 1 addition & 1 deletion test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
156 changes: 76 additions & 80 deletions tools/system_libs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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=[
Expand All @@ -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',
Expand All @@ -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]
Expand Down Expand Up @@ -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',
Expand Down
Loading