Skip to content

Multithreading 9/N: MAIN_THREAD_EM_ASM()#5544

Merged
juj merged 6 commits intoemscripten-core:incomingfrom
juj:em_asm_on_main_thread
Nov 22, 2017
Merged

Multithreading 9/N: MAIN_THREAD_EM_ASM()#5544
juj merged 6 commits intoemscripten-core:incomingfrom
juj:em_asm_on_main_thread

Conversation

@juj
Copy link
Collaborator

@juj juj commented Aug 30, 2017

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.

@juj juj force-pushed the em_asm_on_main_thread branch from 13a5c2d to 02ab817 Compare September 8, 2017 08:52
@kripken
Copy link
Member

kripken commented Sep 12, 2017

Could this be done as a pure-C utility function? without compiler changes to fastcomp which would also require wasm backend changes?

@juj
Copy link
Collaborator Author

juj commented Sep 13, 2017

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?

@kripken
Copy link
Member

kripken commented Sep 14, 2017

Maybe I'm missing something, but here is what an EM_ASM compiles into:

var ASM_CONSTS = [function() { Module.print('hai') }];
function _emscripten_asm_const_i(code) {
  return ASM_CONSTS[code]();
}
[..]
function _main() {
 [..]
 $call = _emscripten_asm_const_i(0)|0;
 [..]
}

We could have the macro for EM_ASM emit the code to do the sync wait if necessary, as well as add the proxying code in JS, something conceptually like

#define MAIN_THREAD_EM_ASM(code) \
  { \
    EM_ASM( Module.proxyToMainThread(code) ); \ // wrap the user JS code with proxy code
    DO_SYNC_WAIT(); \ // use C pthreads to wait (or, do it in JS?)
  }

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?

@dschuff
Copy link
Member

dschuff commented Sep 14, 2017

+cc @jgravelle-google

Copy link
Contributor

@jgravelle-google jgravelle-google left a comment

Choose a reason for hiding this comment

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

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]
Copy link
Contributor

Choose a reason for hiding this comment

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

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]

Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

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):
Copy link
Contributor

Choose a reason for hiding this comment

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

This will all need to be repeated in create_asm_consts_wasm. Good opportunity to extract more common parts?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

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'
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

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.

@juj
Copy link
Collaborator Author

juj commented Nov 10, 2017

We could have the macro for EM_ASM emit the code to do the sync wait if necessary, as well as add the proxying code in JS, something conceptually like

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.

@juj juj force-pushed the em_asm_on_main_thread branch from 02ab817 to f5f0f94 Compare November 10, 2017 16:23
@juj
Copy link
Collaborator Author

juj commented Nov 10, 2017

Updated this PR and addressed comments. This is good for a second pass of review?

Copy link
Contributor

@jgravelle-google jgravelle-google left a comment

Choose a reason for hiding this comment

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

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):
Copy link
Contributor

Choose a reason for hiding this comment

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

@no_wasm_backend these please

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

check

@juj juj force-pushed the em_asm_on_main_thread branch from 03081a8 to 8a058f5 Compare November 22, 2017 11:52
@juj juj merged commit ea92fdd into emscripten-core:incoming Nov 22, 2017
@saschanaz
Copy link
Collaborator

This caused total CI failure because of syntax error. (@@no_wasm_backend is not a valid decorator syntax)

@juj
Copy link
Collaborator Author

juj commented Nov 22, 2017

Ops, thanks for the quick observation on this!

@saschanaz
Copy link
Collaborator

Some tests are still failing, should be investigated more.

@saschanaz
Copy link
Collaborator

Closure complains with Variable _emscripten_asm_const_ii declared more than once.

@juj
Copy link
Collaborator Author

juj commented Nov 22, 2017

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.

@saschanaz
Copy link
Collaborator

Except that many more tests are failing... includes:

  • browser.test_binaryen_native
  • browser.test_fs_lz4fs_package
  • browser.test_html5_mouse
  • browser.test_html5_webgl_create_context
  • browser.test_sdl2glshader
  • browser.test_separate_asm
  • asm2/asm2f/asm2i/asm3.test_fs_nodefs_rw
  • asm2.test_webidl

@kripken
Copy link
Member

kripken commented Nov 22, 2017

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 emsdk latest, but maybe that's not good enough.

@juj
Copy link
Collaborator Author

juj commented Nov 23, 2017

Sorry for the breakage - fix in #5838.

@saschanaz
Copy link
Collaborator

saschanaz commented Nov 23, 2017

Right now it gets emsdk latest, but maybe that's not good enough.

  1. We may create emsdk install latest-beta which would be updated whenever any breakage occurs on latest.
  2. Otherwise we may maintain our own Docker image with emscripten SDK built-in, which would also be updated in the same way.

@kripken
Copy link
Member

kripken commented Nov 27, 2017

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.

@kripken
Copy link
Member

kripken commented Nov 28, 2017

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.

@saschanaz
Copy link
Collaborator

saschanaz commented Nov 28, 2017

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants