-
Notifications
You must be signed in to change notification settings - Fork 3.3k
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
base: main
Are you sure you want to change the base?
WIP: await promise code from sync c #23043
Conversation
src/library.js
Outdated
|
||
emscripten_asm_const_double_sync_on_main_thread: 'emscripten_asm_const_int_sync_on_main_thread', | ||
emscripten_asm_const_async_on_main_thread__deps: ['$runMainThreadEmAsm'], | ||
emscripten_asm_const_async_on_main_thread: (emAsmAddr, sigPtr, argbuf) => runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 0), | ||
emscripten_asm_const_async_on_main_thread: (emAsmAddr, sigPtr, argbuf) => runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 0, 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.
I think you can just leave off the final argument here and elsewhere that you want promise
to be false. This will result in promise
being undefined at runtime.
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.
Agreed
@@ -1896,6 +1896,13 @@ def test_main_thread_em_asm(self, args): | |||
def test_main_thread_async_em_asm(self, args, force_c=False): | |||
self.do_core_test('test_main_thread_async_em_asm.cpp', emcc_args=args, force_c=force_c) | |||
|
|||
@needs_dylink | |||
@parameterized({ | |||
'pthreads': (['-pthread', '-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME'], False), |
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.
Normally with a parameterized test I would expect at least two different variants (normally we have a default/empty one).
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.
I added the empty one, and the test expects for an error. I did not handle the case of calling this macro from the main thread. Do you think we should add that?
src/library_pthread.js
Outdated
#endif | ||
__emscripten_proxy_promise_finish(res, promiseCtx); | ||
}).catch(err => { | ||
__emscripten_proxy_promise_finish(err, promiseCtx); |
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.
I don't see where this is defined
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.
@sbc100 Thanks for taking the time to review this. I addressed all of your comments and did the following:
|
@sbc100 ping 🤞 |
src/library.js
Outdated
@@ -1655,6 +1655,16 @@ 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_promise_on_main_thread__deps: ['$runMainThreadEmAsm'], | |||
emscripten_asm_const_int_await_promise_on_main_thread: (emAsmAddr, sigPtr, argbuf) => { |
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.
How about we drop the promise
part so this becomes emscripten_asm_const_int_await_on_main_thread
and the macro is MAIN_THREAD_EM_ASM_AWAIT
?
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.
Yes, I like that better as well
src/library.js
Outdated
@@ -1631,7 +1631,7 @@ addToLibrary({ | |||
'$proxyToMainThread' | |||
#endif | |||
], | |||
$runMainThreadEmAsm: (emAsmAddr, sigPtr, argbuf, sync) => { | |||
$runMainThreadEmAsm: (emAsmAddr, sigPtr, argbuf, sync, promise) => { |
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.
Maybe call this argument "await" too.. although that could be confused with the JS keyword. Maybe "asyncAwait"?
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.
👍
src/library.js
Outdated
return runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 1, 1); | ||
} | ||
#endif | ||
throw new Error('call to emscripten_asm_const_int_await_promise_on_main_thread is only supported from pthread (but was called from main thread)'); |
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.
We probably don't want this long error message in release builds. How about:
#if ASSERTIONS
assert(ENVIRONMENT_IS_PTHREAD. "emscripten_asm_const_int_await_promise_on_main_thread is not available on the main thread");
#endif
return runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 1, 1);
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.
👍
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.
I had to modify the assert condition because it errored: ReferenceError: ENVIRONMENT_IS_PTHREAD is not defined
assert((typeof ENVIRONMENT_IS_PTHREAD !== 'undefined' && ENVIRONMENT_IS_PTHREAD), "emscripten_asm_const_int_await_on_main_thread is not available on the main thread");
Not sure if there's a prettier way
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.
Use #if ASSERTIONS && PTHREADS
src/library.js
Outdated
emscripten_asm_const_int_await_promise_on_main_thread: (emAsmAddr, sigPtr, argbuf) => { | ||
#if PTHREADS | ||
if (ENVIRONMENT_IS_PTHREAD) { | ||
return runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 1, 1); |
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 you add comments here for these contants e.g.
return runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, /*sync=*/1, /*asyncAwait=*/1);
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.
👍
src/library_pthread.js
Outdated
#endif | ||
if (!rtn.then) { | ||
throw new Error('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 comment
The reason will be displayed to describe this comment to others. Learn more.
Need for both that assertion and runtime check here. I think you can just remove these 3 lines.
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.
oops. nice catch
system/lib/pthread/proxying.c
Outdated
f->funcIndex, f->emAsmAddr, f->callingThread, f->numArgs, f->argBuffer, (void*)f->ctx); | ||
} | ||
|
||
void _emscripten_proxy_promise_finish(void* res, em_proxying_ctx* ctx) { |
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.
I would expect the context to be the first arugment here.
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.
👍
system/lib/pthread/proxying.c
Outdated
assert(false && "emscripten_proxy_promise failed"); | ||
return 0; | ||
} | ||
return f.result; | ||
} |
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.
Maybe structure this as:
if (sync) {
if (promise) {
} else {
}
return f.result;
}
Also the fact that we have int sync
and int promise
here as a separate arguments, but it doesn't make sense for promise
to be set without sync
seem a little odd.
At the very least we should probably assert that promise is never true when sync is not.
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.
@tlively would you mind taking a look, since this contains changes to proxying.c
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.
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.
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.
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.
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.
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
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.
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?
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.
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?
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.
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?
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.
I think the ctx
and promiseCtx
names are fine, but it would be good to add comments explaining that they can be null.
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.
Great, this looks a lot simpler overall, even though I agree that it's a little awkward to get the ctx
into the right place. Thanks!
system/lib/pthread/proxying.c
Outdated
@@ -13,6 +13,7 @@ | |||
#include <stdbool.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <stdio.h> |
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.
Looks like this was left over from debugging.
system/lib/pthread/proxying.c
Outdated
@@ -35,6 +36,17 @@ static em_proxying_queue system_proxying_queue = { | |||
.capacity = 0, | |||
}; | |||
|
|||
typedef struct proxied_js_func_t { |
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.
Let's move this down to its original location to make the diff smaller.
@@ -642,3 +651,34 @@ double _emscripten_run_on_main_thread_js(int func_index, | |||
} | |||
return 0; | |||
} | |||
|
|||
double _emscripten_await_on_main_thread_js(int func_index, |
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.
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 .
system/lib/pthread/proxying.c
Outdated
proxied_js_func_t* func = (proxied_js_func_t*)t->arg; | ||
func->result = (double)(intptr_t)res; | ||
emscripten_proxy_finish(ctx); | ||
} |
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.
Please make sure there is a newline at the end of the file. It would also be good to run git clang-format
to fix any formatting errors.
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.
I think the ctx
and promiseCtx
names are fine, but it would be good to add comments explaining that they can be null.
5aef76b
to
58889f9
Compare
I am sorry I completely messed up git while trying to rebase. |
84fcbd2
to
ae2357d
Compare
@tlively any tips for passing all the CI tests? |
proxying.c looks good to me. It looks like the flake8 bot is failing because of a formatting issue. The other CI bots might be aborting early because of that issue. Merging in main to pick up any unrelated fixes could also help. |
The fact that flake8 is being included at all in CI makes me think that perhaps you are not up-to-date on your branch? The flake8 step was replaced with a step call |
Can you try rebase onto to merging with the latest changes on main? |
464bc2f
to
4416f4d
Compare
4416f4d
to
6ebb010
Compare
b8e7971
to
2f9607c
Compare
2f9607c
to
b71710b
Compare
Introduced a new macro
MAIN_THREAD_EM_ASM_PROMISE_AWAIT
which is used to write javascript code that returns a promise and block the C code from progressing until the promise is resolved (or errors).The motivation for this was discussed before
We have multiple web workers, each of them calls javascript code which is async and has to be ran on the main thread and need to wait for the promise to be resolved.
I am open for suggestions on how to add this more elegantly
TODO: