From 4eb31f783f7de52ea149558c38036451cd193da9 Mon Sep 17 00:00:00 2001 From: Kate Goldenring Date: Fri, 22 Sep 2023 17:14:41 -0700 Subject: [PATCH 1/4] Add Go language guide Signed-off-by: Kate Goldenring --- component-model/src/SUMMARY.md | 1 + .../src/creating-and-consuming/authoring.md | 6 ++ component-model/src/language-support/go.md | 89 +++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 component-model/src/language-support/go.md diff --git a/component-model/src/SUMMARY.md b/component-model/src/SUMMARY.md index 736df0b..716ecc8 100644 --- a/component-model/src/SUMMARY.md +++ b/component-model/src/SUMMARY.md @@ -18,6 +18,7 @@ - [Rust](./language-support/rust.md) - [Javascript](./language-support/javascript.md) - [Python](./language-support/python.md) + - [Go](./language-support/go.md) - [Creating and Consuming Components](./creating-and-consuming.md) - [Authoring Components](./creating-and-consuming/authoring.md) - [Composing Components](./creating-and-consuming/composing.md) diff --git a/component-model/src/creating-and-consuming/authoring.md b/component-model/src/creating-and-consuming/authoring.md index f393d4c..d7e7d05 100644 --- a/component-model/src/creating-and-consuming/authoring.md +++ b/component-model/src/creating-and-consuming/authoring.md @@ -3,3 +3,9 @@ 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. 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.) + +## Command and Reactor Components + +There are two categories of components commonly referred to by component model tooling: reactor and command components. Generally, reactor components can be thought of a libraries consumed by components or hosts, while command components can be see as `bin` components that have a `main` function (called `run` in WASI). + +Specifically, a reactor component is one that imports all the interfaces in the [`wasi:cli/reactor`](https://github.com/WebAssembly/wasi-cli/blob/main/wit/reactor.wit) world. A `command` component is a superset of a `reactor` component. It is a component that imports all of the `reactor` interfaces and exports the `wasi:cli/run` interface. It is defined by the [`wasi:cli/command` world](https://github.com/WebAssembly/wasi-cli/blob/main/wit/**command**.wit). \ No newline at end of file diff --git a/component-model/src/language-support/go.md b/component-model/src/language-support/go.md new file mode 100644 index 0000000..4508f36 --- /dev/null +++ b/component-model/src/language-support/go.md @@ -0,0 +1,89 @@ +# Go Tooling + +The [TinyGo toolchain](https://tinygo.org/docs/guides/webassembly/wasi/) has native support for WASI and with the help of the WASI preview 1 adapter we can create Go components. + +## Building a Component with TinyGo + +The TinyGo toolchain currently only supports `main` packages even if the exported `main` function is not used. Let's create one that implements the `add` function in the [`example` +world](https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/example-host/add.wit). First create your Go project: + +```sh +mkdir add && cd add +go mod init github.com/yourusername/yourproject +``` + +Now, let's generate our TinyGo bindings using `wit-bindgen`: + +```sh +wit-bindgen tiny-go ./add.wit -w example --out-dir=gen +``` + +Now, we can implement the generated `SetExample` type (in `gen/example.go`) that is exported by our component: + +> Note: to resolve the local path to import the bindings from `gen`, update `go.mod`: +> ```sh +> echo "replace github.com/yourusername/yourproject => /Path/to/add" >> go.mod +> ``` + +```go +package main + +//go:generate wit-bindgen tiny-go ./add.wit -w example --out-dir=gen + +import ( + . "github.com/yourusername/yourproject/gen" +) + +type AddImpl struct { +} + +func init() { + a := AddImpl{} + SetExample(a) +} + +func (i AddImpl) Add(x, y int32) int32 { + return x + y +} + +// main is required for the `WASI` target, even if it isn't used. +func main() {} +``` + +Now, we can build our Wasm module, targeting WASI: + +```sh + tinygo build -o add.wasm -target=wasi add.go +``` + +TinyGo (similar to C) targets preview 1 of WASI which does not support the component model (`.wit` files). Fortunately, [Wasmtime provides adapters](https://github.com/bytecodealliance/wit-bindgen#creating-components-wasi) for adapting preview 1 modules to components. There is an adaptor for both [reactor and command components](../creating-and-consuming/authoring.md#command-and-reactor-components). Our `add.wit` world defines a reactor component, so download the `wasi_snapshot_preview1.reactor.wasm` adaptor from [a Wasmtime release](https://github.com/bytecodealliance/wasmtime/releases). + +We will use `wasm-tools`to componetize our Wasm module, first embedding component metadata inside the core module and then encoding the module as a component using the WASI preview 1 adapter. + +```sh +wasm-tools component embed --world example ./add.wit add.wasm -o add.embed.wasm +wasm-tools component new -o add.component.wasm --adapt wasi_snapshot_preview1="$COMPONENT_ADAPTOR_REACTOR" add.embed.wasm +``` + +We now have an add component! + +```sh +$ wasm-tools component wit add.component.wit +package root:component + +world root { + import wasi:io/streams + import wasi:filesystem/types + import wasi:filesystem/preopens + import wasi:cli/stdin + import wasi:cli/stdout + import wasi:cli/stderr + import wasi:cli/terminal-input + import wasi:cli/terminal-output + import wasi:cli/terminal-stdin + import wasi:cli/terminal-stdout + import wasi:cli/terminal-stderr + + export add: func(x: s32, y: s32) -> s32 +} +``` From b7f0e46279fa8fa1430c927c3876aea0f28631d8 Mon Sep 17 00:00:00 2001 From: Kate Goldenring Date: Mon, 2 Oct 2023 16:52:33 -0700 Subject: [PATCH 2/4] Make Go tutorial more informative and explanatory Signed-off-by: Kate Goldenring --- .../examples/example-host/README.md | 2 +- component-model/src/language-support/go.md | 169 +++++++++++++++--- 2 files changed, 144 insertions(+), 27 deletions(-) diff --git a/component-model/examples/example-host/README.md b/component-model/examples/example-host/README.md index 43d9a4e..be423e5 100644 --- a/component-model/examples/example-host/README.md +++ b/component-model/examples/example-host/README.md @@ -13,7 +13,7 @@ world example { ``` The application uses [`wasmtime`](https://github.com/bytecodealliance/wasmtime) -crates to generate Rust bindings, bring in WASI worlds, and executes the `run` +crates to generate Rust bindings, bring in WASI worlds, and executes the `add` function of the component. It takes in two operands and a path to a component. It passes the operands to diff --git a/component-model/src/language-support/go.md b/component-model/src/language-support/go.md index 4508f36..430c9b2 100644 --- a/component-model/src/language-support/go.md +++ b/component-model/src/language-support/go.md @@ -1,71 +1,175 @@ # Go Tooling -The [TinyGo toolchain](https://tinygo.org/docs/guides/webassembly/wasi/) has native support for WASI and with the help of the WASI preview 1 adapter we can create Go components. - -## Building a Component with TinyGo - -The TinyGo toolchain currently only supports `main` packages even if the exported `main` function is not used. Let's create one that implements the `add` function in the [`example` -world](https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/example-host/add.wit). First create your Go project: +The [TinyGo toolchain](https://tinygo.org/docs/guides/webassembly/wasi/) has native support for WASI +and can build Wasm core modules. With the help of some component model tooling, we can then take +that core module and embed it in a component. To demonstrate how to use the tooling, this guide +walks through building a component that implements the `example` world defined in the [`add.wit` +package](../../examples/example-host/add.wit). The component will implement a simple add function. + +## Overview of Building a Component with TinyGo + +There are several steps to building a component in TinyGo: + +1. Determine which world the component will implement +2. Generate bindings for that world using + [`wit-bindgen`](https://github.com/bytecodealliance/wit-bindgen#creating-a-component) +3. Implement the interface defined in the bindings +4. Build a Wasm core module using the native TinyGo toolchain +5. Convert the Wasm core module to a component using + []`wasm-tools`](https://github.com/bytecodealliance/wasm-tools) + +The next section will walk through steps 1-4, producing a core Wasm module that targets WASI preview 1. +Then, the following section will walk through converting this core module to a component that +supports WASI preview 2. + +## Creating a TinyGo Core Wasm Module + +The TinyGo toolchain natively supports compiling Go programs to core Wasm modules. It does have one +key limitation. It currently only supports `main` packages - commands that run start-to-finish and +then exit. Our example program, however, is more like a library which exports an add function that +can be called multiple times; and nothing will ever call its `main` function. To produce a library +("reactor") module from TinyGo requires a little bit of trickery with the Go `init` function, which +we will see shortly. Let's create one that implements the `add` function in the [`example` +world](https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/example-host/add.wit). +First create your Go project: ```sh mkdir add && cd add -go mod init github.com/yourusername/yourproject +go mod init github.com/yourusername/add ``` -Now, let's generate our TinyGo bindings using `wit-bindgen`: +Since component creation is not supported natively in TinyGo, we need to generate TinyGo source code +to create bindings to the APIs described by our add WIT package. We can use +[`wit-bindgen`](https://github.com/bytecodealliance/wit-bindgen#creating-a-component) to generate +these bindings for WIT packages. First, install the latest [release of +`wit-bindgen`](https://github.com/bytecodealliance/wit-bindgen/releases). Now, run `wit-bindgen`, +specifying TinyGo as the target language, the path to the +[`add.wit`](../../examples/example-host/add.wit) package, the name of the world in that package to +generate bindings for (`example`), and a directory to output the generated code (`gen`): ```sh wit-bindgen tiny-go ./add.wit -w example --out-dir=gen ``` -Now, we can implement the generated `SetExample` type (in `gen/example.go`) that is exported by our component: +The `gen` directory now contains several files: + +```sh +$ tree gen +gen +├── example.c +├── example.go +├── example.h +└── example_component_type.o +``` -> Note: to resolve the local path to import the bindings from `gen`, update `go.mod`: +The `example.go` file defines an `Example` interface that matches the structure of our `example` +world. In our Go module, first implement the `Example` interface by defining the `Add` function. + +> Note: to resolve the local path to import the bindings from `gen`, update `go.mod` to point to the +> local path to your add Go module: > ```sh -> echo "replace github.com/yourusername/yourproject => /Path/to/add" >> go.mod +> echo "replace github.com/yourusername/add => /Path/to/add" >> go.mod > ``` ```go package main -//go:generate wit-bindgen tiny-go ./add.wit -w example --out-dir=gen - import ( - . "github.com/yourusername/yourproject/gen" + . "github.com/yourusername/add/gen" ) -type AddImpl struct { +type ExampleImpl struct { } -func init() { - a := AddImpl{} - SetExample(a) +// Implement the Example interface to ensure the component satisfies the +// `example` world +func (i ExampleImpl) Add(x, y int32) int32 { + return x + y +} + +// main is required for the `WASI` target, even if it isn't used. +func main() {} +``` + +Now that we have implemented the example world, we can load it by passing it to the `SetExample` +function. Since our component is a reactor component, `main` will not be called. However, only Go +programs with `main` can target WASI currently. As a loophole, we will initialize our `ExampleImpl` +type inside an `init` function. 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 and +make it callable using the generated C bindings (`example.c`). After populating the `init` function, +our complete implementation looks similar to the following: + +> Note: to resolve the local path to import the bindings from `gen`, update `go.mod` to point to the +> local path to your add Go module: +> ```sh +> echo "replace github.com/yourusername/add => /Path/to/add" >> go.mod +> ``` + +```go +package main + +import ( + . "github.com/yourusername/add/gen" +) + +type ExampleImpl struct { } -func (i AddImpl) Add(x, y int32) int32 { +// Implement the Example interface to ensure the component satisfies the +// `example` world +func (i ExampleImpl) Add(x, y int32) int32 { return x + y } +// To enable our component to be a `reactor`, implement the component in the +// `init` function which is always called first when a Go package is run. +func init() { + example := ExampleImpl{} + SetExample(example) +} + // main is required for the `WASI` target, even if it isn't used. func main() {} ``` -Now, we can build our Wasm module, targeting WASI: +Now, we can build our core Wasm module, targeting WASI preview 1 using the TinyGo compiler. ```sh - tinygo build -o add.wasm -target=wasi add.go +tinygo build -o add.wasm -target=wasi add.go ``` -TinyGo (similar to C) targets preview 1 of WASI which does not support the component model (`.wit` files). Fortunately, [Wasmtime provides adapters](https://github.com/bytecodealliance/wit-bindgen#creating-components-wasi) for adapting preview 1 modules to components. There is an adaptor for both [reactor and command components](../creating-and-consuming/authoring.md#command-and-reactor-components). Our `add.wit` world defines a reactor component, so download the `wasi_snapshot_preview1.reactor.wasm` adaptor from [a Wasmtime release](https://github.com/bytecodealliance/wasmtime/releases). - -We will use `wasm-tools`to componetize our Wasm module, first embedding component metadata inside the core module and then encoding the module as a component using the WASI preview 1 adapter. +## Converting a Wasm Core Module to a Component + +In the previous step, we produced a core module that implements our `example` world. We now want to +convert to a component to gain the benefits of the component model, such as the ability to compose +with it with other components as done in the [`calculator` component in the +tutorial](../tutorial.md#the-calculator-interface). To do this conversion, we will use +[`wasm-tools`](https://github.com/bytecodealliance/wasm-tools), a low level tool for manipulating +Wasm modules. In the future, hopefully most of the functionality of this tool will be embedded +directly into language toolchains (such as is the case with with the Rust toolchain). Download the +latest release from the [project's +repository](https://github.com/bytecodealliance/wasm-tools/releases/tag/wasm-tools-1.0.44). + +We also need to download the WASI preview 1 adapter. TinyGo (similar to C) targets preview 1 of WASI +which does not support the component model (`.wit` files). Fortunately, [Wasmtime provides +adapters](https://github.com/bytecodealliance/wit-bindgen#creating-components-wasi) for adapting +preview 1 modules to preview 2 components. There are adapters for both [reactor and command +components](../creating-and-consuming/authoring.md#command-and-reactor-components). Our `add.wit` +world defines a reactor component, so download the `wasi_snapshot_preview1.reactor.wasm` adapter +from [the latest Wasmtime release](https://github.com/bytecodealliance/wasmtime/releases). + +Now that we have all the prerequisites downloaded, we can use the `wasm-tools component` subcommand +to componentize our Wasm module, first embedding component metadata inside the core module and then +encoding the module as a component using the WASI preview 1 adapter. ```sh +export COMPONENT_ADAPTER_REACTOR=/path/to/wasi_snapshot_preview1.reactor.wasm wasm-tools component embed --world example ./add.wit add.wasm -o add.embed.wasm -wasm-tools component new -o add.component.wasm --adapt wasi_snapshot_preview1="$COMPONENT_ADAPTOR_REACTOR" add.embed.wasm +wasm-tools component new -o add.component.wasm --adapt wasi_snapshot_preview1="$COMPONENT_ADAPTER_REACTOR" add.embed.wasm ``` -We now have an add component! +We now have an add component that satisfies our `example` world, exporting the `add` function, which +we can confirm using another `wasm-tools` command: ```sh $ wasm-tools component wit add.component.wit @@ -87,3 +191,16 @@ world root { export add: func(x: s32, y: s32) -> s32 } ``` + +## Testing an `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`](../../examples/example-host/README.md) 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: + +```sh +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.component.wasm +``` From 6612cbc5420b0aebf3fc54613b51dc3909d95b82 Mon Sep 17 00:00:00 2001 From: Kate Goldenring Date: Fri, 5 Apr 2024 12:52:48 -0700 Subject: [PATCH 3/4] Revamp go example with quickstart and expanded section to target an interface Signed-off-by: Kate Goldenring --- .../src/creating-and-consuming/authoring.md | 8 +- component-model/src/language-support/go.md | 278 +++++++++++------- 2 files changed, 173 insertions(+), 113 deletions(-) diff --git a/component-model/src/creating-and-consuming/authoring.md b/component-model/src/creating-and-consuming/authoring.md index d7e7d05..4e20718 100644 --- a/component-model/src/creating-and-consuming/authoring.md +++ b/component-model/src/creating-and-consuming/authoring.md @@ -2,10 +2,4 @@ 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. -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.) - -## Command and Reactor Components - -There are two categories of components commonly referred to by component model tooling: reactor and command components. Generally, reactor components can be thought of a libraries consumed by components or hosts, while command components can be see as `bin` components that have a `main` function (called `run` in WASI). - -Specifically, a reactor component is one that imports all the interfaces in the [`wasi:cli/reactor`](https://github.com/WebAssembly/wasi-cli/blob/main/wit/reactor.wit) world. A `command` component is a superset of a `reactor` component. It is a component that imports all of the `reactor` interfaces and exports the `wasi:cli/run` interface. It is defined by the [`wasi:cli/command` world](https://github.com/WebAssembly/wasi-cli/blob/main/wit/**command**.wit). \ No newline at end of file +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.) \ No newline at end of file diff --git a/component-model/src/language-support/go.md b/component-model/src/language-support/go.md index 430c9b2..79f6bed 100644 --- a/component-model/src/language-support/go.md +++ b/component-model/src/language-support/go.md @@ -11,142 +11,57 @@ package](../../examples/example-host/add.wit). The component will implement a si There are several steps to building a component in TinyGo: 1. Determine which world the component will implement -2. Generate bindings for that world using - [`wit-bindgen`](https://github.com/bytecodealliance/wit-bindgen#creating-a-component) -3. Implement the interface defined in the bindings -4. Build a Wasm core module using the native TinyGo toolchain -5. Convert the Wasm core module to a component using +2. Build a Wasm core module using the native TinyGo toolchain +3. Convert the Wasm core module to a component using []`wasm-tools`](https://github.com/bytecodealliance/wasm-tools) -The next section will walk through steps 1-4, producing a core Wasm module that targets WASI preview 1. +The next section will walk through steps 1-2, producing a core Wasm module that targets WASI preview 1. Then, the following section will walk through converting this core module to a component that supports WASI preview 2. ## Creating a TinyGo Core Wasm Module -The TinyGo toolchain natively supports compiling Go programs to core Wasm modules. It does have one -key limitation. It currently only supports `main` packages - commands that run start-to-finish and -then exit. Our example program, however, is more like a library which exports an add function that -can be called multiple times; and nothing will ever call its `main` function. To produce a library -("reactor") module from TinyGo requires a little bit of trickery with the Go `init` function, which -we will see shortly. Let's create one that implements the `add` function in the [`example` +The TinyGo toolchain natively supports compiling Go programs to core Wasm modules. Let's create one that implements the `add` function in the [`example` world](https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/example-host/add.wit). -First create your Go project: - -```sh -mkdir add && cd add -go mod init github.com/yourusername/add -``` - -Since component creation is not supported natively in TinyGo, we need to generate TinyGo source code -to create bindings to the APIs described by our add WIT package. We can use -[`wit-bindgen`](https://github.com/bytecodealliance/wit-bindgen#creating-a-component) to generate -these bindings for WIT packages. First, install the latest [release of -`wit-bindgen`](https://github.com/bytecodealliance/wit-bindgen/releases). Now, run `wit-bindgen`, -specifying TinyGo as the target language, the path to the -[`add.wit`](../../examples/example-host/add.wit) package, the name of the world in that package to -generate bindings for (`example`), and a directory to output the generated code (`gen`): - -```sh -wit-bindgen tiny-go ./add.wit -w example --out-dir=gen -``` - -The `gen` directory now contains several files: -```sh -$ tree gen -gen -├── example.c -├── example.go -├── example.h -└── example_component_type.o -``` - -The `example.go` file defines an `Example` interface that matches the structure of our `example` -world. In our Go module, first implement the `Example` interface by defining the `Add` function. - -> Note: to resolve the local path to import the bindings from `gen`, update `go.mod` to point to the -> local path to your add Go module: -> ```sh -> echo "replace github.com/yourusername/add => /Path/to/add" >> go.mod -> ``` +First, implement a simple add function in `add.go`: ```go package main -import ( - . "github.com/yourusername/add/gen" -) - -type ExampleImpl struct { +//go:wasm-module yourmodulename +//export add +func add(x, y int32) int32 { + return x + y } -// Implement the Example interface to ensure the component satisfies the -// `example` world -func (i ExampleImpl) Add(x, y int32) int32 { - return x + y -} - -// main is required for the `WASI` target, even if it isn't used. +// main is required for the `wasi` target, even if it isn't used. func main() {} ``` -Now that we have implemented the example world, we can load it by passing it to the `SetExample` -function. Since our component is a reactor component, `main` will not be called. However, only Go -programs with `main` can target WASI currently. As a loophole, we will initialize our `ExampleImpl` -type inside an `init` function. 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 and -make it callable using the generated C bindings (`example.c`). After populating the `init` function, -our complete implementation looks similar to the following: - -> Note: to resolve the local path to import the bindings from `gen`, update `go.mod` to point to the -> local path to your add Go module: -> ```sh -> echo "replace github.com/yourusername/add => /Path/to/add" >> go.mod -> ``` - -```go -package main - -import ( - . "github.com/yourusername/add/gen" -) - -type ExampleImpl struct { -} - -// Implement the Example interface to ensure the component satisfies the -// `example` world -func (i ExampleImpl) Add(x, y int32) int32 { - return x + y -} - -// To enable our component to be a `reactor`, implement the component in the -// `init` function which is always called first when a Go package is run. -func init() { - example := ExampleImpl{} - SetExample(example) -} - -// main is required for the `WASI` target, even if it isn't used. -func main() {} -``` +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 +then exit. Our example program, however, is more like a library which exports an add function that +can be called multiple times; and nothing will ever call its `main` function. -Now, we can build our core Wasm module, targeting WASI preview 1 using the TinyGo compiler. +Now, we can use TinyGo to build our core Wasm module: ```sh tinygo build -o add.wasm -target=wasi add.go ``` +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. + ## Converting a Wasm Core Module to a Component In the previous step, we produced a core module that implements our `example` world. We now want to convert to a component to gain the benefits of the component model, such as the ability to compose with it with other components as done in the [`calculator` component in the -tutorial](../tutorial.md#the-calculator-interface). To do this conversion, we will use +tutorial](../tutorial.md#the-calculator-interface). +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. + +We will use [`wasm-tools`](https://github.com/bytecodealliance/wasm-tools), a low level tool for manipulating -Wasm modules. In the future, hopefully most of the functionality of this tool will be embedded -directly into language toolchains (such as is the case with with the Rust toolchain). Download the +Wasm modules. Download the latest release from the [project's repository](https://github.com/bytecodealliance/wasm-tools/releases/tag/wasm-tools-1.0.44). @@ -204,3 +119,154 @@ 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.component.wasm ``` + +## Targetting Worlds with Interfaces with TinyGo and Wit-Bindgen + +The [`example` +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: + +1. Determine which world the component will implement +2. Generate bindings for that world using + [`wit-bindgen`](https://github.com/bytecodealliance/wit-bindgen#creating-a-component) +3. Implement the interface defined in the bindings +4. Build a Wasm core module using the native TinyGo toolchain +5. Convert the Wasm core module to a component using + []`wasm-tools`](https://github.com/bytecodealliance/wasm-tools) + +For this example, we will use the following world, which moves the add function behind an `add` interface: + +```wit +package docs:adder@0.1.0; + +interface add { + add: func(a: u32, b: u32) -> u32; +} + +world adder { + export add; +} +``` + +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. + +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: + +```sh +export VERSION=0.24.0 ARCH=aarch64 OS=macos +wget https://github.com/bytecodealliance/wit-bindgen/releases/download/v$VERSION/wit-bindgen-$VERSION-$ARCH-$OS.tar.gz +tar -xzf wit-bindgen-$VERSION-$ARCH-$OS.tar.gz +mv wit-bindgen-$VERSION-$ARCH-$OS/wit-bindgen ./ +rm -rf wit-bindgen-$VERSION-$ARCH-$OS.tar.gz wit-bindgen-$VERSION-$ARCH-$OS +``` + +Now, create your Go project: + +```sh +mkdir add && cd add +go mod init example.com +``` + +Next, run `wit-bindgen`, specifying TinyGo as the target language, the path to the +[`add.wit`](../../examples/example-host/add.wit) package, the name of the world in that package to +generate bindings for (`example`), and a directory to output the generated code (`gen`): + +```sh +wit-bindgen tiny-go ./add.wit --world example --out-dir=gen +``` + +The `gen` directory now contains several files: + +```sh +$ tree gen +gen +├── adder.c +├── adder.go +└── adder.h +``` + +The `adder.go` file defines an `ExportsDocsAdder0_1_0_Add` interface that matches the structure of our `add` +interface. The name of the interface is taken from the WIT package name (`docs:adder@0.1.0`) combined with the interface name (`add`). In our Go module, first implement the `ExportsDocsAdder0_1_0_Add` interface by defining the `Add` function. + +```go +package main + +import ( + . "example.com/gen" +) + +type AdderImpl struct { +} + +// Implement the `ExportsDocsAdder0_1_0_Add` interface to ensure the component satisfies the +// `adder` world +func (i AdderImpl) Add(x, y uint32) uint32 { + return x + y +} + +// main is required for the `wasi` target, even if it isn't used. +func main() {} +``` + +After implementing the adder world, we need to load it by passing it to the `SetExportsDocsAdder0_1_0_Add` +function from our bindings (`adder.go`). Since our component is a library, `main` will not be called. However, only Go +programs with `main` can target WASI currently. As a loophole, we will initialize our `AdderImpl` +type inside an `init` function. 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 and +make it callable using the generated C bindings (`adder.c`). After populating the `init` function, +our complete implementation looks similar to the following: + +```go +package main + +import ( + . "example.com/gen" +) + +type AdderImpl struct { +} + +// Implement the ExportsDocsAdder0_1_0_Add interface to ensure the component satisfies the +// `adder` world +func (i AdderImpl) Add(x, y uint32) uint32 { + return x + y +} + +// To enable our component to be a library, implement the component in the +// `init` function which is always called first when a Go package is run. +func init() { + example := AdderImpl{} + SetExportsDocsAdder0_1_0_Add(example) +} + +// main is required for the `WASI` target, even if it isn't used. +func main() {} +``` + +Once again, we can build our core module using TinyGo, componentize it, and adapt it for WASI 0.2: +```sh +export COMPONENT_ADAPTER_REACTOR=/path/to/wasi_snapshot_preview1.reactor.wasm +tinygo build -o add.wasm -target=wasi add.go +wasm-tools component embed --world example ./add.wit add.wasm -o add.embed.wasm +wasm-tools component new -o add.component.wasm --adapt wasi_snapshot_preview1="$COMPONENT_ADAPTER_REACTOR" add.embed.wasm +``` + +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: + +```sh +wasm-tools component wit add.component.wasm +package root:component; + +world root { + import wasi:io/error@0.2.0; + import wasi:io/streams@0.2.0; + import wasi:cli/stdin@0.2.0; + import wasi:cli/stdout@0.2.0; + import wasi:cli/stderr@0.2.0; + import wasi:clocks/wall-clock@0.2.0; + import wasi:filesystem/types@0.2.0; + import wasi:filesystem/preopens@0.2.0; + + export docs:adder/add@0.1.0; +} +``` From 2be771c1c92c0d827d97a5e59223e19d9453588b Mon Sep 17 00:00:00 2001 From: Kate Goldenring Date: Tue, 9 Apr 2024 13:17:58 -0700 Subject: [PATCH 4/4] Number steps in the go language guide Signed-off-by: Kate Goldenring --- component-model/src/language-support/go.md | 34 ++++++++++++++++------ 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/component-model/src/language-support/go.md b/component-model/src/language-support/go.md index 79f6bed..26dd3eb 100644 --- a/component-model/src/language-support/go.md +++ b/component-model/src/language-support/go.md @@ -13,13 +13,29 @@ There are several steps to building a component in TinyGo: 1. Determine which world the component will implement 2. Build a Wasm core module using the native TinyGo toolchain 3. Convert the Wasm core module to a component using - []`wasm-tools`](https://github.com/bytecodealliance/wasm-tools) + [`wasm-tools`](https://github.com/bytecodealliance/wasm-tools) -The next section will walk through steps 1-2, producing a core Wasm module that targets WASI preview 1. -Then, the following section will walk through converting this core module to a component that -supports WASI preview 2. +The following sections will walk through these steps, producing a core Wasm module that targets WASI +preview 1 and converting this core module to a component that supports WASI preview 2. -## Creating a TinyGo Core Wasm Module +### 1: The `example` World + +The next two sections walk through creating a component that implements the the following [`example` +world](https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/example-host/add.wit): + +```wit +package example:component; + +world example { + export add: func(x: s32, y: s32) -> s32; +} +``` + +This is a simple world that exports one `add` function. If you want to go beyond a quick start to a +more realistic example, jump to the [section on implementing worlds with +interfaces](#implementing-worlds-with-interfaces-with-tinygo-and-wit-bindgen). + +### 2: Creating a TinyGo Core Wasm Module The TinyGo toolchain natively supports compiling Go programs to core Wasm modules. Let's create one that implements the `add` function in the [`example` world](https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/example-host/add.wit). @@ -51,7 +67,7 @@ tinygo build -o add.wasm -target=wasi add.go 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. -## Converting a Wasm Core Module to a Component +### 3: Converting a Wasm Core Module to a Component In the previous step, we produced a core module that implements our `example` world. We now want to convert to a component to gain the benefits of the component model, such as the ability to compose @@ -107,7 +123,7 @@ world root { } ``` -## Testing an `add` Component +### Testing an `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`](../../examples/example-host/README.md) to do @@ -120,7 +136,7 @@ cd component-docs/component-model/examples/example-host cargo run --release -- 1 2 /path/to/add.component.wasm ``` -## Targetting Worlds with Interfaces with TinyGo and Wit-Bindgen +## Implementing Worlds with Interfaces with TinyGo and Wit-Bindgen The [`example` 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: @@ -131,7 +147,7 @@ world](https://github.com/bytecodealliance/component-docs/tree/main/component-mo 3. Implement the interface defined in the bindings 4. Build a Wasm core module using the native TinyGo toolchain 5. Convert the Wasm core module to a component using - []`wasm-tools`](https://github.com/bytecodealliance/wasm-tools) + [`wasm-tools`](https://github.com/bytecodealliance/wasm-tools) For this example, we will use the following world, which moves the add function behind an `add` interface: