Wasm Interop & Performance — Specification¶
Table of Contents¶
- Introduction
- Where This Is Specified
- The
syscall/jsPackage Surface js.ValueSemantics (Per Reference)js.FuncandRelease(Specified Lifetime)CopyBytesToGo/CopyBytesToJSContract- The
go:wasmimport/go:wasmexportDirectives - Linear Memory and the WebAssembly Spec
- Build Targets and Flags
- Differences Across Go Versions
- References
Introduction¶
The Go language specification (go.dev/ref/spec) does not describe wasm interop or performance — these are properties of the toolchain and the syscall/js package, not the language. The authoritative sources are the package documentation, the compiler directive documentation, the Go Wiki's WebAssembly page, and the upstream WebAssembly and WASI specifications for the host side.
Sources of truth, in decreasing formality: 1. syscall/js package docs — pkg.go.dev/syscall/js, the API contract for the js target. 2. cmd/compile directive docs — go:wasmimport, go:wasmexport semantics. 3. WebAssembly Core Specification — linear memory, memory.grow, value types. 4. WASI preview 1 (wasip1) — the host ABI for the non-browser target. 5. Toolchain source — src/syscall/js/, lib/wasm/wasm_exec.js, src/runtime/.
This file separates what the references guarantee from what is implementation behaviour you can observe but should not depend on.
Where This Is Specified¶
syscall/jsis documented and is explicitly marked as not subject to the Go 1 compatibility promise — its API may change. This is stated in the package docs and matters for long-lived code.- The directives
//go:wasmimportand//go:wasmexportare documented under the compiler's directive list and the WebAssembly wiki page. - Binary layout follows the WebAssembly Core Specification (sections, types, memory).
- The build targets
GOOS=js GOARCH=wasmandGOOS=wasip1 GOARCH=wasmare documented in the Go release notes and thecmd/goenvironment docs.
The syscall/js Package Surface¶
The package is available only when GOOS=js. Its core entry points, per the docs:
| Symbol | Contract |
|---|---|
js.Global() Value | Returns the JavaScript global object (globalThis). |
js.ValueOf(x any) Value | Converts a Go value to a Value. Accepts nil, bool, integers, floats, string, []any, map[string]any, and Value/Func. Other types panic. |
Value.Get(p string) Value | JS v[p]. |
Value.Set(p string, x any) | JS v[p] = x. |
Value.Index(i int) Value / SetIndex(i int, x any) | Array element access. |
Value.Call(m string, args ...any) Value | JS v.m(args...). Panics on JS exception. |
Value.Invoke(args ...any) Value | JS v(args...). |
Value.New(args ...any) Value | JS new v(args...). |
Value.Int()/Float()/Bool()/String() | Extract a Go scalar. Panics on type mismatch. |
Value.Type() Type | One of TypeUndefined, TypeNull, TypeBoolean, TypeNumber, TypeString, TypeSymbol, TypeObject, TypeFunction. |
Value.IsUndefined()/IsNull()/IsNaN()/Truthy() | Predicates. |
js.FuncOf(fn func(this Value, args []Value) any) Func | Wraps a Go func as a callable JS function. |
Func.Release() | Frees the function's reference-table slot. |
js.CopyBytesToGo(dst []byte, src Value) int | Bulk copy from a JS Uint8Array/Uint8ClampedArray to a Go slice. |
js.CopyBytesToJS(dst Value, src []byte) int | Bulk copy from a Go slice to a JS typed array. |
js.Value Semantics (Per Reference)¶
The docs specify, and these are the load-bearing guarantees:
- A
Valuereferences a JavaScript value. It is comparable with==only viaValue.Equal; do not use==onValuedirectly (the docs note the zero value and comparability caveats). ValueOfof an unsupported type panics. The accepted set is exactly the list above.Geton a missing property returns aValueof typeTypeUndefined— not an error and not a panic.- A failed
Call/Invoke/New(the JS side throws) causes a Go panic carrying a value that wraps the JS error; recover to handle it. - Scalar extractors (
Int,Float,Bool,String) panic if the underlying value is not of the requested type. CheckType()first for untrusted input.
What is not specified (implementation detail, do not depend on): the NaN-boxing encoding, the exact reference-table mechanism, slot recycling timing, and finalizer timing.
js.Func and Release (Specified Lifetime)¶
The docs state: FuncOf returns a Func that must be released by calling Release when it is no longer needed, otherwise the program keeps a reference to it (a memory leak). This is a hard contract, not advice:
- A
Funcregistered for the lifetime of the program need not be released (it lives until exit). - A
Funccreated transiently (per event, per promise) must be released, or each creation leaks. - After
Release, invoking the function from JS yields an error.
The package does not specify when the slot is reclaimed beyond "after Release," and does not provide reference counting — the caller owns the lifetime.
CopyBytesToGo / CopyBytesToJS Contract¶
Per the docs: - CopyBytesToGo(dst []byte, src Value) int — copies bytes from src (which must be a Uint8Array or Uint8ClampedArray) into dst. Returns the number of bytes copied, which is min(len(dst), src.length). Panics if src is not a recognised typed array. - CopyBytesToJS(dst Value, src []byte) int — the reverse; dst must be a Uint8Array/Uint8ClampedArray. Returns min(dst.length, len(src)).
The functions perform a single bulk copy. The docs do not guarantee a performance characteristic, but the implementation is a typed-array copy over linear memory.
The go:wasmimport / go:wasmexport Directives¶
For non-js targets (notably wasip1):
//go:wasmimport <module> <name>binds a Go function declaration (no body) to an imported host function. Specified parameter/result types are restricted to wasm-representable scalars:int32,uint32,int64,uint64,float32,float64,unsafe.Pointer, anduintptr(and pointer types under documented rules). Strings/slices/structs are not directly representable and must be passed via pointers into linear memory.//go:wasmexport <name>(Go 1.24+) exports a Go function to the host under<name>, with the same type restrictions.- The directives are documented as part of the compiler's directive set; the exact allowed-type matrix is given in the WebAssembly wiki and release notes and has expanded across versions.
- These directives are not available (and not needed) on the
jstarget, which usessyscall/jsinstead.
Linear Memory and the WebAssembly Spec¶
The host-side behaviour that drives the detached-buffer rule is specified by the WebAssembly Core Specification, not by Go:
- A module has one linear memory, addressed as a contiguous byte array, sized in 64 KiB pages.
- The
memory.growinstruction increases the memory by a number of pages; it returns the previous size or-1on failure. - On the JavaScript embedding side, growing memory may detach the existing
ArrayBufferand replace it with a new one (the JS API spec forWebAssembly.Memory.prototype.growdescribes this). AnyTypedArray/DataViewover the old buffer is invalidated.
Go's runtime calls memory.grow through its allocator; the detachment is a property of the embedding, which is why cached JS views must be re-derived. This is specified behaviour, not a Go quirk.
Build Targets and Flags¶
GOOS=js GOARCH=wasm— browser target; usessyscall/js+wasm_exec.js.GOOS=wasip1 GOARCH=wasm— WASI preview 1; usesgo:wasmimporthost functions. Introduced as a preview in Go 1.21, stabilised thereafter.-ldflags="-s -w"—-somits the symbol table,-womits DWARF debug info. Documented undercmd/link. Reduces binary size by removing debug/name data only.wasm_exec.jslocation:$(go env GOROOT)/lib/wasm/wasm_exec.jsin Go 1.21+; previously$(go env GOROOT)/misc/wasm/wasm_exec.js.
Differences Across Go Versions¶
- Go 1.21 —
wasm_exec.jsmoved tolib/wasm/;wasip1target introduced as a preview;go:wasmimportdocumented for wasip1. - Go 1.22 —
go:wasmimportallowed-type expansion and wasip1 refinements. - Go 1.24 —
//go:wasmexportadded, allowing Go functions to be exported to a wasm host; reactor-style modules become possible. - Across versions, the
syscall/jsAPI remains outside the Go 1 compatibility promise — treat it as stable-in-practice but formally subject to change. - Binary-size floor has trended roughly stable (multiple MB for the
jstarget); no version has eliminated the runtime-ships-inside-the-binary fact for standard Go.
References¶
syscall/jspackage documentation — the authoritative API and the no-compatibility-promise note.- Go Wiki: WebAssembly — targets,
go:wasmimport, size guidance. wasm_exec.jsin the Go source tree — the host-side implementation.- Go compiler directives —
go:wasmimport/go:wasmexport— directive semantics. - WebAssembly Core Specification — linear memory and
memory.grow. - MDN:
WebAssembly.Memory.prototype.grow— ArrayBuffer detachment on grow. - WASI preview 1 — the wasip1 host ABI.
- Related: 02-wasi-and-wasip1, 01-goos-js-wasm-browser, 05-wasm-in-production.
In this topic