GetFirefly / firefly

An alternative BEAM implementation, designed for WebAssembly
Apache License 2.0
3.61k stars 103 forks source link

RFC: Exposing WASM runtime to Erlang applications #5

Open bitwalker opened 5 years ago

bitwalker commented 5 years ago

This issue is for discussing how we can expose the Wasm runtime to Erlang applications.

The Wasm runtime is an extension of the core runtime with APIs which match the browser APIs, so things like alert, document.getElementById, etc. These APIs will be primarily generated via something like wasm-bindgen, and should ideally be callable from LLVM IR.

There are non-trivial issues to be sorted out here though:

In particular, the handling of events/callbacks requires careful planning. In Wasm we are planning to dedicate the main thread "scheduler" as more of an event loop that yields back to the JS runtime like a typical JS application would, while the more traditional scheduler threads would run in one or more WebWorkers.

In order to call DOM APIs, it is then necessary for a process wishing to perform such a call, to dispatch a request to the main thread, which would then be suspended by its scheduler, while the main thread would then take responsibility for calling the API, yielding to the JS runtime, and then once the result is received, ensure that the process is woken up so that it can resume execution where it left off. I'm not yet sure whether or not we should try and mimic the continuation-passing style common to JS, or whether we should transparently convert these to await/async equivalents, where calling an API will appear to be blocking, but under the covers constructs a continuation which is then invoked with the result of the call once received.

Related to that, callbacks could then be implemented transparently, either as a continuation, or by constructing a closure which is called with the arguments given to the callback. The main thread will have a generic callback handler which will be the "real" function pointer passed to the JS API, so that it is invoked by the JS side when the callback is invoked. This handler will then construct the closure/continuation value for the origin process, which will be resumed using that value.

Events would more than likely work on a similar mechanism - a subscription would be set up by sending the request to the main thread, which would track the requesting PID, and as part of its event loop, would then dispatch any received events to subscribers by looking up the relevant PIDs and pushing messages onto their signal queue.

In short, the task list is basically these items:

KronicDeth commented 5 years ago

Invoking the DOM APIs, when we write the wrappers by hand is accomplished in lumen_web in #204. We could still come up with a more automatic system.

All calls into the runtime return Promises in #206, which are attached to an Executors that resolve the Promises when Lumen.Web.Wait.with_return() (lumen_web::wait::with_return_0::code) is returned to. It can be returned to because the wasm_bindgen function calls lumen_web::wait::with_return_0::spawn, which allows a Frame to be put above the lumen_web::wait::with_return_0::code Frame and only then is the process scheduled, so that the top frame has to compute a return value and push it on the stack as the return when lumen_web::wait::with_return_0::code finally runs. There was no need for special support in lumen_runtime or lumen_alloc for a web-specific Executor as the general resource::Reference needed for NIFs can contain it.