The Go Blog

Extensible Wasm Applications with Go

Cherry Mui
13 February 2025

Go 1.24 enhances its WebAssembly (Wasm) capabilities with the addition of the go:wasmexport directive and the ability to build a reactor for WebAssembly System Interface (WASI). These features enable Go developers to export Go functions to Wasm, facilitating better integration with Wasm hosts and expanding the possibilities for Go-based Wasm applications.

WebAssembly and the WebAssembly System Interface

WebAssembly (Wasm) is a binary instruction format that was initially created for web browsers, providing the execution of high-performance, low-level code at speeds approaching native performance. Since then, Wasm’s utility has expanded, and it is now used in various environments beyond the browser. Notably, cloud providers offer services that directly execute Wasm executables, taking advantage of the WebAssembly System Interface (WASI) system call API. WASI allows these executables to interact with system resources.

Go first added support for compiling to Wasm in the 1.11 release, through the js/wasm port. Go 1.21 added a new port targeting the WASI preview 1 syscall API through the new GOOS=wasip1 port.

Exporting Go Functions to Wasm with go:wasmexport

Go 1.24 introduces a new compiler directive, go:wasmexport, which allows developers to export Go functions to be called from outside of the Wasm module, typically from a host application that runs the Wasm runtime. This directive instructs the compiler to make the annotated function available as a Wasm export in the resulting Wasm binary.

To use the go:wasmexport directive, simply add it to a function definition:

//go:wasmexport add
func add(a, b int32) int32 { return a + b }

With this, the Wasm module will have an exported function named add that can be called from the host.

This is analogous to the cgo export directive, which makes the function available to be called from C, though go:wasmexport uses a different, simpler mechanism.

Building a WASI Reactor

A WASI reactor is a WebAssembly module that operates continuously, and can be called upon multiple times to react on events or requests. Unlike a “command” module, which terminates after its main function finishes, a reactor instance remains live after initialization, and its exports remain accessible.

With Go 1.24, one can build a WASI reactor with the -buildmode=c-shared build flag.

$ GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o reactor.wasm

The build flag signals to the linker not to generate the _start function (the entry point for a command module), and instead generate an _initialize function, which performs runtime and package initialization, along with any exported functions and their dependencies. The _initialize function must be called before any other exported functions. The main function will not be automatically invoked.

To use a WASI reactor, the host application first initializes it by calling _initialize, then simply invoke the exported functions. Here is an example using Wazero, a Go-based Wasm runtime implementation:

// Create a Wasm runtime, set up WASI.
r := wazero.NewRuntime(ctx)
defer r.Close(ctx)
wasi_snapshot_preview1.MustInstantiate(ctx, r)

// Configure the module to initialize the reactor.
config := wazero.NewModuleConfig().WithStartFunctions("_initialize")

// Instantiate the module.
wasmModule, _ := r.InstantiateWithConfig(ctx, wasmFile, config)

// Call the exported function.
fn := wasmModule.ExportedFunction("add")
var a, b int32 = 1, 2
res, _ := fn.Call(ctx, api.EncodeI32(a), api.EncodeI32(b))
c := api.DecodeI32(res[0])
fmt.Printf("add(%d, %d) = %d\n", a, b, c)

// The instance is still alive. We can call the function again.
res, _ = fn.Call(ctx, api.EncodeI32(b), api.EncodeI32(c))
fmt.Printf("add(%d, %d) = %d\n", b, c, api.DecodeI32(res[0]))

The go:wasmexport directive and the reactor build mode allow applications to be extended by calling into Go-based Wasm code. This is particularly valuable for applications that have adopted Wasm as a plugin or extension mechanism with well-defined interfaces. By exporting Go functions, applications can leverage the Go Wasm modules to provide functionality without needing to recompile the entire application. Furthermore, building as a reactor ensures that the exported functions can be called multiple times without requiring reinitialization, making it suitable for long-running applications or services.

Supporting rich types between the host and the client

Go 1.24 also relaxes the constraints on types that can be used as input and result parameters with go:wasmimport functions. For example, one can pass a bool, a string, a pointer to an int32, or a pointer to a struct which embeds structs.HostLayout and contains supported field types (see the documentation for detail). This allows Go Wasm applications to be written in a more natural and ergonomic way, and removes some unnecessary type conversions.

Limitations

While Go 1.24 has made significant enhancements to its Wasm capabilities, there are still some notable limitations.

Wasm is a single-threaded architecture with no parallelism. A go:wasmexport function can spawn new goroutines. But if a function creates a background goroutine, it will not continue executing when the go:wasmexport function returns, until calling back into the Go-based Wasm module.

While some type restrictions have been relaxed in Go 1.24, there are still limitations on the types that can be used with go:wasmimport and go:wasmexport functions. Due to the unfortunate mismatch between the 64-bit architecture of the client and the 32-bit architecture of the host, it is not possible to pass pointers in memory. For example, a go:wasmimport function cannot take a pointer to a struct that contains a pointer-typed field.

Conclusion

The addition of the ability to build a WASI reactor and export Go functions to Wasm in Go 1.24 represent a significant step forward for Go’s WebAssembly capabilities. These features empower developers to create more versatile and powerful Go-based Wasm applications, opening up new possibilities for Go in the Wasm ecosystem.

Next article: Testing concurrent code with testing/synctest
Previous article: Go 1.24 is released!
Blog Index