diff --git a/Makefile b/Makefile index acc3f404..b0eb9aca 100644 --- a/Makefile +++ b/Makefile @@ -54,7 +54,7 @@ build.examples.as: build.examples.zig: @cd examples/allocation/zig/testdata/ && zig build -Drelease-small=true && mv zig-out/lib/greet.wasm . -tinygo_sources := examples/allocation/tinygo/testdata/greet.go imports/wasi_snapshot_preview1/example/testdata/tinygo/cat.go +tinygo_sources := examples/basic/testdata/add.go examples/allocation/tinygo/testdata/greet.go imports/wasi_snapshot_preview1/example/testdata/tinygo/cat.go .PHONY: build.examples.tinygo build.examples.tinygo: $(tinygo_sources) @for f in $^; do \ diff --git a/README.md b/README.md index 34dcc8f2..175f879d 100644 --- a/README.md +++ b/README.md @@ -15,69 +15,9 @@ Import wazero and extend your Go application with code written in any language! ## Example -The best way to learn wazero is by trying one of our [examples](examples). - -For the impatient, here's a peek of a general flow with wazero: - -First, you need to compile your code into the WebAssembly Binary Format (Wasm). - -Here's source in [TinyGo](https://wazero.io/languages/tinygo), which exports an -"add" function: -```go -package main - -//export add -func add(x, y uint32) uint32 { - return x + y -} -``` - -Here's the minimal command to build a `%.wasm` binary. -```bash -tinygo build -o add.wasm -target=wasi add.go -``` - -Finally, you can run that inside your Go application. -```go -func main() { - // Choose the context to use for function calls. - ctx := context.Background() - - // Read a WebAssembly binary containing an exported "add" function. - wasm, err := os.ReadFile("./path/to/add.wasm") - if err != nil { - log.Panicln(err) - } - - // Create a new WebAssembly Runtime. - r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfig(). - // WebAssembly 2.0 allows use of any version of TinyGo, including 0.24+. - WithWasmCore2()) - defer r.Close(ctx) // This closes everything this Runtime created. - - // Instantiate WASI, which implements system I/O such as console output. - if _, err = wasi_snapshot_preview1.Instantiate(ctx, r); err != nil { - log.Panicln(err) - } - - // Instantiate the module and return its exported functions - module, err := r.InstantiateModuleFromBinary(ctx, wasm) - if err != nil { - log.Panicln(err) - } - - // Discover 1+2=3 - fmt.Println(module.ExportedFunction("add").Call(ctx, 1, 2)) -} -``` - -Notes: - -* The embedding application is often called the "host" in WebAssembly. -* The Wasm binary is often called the "guest" in WebAssembly. Sometimes they - need [imports][imports] to implement features such as console output. -* Many languages compile to (target) Wasm including AssemblyScript, C, C++, - Rust, TinyGo and Zig! +The best way to learn wazero is by trying one of our [examples](examples). The +most [basic example](examples/basic) extends a Go application with an addition +function defined in WebAssembly. ## Deeper dive diff --git a/example_test.go b/example_test.go index 892d9b2d..0af023e8 100644 --- a/example_test.go +++ b/example_test.go @@ -1,48 +1,62 @@ -package wazero +package wazero_test import ( "context" _ "embed" "fmt" "log" + + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" ) // addWasm was generated by the following: // -// cd examples/basic/testdata/testdata; wat2wasm --debug-names add.wat +// cd examples/basic/testdata; tinygo build -o add.wasm -target=wasi add.go // //go:embed examples/basic/testdata/add.wasm var addWasm []byte -// This is an example of how to use WebAssembly via adding two numbers. +// This is an example of how to extend a Go application with an addition +// function defined in WebAssembly. // -// See https://github.com/tetratelabs/wazero/tree/main/examples for more. +// Since addWasm was compiled with TinyGo's `wasi` target, we need to configure +// WASI host imports. +// +// A complete project that does the same as this is available here: +// https://github.com/tetratelabs/wazero/tree/main/examples/basic func Example() { // Choose the context to use for function calls. ctx := context.Background() // Create a new WebAssembly Runtime. - r := NewRuntime(ctx) + r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfig(). + // WebAssembly 2.0 allows use of any version of TinyGo, including 0.24+. + WithWasmCore2()) + defer r.Close(ctx) // This closes everything this Runtime created. - // Add a module to the runtime named "wasm/math" which exports one function - // "add", implemented in WebAssembly. + // Instantiate WASI, which implements host functions needed for TinyGo to + // implement `panic`. + if _, err := wasi_snapshot_preview1.Instantiate(ctx, r); err != nil { + log.Panicln(err) + } + + // Instantiate the guest Wasm into the same runtime. It exports the `add` + // function, implemented in WebAssembly. mod, err := r.InstantiateModuleFromBinary(ctx, addWasm) if err != nil { log.Panicln(err) } - defer mod.Close(ctx) - - // Get a function that can be reused until its module is closed: - add := mod.ExportedFunction("add") + // Call the `add` function and print the results to the console. x, y := uint64(1), uint64(2) - results, err := add.Call(ctx, x, y) + results, err := mod.ExportedFunction("add").Call(ctx, x, y) if err != nil { log.Panicln(err) } - fmt.Printf("%s: %d + %d = %d\n", mod.Name(), x, y, results[0]) + fmt.Printf("%d + %d = %d\n", x, y, results[0]) // Output: - // wasm/math: 1 + 2 = 3 + // 1 + 2 = 3 } diff --git a/examples/basic/README.md b/examples/basic/README.md index 174b26a2..a77c306d 100644 --- a/examples/basic/README.md +++ b/examples/basic/README.md @@ -1,3 +1,36 @@ ## Basic example -This example shows how to use both WebAssembly and Go-defined functions with wazero. +This example shows how to extend a Go application with an addition function +defined in WebAssembly. + +Ex. +```bash +$ go run add.go 7 9 +7 + 9 = 16 +``` + +### Compilation + +wazero is a WebAssembly runtime, embedded in your host application. To run +WebAssembly functions, you need access to a WebAssembly Binary (Wasm), +typically a `%.wasm` file. + +[add.wasm](testdata/add.wasm) was compiled from [add.go](testdata/add.go) with +[TinyGo][1], as it is the most common way to compile Go source to Wasm. Here's +the minimal command to build a `%.wasm` binary. + +```bash +cd testdata; tinygo build -o add.wasm -target=wasi add.go +``` + +### Notes + +* Many other languages compile to (target) Wasm including AssemblyScript, C, + C++, Rust, and Zig! +* The embedding application is often called the "host" in WebAssembly. +* The Wasm binary is often called the "guest" in WebAssembly. Sometimes they + need [imports](../../imports) to implement features such as console output. + TinyGo's `wasi` target, requires [WASI][2] imports. + +[1]: https://wazero.io/languages/tinygo +[2]: https://wazero.io/specs/#wasi diff --git a/examples/basic/add.go b/examples/basic/add.go index 19d34912..99d9be0a 100644 --- a/examples/basic/add.go +++ b/examples/basic/add.go @@ -9,26 +9,40 @@ import ( "strconv" "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" ) // addWasm was generated by the following: // -// cd testdata; wat2wasm --debug-names add.wat +// cd testdata; tinygo build -o add.wasm -target=wasi add.go // //go:embed testdata/add.wasm var addWasm []byte -// main implements a basic function in both Go and WebAssembly. +// main is an example of how to extend a Go application with an addition +// function defined in WebAssembly. +// +// Since addWasm was compiled with TinyGo's `wasi` target, we need to configure +// WASI host imports. func main() { // Choose the context to use for function calls. ctx := context.Background() // Create a new WebAssembly Runtime. - r := wazero.NewRuntime(ctx) + r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfig(). + // WebAssembly 2.0 allows use of any version of TinyGo, including 0.24+. + WithWasmCore2()) defer r.Close(ctx) // This closes everything this Runtime created. - // Add a module to the runtime named "wasm/math" which exports one function "add", implemented in WebAssembly. - wasm, err := r.InstantiateModuleFromBinary(ctx, addWasm) + // Instantiate WASI, which implements host functions needed for TinyGo to + // implement `panic`. + if _, err := wasi_snapshot_preview1.Instantiate(ctx, r); err != nil { + log.Panicln(err) + } + + // Instantiate the guest Wasm into the same runtime. It exports the `add` + // function, implemented in WebAssembly. + mod, err := r.InstantiateModuleFromBinary(ctx, addWasm) if err != nil { log.Panicln(err) } @@ -36,14 +50,14 @@ func main() { // Read two args to add. x, y := readTwoArgs() - // Call the `add` function in the module and print the results to the console. - add := wasm.ExportedFunction("add") + // Call the `add` function and print the results to the console. + add := mod.ExportedFunction("add") results, err := add.Call(ctx, x, y) if err != nil { log.Panicln(err) } - fmt.Printf("%s: %d + %d = %d\n", wasm.Name(), x, y, results[0]) + fmt.Printf("%d + %d = %d\n", x, y, results[0]) } func readTwoArgs() (uint64, uint64) { diff --git a/examples/basic/add_test.go b/examples/basic/add_test.go index 4504ba9f..49607db8 100644 --- a/examples/basic/add_test.go +++ b/examples/basic/add_test.go @@ -12,6 +12,6 @@ import ( // go run add.go 7 9 func Test_main(t *testing.T) { stdout, _ := maintester.TestMain(t, main, "add", "7", "9") - require.Equal(t, `wasm/math: 7 + 9 = 16 + require.Equal(t, `7 + 9 = 16 `, stdout) } diff --git a/examples/basic/testdata/add.go b/examples/basic/testdata/add.go new file mode 100644 index 00000000..64408015 --- /dev/null +++ b/examples/basic/testdata/add.go @@ -0,0 +1,10 @@ +package main + +//export add +func add(x, y uint32) uint32 { + return x + y +} + +// main is required for the `wasi` target, even if it isn't used. +// See https://wazero.io/languages/tinygo/#why-do-i-have-to-define-main +func main() {} diff --git a/examples/basic/testdata/add.wasm b/examples/basic/testdata/add.wasm index 2dbc9adf..daac0e38 100644 Binary files a/examples/basic/testdata/add.wasm and b/examples/basic/testdata/add.wasm differ diff --git a/examples/basic/testdata/add.wat b/examples/basic/testdata/add.wat deleted file mode 100644 index f7880c33..00000000 --- a/examples/basic/testdata/add.wat +++ /dev/null @@ -1,15 +0,0 @@ -(module - ;; Define the optional module name. '$' prefixing is a part of the text format. - $wasm/math - - ;; add returns $x+$y. - ;; - ;; Notes: - ;; * The stack begins empty and anything left must match the result type. - ;; * export allows api.Module to return this via ExportedFunction("add") - (func (export "add") (param $x i32) (param $y i32) (result i32) - local.get $x ;; stack: [$x] - local.get $y ;; stack: [$x, $y] - i32.add ;; stack: [$x+$y] - ) -) diff --git a/go.mod b/go.mod index 743cb8f5..b34ebb73 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,8 @@ module github.com/tetratelabs/wazero // https://go.dev/doc/devel/release (1 version behind latest) go 1.18 -// Retract the first tag, which had the wrong naming convention. Also, retract -// the first beta as it had a memory bug and quickly fixed in v1.0.0-beta.2. -retract [v1.0.0-beta1, v1.0.0-beta.1] +// Wrong naming convention +retract v1.0.0-beta1 + +// Has memory bug and quickly fixed in v1.0.0-beta.2. +retract v1.0.0-beta.1 diff --git a/site/content/_index.md b/site/content/_index.md index 69bc014e..97acaa1c 100644 --- a/site/content/_index.md +++ b/site/content/_index.md @@ -11,68 +11,9 @@ wazero is the only zero dependency WebAssembly runtime written in Go. ## Example -The best way to learn wazero is by trying one of our [examples][1] - -For the impatient, here's a peek of a general flow with wazero: - -First, you need to compile your code into the WebAssembly Binary Format (Wasm). - -Here's source in [TinyGo]({{< ref "/languages/tinygo" >}}), which exports an -"add" function: -```go -package main - -//export add -func add(x, y uint32) uint32 { - return x + y -} -``` - -Here's the minimal command to build a `%.wasm` binary. -```bash -tinygo build -o add.wasm -target=wasi add.go -``` - -Finally, you can run that inside your Go application. -```go -func main() { - // Choose the context to use for function calls. - ctx := context.Background() - - // Read a WebAssembly binary containing an exported "add" function. - wasm, err := os.ReadFile("./path/to/add.wasm") - if err != nil { - log.Panicln(err) - } - - // Create a new WebAssembly Runtime. - r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfig(). - // WebAssembly 2.0 allows use of any version of TinyGo, including 0.24+. - WithWasmCore2()) - defer r.Close(ctx) // This closes everything this Runtime created. - - // Instantiate WASI, which implements system I/O such as console output. - if _, err = wasi_snapshot_preview1.Instantiate(ctx, r); err != nil { - log.Panicln(err) - } - - // Instantiate the module and return its exported functions - module, err := r.InstantiateModuleFromBinary(ctx, wasm) - if err != nil { - log.Panicln(err) - } - - // Discover 1+2=3 - fmt.Println(module.ExportedFunction("add").Call(ctx, 1, 2)) -} -``` - -Notes: - -* The Wasm binary is often called the "guest" in WebAssembly. -* The embedding application is often called the "host" in WebAssembly. -* Many languages compile to (target) Wasm including AssemblyScript, C, C++, - Rust, TinyGo and Zig! +The best way to learn wazero is by trying one of our [examples][1]. The +most [basic example][2] extends a Go application with an addition function +defined in WebAssembly. ## Why zero? @@ -93,8 +34,9 @@ go get github.com/tetratelabs/wazero@main wazero will release its first beta at the end of August 2022, and finalize 1.0 once Go 1.20 is released in Feb 2023. Meanwhile, please practice the -current APIs to ensure they work for you, and give us a [star][2] if you are +current APIs to ensure they work for you, and give us a [star][3] if you are enjoying it so far! [1]: https://github.com/tetratelabs/wazero/blob/main/examples -[2]: https://github.com/tetratelabs/wazero/stargazers +[2]: https://github.com/tetratelabs/wazero/blob/main/examples/basic +[3]: https://github.com/tetratelabs/wazero/stargazers diff --git a/site/content/languages/tinygo.md b/site/content/languages/tinygo.md index b65ee910..f920223c 100644 --- a/site/content/languages/tinygo.md +++ b/site/content/languages/tinygo.md @@ -33,6 +33,9 @@ package main func add(x, y uint32) uint32 { return x + y } + +// main is required for the `wasi` target, even if it isn't used. +func main() {} ``` The following is the minimal command to build a `%.wasm` binary. @@ -334,6 +337,17 @@ it. ## Frequently Asked Questions +### Why do I have to define main? + +If you are using TinyGo's `wasi` target, you should define at least a no-op +`func main() {}` in your source. + +If you don't, instantiation of the WebAssembly will fail unless you've exported +the following from the host: +```webassembly +(func (import "env" "main.main") (param i32) (result i32)) +``` + ### How do I use json? TinyGo doesn't yet implement [reflection APIs][16] needed by `encoding/json`. Meanwhile, most users resort to non-reflective parsers, such as [gjson][17].