WebAssembly is a portable binary format. That means the same file can run anywhere.
To uphold this bold statement, each language, platform and system must be able to run WebAssembly — as fast and safely as possible.
- Using C and C++ bindings
- In PHP, using
- In Python, using
[python-ext-wasm](https://github.com/wasmerio/python-ext-wasm)— wasmer package on PyPI
- In Ruby, using
[ruby-ext-wasm](https://github.com/wasmerio/ruby-ext-wasm)— wasmer gem on RubyGems
- It is now time to hang around Go ?!
We are super happy to announce
github.com/wasmerio/go-ext-wasm/wasmer, a Go library to run WebAssembly binaries, fast.
First, let’s install
wasmer in your go environment (with cgo support).
- export CGO_ENABLED=1; export CC=gcc; go install github.com/wasmerio/go-ext-wasm/wasmer
Let’s jump immediately into some examples.
github.com/wasmerio/go-ext-wasm/wasmer is a regular Go library. The installation is automated with
Let’s get our hands dirty. We will write a program that compiles to WebAssembly easily, using Rust for instance:
After compilation to WebAssembly, we get a file like this one, named
The following Go program executes the
sum function by passing
37 as arguments:
Great! We have successfully executed a WebAssembly file inside Go.
Note: Go values passed to the WebAssembly exported function are automatically cast to WebAssembly values. Types are inferred and casting is done automatically. Thus, a WebAssembly function acts as any regular Go function.
A WebAssembly module exports some functions, so that they can be called from the outside world. This is the entry point to execute WebAssembly.
Nonetheless, a WebAssembly module can also have imported functions. Let’s consider the following Rust program:
The exported function
add1 calls the
sum function. Its implementation is absent, only its signature is defined. This is an “extern function”, and for WebAssembly, this is an imported function, because its implementation must be imported.
Let’s implement the
sum function in Go! To do so, need to use cgo:
sumfunction signature is defined in C (see the comment above
sumimplementation is defined in Go. Notice the
//exportwhich is the way cgo uses to map Go code to C code,
NewImportsis an API used to create WebAssembly imports. In this code
"sum"is the WebAssembly imported function name,
sumis the Go function pointer, and
C.sumis the cgo function pointer,
NewInstanceWithImportsis the constructor to use to instantiate the WebAssembly module with imports. That’s it.
Let’s see the complete program:
A WebAssembly instance has a linear memory. Let’s see how to read it. Consider the following Rust program:
return_hello function returns a pointer to a string. The string terminates by a null byte, à la C. Let’s jump on the Go side:
return_hello function returns a pointer as an
i32 value. We get its value by calling
ToI32. Then, we fetch the memory data with
This function returns a slice over the WebAssembly instance memory. It can be used as any regular Go slice.
Fortunately for us, we already know the length of the string we want to read, so
memory[pointer : pointer+13] is enough to read the bytes, that are then cast to a string. Et voilà !
You can read the Greet Example to see a more advanced usage of the memory API.
github.com/wasmerio/go-ext-wasm/wasmer has a nice API, but …is it fast?
Contrary to PHP or Ruby, there are already existing runtimes in the Go world to execute WebAssembly. The main candidates are:
- Life, from Perlin Network, a WebAssembly interpreter
- Wagon, from Go Interpreter, a WebAssembly interpreter and toolkit.
In our blog post about the PHP extension, we have used the n-body algorithm to benchmark the performance. Life provides more benchmarks: the Fibonacci algorithm (the recursive version), the Pollard’s rho algorithm, and the Snappy Compress operation. The latter works successfully with
github.com/wasmerio/go-ext-wasm/wasmer but not with Life or Wagon. We have removed it from the benchmark suites. Benchmark sources are online.
We use Life 20190521143330–57f3819c2df0, and Wagon 0.4.0, i.e. the latest versions to date.
The benchmark numbers represent the average result for 10 runs each. The computer that ran these benchmarks is a MacBook Pro 15” from 2016, 2.9Ghz Core i7 with 16Gb of memory.
Results are grouped by benchmark algorithm on the X axis. The Y axis represents the time used to run the algorithm, expressed in milliseconds. The lower, the better.
While both Life and Wagon provide on average the same speed, Wasmer (
github.com/wasmerio/go-ext/wasmer) is on average 72 times faster ?.
It is important to know that Wasmer comes with 3 backends: Singlepass, Cranelift, and LLVM. The default backend that is used by the Go library is Cranelift (learn more about Cranelift). Using LLVM will provide performance close to native, but we decided to start with Cranelift as it offers the best tradeoff between compilation-time and execution-time (learn more about the different backends, when to use them, pros and cons etc.).
[github.com/wasmerio/go-ext-wasm/wasmer](https://github.com/wasmerio/go-ext-wasm) is a new Go library to execute WebAssembly binaries. It embeds the Wasmer runtime. The first version supports all the required API for the most common usages.
The current benchmarks (a mix from our benchmark suites and from Life suites) show that Wasmer is — on average — 72 times faster than Life and Wagon, the two major existing WebAssembly runtimes in the Go world.
If you want to follow the development, take a look at @wasmerio and @mnt_io on Twitter, or @wasmer"">@email@example.com on Mastodon. And of course, everything is open source at https://github.com/wasmerio/go-ext-wasm.
Thank you for your time, we can’t wait to see what you build with us!