wasmoon

A slow and insecure runtime for WebAssembly

wasm
webassembly
runtime
jit
Download zip
Author
Version
0.14.0
License
Apache-2.0
Last updated
16 hours ago
Downloads
138

#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
  • Async host functions: Suspend interpreter continuations or JIT native fibers without blocking the host thread
  • 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

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

#Async host functions

Register an async import with Linker::add_async_host_func, then enter Wasm through an async call API. Wasm observes an ordinary blocking import call, while the host future is awaited only after the interpreter continuation or JIT native fiber has been parked.

linker.add_async_host_func(
"host",
"double_later",
async fn(args) {
@async.sleep(10)
match args[0] {
I32(value) => [I32(value * 2)]
_ => raise @runtime.TypeMismatch
}
},
func_type={ params: [I32], results: [I32], },
)

let values = @executor.call_exported_func_async(
linker.get_store(), instance, "run", [I32(21)],
)

Use JITModuleContext::call_core_func_async for a prepared JIT module. Calling an async import through a synchronous entry point fails explicitly instead of blocking the host thread. Cancelling the surrounding MoonBit task cancels the host future and releases the parked Wasm continuation or native fiber. Async suspension and structured host errors also propagate when JIT-compiled Wasm calls an interpreted imported-Wasm function before reaching the async host. Reference arguments and completed host results remain Store GC roots across every parked or not-yet-consumed boundary.

If a module's start function can reach an async import, instantiate it with instantiate_with_linker_async or instantiate_module_with_imports_async. The synchronous instantiation APIs reject that path with AsyncHostRequiresAsyncCall.

MoonBit async functions do not expose a synchronously pollable Future to a synchronous native callback. Wasmoon therefore starts the host operation as a structured child task and parks Wasm once on the first encounter, even when the host function completes without awaiting. Subsequent waiting and resumption do not block the host thread.

#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 opt-milkir 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 MilkIR/Target VCode/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

#Contributor Note

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

#License

Apache-2.0

WasmCompilerPipelineError

pub suberror WasmCompilerPipelineError {
WasmCompilerPipelineFailure(message~ : String)
} derive(Eq,
Debug
)

WasmCompilerPipelineError::equal

WasmCompilerPipelineError::not_equal

WasmCompilerPipelineError::output

fn WasmCompilerPipelineError::output(self : WasmCompilerPipelineError, logger : &Logger) -> Unit

WasmCompilerPipelineError::to_string

CompilerInfrastructureAssembly

pub(all) struct CompilerInfrastructureAssembly {
signature :
Signature

milkir_function :
Function

code_object :
JitCodeObject

}

RuntimeAssembly

pub(all) struct RuntimeAssembly {
product_module : String
owned_components : Array[RuntimeComponent]
product_support_modules : Array[String]
reusable_modules : Array[String]
} derive(Eq,
Debug
)

RuntimeAssembly::equal

RuntimeAssembly::not_equal

fn RuntimeAssembly::not_equal(x : RuntimeAssembly, y : RuntimeAssembly) -> Bool

RuntimeComponent

pub(all) enum RuntimeComponent {
Cli
RuntimeObjects
InterpreterExecutor
WasiPreview1
JitSelection
} derive(Eq,
Debug
)

RuntimeComponent::equal

RuntimeComponent::not_equal

fn RuntimeComponent::not_equal(x : RuntimeComponent, y : RuntimeComponent) -> Bool

compiler_infrastructure_assembly

fn compiler_infrastructure_assembly() -> CompilerInfrastructureAssembly raise WasmCompilerPipelineError

runtime_assembly

fn runtime_assembly() -> RuntimeAssembly

wasm_frontend_embedding_environment

fn wasm_frontend_embedding_environment(runtime_symbol_prefix? : String, hidden_context_type? :
Type
?, cancellation_safepoints? : Bool) ->
EmbeddingEnvironment