wasmedge-hs

Haskell FFI bindings for the WasmEdge 0.18 WebAssembly runtime

wasmedge-hs

Type-safe, idiomatic, high-performance Haskell FFI bindings for the WasmEdge WebAssembly runtime.

Build & Test Release Hackage Hackage Deps License: MIT Haddock


Overview

wasmedge-hs provides a ground-up rework of the Haskell bindings for WasmEdge (supporting 0.17.x and 0.18.x). It is designed following modern Haskell FFI best practices:

  • Two-Layer Architecture:
    • WasmEdge.Internal.FFI.*: Raw low-level C FFI with shim layer handling struct-by-value C ABI.
    • Safe, idiomatic Haskell interface with standard types (Text, ByteString, sum types).
  • Strong Phantom Ownership:
    • OwnedCtx tag ensures resources are tracked and finalized via GC finalizers or explicit bracket calls.
    • Container-owned elements (such as FunctionInstance added to a ModuleInstance) are automatically disarmed to eliminate double-free bugs.
  • Full Feature Coverage:
    • WebAssembly execution (one-shot and step-by-step pipeline)
    • Bidirectional host functions (calling Haskell functions from WASM)
    • Ahead-of-Time (AOT) compilation to native shared objects (.so / .dylib)
    • WASI support with arguments, environment variables, and preopened directories
    • Non-blocking asynchronous execution via WasmEdge.Async
    • Plug-in discovery and loading (e.g. wasi_nn, wasi_logging)
    • WasmEdge 0.18+ exception handling and Tag instances

Installation & Prerequisites

1. Install the WasmEdge C runtime

macOS (Homebrew)

brew install wasmedge

Linux

curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh \
  | sudo bash -s -- -v 0.18.1 -p /usr/local

2. Add wasmedge-hs to your project

Cabal (cabal.project / .cabal):

-- your-package.cabal
build-depends: wasmedge-hs >= 0.18.1 && < 0.19
cabal build
cabal test

Stack (stack.yaml):

extra-deps:
  - wasmedge-hs-0.18.1.0
# Default build (LTS 22 / GHC 9.6.6)
stack build
stack test

# Or choose a specific GHC / LTS version:
stack --stack-yaml stack-lts-22.yaml test  # GHC 9.6.6
stack --stack-yaml stack-lts-23.yaml test  # GHC 9.8.4
stack --stack-yaml stack-lts-24.yaml test  # GHC 9.10.1
stack --stack-yaml stack-nightly.yaml test # GHC 9.12.x

Quick Start

1. Execute a WebAssembly Function

{-# LANGUAGE OverloadedStrings #-}
import qualified Data.ByteString as BS
import WasmEdge.VM
import WasmEdge.Types

-- Minimal WebAssembly module implementing: add(a: i32, b: i32) -> i32
addWasm :: BS.ByteString
addWasm = BS.pack
  [ 0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00  -- magic + version
  , 0x01, 0x07, 0x01, 0x60, 0x02, 0x7F, 0x7F, 0x01, 0x7F  -- type: (i32, i32) -> i32
  , 0x03, 0x02, 0x01, 0x00                              -- function section
  , 0x07, 0x07, 0x01, 0x03, 0x61, 0x64, 0x64, 0x00, 0x00  -- export "add"
  , 0x0A, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6A, 0x0B  -- body
  ]

main :: IO ()
main = do
  result <- withVM Nothing Nothing $ \vm ->
    runWasmBytes vm addWasm "add" [WasmI32 40, WasmI32 2]
  case result of
    Right [WasmI32 ans] -> putStrLn $ "Result: 40 + 2 = " ++ show ans -- 42
    Left err            -> putStrLn $ "Error: " ++ show err

2. Export Haskell Functions to WebAssembly

{-# LANGUAGE OverloadedStrings #-}
import WasmEdge.VM
import WasmEdge.Types
import WasmEdge.Function
import WasmEdge.Module

haskellAddOne :: CallingFrame -> [WasmVal] -> IO (WasmResult [WasmVal])
haskellAddOne _ [WasmI32 n] = pure $ Right [WasmI32 (n + 1)]
haskellAddOne _ args        = pure $ Right args

main :: IO ()
main = do
  withFunctionType [I32] [I32] $ \fnType ->
    withFunction fnType haskellAddOne $ \hostFn -> do
      hostMod <- buildHostModule "host_module" $ \b ->
        builderAddFunction b "add_one" hostFn

      withVM Nothing Nothing $ \vm -> do
        Right () <- registerModuleFromImport vm hostMod
        res <- runWasmBytes vm guestWasm "call_host" [WasmI32 99]
        print res -- Right [WasmI32 100]

For typed host functions (no manual WasmVal unpacking), use builderAddHostFunc:

buildHostModule "host_module" $ \b -> do
  builderAddHostFunc b "add" $ \(x :: Int32) (y :: Int32) -> pure (x + y) :: IO Int32

See cabal run wasmedge-hs-example-hostfunc for the complete, runnable example.

3. Ahead-Of-Time (AOT) Compilation

import WasmEdge.Compiler
import WasmEdge.Configure
import WasmEdge.Types (OptimizationLevel (..))

compileToNative :: IO ()
compileToNative = do
  withConfigure $ \cfg -> do
    setCompilerOptions cfg (defaultCompilerOptions { compOptLevel = OptO3 })
    withCompiler (Just cfg) $ \compiler -> do
      res <- compileFile compiler "input.wasm" "output.so"
      case res of
        Right () -> putStrLn "Compiled successfully to output.so"
        Left err -> putStrLn $ "Compilation failed: " ++ show err

Examples

Run any of the included examples:

# Cabal
cabal run wasmedge-hs-example-hello
cabal run wasmedge-hs-example-hostfunc
cabal run wasmedge-hs-example-plugin
cabal run wasmedge-hs-example-aot

# Stack
stack run wasmedge-hs-example-hello
  • hello — Minimal WASM execution (add two i32 values)
  • hostfunc — Calling Haskell callbacks from WebAssembly
  • plugin — Dynamic plugin discovery and module introspection
  • aot — AOT compilation of bytecode to optimized native code

Benchmarks

Microbenchmarks with Criterion:

cabal run wasmedge-hs-bench --enable-benchmarks
Benchmark ScenarioMean Latency
Pre-loaded Function Execution~380 ns
One-shot WASM Execution (runWasmBytes)~26 μs
Step-by-step Pipeline (load/validate/instantiate/exec)~26 μs
VM Lifecycle (createVM/deleteVM)~23 μs

Supported GHC Versions

GHCStackage LTSStatus
9.6.6LTS 22✅ Tested
9.8.4LTS 23✅ Tested
9.10.1LTS 24✅ Tested
9.12.xNightly🧪 Experimental

Documentation


Branching Strategy

BranchPurpose
mainStable, release-tagged. Only release commits land here.
developIntegration branch. All PRs target develop first.
feat/*Feature branches — merge into develop via PR.
fix/*Bug-fix branches — merge into develop via PR.
chore/*Infrastructure, docs, CI changes.

Releases are created by merging develop → main, then pushing an annotated tag v<version>.


Contributing

See docs/CONTRIBUTING.md for the full guide. Quick start:

git clone https://github.com/tusharad/wasmedge-hs
cd wasmedge-hs
brew install wasmedge        # macOS
stack build
stack test

License

MIT License. Copyright (c) 2026 Tushar Adhatrao. See LICENSE for details.