-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Restore the dynCall() and dynCall_sig() API into the build #13296
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
Conversation
…E_LEGACY_DYNCALLS=1 is passed. Add support for dynCall() in MINIMAL_RUNTIME builds (that was not implemented before).
One question I have is whether we should rename the setting |
Looks good to me. I agree we could rename this option. If we are going to pick a new name I would go with just If its not too annoying would you mind splitting out the MINIMAL_RUNTIME part? Is there a reason you chose a different interface for MINIMAL_RUNTIME? Why not just do Going forward is the plan to transition from binaryen-generated dynCalls to table-base JS dynCalls? Presumably we could keep the exact same API while making this transition? I hope we can eventually remove the dynCall generation from binaryen |
Renamed to
I would rather not, if that is ok. This PR is already quite small that I hope it is well reviewable, I would like to keep the wrangling simple, and I already started splitting up that previous PR.
There does not exist Module['dynCall_v'] = dynCall_v = asm['k'];
Module['dynCall_ii'] = dynCall_ii = asm['l'];
...
function dynCall(sig, ...) {
Module['dynCall_'+sig].apply(null, ...);
} whereas with MINIMAL_RUNTIME we do not have the var dynCall_v, dynCalls_ii, dynCalls = {};
...
dynCalls['v'] = dynCall_v = asm['k'];
dynCalls['ii'] = dynCall_ii = asm['l'];
...
function dynCall(sig, ...) {
dynCalls[sig].apply(null, ...);
} It does not make sense to use that structure for classic runtime, because it already has the
Yes, that is the intent. When cached, table-based calls are faster than dynCalls (+61.7% faster for 'void' signatures, +81.2% for complex 'iffddii' signatures for static dispatch). We just need a good path to migrate forward from dynCalls to table-based calls. The issue here is that the change is global affecting everyone's code, so we need a way to let all JS code authors have a way to update at independent times before we drop dynCalls. However I do notice this does have an effect that table-based lookups penalize Firefox performance on dynamic signatures (+27.7% slower vs dynamic dispatch via dynCall() on 'iffddii' signature), although even with that slowdown, Firefox is still more than three times faster than Chrome and Safari, so it's probably ok. Safari gets ~+18% faster, .
While we do the transition, I propose we keep the same API. But in general the |
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. I think I understand (and agree with) the direction now. Thanks for explaining.
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 test here could be merged with test_dyncall_specific
, I think? Adding to there would be shorter. lgtm with that.
tests/test_core.py
Outdated
@@ -8270,6 +8270,19 @@ def test_gl_main_module(self): | |||
self.set_setting('MAIN_MODULE') | |||
self.do_runf(path_from_root('tests', 'core', 'test_gl_get_proc_address.c')) | |||
|
|||
def test_dyncalls(self): |
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.
def test_dyncalls(self): | |
@also_with_wasm_bigint | |
def test_dyncalls(self): |
That will verify the emscripten.py changes here.
The current test failure in |
…Call_sig # Conflicts: # src/parseTools.js
self.do_run_in_out_file_test('tests', 'core', 'test_dyncalls.c') | ||
|
||
self.emcc_args += ['-s', 'MINIMAL_RUNTIME=1', '-s', 'DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=["$dynCall"]'] | ||
self.do_run_in_out_file_test('tests', 'core', 'test_dyncalls.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.
Sorry, I'm not sure sure what is going on with this test?
Why why where these lines added to this test rather than creating a new one? This test has a loop over 3 different modes but these 2 new do_run_in_out_file_test
don't seem to use the loop variables at all.
Should the first part be it own test alongside test_dyncalls_minimal_runtime
below? The second part looks identical to test_dyncalls_minimal_runtime
.. unless I'm missing something?
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 why where these lines added to this test rather than creating a new one?
@kripken asked me in #13296 (review) to merge these two tests together. Good point about misplacing the tests inside the loop, I'll follow up with a PR to fix.
|
||
// 2. Access a function pointer using the convenience/legacy 'dynCall' function (32-bit ABI) | ||
// (this form should never be used, it is suboptimal for performance, but provided for legacy compatibility) | ||
var ret = dynCall('iii', funcPtr, [2, 3]); // Available only in WASM_BIGINT != 2 builds |
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 remove the comments about WASM_BIGINT == 2 (which I don't think we planning to add in the end right?)
(Sorry for the late feedback).
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.
Thanks, sharp eye, posted #13352 to fix.
@juj, I was hoping to look into removal the underlying dynCall stuff in favor of just using the wasm table. Can you share some info about the kind of benchmark you wrote for this? Was it just a simple microbenchmark calling and empty function in a loop, or something more complex? |
Also, you might have mentioned this somewhere already, but do you have users/codebases that depend heavily on the performance of dynCalls (i.e. making many dyncalls per frame?). |
Unfortunately at Unity we are not ready for this. If we'd remove dynCall support in Emscripten, we would then need to have some kind of emulation layer adopted in Unity codebase. (though this would be a bit tricky to do, since the knowledge of possible signatures come at build time) The challenge we have at Unity is that the We have shipped the new table based mechanism API, and have prepared documentation for this change. These are in beta channels currently. In this area btw, I find that we don't quite have documentation for makeDynCall or getWasmTableEntry in Emscripten, so the "good" APIs are currently not as discoverable as they could. I think this is something we should look to improve.
Looks like the benchmark was here: #12733 (comment) |
Restore the dynCall() and dynCall_sig() API into the build when -s USE_LEGACY_DYNCALLS=1 is passed. Add support for dynCall() in MINIMAL_RUNTIME builds (that was not implemented before).
This makes -s USE_LEGACY_DYNCALLS=1 a user-facing option.