This project provides an embedded domain-specific language (eDSL) for encoding WebAssembly in Haskell in a type-safe way (i.e. writing invalid Wasm results in a Haskell type error).
Below is a simple example of the DSL, a Wasm program that computes the factorial of 5:
factorial :: WasmModule
factorial = wasm do
fn #factorial @'[Int] do
dup
const 1
if gt then do
dup
const 1
sub
call #factorial
mul
else do
drop
const 1
fn #main do
const 5
call #factorial
print- Arithmetic and comparison instructions
- Blocks and structured control-flow
- Local and global variables
- Type-safe dynamic memory access
- Functions, including mutual recursion
To provide better ergonomics as a Haskell DSL, the project deviates from the WebAssembly specification in a few aspects:
- The operand stack can contain values of any Haskell type, not just the Wasm primitive types (
i32,i64,f32,f64). - Additional instructions are supported (
dup,swap) that are not present in Wasm. - Arithmetic and comparison instructions are overloaded using the standard Haskell typeclasses (
Num,Ordetc.). - Boolean instructions (comparisons,
br_ifetc.) use Haskell's nativeBooltype, rather than encoding booleans as0or1of typei32. - Local variables are scoped explicitly (using a
let'instruction), instead of being function-scoped. - Memories are typed, and are scoped similarly to local variables (allocated with the
let_meminstruction).
The project also includes an interpreter that uses continuation-passing style for efficient jumps, and local instances (via WithDict) for constant-time variable lookup.
Global variables and memories can be initialised with host-provided mutable references (IORefs), which allows the host to pass input data to the Wasm module, and inspect its mutations after execution.
- The DSL only allows the construction of self-contained Wasm modules (i.e. no external imports or exports).
call_indirectandbr_tableare not supported.
The main modules of the library are Language.Wasm.Instr, which defines the core Instr AST datatype and evaluation functions; and Language.Wasm.Module, which builds upon Instr and defines a datatype for bundling definitions into modules, as well as module evaluation functions.
Language.Wasm.Syntax defines the DSL's syntactic sugar, and Language.Wasm.Prelude ties everything together into a single import.
The Language.Wasm.Examples module defines a number of example Wasm programs, and Main contains the driver code.
- GHC >=9.8.1
- Cabal >=3.10.2.0
(Both can be installed via GHCup)
The project has no external (non-Haskell) dependencies, and can simply be built using cabal:
cabal buildRunning the project with no arguments will run all the example programs:
cabal runIf you want to run only specific examples, pass their names as arguments:
cabal run . -- factorial fibonacciIf you specify an example multiple times, it will be executed that many times. This can be used to observe side-effects such as mutating memory shared by the host:
cabal run . -- squareAll squareAllTo run the library's test-suite, use the following command:
cabal run wasm-hs-test