wasmoon

    A slow and insecure runtime for WebAssembly

    wasm
    webassembly
    runtime
    jit
    Download zip
    Author
    Version
    0.15.0
    License
    Apache-2.0
    Last updated
    17 days ago
    Downloads
    165

    #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