Skip to content

Use direct table access over dynCall in invoke_xx functions #11979

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

Merged
merged 2 commits into from
Aug 25, 2020
Merged

Conversation

sbc100
Copy link
Collaborator

@sbc100 sbc100 commented Aug 20, 2020

This change starts the transition away from dynCall and towards
direct use of the wasm table.

In particular this change replaces the use of dynCall in invoke_xx
functions where possible. dynCall is still needed for functions with
i64 in thier signature when WASM_BIGINT is not enabled. When
WASM_BIGINT is enabled this change removes all use of dynCall by
invoke_xx functions.

Copy link
Member

@kripken kripken left a comment

Choose a reason for hiding this comment

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

Nice work!

@sbc100
Copy link
Collaborator Author

sbc100 commented Aug 20, 2020

I'm a little worried about the cost of using the Array.slice for each invoke_xx call.

I have a couple of implementation options:

  1. Use ES6 ...rest operator (we currently can't do this because we require all our code to be ES5 still.. sigh):
function dynCallDirect(ptr, ...args) {                                           
  return wasmTable.get(ptr).apply(null, args);                                   
} 
  1. Use ES5 and Array.slice
function dynCallDirect(ptr) {                                                    
  return wasmTable.get(ptr).apply(null, Array.prototype.slice.call(arguments, 1));
} 
  1. Stamp out a different func for each signature to avoid variadic function:
function dynCallDirect_vi(ptr, a1)...
function dynCallDirect_vii(ptr, a1, a2)...
etc...

@sbc100
Copy link
Collaborator Author

sbc100 commented Aug 20, 2020

I guess I should go with option 3 since switch to ES6 is not something I want to take on right now :)

And Array.slice is costly? Can someone confirm the cost of this?

@sbc100 sbc100 force-pushed the dyn_call branch 2 times, most recently from 107f180 to b99856a Compare August 20, 2020 19:31
@kripken
Copy link
Member

kripken commented Aug 20, 2020

Array.slice does an allocation. It's not a huge cost, but it's good to avoid it, as there will be cases where it matters.

How about this, we use similar code patterns in other places:

var dynCallDirectArray = [];
function dynCallDirect(ptr) {
  dynCallDirectArray.length = arguments.length - 1;
  for (var i = 1; i < arguments.length; i++) {
    dynCallDirectArray[i - 1] = arguments[i];
  }
  return wasmTable.get(ptr).apply(null, dynCallDirectArray);
}

@sbc100
Copy link
Collaborator Author

sbc100 commented Aug 20, 2020

Array.slice does an allocation. It's not a huge cost, but it's good to avoid it, as there will be cases where it matters.

How about this, we use similar code patterns in other places:

var dynCallDirectArray = [];
function dynCallDirect(ptr) {
  dynCallDirectArray.length = arguments.length - 1;
  for (var i = 1; i < arguments.length; i++) {
    dynCallDirectArray[i - 1] = arguments[i];
  }
  return wasmTable.get(ptr).apply(null, dynCallDirectArray);
}

But compare that to:

function dynCallDirect(ptr, ...args) {                                           
  return wasmTable.get(ptr).apply(null, args);                                   
} 

Doesn't it make your you want to cry a little inside?

@kripken
Copy link
Member

kripken commented Aug 20, 2020

@sbc100 yeah... ES6 is nice!

We've said at some point we should write ES6 and use babel to downcompile it...

@sbc100
Copy link
Collaborator Author

sbc100 commented Aug 20, 2020

@sbc100 yeah... ES6 is nice!

We've said at some point we should write ES6 and use babel to downcompile it...

I know we have discussed before. Perhaps we can push it forward this time.

I know I probably sound like stuck record but can we not just consider ES6 -> ES5 conversion something that can easily be done downstream of us?

I would imagine that teams targeting old engines already have babel in their pipeline and it would be a little odd for us to embed it in addition. I suppose you can say the same for minification and closure, but we run those is specialized ways so they are little different.

Perhaps I should try to land this change as ES5 and push for ES6 separately in the coming weeks? If nothing else it would be nice code size win I imagine for most users.

@sbc100 sbc100 force-pushed the dyn_call branch 5 times, most recently from 331a776 to cf6c237 Compare August 21, 2020 12:29
@sbc100
Copy link
Collaborator Author

sbc100 commented Aug 21, 2020

OK I think all the issues with this are now sorted. I've avoided ES6 for now. This change doesn't remove all the dynCall usage but is removes a lot of it.

I have followup changes to binaryen to limit the emitting of dynCall.

@sbc100 sbc100 force-pushed the dyn_call branch 5 times, most recently from 6ef8b79 to d85316f Compare August 21, 2020 18:15
@sbc100 sbc100 force-pushed the dyn_call branch 2 times, most recently from 80021bc to 54fd105 Compare August 22, 2020 10:10
@sbc100
Copy link
Collaborator Author

sbc100 commented Aug 22, 2020

I've reduced the scope of this change significantly such that it only covers invoke_xx functions.

Since we have one invoke function for each signature this allows me to avoid using Array.slice, or spread, or any kind of allocation.

@sbc100 sbc100 changed the title Use direct table access over dynCall where possible Use direct table access over dynCall in invoke_xx functions Aug 22, 2020
@sbc100
Copy link
Collaborator Author

sbc100 commented Aug 24, 2020

PTAL, I hope this good to land now.

As a followup PR, I plan to start limiting the dynCalls generated by wasm-emscripten-finalize.

@awtcode

Copy link
Member

@kripken kripken left a comment

Choose a reason for hiding this comment

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

It is a little unfortunate to always export the table in standalone mode, but I can't think of a good idea to avoid that.

#if ASSERTIONS
assert(('dynCall_' + sig) in Module, 'bad function pointer type - no table for sig \'' + sig + '\'');
if (args && args.length) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (args && args.length) {
if (args) {

an empty array is truthy in JS (but not in python)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This code is pre-existing, plus it seems like we need both check here.. we want to check for args being defined and having a length.

If we remove the first check we will get Cannot read property 'length' of undefined.. if we remove the second check as you suggest when the empty array will go down the wrong path (since as you say its truthy).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm going to land this as is for the above reasons.. if you think i'm wrong we can fix after.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, right, I was mistaken about args.length, lgtm.

#endif
return Module['dynCall_' + sig].call(null, ptr);
if (args && args.length) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (args && args.length) {
if (args) {

sbc100 added 2 commits August 25, 2020 03:39
This change starts the transition away from dynCall and towards
direct use of the wasm table.

In particular this change replaces the use of dynCall in invoke_xx
functions where possible.  dynCall is still needed for functions with
i64 in thier signature when WASM_BIGINT is not enabled.  When
WASM_BIGING is enabled this change removes all use of dynCall by
invoke_xx functions.
@sbc100 sbc100 merged commit 7550a3a into master Aug 25, 2020
@sbc100 sbc100 deleted the dyn_call branch August 25, 2020 11:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants