Wazero: Zero dependency WebAssembly runtime written in Go
wazero.io
wazero.io
At Segment I always wanted a good sandbox environment for running customer code. This is the ideal solution, and the fact that it doesn't require CGo: :chefs_kiss:
Happy to answer any questions people considering using it might have!
For this use case, wazero appears to be the cleanest solution as it's the only runtime that doesn't need CGo. The biggest appeal to me embedding WASM in Go is avoiding CGo to allow cross compilation. If my dependency is using CGo anyway, I might as well as link against a C library. I think native WASM runtimes like wazero are currently the best options for porting code language-agnostically.
I also had to play around a bit with wasm relocations and the encoding/decoding libraries they've built are very readable, too, so I was able to hack together what I needed.
Fingers crossed for the optimizing compiler that's planned for some point!
I did compile some helper functions from Rust though, which also served as the "skeleton" of the wasm artifact I was later generating my wasm into, since wasm doesn't have any kind of proper dynamic linking right now.
That said, here's some of the wazero-based code on a branch - https://github.com/cube2222/octosql/tree/wasm-experiment/was...
It really is just a very very basic prototype.
Just like JavaScript was made to run in a browser, but additional runtimes have been created.
https://github.com/cue-lang/cue/blob/0520a3f9e73e63d77e43c9b...
You can already do CUE via WASM, this is how the playground works.
This is not just one of those "written in $LANG" kinda deal: most other runtimes require linking against a native library; this in turn plays against a lot of the killer features of Go, such as good tooling, easy cross-compilation, goroutines [0]; so, not requiring Cgo means a team may bring wazero to their Go project without a second thought.
As for other features, we may not be as bleeding edge as other projects, but we are a rather tiny team. If you expect support for WASI (preview 1) we have it :) we try to concentrate our effort on high-quality support of stable specs.
EDIT: oh, and a huge shout-out to Stealthrocket, they are building a lot of cool stuff on top of wazero:
- wzprof, a wasm profiler integrating with pprof https://github.com/stealthrocket/wzprof
- timecraft, a wasm time-traveling runtime https://github.com/stealthrocket/timecraft
[0] see "CGO is not Go" https://dave.cheney.net/2016/01/18/cgo-is-not-go
[1] https://github.com/bytecodealliance/wasmtime [2] https://github.com/bytecodealliance/wasmtime-go
The Wasm model itself gives a certain degree of safety: e.g. you do not have complete control over the host OS, because you have to explicitly expose functions to the gues module; we also provide a certain degree of control over e.g. execution time (you can instantiate a module in such a way that executions can be cancelled or set a timeout) and memory space.
We also provide an interpreter for all platforms where Go runs, and Takeshi (the founder of the project) is also working on bootstrapping an optimizing compiler that should soon land on main, and more work will happen in that space.
The good news is an optimizing backend is being worked on as we speak and should be available very soon :)
Also, how do you sandbox the guest program? Just bounds checks on memory accesses? Do you use the same trick as wasmtime does where they map a ton of inaccessible address space and depend on a SIGSEGV to catch violations? What about potential stack overflows - how do you protect against those?
as for the second, that's an excellent question but I'll have to be honest and tell you that I am the n00b of the team and I don't know the answer :D
I can refer you to these possibly related docs
- https://github.com/tetratelabs/wazero/blob/main/RATIONALE.md...
- https://wazero.io/docs/how_do_compiler_functions_work/ (e.g. traps are handled on the Go side)
...and then my awesome team mates might get back to you with a proper answer later when they wake up :^)
Also, there's been some progress since this was published, but results are still mostly valid: https://00f.net/2023/01/04/webassembly-benchmark-2023/
An optimizing compiler is being worked on; the current single pass compiler compiles fast, but generated code quality is hard to improve.
This has a runtime cost, the reduction of which was a focus of the last release: https://github.com/tetratelabs/wazero/releases/tag/v1.2.1
The optimizing compiler should also help here (with bounds checks elimination, and elision of some other checks, like divide-by-zero).
I just wanted to say that the wazero team has done a great job keeping the integration up to date and have been very responsive. For example, our project also supports Linux Arm32 and wazero was initially not testing against that architecture which led to a compile issue for us. Once we pointed this out they fixed this and added this architecture to their CI tests as well.
EDIT: Also because our project can be compiled for any common O/S and architecture combination the pure Go implementation is critical. We do not accept use of CGO.
Try it yourself, clone their repo and ``make dist``
That's what I love about Go
wazero under the hood, by one of our awesome community members
go plugin package gets close, but the compile part is completely broken, where in reality the plugin has dependencies that can collide with the host. It would be fine if it was just the go runtime requirement that must match, but in reality it forces usage of cgo and a set of problems that make it really hard to use.
Plugins in wordpress are installed from the web UI and that's all, these just work with the same performance as the whole application (terrible native performance, but that's a PHP problem).
In go to achieve that I would need to compile the plugins on the fly, as well as recompile the whole application, instead of delivering an executable file and a bunch of dynamic libraries.
In .NET this was further used in large applications to reduce boot time, by loading code when it was first needed (DLLs)
we embed wasmtime in your Go right now, but not for long…
Then I have a HTTP server in Go, which loads the wasm module on startup, just like dlopen but with a small ~10% performance overhead, rips the HTTP payload out, and calls the WASM function with the arguments filled in.
If you go that route (use wazero instead) and want to avoid cgo, you may be interested in my wazero based SQLite bindings: https://github.com/ncruces/go-sqlite3
It's almost time I dive in. I would like to be able to call android/iOS routines with this.
Wondering what the overhead would be too.
Or it replaces cloudflare workers instead?
Also, the Go SDK. There is also the OS and the hardware.
I think what they mean to say is that their module has no additional dependencies on other modules. But, how much should users care? Go is pretty good at managing dependencies and they’re okay within reason.
It seems like an odd way to name your module? I guess it shows commitment. But if they later decide on adding a dependency, they could rename it.
But I suppose generated Go code should be okay? Definitions are hard.
“Why zero?
By avoiding CGO, wazero avoids prerequisites such as shared libraries or libc, and lets you keep features like cross compilation. Being pure Go, wazero adds only a small amount of size to your binary. Meanwhile, wazero’s API gives features you expect in Go, such as safe concurrency and context propagation.”
Though that seems to be more about specifically C dependencies