Haskell webassembly within the browser
So far as I do know that is the primary Haskell program
compiled to Webassembly (WASM) with mainline ghc and utilizing the browser DOM.
ghc’s WASM backend is strong, nevertheless it solely offers very low-level FFI bindings
when used within the browser. Ints and tips that could WASM reminiscence.
(See here for
details and for directions on getting the ghc WASM toolchain I used.)
I think about that sooner or later, WASM code will interface with the DOM by
utilizing a
WASI “world”
that defines a whole API (and browsers will not embrace Javascript engines
anymore). However at present, WASM cannot do something in a browser with out
calling again to Javascript.
For this challenge, I wanted 63 traces of (reusable) javascript
(here).
Plus one other 18 to bootstrap operating the WASM program
(here).
(Additionally browser_wasi_shim)
However let’s begin with the Haskell code. A easy program to pop up
an alert within the browser seems like this:
{-# LANGUAGE OverloadedStrings #-}
import Wasmjsbridge
overseas export ccall whats up :: IO ()
whats up :: IO ()
whats up = do
alert <- get_js_object_method "window" "alert"
call_js_function_ByteString_Void alert "whats up, world!"
A bigger program that pulls on the canvas and generated the picture above
is here.
The Haskell facet of the FFI interface is a bunch of pretty mechanical
features like this:
overseas import ccall unsafe "call_js_function_string_void"
_call_js_function_string_void :: Int -> CString -> Int -> IO ()
call_js_function_ByteString_Void :: JSFunction -> B.ByteString -> IO ()
call_js_function_ByteString_Void (JSFunction n) b =
BU.unsafeUseAsCStringLen b $ (buf, len) ->
_call_js_function_string_void n buf len
Many extra would should be added, or generated, to proceed down this
path to finish protection of all information sorts. All in all it is 64 traces
of code to this point
(here).
Additionally a C shim is required, that imports from WASI modules and offers
C features which can be utilized by the Haskell FFI. It seems like this:
void _call_js_function_string_void(uint32_t fn, uint8_t *buf, uint32_t len) __attribute__((
__import_module__("wasmjsbridge"),
__import_name__("call_js_function_string_void")
));
void call_js_function_string_void(uint32_t fn, uint8_t *buf, uint32_t len) {
_call_js_function_string_void(fn, buf, len);
}
One other 64 traces of code for that
(here).
I discovered this sample in Joachim Breitner’s haskell-on-fastly and copied it relatively blindly.
Lastly, the Javascript that will get run for that’s:
call_js_function_string_void(n, b, sz) {
const fn = globalThis.wasmjsbridge_functionmap.get(n);
const buffer = globalThis.wasmjsbridge_exports.reminiscence.buffer;
fn(decoder.decode(new Uint8Array(buffer, b, sz)));
},
Discover that this will get an identifier representing the javascript operate
to run, which is likely to be any technique of any object. It seems it up in a map
and runs it. And the ByteString that acquired handed from Haskell must be decoded to a
javascript string.
Within the Haskell program above, the operate is doc.alert
. Why not
move a ByteString with that by the FFI? Nicely, you possibly can. However then
it must eval it. That will make operating WASM within the browser be
evaling Javascript each time it calls a operate. That doesn’t look like a
good thought if the aim is velocity. GHC’s
javascript backend
does use Javascript`FFI snippets like that, however there they get pasted into the generated
Javascript hairball, so no eval is required.
So my code has issues like get_js_object_method
that lookup issues like
Javascript features and generate identifiers. It additionally has this:
call_js_function_ByteString_Object :: JSFunction -> B.ByteString -> IO JSObject
Which can be utilized to name issues like doc.getElementById
that return a javascript object:
getElementById <- get_js_object_method (JSObjectName "doc") "getElementById"
canvas <- call_js_function_ByteString_Object getElementById "myCanvas"
This is the Javascript referred to as by get_js_object_method
. It generates a
Javascript operate that might be used to name the specified technique of the article,
and allocates an identifier for it, and returns that to the caller.
get_js_objectname_method(ob, osz, nb, nsz) {
const buffer = globalThis.wasmjsbridge_exports.reminiscence.buffer;
const objname = decoder.decode(new Uint8Array(buffer, ob, osz));
const funcname = decoder.decode(new Uint8Array(buffer, nb, nsz));
const func = operate (...args) { return globalThis[objname][funcname](...args) };
const n = globalThis.wasmjsbridge_counter + 1;
globalThis.wasmjsbridge_counter = n;
globalThis.wasmjsbridge_functionmap.set(n, func);
return n;
},
This does imply that each time a Javascript operate id is seemed up,
some extra reminiscence is used on the Javascript facet. For extra critical makes use of of this,
one thing would should be carried out about that. A number of different stuff like
object worth getting and setting can also be not applied, there’s
no assist but for callbacks, and so forth. Nonetheless, I am joyful the place this has
gotten to after 12 hours of labor on it.
I may launch the reusable elements of this as a Haskell library, though
it appears doubtless that ongoing growth of ghc will make it out of date. Within the
meantime, clone the
git repo
to have a play with it.
This weblog submit was sponsored by unqueued on Patreon.