Asterius.Builtins, there are WebAssembly shims which serve as our runtime.
We choose to write WebAssembly code in Haskell, using Haskell as our familiar
As of now, there are two ways of writing WebAssembly code in Haskell. The first
way is directly manipulating AST types as specified in
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
Its core type is
EDSL a, and can be composed with a
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.
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 accidentally 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".