diff --git a/.circleci/config.yml b/.circleci/config.yml index 0408ca741c756..efe5db357d312 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -969,6 +969,7 @@ jobs: core0.test_pthread_join_and_asyncify core0.test_async_ccall_promise_jspi* core0.test_cubescript_jspi + core0.test_poll_blocking_jspi " # Run some basic tests with the minimum version of node that we currently # support in the generated code. diff --git a/src/closure-externs/closure-externs.js b/src/closure-externs/closure-externs.js index dbb17a89417aa..4ef280be01221 100644 --- a/src/closure-externs/closure-externs.js +++ b/src/closure-externs/closure-externs.js @@ -268,3 +268,5 @@ Symbol.dispose; var os = {}; AudioWorkletProcessor.parameterDescriptors; + +Promise.withResolvers = function() {}; diff --git a/src/jsifier.mjs b/src/jsifier.mjs index 0d9f9aeddffa6..a1cf6adabed3f 100644 --- a/src/jsifier.mjs +++ b/src/jsifier.mjs @@ -446,12 +446,20 @@ function(${args}) { error(`JS library error: invalid proxying mode '${symbol}__proxy: ${proxyingMode}' specified`); } if (SHARED_MEMORY && proxyingMode != 'none') { - const sync = proxyingMode === 'sync'; if (PTHREADS) { snippet = modifyJSFunction(snippet, (args, body, async_, oneliner) => { if (oneliner) { body = `return ${body}`; } + let sync = '0' + if (proxyingMode === 'sync') { + const isAsyncFunction = LibraryManager.library[symbol + '__async']; + if (isAsyncFunction) { + sync = '2'; + } else { + sync = '1'; + } + } const rtnType = sig && sig.length ? sig[0] : null; const proxyFunc = MEMORY64 && rtnType == 'p' ? 'proxyToMainThreadPtr' : 'proxyToMainThread'; @@ -459,7 +467,7 @@ function(${args}) { return ` ${async_}function(${args}) { if (ENVIRONMENT_IS_PTHREAD) - return ${proxyFunc}(${proxiedFunctionTable.length}, 0, ${+sync}${args ? ', ' : ''}${args}); + return ${proxyFunc}(${proxiedFunctionTable.length}, 0, ${sync}${args ? ', ' : ''}${args}); ${body} }\n`; }); diff --git a/src/lib/libpthread.js b/src/lib/libpthread.js index 54c2f913cdc32..ce8c06cbc3389 100644 --- a/src/lib/libpthread.js +++ b/src/lib/libpthread.js @@ -982,8 +982,9 @@ var LibraryPThread = { _emscripten_receive_on_main_thread_js__deps: [ '$proxyToMainThread', + '_emscripten_run_js_on_main_thread_done', '$proxiedJSCallArgs'], - _emscripten_receive_on_main_thread_js: (funcIndex, emAsmAddr, callingThread, bufSize, args) => { + _emscripten_receive_on_main_thread_js: (funcIndex, emAsmAddr, callingThread, bufSize, args, ctx, ctxArgs) => { // Sometimes we need to backproxy events to the calling thread (e.g. // HTML5 DOM events handlers such as // emscripten_set_mousemove_callback()), so keep track in a globally @@ -1022,6 +1023,11 @@ var LibraryPThread = { PThread.currentProxiedOperationCallerThread = callingThread; var rtn = func(...proxiedJSCallArgs); PThread.currentProxiedOperationCallerThread = 0; + if (ctx) { + rtn.then((rtn) => __emscripten_run_js_on_main_thread_done(ctx, ctxArgs, rtn)); + return; + } + #if MEMORY64 // In memory64 mode some proxied functions return bigint/pointer but // our return type is i53/double. @@ -1160,14 +1166,11 @@ var LibraryPThread = { } }, - // Asynchronous version dlsync_threads. Always run on the main thread. - // This work happens asynchronously. The `callback` is called once this work - // is completed, passing the ctx. - // TODO(sbc): Should we make a new form of __proxy attribute for JS library - // function that run asynchronously like but blocks the caller until they are - // done. Perhaps "sync_with_ctx"? - _emscripten_dlsync_threads_async__deps: ['_emscripten_proxy_dlsync_async', '$makePromise'], - _emscripten_dlsync_threads_async: (caller, callback, ctx) => { + // Asynchronous version dlsync. Always run on the main thread. + // This work happens asynchronously. + $dlsyncAsync__deps: ['_emscripten_proxy_dlsync_async', '$makePromise'], + $dlsyncAsync: async () => { + const caller = PThread.currentProxiedOperationCallerThread; #if PTHREADS_DEBUG dbg("_emscripten_dlsync_threads_async caller=" + ptrToString(caller)); #endif @@ -1202,27 +1205,27 @@ var LibraryPThread = { #if PTHREADS_DEBUG dbg(`_emscripten_dlsync_threads_async: waiting on ${promises.length} promises`); #endif - // Once all promises are resolved then we know all threads are in sync and - // we can call the callback. - Promise.all(promises).then(() => { - PThread.outstandingPromises = {}; + await Promise.all(promises); + + PThread.outstandingPromises = {}; #if PTHREADS_DEBUG - dbg('_emscripten_dlsync_threads_async done: calling callback'); + dbg('_emscripten_dlsync_threads_async done'); #endif - {{{ makeDynCall('vp', 'callback') }}}(ctx); - }); }, - // Synchronous version dlsync_threads. This is only needed for the case then + // Synchronous version of dlsync_threads. This is only needed for the case then // the main thread call dlopen and in that case we have not choice but to // synchronously block the main thread until all other threads are in sync. // When `dlopen` is called from a worker, the worker itself is blocked but // the operation its waiting on (on the main thread) can be async. - _emscripten_dlsync_threads__deps: ['_emscripten_proxy_dlsync'], - _emscripten_dlsync_threads: () => { -#if ASSERTIONS - assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! _emscripten_dlsync_threads() can only ever be called from main thread'); -#endif + _dlsync__deps: ['_emscripten_proxy_dlsync', '$dlsyncAsync'], + _dlsync__async: true, + _dlsync__proxy: 'sync', + _dlsync: () => { + const callingThread = PThread.currentProxiedOperationCallerThread; + if (callingThread) { + return dlsyncAsync() + } for (const ptr of Object.keys(PThread.pthreads)) { const pthread_ptr = Number(ptr); if (!PThread.finishedThreads.has(pthread_ptr)) { diff --git a/src/lib/libsigs.js b/src/lib/libsigs.js index ba71e1e5b30d7..171d95676d35a 100644 --- a/src/lib/libsigs.js +++ b/src/lib/libsigs.js @@ -262,6 +262,7 @@ sigs = { __syscall_newfstatat__sig: 'iippi', __syscall_openat__sig: 'iipip', __syscall_pipe__sig: 'ip', + __syscall_poll__sig: 'ipii', __syscall_readlinkat__sig: 'iippp', __syscall_recvfrom__sig: 'iippipp', __syscall_recvmsg__sig: 'iipiiii', @@ -281,6 +282,7 @@ sigs = { _dlopen_js__sig: 'pp', _dlsym_catchup_js__sig: 'ppi', _dlsym_js__sig: 'pppp', + _dlsync__sig: 'v', _embind_create_inheriting_constructor__sig: 'pppp', _embind_finalize_value_array__sig: 'vp', _embind_finalize_value_object__sig: 'vp', @@ -313,8 +315,6 @@ sigs = { _embind_register_void__sig: 'vpp', _emscripten_create_wasm_worker__sig: 'ipi', _emscripten_dlopen_js__sig: 'vpppp', - _emscripten_dlsync_threads__sig: 'v', - _emscripten_dlsync_threads_async__sig: 'vppp', _emscripten_fetch_get_response_headers__sig: 'pipp', _emscripten_fetch_get_response_headers_length__sig: 'pi', _emscripten_fs_load_embedded_files__sig: 'vp', @@ -329,7 +329,7 @@ sigs = { _emscripten_notify_mailbox_postmessage__sig: 'vpp', _emscripten_push_main_loop_blocker__sig: 'vppp', _emscripten_push_uncounted_main_loop_blocker__sig: 'vppp', - _emscripten_receive_on_main_thread_js__sig: 'dippip', + _emscripten_receive_on_main_thread_js__sig: 'dippippp', _emscripten_runtime_keepalive_clear__sig: 'v', _emscripten_system__sig: 'ip', _emscripten_thread_cleanup__sig: 'vp', @@ -379,7 +379,6 @@ sigs = { _mmap_js__sig: 'ipiiijpp', _msync_js__sig: 'ippiiij', _munmap_js__sig: 'ippiiij', - _poll_js__sig: 'ipiipp', _setitimer_js__sig: 'iid', _timegm_js__sig: 'jp', _tzset_js__sig: 'vpppp', diff --git a/src/lib/libsyscall.js b/src/lib/libsyscall.js index f20a9ae4c614b..aaf65535148ab 100644 --- a/src/lib/libsyscall.js +++ b/src/lib/libsyscall.js @@ -551,15 +551,17 @@ var SyscallsLibrary = { var stream = SYSCALLS.getStreamFromFD(fd); return 0; // we can't do anything synchronously; the in-memory FS is already synced to }, - _poll_js__proxy: 'none', - _poll_js__deps: [ + __syscall_poll__proxy: 'sync', + __syscall_poll__async: true, + __syscall_poll: (fds, nfds, timeout) => { +#if PTHREADS || JSPI #if PTHREADS - '_emscripten_proxy_poll_finish', + const isAsyncContext = PThread.currentProxiedOperationCallerThread; +#else + const isAsyncContext = true; #endif - ], - _poll_js: (fds, nfds, timeout, ctx, arg) => { -#if PTHREADS // Enable event handlers only when the poll call is proxied from a worker. + var { promise, resolve, reject } = Promise.withResolvers(); var cleanupFuncs = []; var notifyDone = false; function asyncPollComplete(count) { @@ -571,7 +573,7 @@ var SyscallsLibrary = { dbg('asyncPollComplete', count); #endif cleanupFuncs.forEach(cb => cb()); - __emscripten_proxy_poll_finish(ctx, arg, count); + resolve(count); } function makeNotifyCallback(stream, pollfd) { var cb = (flags) => { @@ -595,17 +597,18 @@ var SyscallsLibrary = { return cb; } - if (ctx) { + if (isAsyncContext) { #if RUNTIME_DEBUG dbg('async poll start'); #endif if (timeout > 0) { - setTimeout(() => { + var t = setTimeout(() => { #if RUNTIME_DEBUG - dbg('poll: timeout'); + dbg('poll: timeout', timeout); #endif asyncPollComplete(0); }, timeout); + cleanupFuncs.push(() => clearTimeout(t)); } } #endif @@ -619,8 +622,8 @@ var SyscallsLibrary = { var stream = FS.getStream(fd); if (stream) { if (stream.stream_ops.poll) { -#if PTHREADS - if (ctx && timeout) { +#if PTHREADS || JSPI + if (isAsyncContext && timeout) { flags = stream.stream_ops.poll(stream, timeout, makeNotifyCallback(stream, pollfd)); } else #endif @@ -634,12 +637,12 @@ var SyscallsLibrary = { {{{ makeSetValue('pollfd', C_STRUCTS.pollfd.revents, 'flags', 'i16') }}}; } -#if PTHREADS - if (ctx) { +#if PTHREADS || JSPI + if (isAsyncContext) { if (count || !timeout) { asyncPollComplete(count); } - return 0; + return promise; } #endif diff --git a/system/lib/libc/dynlink.c b/system/lib/libc/dynlink.c index 71d9e14b98455..9d139c6561bc9 100644 --- a/system/lib/libc/dynlink.c +++ b/system/lib/libc/dynlink.c @@ -83,8 +83,6 @@ static thread_local struct dlevent* thread_local_tail = &main_event; static pthread_mutex_t write_lock = PTHREAD_MUTEX_INITIALIZER; static thread_local bool skip_dlsync = false; -static void dlsync(); - static void do_write_lock() { // Once we have the lock we want to avoid automatic code sync as that would // result in a deadlock. @@ -161,7 +159,7 @@ static void load_library_done(struct dso* p) { new_dlevent(p, -1); #ifdef _REENTRANT // Block until all other threads have loaded this module. - dlsync(); + _dlsync(); #endif // TODO: figure out some way to tell when its safe to free p->file_data. Its // not safe to do here because some threads could have been alseep then when @@ -431,35 +429,6 @@ int _emscripten_proxy_dlsync(pthread_t target_thread) { } return result; } - -static void done_sync_all(em_proxying_ctx* ctx) { - dbg("done_sync_all"); - emscripten_proxy_finish(ctx); -} - -static void run_dlsync_async(em_proxying_ctx* ctx, void* arg) { - pthread_t calling_thread = (pthread_t)arg; - dbg("main_thread_dlsync calling=%p", calling_thread); - _emscripten_dlsync_threads_async(calling_thread, done_sync_all, ctx); -} - -static void dlsync() { - // Call dlsync process. This call will block until all threads are in sync. - // This gets called after a shared library is loaded by a worker. - dbg("dlsync main=%p", emscripten_main_runtime_thread_id()); - if (emscripten_is_main_runtime_thread()) { - // dlsync was called on the main thread. In this case we have no choice by - // to run the blocking version of emscripten_dlsync_threads. - _emscripten_dlsync_threads(); - } else { - // Otherwise we block here while the asynchronous version runs in the main - // thread. - em_proxying_queue* q = emscripten_proxy_get_system_queue(); - int success = emscripten_proxy_sync_with_ctx( - q, emscripten_main_runtime_thread_id(), run_dlsync_async, pthread_self()); - assert(success); - } -} #endif // _REENTRANT static void dlopen_onsuccess(struct dso* dso, void* user_data) { @@ -683,7 +652,7 @@ void* __dlsym(void* restrict p, const char* restrict s, void* restrict ra) { new_dlevent(p, sym_index); #ifdef _REENTRANT // Block until all other threads have loaded this module. - dlsync(); + _dlsync(); #endif } dbg("__dlsym done dso:%p res:%p", p, res); diff --git a/system/lib/libc/emscripten_internal.h b/system/lib/libc/emscripten_internal.h index f4c0751acda98..21746c3b51065 100644 --- a/system/lib/libc/emscripten_internal.h +++ b/system/lib/libc/emscripten_internal.h @@ -94,18 +94,7 @@ void* _dlsym_catchup_js(struct dso* handle, int sym_index); int _setitimer_js(int which, double timeout); -// Synchronous version of "dlsync_threads". Called only on the main thread. -// Runs _emscripten_dlsync_self on each of the threads that are running at -// the time of the call. -void _emscripten_dlsync_threads(); - -// Asynchronous version of "dlsync_threads". Called only on the main thread. -// Runs _emscripten_dlsync_self on each of the threads that are running at -// the time of the call. Once this is done the callback is called with the -// given em_proxying_ctx. -void _emscripten_dlsync_threads_async(pthread_t calling_thread, - void (*callback)(em_proxying_ctx*), - em_proxying_ctx* ctx); +void _dlsync(); #ifdef _GNU_SOURCE void __call_sighandler(sighandler_t handler, int sig); diff --git a/system/lib/libc/proxying_poll.c b/system/lib/libc/proxying_poll.c deleted file mode 100644 index 04e4ee45144e4..0000000000000 --- a/system/lib/libc/proxying_poll.c +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2025 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. - */ - -#include -#include -#include -#include - -#include "emscripten_internal.h" - -#if _REENTRANT -typedef struct proxied_poll_t { - struct pollfd* fds; - int nfds; - int timeout; - int result; -} proxied_poll_t; - -static void call_poll_js(em_proxying_ctx* ctx, void* arg) { - proxied_poll_t* t = arg; - _poll_js(t->fds, t->nfds, t->timeout, ctx, arg); -} - -void _emscripten_proxy_poll_finish(em_proxying_ctx* ctx, void* arg, int ret) { - proxied_poll_t* t = arg; - t->result = ret; - emscripten_proxy_finish(ctx); -} - -static int proxy_poll(struct pollfd *fds, int nfds, int timeout) { - em_proxying_queue* q = emscripten_proxy_get_system_queue(); - pthread_t target = emscripten_main_runtime_thread_id(); - proxied_poll_t t = {.fds = fds, .nfds = nfds, .timeout = timeout}; - if (!emscripten_proxy_sync_with_ctx(q, target, call_poll_js, &t)) { - assert(false && "emscripten_proxy_sync failed"); - return -1; - } - return t.result; -} -#endif - -// Marked as weak since libwasmfs needs to be able to override this. -weak int __syscall_poll(intptr_t _fds, int nfds, int timeout) { - struct pollfd* fds = (struct pollfd*)_fds; -#if _REENTRANT - if (!emscripten_is_main_runtime_thread()) { - return proxy_poll(fds, nfds, timeout); - } -#endif - return _poll_js(fds, nfds, timeout, 0, 0); -} diff --git a/system/lib/pthread/proxying.c b/system/lib/pthread/proxying.c index 9dec45b434e02..4d93ff211f67d 100644 --- a/system/lib/pthread/proxying.c +++ b/system/lib/pthread/proxying.c @@ -612,18 +612,36 @@ typedef struct proxied_js_func_t { static void run_js_func(void* arg) { proxied_js_func_t* f = (proxied_js_func_t*)arg; f->result = _emscripten_receive_on_main_thread_js( - f->funcIndex, f->emAsmAddr, f->callingThread, f->bufSize, f->argBuffer); + f->funcIndex, f->emAsmAddr, f->callingThread, f->bufSize, f->argBuffer, 0, 0); if (f->owned) { free(f->argBuffer); free(f); } } +static void run_js_func_with_ctx(em_proxying_ctx* ctx, void* arg) { + proxied_js_func_t* f = (proxied_js_func_t*)arg; + _emscripten_receive_on_main_thread_js( + f->funcIndex, f->emAsmAddr, f->callingThread, f->bufSize, f->argBuffer, ctx, arg); + + // run_js_func_with_ctx is always syncrously proxied and therefor never + // arg should never be owned on the main thread. + // owning arg; + assert(!f->owned); +} + +void _emscripten_run_js_on_main_thread_done(void* ctx, void* arg, double result) { + proxied_js_func_t* f = (proxied_js_func_t*)arg; + f->result = result; + emscripten_proxy_finish(ctx); +} + + double _emscripten_run_js_on_main_thread(int func_index, void* em_asm_addr, int buf_size, double* buffer, - bool sync) { + int sync) { proxied_js_func_t f = { .funcIndex = func_index, .emAsmAddr = em_asm_addr, @@ -637,8 +655,14 @@ double _emscripten_run_js_on_main_thread(int func_index, pthread_t target = emscripten_main_runtime_thread_id(); if (sync) { - if (!emscripten_proxy_sync(q, target, run_js_func, &f)) { - assert(false && "emscripten_proxy_sync failed"); + int rtn; + if (sync == 2) { + rtn = emscripten_proxy_sync_with_ctx(q, target, run_js_func_with_ctx, &f); + } else { + rtn = emscripten_proxy_sync(q, target, run_js_func, &f); + } + if (!rtn) { + assert(false && "emscripten_proxy_sync_with_ctx failed"); return 0; } return f.result; diff --git a/system/lib/pthread/threading_internal.h b/system/lib/pthread/threading_internal.h index eae455944d578..5ce7e251fe2e6 100644 --- a/system/lib/pthread/threading_internal.h +++ b/system/lib/pthread/threading_internal.h @@ -96,7 +96,9 @@ int __pthread_create_js(struct __pthread *thread, const pthread_attr_t *attr, vo int _emscripten_default_pthread_stack_size(); void __set_thread_state(pthread_t ptr, int is_main, int is_runtime, int can_block); -double _emscripten_receive_on_main_thread_js(int funcIndex, void* emAsmAddr, pthread_t callingThread, int numCallArgs, double* args); +double _emscripten_receive_on_main_thread_js(int funcIndex, void* emAsmAddr, pthread_t callingThread, int numCallArgs, double* args, void* ctx, void* ctxArgs); + +void _emscripten_run_js_on_main_thread_done(void* ctx, void* arg, double result); // Return non-zero if the calling thread supports Atomic.wait (For example // if called from the main browser thread, this function will return zero diff --git a/test/codesize/test_codesize_hello_dylink_all.json b/test/codesize/test_codesize_hello_dylink_all.json index f7aff3b76c4e2..5661dd22d5124 100644 --- a/test/codesize/test_codesize_hello_dylink_all.json +++ b/test/codesize/test_codesize_hello_dylink_all.json @@ -1,7 +1,7 @@ { - "a.out.js": 244848, - "a.out.nodebug.wasm": 574227, - "total": 819075, + "a.out.js": 244852, + "a.out.nodebug.wasm": 574191, + "total": 819043, "sent": [ "IMG_Init", "IMG_Load", @@ -248,6 +248,7 @@ "__syscall_newfstatat", "__syscall_openat", "__syscall_pipe", + "__syscall_poll", "__syscall_readlinkat", "__syscall_recvfrom", "__syscall_recvmsg", @@ -287,7 +288,6 @@ "_mmap_js", "_msync_js", "_munmap_js", - "_poll_js", "_setitimer_js", "_timegm_js", "_tzset_js", @@ -1437,6 +1437,7 @@ "env.__syscall_newfstatat", "env.__syscall_openat", "env.__syscall_pipe", + "env.__syscall_poll", "env.__syscall_readlinkat", "env.__syscall_recvfrom", "env.__syscall_recvmsg", @@ -1470,7 +1471,6 @@ "env._mmap_js", "env._msync_js", "env._munmap_js", - "env._poll_js", "env._setitimer_js", "env._timegm_js", "env._tzset_js", @@ -1889,7 +1889,6 @@ "__syscall_munmap", "__syscall_pause", "__syscall_pipe2", - "__syscall_poll", "__syscall_prlimit64", "__syscall_pselect6", "__syscall_recvmmsg", @@ -3751,7 +3750,6 @@ "$__syscall_munmap", "$__syscall_pause", "$__syscall_pipe2", - "$__syscall_poll", "$__syscall_prlimit64", "$__syscall_pselect6", "$__syscall_recvmmsg", diff --git a/test/codesize/test_codesize_minimal_pthreads.json b/test/codesize/test_codesize_minimal_pthreads.json index d1e1883b60cdc..010b98550b44f 100644 --- a/test/codesize/test_codesize_minimal_pthreads.json +++ b/test/codesize/test_codesize_minimal_pthreads.json @@ -1,23 +1,23 @@ { - "a.out.js": 7694, - "a.out.js.gz": 3815, - "a.out.nodebug.wasm": 19604, - "a.out.nodebug.wasm.gz": 9079, - "total": 27298, - "total_gz": 12894, + "a.out.js": 7744, + "a.out.js.gz": 3845, + "a.out.nodebug.wasm": 19729, + "a.out.nodebug.wasm.gz": 9140, + "total": 27473, + "total_gz": 12985, "sent": [ "a (memory)", "b (emscripten_get_now)", "c (exit)", "d (_emscripten_thread_set_strongref)", - "e (fd_write)", - "f (emscripten_runtime_keepalive_check)", - "g (emscripten_resize_heap)", - "h (emscripten_exit_with_live_runtime)", - "i (emscripten_check_blocking_allowed)", - "j (_emscripten_thread_mailbox_await)", - "k (_emscripten_thread_cleanup)", - "l (_emscripten_receive_on_main_thread_js)", + "e (_emscripten_receive_on_main_thread_js)", + "f (fd_write)", + "g (emscripten_runtime_keepalive_check)", + "h (emscripten_resize_heap)", + "i (emscripten_exit_with_live_runtime)", + "j (emscripten_check_blocking_allowed)", + "k (_emscripten_thread_mailbox_await)", + "l (_emscripten_thread_cleanup)", "m (_emscripten_notify_mailbox_postmessage)", "n (_emscripten_init_main_thread_js)", "o (__pthread_create_js)" @@ -27,26 +27,27 @@ "b (emscripten_get_now)", "c (exit)", "d (_emscripten_thread_set_strongref)", - "e (fd_write)", - "f (emscripten_runtime_keepalive_check)", - "g (emscripten_resize_heap)", - "h (emscripten_exit_with_live_runtime)", - "i (emscripten_check_blocking_allowed)", - "j (_emscripten_thread_mailbox_await)", - "k (_emscripten_thread_cleanup)", - "l (_emscripten_receive_on_main_thread_js)", + "e (_emscripten_receive_on_main_thread_js)", + "f (fd_write)", + "g (emscripten_runtime_keepalive_check)", + "h (emscripten_resize_heap)", + "i (emscripten_exit_with_live_runtime)", + "j (emscripten_check_blocking_allowed)", + "k (_emscripten_thread_mailbox_await)", + "l (_emscripten_thread_cleanup)", "m (_emscripten_notify_mailbox_postmessage)", "n (_emscripten_init_main_thread_js)", "o (__pthread_create_js)" ], "exports": [ - "A (_emscripten_thread_free_data)", - "B (_emscripten_thread_exit)", - "C (_emscripten_check_mailbox)", - "D (emscripten_stack_set_limits)", - "E (_emscripten_stack_restore)", - "F (_emscripten_stack_alloc)", - "G (emscripten_stack_get_current)", + "A (_emscripten_run_js_on_main_thread)", + "B (_emscripten_thread_free_data)", + "C (_emscripten_thread_exit)", + "D (_emscripten_check_mailbox)", + "E (emscripten_stack_set_limits)", + "F (_emscripten_stack_restore)", + "G (_emscripten_stack_alloc)", + "H (emscripten_stack_get_current)", "p (__wasm_call_ctors)", "q (add)", "r (main)", @@ -57,7 +58,7 @@ "w (_emscripten_proxy_main)", "x (_emscripten_thread_init)", "y (_emscripten_thread_crashed)", - "z (_emscripten_run_js_on_main_thread)" + "z (_emscripten_run_js_on_main_thread_done)" ], "funcs": [ "$__emscripten_stdout_seek", @@ -92,6 +93,7 @@ "$_emscripten_check_mailbox", "$_emscripten_proxy_main", "$_emscripten_run_js_on_main_thread", + "$_emscripten_run_js_on_main_thread_done", "$_emscripten_stack_alloc", "$_emscripten_stack_restore", "$_emscripten_thread_crashed", @@ -131,6 +133,8 @@ "$emscripten_builtin_malloc", "$emscripten_futex_wait", "$emscripten_futex_wake", + "$emscripten_proxy_finish", + "$emscripten_proxy_sync_with_ctx", "$emscripten_stack_get_current", "$emscripten_stack_set_limits", "$free_ctx", @@ -149,6 +153,7 @@ "$receive_notification", "$remove_active_ctx", "$run_js_func", + "$run_js_func_with_ctx", "$sbrk", "$undo", "$unlock" diff --git a/test/codesize/test_codesize_minimal_pthreads_memgrowth.json b/test/codesize/test_codesize_minimal_pthreads_memgrowth.json index 5f7594e62933d..0d4f021e37968 100644 --- a/test/codesize/test_codesize_minimal_pthreads_memgrowth.json +++ b/test/codesize/test_codesize_minimal_pthreads_memgrowth.json @@ -1,23 +1,23 @@ { - "a.out.js": 8116, - "a.out.js.gz": 4015, - "a.out.nodebug.wasm": 19605, - "a.out.nodebug.wasm.gz": 9080, - "total": 27721, - "total_gz": 13095, + "a.out.js": 8166, + "a.out.js.gz": 4047, + "a.out.nodebug.wasm": 19730, + "a.out.nodebug.wasm.gz": 9140, + "total": 27896, + "total_gz": 13187, "sent": [ "a (memory)", "b (emscripten_get_now)", "c (exit)", "d (_emscripten_thread_set_strongref)", - "e (fd_write)", - "f (emscripten_runtime_keepalive_check)", - "g (emscripten_resize_heap)", - "h (emscripten_exit_with_live_runtime)", - "i (emscripten_check_blocking_allowed)", - "j (_emscripten_thread_mailbox_await)", - "k (_emscripten_thread_cleanup)", - "l (_emscripten_receive_on_main_thread_js)", + "e (_emscripten_receive_on_main_thread_js)", + "f (fd_write)", + "g (emscripten_runtime_keepalive_check)", + "h (emscripten_resize_heap)", + "i (emscripten_exit_with_live_runtime)", + "j (emscripten_check_blocking_allowed)", + "k (_emscripten_thread_mailbox_await)", + "l (_emscripten_thread_cleanup)", "m (_emscripten_notify_mailbox_postmessage)", "n (_emscripten_init_main_thread_js)", "o (__pthread_create_js)" @@ -27,26 +27,27 @@ "b (emscripten_get_now)", "c (exit)", "d (_emscripten_thread_set_strongref)", - "e (fd_write)", - "f (emscripten_runtime_keepalive_check)", - "g (emscripten_resize_heap)", - "h (emscripten_exit_with_live_runtime)", - "i (emscripten_check_blocking_allowed)", - "j (_emscripten_thread_mailbox_await)", - "k (_emscripten_thread_cleanup)", - "l (_emscripten_receive_on_main_thread_js)", + "e (_emscripten_receive_on_main_thread_js)", + "f (fd_write)", + "g (emscripten_runtime_keepalive_check)", + "h (emscripten_resize_heap)", + "i (emscripten_exit_with_live_runtime)", + "j (emscripten_check_blocking_allowed)", + "k (_emscripten_thread_mailbox_await)", + "l (_emscripten_thread_cleanup)", "m (_emscripten_notify_mailbox_postmessage)", "n (_emscripten_init_main_thread_js)", "o (__pthread_create_js)" ], "exports": [ - "A (_emscripten_thread_free_data)", - "B (_emscripten_thread_exit)", - "C (_emscripten_check_mailbox)", - "D (emscripten_stack_set_limits)", - "E (_emscripten_stack_restore)", - "F (_emscripten_stack_alloc)", - "G (emscripten_stack_get_current)", + "A (_emscripten_run_js_on_main_thread)", + "B (_emscripten_thread_free_data)", + "C (_emscripten_thread_exit)", + "D (_emscripten_check_mailbox)", + "E (emscripten_stack_set_limits)", + "F (_emscripten_stack_restore)", + "G (_emscripten_stack_alloc)", + "H (emscripten_stack_get_current)", "p (__wasm_call_ctors)", "q (add)", "r (main)", @@ -57,7 +58,7 @@ "w (_emscripten_proxy_main)", "x (_emscripten_thread_init)", "y (_emscripten_thread_crashed)", - "z (_emscripten_run_js_on_main_thread)" + "z (_emscripten_run_js_on_main_thread_done)" ], "funcs": [ "$__emscripten_stdout_seek", @@ -92,6 +93,7 @@ "$_emscripten_check_mailbox", "$_emscripten_proxy_main", "$_emscripten_run_js_on_main_thread", + "$_emscripten_run_js_on_main_thread_done", "$_emscripten_stack_alloc", "$_emscripten_stack_restore", "$_emscripten_thread_crashed", @@ -131,6 +133,8 @@ "$emscripten_builtin_malloc", "$emscripten_futex_wait", "$emscripten_futex_wake", + "$emscripten_proxy_finish", + "$emscripten_proxy_sync_with_ctx", "$emscripten_stack_get_current", "$emscripten_stack_set_limits", "$free_ctx", @@ -149,6 +153,7 @@ "$receive_notification", "$remove_active_ctx", "$run_js_func", + "$run_js_func_with_ctx", "$sbrk", "$undo", "$unlock" diff --git a/test/common.py b/test/common.py index 10e19997fc5d8..96196982ea6c7 100644 --- a/test/common.py +++ b/test/common.py @@ -412,7 +412,7 @@ def require_node_canary(self): self.fail('node canary required to run this test. Use EMTEST_SKIP_NODE_CANARY to skip') def require_engine(self, engine): - logger.debug(f'require_engine: {engine}') + print(f'require_engine: {engine}') if self.required_engine and self.required_engine != engine: self.skipTest(f'Skipping test that requires `{engine}` when `{self.required_engine}` was previously required') self.required_engine = engine @@ -500,7 +500,7 @@ def require_wasm_eh(self): v8 = self.get_v8() if v8: self.cflags.append('-sENVIRONMENT=shell') - self.js_engines = [v8] + self.require_engine(v8) self.v8_args.append('--experimental-wasm-exnref') return @@ -528,8 +528,8 @@ def require_jspi(self): v8 = self.get_v8() if v8: + self.require_engine(v8) self.cflags.append('-sENVIRONMENT=shell') - self.js_engines = [v8] return self.fail('either d8 or node v24 required to run JSPI tests. Use EMTEST_SKIP_JSPI to skip') diff --git a/test/core/test_poll_blocking.c b/test/core/test_poll_blocking.c index b577f380bbf94..33e9e11329690 100644 --- a/test/core/test_poll_blocking.c +++ b/test/core/test_poll_blocking.c @@ -7,7 +7,6 @@ // Duplicate of test_select_blocking.c using poll() instead of select() -#include #include #include #include @@ -25,7 +24,10 @@ void test_timeout_without_fds() { gettimeofday(&begin, NULL); assert(poll(NULL, 0, 1000) == 0); gettimeofday(&end, NULL); - assert((end.tv_sec - begin.tv_sec) * 1000000 + end.tv_usec - begin.tv_usec >= 1000000); + + long ms = (end.tv_sec - begin.tv_sec) * 1000 + (end.tv_usec - begin.tv_usec) / 1000; + printf("poll took: %ldms\n", ms); + assert(ms >= 1000); } // Check if timeout works with fds without events diff --git a/test/core/test_poll_blocking_jspi.c b/test/core/test_poll_blocking_jspi.c new file mode 100644 index 0000000000000..e056627408be7 --- /dev/null +++ b/test/core/test_poll_blocking_jspi.c @@ -0,0 +1,71 @@ +/* + * Copyright 2025 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. + */ + +// Duplicate of test_poll_blocking.c but without the pthread stuff + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// Check if timeout works without fds +void test_timeout_without_fds() { + printf("test_timeout_without_fds\n"); + struct timeval begin, end; + + gettimeofday(&begin, NULL); + assert(poll(NULL, 0, 1000) == 0); + gettimeofday(&end, NULL); + + long ms = (end.tv_sec - begin.tv_sec) * 1000 + (end.tv_usec - begin.tv_usec) / 1000; + printf("poll took: %ldms\n", ms); + assert(ms >= 1000); +} + +int pipe_shared[2]; + +void write_to_pipe(void * arg) { + const char *t = "test\n"; + write(pipe_shared[1], t, strlen(t)); +} + +// Check if poll can unblock on an event +void test_unblock_poll() { + printf("test_unblock_poll\n"); + struct timeval begin, end; + int pipe_a[2]; + + assert(pipe(pipe_a) == 0); + assert(pipe(pipe_shared) == 0); + + struct pollfd fds[2] = { + {pipe_a[0], POLLIN, 0}, + {pipe_shared[0], POLLIN, 0}, + }; + emscripten_set_timeout(write_to_pipe, 0, NULL); + gettimeofday(&begin, NULL); + assert(poll(fds, 2, -1) == 1); + gettimeofday(&end, NULL); + assert(fds[1].revents & POLLIN); + assert((end.tv_sec - begin.tv_sec) * 1000000 + end.tv_usec - begin.tv_usec >= 1000000); + + close(pipe_a[0]); close(pipe_a[1]); + close(pipe_shared[0]); close(pipe_shared[1]); +} + +int main() { + test_timeout_without_fds(); + test_unblock_poll(); + printf("done\n"); + return 0; +} diff --git a/test/core/test_select_blocking.c b/test/core/test_select_blocking.c index 6364b9b40e846..8144b49a9e690 100644 --- a/test/core/test_select_blocking.c +++ b/test/core/test_select_blocking.c @@ -23,7 +23,10 @@ void test_timeout_without_fds() { gettimeofday(&begin, NULL); assert(select(0, NULL, NULL, NULL, &tv) == 0); gettimeofday(&end, NULL); - assert((end.tv_sec - begin.tv_sec) * 1000000 + end.tv_usec - begin.tv_usec >= 1000000); + + long ms = (end.tv_sec - begin.tv_sec) * 1000 + (end.tv_usec - begin.tv_usec) / 1000; + printf("select took: %ldms\n", ms); + assert(ms >= 1000); } // Check if timeout works with fds without events diff --git a/test/test_core.py b/test/test_core.py index 6c50db4ef2d9a..a3e34a42a9691 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -8509,8 +8509,8 @@ def test_pthread_join_and_asyncify(self): self.require_jspi() self.do_runf('core/test_pthread_join_and_asyncify.c', 'joining thread!\njoined thread!', cflags=['-sJSPI', - '-sEXIT_RUNTIME=1', - '-pthread', '-sPROXY_TO_PTHREAD']) + '-sEXIT_RUNTIME=1', + '-pthread', '-sPROXY_TO_PTHREAD']) # Test basic wasm2js functionality in all core compilation modes. @no_sanitize('no wasm2js support yet in sanitizers') @@ -9673,6 +9673,11 @@ def test_select_blocking(self): def test_poll_blocking(self): self.do_runf('core/test_poll_blocking.c', cflags=['-pthread', '-sPROXY_TO_PTHREAD=1', '-sEXIT_RUNTIME=1']) + @requires_node + def test_poll_blocking_jspi(self): + self.require_jspi() + self.do_runf('core/test_poll_blocking_jspi.c', cflags=['-sJSPI']) + @parameterized({ '': ([],), 'pthread': (['-pthread'],), diff --git a/tools/emscripten.py b/tools/emscripten.py index f440909e3bacd..6cae05e49765d 100644 --- a/tools/emscripten.py +++ b/tools/emscripten.py @@ -1196,6 +1196,7 @@ def create_pointer_conversion_wrappers(metadata): '_wasmfs_node_record_dirent': '_pp_', '__dl_seterr': '_pp', '_emscripten_run_js_on_main_thread': '__p_p_', + '_emscripten_run_js_on_main_thread_done': '_pp_', '_emscripten_thread_exit': '_p', '_emscripten_thread_init': '_p_____', '_emscripten_thread_free_data': '_p', diff --git a/tools/system_libs.py b/tools/system_libs.py index f6d8183d0a216..b6fde4e9b860e 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -1367,7 +1367,6 @@ def get_files(self): 'sigtimedwait.c', 'wasi-helpers.c', 'system.c', - 'proxying_poll.c', ]) if settings.RELOCATABLE or settings.MAIN_MODULE: