Go Gets Native WebAssembly Tooling, and the Ecosystem Gap It Fills
Source: eli-bendersky
For years, working with WebAssembly as an artifact, rather than just compiling to it, meant leaving the Go ecosystem. You needed wabt (the WebAssembly Binary Toolkit, written in C++) or wasm-tools (Rust, from the Bytecode Alliance) to do anything serious: parse the text format, validate a module, convert WAT to binary. That gap just got closed.
Eli Bendersky has released watgo, a pure Go, zero-dependency WebAssembly toolkit. It covers the same ground as wabt and wasm-tools: parsing WAT (WebAssembly Text format), validating modules against the spec, encoding to binary WASM, and decoding binary WASM back to a semantic representation. No CGO, no vendored C libraries, just Go.
The Tooling Landscape Before watgo
WebAssembly’s tooling has historically been organized around two major projects, both outside the Go world.
wabt came first, built alongside the WebAssembly specification itself. It’s a C++ project maintained by the WebAssembly community group and includes a collection of tools: wat2wasm for text-to-binary conversion, wasm2wat for disassembly, wasm-validate for checking modules against the spec, wasm-objdump for inspecting binary structure, and wasm-strip for removing custom sections. These tools are robust and widely used, but integrating them into Go tooling means either subprocess calls or writing CGO bindings.
wasm-tools, maintained by the Bytecode Alliance, takes a Rust-first approach and has expanded significantly beyond the original scope, now covering the Component Model and WIT (WebAssembly Interface Types). It’s excellent if your stack is already Rust-centric or if you’re working with the broader WASI ecosystem. For Go, again: subprocess or FFI.
Both are fine choices when you’re just running a build pipeline. When you want to build Go programs that work with WebAssembly programmatically, as a first-class data structure you can inspect and manipulate, the dependency situation starts to hurt.
What watgo Actually Provides
The toolkit has four core capabilities, which form a clean pipeline:
- Parse: Convert WAT source text into a
wasmirrepresentation - Validate: Check a
wasmirmodule against the official WebAssembly validation semantics - Encode: Serialize
wasmirinto the binary WASM format - Decode: Read a WASM binary and produce a
wasmirrepresentation
At the center is wasmir, the semantic intermediate representation of a WebAssembly module. This isn’t just an AST of the text format. It’s a semantic model: functions with resolved types, imports and exports as structured data, the full type section, memory layout, global initializers. It’s the thing you’d want when writing a tool that analyzes or transforms WebAssembly.
The CLI installs the standard Go way:
go install github.com/eliben/watgo/cmd/watgo@latest
A basic conversion from text to binary:
watgo parse stack.wat -o stack.wasm
That command parses the WAT file, runs validation, and writes the binary. It’s designed to be compatible with wasm-tools’ parse subcommand, which means existing build scripts can switch without modification. Bendersky has already switched his wasm-wat-samples project over, which signals confidence in the tool’s readiness beyond announcement.
The Go API and What wasmir Enables
The CLI is the entry point, but the library is where things get more interesting. The Go API lets you parse, validate, inspect, and encode WebAssembly modules as ordinary Go values:
package main
import (
"fmt"
"github.com/eliben/watgo"
"github.com/eliben/watgo/wasmir"
)
const wasmText = `
(module
(func (export "add") (param i32) (param i32) (result i32)
local.get 0
local.get 1
i32.add))
`
func main() {
mod, err := watgo.ParseText([]byte(wasmText))
if err != nil {
panic(err)
}
if err := watgo.Validate(mod); err != nil {
panic(err)
}
for _, fn := range mod.Funcs {
fmt.Printf("function: %v, params: %v, results: %v\n",
fn.Name, fn.Type.Params, fn.Type.Results)
}
encoded, err := watgo.Encode(mod)
if err != nil {
panic(err)
}
fmt.Printf("binary size: %d bytes\n", len(encoded))
}
The key is that wasmir.Module is a first-class Go struct you can range over and inspect. This is the surface that lets you build higher-level tools: WASM analyzers, size profilers, module mergers, import/export auditors, anything that needs to understand module structure without reimplementing the binary format parser yourself. The round-trip story (WAT to wasmir to binary, or binary to wasmir to WAT) means you can read existing compiled WASM, make programmatic changes, and write it back out.
What WebAssembly Validation Actually Checks
It’s worth being precise about what “validate” means here, because it’s not syntax checking.
The WebAssembly validation specification defines a type system for the stack-based instruction set. Every instruction has a type signature: it consumes some values from the operand stack and produces others. Validation ensures those signatures are consistent throughout the instruction sequence, that control flow structures (blocks, loops, ifs) are properly nested, that branch targets have compatible types, and that all references to functions, tables, memories, and globals are within bounds.
This is non-trivial to implement correctly. The spec’s validation algorithm uses a form of abstract interpretation over a polymorphic stack type. Handling the unreachable instruction correctly is a common source of bugs in custom implementations: after unreachable, the operand stack becomes polymorphic for the remainder of its enclosing block, meaning any instruction is locally type-correct until the block ends. Getting this right across nested control flow requires careful tracking of stack state through every branch.
Having a correct implementation of the full validation spec as a Go library, rather than having to shell out to wasm-validate, removes a significant burden from anyone building WebAssembly tooling in Go.
Zero Dependency in the Go Sense
The “zero-dependency” claim carries more weight in the Go ecosystem than it might elsewhere. Combined with no CGO, it means:
go getworks without system libraries to install or configure- Cross-compilation continues to work without a C++ toolchain;
GOOS=linux GOARCH=arm64 go buildjust works - No CGO means no compilation overhead and no need to set
CGO_ENABLED=0explicitly in containerized builds - The module graph stays clean; downstream users of the library don’t inherit transitive dependencies they didn’t ask for
For a library that will end up as a build-time or toolchain dependency in Go projects that emit or process WASM, these properties compound quickly. Projects using watgo as a library dependency can include it without worrying about what it pulls in.
Context in Go’s WebAssembly Story
Go can compile to WebAssembly through two paths: GOOS=js targets the browser via the JavaScript runtime, and GOOS=wasip1 targets the WASI preview 1 interface for server-side and embedded WASM hosts. TinyGo covers similar ground with meaningfully smaller output binaries, which matters in environments where WASM size is constrained.
What has been missing is the tooling layer for working with WASM artifacts after they’re produced. When you want to inspect a compiled WASM binary, audit its imports, strip debug sections to reduce size, or build a pipeline that processes WASM modules programmatically, the previous answer was: call out to wabt or wasm-tools and cross a language boundary. watgo is the first serious attempt to fill that gap with native Go.
The analogy to other ecosystems is clear. When the Rust community wanted WebAssembly tooling, they didn’t just wrap wabt; they built wasm-tools from scratch and made it idiomatic Rust. The result is a library that integrates naturally with Rust’s ownership model and cargo ecosystem, and has grown to cover the Component Model in ways that wabt never will. watgo follows the same logic for Go: rather than wrapping existing C++ or Rust code through FFI, Bendersky built from the spec up, in pure Go.
The three-language toolkit story now has a complete row. C++ has wabt, Rust has wasm-tools, and Go has watgo. For Go tooling authors who’ve been shelling out to wabt in build scripts, there’s now a cleaner path. For anyone building programmatic WASM processing in Go, there’s an actual library to work with rather than hand-parsing the binary format specification.
The project is available at github.com/eliben/watgo and the CLI installs with a single go install invocation. Given Bendersky’s track record with well-documented, long-maintained Go projects, this is the kind of foundational library the Go WebAssembly tooling ecosystem has been waiting for.