Skip to content
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

napi wasm bindings #375

Closed
devsnek opened this issue Jun 6, 2019 · 13 comments
Closed

napi wasm bindings #375

devsnek opened this issue Jun 6, 2019 · 13 comments

Comments

@devsnek
Copy link
Member

devsnek commented Jun 6, 2019

@josephg brought up a cool idea in #374 to expose napi to wasm. This would allow shipping abi stable binaries that are also platform independent(!!)

my initial exploration shows that at most our in-core changes are basically adding __attribute__((__import_module__("napi"))) to NAPI_EXTERN if __wasm32 is defined.

We could ship the bindings themselves as a separate module or include them in core, but they aren't actually too complex:

    napi_create_array: (envPtr, resultPtr) => {
      this.refreshMemory();
      this.writeValue(resultPtr, this.store([]));
      return NAPI_OK;
    },

At that point, you can do this:

@mhdawson
Copy link
Member

mhdawson commented Jun 6, 2019

@devsnek I was just talking to Lin Clark to set up a meeting between the N-API and WASM/WASI team to better understand. I was thinking that interacting between a WASM module and JavasScript Objects etc. was something where N-API might fit in and it looks like that you are thinking along the same lines.

What I want to figure out if this fits/makes sense with what the WASM team is planning.

Right now its looking like we'll hopefully have a meeting with the WASM team the first week of July to get the discussion going (they are very busy between now and then).

Do you have your experimental work somewhere I could clone and experiment with to learn more? I've not used WASM/WASI yet and this would be a good way for me to ramp up.

@devsnek
Copy link
Member Author

devsnek commented Jun 6, 2019

i haven't uploaded napi anywhere yet but you can play with wasi by cloning this: nodejs/node#27850

@gabrielschulhof
Copy link
Collaborator

@devsnek I looked at the code in your PR and one thing I can't wrap my head around is how we would write a function that returns a JS value? All the examples AFAICT are self-contained programs that basically execute int main(void) {} from top to bottom.

N-API addons aren't really structured to be called from a main().

I guess I'd love to see an example of a simple library written as a WASM module.

@devsnek
Copy link
Member Author

devsnek commented Jun 12, 2019

@gabrielschulhof

one thing I can't wrap my head around is how we would write a function that returns a JS value?

if you have this:

napi_value init(napi_env env, napi_value exports) {
  return exports;
}

it gets compiled to something like this in wasm:

(func $init (param $env i32) (param $exports i32) (result i32)
  (local.get $exports)
)

then from js you can simulate a handle scope with an array

const scope = [null, {}]; // 1 is exports
const out_idx = module.init(0, 1);
// `out_idx` is 1
const obj = scope[out_idx];

something like napi_create_object pushes a new value into the scope and returns the index.

you can see a mostly complete application of this concept here: https://gist.github.com/devsnek/db5499bf774f078e9ebb679680bd2cd1

N-API addons aren't really structured to be called from a main().

So what I did about this was:

#ifdef __wasm32__
#define NAPI_MODULE(modname, regfunc)                                 \
  NAPI_MODULE_EXPORT napi_value _napi_register(napi_env env,          \
                                               napi_value exports) {  \
    return regfunc(env, exports);                                     \
  }
#else
#define NAPI_MODULE(modname, regfunc)                                 \
  NAPI_MODULE_X(modname, regfunc, NULL, 0)  // NOLINT (readability/null_usage)
#endif

@OhadRau
Copy link

OhadRau commented Jun 14, 2019

Hey just wanted to pipe in here since this is something I've been working on as well for the past few days. I'm approaching this a little differently, exposing N-API directly into WebAssembly using the Wasm C API (which just landed in V8's LKGR branch a few weeks ago). It's not immediately clear to me how all of N-API's functionality can be implemented in JS code alone, so I believe exposing N-API directly would get around potential issues in that regard. Also, performance-wise (after speaking with @ofrobots) it sounds like there is some edge to calling directly into C(++) rather than into JS. However some translation will have to happen between N-API and the WebAssembly wrapper library. I'm nearing a point where I'll be able to test some real libraries on this implementation, so hopefully I'll have a better picture of what the advantages/disadvantages of this approach are early next week.

(I spoke earlier today with @devsnek to get a picture of what he's working on and how we can work on this functionality without stepping on each other's feet too much. I've decided to continue working on my implementation so that we can better evaluate different approaches here and pick what makes the most sense for the Node community.)

@devsnek
Copy link
Member Author

devsnek commented Jun 15, 2019

(fwiw i also feel that the capi implementation is the ideal way to go, i just was going for a quick mvp as we don't have a version of v8 with the wasm capi in node yet.)

@gabrielschulhof
Copy link
Collaborator

There may be another good reason to maintain a JS implementation of N-API, namely if we want to be able to run N-API addons in the browser. Unless V8 incorporates the N-API implementation we have in Node.js and makes it available as a WASM C API in the browser, we shall have to supply the JS implementation as a module that can be used to resolve a WASM-compiled N-API addon's napi_* symbols.

@OhadRau
Copy link

OhadRau commented Jun 20, 2019

Yep @gabrielschulhof I definitely think it's worth pursuing both options here and evaluating them against each other. There's a very good chance that the JS version ends up working better for us here. For example, the overhead of doing everything in JS might be lower than the overhead of using the "actual" N-API to interface with JS (working on benchmarking this still).

Since some N-API functionality won't make sense in the browser (e.g. napi_get_uv_event_loop) I think it could be a good idea to define some subset of N-API that's safe to use in the browser/with WASM and then we can base the JS implementation off of that. (As of now I can't think of how we'd fit in the UV event loop to WASM modules anyways, so I think it makes sense to make it a native-only feature).

@gabrielschulhof
Copy link
Collaborator

@mhdawson
Copy link
Member

As @gabrielschulhof mentioned about we've already separated out the Node and JS parts for this exact reason.

@mhdawson
Copy link
Member

@OhadRau, @ofrobots any update on this?

@mhdawson
Copy link
Member

mhdawson commented Oct 1, 2019

From @ofrobots, unfortunately, they did not get as far along as they would have liked but these are the patches: https://github.com/OhadRau/node-v8/pulls

@mhdawson
Copy link
Member

I think at this point we should close this issue and we can create a new one if there is a future effort. Please let us know if you think that was not the right thing to do.

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

No branches or pull requests

4 participants