Skip to content
c0mm4ndPublic

About

Another wasm interpreter engine for gophers based on pure golang

Topics

Resources

Stars

11 stars

Watchers

1 watching

Forks

Latest commit

 

History

145 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

WASMan (WebAssembly Manager)

Go Reference Wasm core spec CI Go version Release

A WebAssembly interpreter engine for gophers: pure Go, zero dependencies, Go 1.18+.

Features

  • Complete core semantics — passes the official WebAssembly core test suite with zero failures and zero skips (19k+ assertions; see spectest/README.md for the details and how to run it)
  • Full static validation — strict binary decoding plus spec-conformant type checking of every function body at load time
  • Cross-module linking — import/export functions, tables, memories and globals between modules; imported functions execute in their defining module's state, tables dispatch correctly across modules, and mutable globals share storage
  • Post-MVP features — sign-extension operators, non-trapping float→int (trunc_sat), multi-value blocks, multiple tables, bulk-memory segment encodings, reference-type element lists, extended constant expressions
  • Host integration — define Go functions, globals, memories and tables that wasm modules import; reach into instance memory from host functions
  • Metering & safety rails — per-instruction tolls with a budget (TollStation), call-depth limits, panic recovery, and an opt-out (SkipValidation) for trusted-but-nonconforming modules

Usage

As a library

go get github.com/c0mm4nd/wasman
package main

import (
	"fmt"
	"os"

	"github.com/c0mm4nd/wasman"
	"github.com/c0mm4nd/wasman/config"
)

func main() {
	f, _ := os.Open("module.wasm")
	defer f.Close()

	// NewModule decodes AND validates; invalid modules are rejected here
	module, err := wasman.NewModule(config.ModuleConfig{}, f)
	if err != nil {
		panic(err)
	}

	ins, err := wasman.NewInstance(module, nil)
	if err != nil {
		panic(err)
	}

	returns, _, err := ins.CallExportedFunc("fib", 20)
	if err != nil {
		panic(err)
	}
	fmt.Println(returns[0]) // 6765
}

Defining host functions a module can import:

linker := wasman.NewLinker(config.LinkerConfig{})

// a plain Go func, importable as (import "env" "log_i32" (func (param i32)))
_ = linker.DefineFunc("env", "log_i32", func(v int32) { fmt.Println("wasm says:", v) })

// an "advanced" func that can touch the instance (memory, exported calls, tolls)
_ = linker.DefineAdvancedFunc("env", "print", func(ins *wasman.Instance) interface{} {
	return func(ptr, size uint32) {
		fmt.Println(string(ins.Memory.Value[ptr : ptr+size]))
	}
})

ins, err := linker.Instantiate(module)

More in the examples folder — including fully self-contained examples for cross-module linking, validation and gas metering that run with just go run.

As an executable

go install github.com/c0mm4nd/wasman/cmd/wasman@latest
$ wasman -h
Usage of ./wasman:
  -extern-files string
        external modules files
  -func string
        main func (default "main")
  -main string
        main module (default "module.wasm")
  -max-toll uint
        the maximum toll in simple toll station

Example: numeric.wasm

$ wasman -main numeric.wasm -func fib 20 # calc the fibonacci number
{
  type: i32
  result: 6765
  toll: 315822
}

If the max toll is limited, execution aborts on overflow:

$ wasman -main numeric.wasm -max-toll 300000 -func fib 20
panic: toll overflow

Validation

wasman.NewModule performs full static validation (strict binary decoding plus spec-conformant type checking of every function body) and rejects malformed or invalid modules with an error. Modules that older wasman versions loaded permissively may now be refused — that is intentional.

If you must load a trusted-but-nonconforming module, set config.ModuleConfig{SkipValidation: true} and be aware that invalid code can then trap, misbehave, or (with Recover disabled) panic at run time. See examples/validation.

Configuration

config.ModuleConfig knobs:

field effect
TollStation per-instruction metering with a budget; execution errors on overflow
CallDepthLimit caps call nesting; runaway recursion traps instead of overflowing the Go stack
MaxMemoryPages host-side hard cap on linear memory, regardless of the module's declared limits
Recover converts VM panics into returned errors, keeping the host process alive
CanonicalizeNaNs canonicalizes float-arithmetic NaNs for fully deterministic execution
EnableJIT compiles function bodies to native code at instantiation (arm64/amd64); unsupported constructs fall back to the interpreter per function
EnableWideInt exposes the optional u128/u256 import namespaces: 128/256-bit arithmetic over little-endian values in linear memory (EVM division conventions; _s variants cover i128/i256). Beyond the elementary ops, u256.mul_div computes floor(a*b/c) over the full 512-bit product (exact when a*b >= 2^256; c==0 yields 0, a quotient past 256 bits traps) and u256.isqrt computes floor(sqrt(a)) — the DeFi primitives (Uniswap-style mulDiv, geometric-mean minting), both allocation-free (~130ns / ~500ns). With the JIT, add/sub/mul/compare/bitwise/iszero compile to inline native code; div/rem/shifts/mul_div/isqrt dispatch reflection-free to the runtime
SkipValidation skips load-time validation (trusted modules only)

Run-time control on an Instance:

API effect
CallExportedFuncWithContext binds a call to a context.Context: cancellation/timeout interrupts execution
Interrupt stops the running (or next) execution from any goroutine
Reset restores the post-instantiation snapshot (memory + own globals) for instance pooling
Module.Exports lists exports with resolved function signatures before instantiation

Host functions defined with DefineFunc/DefineAdvancedFunc may declare a trailing error return: a non-nil error traps the calling wasm code. Traps carry a wasm backtrace using names from the module's custom name section.

Performance

wasman is the fastest Go WebAssembly interpreter in our cross-runtime benchmarks — ahead of wazero (interpreter mode), wagon and life on every workload — with ~1 allocation per exported call at steady state and the fastest instantiation (including full validation) by 3–14×.

On arm64 and amd64, EnableJIT additionally compiles function bodies to native machine code at instantiation time — a tiered, pure-Go JIT (an optimizing compiler with register allocation and native calls, plus a baseline tier and per-function interpreter fallback; still zero dependencies). It runs these workloads 27–116× faster than the interpreter, which puts wasman in the same performance class as wazero's optimizing compiler: ahead on memory-bound code, level on arithmetic loops, within ~10% on call-heavy recursion (arm64). The whole official test suite passes with the JIT enabled — including under a metering TollStation, which the baseline tier charges inline per opcode (bit-identical gas and trap points to the interpreter, ~17x faster). See bench/README.md for numbers and methodology.

Conformance

The spectest harness converts the official WebAssembly core test suite with wast2json and runs all of it against wasman — behavioral assertions and rejection assertions (assert_invalid, assert_malformed, assert_unlinkable, assert_uninstantiable) alike, including the text-format malformed cases via the wat text-format reader. Current status: pass=19220 fail=0 skip=0. See spectest/README.md.

About

Another wasm interpreter engine for gophers based on pure golang

Topics

Resources

Stars

11 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages