Go Tooling

The TinyGo compiler v0.34.0 and above has native support for the WebAssembly Component Model and WASI 0.2.0. This guide walks through building a component that implements example world defined in the add.wit package. The component will implement a simple add function.

1. Install the tools

Follow the TinyGo installation instructions to install the TinyGo compiler. Additionally, install the wasm-tools CLI tool from the wasm-tools repository.

To verify the installation, run the following commands:

$ tinygo version
tinygo version 0.34.0 ...
$ wasm-tools -V
wasm-tools 1.219.1 ...

Optional: Install the wkg CLI tool to resolve the imports in the WIT file. The wkg CLI is a part of the Wasm Component package manager

2. Determine which World the Component will Implement

The wasip2 target of TinyGo assumes that the component is targeting wasi:cli/command@0.2.0 world so it requires the imports of wasi:cli/imports@0.2.0. We need to include them in the add.wit.

Tools like wkg can be convenient to build a complete WIT package by resolving the imports.

# wit/add.wit
package docs:adder@0.1.0;
world adder {
  include wasi:cli/imports@0.2.0;
  export add: func(x: s32, y: s32) -> s32;
}

Running the wkg wit build command will resolve the imports and generate the complete WIT file encoded as a Wasm component.

$ wkg wit build       
WIT package written to docs:adder@0.1.0.wasm

The docs:adder@0.1.0.wasm file is a Wasm encoding of the WIT package. Next, we can generate the bindings for it:

$ go get go.bytecodealliance.org/cmd/wit-bindgen-go
$ go run go.bytecodealliance.org/cmd/wit-bindgen-go generate -o internal/ ./docs:adder@0.1.0.wasm

Now, create your Go project:

$ mkdir add && cd add
$ go mod init example.com

Next, we can generate the bindings for the add.wit file:

$ go get go.bytecodealliance.org/cmd/wit-bindgen-go
$ go run go.bytecodealliance.org/cmd/wit-bindgen-go generate -o internal/ ./docs:adder@0.1.0.wasm

The internal directory will contain the generated Go code that WIT package.

$ tree internal
internal
├── docs
│   └── adder
│       └── adder
│           ├── adder.exports.go
│           ├── adder.wasm.go
│           ├── adder.wit
│           ├── adder.wit.go
│           └── empty.s
└── wasi
    ├── cli
    │   └── stdout
    │       ├── empty.s
    │       ├── stdout.wasm.go
    │       └── stdout.wit.go
    ├── io
    │   ├── error
    │   │   ├── empty.s
    │   │   ├── error.wit.go
    │   │   └── ioerror.wasm.go
    │   └── streams
    │       ├── empty.s
    │       ├── streams.wasm.go
    │       └── streams.wit.go
    └── random
        └── random
            ├── empty.s
            ├── random.wasm.go
            └── random.wit.go

The adder.exports.go file contains the exported functions that need to be implemented in the Go code called Exports.

3. Implement the add Function

package main

import (
	"example.com/internal/example/component/example"
)

func init() {
	example.Exports.Add = func(x int32, y int32) int32 {
		return x + y
	}
}

// main is required for the `wasi` target, even if it isn't used.
func main() {}

Go's init functions are used to do initialization tasks that should be done before any other tasks. In this case, we are using it to export the Add function.

4. Build the Component

We can build our component using TinyGo by specifying the wit-package to be add.wit and the WIT world to be adder.

Under the hood, TinyGo invokes wasm-tools to embed the WIT file to the module and componentize it.

$ tinygo build -target=wasip2 -o add.wasm --wit-package add.wit --wit-world adder main.go

We now have an add component that satisfies our adder world, exporting the add function, which we can confirm using the wasm-tools component wit command:

$ wasm-tools component wit add.wasm
package root:component;

world root {
  import wasi:io/error@0.2.0;
  import wasi:io/streams@0.2.0;
  import wasi:cli/stdout@0.2.0;
  import wasi:random/random@0.2.0;

  export add: func(x: s32, y: s32) -> s32;
}
...

5. Testing the add Component

To run our add component, we need to use a host program with a WASI runtime that understands the example world. We've provided an example-host to do just that. It calls the add function of a passed in component providing two operands. To use it, clone this repository and run the Rust program:

git clone git@github.com:bytecodealliance/component-docs.git
cd component-docs/component-model/examples/example-host
cargo run --release -- 1 2 /path/to/add.wasm