Skip to content

Three Layers

GoMode has three layers compiled into a single WASM binary.

GoMode Architecture — three layers: JS Worker, zero-copy data exchange via zerobuf, and WASM (Go + Zig ABI)
CF Request → Worker (JS)
→ writes request as zerobuf tagged values into WASM memory
→ calls handle_zerobuf(reqPtr)
→ Go reads request, calls Zig SIMD internally (direct call)
→ Go writes response at fixed offsets
→ JS reads response directly from WASM memory
→ CF Response

Zig compiles to a relocatable .o and gets linked into go.wasm via wasm-ld. Go calls Zig functions through CGo — direct call instructions, zero overhead.

Compiled with -mcpu=generic+simd128 for WASM SIMD v128 instructions:

zig_simd_sum_f64(ptr, count) → sum of f64 array
zig_simd_dot_f64(a, b, count) → dot product
zig_simd_scale_f64(ptr, count, s) → multiply by scalar
zig_simd_minmax_f64(ptr, count, o) → min/max in one pass
zig_simd_add_f64(dst, a, b, count) → element-wise addition
zig_alloc(len) → allocate in WASM memory
zig_free(ptr, len) → free allocation
zig_free_result(ptr) → free length-prefixed result

Go passes raw pointers to Zig — same linear memory, zero copy.

worker.ts — Entry point. Routes requests to direct WASM or Durable Object. Uses zerobuf to write request fields and read response fields directly in WASM memory. Only provides 3 WASI imports (fd_write, proc_exit, random_get).

go-do.ts — Durable Object. Full parity with worker path — body, headers, cookies, multi-fetch, zero-copy responses. WASM instance persists for DO lifetime.

JS writes request fields directly into WASM memory as zerobuf tagged value slots (16 bytes each). Go reads at fixed offsets. Response body is read as Uint8Array — no string decode/re-encode.

Request: [0]=method [1]=path+query [2]=body [3]=headers [4]=fetch-result [5]=call-index [6+]=fan-out
Response: [0]=status [1]=content-type [2]=body [3]=headers

Go handlers can call http.Get() multiple times. Each call triggers one JS round-trip — no Asyncify needed.

Handler calls http.Get(url1) → returns status=-1, JS fetches url1, replays
Handler calls http.Get(url1) → cached ✓, calls http.Get(url2) → returns status=-1, JS fetches url2, replays
Handler calls http.Get(url1) → cached ✓, calls http.Get(url2) → cached ✓ → handler completes

Layer 3: Go + Zig Handler (examples/hello-worker/)

Section titled “Layer 3: Go + Zig Handler (examples/hello-worker/)”

Standard Go net/http handlers run unchanged on GoMode. The overlay replaces net/http at build time with a WASM-compatible implementation that uses the same types and interfaces.

package main
import (
"encoding/json"
"gomode"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello from GoMode!"))
})
http.HandleFunc("/stats", func(w http.ResponseWriter, r *http.Request) {
data := []float64{1, 2, 3, 4, 5, 6, 7, 8}
sum := gomode.SumF64(data) // Zig SIMD
min, max := gomode.MinMaxF64(data) // Zig SIMD
json.NewEncoder(w).Encode(map[string]float64{
"sum": sum, "min": min, "max": max,
})
})
http.HandleFunc("/exchange", func(w http.ResponseWriter, r *http.Request) {
// Outbound fetch — triggers multi-fetch two-phase protocol
resp, err := http.Get("https://api.example.com/rates")
if err != nil {
http.Error(w, err.Error(), 502)
return
}
// Process resp.Body as normal...
})
http.ListenAndServe(":8080", nil)
}
//export handle_zerobuf
func handleZerobuf(reqBase uint32) uint32 {
return http.HandleRequest(reqBase)
}

Go calls Zig SIMD via CGo — the C header declares Zig function signatures, and TinyGo’s wasm-ld links both into one binary:

go-sdk/zig_abi.h
double zig_simd_sum_f64(uint32_t ptr, uint32_t count);
double zig_simd_dot_f64(uint32_t a, uint32_t b, uint32_t count);
// go-sdk/gomode.go — high-level API wrapping CGo calls
func SumF64(data []float64) float64
func DotF64(a, b []float64) float64
func MinMaxF64(data []float64) (min, max float64)
func ScaleF64(data []float64, scalar float64)