gomode
TinyGo + Zig in a single WASM binary. 58KB, SIMD, zero-copy, zero overhead.
The problem
Section titled “The problem”Go on WASM is broken in two different ways:
| Binary size | Stdlib | Speed | |
|---|---|---|---|
| Standard Go → WASM | 3MB+ | Full | Slow (heavy runtime) |
| TinyGo → WASM | ~700KB | Incomplete | Fast |
Standard Go gives you the full stdlib but a massive binary with a heavy runtime. TinyGo gives you a small fast binary but is missing net/http, crypto, full reflect, and more.
GoMode’s answer
Section titled “GoMode’s answer”Use TinyGo for the small binary, use Zig to fill every gap — linked into the same WASM binary via wasm-ld. Zero runtime overhead.
Zig (SIMD, allocator, crypto) ──────────→ zig-abi.oGo handler (TinyGo, CGo, -gc=custom) ──→ wasm-ld → go.wasm (58KB)
JS Worker writes/reads via zerobuf layout (direct DataView on hot path)Go calls Zig via CGo — compiles to direct WASM call instructions. Same linear memory, no imports, no JS proxy, no serialization.
Quick start
Section titled “Quick start”package main
import ( "gomode" "unsafe")
//export handle_zerobuffunc handleZerobuf(reqBase uint32) uint32 { path := readZBString(uintptr(reqBase) + 1*16)
switch path { case "/": return writeResponse(200, "text/plain", "Hello from GoMode!") case "/simd": data := []float64{1, 2, 3, 4, 5, 6, 7, 8} sum := gomode.ZigSimdSumF64( uint32(uintptr(unsafe.Pointer(&data[0]))), uint32(len(data)), ) return writeResponse(200, "text/plain", formatFloat(sum)) } return writeResponse(404, "text/plain", "not found")}
func main() {}npm run build && npm run dev# → http://localhost:8787