-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Add MAIN_THREAD_EM_ASM_PROMISE_AWAIT #23043
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1572,12 +1572,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) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about calling this |
||
var args = readEmAsmArgs(sigPtr, argbuf); | ||
#if PTHREADS | ||
if (ENVIRONMENT_IS_PTHREAD) { | ||
|
@@ -1590,7 +1591,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 | ||
|
@@ -1601,6 +1602,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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The convention we have is for |
||
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"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we wrap this whole new function in This this assertion can simply be |
||
#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), | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -870,9 +870,9 @@ var LibraryPThread = { | |
$proxyToMainThreadPtr: (...args) => BigInt(proxyToMainThread(...args)), | ||
#endif | ||
|
||
$proxyToMainThread__deps: ['$stackSave', '$stackRestore', '$stackAlloc', '_emscripten_run_on_main_thread_js'], | ||
$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'], | ||
$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 | ||
|
@@ -910,7 +910,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; | ||
}, | ||
|
@@ -920,8 +925,13 @@ var LibraryPThread = { | |
|
||
_emscripten_receive_on_main_thread_js__deps: [ | ||
'$proxyToMainThread', | ||
'$proxiedJSCallArgs'], | ||
_emscripten_receive_on_main_thread_js: (funcIndex, emAsmAddr, callingThread, numCallArgs, args) => { | ||
'$proxiedJSCallArgs', | ||
'_emscripten_proxy_promise_finish'], | ||
/** | ||
* @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 | ||
|
@@ -960,6 +970,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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think you need the |
||
#endif | ||
rtn.then(res => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We put braces around the |
||
#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. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Although I see we already have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you prefer that I introduce a new function similar to There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The challenging part was to pass the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now that I think of it, it is a little weird that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the |
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No space before |
||
} 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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do think there is enough code shared with |
||
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); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,10 @@ | ||
{ | ||
"a.html": 552, | ||
"a.html.gz": 380, | ||
"a.js": 8609, | ||
"a.js.gz": 3806, | ||
"a.wasm": 7290, | ||
"a.wasm.gz": 3343, | ||
"total": 16451, | ||
"total_gz": 7529 | ||
"a.js": 9094, | ||
"a.js.gz": 3993, | ||
"a.wasm": 7332, | ||
"a.wasm.gz": 3369, | ||
"total": 16994, | ||
"total_gz": 7742 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,10 @@ | ||
{ | ||
"a.html": 552, | ||
"a.html.gz": 380, | ||
"a.js": 6469, | ||
"a.js.gz": 2800, | ||
"a.wasm": 9083, | ||
"a.wasm.gz": 4682, | ||
"total": 16104, | ||
"total_gz": 7862 | ||
"a.js": 6940, | ||
"a.js.gz": 2999, | ||
"a.wasm": 9133, | ||
"a.wasm.gz": 4710, | ||
"total": 16625, | ||
"total_gz": 8089 | ||
} |
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() | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Open brace on same line as function decl. |
||
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; | ||
} |
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
// 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 <emscripten.h> | ||
#include <stdio.h> | ||
#include <pthread.h> | ||
|
||
void *thread_func(void* arg) { | ||
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If code that uses threads its better to use |
||
return NULL; | ||
} | ||
|
||
int main() { | ||
// start new thread | ||
pthread_t thread; | ||
pthread_create(&thread, NULL, thread_func, NULL); | ||
|
||
// wait for thread to finish | ||
pthread_join(thread, NULL); | ||
return 0; | ||
} |
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can/should this be
boolean=
? Is the__docs
actually needed?