Writing WebAssembly code in Haskell
Asterius.Builtins, there are WebAssembly shims which serve as our runtime. We choose to write WebAssembly code in Haskell, using Haskell as our familiar meta-language.
As of now, there are two ways of writing WebAssembly code in Haskell. The first way is directly manipulating AST types as specified in
Asterius.Types. Those types are pretty bare-metal and maps closely to binaryen IR. Simply write some code to generate an
AsteriusFunction, and ensure the function and its symbol is present in the store when linking starts. It will eventually be bundled into output WebAssembly binary file.
Asterius.Types is not a pleasant experience, it's basically a DDoS on one's working memory, since the developer needs to keep a lot of things in mind: parameter/local ids, block/loop labels, etc. Also, the resulting Haskell code is pretty verbose, littered with syntactic noise (e.g. tons of list concats when constructing a block)
We now provide an EDSL in
Asterius.EDSL to construct an
AsteriusFunction. Its core type is
EDSL a, and can be composed with a
Monoid interface. Most builtin functions in
Asterius.Builtins are already refactored to use this EDSL. Typical usages:
- "Allocate" a parameter/local. Use
localto obtain an immutable
Expressionwhich corresponds to the value of a new parameter/local. There are also mutable variants.
- An opaque
LValtype is provided to uniformly deal with local reads/assignments and memory loads/stores. Once an
LValis instantiated, it can be used to read an
Expressionin the pure world, or set an
- Several side-effecting instructions can simply be composed with the monadic/monoidal interface, without the need to explicitly construct an anonymous block.
- When we need named blocks/loops with branching instructions inside, use the
loopcombinators which has the type
(Label -> EDSL ()) -> EDSL (). Inside the passed in continuation, we can use
break'to perform branching. The
Labeltype is also opaque and cannot be inspected, the only thing we know is that it's scope-checked just like any ordinary Haskell value, so it's impossible to accidently branch to an "inner" label.
The EDSL only checks for scope safety, so we don't mess up different locals or jump to non-existent labels. Type-safety is not guaranteed (binaryen validator checks for it anyway). Underneath it's just a shallow embedded DSL implemented with a plain old state monad. Some people call it the "remote monad design pattern".