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
,Ord
etc.). - Boolean instructions (comparisons,
br_if
etc.) use Haskell's nativeBool
type, rather than encoding booleans as0
or1
of 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_mem
instruction).
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 (IORef
s), 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_indirect
andbr_table
are 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 build
Running the project with no arguments will run all the example programs:
cabal run
If you want to run only specific examples, pass their names as arguments:
cabal run . -- factorial fibonacci
If 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 squareAll
To run the library's test-suite, use the following command:
cabal run wasm-hs-test