Wasmer is a Go library for executing WebAssembly binaries.
To install the library, follow the classical:
# Enable cgo
$ export CGO_ENABLED=1; export CC=gcc;
$ go get github.com/wasmerio/go-ext-wasm/wasmer
To install the go-wasmer
CLI, follow the classical:
$ go install github.com/wasmerio/go-ext-wasm/go-wasmer
go install
will work on many macOS and Linux distributions. It will
not work on Windows yet, we are working on it.
The documentation can be read online on godoc.org. It contains function descriptions, short examples, long examples etc. Everything one need to start using Wasmer with Go!
Also, there is this article written for the announcement that introduces the project: Announcing the fastest WebAssembly runtime for Go: wasmer.
There is a toy program in wasmer/test/testdata/examples/simple.rs
,
written in Rust (or any other language that compiles to WebAssembly):
#[no_mangle]
pub extern fn sum(x: i32, y: i32) -> i32 {
x + y
}
After compilation to WebAssembly, the
wasmer/test/testdata/examples/simple.wasm
binary file is generated. (Download
it).
Then, we can execute it in Go:
package main
import (
"fmt"
wasm "github.com/wasmerio/go-ext-wasm/wasmer"
)
func main() {
// Reads the WebAssembly module as bytes.
bytes, _ := wasm.ReadBytes("simple.wasm")
// Instantiates the WebAssembly module.
instance, _ := wasm.NewInstance(bytes)
defer instance.Close()
// Gets the `sum` exported function from the WebAssembly instance.
sum := instance.Exports["sum"]
// Calls that exported function with Go standard values. The WebAssembly
// types are inferred and values are casted automatically.
result, _ := sum(5, 37)
fmt.Println(result) // 42!
}
A WebAssembly module can export functions, this is how to run a
WebAssembly function, like we did in the previous example with
instance.Exports["sum"](1, 2)
. Nonetheless, a WebAssembly module can
depend on “extern functions”, then called imported functions. For
instance, let's consider the basic following Rust program:
extern {
fn sum(x: i32, y: i32) -> i32;
}
#[no_mangle]
pub extern fn add1(x: i32, y: i32) -> i32 {
unsafe { sum(x, y) + 1 }
}
In this case, the add1
function is a WebAssembly exported function,
whilst the sum
function is a WebAssembly imported function (the
WebAssembly instance needs to import it to complete the
program). Good news: We can write the implementation of the sum
function directly in Go!
First, we need to declare the sum
function signature in C inside a
Go comment (with the help of cgo):
package main
// #include <stdlib.h>
//
// extern int32_t sum(void *context, int32_t x, int32_t y);
import "C"
Second, we declare the sum
function implementation in Go. Notice the
//export
which is the way cgo uses to map Go code to C code.
//export sum
func sum(context unsafe.Pointer, x int32, y int32) int32 {
return x + y
}
Third, we use NewImports
to create the WebAssembly imports. In this
code:
"sum"
is the imported function name,sum
is the Go function pointer, andC.sum
is the cgo function pointer.
imports, _ := wasm.NewImports().Append("sum", sum, C.sum)
Finally, we use NewInstanceWithImports
to inject the imports:
bytes, _ := wasm.ReadBytes("imported_function.wasm")
instance, _ := wasm.NewInstanceWithImports(bytes, imports)
defer instance.Close()
// Gets and calls the `add1` exported function from the WebAssembly instance.
results, _ := instance.Exports["add1"](1, 2)
fmt.Println(result)
// add1(1, 2)
// = sum(1 + 2) + 1
// = 1 + 2 + 1
// = 4
// QED
A WebAssembly instance has a linear memory. Let's see how to read it. Consider the following Rust program:
#[no_mangle]
pub extern fn return_hello() -> *const u8 {
b"Hello, World!\0".as_ptr()
}
The return_hello
function returns a pointer to a string. This string
is stored in the WebAssembly memory. Let's read it.
bytes, _ := wasm.ReadBytes("memory.wasm")
instance, _ := wasm.NewInstance(bytes)
defer instance.Close()
// Calls the `return_hello` exported function.
// This function returns a pointer to a string.
result, _ := instance.Exports["return_hello"]()
// Gets the pointer value as an integer.
pointer := result.ToI32()
// Reads the memory.
memory := instance.Memory.Data()
fmt.Println(string(memory[pointer : pointer+13])) // Hello, World!
In this example, we already know the string length, and we use a slice
to read a portion of the memory directly. Notice that the string
terminates by a null byte, which means we could iterate over the
memory starting from pointer
until a null byte is met; that's a
similar approach.
For a more complete example, see the Greet Example.
The Go library is written in Go and Rust.
To build both parts, run the following commands:
$ just rust
$ just build
To build the Go part, run:
$ just build
(Yes, you need just
).
Once the library is build, run the following command:
$ just test
We compared Wasmer to Wagon and Life. The benchmarks
are in benchmarks/
. The computer that ran these benchmarks is a
MacBook Pro 15" from 2016, 2.9Ghz Core i7 with 16Gb of memory. Here
are the results in a table (the lower the ratio is, the better):
Benchmark | Runtime | Time (ms) | Ratio |
---|---|---|---|
N-Body | Wasmer | 42.078 | 1× |
Wagon | 1841.950 | 44× | |
Life | 1976.215 | 47× | |
Fibonacci (recursive) | Wasmer | 28.559 | 1× |
Wagon | 3238.050 | 113× | |
Life | 3029.209 | 106× | |
Pollard rho 128 | Wasmer | 37.478 | 1× |
Wagon | 2165.563 | 58× | |
Life | 2407.752 | 64× |
While both Life and Wagon provide on average the same speed, Wasmer is on average 72 times faster.
Put on a graph, it looks like this (reminder: the lower, the better):
Quoting the WebAssembly site:
WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable target for compilation of high-level languages like C/C++/Rust, enabling deployment on the web for client and server applications.
About speed:
WebAssembly aims to execute at native speed by taking advantage of common hardware capabilities available on a wide range of platforms.
About safety:
WebAssembly describes a memory-safe, sandboxed execution environment […].
The entire project is under the MIT License. Please read the
LICENSE
file.