Skip to content

Repository files navigation

Wasmoon

A WebAssembly runtime written in MoonBit with JIT compilation support.

Warning: This project is primarily developed with AI assistance and has not been thoroughly audited. Do not use in production or security-sensitive environments.

Note: JIT optimization is actively improving. Performance depends on workload and platform; benchmark your target programs for an accurate comparison.

Features

  • JIT Compiler: AArch64 and amd64 native code generation with SSA-based IR
  • Interpreter: Full WebAssembly 1.0 execution engine, available via --no-jit
  • WAT/WASM Parser: Parse both text and binary formats
  • WASI Preview 1 Support: File I/O, environment variables, command-line arguments
  • GC Proposal Support: i31/struct/array/ref operations in interpreter and JIT
  • Component Model: Component parser, validator, runtime, and stable WIT-shaped facade
  • WASI Components: Preview 2 and WASI 0.3 hosts with native Component Async JIT on macOS AArch64 and Linux AMD64

Requirements

  • Required:
    • moon
    • python3
  • Optional:
    • wasmtime (useful for differential/performance comparison workflows)

Installation

Path A: Global install (recommended)

moon install Milky2018/wasmoon/cmd/wasmoon
moon install Milky2018/wasmoon/cmd/wasmoon-tools

To use unreleased changes directly from the Git repository:

moon install https://github.com/Milky2018/wasmoon.git cmd/wasmoon
moon install https://github.com/Milky2018/wasmoon.git cmd/wasmoon-tools

Verify binaries are on PATH:

wasmoon --help
wasmoon-tools --help

By default these commands install binaries to ~/.moon/bin/ as:

  • wasmoon (runtime CLI)
  • wasmoon-tools (utility CLI)

Path B: Repo-local build (development)

git clone https://github.com/Milky2018/wasmoon.git
cd wasmoon
./install.sh

./install.sh uses moon build --target native --release to build local binaries into target/moon-install-build/, copies them to target/moon-install-bin/, and then refreshes two repo-root executables:

  • ./wasmoon
  • ./wasmoon-tools

After code changes, re-run ./install.sh to refresh both executables.

As Library

moon add Milky2018/wasmoon

Quick Start (60 seconds)

# 1) Run with default _start
wasmoon run examples/add.wat

# 2) Invoke an export with arguments
wasmoon run examples/add.wat --invoke add --arg 5 --arg 3

# 3) Interpreter mode
wasmoon run examples/add.wat --invoke add --arg 5 --arg 3 --no-jit

# 4) WASI dirs/env/options
wasmoon run examples/hello_wasi.wat \
  --dir . \
  --env FOO=bar \
  -S inherit-env

For detailed flags, run:

wasmoon run --help

CLI Commands (concise)

# run
wasmoon run examples/add.wat --invoke add --arg 1 --arg 2
wasmoon run --help

# test
wasmoon test spec/i32.wast
wasmoon test --help

# explore
wasmoon explore examples/add.wat \
  --stage milkir machv vcode allocated-vcode code-object mc
wasmoon explore --help

# component
wasmoon component path/to/component.wasm --validate
wasmoon component path/to/component.wasm \
  --invoke 'math#increment' \
  --arg 41
wasmoon component path/to/command.component.wasm --run \
  --dir /srv/data::/data \
  --network loopback
wasmoon component path/to/command.component.wasm --run --no-jit
wasmoon component --help

# component-test
wasmoon component-test path/to/component-tests.json
wasmoon component-test path/to/component-tests.json --no-jit
wasmoon component-test --help

# disasm
wasmoon disasm examples/stream.wasm
wasmoon disasm examples/add.wat
wasmoon disasm --help

Quick differential testing vs Wasmtime (wasm-smith):

python3 scripts/smith_diff/run.py run --count 1000

JIT Trap Debugging

Use these options when diagnosing JIT failures:

  • -D: debug logging
  • --dump-on-trap: dump IR/MachV/MC for the trapping function
  • -W: generate DWARF debug info for JIT code (better stack traces in LLDB)

Example:

wasmoon run examples/core_ed25519.wasm -D --dump-on-trap -W

LLDB quick recipe:

lldb -- ./wasmoon run examples/core_ed25519.wasm
(lldb) run
(lldb) bt

wasmoon-tools Usage

wasmoon-tools provides common validation/conversion/WIT workflows:

# Validate a core Wasm module (WASM/WAT)
wasmoon-tools validate examples/add.wat

# Convert between WASM and WAT
wasmoon-tools wasm2wat examples/stream.wasm -o examples/stream.wat
wasmoon-tools wat2wasm examples/add.wat -o examples/add.wasm

# Parse WIT and print normalized text / JSON
wasmoon-tools wit path/to/foo.wit
wasmoon-tools wit path/to/foo.wit --json

# Resolve a directory package (with deps/) and emit graph
wasmoon-tools wit path/to/pkgdir
wasmoon-tools wit path/to/pkgdir --out-dir out

# Encode WIT package as component binary / text
wasmoon-tools wit path/to/foo.wit --wasm -o foo.wasm
wasmoon-tools wit path/to/foo.wit --wat > foo.wat

# Importize world flow
wasmoon-tools wit foo.wasm --importize --wat
wasmoon-tools wit path/to/pkgdir --importize-world my-world --wat

wasmoon-tools wit supports parsing and dependency resolution through deps/, JSON output, component encoding and decoding, importize workflows, and tested non-scalar and resource-related cases. Unsupported specification cases return a diagnostic.

License

Wasmoon is licensed under Apache-2.0. Some test suites, benchmark workloads, and generated diagnostic artifacts are imported from third-party projects under their own compatible licenses. See THIRD_PARTY_NOTICES.md.

Validation / CI-equivalent Checks

moon check --target native
moon test --target native
./install.sh
cargo install wasm-tools --version 1.254.0 --locked
python3 scripts/run_all_wast.py --dir spec --rec
python3 scripts/check_component_snapshot.py
python3 scripts/run_component_wast.py --suite stable-0.2
python3 scripts/run_component_wast.py --suite stable-0.2 --no-jit
python3 scripts/run_component_wast.py --suite async-0.3
python3 scripts/run_component_wast.py --suite async-0.3 --no-jit
python3 scripts/run_component_wast.py --suite future-gated
python3 scripts/run_component_wast.py --suite future-gated --no-jit

Library Usage

JIT GC Setup

Call @jit.gc_setup(...) with the VMContext and function-table data used by typed function references:

  • ctx_ptr
  • func_type_indices
  • func_table_ptr
  • num_funcs

The setup associates GC runtime state with ctx_ptr. Pass the same pointer to @jit.gc_teardown(...) when releasing that state. Incomplete or inconsistent setup data raises GCSetupError.

Basic Example

///|
test "basic add" {
  let wat =
    #|(module
    #|  (func (export "add") (param i32 i32) (result i32)
    #|    local.get 0
    #|    local.get 1
    #|    i32.add))
  let mod = @wat.parse(wat)
  let (store, instance) = @executor.instantiate_module(mod)
  let result = @executor.call_exported_func(store, instance, "add", [
    I32(5),
    I32(3),
  ])
  debug_inspect(result, content="[I32(8)]")
}

Memory Operations

///|
test "memory" {
  let wat =
    #|(module
    #|  (memory (export "mem") 1)
    #|  (func (export "store") (param i32 i32)
    #|    local.get 0 local.get 1 i32.store)
    #|  (func (export "load") (param i32) (result i32)
    #|    local.get 0 i32.load))
  let mod = @wat.parse(wat)
  let (store, instance) = @executor.instantiate_module(mod)
  @executor.call_exported_func(store, instance, "store", [I32(0), I32(42)])
  |> ignore
  let result = @executor.call_exported_func(store, instance, "load", [I32(0)])
  debug_inspect(result, content="[I32(42)]")
}

Cross-module Imports

///|
test "cross-module" {
  let linker = @runtime.Linker::Linker()
  let mod_a =
    #|(module (func (export "add") (param i32 i32) (result i32)
    #|  local.get 0 local.get 1 i32.add))
  let mod_a = @wat.parse(mod_a)
  let inst_a = @executor.instantiate_with_linker(linker, "math", mod_a)
  linker.register("math", inst_a)
  let mod_b =
    #|(module
    #|  (import "math" "add" (func $add (param i32 i32) (result i32)))
    #|  (func (export "use_add") (param i32 i32) (result i32)
    #|    local.get 0 local.get 1 call $add))
  let mod_b = @wat.parse(mod_b)
  let inst_b = @executor.instantiate_with_linker(linker, "main", mod_b)
  let result = @executor.call_exported_func(
    linker.get_store(),
    inst_b,
    "use_add",
    [I32(3), I32(5)],
  )
  debug_inspect(result, content="[I32(8)]")
}

Host Functions

///|
test "host function" {
  let linker = @runtime.Linker::Linker()
  // Register a host function that doubles an i32
  linker.add_host_func(
    "env",
    "double",
    fn(args) {
      guard args[0] is I32(x) else { return [] }
      [I32(x * 2)]
    },
    func_type={ params: [I32], results: [I32] },
  )
  let wat =
    #|(module
    #|  (import "env" "double" (func $double (param i32) (result i32)))
    #|  (func (export "quadruple") (param i32) (result i32)
    #|    local.get 0 call $double call $double))
  let mod = @wat.parse(wat)
  let instance = @executor.instantiate_with_linker(linker, "main", mod)
  let result = @executor.call_exported_func(
    linker.get_store(),
    instance,
    "quadruple",
    [I32(5)],
  )
  debug_inspect(result, content="[I32(20)]")
}

Project Status

  • WebAssembly 1.0 core specification
  • JIT compiler (AArch64, amd64)
  • Multi-value returns
  • Reference types (funcref, externref)
  • Tail calls
  • Cross-module function calls
  • WASI Preview 1 support
  • GC proposal support (experimental subset)
  • Component Model support
  • JIT optimizations (constant folding, dead code elimination, etc.)

Contributor Note

  • Edit README.mbt.md as the source of truth.
  • README.md is a symlink to README.mbt.md.

License

Apache-2.0

About

A slow and insecure runtime for WebAssembly

Resources

Stars

29 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages