BTC
ETH
SOL
BNB
GOLD
XRP
DOGE
ADA
Back to home
Tech

watgo – a WebAssembly Toolkit for Go – Eli Bendersky’s website

Eli Bendersky just released watgo 1.0, a pure-Go toolkit for WebAssembly that parses WAT text, validates modules, encodes to WASM binaries, and decodes binaries back to an internal representation.

Eli Bendersky just released watgo 1.0, a pure-Go toolkit for WebAssembly that parses WAT text, validates modules, encodes to WASM binaries, and decodes binaries back to an internal representation. Zero dependencies, with both CLI and API access. It targets Go developers tired of pulling in C++ wabt or Rust’s wasm-tools for WASM work.

This matters because WebAssembly runs everywhere—browsers, servers, edge devices—via runtimes like Wasmtime or WasmEdge. WAT, its text format, lets humans write and debug modules, but Go lacked a native toolchain. Watgo closes that gap. Parse a WAT file, run official validation semantics, output binary: all in Go. No FFI headaches, no cross-compilation nightmares. For security analysts or crypto devs embedding WASM (think zk proofs or secure enclaves), a pure-Go parser means easier static analysis without toolchain bloat.

CLI: Drop-in for wasm-tools

Install it simply:

$ go install github.com/eliben/watgo/cmd/watgo@latest

The CLI mimics wasm-tools commands for compatibility. Bendersky already migrated his wasm-wat-samples repo to it.

Core workflow:

$ watgo parse stack.wat -o stack.wasm

This reads WAT, parses to internal IR, validates against WASM spec, then encodes binary. Decode works too: watgo decode input.wasm -o output.wat. Skeptical note: benchmarks aren’t public yet, but Go’s speed should handle most modules fine—WASM validation is linear time, not a perf killer for <1MB binaries common in practice.

Implications? Script WASM pipelines in bash or CI without Rust/C++ installed. Test fuzzers or metamorphic tools stay in Go land.

API: wasmir for Analysis and Tweaks

At the core sits wasmir, a semantic IR for WASM modules. It flattens WAT syntax sugar—unfolds blocks, resolves names to indices—matching binary format and validator expectations. Parse WAT bytes straight to wasmir.Module, inspect funcs, types, instructions.

Example: Load WAT, count i32 params, local.gets, and i32.adds across funcs.

package main

import (
	"fmt"
	"github.com/eliben/watgo"
	"github.com/eliben/watgo/wasmir"
)

const wasmText = `
(module
  (func (export "add") (param i32 i32) (result i32)
    local.get 0
    local.get 1
    i32.add
  )
  (func (param f32 i32) (result i32)
    local.get 1
    i32.const 1
    i32.add
    drop
    i32.const 0
  )
)
`

func main() {
	m, err := watgo.ParseWAT([]byte(wasmText))
	if err != nil {
		panic(err)
	}

	i32Params := 0
	localGets := 0
	i32Adds := 0

	for _, fn := range m.Funcs {
		sig := m.Types[fn.TypeIdx]
		for _, param := range sig.Params {
			if param.Kind == wasmir.ValueKindI32 {
				i32Params++
			}
		}
		for _, instr := range fn.Body {
			switch instr.Kind {
			case wasmir.InstrLocalGet:
				localGets++
			case wasmir.InstrI32Add:
				i32Adds++
			}
		}
	}

	fmt.Printf("module-defined funcs: %d\n", len(m.Funcs))
	fmt.Printf("i32 params: %d\n", i32Params)
	fmt.Printf("local.get instructions: %d\n", localGets)
	fmt.Printf("i32.add instructions: %d\n", i32Adds)
}

Output: 2 funcs, 2 i32 params, 2 local.gets, 1 i32.add. Clean API—m.Funcs is slice of bodies as flat Instr slices. Want to mutate? Tweak wasmir, re-encode.

Under the hood, textformat package holds syntactic AST before lowering to wasmir. Preserves labels, folds for pretty-printing if needed.

Why build on this? Roll custom validators for side-channel leaks in WASM crypto libs. Or optimizers for TinyGo outputs. Go’s WASM story strengthens—watgo lowers barriers for security researchers probing modules (e.g., Spectre in WASM). Fair caveat: it’s new, so audit the parser yourself for prod. Source on GitHub: github.com/eliben/watgo. At ~10k LOC pure Go, it’s auditable.

April 10, 2026 · 3 min · 11 views · Source: Lobsters

Related