Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions component-model/examples/tutorial/go/adder/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//go:generate go tool wit-bindgen-go generate --world adder --out internal ./docs:adder@0.1.0.wasm

package main

import (
"example.com/internal/docs/adder/add"
)

func init() {
add.Exports.Add = func(x uint32, y uint32) uint32 {
return x + y
}
}

// main is required for the `wasi` target, even if it isn't used.
func main() {}
11 changes: 11 additions & 0 deletions component-model/examples/tutorial/go/adder/world2.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package docs:adder@0.1.0;

interface add {
add: func(x: u32, y: u32) -> u32;
}

world adder {
include wasi:cli/imports@0.2.0;

export add;
}
139 changes: 68 additions & 71 deletions component-model/src/language-support/go.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

The [TinyGo compiler](https://tinygo.org/) 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 `adder` world defined in the [`adder/world.wit` package][adder-wit].
The component will implement the `adder` world, which contains `add` interface with a `add` function.
This guide walks through building a component that implements
the `adder` world defined in the [`adder/world.wit` package][adder-wit].
The component will implement the `adder` world,
which contains an `add` interface with an `add` function.

[adder-wit]: https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/tutorial/wit/adder/world.wit

Expand Down Expand Up @@ -32,9 +34,13 @@ $ wasm-tools -V
wasm-tools 1.255.0 ...
```

Optional: Install the [`wkg`][wkg] CLI tool to resolve the imports in the WIT file. The `wkg` CLI is a part of the [Wasm Component package manager](https://github.com/bytecodealliance/wasm-pkg-tools/releases)
Optional: Install the [`wkg`][wkg] CLI tool to resolve the imports in the WIT file.
The `wkg` CLI is a part of the [Wasm Component package manager][wasm-pkg-tools-releases].
See [the wasm-pkg-tools installation instructions][wasm-pkg-tools] to install manually or using `cargo`.

[wkg]: https://github.com/bytecodealliance/wasm-pkg-tools/tree/main/crates/wkg
[wasm-pkg-tools]: https://github.com/bytecodealliance/wasm-pkg-tools?tab=readme-ov-file#installation
[wasm-pkg-tools-releases]: https://github.com/bytecodealliance/wasm-pkg-tools/releases

## 2. Create your Go project

Expand All @@ -45,12 +51,10 @@ mkdir add && cd add
go mod init example.com
```

Ensure that the following `tool`s are installed:
Install the following `tool`:

```
tool (
go.bytecodealliance.org/cmd/wit-bindgen-go
)
```console
go get -tool go.bytecodealliance.org/cmd/wit-bindgen-go
```

> [!NOTE]
Expand All @@ -60,41 +64,29 @@ Consider also running `go mod tidy` after adding the above tool.

[go-1-24-release]: https://go.dev/blog/go1.24

## 2. Determine which World the Component will Implement
## 2. Determine which world the component will implement

Since we will be implementing the [`adder` world][adder-wit], we can copy the WIT to our project,
under the `wit` folder (e.g. `wit/component.wit`):
Since we will be implementing the [`adder` world][adder-wit], we can copy the WIT to our project.
Create a subdirectory called `wit` and paste the following code
into a file called `wit/component.wit`:

```wit
package docs:adder@0.1.0;

interface add {
add: func(x: u32, y: u32) -> u32;
}

world adder {
export add;
}
{{#include ../../examples/tutorial/go/adder/world2.wit}}
```

The `wasip2` target of TinyGo assumes that the component is targeting `wasi:cli/command@0.2.0` world
(part of [`wasi:cli`][wasi-cli]) so it requires the imports of `wasi:cli/imports@0.2.0`.

We need to include those interfaces as well in `component.wit`, by editing the `adder` world:

```wit
world adder {
include wasi:cli/imports@0.2.0;
export add;
}
```
The line `include wasi:cli/imports@0.2.0` is necessary because
we are using the `wasip2` target of TinyGo.
TinyGo assumes that the component targets the `wasi:cli/command@0.2.0` world
(part of [`wasi:cli`][wasi-cli]),
so it requires the imports of `wasi:cli/imports@0.2.0`.

### Using `wkg` to automatically resolve and download imports

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

Running the `wkg wit fetch` command will resolve the imports and populate your `wit` folder with all relevant
imported namespaces and packages.
Running the `wkg wit build` command encodes the WIT into the Component Model binary format.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The title of this section says it's about "resolve and download imports" so as a reader I have questions about why I am doing this encoding malarkey. After all, "resolve and download" is what fetch does, so why am I not doing that?

Looking ahead to section 3, I am guessing it is something like: in order to use go tool wit-bindgen-go, we must have a package (that is addressable using package notation) rather than a WIT text tree: therefore we must do a wit build rather than a wit fetch.

Is that correct? That go tool wit-bindgen-go can't operate on the text tree, only on a binary package reference? I'm a bit surprised by that but the one thing I do know about Go is its infinite capacity to surprise me! grin

If that is the reason, I'd suggest explicitly explaining this context, because reading this now the build step feels a bit arbitrary, likely as a result of partial updates as the Go tooling has changed over time...

As a side effect, it resolves the imports
and populates your `wit` folder with all relevant imported namespaces and packages.

```
$ wkg wit build
Expand All @@ -105,8 +97,8 @@ WIT package written to docs:adder@0.1.0.wasm

## 3. Generate bindings for the Wasm component

Now that we have our WIT definitions bundled together into a WASM file,
we can generate the bindings for our Wasm component, by adding a build directive:
Now that we have our WIT definitions bundled together into a `.wasm` file,
we can generate the bindings for our WebAssembly component, by adding a build directive:

```console
go tool wit-bindgen-go generate --world adder --out internal ./docs:adder@0.1.0.wasm
Expand All @@ -116,7 +108,7 @@ go tool wit-bindgen-go generate --world adder --out internal ./docs:adder@0.1.0.
> The `go tool` directive (added in [Golang 1.24][go-1-24-release]) installs and enables use of `wit-bindgen-go`,
> part of the Bytecode Alliance suite of Golang tooling.

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

```console
$ tree internal
Expand Down Expand Up @@ -254,50 +246,55 @@ internal
39 directories, 91 files
```

The `adder.exports.go` file contains the exported functions that need to be implemented in the Go code called `Exports`.
The `add.exports.go` file contains an `Exports` struct, containing declarations for
the exported functions that need to be implemented in the Go code.

## 4. Implement the `add` Function

```Go
//go:generate go tool wit-bindgen-go generate --world adder --out internal ./docs:adder@0.1.0.wasm
In your `add` directory, create a file called `main.go`
and paste the following code into it:

package main

import (
"example.com/internal/docs/adder/add"
)

func init() {
add.Exports.Add = func(x uint32, y uint32) uint32 {
return x + y
}
}

// main is required for the `wasi` target, even if it isn't used.
func main() {}
```Go
{{#include ../../examples/tutorial/go/adder/main.go}}
```

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.
Go's `init` function is 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.

## 5. 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.
We can build our component using TinyGo.
Under the hood, TinyGo invokes `wasm-tools`
to embed the WIT file to the module and componentize it.

```console
tinygo build -target=wasip2 -o add.wasm --wit-package docs:adder@0.1.0.wasm --wit-world adder main.go
tinygo build -target=wasip2 \
-o adder.wasm \
--wit-package docs:adder@0.1.0.wasm \
--wit-world adder main.go
```

> **WARNING:** By default, tinygo includes all debug-related information in your .wasm file. That is desirable when prototyping or testing locally to obtain useful backtraces in case of errors (for example, with `wasmtime::WasmBacktraceDetails::Enable`). To remove debug data and optimize your binary file, build with `-no-debug`. The resulting .wasm file will be considerably smaller (up to 75% reduction in size).
* The `-target=wasip2` flag specifies that the code should be compiled
to WebAssembly using Preview 2 methods.
* The `-o adder.wasm` flag directs the output to be saved in `add.wasm` in the current directory.
* The `--wit-package` flag specifies the package name for the WIT code we are using.
* The `--wit-world` flag specifies that the WIT world that defines the imports and exports
for our component is `adder`.

We now have an add component that satisfies our `adder` world, exporting the `add` function, which
We now have an `adder` component that satisfies our `adder` world, exporting the `add` function.

> [!WARNING]
> By default, tinygo includes all debug-related information in your .wasm file.
> That is desirable when prototyping or testing locally to obtain useful backtraces in case of errors
> (for example, with `wasmtime::WasmBacktraceDetails::Enable`).
> To remove debug data and optimize your binary file, build with `-no-debug`.
> The resulting .wasm file will be considerably smaller (up to 75% reduction in size).

We can confirm using the `wasm-tools component wit` command:

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

world root {
Expand All @@ -313,20 +310,20 @@ world root {

## 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`][example-host] that does just that.
The following section requires you to have [a Rust toolchain][rust] installed.

The example host calls the `add` function of a passed in component providing two operands.
To run our add component, we need to use a host program with a WASI runtime that understands
the `example` world.

To use the example host, clone this repository and run the Rust program:
{{#include example-host-part1.md}}

```console
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
```
A successful run should show the following output
(of course, the paths to your example host and adder component will vary):

{{#include example-host-part2.md}}

[example-host]: https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/example-host
[rust]: https://www.rust-lang.org/learn/get-started

[!NOTE]: #
[!WARNING]: #