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

JS API; asynchronous imports #720

Open
wanderer opened this issue Jul 8, 2016 · 17 comments
Open

JS API; asynchronous imports #720

wanderer opened this issue Jul 8, 2016 · 17 comments

Comments

@wanderer
Copy link
Contributor

wanderer commented Jul 8, 2016

Currently it doesn't look like there is a way for imports written in JS to return asynchronously. If this is true, would there be interest in adding a way?

I think the easiest way would be to return a promise

@lukewagner
Copy link
Member

In the MVP, I agree there isn't any direct support; wasm can really only take and receive numbers. However, one could generate or hand-write a JS glue layer that supports interfacing async JS. E.g., the glue layer could create a JS function closure that contained an exported wasm function and wasm closure state (probably an integer; i.e., the classic C-API-style void* closure arg) such that, when you called the JS wrapper function, it'd call the underlying wasm function, passing the closure state.

Post-MVP, with GC and WebIDL integration, I think we will be able to support more direct integration with async JS (and also a new breed of async Web APIs). In particular, I think we should provide the building blocks for wasm to directly construct callback functions (ultimately just code-pointer + data-pointer) which would then allow wasm to directly call, e.g., a Promise's then method.

@wanderer
Copy link
Contributor Author

wanderer commented Jul 8, 2016

the glue layer could create a JS function closure that contained an exported wasm function and wasm closure state

This won't work if you also use start. (which might be fine, but confused me for a second when trying the above)

@lukewagner
Copy link
Member

Oh, are you talking specifically about the in-progress (or maybe it has advanced?) async-in-ES6-module-top-level proposal? That would require a new "async_start" section, I expect, but it looks like it's a bit farther out.

@kumavis
Copy link

kumavis commented Aug 10, 2016

@lukewagner looking to have a wasm module perform some async network I/O mediated by JS - any way of doing this with the current api?

@wanderer
Copy link
Contributor Author

wanderer commented Aug 10, 2016

@lukewagner we are thinking about this again now in our prototype. One of the import method we define needs to do a async look up (if in node.js from the disk or from the network if in the browser).

However, one could generate or hand-write a JS glue layer that supports interfacing async JS. E.g., the glue layer could create a JS function closure that contained an exported wasm function and wasm closure state (probably an integer; i.e., the classic C-API-style void* closure arg) such that, when you called the JS wrapper function, it'd call the underlying wasm function, passing the closure state.

Yes, this is possible but we can't rely on the wasm code to export its closure state. So I don't think it will work for us. We don't have any control the wasm code being run. (apart from adding metering via a transform).

@lukewagner
Copy link
Member

@kumavis I might be misunderstanding what you mean, but I think there should be no problem; the implementation of emscripten_async_wget does this. Is there a specific issue you're having you could describe?

@wanderer
Copy link
Contributor Author

@lukewagner I don't think that will work, we don't control the code is running in the wasm instance. We want to be able to give the wasn instance an import that has to do some async work `(import $async_stuff_may_happen 'ethereum' 'getStorage')' which third parties will use.

@lukewagner
Copy link
Member

@wanderer What do you mean by "has to do some async work"? In your example import, is getStorage attempting to synchronously return a result or does it take a callback into user code that it calls when the asynchronous work is completed? If the former, then you can't do that in JS or wasm if the implementation of getStorage needs to call async APIs. If the latter, then that's comparable to what emscripten_async_wget is doing so I don't see the problem: you just have these APIs take callbacks (in the form of two i32s: the first is an index into the function table that serves as the function-pointer and the second is a pointer into linear memory and serves as the classic C-API "closure" argument).

@axic
Copy link

axic commented Aug 10, 2016

@lukewagner would there be a way to refer to WebAssembly exports with an index? That could simplify this.

@wanderer
Copy link
Contributor Author

wanderer commented Aug 10, 2016

What do you mean by "has to do some async work"?

getStorage need to pull load from value from either the network or disk. Both of which are not nice to make synchronous in JS.

if the former, then you can't do that in JS

I'm not sure I understand this. Here is an example of what I was thinking. In this example the imported function returns a value via a callback instead of a return. This pattern can be implemented in pure JS if new WebAssembly.Instance was a JS function.

example

 var instance;
 var importObj = {
        getStorage: function (key, cb) {
            lookupVal(function (val, err) {
               cb(val); // return value to the wasm instance
            });
        }
    };

instance = new WebAssembly.Instance(module, importObj);

you just have these APIs take callbacks

This is an option, but it would only apply to clients running in the browser. Non-browser clients will have full control of the VM so they don't have to worry about this. This mean we have to transform the code on the fly in the browser by looking for (call_import $getStorage (i32)) and converting into (call_import $getStorage (i32 i32)) and injecting code to save all the locals and injecting a callback function which reloads the local and continues. Which is all doable (but not very nice and hacky).

But it seem to me that async lookups would be common usecase and it might make sense to have that optionality in the JS api.

@lukewagner
Copy link
Member

@axic Yes, you could either put all the exports into a JS array or you could put them into an exported WebAssembly.Table which you can also access from JS.

This is an option, but it would only apply to clients running in the browser.

Ok, so you're talking about designing a single host-environment that can run modules in either a browser or non-browser wasm VMs. (Sounds cool!) I still think passing the pair of (func-ptr, closure) indices would be suitable since they could have the same meaning in a non-browser VM: func-ptr would index into either the exported functions or an exported table; closure into exported linear memory.

@kumavis
Copy link

kumavis commented Aug 11, 2016

Thanks @lukewagner

found emscripten_async_wget defined in js
This calls Runtime.dynCall here

Runtime.dynCall('vi', callback, [allocate(intArrayFromString(_file), 'i8', ALLOC_STACK)]);

Runtime.dynCall is defined here
which calls the exported wasm function here

return Module['dynCall_' + sig].apply(null, args);

result is calling

Module.dynCall_vi(callback, allocate(intArrayFromString(_file), 'i8', ALLOC_STACK))

vi refers to the function sig void (int) (no return, receive int)

@lukewagner That clarified things for me a bit but I think the next step would be to find some wasm example code that uses emscripten_async_wget or just emscripten_async_call

@lukewagner
Copy link
Member

@kumavis FWIW, much of that dynCall machinery is working around the lack of an asm.js equivalent to WebAssembly.Table; with wasm, Emscripten should be able to simply tbl.get(i)(x,y,z) from JS (although it seems like there is also some parameter marshalling going on there too which is orthogonal).

@jfbastien
Copy link
Member

Is someone interested in championing a concrete proposal here?

@wanderer
Copy link
Contributor Author

wanderer commented Jun 3, 2017

@jfbastien so far we have managed work around this limitatio by adding callback to all the c interface. Another option to run the wasm instance in a webworker use a SAB and Atomic.wait to "pause", then do the async work in a the main thread.

But it might be less "hacky" if the imported function returned a promise wasm instance would resume after the promised resolved. Are there any immediate problems with this?

@jfbastien
Copy link
Member

I'd need to see more concrete code to understand clearly.

@cdetrio
Copy link

cdetrio commented Dec 31, 2017

@jfbastien here's a minimal example to demonstrate the issue https://gist.github.com/cdetrio/7f5486004b0054b5c08e0496cf3ab21f

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

No branches or pull requests

8 participants