Skip to content

Commit d17af1e

Browse files
committed
Allow synchronous proxying of async JS functions
This new mode allows the proxied work itself to be asynchronous (i.e. return a promise). The new behaviour is triggered by marking a functions as both `__proxy: 'sync'` and also `__async: true`. We can use this new mode to replace the bespoke proxying code that exists for `dlsync` as well as the `poll` system call. For the poll system call this bespoke code was added in #25523 and #25990, but can now be completely removed. In addition to this, because the `poll` syscall is now marked as `__async` it automatically works under JSPI too. One downside of this new mode is that the proxied work requires the main thread event loop to run. Unlike the synchronous proxied work that can complete even when we are deeply nested. Fixes: #25970
1 parent 43af76f commit d17af1e

20 files changed

+257
-224
lines changed

src/closure-externs/closure-externs.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,3 +268,5 @@ Symbol.dispose;
268268
var os = {};
269269

270270
AudioWorkletProcessor.parameterDescriptors;
271+
272+
Promise.withResolvers = function() {};

src/jsifier.mjs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -446,20 +446,28 @@ function(${args}) {
446446
error(`JS library error: invalid proxying mode '${symbol}__proxy: ${proxyingMode}' specified`);
447447
}
448448
if (SHARED_MEMORY && proxyingMode != 'none') {
449-
const sync = proxyingMode === 'sync';
450449
if (PTHREADS) {
451450
snippet = modifyJSFunction(snippet, (args, body, async_, oneliner) => {
452451
if (oneliner) {
453452
body = `return ${body}`;
454453
}
454+
let sync = '0'
455+
if (proxyingMode === 'sync') {
456+
const isAsyncFunction = LibraryManager.library[symbol + '__async'];
457+
if (isAsyncFunction) {
458+
sync = '2';
459+
} else {
460+
sync = '1';
461+
}
462+
}
455463
const rtnType = sig && sig.length ? sig[0] : null;
456464
const proxyFunc =
457465
MEMORY64 && rtnType == 'p' ? 'proxyToMainThreadPtr' : 'proxyToMainThread';
458466
deps.push('$' + proxyFunc);
459467
return `
460468
${async_}function(${args}) {
461469
if (ENVIRONMENT_IS_PTHREAD)
462-
return ${proxyFunc}(${proxiedFunctionTable.length}, 0, ${+sync}${args ? ', ' : ''}${args});
470+
return ${proxyFunc}(${proxiedFunctionTable.length}, 0, ${sync}${args ? ', ' : ''}${args});
463471
${body}
464472
}\n`;
465473
});

src/lib/libpthread.js

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -982,8 +982,9 @@ var LibraryPThread = {
982982
983983
_emscripten_receive_on_main_thread_js__deps: [
984984
'$proxyToMainThread',
985+
'_emscripten_run_js_on_main_thread_done',
985986
'$proxiedJSCallArgs'],
986-
_emscripten_receive_on_main_thread_js: (funcIndex, emAsmAddr, callingThread, bufSize, args) => {
987+
_emscripten_receive_on_main_thread_js: (funcIndex, emAsmAddr, callingThread, bufSize, args, ctx, ctxArgs) => {
987988
// Sometimes we need to backproxy events to the calling thread (e.g.
988989
// HTML5 DOM events handlers such as
989990
// emscripten_set_mousemove_callback()), so keep track in a globally
@@ -1022,6 +1023,11 @@ var LibraryPThread = {
10221023
PThread.currentProxiedOperationCallerThread = callingThread;
10231024
var rtn = func(...proxiedJSCallArgs);
10241025
PThread.currentProxiedOperationCallerThread = 0;
1026+
if (ctx) {
1027+
rtn.then((rtn) => __emscripten_run_js_on_main_thread_done(ctx, ctxArgs, rtn));
1028+
return;
1029+
}
1030+
10251031
#if MEMORY64
10261032
// In memory64 mode some proxied functions return bigint/pointer but
10271033
// our return type is i53/double.
@@ -1160,14 +1166,11 @@ var LibraryPThread = {
11601166
}
11611167
},
11621168
1163-
// Asynchronous version dlsync_threads. Always run on the main thread.
1164-
// This work happens asynchronously. The `callback` is called once this work
1165-
// is completed, passing the ctx.
1166-
// TODO(sbc): Should we make a new form of __proxy attribute for JS library
1167-
// function that run asynchronously like but blocks the caller until they are
1168-
// done. Perhaps "sync_with_ctx"?
1169-
_emscripten_dlsync_threads_async__deps: ['_emscripten_proxy_dlsync_async', '$makePromise'],
1170-
_emscripten_dlsync_threads_async: (caller, callback, ctx) => {
1169+
// Asynchronous version dlsync. Always run on the main thread.
1170+
// This work happens asynchronously.
1171+
$dlsyncAsync__deps: ['_emscripten_proxy_dlsync_async', '$makePromise'],
1172+
$dlsyncAsync: async () => {
1173+
const caller = PThread.currentProxiedOperationCallerThread;
11711174
#if PTHREADS_DEBUG
11721175
dbg("_emscripten_dlsync_threads_async caller=" + ptrToString(caller));
11731176
#endif
@@ -1202,27 +1205,27 @@ var LibraryPThread = {
12021205
#if PTHREADS_DEBUG
12031206
dbg(`_emscripten_dlsync_threads_async: waiting on ${promises.length} promises`);
12041207
#endif
1205-
// Once all promises are resolved then we know all threads are in sync and
1206-
// we can call the callback.
1207-
Promise.all(promises).then(() => {
1208-
PThread.outstandingPromises = {};
1208+
await Promise.all(promises);
1209+
1210+
PThread.outstandingPromises = {};
12091211
#if PTHREADS_DEBUG
1210-
dbg('_emscripten_dlsync_threads_async done: calling callback');
1212+
dbg('_emscripten_dlsync_threads_async done');
12111213
#endif
1212-
{{{ makeDynCall('vp', 'callback') }}}(ctx);
1213-
});
12141214
},
12151215
1216-
// Synchronous version dlsync_threads. This is only needed for the case then
1216+
// Synchronous version of dlsync_threads. This is only needed for the case then
12171217
// the main thread call dlopen and in that case we have not choice but to
12181218
// synchronously block the main thread until all other threads are in sync.
12191219
// When `dlopen` is called from a worker, the worker itself is blocked but
12201220
// the operation its waiting on (on the main thread) can be async.
1221-
_emscripten_dlsync_threads__deps: ['_emscripten_proxy_dlsync'],
1222-
_emscripten_dlsync_threads: () => {
1223-
#if ASSERTIONS
1224-
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! _emscripten_dlsync_threads() can only ever be called from main thread');
1225-
#endif
1221+
_dlsync__deps: ['_emscripten_proxy_dlsync', '$dlsyncAsync'],
1222+
_dlsync__async: true,
1223+
_dlsync__proxy: 'sync',
1224+
_dlsync: () => {
1225+
const callingThread = PThread.currentProxiedOperationCallerThread;
1226+
if (callingThread) {
1227+
return dlsyncAsync()
1228+
}
12261229
for (const ptr of Object.keys(PThread.pthreads)) {
12271230
const pthread_ptr = Number(ptr);
12281231
if (!PThread.finishedThreads.has(pthread_ptr)) {

src/lib/libsigs.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ sigs = {
262262
__syscall_newfstatat__sig: 'iippi',
263263
__syscall_openat__sig: 'iipip',
264264
__syscall_pipe__sig: 'ip',
265+
__syscall_poll__sig: 'ipii',
265266
__syscall_readlinkat__sig: 'iippp',
266267
__syscall_recvfrom__sig: 'iippipp',
267268
__syscall_recvmsg__sig: 'iipiiii',
@@ -281,6 +282,7 @@ sigs = {
281282
_dlopen_js__sig: 'pp',
282283
_dlsym_catchup_js__sig: 'ppi',
283284
_dlsym_js__sig: 'pppp',
285+
_dlsync__sig: 'v',
284286
_embind_create_inheriting_constructor__sig: 'pppp',
285287
_embind_finalize_value_array__sig: 'vp',
286288
_embind_finalize_value_object__sig: 'vp',
@@ -313,8 +315,6 @@ sigs = {
313315
_embind_register_void__sig: 'vpp',
314316
_emscripten_create_wasm_worker__sig: 'ipi',
315317
_emscripten_dlopen_js__sig: 'vpppp',
316-
_emscripten_dlsync_threads__sig: 'v',
317-
_emscripten_dlsync_threads_async__sig: 'vppp',
318318
_emscripten_fetch_get_response_headers__sig: 'pipp',
319319
_emscripten_fetch_get_response_headers_length__sig: 'pi',
320320
_emscripten_fs_load_embedded_files__sig: 'vp',
@@ -329,7 +329,7 @@ sigs = {
329329
_emscripten_notify_mailbox_postmessage__sig: 'vpp',
330330
_emscripten_push_main_loop_blocker__sig: 'vppp',
331331
_emscripten_push_uncounted_main_loop_blocker__sig: 'vppp',
332-
_emscripten_receive_on_main_thread_js__sig: 'dippip',
332+
_emscripten_receive_on_main_thread_js__sig: 'dippippp',
333333
_emscripten_runtime_keepalive_clear__sig: 'v',
334334
_emscripten_system__sig: 'ip',
335335
_emscripten_thread_cleanup__sig: 'vp',
@@ -379,7 +379,6 @@ sigs = {
379379
_mmap_js__sig: 'ipiiijpp',
380380
_msync_js__sig: 'ippiiij',
381381
_munmap_js__sig: 'ippiiij',
382-
_poll_js__sig: 'ipiipp',
383382
_setitimer_js__sig: 'iid',
384383
_timegm_js__sig: 'jp',
385384
_tzset_js__sig: 'vpppp',

src/lib/libsyscall.js

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -551,15 +551,17 @@ var SyscallsLibrary = {
551551
var stream = SYSCALLS.getStreamFromFD(fd);
552552
return 0; // we can't do anything synchronously; the in-memory FS is already synced to
553553
},
554-
_poll_js__proxy: 'none',
555-
_poll_js__deps: [
554+
__syscall_poll__proxy: 'sync',
555+
__syscall_poll__async: true,
556+
__syscall_poll: (fds, nfds, timeout) => {
557+
#if PTHREADS || JSPI
556558
#if PTHREADS
557-
'_emscripten_proxy_poll_finish',
559+
const isAsyncContext = PThread.currentProxiedOperationCallerThread;
560+
#else
561+
const isAsyncContext = true;
558562
#endif
559-
],
560-
_poll_js: (fds, nfds, timeout, ctx, arg) => {
561-
#if PTHREADS
562563
// Enable event handlers only when the poll call is proxied from a worker.
564+
var { promise, resolve, reject } = Promise.withResolvers();
563565
var cleanupFuncs = [];
564566
var notifyDone = false;
565567
function asyncPollComplete(count) {
@@ -571,7 +573,7 @@ var SyscallsLibrary = {
571573
dbg('asyncPollComplete', count);
572574
#endif
573575
cleanupFuncs.forEach(cb => cb());
574-
__emscripten_proxy_poll_finish(ctx, arg, count);
576+
resolve(count);
575577
}
576578
function makeNotifyCallback(stream, pollfd) {
577579
var cb = (flags) => {
@@ -595,17 +597,18 @@ var SyscallsLibrary = {
595597
return cb;
596598
}
597599
598-
if (ctx) {
600+
if (isAsyncContext) {
599601
#if RUNTIME_DEBUG
600602
dbg('async poll start');
601603
#endif
602604
if (timeout > 0) {
603-
setTimeout(() => {
605+
var t = setTimeout(() => {
604606
#if RUNTIME_DEBUG
605-
dbg('poll: timeout');
607+
dbg('poll: timeout', timeout);
606608
#endif
607609
asyncPollComplete(0);
608610
}, timeout);
611+
cleanupFuncs.push(() => clearTimeout(t));
609612
}
610613
}
611614
#endif
@@ -619,8 +622,8 @@ var SyscallsLibrary = {
619622
var stream = FS.getStream(fd);
620623
if (stream) {
621624
if (stream.stream_ops.poll) {
622-
#if PTHREADS
623-
if (ctx && timeout) {
625+
#if PTHREADS || JSPI
626+
if (isAsyncContext && timeout) {
624627
flags = stream.stream_ops.poll(stream, timeout, makeNotifyCallback(stream, pollfd));
625628
} else
626629
#endif
@@ -634,12 +637,12 @@ var SyscallsLibrary = {
634637
{{{ makeSetValue('pollfd', C_STRUCTS.pollfd.revents, 'flags', 'i16') }}};
635638
}
636639
637-
#if PTHREADS
638-
if (ctx) {
640+
#if PTHREADS || JSPI
641+
if (isAsyncContext) {
639642
if (count || !timeout) {
640643
asyncPollComplete(count);
641644
}
642-
return 0;
645+
return promise;
643646
}
644647
#endif
645648

system/lib/libc/dynlink.c

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,6 @@ static thread_local struct dlevent* thread_local_tail = &main_event;
8383
static pthread_mutex_t write_lock = PTHREAD_MUTEX_INITIALIZER;
8484
static thread_local bool skip_dlsync = false;
8585

86-
static void dlsync();
87-
8886
static void do_write_lock() {
8987
// Once we have the lock we want to avoid automatic code sync as that would
9088
// result in a deadlock.
@@ -161,7 +159,7 @@ static void load_library_done(struct dso* p) {
161159
new_dlevent(p, -1);
162160
#ifdef _REENTRANT
163161
// Block until all other threads have loaded this module.
164-
dlsync();
162+
_dlsync();
165163
#endif
166164
// TODO: figure out some way to tell when its safe to free p->file_data. Its
167165
// 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) {
431429
}
432430
return result;
433431
}
434-
435-
static void done_sync_all(em_proxying_ctx* ctx) {
436-
dbg("done_sync_all");
437-
emscripten_proxy_finish(ctx);
438-
}
439-
440-
static void run_dlsync_async(em_proxying_ctx* ctx, void* arg) {
441-
pthread_t calling_thread = (pthread_t)arg;
442-
dbg("main_thread_dlsync calling=%p", calling_thread);
443-
_emscripten_dlsync_threads_async(calling_thread, done_sync_all, ctx);
444-
}
445-
446-
static void dlsync() {
447-
// Call dlsync process. This call will block until all threads are in sync.
448-
// This gets called after a shared library is loaded by a worker.
449-
dbg("dlsync main=%p", emscripten_main_runtime_thread_id());
450-
if (emscripten_is_main_runtime_thread()) {
451-
// dlsync was called on the main thread. In this case we have no choice by
452-
// to run the blocking version of emscripten_dlsync_threads.
453-
_emscripten_dlsync_threads();
454-
} else {
455-
// Otherwise we block here while the asynchronous version runs in the main
456-
// thread.
457-
em_proxying_queue* q = emscripten_proxy_get_system_queue();
458-
int success = emscripten_proxy_sync_with_ctx(
459-
q, emscripten_main_runtime_thread_id(), run_dlsync_async, pthread_self());
460-
assert(success);
461-
}
462-
}
463432
#endif // _REENTRANT
464433

465434
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) {
683652
new_dlevent(p, sym_index);
684653
#ifdef _REENTRANT
685654
// Block until all other threads have loaded this module.
686-
dlsync();
655+
_dlsync();
687656
#endif
688657
}
689658
dbg("__dlsym done dso:%p res:%p", p, res);

system/lib/libc/emscripten_internal.h

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -94,18 +94,7 @@ void* _dlsym_catchup_js(struct dso* handle, int sym_index);
9494

9595
int _setitimer_js(int which, double timeout);
9696

97-
// Synchronous version of "dlsync_threads". Called only on the main thread.
98-
// Runs _emscripten_dlsync_self on each of the threads that are running at
99-
// the time of the call.
100-
void _emscripten_dlsync_threads();
101-
102-
// Asynchronous version of "dlsync_threads". Called only on the main thread.
103-
// Runs _emscripten_dlsync_self on each of the threads that are running at
104-
// the time of the call. Once this is done the callback is called with the
105-
// given em_proxying_ctx.
106-
void _emscripten_dlsync_threads_async(pthread_t calling_thread,
107-
void (*callback)(em_proxying_ctx*),
108-
em_proxying_ctx* ctx);
97+
void _dlsync();
10998

11099
#ifdef _GNU_SOURCE
111100
void __call_sighandler(sighandler_t handler, int sig);

system/lib/libc/proxying_poll.c

Lines changed: 0 additions & 55 deletions
This file was deleted.

0 commit comments

Comments
 (0)