Skip to content

Commit

Permalink
awaitable inlined js
Browse files Browse the repository at this point in the history
  • Loading branch information
hedwigz committed Jan 6, 2025
1 parent 2396d3e commit 2f9607c
Show file tree
Hide file tree
Showing 19 changed files with 217 additions and 20 deletions.
2 changes: 1 addition & 1 deletion src/jsifier.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,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}, 0${args ? ', ' : ''}${args});
${body}
}\n`;
});
Expand Down
21 changes: 19 additions & 2 deletions src/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -1599,7 +1599,11 @@ addToLibrary({
'$proxyToMainThread'
#endif
],
$runMainThreadEmAsm: (emAsmAddr, sigPtr, argbuf, sync) => {
/**
* @param {number=} asyncAwait Optionally, when set, expect func to return a Promise
* and await for it to resolve.
*/
$runMainThreadEmAsm: (emAsmAddr, sigPtr, argbuf, sync, asnycAwait) => {
var args = readEmAsmArgs(sigPtr, argbuf);
#if PTHREADS
if (ENVIRONMENT_IS_PTHREAD) {
Expand All @@ -1612,7 +1616,7 @@ addToLibrary({
// of using __proxy. (And dor simplicity, do the same in the sync
// case as well, even though it's not strictly necessary, to keep the two
// code paths as similar as possible on both sides.)
return proxyToMainThread(0, emAsmAddr, sync, ...args);
return proxyToMainThread(0, emAsmAddr, sync, asnycAwait, ...args);
}
#endif
#if ASSERTIONS
Expand All @@ -1623,6 +1627,19 @@ addToLibrary({
emscripten_asm_const_int_sync_on_main_thread__deps: ['$runMainThreadEmAsm'],
emscripten_asm_const_int_sync_on_main_thread: (emAsmAddr, sigPtr, argbuf) => runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 1),

emscripten_asm_const_int_await_on_main_thread__deps: ['$runMainThreadEmAsm'],
emscripten_asm_const_int_await_on_main_thread: (emAsmAddr, sigPtr, argbuf) => {
#if PTHREADS
if (ENVIRONMENT_IS_PTHREAD) {
return runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, /*sync=*/1, /*asyncAwait=*/1);
}
#endif
#if ASSERTIONS
assert((typeof ENVIRONMENT_IS_PTHREAD !== 'undefined' && ENVIRONMENT_IS_PTHREAD), "emscripten_asm_const_int_await_on_main_thread is not available on the main thread");
#endif
return runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, /*sync*/1, /*asyncAwait=*/1);
},

emscripten_asm_const_ptr_sync_on_main_thread__deps: ['$runMainThreadEmAsm'],
emscripten_asm_const_ptr_sync_on_main_thread: (emAsmAddr, sigPtr, argbuf) => runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 1),

Expand Down
35 changes: 31 additions & 4 deletions src/library_pthread.js
Original file line number Diff line number Diff line change
Expand Up @@ -893,9 +893,9 @@ var LibraryPThread = {
$proxyToMainThreadPtr: (...args) => BigInt(proxyToMainThread(...args)),
#endif
$proxyToMainThread__deps: ['$stackSave', '$stackRestore', '$stackAlloc', '_emscripten_run_on_main_thread_js', ...i53ConversionDeps],
$proxyToMainThread__deps: ['$stackSave', '$stackRestore', '$stackAlloc', '_emscripten_run_on_main_thread_js', '_emscripten_await_on_main_thread_js', ...i53ConversionDeps],
$proxyToMainThread__docs: '/** @type{function(number, (number|boolean), ...number)} */',
$proxyToMainThread: (funcIndex, emAsmAddr, sync, ...callArgs) => {
$proxyToMainThread: (funcIndex, emAsmAddr, sync, asyncAwait, ...callArgs) => {
// EM_ASM proxying is done by passing a pointer to the address of the EM_ASM
// content as `emAsmAddr`. JS library proxying is done by passing an index
// into `proxiedJSCallArgs` as `funcIndex`. If `emAsmAddr` is non-zero then
Expand Down Expand Up @@ -933,7 +933,12 @@ var LibraryPThread = {
HEAPF64[b + i] = arg;
#endif
}
var rtn = __emscripten_run_on_main_thread_js(funcIndex, emAsmAddr, serializedNumCallArgs, args, sync);
var rtn;
if (asyncAwait) {
rtn = __emscripten_await_on_main_thread_js(funcIndex, emAsmAddr, serializedNumCallArgs, args);
} else {
rtn = __emscripten_run_on_main_thread_js(funcIndex, emAsmAddr, serializedNumCallArgs, args, sync);
}
stackRestore(sp);
return rtn;
},
Expand All @@ -944,7 +949,11 @@ var LibraryPThread = {
_emscripten_receive_on_main_thread_js__deps: [
'$proxyToMainThread',
'$proxiedJSCallArgs'],
_emscripten_receive_on_main_thread_js: (funcIndex, emAsmAddr, callingThread, numCallArgs, args) => {
/**
* @param {number=} promiseCtx Optionally, when set, expect func to return a Promise
* and use promiseCtx to signal awaiting pthread.
*/
_emscripten_receive_on_main_thread_js: (funcIndex, emAsmAddr, callingThread, numCallArgs, args, promiseCtx) => {
// 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
Expand Down Expand Up @@ -983,6 +992,24 @@ var LibraryPThread = {
PThread.currentProxiedOperationCallerThread = callingThread;
var rtn = func(...proxiedJSCallArgs);
PThread.currentProxiedOperationCallerThread = 0;
if (promiseCtx) {
#if ASSERTIONS
assert(!!rtn.then, 'Return value of proxied function expected to be a Promise but got' + rtn);
#endif
rtn.then(res => {
#if MEMORY64
// In memory64 mode some proxied functions return bigint/pointer but
// our return type is i53/double.
if (typeof res == "bigint") {
res = bigintToI53Checked(res);
}
#endif
__emscripten_proxy_promise_finish(promiseCtx, res);
}).catch(err => {
__emscripten_proxy_promise_finish(promiseCtx, err);
});
return;
}
#if MEMORY64
// In memory64 mode some proxied functions return bigint/pointer but
// our return type is i53/double.
Expand Down
3 changes: 2 additions & 1 deletion src/library_sigs.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,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: 'dippipp',
_emscripten_runtime_keepalive_clear__sig: 'v',
_emscripten_system__sig: 'ip',
_emscripten_thread_cleanup__sig: 'vp',
Expand Down Expand Up @@ -566,6 +566,7 @@ sigs = {
emscripten_asm_const_double__sig: 'dppp',
emscripten_asm_const_double_sync_on_main_thread__sig: 'dppp',
emscripten_asm_const_int__sig: 'ippp',
emscripten_asm_const_int_await_on_main_thread__sig: 'ippp',
emscripten_asm_const_int_sync_on_main_thread__sig: 'ippp',
emscripten_asm_const_ptr__sig: 'pppp',
emscripten_asm_const_ptr_sync_on_main_thread__sig: 'pppp',
Expand Down
11 changes: 11 additions & 0 deletions system/include/emscripten/em_asm.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ __attribute__((nothrow))
int emscripten_asm_const_int_sync_on_main_thread(
const char* code, const char* arg_sigs, ...);
__attribute__((nothrow))
int emscripten_asm_const_int_await_on_main_thread(
const char* code, const char* arg_sigs, ...);
__attribute__((nothrow))
void* emscripten_asm_const_ptr_sync_on_main_thread(
const char* code, const char* arg_sigs, ...);
__attribute__((nothrow))
Expand All @@ -51,6 +54,7 @@ void emscripten_asm_const_async_on_main_thread(
#define EM_ASM_PTR(...) EM_ASM_ERROR
#define EM_ASM_DOUBLE(...) EM_ASM_ERROR
#define MAIN_THREAD_EM_ASM(...) EM_ASM_ERROR
#define MAIN_THREAD_EM_ASM_AWAIT(...) EM_ASM_ERROR
#define MAIN_THREAD_EM_ASM_INT(...) EM_ASM_ERROR
#define MAIN_THREAD_EM_ASM_PTR(...) EM_ASM_ERROR
#define MAIN_THREAD_EM_ASM_DOUBLE(...) EM_ASM_ERROR
Expand Down Expand Up @@ -250,6 +254,13 @@ const char __em_asm_sig_builder<__em_asm_type_tuple<Args...> >::buffer[] = { __e
// functions.
#define MAIN_THREAD_EM_ASM(code, ...) ((void)emscripten_asm_const_int_sync_on_main_thread(CODE_EXPR(#code) _EM_ASM_PREP_ARGS(__VA_ARGS__)))

// Runs the given Javascript code on the main browser thread.
// It must be called from a non-main thread.
// The code must return a promise, and this function will wait for the promise
// to resolve or reject, essentially blocking the calling thread until then.
// In either case the function will return an integer, which is the result of the promise.
#define MAIN_THREAD_EM_ASM_AWAIT(code, ...) emscripten_asm_const_int_await_on_main_thread(CODE_EXPR(#code) _EM_ASM_PREP_ARGS(__VA_ARGS__))

// Runs the given JavaScript code synchronously on the main browser thread, and
// returns an integer back.
// The same considerations apply as with MAIN_THREAD_EM_ASM().
Expand Down
44 changes: 43 additions & 1 deletion system/lib/pthread/proxying.c
Original file line number Diff line number Diff line change
Expand Up @@ -591,12 +591,15 @@ typedef struct proxied_js_func_t {
double* argBuffer;
double result;
bool owned;
// Only used when the underlying js func is async.
// Can be null when the function is sync.
em_proxying_ctx * ctx;
} 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->numArgs, f->argBuffer);
f->funcIndex, f->emAsmAddr, f->callingThread, f->numArgs, f->argBuffer, f->ctx);
if (f->owned) {
free(f->argBuffer);
free(f);
Expand All @@ -615,6 +618,7 @@ double _emscripten_run_on_main_thread_js(int func_index,
.numArgs = num_args,
.argBuffer = buffer,
.owned = false,
.ctx = NULL,
};

em_proxying_queue* q = emscripten_proxy_get_system_queue();
Expand Down Expand Up @@ -642,3 +646,41 @@ double _emscripten_run_on_main_thread_js(int func_index,
}
return 0;
}

static void call_proxied_js_task_with_ctx(em_proxying_ctx* ctx, void* arg) {
task* t = arg;
proxied_js_func_t* p = t->arg;
p->ctx = ctx;
t->func(t->arg);
}

double _emscripten_await_on_main_thread_js(int func_index,
void* em_asm_addr,
int num_args,
double* buffer) {
em_proxying_queue* q = emscripten_proxy_get_system_queue();
pthread_t target = emscripten_main_runtime_thread_id();

proxied_js_func_t f = {
.funcIndex = func_index,
.emAsmAddr = em_asm_addr,
.callingThread = pthread_self(),
.numArgs = num_args,
.argBuffer = buffer,
.owned = false,
};
task t = {.func = run_js_func, .arg = &f};

if (!emscripten_proxy_sync_with_ctx(q, target, call_proxied_js_task_with_ctx, &t)) {
assert(false && "emscripten_proxy_sync_with_ctx failed");
return 0;
}
return f.result;
}

void _emscripten_proxy_promise_finish(em_proxying_ctx* ctx, void* res) {
task* t = (task*)ctx->arg;
proxied_js_func_t* func = (proxied_js_func_t*)t->arg;
func->result = (double)(intptr_t)res;
emscripten_proxy_finish(ctx);
}
2 changes: 1 addition & 1 deletion system/lib/pthread/threading_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ 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);

// Return non-zero if the calling thread supports Atomic.wait (For example
// if called from the main browser thread, this function will return zero
Expand Down
25 changes: 25 additions & 0 deletions test/core/test_main_thread_async_em_asm_await.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2024 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 <emscripten.h>
#include <stdio.h>

int main()
{
printf("Before MAIN_THREAD_EM_ASM_AWAIT\n");
int res = MAIN_THREAD_EM_ASM_AWAIT({
out('Inside MAIN_THREAD_EM_ASM_AWAIT: ' + $0 + ' ' + $1);
const asyncOp = new Promise((resolve,reject) => {
setTimeout(() => {
out('Inside asyncOp');
resolve(2);
}, 1000);
});
return asyncOp;
}, 42, 3.5);
printf("After MAIN_THREAD_EM_ASM_AWAIT\n");
printf("result: %d\n", res);
return 0;
}
5 changes: 5 additions & 0 deletions test/core/test_main_thread_async_em_asm_await.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Before MAIN_THREAD_EM_ASM_AWAIT
Inside MAIN_THREAD_EM_ASM_AWAIT: 42 3.5
Inside asyncOp
After MAIN_THREAD_EM_ASM_AWAIT
result: 2
34 changes: 34 additions & 0 deletions test/core/test_main_thread_async_em_asm_await_reject.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2024 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 <emscripten.h>
#include <stdio.h>
#include <pthread.h>

int main()
{
// start new thread
pthread_t thread;
pthread_create(&thread, NULL, [](void*) -> void* {
printf("Before MAIN_THREAD_EM_ASM_AWAIT\n");
int res = MAIN_THREAD_EM_ASM_AWAIT({
out('Inside MAIN_THREAD_EM_ASM_AWAIT: ' + $0 + ' ' + $1);
const asyncOp = new Promise((resolve,reject) => {
setTimeout(() => {
out('Inside asyncOp');
reject(2);
}, 1000);
});
return asyncOp;
}, 42, 3.5);
printf("After MAIN_THREAD_EM_ASM_AWAIT rejected\n");
printf("result: %d\n", res);
return NULL;
}, NULL);

// wait for thread to finish
pthread_join(thread, NULL);
return 0;
}
5 changes: 5 additions & 0 deletions test/core/test_main_thread_async_em_asm_await_reject.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Before MAIN_THREAD_EM_ASM_AWAIT
Inside MAIN_THREAD_EM_ASM_AWAIT: 42 3.5
Inside asyncOp
After MAIN_THREAD_EM_ASM_AWAIT rejected
result: 2
16 changes: 9 additions & 7 deletions test/other/codesize/test_codesize_minimal_pthreads.exports
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
A (_emscripten_thread_exit)
B (_emscripten_check_mailbox)
C (emscripten_stack_set_limits)
D (_emscripten_stack_restore)
E (_emscripten_stack_alloc)
F (emscripten_stack_get_current)
A (_emscripten_proxy_promise_finish)
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)
Expand All @@ -14,4 +16,4 @@ v (_emscripten_proxy_main)
w (_emscripten_thread_init)
x (_emscripten_thread_crashed)
y (_emscripten_run_on_main_thread_js)
z (_emscripten_thread_free_data)
z (_emscripten_await_on_main_thread_js)
5 changes: 5 additions & 0 deletions test/other/codesize/test_codesize_minimal_pthreads.funcs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ $__wasi_syscall_ret
$__wasm_call_ctors
$__wasm_init_memory
$__wasm_init_tls
$_emscripten_await_on_main_thread_js
$_emscripten_check_mailbox
$_emscripten_proxy_main
$_emscripten_proxy_promise_finish
$_emscripten_run_on_main_thread_js
$_emscripten_stack_alloc
$_emscripten_stack_restore
Expand All @@ -50,6 +52,7 @@ $a_swap
$add
$call_callback_then_free_ctx
$call_cancel_then_free_ctx
$call_proxied_js_task_with_ctx
$call_then_finish_task
$call_with_ctx
$cancel_active_ctxs
Expand All @@ -69,6 +72,8 @@ $emscripten_builtin_free
$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
Expand Down
2 changes: 1 addition & 1 deletion test/other/codesize/test_codesize_minimal_pthreads.gzsize
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4186
4219
2 changes: 1 addition & 1 deletion test/other/codesize/test_codesize_minimal_pthreads.jssize
Original file line number Diff line number Diff line change
@@ -1 +1 @@
8728
8810
2 changes: 1 addition & 1 deletion test/other/codesize/test_codesize_minimal_pthreads.size
Original file line number Diff line number Diff line change
@@ -1 +1 @@
19405
19656
Loading

0 comments on commit 2f9607c

Please sign in to comment.