Skip to content

Commit e209a6f

Browse files
Merge pull request #77 from kate-goldenring/go-language-guide
Add Go language guide
2 parents 6c7387b + 2be771c commit e209a6f

File tree

4 files changed

+291
-2
lines changed

4 files changed

+291
-2
lines changed

component-model/examples/example-host/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ world example {
1313
```
1414

1515
The application uses [`wasmtime`](https://github.com/bytecodealliance/wasmtime)
16-
crates to generate Rust bindings, bring in WASI worlds, and executes the `run`
16+
crates to generate Rust bindings, bring in WASI worlds, and executes the `add`
1717
function of the component.
1818

1919
It takes in two operands and a path to a component. It passes the operands to

component-model/src/SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
- [Rust](./language-support/rust.md)
1919
- [Javascript](./language-support/javascript.md)
2020
- [Python](./language-support/python.md)
21+
- [Go](./language-support/go.md)
2122
- [Creating and Consuming Components](./creating-and-consuming.md)
2223
- [Authoring Components](./creating-and-consuming/authoring.md)
2324
- [Composing Components](./creating-and-consuming/composing.md)

component-model/src/creating-and-consuming/authoring.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
You can write WebAssembly core modules in a wide variety of languages, and the set of languages that can directly create components is growing. See the [Language Support](../language-support.md) section for information on building components directly from source code.
44

5-
If your preferred language supports WebAssembly but not components, you can still create components using the [`wasm-tools component`](https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wit-component) tool. (A future version of this page will cover this in more detail.)
5+
If your preferred language supports WebAssembly but not components, you can still create components using the [`wasm-tools component`](https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wit-component) tool. (A future version of this page will cover this in more detail.)
+288
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
# Go Tooling
2+
3+
The [TinyGo toolchain](https://tinygo.org/docs/guides/webassembly/wasi/) has native support for WASI
4+
and can build Wasm core modules. With the help of some component model tooling, we can then take
5+
that core module and embed it in a component. To demonstrate how to use the tooling, this guide
6+
walks through building a component that implements the `example` world defined in the [`add.wit`
7+
package](../../examples/example-host/add.wit). The component will implement a simple add function.
8+
9+
## Overview of Building a Component with TinyGo
10+
11+
There are several steps to building a component in TinyGo:
12+
13+
1. Determine which world the component will implement
14+
2. Build a Wasm core module using the native TinyGo toolchain
15+
3. Convert the Wasm core module to a component using
16+
[`wasm-tools`](https://github.com/bytecodealliance/wasm-tools)
17+
18+
The following sections will walk through these steps, producing a core Wasm module that targets WASI
19+
preview 1 and converting this core module to a component that supports WASI preview 2.
20+
21+
### 1: The `example` World
22+
23+
The next two sections walk through creating a component that implements the the following [`example`
24+
world](https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/example-host/add.wit):
25+
26+
```wit
27+
package example:component;
28+
29+
world example {
30+
export add: func(x: s32, y: s32) -> s32;
31+
}
32+
```
33+
34+
This is a simple world that exports one `add` function. If you want to go beyond a quick start to a
35+
more realistic example, jump to the [section on implementing worlds with
36+
interfaces](#implementing-worlds-with-interfaces-with-tinygo-and-wit-bindgen).
37+
38+
### 2: Creating a TinyGo Core Wasm Module
39+
40+
The TinyGo toolchain natively supports compiling Go programs to core Wasm modules. Let's create one that implements the `add` function in the [`example`
41+
world](https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/example-host/add.wit).
42+
43+
First, implement a simple add function in `add.go`:
44+
45+
```go
46+
package main
47+
48+
//go:wasm-module yourmodulename
49+
//export add
50+
func add(x, y int32) int32 {
51+
return x + y
52+
}
53+
54+
// main is required for the `wasi` target, even if it isn't used.
55+
func main() {}
56+
```
57+
58+
Note, we must still provide a `main` function. This is a limitation of TinyGo's support of WASI as it currently only supports `main` packages - commands that run start-to-finish and
59+
then exit. Our example program, however, is more like a library which exports an add function that
60+
can be called multiple times; and nothing will ever call its `main` function.
61+
62+
Now, we can use TinyGo to build our core Wasm module:
63+
64+
```sh
65+
tinygo build -o add.wasm -target=wasi add.go
66+
```
67+
68+
You should now have an `add.wasm` module. But at the moment, this is a core module. In the next section, we will convert it into a component.
69+
70+
### 3: Converting a Wasm Core Module to a Component
71+
72+
In the previous step, we produced a core module that implements our `example` world. We now want to
73+
convert to a component to gain the benefits of the component model, such as the ability to compose
74+
with it with other components as done in the [`calculator` component in the
75+
tutorial](../tutorial.md#the-calculator-interface).
76+
TinyGo is actively developing a `wasip2` target (in this [PR](https://github.com/tinygo-org/tinygo/pull/4027)), but for now we must take additional steps to convert the module to a component.
77+
78+
We will use
79+
[`wasm-tools`](https://github.com/bytecodealliance/wasm-tools), a low level tool for manipulating
80+
Wasm modules. Download the
81+
latest release from the [project's
82+
repository](https://github.com/bytecodealliance/wasm-tools/releases/tag/wasm-tools-1.0.44).
83+
84+
We also need to download the WASI preview 1 adapter. TinyGo (similar to C) targets preview 1 of WASI
85+
which does not support the component model (`.wit` files). Fortunately, [Wasmtime provides
86+
adapters](https://github.com/bytecodealliance/wit-bindgen#creating-components-wasi) for adapting
87+
preview 1 modules to preview 2 components. There are adapters for both [reactor and command
88+
components](../creating-and-consuming/authoring.md#command-and-reactor-components). Our `add.wit`
89+
world defines a reactor component, so download the `wasi_snapshot_preview1.reactor.wasm` adapter
90+
from [the latest Wasmtime release](https://github.com/bytecodealliance/wasmtime/releases).
91+
92+
Now that we have all the prerequisites downloaded, we can use the `wasm-tools component` subcommand
93+
to componentize our Wasm module, first embedding component metadata inside the core module and then
94+
encoding the module as a component using the WASI preview 1 adapter.
95+
96+
```sh
97+
export COMPONENT_ADAPTER_REACTOR=/path/to/wasi_snapshot_preview1.reactor.wasm
98+
wasm-tools component embed --world example ./add.wit add.wasm -o add.embed.wasm
99+
wasm-tools component new -o add.component.wasm --adapt wasi_snapshot_preview1="$COMPONENT_ADAPTER_REACTOR" add.embed.wasm
100+
```
101+
102+
We now have an add component that satisfies our `example` world, exporting the `add` function, which
103+
we can confirm using another `wasm-tools` command:
104+
105+
```sh
106+
$ wasm-tools component wit add.component.wit
107+
package root:component
108+
109+
world root {
110+
import wasi:io/streams
111+
import wasi:filesystem/types
112+
import wasi:filesystem/preopens
113+
import wasi:cli/stdin
114+
import wasi:cli/stdout
115+
import wasi:cli/stderr
116+
import wasi:cli/terminal-input
117+
import wasi:cli/terminal-output
118+
import wasi:cli/terminal-stdin
119+
import wasi:cli/terminal-stdout
120+
import wasi:cli/terminal-stderr
121+
122+
export add: func(x: s32, y: s32) -> s32
123+
}
124+
```
125+
126+
### Testing an `add` Component
127+
128+
To run our add component, we need to use a host program with a WASI runtime that understands the
129+
`example` world. We've provided an [`example-host`](../../examples/example-host/README.md) to do
130+
just that. It calls the `add` function of a passed in component providing two operands. To use it,
131+
clone this repository and run the Rust program:
132+
133+
```sh
134+
git clone [email protected]:bytecodealliance/component-docs.git
135+
cd component-docs/component-model/examples/example-host
136+
cargo run --release -- 1 2 /path/to/add.component.wasm
137+
```
138+
139+
## Implementing Worlds with Interfaces with TinyGo and Wit-Bindgen
140+
141+
The [`example`
142+
world](https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/example-host/add.wit) we were using in the previous sections simply exports a function. However, to use your component from another component, it must export an interface. This means we will need to use a tool to generate bindings to use as glue code, and adds a couple more steps (2-3) to building Wasm components with TinyGo:
143+
144+
1. Determine which world the component will implement
145+
2. Generate bindings for that world using
146+
[`wit-bindgen`](https://github.com/bytecodealliance/wit-bindgen#creating-a-component)
147+
3. Implement the interface defined in the bindings
148+
4. Build a Wasm core module using the native TinyGo toolchain
149+
5. Convert the Wasm core module to a component using
150+
[`wasm-tools`](https://github.com/bytecodealliance/wasm-tools)
151+
152+
For this example, we will use the following world, which moves the add function behind an `add` interface:
153+
154+
```wit
155+
package docs:[email protected];
156+
157+
interface add {
158+
add: func(a: u32, b: u32) -> u32;
159+
}
160+
161+
world adder {
162+
export add;
163+
}
164+
```
165+
166+
Our new steps use a low-level tool, [`wit-bindgen`](https://github.com/bytecodealliance/wit-bindgen?tab=readme-ov-file#guest-tinygo) to generate bindings, or wrapper code, for implementing the desired world.
167+
168+
First, install [a release of `wit-bindgen`](https://github.com/bytecodealliance/wit-bindgen/releases), updating the environment variables for your desired version, architecture and OS:
169+
170+
```sh
171+
export VERSION=0.24.0 ARCH=aarch64 OS=macos
172+
wget https://github.com/bytecodealliance/wit-bindgen/releases/download/v$VERSION/wit-bindgen-$VERSION-$ARCH-$OS.tar.gz
173+
tar -xzf wit-bindgen-$VERSION-$ARCH-$OS.tar.gz
174+
mv wit-bindgen-$VERSION-$ARCH-$OS/wit-bindgen ./
175+
rm -rf wit-bindgen-$VERSION-$ARCH-$OS.tar.gz wit-bindgen-$VERSION-$ARCH-$OS
176+
```
177+
178+
Now, create your Go project:
179+
180+
```sh
181+
mkdir add && cd add
182+
go mod init example.com
183+
```
184+
185+
Next, run `wit-bindgen`, specifying TinyGo as the target language, the path to the
186+
[`add.wit`](../../examples/example-host/add.wit) package, the name of the world in that package to
187+
generate bindings for (`example`), and a directory to output the generated code (`gen`):
188+
189+
```sh
190+
wit-bindgen tiny-go ./add.wit --world example --out-dir=gen
191+
```
192+
193+
The `gen` directory now contains several files:
194+
195+
```sh
196+
$ tree gen
197+
gen
198+
├── adder.c
199+
├── adder.go
200+
└── adder.h
201+
```
202+
203+
The `adder.go` file defines an `ExportsDocsAdder0_1_0_Add` interface that matches the structure of our `add`
204+
interface. The name of the interface is taken from the WIT package name (`docs:[email protected]`) combined with the interface name (`add`). In our Go module, first implement the `ExportsDocsAdder0_1_0_Add` interface by defining the `Add` function.
205+
206+
```go
207+
package main
208+
209+
import (
210+
. "example.com/gen"
211+
)
212+
213+
type AdderImpl struct {
214+
}
215+
216+
// Implement the `ExportsDocsAdder0_1_0_Add` interface to ensure the component satisfies the
217+
// `adder` world
218+
func (i AdderImpl) Add(x, y uint32) uint32 {
219+
return x + y
220+
}
221+
222+
// main is required for the `wasi` target, even if it isn't used.
223+
func main() {}
224+
```
225+
226+
After implementing the adder world, we need to load it by passing it to the `SetExportsDocsAdder0_1_0_Add`
227+
function from our bindings (`adder.go`). Since our component is a library, `main` will not be called. However, only Go
228+
programs with `main` can target WASI currently. As a loophole, we will initialize our `AdderImpl`
229+
type inside an `init` function. Go's `init` functions are used to do initialization tasks that
230+
should be done before any other tasks. In this case, we are using it to export the `Add` function and
231+
make it callable using the generated C bindings (`adder.c`). After populating the `init` function,
232+
our complete implementation looks similar to the following:
233+
234+
```go
235+
package main
236+
237+
import (
238+
. "example.com/gen"
239+
)
240+
241+
type AdderImpl struct {
242+
}
243+
244+
// Implement the ExportsDocsAdder0_1_0_Add interface to ensure the component satisfies the
245+
// `adder` world
246+
func (i AdderImpl) Add(x, y uint32) uint32 {
247+
return x + y
248+
}
249+
250+
// To enable our component to be a library, implement the component in the
251+
// `init` function which is always called first when a Go package is run.
252+
func init() {
253+
example := AdderImpl{}
254+
SetExportsDocsAdder0_1_0_Add(example)
255+
}
256+
257+
// main is required for the `WASI` target, even if it isn't used.
258+
func main() {}
259+
```
260+
261+
Once again, we can build our core module using TinyGo, componentize it, and adapt it for WASI 0.2:
262+
```sh
263+
export COMPONENT_ADAPTER_REACTOR=/path/to/wasi_snapshot_preview1.reactor.wasm
264+
tinygo build -o add.wasm -target=wasi add.go
265+
wasm-tools component embed --world example ./add.wit add.wasm -o add.embed.wasm
266+
wasm-tools component new -o add.component.wasm --adapt wasi_snapshot_preview1="$COMPONENT_ADAPTER_REACTOR" add.embed.wasm
267+
```
268+
269+
We now have an add component that satisfies our `adder` world, exporting the `add` function, which
270+
we can confirm using the `wasm-tools component wit` command:
271+
272+
```sh
273+
wasm-tools component wit add.component.wasm
274+
package root:component;
275+
276+
world root {
277+
import wasi:io/[email protected];
278+
import wasi:io/[email protected];
279+
import wasi:cli/[email protected];
280+
import wasi:cli/[email protected];
281+
import wasi:cli/[email protected];
282+
import wasi:clocks/[email protected];
283+
import wasi:filesystem/[email protected];
284+
import wasi:filesystem/[email protected];
285+
286+
export docs:adder/[email protected];
287+
}
288+
```

0 commit comments

Comments
 (0)