diff --git a/src/lib/libatomic.js b/src/lib/libatomic.js index 1ea3625244eb1..d636c8ddff782 100644 --- a/src/lib/libatomic.js +++ b/src/lib/libatomic.js @@ -75,17 +75,22 @@ addToLibrary({ }, #if ASYNCIFY - _emscripten_atomic_wait_promise__deps: ['$polyfillWaitAsync', '$atomicWaitStates', '$addPromise'], + _emscripten_atomic_wait_promise__deps: ['$polyfillWaitAsync', '$atomicWaitStates'], _emscripten_atomic_wait_promise: (addr, val, maxWaitMilliseconds) => { var wait = Atomics.waitAsync(HEAP32, {{{ getHeapOffset('addr', 'i32') }}}, val, maxWaitMilliseconds); if (wait.async) { // In the async case return the promise ID. var chainedPromise = wait.value.then((value) => atomicWaitStates.indexOf(value)); - var id = addPromise(chainedPromise); - return id; +#if RUNTIME_DEBUG + dbg("returning promise, -1 pair") +#endif + return [chainedPromise, -1]; } +#if RUNTIME_DEBUG + dbg("_emscripten_atomic_wait_promise returning sync result: " + wait.value) +#endif // In the synchronous case return the negative result code - return -atomicWaitStates.indexOf(wait.value); + return [undefined, atomicWaitStates.indexOf(wait.value)] }, #else _emscripten_atomic_wait_promise: (addr, val, maxWaitMilliseconds) => { diff --git a/src/lib/libpromise.js b/src/lib/libpromise.js index 35a5700c2925c..c1fc31f186d3d 100644 --- a/src/lib/libpromise.js +++ b/src/lib/libpromise.js @@ -268,6 +268,17 @@ addToLibrary({ #endif return getPromise(id); }, + + // Just like emscripten_promise_await_unchecked above except the argument + // is an actual JS promise. We call this from Wasm when we have an `externref` + // that refers to a JS promise object. + emscripten_promise_ref_await_unchecked__async: 'auto', + emscripten_promise_ref_await_unchecked: (promise) => { +#if RUNTIME_DEBUG + dbg(`emscripten_promise_ref_await_unchecked: ${promise}`); +#endif + return promise; + }, #else emscripten_promise_await: (returnValuePtr, id) => { abort('emscripten_promise_await is only available with ASYNCIFY'); @@ -276,5 +287,8 @@ addToLibrary({ abort('emscripten_promise_await_unchecked is only available with ASYNCIFY'); return 0; }, + emscripten_promise_ref_await_unchecked: (id) => { + abort('emscripten_promise_await_unchecked is only available with ASYNCIFY'); + }, #endif }); diff --git a/src/lib/libsigs.js b/src/lib/libsigs.js index b26e411a62364..4438d8bcb48a4 100644 --- a/src/lib/libsigs.js +++ b/src/lib/libsigs.js @@ -314,7 +314,6 @@ sigs = { _embind_register_value_object__sig: 'vpppppp', _embind_register_value_object_field__sig: 'vpppppppppp', _embind_register_void__sig: 'vpp', - _emscripten_atomic_wait_promise__sig: 'ppid', _emscripten_create_audio_worklet__sig: 'viipippp', _emscripten_create_wasm_worker__sig: 'iipip', _emscripten_dlopen_js__sig: 'vpppp', @@ -728,6 +727,7 @@ sigs = { emscripten_promise_create__sig: 'p', emscripten_promise_destroy__sig: 'vp', emscripten_promise_race__sig: 'ppp', + emscripten_promise_ref_await_unchecked__sig: 'pr', emscripten_promise_resolve__sig: 'vpip', emscripten_promise_then__sig: 'ppppp', emscripten_random__sig: 'f', diff --git a/system/include/emscripten/promise.h b/system/include/emscripten/promise.h index a805b364ecbb3..1efd4cf593ac8 100644 --- a/system/include/emscripten/promise.h +++ b/system/include/emscripten/promise.h @@ -153,6 +153,8 @@ emscripten_promise_await(em_promise_t promise); // chain, or result in a top level unhandled rejection. [[nodiscard]] void* emscripten_promise_await_unchecked(em_promise_t promise); +[[nodiscard]] void* emscripten_promise_ref_await_unchecked(__externref_t promise); + #ifdef __cplusplus } #endif diff --git a/system/lib/pthread/emscripten_atomic_wait_promise_asm.S b/system/lib/pthread/emscripten_atomic_wait_promise_asm.S new file mode 100644 index 0000000000000..f068772118736 --- /dev/null +++ b/system/lib/pthread/emscripten_atomic_wait_promise_asm.S @@ -0,0 +1,35 @@ +/* + * 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. + * + * Helper function for emscripten_atomic_wait_suspending. + * + * This function uses multi-value so it cannot be expressed in C code. W + * call out to the `_emscripten_atomic_wait_promise` JS library function which + * returns a pair of [ Promise|undefined, result code ]. We then write the + * result code to an out parameter and return the Promise as an externref + */ + +#ifdef __wasm64__ +#define PTR i64 +#else +#define PTR i32 +#endif + +.functype _emscripten_atomic_wait_promise (PTR, i32, f64) -> (externref, i32) + +.globl _emscripten_atomic_wait_promise_asm +_emscripten_atomic_wait_promise_asm: + .functype _emscripten_atomic_wait_promise_asm (PTR, i32, f64, PTR) -> (externref) + .local i32 + local.get 0 + local.get 1 + local.get 2 + call _emscripten_atomic_wait_promise + local.set 4 + local.get 3 + local.get 4 + i32.store 0 + end_function diff --git a/system/lib/pthread/emscripten_atomic_wait_suspending.c b/system/lib/pthread/emscripten_atomic_wait_suspending.c index 32e09ddd25ed5..6006b629cc770 100644 --- a/system/lib/pthread/emscripten_atomic_wait_suspending.c +++ b/system/lib/pthread/emscripten_atomic_wait_suspending.c @@ -5,21 +5,29 @@ * found in the LICENSE file. */ -#include #include +#include +#include #include "threading_internal.h" +// Internal promise-returning API used to implement +// emscripten_atomic_wait_syspending. +__externref_t _emscripten_atomic_wait_promise_asm(volatile void *addr, + uint32_t value, + double maxWaitMilliseconds, + int32_t* result); + ATOMICS_WAIT_TOKEN_T emscripten_atomic_wait_suspending(volatile void * _Nonnull addr, uint32_t value, double maxWaitMilliseconds) { - intptr_t res = _emscripten_atomic_wait_promise(addr, value, maxWaitMilliseconds); + int32_t result = 42; + __externref_t promise = _emscripten_atomic_wait_promise_asm(addr, value, maxWaitMilliseconds, &result); // A negative value is a synchronous result code. - if (res < 0) { - return (ATOMICS_WAIT_TOKEN_T)-res; + if (result != -1) { + return (ATOMICS_WAIT_TOKEN_T)result; } // Otherwise a positive value is a promise ID, and we can then `await` using // ASYNCIFY/JSPI. - em_promise_t promise = (em_promise_t)res; - void* await_result = emscripten_promise_await_unchecked(promise); + void* await_result = emscripten_promise_ref_await_unchecked(promise); return (ATOMICS_WAIT_TOKEN_T)(intptr_t)await_result; } diff --git a/system/lib/pthread/threading_internal.h b/system/lib/pthread/threading_internal.h index f84a8e5444ab1..2ac7ba2dd4a31 100644 --- a/system/lib/pthread/threading_internal.h +++ b/system/lib/pthread/threading_internal.h @@ -132,9 +132,3 @@ void* _emscripten_init_pthread(void *base, size_t* size, pid_t tid); // __builtin_wasm_memory_atomic_waitXX then they will not be woken by // this method. void _emscripten_thread_notify(pthread_t thread); - -// Internal, promise-returning API used to implement -// emscripten_atomic_wait_suspending. -intptr_t _emscripten_atomic_wait_promise(volatile void *addr, - uint32_t value, - double maxWaitMilliseconds); diff --git a/tools/maint/gen_sig_info.py b/tools/maint/gen_sig_info.py index 8b04cac54fd5f..1a7a469efdd08 100755 --- a/tools/maint/gen_sig_info.py +++ b/tools/maint/gen_sig_info.py @@ -179,6 +179,7 @@ def ignore_symbol(s, cxx): return True if s in {'__stack_base', '__memory_base', '__table_base', '__global_base', '__heap_base', '__stack_pointer', '__stack_high', '__stack_low', + '_emscripten_atomic_wait_promise', # legacy aliases, not callable from native code. 'stackSave', 'stackRestore', 'stackAlloc', 'getTempRet0', 'setTempRet0', }: @@ -207,6 +208,7 @@ def valuetype_to_chr(t, t64): webassembly.Type.I64: 'j', webassembly.Type.F32: 'f', webassembly.Type.F64: 'd', + webassembly.Type.EXTERNREF: 'r', }[t] diff --git a/tools/system_libs.py b/tools/system_libs.py index f95110fc383ff..35150f4cefe21 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -1284,6 +1284,7 @@ def get_files(self): 'emscripten_futex_wait.c', 'emscripten_futex_wake.c', 'emscripten_atomic_wait_suspending.c', + 'emscripten_atomic_wait_promise_asm.S', ]) # These files are in libc directories, but only built in libc_optz.