|
| 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 | + |
| 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 | + |
| 278 | + |
| 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