This project provides an embedded domain-specific language (eDSL) that enables the embedding of a significant subset of WebAssembly into 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 counts up to 10:
countTo10 = main do
const @Int 0
let' #i do
loop #next do
local.get #i
const 1
add
local.tee #i
dup
print
const 10
cmp.lt
br_if #next
- Arithmetic and comparison instructions
- Blocks and structured control-flow
- Local and global variables
- Type-safe dynamic memory access
- Recursive functions
In order 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
). - 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
. - Variables use alphanumeric names (rather than numeric indices), and are scoped explicitly (using a
let'
instruction), rather than being function-scoped. - Similarly, block labels are also named, and are introduced by the block instructions (
block
,loop
,if
). - Rather than using a single untyped linear memory, the user can explicitly allocate typed memory segments, inspired by the MSWasm paper. Segments are scoped similarly to local variables, and are allocated with the
let_seg
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 segments can be initialised with host-provided mutable references (IORef
s), which allows the host to pass inputs to the Wasm module, and inspect its outputs and side-effects.
- The DSL only allows the construction of self-contained Wasm modules (i.e. no external imports or exports).
- Functions can only refer to functions defined before them in the same module (and to themselves), thus mutually-recursive functions are not supported.
- Indirect calls and
br_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 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