Skip to content

Commit 0eb8406

Browse files
committed
awaitable inlined js
1 parent 242af00 commit 0eb8406

17 files changed

+217
-19
lines changed

src/jsifier.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ function(${args}) {
357357
return `
358358
${async_}function(${args}) {
359359
if (ENVIRONMENT_IS_PTHREAD)
360-
return ${proxyFunc}(${proxiedFunctionTable.length}, 0, ${+sync}${args ? ', ' : ''}${args});
360+
return ${proxyFunc}(${proxiedFunctionTable.length}, 0, ${+sync}, 0${args ? ', ' : ''}${args});
361361
${body}
362362
}\n`;
363363
});

src/lib/libcore.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1594,12 +1594,13 @@ addToLibrary({
15941594
return runEmAsmFunction(code, sigPtr, argbuf);
15951595
},
15961596

1597+
$runMainThreadEmAsm__docs: '/** @param {number=} asyncAwait */',
15971598
$runMainThreadEmAsm__deps: ['$readEmAsmArgs',
15981599
#if PTHREADS
15991600
'$proxyToMainThread'
16001601
#endif
16011602
],
1602-
$runMainThreadEmAsm: (emAsmAddr, sigPtr, argbuf, sync) => {
1603+
$runMainThreadEmAsm: (emAsmAddr, sigPtr, argbuf, sync, asyncAwait) => {
16031604
var args = readEmAsmArgs(sigPtr, argbuf);
16041605
#if PTHREADS
16051606
if (ENVIRONMENT_IS_PTHREAD) {
@@ -1612,7 +1613,7 @@ addToLibrary({
16121613
// of using __proxy. (And dor simplicity, do the same in the sync
16131614
// case as well, even though it's not strictly necessary, to keep the two
16141615
// code paths as similar as possible on both sides.)
1615-
return proxyToMainThread(0, emAsmAddr, sync, ...args);
1616+
return proxyToMainThread(0, emAsmAddr, sync, asyncAwait, ...args);
16161617
}
16171618
#endif
16181619
#if ASSERTIONS
@@ -1623,6 +1624,19 @@ addToLibrary({
16231624
emscripten_asm_const_int_sync_on_main_thread__deps: ['$runMainThreadEmAsm'],
16241625
emscripten_asm_const_int_sync_on_main_thread: (emAsmAddr, sigPtr, argbuf) => runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 1),
16251626

1627+
emscripten_asm_const_int_await_on_main_thread__deps: ['$runMainThreadEmAsm'],
1628+
emscripten_asm_const_int_await_on_main_thread: (emAsmAddr, sigPtr, argbuf) => {
1629+
#if PTHREADS
1630+
if (ENVIRONMENT_IS_PTHREAD) {
1631+
return runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, /*sync=*/1, /*asyncAwait=*/1);
1632+
}
1633+
#endif
1634+
#if ASSERTIONS
1635+
assert((typeof ENVIRONMENT_IS_PTHREAD !== 'undefined' && ENVIRONMENT_IS_PTHREAD), "emscripten_asm_const_int_await_on_main_thread is not available on the main thread");
1636+
#endif
1637+
return runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, /*sync*/1, /*asyncAwait=*/1);
1638+
},
1639+
16261640
emscripten_asm_const_ptr_sync_on_main_thread__deps: ['$runMainThreadEmAsm'],
16271641
emscripten_asm_const_ptr_sync_on_main_thread: (emAsmAddr, sigPtr, argbuf) => runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 1),
16281642

src/lib/libpthread.js

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -894,9 +894,9 @@ var LibraryPThread = {
894894
$proxyToMainThreadPtr: (...args) => BigInt(proxyToMainThread(...args)),
895895
#endif
896896
897-
$proxyToMainThread__deps: ['$stackSave', '$stackRestore', '$stackAlloc', '_emscripten_run_on_main_thread_js', ...i53ConversionDeps],
898-
$proxyToMainThread__docs: '/** @type{function(number, (number|boolean), ...number)} */',
899-
$proxyToMainThread: (funcIndex, emAsmAddr, sync, ...callArgs) => {
897+
$proxyToMainThread__deps: ['$stackSave', '$stackRestore', '$stackAlloc', '_emscripten_run_on_main_thread_js', '_emscripten_await_on_main_thread_js', ...i53ConversionDeps],
898+
$proxyToMainThread__docs: '/** @type{function(number, (number|boolean), number, (number|undefined), ...number)} */',
899+
$proxyToMainThread: (funcIndex, emAsmAddr, sync, asyncAwait, ...callArgs) => {
900900
// EM_ASM proxying is done by passing a pointer to the address of the EM_ASM
901901
// content as `emAsmAddr`. JS library proxying is done by passing an index
902902
// into `proxiedJSCallArgs` as `funcIndex`. If `emAsmAddr` is non-zero then
@@ -934,7 +934,12 @@ var LibraryPThread = {
934934
HEAPF64[b + i] = arg;
935935
#endif
936936
}
937-
var rtn = __emscripten_run_on_main_thread_js(funcIndex, emAsmAddr, serializedNumCallArgs, args, sync);
937+
var rtn;
938+
if (asyncAwait) {
939+
rtn = __emscripten_await_on_main_thread_js(funcIndex, emAsmAddr, serializedNumCallArgs, args);
940+
} else {
941+
rtn = __emscripten_run_on_main_thread_js(funcIndex, emAsmAddr, serializedNumCallArgs, args, sync);
942+
}
938943
stackRestore(sp);
939944
return rtn;
940945
},
@@ -945,7 +950,11 @@ var LibraryPThread = {
945950
_emscripten_receive_on_main_thread_js__deps: [
946951
'$proxyToMainThread',
947952
'$proxiedJSCallArgs'],
948-
_emscripten_receive_on_main_thread_js: (funcIndex, emAsmAddr, callingThread, numCallArgs, args) => {
953+
/**
954+
* @param {number=} promiseCtx Optionally, when set, expect func to return a Promise
955+
* and use promiseCtx to signal awaiting pthread.
956+
*/
957+
_emscripten_receive_on_main_thread_js: (funcIndex, emAsmAddr, callingThread, numCallArgs, args, promiseCtx) => {
949958
// Sometimes we need to backproxy events to the calling thread (e.g.
950959
// HTML5 DOM events handlers such as
951960
// emscripten_set_mousemove_callback()), so keep track in a globally
@@ -984,6 +993,24 @@ var LibraryPThread = {
984993
PThread.currentProxiedOperationCallerThread = callingThread;
985994
var rtn = func(...proxiedJSCallArgs);
986995
PThread.currentProxiedOperationCallerThread = 0;
996+
if (promiseCtx) {
997+
#if ASSERTIONS
998+
assert(!!rtn.then, 'Return value of proxied function expected to be a Promise but got' + rtn);
999+
#endif
1000+
rtn.then(res => {
1001+
#if MEMORY64
1002+
// In memory64 mode some proxied functions return bigint/pointer but
1003+
// our return type is i53/double.
1004+
if (typeof res == "bigint") {
1005+
res = bigintToI53Checked(res);
1006+
}
1007+
#endif
1008+
__emscripten_proxy_promise_finish(promiseCtx, res);
1009+
}).catch(err => {
1010+
__emscripten_proxy_promise_finish(promiseCtx, 0);
1011+
});
1012+
return;
1013+
}
9871014
#if MEMORY64
9881015
// In memory64 mode some proxied functions return bigint/pointer but
9891016
// our return type is i53/double.

src/lib/libsigs.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ sigs = {
324324
_emscripten_notify_mailbox_postmessage__sig: 'vpp',
325325
_emscripten_push_main_loop_blocker__sig: 'vppp',
326326
_emscripten_push_uncounted_main_loop_blocker__sig: 'vppp',
327-
_emscripten_receive_on_main_thread_js__sig: 'dippip',
327+
_emscripten_receive_on_main_thread_js__sig: 'dippipp',
328328
_emscripten_runtime_keepalive_clear__sig: 'v',
329329
_emscripten_system__sig: 'ip',
330330
_emscripten_thread_cleanup__sig: 'vp',
@@ -571,6 +571,7 @@ sigs = {
571571
emscripten_asm_const_double__sig: 'dppp',
572572
emscripten_asm_const_double_sync_on_main_thread__sig: 'dppp',
573573
emscripten_asm_const_int__sig: 'ippp',
574+
emscripten_asm_const_int_await_on_main_thread__sig: 'ippp',
574575
emscripten_asm_const_int_sync_on_main_thread__sig: 'ippp',
575576
emscripten_asm_const_ptr__sig: 'pppp',
576577
emscripten_asm_const_ptr_sync_on_main_thread__sig: 'pppp',

system/include/emscripten/em_asm.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ __attribute__((nothrow))
2828
int emscripten_asm_const_int_sync_on_main_thread(
2929
const char* code, const char* arg_sigs, ...);
3030
__attribute__((nothrow))
31+
int emscripten_asm_const_int_await_on_main_thread(
32+
const char* code, const char* arg_sigs, ...);
33+
__attribute__((nothrow))
3134
void* emscripten_asm_const_ptr_sync_on_main_thread(
3235
const char* code, const char* arg_sigs, ...);
3336
__attribute__((nothrow))
@@ -51,6 +54,7 @@ void emscripten_asm_const_async_on_main_thread(
5154
#define EM_ASM_PTR(...) EM_ASM_ERROR
5255
#define EM_ASM_DOUBLE(...) EM_ASM_ERROR
5356
#define MAIN_THREAD_EM_ASM(...) EM_ASM_ERROR
57+
#define MAIN_THREAD_EM_ASM_AWAIT(...) EM_ASM_ERROR
5458
#define MAIN_THREAD_EM_ASM_INT(...) EM_ASM_ERROR
5559
#define MAIN_THREAD_EM_ASM_PTR(...) EM_ASM_ERROR
5660
#define MAIN_THREAD_EM_ASM_DOUBLE(...) EM_ASM_ERROR
@@ -250,6 +254,13 @@ const char __em_asm_sig_builder<__em_asm_type_tuple<Args...> >::buffer[] = { __e
250254
// functions.
251255
#define MAIN_THREAD_EM_ASM(code, ...) ((void)emscripten_asm_const_int_sync_on_main_thread(CODE_EXPR(#code) _EM_ASM_PREP_ARGS(__VA_ARGS__)))
252256

257+
// Runs the given Javascript code on the main browser thread.
258+
// It must be called from a non-main thread.
259+
// The code must return a promise, and this function will wait for the promise
260+
// to resolve or reject, essentially blocking the calling thread until then.
261+
// In either case the function will return an integer, which is the result of the promise.
262+
#define MAIN_THREAD_EM_ASM_AWAIT(code, ...) emscripten_asm_const_int_await_on_main_thread(CODE_EXPR(#code) _EM_ASM_PREP_ARGS(__VA_ARGS__))
263+
253264
// Runs the given JavaScript code synchronously on the main browser thread, and
254265
// returns an integer back.
255266
// The same considerations apply as with MAIN_THREAD_EM_ASM().

system/lib/pthread/proxying.c

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -591,12 +591,15 @@ typedef struct proxied_js_func_t {
591591
double* argBuffer;
592592
double result;
593593
bool owned;
594+
// Only used when the underlying js func is async.
595+
// Can be null when the function is sync.
596+
em_proxying_ctx * ctx;
594597
} proxied_js_func_t;
595598

596599
static void run_js_func(void* arg) {
597600
proxied_js_func_t* f = (proxied_js_func_t*)arg;
598601
f->result = _emscripten_receive_on_main_thread_js(
599-
f->funcIndex, f->emAsmAddr, f->callingThread, f->numArgs, f->argBuffer);
602+
f->funcIndex, f->emAsmAddr, f->callingThread, f->numArgs, f->argBuffer, f->ctx);
600603
if (f->owned) {
601604
free(f->argBuffer);
602605
free(f);
@@ -615,6 +618,7 @@ double _emscripten_run_on_main_thread_js(int func_index,
615618
.numArgs = num_args,
616619
.argBuffer = buffer,
617620
.owned = false,
621+
.ctx = NULL,
618622
};
619623

620624
em_proxying_queue* q = emscripten_proxy_get_system_queue();
@@ -642,3 +646,45 @@ double _emscripten_run_on_main_thread_js(int func_index,
642646
}
643647
return 0;
644648
}
649+
650+
static void call_proxied_js_task_with_ctx(em_proxying_ctx* ctx, void* arg) {
651+
task* t = arg;
652+
proxied_js_func_t* p = t->arg;
653+
p->ctx = ctx;
654+
t->func(t->arg);
655+
}
656+
657+
double _emscripten_await_on_main_thread_js(int func_index,
658+
void* em_asm_addr,
659+
int num_args,
660+
double* buffer) {
661+
em_proxying_queue* q = emscripten_proxy_get_system_queue();
662+
pthread_t target = emscripten_main_runtime_thread_id();
663+
664+
proxied_js_func_t f = {
665+
.funcIndex = func_index,
666+
.emAsmAddr = em_asm_addr,
667+
.callingThread = pthread_self(),
668+
.numArgs = num_args,
669+
.argBuffer = buffer,
670+
.owned = false,
671+
};
672+
task t = {.func = run_js_func, .arg = &f};
673+
674+
if (!emscripten_proxy_sync_with_ctx(q, target, call_proxied_js_task_with_ctx, &t)) {
675+
assert(false && "emscripten_proxy_sync_with_ctx failed");
676+
return 0;
677+
}
678+
return f.result;
679+
}
680+
681+
void _emscripten_proxy_promise_finish(em_proxying_ctx* ctx, void* res) {
682+
task* t = (task*)ctx->arg;
683+
proxied_js_func_t* func = (proxied_js_func_t*)t->arg;
684+
if (res == NULL) {
685+
func->result = 0;
686+
} else {
687+
func->result = (double)(intptr_t)res;
688+
}
689+
emscripten_proxy_finish(ctx);
690+
}

system/lib/pthread/threading_internal.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ int __pthread_create_js(struct __pthread *thread, const pthread_attr_t *attr, vo
9696
int _emscripten_default_pthread_stack_size();
9797
void __set_thread_state(pthread_t ptr, int is_main, int is_runtime, int can_block);
9898

99-
double _emscripten_receive_on_main_thread_js(int funcIndex, void* emAsmAddr, pthread_t callingThread, int numCallArgs, double* args);
99+
double _emscripten_receive_on_main_thread_js(int funcIndex, void* emAsmAddr, pthread_t callingThread, int numCallArgs, double* args, void *ctx);
100100

101101
// Return non-zero if the calling thread supports Atomic.wait (For example
102102
// if called from the main browser thread, this function will return zero
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2024 The Emscripten Authors. All rights reserved.
2+
// Emscripten is available under two separate licenses, the MIT license and the
3+
// University of Illinois/NCSA Open Source License. Both these licenses can be
4+
// found in the LICENSE file.
5+
6+
#include <emscripten.h>
7+
#include <stdio.h>
8+
9+
int main()
10+
{
11+
printf("Before MAIN_THREAD_EM_ASM_AWAIT\n");
12+
int res = MAIN_THREAD_EM_ASM_AWAIT({
13+
out('Inside MAIN_THREAD_EM_ASM_AWAIT: ' + $0 + ' ' + $1);
14+
const asyncOp = new Promise((resolve,reject) => {
15+
setTimeout(() => {
16+
out('Inside asyncOp');
17+
resolve(2);
18+
}, 1000);
19+
});
20+
return asyncOp;
21+
}, 42, 3.5);
22+
printf("After MAIN_THREAD_EM_ASM_AWAIT\n");
23+
printf("result: %d\n", res);
24+
return 0;
25+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Before MAIN_THREAD_EM_ASM_AWAIT
2+
Inside MAIN_THREAD_EM_ASM_AWAIT: 42 3.5
3+
Inside asyncOp
4+
After MAIN_THREAD_EM_ASM_AWAIT
5+
result: 2
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright 2024 The Emscripten Authors. All rights reserved.
2+
// Emscripten is available under two separate licenses, the MIT license and the
3+
// University of Illinois/NCSA Open Source License. Both these licenses can be
4+
// found in the LICENSE file.
5+
6+
#include <emscripten.h>
7+
#include <stdio.h>
8+
#include <pthread.h>
9+
10+
int main()
11+
{
12+
// start new thread
13+
pthread_t thread;
14+
pthread_create(&thread, NULL, [](void*) -> void* {
15+
printf("Before MAIN_THREAD_EM_ASM_AWAIT\n");
16+
int res = MAIN_THREAD_EM_ASM_AWAIT({
17+
out('Inside MAIN_THREAD_EM_ASM_AWAIT: ' + $0 + ' ' + $1);
18+
const asyncOp = new Promise((resolve,reject) => {
19+
setTimeout(() => {
20+
out('Inside asyncOp');
21+
reject(new Error('asyncOp rejected'));
22+
}, 1000);
23+
});
24+
return asyncOp;
25+
}, 42, 3.5);
26+
printf("After MAIN_THREAD_EM_ASM_AWAIT rejected\n");
27+
printf("result: %d\n", res);
28+
return NULL;
29+
}, NULL);
30+
31+
// wait for thread to finish
32+
pthread_join(thread, NULL);
33+
return 0;
34+
}

0 commit comments

Comments
 (0)