diff --git a/ChangeLog.md b/ChangeLog.md index f029dcb9ca1e2..25b7b78df521d 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -29,6 +29,12 @@ See docs/process.md for more on how version tagging works. default in #22257, and is no longer used by emscripten itself. It is also problematic as it injects a global process.on handler. It is easy to replace with a simple `--pre-js` file for those that require it. (#26326) +- The following APIs are now available in Wasm Workers: + - emscripten_futex_wait + - emscripten_futex_wake + - emscripten_is_main_runtime_thread + - emscripten_is_main_browser_thread + (#26325) - Several low level emscripten APIs that return success/failure now return the C `bool` type rather than `int`. For example `emscripten_proxy_sync` and `emscripten_is_main_runtime_thread`. (#26316) diff --git a/site/source/docs/api_reference/wasm_workers.rst b/site/source/docs/api_reference/wasm_workers.rst index 2cfcaae186cc6..b89e525ad25ec 100644 --- a/site/source/docs/api_reference/wasm_workers.rst +++ b/site/source/docs/api_reference/wasm_workers.rst @@ -86,6 +86,7 @@ the middle. Pthreads and Wasm Workers share several similarities: * Both can use emscripten_atomic_* Atomics API, + * 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 types of threads have a local stack. diff --git a/src/lib/libwasm_worker.js b/src/lib/libwasm_worker.js index 7d670c950849e..0deeb0a8dc810 100644 --- a/src/lib/libwasm_worker.js +++ b/src/lib/libwasm_worker.js @@ -25,6 +25,8 @@ #error "internal error, feature_matrix should not allow this" #endif +#endif // ~WASM_WORKERS + {{{ const workerSupportsFutexWait = () => AUDIO_WORKLET ? "!ENVIRONMENT_IS_AUDIO_WORKLET" : '1'; const wasmWorkerJs = ` @@ -59,7 +61,6 @@ }`; }}} -#endif // ~WASM_WORKERS addToLibrary({ @@ -342,5 +343,20 @@ if (ENVIRONMENT_IS_WASM_WORKER else dispatch(-1/*idx*/, 2/*'timed-out'*/); }; tryAcquireSemaphore(); - } + }, + +#if !PTHREADS + // When pthreads are used we call `__set_thread_state` immediately on worker + // creation. When wasm workers is used without pthreads, we call + // `__set_thread_state` lazily to save code size for programs that don't use + // the threads state. + __do_set_thread_state__deps: ['__set_thread_state'], + __do_set_thread_state: (tb) => { + ___set_thread_state( + /*thread_ptr=*/0, + /*is_main_thread=*/!ENVIRONMENT_IS_WORKER, + /*is_runtime_thread=*/!ENVIRONMENT_IS_WASM_WORKER, + /*supports_wait=*/ENVIRONMENT_IS_WORKER && {{{ workerSupportsFutexWait() }}}); + }, +#endif }); diff --git a/system/include/emscripten/threading.h b/system/include/emscripten/threading.h index fbc34f4d77639..91d0c113a0971 100644 --- a/system/include/emscripten/threading.h +++ b/system/include/emscripten/threading.h @@ -42,11 +42,13 @@ int emscripten_futex_wake(volatile void/*uint32_t*/ * _Nonnull addr, int count); // Returns true if the current thread is the thread that hosts the Emscripten // runtime. +// Returns false on pthreads and Wasm Workers. bool emscripten_is_main_runtime_thread(void); // Returns true if the current thread is the main browser thread. In the case // that the Emscripten module is started in a worker there will be no thread // for which this returns true. +// Returns false on pthreads and Wasm Workers. bool emscripten_is_main_browser_thread(void); // A temporary workaround to issue diff --git a/system/lib/pthread/emscripten_thread_state.S b/system/lib/pthread/emscripten_thread_state.S index 9d0296bb6062e..bbe707a324bc9 100644 --- a/system/lib/pthread/emscripten_thread_state.S +++ b/system/lib/pthread/emscripten_thread_state.S @@ -18,13 +18,12 @@ is_runtime_thread: .globaltype supports_wait, i32 supports_wait: -.section .text,"",@ +#if WASM_WORKERS_ONLY +.globaltype done_init, i32 +done_init: +#endif -.globl __get_tp -__get_tp: - .functype __get_tp () -> (PTR) - global.get thread_ptr - end_function +.section .text,"",@ .globl __set_thread_state __set_thread_state: @@ -39,10 +38,40 @@ __set_thread_state: global.set supports_wait end_function +#if WASM_WORKERS_ONLY +// With Wasm Workers we do lazy initializtion of the thread +// state so that only workers that call these APIs actually +// initializes their state. +.functype __do_set_thread_state () -> () + +lazy_init_thread_state: + .functype lazy_init_thread_state () -> () + block + global.get done_init + br_if 0 + call __do_set_thread_state + i32.const 1 + global.set done_init + end_block + end_function +#endif + +.globl __get_tp +__get_tp: + .functype __get_tp () -> (PTR) +#if WASM_WORKERS_ONLY + call lazy_init_thread_state +#endif + global.get thread_ptr + end_function + # Semantically the same as testing "!ENVIRONMENT_IS_PTHREAD" in JS .globl emscripten_is_main_runtime_thread emscripten_is_main_runtime_thread: .functype emscripten_is_main_runtime_thread () -> (i32) +#if WASM_WORKERS_ONLY + call lazy_init_thread_state +#endif global.get is_runtime_thread end_function @@ -50,6 +79,9 @@ emscripten_is_main_runtime_thread: .globl emscripten_is_main_browser_thread emscripten_is_main_browser_thread: .functype emscripten_is_main_browser_thread () -> (i32) +#if WASM_WORKERS_ONLY + call lazy_init_thread_state +#endif global.get is_main_thread end_function @@ -57,5 +89,8 @@ emscripten_is_main_browser_thread: .globl _emscripten_thread_supports_atomics_wait _emscripten_thread_supports_atomics_wait: .functype _emscripten_thread_supports_atomics_wait () -> (i32) +#if WASM_WORKERS_ONLY + call lazy_init_thread_state +#endif global.get supports_wait end_function diff --git a/system/lib/pthread/library_pthread_stub.c b/system/lib/pthread/library_pthread_stub.c index e380f8ef2a9c9..33e153a48677b 100644 --- a/system/lib/pthread/library_pthread_stub.c +++ b/system/lib/pthread/library_pthread_stub.c @@ -21,6 +21,10 @@ bool emscripten_has_threading_support() { return false; } int emscripten_num_logical_cores() { return 1; } +#ifndef __EMSCRIPTEN_WASM_WORKERS__ +// These low level primites are defined in both pthreads and wasm workers +// builds. + int emscripten_futex_wait(volatile void /*uint32_t*/* addr, uint32_t val, double maxWaitMilliseconds) { @@ -39,10 +43,20 @@ int emscripten_futex_wake(volatile void /*uint32_t*/* addr, int count) { } bool emscripten_is_main_runtime_thread() { - // TODO: We probably shouldn't be returning true here in WASM_WORKERS builds. return true; } +void __wait(volatile int *addr, volatile int *waiters, int val, int priv) {} + +void __lock(void* ptr) {} + +void __unlock(void* ptr) {} + +void __acquire_ptc() {} + +void __release_ptc() {} +#endif + void emscripten_main_thread_process_queued_calls() { // nop } @@ -385,16 +399,6 @@ int sem_destroy(sem_t *sem) { return 0; } -void __wait(volatile int *addr, volatile int *waiters, int val, int priv) {} - -void __lock(void* ptr) {} - -void __unlock(void* ptr) {} - -void __acquire_ptc() {} - -void __release_ptc() {} - // When pthreads is not enabled, we can't use the Atomics futex api to do // proper sleeps, so simulate a busy spin wait loop instead. void emscripten_thread_sleep(double msecs) { diff --git a/test/codesize/test_codesize_cxx_ctors1.json b/test/codesize/test_codesize_cxx_ctors1.json index e63fad8fa97f1..5db2d8218b145 100644 --- a/test/codesize/test_codesize_cxx_ctors1.json +++ b/test/codesize/test_codesize_cxx_ctors1.json @@ -2,9 +2,9 @@ "a.out.js": 19555, "a.out.js.gz": 8102, "a.out.nodebug.wasm": 132828, - "a.out.nodebug.wasm.gz": 49874, + "a.out.nodebug.wasm.gz": 49876, "total": 152383, - "total_gz": 57976, + "total_gz": 57978, "sent": [ "__cxa_throw", "_abort_js", diff --git a/test/codesize/test_codesize_cxx_ctors2.json b/test/codesize/test_codesize_cxx_ctors2.json index 2d2575871e287..20abc721bc9c0 100644 --- a/test/codesize/test_codesize_cxx_ctors2.json +++ b/test/codesize/test_codesize_cxx_ctors2.json @@ -2,9 +2,9 @@ "a.out.js": 19532, "a.out.js.gz": 8087, "a.out.nodebug.wasm": 132248, - "a.out.nodebug.wasm.gz": 49531, + "a.out.nodebug.wasm.gz": 49533, "total": 151780, - "total_gz": 57618, + "total_gz": 57620, "sent": [ "__cxa_throw", "_abort_js", diff --git a/test/codesize/test_codesize_cxx_except.json b/test/codesize/test_codesize_cxx_except.json index c3e7a0ff4dc6c..0be8df290a23c 100644 --- a/test/codesize/test_codesize_cxx_except.json +++ b/test/codesize/test_codesize_cxx_except.json @@ -2,9 +2,9 @@ "a.out.js": 23216, "a.out.js.gz": 9081, "a.out.nodebug.wasm": 172758, - "a.out.nodebug.wasm.gz": 57395, + "a.out.nodebug.wasm.gz": 57391, "total": 195974, - "total_gz": 66476, + "total_gz": 66472, "sent": [ "__cxa_begin_catch", "__cxa_end_catch", diff --git a/test/codesize/test_codesize_cxx_except_wasm.json b/test/codesize/test_codesize_cxx_except_wasm.json index fcc8b1204617a..57e784233b1c7 100644 --- a/test/codesize/test_codesize_cxx_except_wasm.json +++ b/test/codesize/test_codesize_cxx_except_wasm.json @@ -2,9 +2,9 @@ "a.out.js": 19366, "a.out.js.gz": 8022, "a.out.nodebug.wasm": 148153, - "a.out.nodebug.wasm.gz": 55275, + "a.out.nodebug.wasm.gz": 55273, "total": 167519, - "total_gz": 63297, + "total_gz": 63295, "sent": [ "_abort_js", "_tzset_js", diff --git a/test/codesize/test_codesize_cxx_except_wasm_legacy.json b/test/codesize/test_codesize_cxx_except_wasm_legacy.json index 2b80270d360e4..a78de39fcd872 100644 --- a/test/codesize/test_codesize_cxx_except_wasm_legacy.json +++ b/test/codesize/test_codesize_cxx_except_wasm_legacy.json @@ -2,9 +2,9 @@ "a.out.js": 19440, "a.out.js.gz": 8042, "a.out.nodebug.wasm": 145959, - "a.out.nodebug.wasm.gz": 54906, + "a.out.nodebug.wasm.gz": 54905, "total": 165399, - "total_gz": 62948, + "total_gz": 62947, "sent": [ "_abort_js", "_tzset_js", diff --git a/test/codesize/test_codesize_cxx_mangle.json b/test/codesize/test_codesize_cxx_mangle.json index 5f386c71f840d..b5783429ae184 100644 --- a/test/codesize/test_codesize_cxx_mangle.json +++ b/test/codesize/test_codesize_cxx_mangle.json @@ -2,9 +2,9 @@ "a.out.js": 23266, "a.out.js.gz": 9101, "a.out.nodebug.wasm": 239192, - "a.out.nodebug.wasm.gz": 79781, + "a.out.nodebug.wasm.gz": 79786, "total": 262458, - "total_gz": 88882, + "total_gz": 88887, "sent": [ "__cxa_begin_catch", "__cxa_end_catch", diff --git a/test/codesize/test_codesize_cxx_noexcept.json b/test/codesize/test_codesize_cxx_noexcept.json index 310ac1a386de9..3d7801b106351 100644 --- a/test/codesize/test_codesize_cxx_noexcept.json +++ b/test/codesize/test_codesize_cxx_noexcept.json @@ -2,9 +2,9 @@ "a.out.js": 19555, "a.out.js.gz": 8102, "a.out.nodebug.wasm": 134835, - "a.out.nodebug.wasm.gz": 50701, + "a.out.nodebug.wasm.gz": 50703, "total": 154390, - "total_gz": 58803, + "total_gz": 58805, "sent": [ "__cxa_throw", "_abort_js", diff --git a/test/codesize/test_codesize_cxx_wasmfs.json b/test/codesize/test_codesize_cxx_wasmfs.json index d9b9ec90b92c0..4d0981e0db2bb 100644 --- a/test/codesize/test_codesize_cxx_wasmfs.json +++ b/test/codesize/test_codesize_cxx_wasmfs.json @@ -2,9 +2,9 @@ "a.out.js": 7053, "a.out.js.gz": 3325, "a.out.nodebug.wasm": 172904, - "a.out.nodebug.wasm.gz": 63288, + "a.out.nodebug.wasm.gz": 63290, "total": 179957, - "total_gz": 66613, + "total_gz": 66615, "sent": [ "__cxa_throw", "_abort_js", diff --git a/test/test_browser.py b/test/test_browser.py index ddfbcfc257093..c109b1a4e54d9 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -5042,8 +5042,7 @@ def test_wasm_worker_hello_embedded(self): # Tests that it is possible to call emscripten_futex_wait() in Wasm Workers when pthreads # are also enabled. @parameterized({ - # Without pthreads we expect the stub version of the futex API - '': (['-DEXPECT_STUB'],), + '': ([],), 'pthread': (['-pthread'],), }) def test_wasm_worker_futex_wait(self, args): diff --git a/test/wasm_worker/hello_wasm_worker.c b/test/wasm_worker/hello_wasm_worker.c index 71dddb91eac7b..4d75b0648e908 100644 --- a/test/wasm_worker/hello_wasm_worker.c +++ b/test/wasm_worker/hello_wasm_worker.c @@ -2,6 +2,7 @@ #include #include #include +#include #include // This is the code example in site/source/docs/api_reference/wasm_workers.rst @@ -13,11 +14,14 @@ void do_exit() { void run_in_worker() { emscripten_out("Hello from wasm worker!"); + assert(!emscripten_is_main_runtime_thread()); + assert(!emscripten_is_main_browser_thread()); EM_ASM(typeof checkStackCookie == 'function' && checkStackCookie()); emscripten_wasm_worker_post_function_v(EMSCRIPTEN_WASM_WORKER_ID_PARENT, do_exit); } int main() { + assert(emscripten_is_main_runtime_thread()); emscripten_wasm_worker_t worker = emscripten_malloc_wasm_worker(/*stack size: */1024); assert(worker); emscripten_wasm_worker_post_function_v(worker, run_in_worker); diff --git a/test/wasm_worker/wasm_worker_futex_wait.c b/test/wasm_worker/wasm_worker_futex_wait.c index 579a3ef2e23e1..82e0bc6cd5031 100644 --- a/test/wasm_worker/wasm_worker_futex_wait.c +++ b/test/wasm_worker/wasm_worker_futex_wait.c @@ -28,10 +28,6 @@ void worker_main() { int rc = emscripten_futex_wait(&futex_value, 0, 100); double end = emscripten_performance_now(); printf("emscripten_futex_wait returned: %d after %.2fms\n", rc, end - start); -#if EXPECT_STUB - // The stub implemenation returns -ENOTSUP - assert(rc == -ENOTSUP); -#else assert(rc == -ETIMEDOUT); assert((end - start) >= 100); @@ -41,7 +37,6 @@ void worker_main() { printf("emscripten_futex_wait returned: %d\n", rc); assert(rc == 0); assert(futex_value == 1); -#endif #ifdef REPORT_RESULT REPORT_RESULT(0); diff --git a/tools/link.py b/tools/link.py index 9468e4a57603f..4303f257ea9dc 100644 --- a/tools/link.py +++ b/tools/link.py @@ -1540,9 +1540,11 @@ def limit_incoming_module_api(): if settings.MEMORY64 and settings.RELOCATABLE: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('__table_base32') + if settings.WASM_WORKERS or (settings.SHARED_MEMORY and not settings.PTHREADS): + add_system_js_lib('libwasm_worker.js') + if settings.WASM_WORKERS: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$_wasmWorkerInitializeRuntime'] - add_system_js_lib('libwasm_worker.js') # Set min browser versions based on certain settings such as WASM_BIGINT, # PTHREADS, AUDIO_WORKLET diff --git a/tools/system_libs.py b/tools/system_libs.py index 14eccb71331ba..8eb285bcde86e 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -716,7 +716,7 @@ def get_cflags(self): if self.is_mt: cflags += ['-pthread', '-sWASM_WORKERS'] if self.is_ww: - cflags += ['-sWASM_WORKERS'] + cflags += ['-sWASM_WORKERS', '-DWASM_WORKERS_ONLY'] return cflags def get_base_name(self): @@ -1181,7 +1181,6 @@ def get_files(self): ] ignore += LIBC_SOCKETS - if self.is_mt: ignore += [ 'clone.c', @@ -1207,9 +1206,6 @@ def get_files(self): 'pthread_create.c', 'pthread_kill.c', 'emscripten_thread_init.c', - 'emscripten_thread_state.S', - 'emscripten_futex_wait.c', - 'emscripten_futex_wake.c', 'emscripten_yield.c', ]) else: @@ -1268,6 +1264,25 @@ def get_files(self): 'proxying_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', + ]) + + if self.is_mt or self.is_ww: + # Low level thread primitives available in both pthreads and wasm workers builds. + libc_files += files_in_path( + path='system/lib/pthread', + filenames=[ + 'emscripten_thread_state.S', + 'emscripten_futex_wait.c', + 'emscripten_futex_wake.c', + ]) + # These files are in libc directories, but only built in libc_optz. ignore += ['pow_small.c', 'log_small.c', 'log2_small.c']