Multithreading 9/N: MAIN_THREAD_EM_ASM()#5544
Conversation
13a5c2d to
02ab817
Compare
|
Could this be done as a pure-C utility function? without compiler changes to fastcomp which would also require wasm backend changes? |
|
I don't see how. There is no way to take an address of an EM_ASM function that I could pass on to another thread, and it would come down to somehow creating a lambda function in C side on the spot, and taking its address to carry over to another thread to call. Or do you have a proof of concept on how to implement? |
|
Maybe I'm missing something, but here is what an EM_ASM compiles into: We could have the macro for We do need to add some code for the JS glue on the other side (main thread), but it seems like we don't need to modify fastcomp/wasm backend code? |
jgravelle-google
left a comment
There was a problem hiding this comment.
At a high level this doesn't fundamentally break the structure of how the wasm backend handles EM_ASM, but we will need a patch in s2wasm to support generating the same metadata.
Though it turns out we don't currently share how we parse EM_ASM metadata, so if this landed as-is it technically wouldn't regress existing EM_ASM calls. However I'd much rather not diverge, because therein lies madness.
emscripten.py
Outdated
| for k, v in metadata['asmConsts'].iteritems(): | ||
| const = v[0].encode('utf-8') | ||
| sigs = v[1] | ||
| call_types = v[1][0::2] |
There was a problem hiding this comment.
That is uh, non-obvious
Instead of interleaving signature and calltype, can we make them separate sub-arrays? So this can be just:
call_types = v[1]
sigs = v[2]There was a problem hiding this comment.
Also apparently I missed this obvious step back in #5137, and create_asm_consts_wasm isn't calling all_asm_consts, which would make our lives easier.
There was a problem hiding this comment.
Instead of interleaving signature and calltype, can we make them separate sub-arrays?
Ok, that is also backwards compatible, so probably better.
emscripten.py
Outdated
| asm_const_funcs = [] | ||
| for sig in set(all_sigs): | ||
| forwarded_json['Functions']['libraryFunctions']['_emscripten_asm_const_' + sig] = 1 | ||
| for call_type, sig in set(all_sigs): |
There was a problem hiding this comment.
This will all need to be repeated in create_asm_consts_wasm. Good opportunity to extract more common parts?
There was a problem hiding this comment.
Not sure what to extract here. Refactoring the code a little, as requested in your comment below as well.
emscripten.py
Outdated
| forwarded_json['Functions']['libraryFunctions']['_emscripten_asm_const_' + call_type + sig] = 1 | ||
| args = ['a%d' % i for i in range(len(sig)-1)] | ||
| all_args = ['code'] + args | ||
| if shared.Settings.USE_PTHREADS and call_type == 'sync_on_main_thread_': proxy_to_main_thread = ' if (ENVIRONMENT_IS_PTHREAD) { ' + ('Runtime.warnOnce("proxying function " + code); ' if shared.Settings.PTHREADS_DEBUG else '') + 'return _emscripten_sync_run_in_browser_thread_' + sig + '(-1 - ' + ','.join(all_args) + '); } \n' |
There was a problem hiding this comment.
These lines are pretty long. Can they be broken up?
There's a lot of repetition as well, where things only differ slightly (e.g., Runtime.warnOnce(message)), some intermediate variables would probably go a long way here.
There was a problem hiding this comment.
What on earth is '(-1 - ' + ','.join(all_args) + '); doing? Call this but with the first argument (the function index iirc) plus one, negated? Why?
My guess is it's a sign to defer the call to the main thread, undo the transform, and call the index there, but if so that is magic, and not in a good way.
There was a problem hiding this comment.
What on earth is '(-1 - ' + ','.join(all_args) + '); doing?
The proxying mechanism here is the same as the proxying mechanism for regular compiled C function pointers, so the concept here is that positive ordinals 1, 2, 3, ... are regarded as pointers to compiled code functions, and to reuse the same mechanism, negative ordinals -1, -2, -3, ... are used to refer to EM_ASM blocks. So this code is remapping the sequence 0, 1, 2, ... over to -1, -2, -3, .... I think I had a comment somewhere about this, but adding one here as well.
Tried to do this via a macro approach, but I don't see how to do this without doing some changes at least to the backend. The issue is that at #define EM_ASM() time in C code, there is no concept of the ID ordinal of the EM_ASM block, and when the EM_ASM block is executing, it does not get passed an integer that would denote what its own EM_ASM ordinal number is that could be passed out to reference the right block. In the current form, this would require sending the whole EM_ASM block as an eval()able string. Having the proxying decision happen inside the invocation instead of the ASM_CONSTS block does have a small code size win, since there are fewer signatures than there are functions, so if multiple functions with same signature need to be proxied, there is code size overhead added only in the particular signature invoker. |
02ab817 to
f5f0f94
Compare
|
Updated this PR and addressed comments. This is good for a second pass of review? |
f5f0f94 to
03081a8
Compare
jgravelle-google
left a comment
There was a problem hiding this comment.
Overall seems fine to me. No support included for wasm backend, but we I believe we should be able to implement the current approach in roughly the same way, and I also believe this won't change anything in that pipeline.
| # Tests various different ways to invoke the MAIN_THREAD_EM_ASM(), MAIN_THREAD_EM_ASM_INT() and MAIN_THREAD_EM_ASM_DOUBLE() macros. | ||
| # This test is identical to test_em_asm_2, just search-replaces EM_ASM to MAIN_THREAD_EM_ASM on the test file. That way if new | ||
| # test cases are added to test_em_asm_2.cpp for EM_ASM, they will also get tested in MAIN_THREAD_EM_ASM form. | ||
| def test_main_thread_em_asm(self): |
There was a problem hiding this comment.
@no_wasm_backend these please
…that allow synchronous and asynchronous proxying of EM_ASM calls to main browser thread.
03081a8 to
8a058f5
Compare
… version that does not specify the call types
|
This caused total CI failure because of syntax error. ( |
|
Ops, thanks for the quick observation on this! |
|
Some tests are still failing, should be investigated more. |
|
Closure complains with |
|
Oh, we have the issue that we need a new compiler build tag go live first before this will work. Disabled the relevant tests for now, pending that update - because otherwise we'll get annoying unrelated failures that diminish the value of the PR try builds. |
|
Except that many more tests are failing... includes:
|
|
If this is a large amount of tests and it can't be fixed quickly, perhaps we should back out this PR? In the future, we should be careful to look at CI results before merging. If this is going to be a general problem, of any change to the LLVM build causing this type of situation, then perhaps we need to rethink our CI approach here. Right now it gets |
|
Sorry for the breakage - fix in #5838. |
|
|
Maybe we really should maintain our own Docker image for testing. (It might not need to use the emsdk, whichever way is easiest.) I've not really used Docker that way myself, not sure how much effort it would be to keep an image constantly up to date. |
|
I pushed a 1.37.23 version, and now there is a failing test because the emsdk gets 1.37.22. @juj's bots should finish building it and then things will work. But I think this makes it even more important that we have more control over our CI testing. Optimally when we push a new version we'd update a Docker image with it. |
|
I think pushing a new version will always cause a test failure as long as we depend on other tools having same version tags. Building Docker image won't help here as it also takes time as much as @juj's bots do. |
This adds support for routing EM_ASM() calls to either synchronously or asynchronously proxy to the main thread. This is conceptually symmetric to #5531, which does sync and async calls to JS library functions.
Requires emscripten-core/emscripten-fastcomp#196 to land at the same time.