/wazero

wazero lets you run WebAssembly modules with zero platform dependencies

Primary LanguageGoApache License 2.0Apache-2.0

wazero

wazero lets you run WebAssembly modules with zero platform dependencies. Since wazero doesn’t rely on CGO, you keep portability features like cross compilation. Import wazero and extend your Go application with code written in any language!

Example

Here's an example of using wazero to invoke a Fibonacci function included in a Wasm binary.

While our source for this is TinyGo, it could have been written in another language that targets Wasm, such as AssemblyScript/C/C++/Rust/Zig.

package main

import (
	"context"
	"fmt"
	"os"

	"github.com/tetratelabs/wazero/wasi"
	"github.com/tetratelabs/wazero/wasm"
	"github.com/tetratelabs/wazero/wasm/binary"
	"github.com/tetratelabs/wazero/wasm/interpreter"
)

func main() {
	// Default context impl. by Go
	ctx := context.Background()
	// Read WebAssembly binary.
	source, _ := os.ReadFile("examples/testdata/fibonacci.wasm")
	// Decode the binary as WebAssembly module.
	mod, _ := binary.DecodeModule(source)
	// Initialize the execution environment called "store" with Interpreter-based engine.
	store := wasm.NewStore(interpreter.NewEngine())
	// To resolve WASI specific methods, such as `fd_write`
	wasi.RegisterAPI(store)
	// Instantiate the decoded module.
	store.Instantiate(mod, "test")
	// Execute the exported "fibonacci" function from the instantiated module.
	ret, _, _ := store.CallFunction(ctx, "test", "fibonacci", 20)
	// Give us the fibonacci number for 20, namely 6765!
	fmt.Println(ret[0])
}

Status

wazero is an early project, so APIs are subject to change until version 1.0.

There's the concept called "engine" in wazero (which is a word commonly used in Wasm runtimes). Engines are responsible for compiling and executing WebAssembly modules. There are two types of engines are available for wazero, and you have to choose one of them to use wazero runtime:

  1. Interpreter: a naive interpreter-based implementation of Wasm virtual machine. Its implementation doesn't have any platform (GOARCH, GOOS) specific code, therefore interpreter engine can be used for any compilation target available for Go (such as arm64).
  2. JIT engine: compiles WebAssembly modules, generates the machine code, and executing it all at runtime. Currently wazero only implements the JIT compiler for amd64 target. Generally speaking, JIT engine is faster than Interpreter by order of magnitude. However, the implementation is immature and has bunch of aspects that could be impvoved (for example, it just does a singlepass compilation and doesn't do any optimizations, etc.). Please refer to wasm/jit/RATIONALE.md for the design choices and considerations in our JIT engine.

Both of engines passes 100% of WebAssembly spec test suites (on supported platforms).

Engine Usage GOARCH=amd64 GOARCH=others
Interpreter interpreter.NewEngine()
JIT engine jit.NewEngine()

Background

If you want to provide Wasm host environments in your Go programs, currently there's no other choice than using CGO and leveraging the state-of-the-art runtimes written in C++/Rust (e.g. V8, Wasmtime, Wasmer, WAVM, etc.), and there's no pure Go Wasm runtime out there. (There's only one exception named wagon, but it was archived with the mention to this project.)

First of all, why do you want to write host environments in Go? You might want to have plugin systems in your Go project and want these plugin systems to be safe/fast/flexible, and enable users to write plugins in their favorite lanugages. That's where Wasm comes into play. You write your own Wasm host environments and embed Wasm runtime in your projects, and now users are able to write plugins in their own favorite lanugages (AssembyScript, C, C++, Rust, Zig, etc.). As an specific example, you maybe write proxy severs in Go and want to allow users to extend the proxy via Proxy-Wasm ABI. Maybe you are writing server-side rendering applications via Wasm, or OpenPolicyAgent is using Wasm for plugin system.

However, experienced Golang developers often avoid using CGO because CGO is not Go -- Rob Pike, and it introduces another complexity into your projects. But unfortunately, as I mentioned there's no pure Go Wasm runtime out there, so you have to resort to CGO.

Currently any performance optimization hasn't been done to this runtime yet, and the runtime is just a simple interpreter of Wasm binary. That means in terms of performance, the runtime here is infereior to any aforementioned runtimes (e.g. Wasmtime) for now.

However theoretically speaking, this project have the potential to compete with these state-of-the-art JIT-style runtimes. The rationale for that is it is well-know that CGO is slow. More specifically, if you make large amount of CGO calls which cross the boundary between Go and C (stack) space, then the usage of CGO could be a bottleneck.

Since we can do JIT compilation purely in Go, this runtime could be the fastest one for some use cases where we have to make large amount of CGO calls (e.g. Proxy-Wasm host environment, or request-based plugin systems).