From 7bca7aac7c2160ef6acdc1be2ce4b166bbfba28c Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Fri, 8 Nov 2024 19:36:40 +0000 Subject: [PATCH 1/3] [JSPI] Automatically add promising JSPI wrappers to thread entry points. Use a WebAssembly.Promising wrapper on the thread entry points to allow JSPI to be used from worker threads. The code that invokes the wrapper also adds async/await to handle this async call to entry point. Fixes #22354 --- src/library_pthread.js | 11 +++++++---- src/runtime_pthread.js | 4 ++-- test/core/test_pthread_join_and_asyncify.c | 6 ++++-- test/test_core.py | 2 +- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/library_pthread.js b/src/library_pthread.js index 15cf1468127ae..cd837428473e8 100644 --- a/src/library_pthread.js +++ b/src/library_pthread.js @@ -1043,7 +1043,7 @@ var LibraryPThread = { '$runtimeKeepaliveCounter', #endif ], - $invokeEntryPoint: (ptr, arg) => { + $invokeEntryPoint: {{{ asyncIf(ASYNCIFY == 2) }}} (ptr, arg) => { #if PTHREADS_DEBUG dbg(`invokeEntryPoint: ${ptrToString(ptr)}`); #endif @@ -1079,7 +1079,11 @@ var LibraryPThread = { // *ThreadMain(void *arg) form, or try linking with the Emscripten linker // flag -sEMULATE_FUNCTION_POINTER_CASTS to add in emulation for this x86 // ABI extension. +#if ASYNCIFY == 2 + var result = WebAssembly.promising({{{ makeDynCall('pp', 'ptr') }}})(arg); +#else var result = {{{ makeDynCall('pp', 'ptr') }}}(arg); +#endif #if STACK_OVERFLOW_CHECK checkStackCookie(); #endif @@ -1098,10 +1102,9 @@ var LibraryPThread = { #endif } #if ASYNCIFY == 2 - Promise.resolve(result).then(finish); -#else - finish(result); + result = await result; #endif + finish(result); }, #if MAIN_MODULE diff --git a/src/runtime_pthread.js b/src/runtime_pthread.js index f4a425dff7e35..7559d01cbb8e0 100644 --- a/src/runtime_pthread.js +++ b/src/runtime_pthread.js @@ -90,7 +90,7 @@ if (ENVIRONMENT_IS_PTHREAD) { // notified about them. self.onunhandledrejection = (e) => { throw e.reason || e; }; - function handleMessage(e) { + {{{ asyncIf(ASYNCIFY == 2) }}} function handleMessage(e) { try { var msgData = e['data']; //dbg('msgData: ' + Object.keys(msgData)); @@ -201,7 +201,7 @@ if (ENVIRONMENT_IS_PTHREAD) { } try { - invokeEntryPoint(msgData.start_routine, msgData.arg); + {{{ awaitIf(ASYNCIFY == 2) }}} invokeEntryPoint(msgData.start_routine, msgData.arg); } catch(ex) { if (ex != 'unwind') { // The pthread "crashed". Do not call `_emscripten_thread_exit` (which diff --git a/test/core/test_pthread_join_and_asyncify.c b/test/core/test_pthread_join_and_asyncify.c index de358f5aee7b7..03b806b6fff7d 100644 --- a/test/core/test_pthread_join_and_asyncify.c +++ b/test/core/test_pthread_join_and_asyncify.c @@ -13,8 +13,7 @@ EM_ASYNC_JS(int, async_call, (), { return 42; }); -// TODO Remove EMSCRIPTEN_KEEPALIVE when support for async attributes is enabled. -EMSCRIPTEN_KEEPALIVE void *run_thread(void *args) { +void *run_thread(void *args) { int ret = async_call(); assert(ret == 42); return NULL; @@ -22,6 +21,9 @@ EMSCRIPTEN_KEEPALIVE void *run_thread(void *args) { int main() { pthread_t id; + // Test that JSPI works on main thread. + emscripten_sleep(1); + // Also test that JSPI works on other threads. pthread_create(&id, NULL, run_thread, NULL); printf("joining thread!\n"); pthread_join(id, NULL); diff --git a/test/test_core.py b/test/test_core.py index ba8c3d64e2e1e..14406dd781095 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -8300,7 +8300,7 @@ def test_pthread_join_and_asyncify(self): # TODO Test with ASYNCIFY=1 https://github.com/emscripten-core/emscripten/issues/17552 self.require_jspi() self.do_runf('core/test_pthread_join_and_asyncify.c', 'joining thread!\njoined thread!', - emcc_args=['-sJSPI_EXPORTS=run_thread', + emcc_args=['-sJSPI', '-sEXIT_RUNTIME=1', '-pthread', '-sPROXY_TO_PTHREAD']) From 231c37dfd93f768481918f7f37248c47c1b8b6b4 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 12 Nov 2024 11:37:22 -0800 Subject: [PATCH 2/3] Update test/core/test_pthread_join_and_asyncify.c Co-authored-by: Alon Zakai --- test/core/test_pthread_join_and_asyncify.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/core/test_pthread_join_and_asyncify.c b/test/core/test_pthread_join_and_asyncify.c index 03b806b6fff7d..d3f24cdf69481 100644 --- a/test/core/test_pthread_join_and_asyncify.c +++ b/test/core/test_pthread_join_and_asyncify.c @@ -21,7 +21,7 @@ void *run_thread(void *args) { int main() { pthread_t id; - // Test that JSPI works on main thread. + // Test that JSPI works on the main thread. emscripten_sleep(1); // Also test that JSPI works on other threads. pthread_create(&id, NULL, run_thread, NULL); From fa0b352058fbe8d3add42dff6f4a83d48853e209 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 12 Nov 2024 22:24:35 +0000 Subject: [PATCH 3/3] Add option to generate async dyncall to fix wasm64. --- src/library_pthread.js | 8 +++----- src/parseTools.mjs | 12 +++++++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/library_pthread.js b/src/library_pthread.js index cd837428473e8..1daa32022aad1 100644 --- a/src/library_pthread.js +++ b/src/library_pthread.js @@ -1079,11 +1079,9 @@ var LibraryPThread = { // *ThreadMain(void *arg) form, or try linking with the Emscripten linker // flag -sEMULATE_FUNCTION_POINTER_CASTS to add in emulation for this x86 // ABI extension. -#if ASYNCIFY == 2 - var result = WebAssembly.promising({{{ makeDynCall('pp', 'ptr') }}})(arg); -#else - var result = {{{ makeDynCall('pp', 'ptr') }}}(arg); -#endif + + var result = {{{ makeDynCall('pp', 'ptr', ASYNCIFY == 2) }}}(arg); + #if STACK_OVERFLOW_CHECK checkStackCookie(); #endif diff --git a/src/parseTools.mjs b/src/parseTools.mjs index 01c3dc242d9e6..ca474affa3978 100644 --- a/src/parseTools.mjs +++ b/src/parseTools.mjs @@ -597,11 +597,12 @@ function charCode(char) { return char.charCodeAt(0); } -function makeDynCall(sig, funcPtr) { +function makeDynCall(sig, funcPtr, promising = false) { assert( !sig.includes('j'), 'Cannot specify 64-bit signatures ("j" in signature string) with makeDynCall!', ); + assert(!(DYNCALLS && promising), 'DYNCALLS cannot be used with JSPI.'); let args = []; for (let i = 1; i < sig.length; ++i) { @@ -672,10 +673,15 @@ Please update to new syntax.`); return `(() => ${dyncall}(${funcPtr}))`; } + let getWasmTableEntry = `getWasmTableEntry(${funcPtr})`; + if (promising) { + getWasmTableEntry = `WebAssembly.promising(${getWasmTableEntry})`; + } + if (needArgConversion) { - return `((${args}) => getWasmTableEntry(${funcPtr}).call(null, ${callArgs}))`; + return `((${args}) => ${getWasmTableEntry}.call(null, ${callArgs}))`; } - return `getWasmTableEntry(${funcPtr})`; + return getWasmTableEntry; } function makeEval(code) {