Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: await promise code from sync c #23043

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/jsifier.mjs
Original file line number Diff line number Diff line change
@@ -374,7 +374,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`;
});
18 changes: 16 additions & 2 deletions src/lib/libcore.js
Original file line number Diff line number Diff line change
@@ -1589,12 +1589,13 @@ addToLibrary({
return runEmAsmFunction(code, sigPtr, argbuf);
},

$runMainThreadEmAsm__docs: '/** @param {number=} asyncAwait */',
$runMainThreadEmAsm__deps: ['$readEmAsmArgs',
#if PTHREADS
'$proxyToMainThread'
#endif
],
$runMainThreadEmAsm: (emAsmAddr, sigPtr, argbuf, sync) => {
$runMainThreadEmAsm: (emAsmAddr, sigPtr, argbuf, sync, asyncAwait) => {
var args = readEmAsmArgs(sigPtr, argbuf);
#if PTHREADS
if (ENVIRONMENT_IS_PTHREAD) {
@@ -1607,7 +1608,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, asyncAwait, ...args);
}
#endif
#if ASSERTIONS
@@ -1618,6 +1619,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),

37 changes: 32 additions & 5 deletions src/lib/libpthread.js
Original file line number Diff line number Diff line change
@@ -899,9 +899,9 @@ var LibraryPThread = {
$proxyToMainThreadPtr: (...args) => BigInt(proxyToMainThread(...args)),
#endif

$proxyToMainThread__deps: ['$stackSave', '$stackRestore', '$stackAlloc', '_emscripten_run_on_main_thread_js', ...i53ConversionDeps],
$proxyToMainThread__docs: '/** @type{function(number, (number|boolean), ...number)} */',
$proxyToMainThread: (funcIndex, emAsmAddr, sync, ...callArgs) => {
$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, (number|undefined), ...number)} */',
$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
@@ -939,7 +939,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;
},
@@ -950,7 +955,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
@@ -989,6 +998,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, 0);
});
return 0;
}
#if MEMORY64
// In memory64 mode some proxied functions return bigint/pointer but
// our return type is i53/double.
3 changes: 2 additions & 1 deletion src/lib/libsigs.js
Original file line number Diff line number Diff line change
@@ -325,7 +325,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',
@@ -572,6 +572,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',
11 changes: 11 additions & 0 deletions system/include/emscripten/em_asm.h
Original file line number Diff line number Diff line change
@@ -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))
@@ -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
@@ -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().
48 changes: 47 additions & 1 deletion system/lib/pthread/proxying.c
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the new code in this file does not implement the API surface in proxying.h, it would be best to move it to a different file and leave proxying.c unchanged. It would also be best to implement the new functionality in terms of the public API in proxying.h rather than using internal implementation details. For example, there should be no need to reach inside the em_proxying_ctx object.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although I see we already have _emscripten_run_on_main_thread_js at the end of proxying.c. Adding more code there would be fine, but it should still use only the public API from proxying.h.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you prefer that I introduce a new function similar to _emscripten_run_on_main_thread_js but that uses only public proxying.c API?
The problem with introducing a new function similar to emscripten_run_on_main_thread_js is the "path" to that function call.
It'd a bit tedious. And it'd modify the whole stack leading to this logic.
This is why I chose to introduce the new function param asyncAwait (previously promise) just so I could "ride" the whole functions call path.
But, I don't insist on this at all.
As for using the public API - I agree that we can do that if I indeed fork from _emscripten_run_on_main_thread_js
Let me know what you think

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need to use internal details of the proxying mechanism if you don't create an entirely new version of _emscripten_run_on_main_thread_js? Can't you use emscripten_proxy_sync_with_ctx to synchronously wait for the promise to be resolved on the main thread instead of introducing a new emscripten_proxy_async_await function?

Copy link
Author

@hedwigz hedwigz Dec 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The challenging part was to pass the em_proxying_ctx all the way down to _emscripten_receive_on_main_thread_js, through the task and proxied_js_func_t wrappers, because I have to call _emscripten_proxy_promise_finish from within _emscripten_receive_on_main_thread_js.
This is also why I added em_proxying_ctx * ctx to proxied_js_func_t and moved it upwards.
I just pushed a significant simplification and deleted most of the previous code in proxying.c
WDYT?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I think of it, it is a little weird that proxied_js_func_t has a field ctx that if set, causes _emscripten_receive_on_main_thread_js to expect a promise. Maybe I should name it differently?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the ctx and promiseCtx names are fine, but it would be good to add comments explaining that they can be null.

Original file line number Diff line number Diff line change
@@ -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);
@@ -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();
@@ -642,3 +646,45 @@ 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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do think there is enough code shared with _emscripten_run_on_main_thread_js that it may make sense to combine them, but I don't feel strongly about it. If you do keep them separate, you should explicitly initialize the .ctx field to NULL in _emscripten_run_on_main_thread_js to make sure it is initialized .

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;
if (res == NULL) {
func->result = 0;
} else {
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
@@ -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
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(new Error('asyncOp rejected'));
}, 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: 0
2 changes: 1 addition & 1 deletion test/other/codesize/test_codesize_hello_dylink.funcs
Original file line number Diff line number Diff line change
@@ -4,8 +4,8 @@ $__fwritex
$__stdio_write
$__towrite
$__wasm_apply_data_relocs
$__wasm_apply_global_relocs
$__wasm_call_ctors
$__wasm_start
$_emscripten_stack_alloc
$_emscripten_stack_restore
$dlcalloc
2 changes: 1 addition & 1 deletion test/other/codesize/test_codesize_hello_dylink.size
Original file line number Diff line number Diff line change
@@ -1 +1 @@
8176
9712
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)
@@ -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
@@ -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
@@ -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
@@ -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
Loading
Oops, something went wrong.