Makes examples runnable and pares down list (#458)
This deduplicates examples, leaving only the most maintained or targeted ones. Notably, this makes each runnable, avoiding test dependencies. Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
4
.github/workflows/examples.yaml
vendored
4
.github/workflows/examples.yaml
vendored
@@ -4,13 +4,13 @@ on:
|
||||
branches: [main]
|
||||
paths:
|
||||
- '.github/workflows/examples.yaml'
|
||||
- 'examples/wasm/*.go'
|
||||
- 'examples/*/testdata/*.go'
|
||||
- 'Makefile'
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- '.github/workflows/examples.yaml'
|
||||
- 'examples/wasm/*.go'
|
||||
- 'examples/*/testdata/*.go'
|
||||
- 'Makefile'
|
||||
|
||||
jobs:
|
||||
|
||||
2
Makefile
2
Makefile
@@ -17,7 +17,7 @@ bench_testdata_dir := tests/bench/testdata
|
||||
build.bench:
|
||||
tinygo build -o $(bench_testdata_dir)/case.wasm -scheduler=none -target=wasi $(bench_testdata_dir)/case.go
|
||||
|
||||
wasi_testdata_dir := ./examples/testdata
|
||||
wasi_testdata_dir := ./examples/*/testdata
|
||||
.PHONY: build.examples
|
||||
build.examples:
|
||||
@find $(wasi_testdata_dir) -type f -name "*.go" | xargs -Ip /bin/sh -c 'tinygo build -o $$(echo p | sed -e 's/\.go/\.wasm/') -scheduler=none -target=wasi p'
|
||||
|
||||
@@ -12,7 +12,9 @@ Import wazero and extend your Go application with code written in any language!
|
||||
|
||||
## Example
|
||||
|
||||
Here's an example of using wazero to invoke a factorial function:
|
||||
The best way to learn this and other features you get with wazero is by trying one of our [examples](examples).
|
||||
For the impatient, here's how invoking a factorial function looks in wazero:
|
||||
|
||||
```golang
|
||||
func main() {
|
||||
// Read a WebAssembly binary containing an exported "fac" function.
|
||||
@@ -55,9 +57,6 @@ defer module.Close()
|
||||
...
|
||||
```
|
||||
|
||||
The best way to learn this and other features you get with wazero is by trying
|
||||
[examples](examples).
|
||||
|
||||
## Runtime
|
||||
|
||||
There are two runtime configurations supported in wazero, where _JIT_ is default:
|
||||
|
||||
10
examples/README.md
Normal file
10
examples/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
## wazero examples
|
||||
|
||||
The following example projects can help you practice WebAssembly with wazero:
|
||||
|
||||
* [basic](basic) - how to use WebAssembly and Go-defined functions.
|
||||
* [multiple-results](multiple-results) - how to return more than one result from WebAssembly or Go-defined functions.
|
||||
* [replace-import](replace-import) - how to override a module name hard-coded in a WebAssembly module.
|
||||
* [wasi](wasi) - how to use I/O in your WebAssembly modules using WASI (WebAssembly System Interface).
|
||||
|
||||
Please [open an issue](https://github.com/tetratelabs/wazero/issues/new) if you would like to see another example.
|
||||
@@ -1,39 +0,0 @@
|
||||
package examples
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
)
|
||||
|
||||
// Test_AddInt shows how you can define a function in text format and have it compiled inline.
|
||||
// See https://github.com/summerwind/the-art-of-webassembly-go/blob/main/chapter1/addint/addint.wat
|
||||
func Test_AddInt(t *testing.T) {
|
||||
module, err := wazero.NewRuntime().InstantiateModuleFromCode([]byte(`(module $test
|
||||
(func $addInt ;; TODO: function module (export "AddInt")
|
||||
(param $value_1 i32) (param $value_2 i32)
|
||||
(result i32)
|
||||
local.get 0 ;; TODO: instruction variables $value_1
|
||||
local.get 1 ;; TODO: instruction variables $value_2
|
||||
i32.add
|
||||
)
|
||||
(export "AddInt" (func $addInt))
|
||||
)`))
|
||||
require.NoError(t, err)
|
||||
defer module.Close()
|
||||
|
||||
addInt := module.ExportedFunction("AddInt")
|
||||
|
||||
for _, c := range []struct {
|
||||
value1, value2, expected uint64 // i32i32_i32 sig, but wasm.ExportedFunction params and results are uint64
|
||||
}{
|
||||
{value1: 1, value2: 2, expected: 3},
|
||||
{value1: 5, value2: 5, expected: 10},
|
||||
} {
|
||||
results, err := addInt.Call(nil, c.value1, c.value2)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, c.expected, results[0])
|
||||
}
|
||||
}
|
||||
1
examples/basic/.gitignore
vendored
Normal file
1
examples/basic/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
add
|
||||
3
examples/basic/README.md
Normal file
3
examples/basic/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## Basic example
|
||||
|
||||
This example shows how to use WebAssembly and Go-defined functions with wazero.
|
||||
69
examples/basic/add.go
Normal file
69
examples/basic/add.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
// main implements a basic function in both Go and WebAssembly.
|
||||
func main() {
|
||||
// Create a new WebAssembly Runtime.
|
||||
r := wazero.NewRuntime()
|
||||
|
||||
// Add a module to the runtime named "wasm/math" which exports one function "add", implemented in WebAssembly.
|
||||
wasm, err := r.InstantiateModuleFromCode([]byte(`(module $wasm/math
|
||||
(func $add (param i32 i32) (result i32)
|
||||
local.get 0
|
||||
local.get 1
|
||||
i32.add
|
||||
)
|
||||
(export "add" (func $add))
|
||||
)`))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer wasm.Close()
|
||||
|
||||
// Add a module to the runtime named "host/math" which exports one function "add", implemented in Go.
|
||||
host, err := r.NewModuleBuilder("host/math").
|
||||
ExportFunction("add", func(v1, v2 uint32) uint32 {
|
||||
return v1 + v2
|
||||
}).Instantiate()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer host.Close()
|
||||
|
||||
// Read two args to add.
|
||||
x, y := readTwoArgs()
|
||||
|
||||
// Call the same function in both modules and print the results to the console.
|
||||
for _, mod := range []api.Module{wasm, host} {
|
||||
add := mod.ExportedFunction("add")
|
||||
results, err := add.Call(nil, x, y)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s: %d + %d = %d\n", mod.Name(), x, y, results[0])
|
||||
}
|
||||
}
|
||||
|
||||
func readTwoArgs() (uint64, uint64) {
|
||||
x, err := strconv.ParseUint(os.Args[1], 10, 64)
|
||||
if err != nil {
|
||||
log.Fatalf("invalid arg %v: %v", os.Args[1], err)
|
||||
}
|
||||
|
||||
y, err := strconv.ParseUint(os.Args[2], 10, 64)
|
||||
if err != nil {
|
||||
log.Fatalf("invalid arg %v: %v", os.Args[2], err)
|
||||
}
|
||||
return x, y
|
||||
}
|
||||
23
examples/basic/add_test.go
Normal file
23
examples/basic/add_test.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// ExampleMain ensures the following will work:
|
||||
//
|
||||
// go build add.go
|
||||
// ./add 7 9
|
||||
func ExampleMain() {
|
||||
|
||||
// Save the old os.Args and replace with our example input.
|
||||
oldArgs := os.Args
|
||||
os.Args = []string{"add", "7", "9"}
|
||||
defer func() { os.Args = oldArgs }()
|
||||
|
||||
main()
|
||||
|
||||
// Output:
|
||||
// wasm/math: 7 + 9 = 16
|
||||
// host/math: 7 + 9 = 16
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package examples
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/wasi"
|
||||
)
|
||||
|
||||
// fibWasm was compiled from TinyGo testdata/fibonacci.go
|
||||
//go:embed testdata/fibonacci.wasm
|
||||
var fibWasm []byte // TODO: implement this in text format as it is less distracting setup
|
||||
|
||||
func Test_fibonacci(t *testing.T) {
|
||||
r := wazero.NewRuntime()
|
||||
|
||||
// Note: fibonacci.go doesn't directly use WASI, but TinyGo needs to be initialized as a WASI Command.
|
||||
wm, err := wasi.InstantiateSnapshotPreview1(r)
|
||||
require.NoError(t, err)
|
||||
defer wm.Close()
|
||||
|
||||
module, err := r.InstantiateModuleFromCode(fibWasm)
|
||||
require.NoError(t, err)
|
||||
defer module.Close()
|
||||
|
||||
fibonacci := module.ExportedFunction("fibonacci")
|
||||
|
||||
for _, c := range []struct {
|
||||
input, expected uint64 // i32_i32 sig, but wasm.ExportedFunction params and results are uint64
|
||||
}{
|
||||
{input: 20, expected: 6765},
|
||||
{input: 10, expected: 55},
|
||||
{input: 5, expected: 5},
|
||||
} {
|
||||
results, err := fibonacci.Call(nil, c.input)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, c.expected, results[0])
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package examples
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
_ "embed"
|
||||
"io/fs"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/wasi"
|
||||
)
|
||||
|
||||
// catFS is an embedded filesystem limited to cat.go
|
||||
//go:embed testdata/cat.go
|
||||
var catFS embed.FS
|
||||
|
||||
// catGo is the TinyGo source
|
||||
//go:embed testdata/cat.go
|
||||
var catGo []byte
|
||||
|
||||
// catWasm was compiled from catGo
|
||||
//go:embed testdata/cat.wasm
|
||||
var catWasm []byte
|
||||
|
||||
// Test_Cat writes the input file to stdout, just like `cat`.
|
||||
//
|
||||
// This is a basic introduction to the WebAssembly System Interface (WASI).
|
||||
// See https://github.com/WebAssembly/WASI
|
||||
func Test_Cat(t *testing.T) {
|
||||
r := wazero.NewRuntime()
|
||||
|
||||
// First, configure where the WebAssembly Module (Wasm) console outputs to (stdout).
|
||||
stdoutBuf := bytes.NewBuffer(nil)
|
||||
|
||||
// Since wazero uses fs.FS, we can use standard libraries to do things like trim the leading path.
|
||||
rooted, err := fs.Sub(catFS, "testdata")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Combine the above into our baseline config, overriding defaults (which discard stdout and have no file system).
|
||||
config := wazero.NewModuleConfig().WithStdout(stdoutBuf).WithFS(rooted)
|
||||
|
||||
// Instantiate WASI, which implements system I/O such as console output.
|
||||
wm, err := wasi.InstantiateSnapshotPreview1(r)
|
||||
require.NoError(t, err)
|
||||
defer wm.Close()
|
||||
|
||||
// InstantiateModuleFromCodeWithConfig runs the "_start" function which is what TinyGo compiles "main" to.
|
||||
// * Set the program name (arg[0]) to "cat" and add args to write "cat.go" to stdout twice.
|
||||
// * We use both "/cat.go" and "./cat.go" because WithFS by default maps the workdir "." to "/".
|
||||
cat, err := r.InstantiateModuleFromCodeWithConfig(catWasm, config.WithArgs("cat", "/cat.go", "./cat.go"))
|
||||
require.NoError(t, err)
|
||||
defer cat.Close()
|
||||
|
||||
// We expect the WebAssembly function wrote "cat.go" twice!
|
||||
require.Equal(t, append(catGo, catGo...), stdoutBuf.Bytes())
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package examples
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
_ "embed"
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/wasi"
|
||||
)
|
||||
|
||||
type testKey struct{}
|
||||
|
||||
// hostFuncWasm was compiled from TinyGo testdata/host_func.go
|
||||
//go:embed testdata/host_func.wasm
|
||||
var hostFuncWasm []byte
|
||||
|
||||
func Test_hostFunc(t *testing.T) {
|
||||
// The function for allocating the in-Wasm memory region.
|
||||
// We resolve this function after main module instantiation.
|
||||
allocateInWasmBuffer := func(api.Module, uint32) uint32 {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
var expectedBase64String string
|
||||
|
||||
// Host-side implementation of get_random_string on Wasm import.
|
||||
getRandomBytes := func(ctx api.Module, retBufPtr uint32, retBufSize uint32) {
|
||||
// Assert that context values passed in from CallFunctionContext are accessible.
|
||||
contextValue := ctx.Context().Value(testKey{}).(int64)
|
||||
require.Equal(t, int64(12345), contextValue)
|
||||
|
||||
const bufferSize = 10
|
||||
offset := allocateInWasmBuffer(ctx, bufferSize)
|
||||
|
||||
// Store the address info to the memory.
|
||||
require.True(t, ctx.Memory().WriteUint32Le(retBufPtr, offset))
|
||||
require.True(t, ctx.Memory().WriteUint32Le(retBufSize, uint32(bufferSize)))
|
||||
|
||||
// Now store the random values in the region.
|
||||
b, ok := ctx.Memory().Read(offset, bufferSize)
|
||||
require.True(t, ok)
|
||||
|
||||
n, err := rand.Read(b)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, bufferSize, n)
|
||||
|
||||
expectedBase64String = base64.StdEncoding.EncodeToString(b)
|
||||
}
|
||||
|
||||
r := wazero.NewRuntime()
|
||||
|
||||
_, err := r.NewModuleBuilder("env").ExportFunction("get_random_bytes", getRandomBytes).Instantiate()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Configure stdout (console) to write to a buffer.
|
||||
stdout := bytes.NewBuffer(nil)
|
||||
config := wazero.NewModuleConfig().WithStdout(stdout)
|
||||
|
||||
// Instantiate WASI, which implements system I/O such as console output.
|
||||
wm, err := wasi.InstantiateSnapshotPreview1(r)
|
||||
require.NoError(t, err)
|
||||
defer wm.Close()
|
||||
|
||||
// InstantiateModuleFromCodeWithConfig runs the "_start" function which is what TinyGo compiles "main" to.
|
||||
module, err := r.InstantiateModuleFromCodeWithConfig(hostFuncWasm, config)
|
||||
require.NoError(t, err)
|
||||
defer module.Close()
|
||||
|
||||
allocateInWasmBufferFn := module.ExportedFunction("allocate_buffer")
|
||||
require.NotNil(t, allocateInWasmBufferFn)
|
||||
|
||||
// Implement the function pointer. This mainly shows how you can decouple a module function dependency.
|
||||
allocateInWasmBuffer = func(ctx api.Module, size uint32) uint32 {
|
||||
res, err := allocateInWasmBufferFn.Call(ctx, uint64(size))
|
||||
require.NoError(t, err)
|
||||
return uint32(res[0])
|
||||
}
|
||||
|
||||
// Set a context variable that should be available in a wasm.Function.
|
||||
ctx := context.WithValue(context.Background(), testKey{}, int64(12345))
|
||||
|
||||
// Invoke a module-defined function that depends on a host function import
|
||||
_, err = module.ExportedFunction("base64").Call(module.WithContext(ctx))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify that in-Wasm calculated base64 string matches the one calculated in native Go.
|
||||
require.Equal(t, expectedBase64String, strings.TrimSpace(stdout.String()))
|
||||
}
|
||||
1
examples/multiple-results/.gitignore
vendored
Normal file
1
examples/multiple-results/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
multiple-results
|
||||
3
examples/multiple-results/README.md
Normal file
3
examples/multiple-results/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## Multiple results example
|
||||
|
||||
This example shows how to return more than one result from WebAssembly or Go-defined functions.
|
||||
@@ -1,38 +1,75 @@
|
||||
package examples
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
// Test_MultiReturn_V1_0 implements functions with multiple returns values, using an approach portable with any
|
||||
// WebAssembly 1.0 (20191205) runtime.
|
||||
// main implements functions with multiple returns values, using both an approach portable with any WebAssembly 1.0
|
||||
// (20191205) runtime, as well one dependent on the "multiple-results" feature.
|
||||
//
|
||||
// Note: This is the same approach used by WASI snapshot-01!
|
||||
// The portable approach uses parameters to return additional results. The parameter value is a memory offset to write
|
||||
// the next value. This is the same approach used by WASI snapshot-01!
|
||||
// * resultOffsetWasmFunctions
|
||||
// * resultOffsetHostFunctions
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md
|
||||
func Test_MultipleResults(t *testing.T) {
|
||||
r := wazero.NewRuntime()
|
||||
//
|
||||
// Another approach is to enable the "multiple-results" feature. While "multiple-results" is not yet a W3C recommendation, most
|
||||
// WebAssembly runtimes support it by default.
|
||||
// * multiValueWasmFunctions
|
||||
// * multiValueHostFunctions
|
||||
// See https://github.com/WebAssembly/spec/blob/main/proposals/multi-value/Overview.md
|
||||
func main() {
|
||||
// Create a portable WebAssembly Runtime.
|
||||
runtime := wazero.NewRuntime()
|
||||
|
||||
// Instantiate a module that illustrates multiple results using functions defined as WebAssembly instructions.
|
||||
wasm, err := resultOffsetWasmFunctions(r)
|
||||
require.NoError(t, err)
|
||||
// Add a module that uses offset parameters for multiple results, with functions defined in WebAssembly.
|
||||
wasm, err := resultOffsetWasmFunctions(runtime)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer wasm.Close()
|
||||
|
||||
// Instantiate a module that illustrates multiple results using functions defined in Go.
|
||||
host, err := resultOffsetHostFunctions(r)
|
||||
require.NoError(t, err)
|
||||
defer wasm.Close()
|
||||
// Add a module that uses offset parameters for multiple results, with functions defined in Go.
|
||||
host, err := resultOffsetHostFunctions(runtime)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer host.Close()
|
||||
|
||||
// Prove both implementations have the same effects!
|
||||
for _, mod := range []api.Module{wasm, host} {
|
||||
callGetNumber := mod.ExportedFunction("call_get_age")
|
||||
results, err := callGetNumber.Call(nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []uint64{37}, results)
|
||||
// wazero enables only W3C recommended features by default. Opt-in to other features like so:
|
||||
runtimeWithMultiValue := wazero.NewRuntimeWithConfig(
|
||||
wazero.NewRuntimeConfig().WithFeatureMultiValue(true),
|
||||
// ^^ Note: You can enable all features via WithFinishedFeatures.
|
||||
)
|
||||
|
||||
// Add a module that uses multiple results values, with functions defined in WebAssembly.
|
||||
wasmWithMultiValue, err := multiValueWasmFunctions(runtimeWithMultiValue)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer wasmWithMultiValue.Close()
|
||||
|
||||
// Add a module that uses multiple results values, with functions defined in Go.
|
||||
hostWithMultiValue, err := multiValueHostFunctions(runtimeWithMultiValue)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer hostWithMultiValue.Close()
|
||||
|
||||
// Call the same function in all modules and print the results to the console.
|
||||
for _, mod := range []api.Module{wasm, host, wasmWithMultiValue, hostWithMultiValue} {
|
||||
getAge := mod.ExportedFunction("call_get_age")
|
||||
results, err := getAge.Call(nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s: age=%d\n", mod.Name(), results[0])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,38 +133,7 @@ func resultOffsetHostFunctions(r wazero.Runtime) (api.Module, error) {
|
||||
}).Instantiate()
|
||||
}
|
||||
|
||||
// Test_MultipleResults_MultiValue implements functions with multiple returns values naturally, due to enabling the
|
||||
// "multi-value" feature.
|
||||
//
|
||||
// Note: While "multi-value" is not yet a W3C recommendation, most WebAssembly runtimes support it by default.
|
||||
// See https://github.com/WebAssembly/spec/blob/main/proposals/multi-value/Overview.md
|
||||
func Test_MultipleResults_MultiValue(t *testing.T) {
|
||||
// wazero enables only W3C recommended features by default. Opt-in to other features like so:
|
||||
r := wazero.NewRuntimeWithConfig(
|
||||
wazero.NewRuntimeConfig().WithFeatureMultiValue(true),
|
||||
// ^^ Note: You can enable all features via WithFinishedFeatures.
|
||||
)
|
||||
|
||||
// Instantiate a module that illustrates multi-value functions defined as WebAssembly instructions.
|
||||
wasm, err := multiValueWasmFunctions(r)
|
||||
require.NoError(t, err)
|
||||
defer wasm.Close()
|
||||
|
||||
// Instantiate a module that illustrates multi-value functions using functions defined in Go.
|
||||
host, err := multiValueHostFunctions(r)
|
||||
require.NoError(t, err)
|
||||
defer wasm.Close()
|
||||
|
||||
// Prove both implementations have the same effects!
|
||||
for _, mod := range []api.Module{wasm, host} {
|
||||
callGetNumber := mod.ExportedFunction("call_get_age")
|
||||
results, err := callGetNumber.Call(nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []uint64{37}, results)
|
||||
}
|
||||
}
|
||||
|
||||
// multiValueWasmFunctions defines Wasm functions that illustrate multiple results using the "multi-value" feature.
|
||||
// multiValueWasmFunctions defines Wasm functions that illustrate multiple results using the "multiple-results" feature.
|
||||
func multiValueWasmFunctions(r wazero.Runtime) (api.Module, error) {
|
||||
return r.InstantiateModuleFromCode([]byte(`(module $multi-value/wasm
|
||||
|
||||
@@ -148,7 +154,7 @@ func multiValueWasmFunctions(r wazero.Runtime) (api.Module, error) {
|
||||
)`))
|
||||
}
|
||||
|
||||
// multiValueHostFunctions defines Wasm functions that illustrate multiple results using the "multi-value" feature.
|
||||
// multiValueHostFunctions defines Wasm functions that illustrate multiple results using the "multiple-results" feature.
|
||||
func multiValueHostFunctions(r wazero.Runtime) (api.Module, error) {
|
||||
return r.NewModuleBuilder("multi-value/host").
|
||||
// Define a function that returns two results
|
||||
16
examples/multiple-results/multiple-results_test.go
Normal file
16
examples/multiple-results/multiple-results_test.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package main
|
||||
|
||||
// ExampleMain ensures the following will work:
|
||||
//
|
||||
// go build multiple-results.go
|
||||
// ./multiple-results
|
||||
func ExampleMain() {
|
||||
|
||||
main()
|
||||
|
||||
// Output:
|
||||
// result-offset/wasm: age=37
|
||||
// result-offset/host: age=37
|
||||
// multi-value/wasm: age=37
|
||||
// multi-value/host: age=37
|
||||
}
|
||||
1
examples/replace-import/.gitignore
vendored
Normal file
1
examples/replace-import/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
replace-import
|
||||
3
examples/replace-import/README.md
Normal file
3
examples/replace-import/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## Replace import example
|
||||
|
||||
This example shows how to override a module name hard-coded in a WebAssembly module.
|
||||
@@ -1,16 +1,16 @@
|
||||
package examples
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
// Test_Replace shows how you can replace a module import when it doesn't match instantiated modules.
|
||||
func Test_Replace(t *testing.T) {
|
||||
// main shows how you can replace a module import when it doesn't match instantiated modules.
|
||||
func main() {
|
||||
r := wazero.NewRuntime()
|
||||
|
||||
// Instantiate a function that closes the module under "assemblyscript.abort".
|
||||
@@ -18,25 +18,30 @@ func Test_Replace(t *testing.T) {
|
||||
ExportFunction("abort", func(m api.Module, messageOffset, fileNameOffset, line, col uint32) {
|
||||
_ = m.CloseWithExitCode(255)
|
||||
}).Instantiate()
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer host.Close()
|
||||
|
||||
// Compile code that needs the function "env.abort".
|
||||
code, err := r.CompileModule([]byte(`(module
|
||||
code, err := r.CompileModule([]byte(`(module $needs-import
|
||||
(import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32)))
|
||||
|
||||
(export "abort" (func 0)) ;; exports the import for testing
|
||||
)`))
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Instantiate the module, replacing the import "env.abort" with "assemblyscript.abort".
|
||||
mod, err := r.InstantiateModuleWithConfig(code, wazero.NewModuleConfig().
|
||||
WithName(t.Name()).
|
||||
WithImport("env", "abort", "assemblyscript", "abort"))
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer mod.Close()
|
||||
|
||||
// Since the above worked, the exported function closes the module.
|
||||
_, err = mod.ExportedFunction("abort").Call(nil, 0, 0, 0, 0)
|
||||
require.EqualError(t, err, `module "Test_Replace" closed with exit_code(255)`)
|
||||
fmt.Println(err)
|
||||
}
|
||||
13
examples/replace-import/replace-import_test.go
Normal file
13
examples/replace-import/replace-import_test.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package main
|
||||
|
||||
// ExampleMain ensures the following will work:
|
||||
//
|
||||
// go build replace-import.go
|
||||
// ./replace-import
|
||||
func ExampleMain() {
|
||||
|
||||
main()
|
||||
|
||||
// Output:
|
||||
// module "needs-import" closed with exit_code(255)
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package examples
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
)
|
||||
|
||||
// Test_Simple implements a basic function in go: hello. This is imported as the Wasm name "$hello" and run on start.
|
||||
func Test_Simple(t *testing.T) {
|
||||
stdout := new(bytes.Buffer)
|
||||
hello := func() {
|
||||
_, _ = fmt.Fprintln(stdout, "hello!")
|
||||
}
|
||||
|
||||
r := wazero.NewRuntime()
|
||||
|
||||
// Host functions can be exported as any module name, including the empty string.
|
||||
host, err := r.NewModuleBuilder("").ExportFunction("hello", hello).Instantiate()
|
||||
require.NoError(t, err)
|
||||
defer host.Close()
|
||||
|
||||
// The "hello" function was imported as $hello in Wasm. Since it was marked as the start
|
||||
// function, it is invoked on instantiation. Ensure that worked: "hello" was called!
|
||||
mod, err := r.InstantiateModuleFromCode([]byte(`(module $test
|
||||
(import "" "hello" (func $hello))
|
||||
(start $hello)
|
||||
)`))
|
||||
require.NoError(t, err)
|
||||
defer mod.Close()
|
||||
|
||||
require.Equal(t, "hello!\n", stdout.String())
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package examples
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/wasi"
|
||||
)
|
||||
|
||||
// stdioWasm was compiled from TinyGo testdata/stdio.go
|
||||
//go:embed testdata/stdio.wasm
|
||||
var stdioWasm []byte
|
||||
|
||||
func Test_stdio(t *testing.T) {
|
||||
r := wazero.NewRuntime()
|
||||
|
||||
// Configure standard I/O (ex stdout) to write to buffers instead of no-op.
|
||||
stdinBuf := bytes.NewBuffer([]byte("WASI\n"))
|
||||
stdoutBuf := bytes.NewBuffer(nil)
|
||||
stderrBuf := bytes.NewBuffer(nil)
|
||||
config := wazero.NewModuleConfig().WithStdin(stdinBuf).WithStdout(stdoutBuf).WithStderr(stderrBuf)
|
||||
|
||||
// Instantiate WASI, which implements system I/O such as console output.
|
||||
wm, err := wasi.InstantiateSnapshotPreview1(r)
|
||||
require.NoError(t, err)
|
||||
defer wm.Close()
|
||||
|
||||
// InstantiateModuleFromCodeWithConfig runs the "_start" function which is what TinyGo compiles "main" to.
|
||||
module, err := r.InstantiateModuleFromCodeWithConfig(stdioWasm, config)
|
||||
require.NoError(t, err)
|
||||
defer module.Close()
|
||||
|
||||
require.Equal(t, "Hello, WASI!", strings.TrimSpace(stdoutBuf.String()))
|
||||
require.Equal(t, "Error Message", strings.TrimSpace(stderrBuf.String()))
|
||||
}
|
||||
11
examples/testdata/fibonacci.go
vendored
11
examples/testdata/fibonacci.go
vendored
@@ -1,11 +0,0 @@
|
||||
package main
|
||||
|
||||
func main() {}
|
||||
|
||||
//export fibonacci
|
||||
func fibonacci(in uint32) uint32 {
|
||||
if in <= 1 {
|
||||
return in
|
||||
}
|
||||
return fibonacci(in-1) + fibonacci(in-2)
|
||||
}
|
||||
BIN
examples/testdata/fibonacci.wasm
vendored
BIN
examples/testdata/fibonacci.wasm
vendored
Binary file not shown.
44
examples/testdata/host_func.go
vendored
44
examples/testdata/host_func.go
vendored
@@ -1,44 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func main() {}
|
||||
|
||||
//export allocate_buffer
|
||||
func allocateBuffer(size uint32) *byte {
|
||||
// Allocate the in-Wasm memory region and returns its pointer to hosts.
|
||||
// The region is supposed to store random bytes generated in hosts.
|
||||
buf := make([]byte, size)
|
||||
return &buf[0]
|
||||
}
|
||||
|
||||
// Note: Export on a function without an implementation is a Wasm import that defaults to the module "env".
|
||||
//export get_random_bytes
|
||||
func get_random_bytes(retBufPtr **byte, retBufSize *int)
|
||||
|
||||
// Get random bytes from the host.
|
||||
func getRandomBytes() []byte {
|
||||
var bufPtr *byte
|
||||
var bufSize int
|
||||
get_random_bytes(&bufPtr, &bufSize)
|
||||
//nolint
|
||||
return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
|
||||
Data: uintptr(unsafe.Pointer(bufPtr)),
|
||||
Len: uintptr(bufSize),
|
||||
Cap: uintptr(bufSize),
|
||||
}))
|
||||
}
|
||||
|
||||
//export base64
|
||||
func base64OnString() {
|
||||
// Get random bytes from the host and
|
||||
// do base64 encoding them for given times.
|
||||
buf := getRandomBytes()
|
||||
encoded := base64.StdEncoding.EncodeToString(buf)
|
||||
fmt.Println(encoded)
|
||||
}
|
||||
BIN
examples/testdata/host_func.wasm
vendored
BIN
examples/testdata/host_func.wasm
vendored
Binary file not shown.
22
examples/testdata/stdio.go
vendored
22
examples/testdata/stdio.go
vendored
@@ -1,22 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := bufio.NewScanner(os.Stdin)
|
||||
for s.Scan() {
|
||||
line := s.Text()
|
||||
if _, err := fmt.Printf("Hello, %s!\n", strings.TrimSpace(line)); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintln(os.Stderr, "Error Message"); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
examples/testdata/stdio.wasm
vendored
BIN
examples/testdata/stdio.wasm
vendored
Binary file not shown.
1
examples/wasi/.gitignore
vendored
Normal file
1
examples/wasi/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
wasi
|
||||
3
examples/wasi/README.md
Normal file
3
examples/wasi/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## WASI example
|
||||
|
||||
This example shows how to use I/O in your WebAssembly modules using WASI (WebAssembly System Interface).
|
||||
53
examples/wasi/cat.go
Normal file
53
examples/wasi/cat.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
_ "embed"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/wasi"
|
||||
)
|
||||
|
||||
// catFS is an embedded filesystem limited to test.txt
|
||||
//go:embed testdata/test.txt
|
||||
var catFS embed.FS
|
||||
|
||||
// catWasm was compiled the TinyGo source testdata/cat.go
|
||||
//go:embed testdata/cat.wasm
|
||||
var catWasm []byte
|
||||
|
||||
// main writes an input file to stdout, just like `cat`.
|
||||
//
|
||||
// This is a basic introduction to the WebAssembly System Interface (WASI).
|
||||
// See https://github.com/WebAssembly/WASI
|
||||
func main() {
|
||||
r := wazero.NewRuntime()
|
||||
|
||||
// Since wazero uses fs.FS, we can use standard libraries to do things like trim the leading path.
|
||||
rooted, err := fs.Sub(catFS, "testdata")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Combine the above into our baseline config, overriding defaults (which discard stdout and have no file system).
|
||||
config := wazero.NewModuleConfig().WithStdout(os.Stdout).WithFS(rooted)
|
||||
|
||||
// Instantiate WASI, which implements system I/O such as console output.
|
||||
wm, err := wasi.InstantiateSnapshotPreview1(r)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer wm.Close()
|
||||
|
||||
// InstantiateModuleFromCodeWithConfig runs the "_start" function which is what TinyGo compiles "main" to.
|
||||
// * Set the program name (arg[0]) to "wasi" and add args to write "test.txt" to stdout twice.
|
||||
// * We use "/test.txt" or "./test.txt" because WithFS by default maps the workdir "." to "/".
|
||||
cat, err := r.InstantiateModuleFromCodeWithConfig(catWasm, config.WithArgs("wasi", os.Args[1]))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cat.Close()
|
||||
}
|
||||
20
examples/wasi/cat_test.go
Normal file
20
examples/wasi/cat_test.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import "os"
|
||||
|
||||
// ExampleMain ensures the following will work:
|
||||
//
|
||||
// go build cat.go
|
||||
// ./cat ./test.txt
|
||||
func ExampleMain() {
|
||||
|
||||
// Save the old os.Args and replace with our example input.
|
||||
oldArgs := os.Args
|
||||
os.Args = []string{"cat", "./test.txt"}
|
||||
defer func() { os.Args = oldArgs }()
|
||||
|
||||
main()
|
||||
|
||||
// Output:
|
||||
// hello filesystem
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// main is the same as cat: "concatenate and print files."
|
||||
// main is the same as wasi: "concatenate and print files."
|
||||
func main() {
|
||||
// Start at arg[1] because args[0] is the program name.
|
||||
for i := 1; i < len(os.Args); i++ {
|
||||
Binary file not shown.
1
examples/wasi/testdata/test.txt
vendored
Normal file
1
examples/wasi/testdata/test.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
hello filesystem
|
||||
@@ -1,67 +0,0 @@
|
||||
package examples
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/wasi"
|
||||
)
|
||||
|
||||
func Test_WASI(t *testing.T) {
|
||||
// built-in WASI function to write a random value to memory
|
||||
randomGet := func(ctx api.Module, buf, bufLen uint32) wasi.Errno {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
stdout := new(bytes.Buffer)
|
||||
random := func(ctx api.Module) {
|
||||
// Write 8 random bytes to memory using WASI.
|
||||
errno := randomGet(ctx, 0, 8)
|
||||
require.Equal(t, wasi.ErrnoSuccess, errno)
|
||||
|
||||
// Read them back and print it in hex!
|
||||
random, ok := ctx.Memory().ReadUint64Le(0)
|
||||
require.True(t, ok)
|
||||
_, _ = fmt.Fprintf(stdout, "random: %x\n", random)
|
||||
}
|
||||
|
||||
r := wazero.NewRuntime()
|
||||
|
||||
// Host functions can be exported as any module name, including the empty string.
|
||||
host, err := r.NewModuleBuilder("").ExportFunction("random", random).Instantiate()
|
||||
require.NoError(t, err)
|
||||
defer host.Close()
|
||||
|
||||
// Configure WASI and implement the function to use it
|
||||
wm, err := wasi.InstantiateSnapshotPreview1(r)
|
||||
require.NoError(t, err)
|
||||
defer wm.Close()
|
||||
|
||||
randomGetFn := wm.ExportedFunction("random_get")
|
||||
|
||||
// Implement the function pointer. This mainly shows how you can decouple a host function dependency.
|
||||
randomGet = func(ctx api.Module, buf, bufLen uint32) wasi.Errno {
|
||||
res, err := randomGetFn.Call(ctx, uint64(buf), uint64(bufLen))
|
||||
require.NoError(t, err)
|
||||
return wasi.Errno(res[0])
|
||||
}
|
||||
|
||||
// The "random" function was imported as $random in Wasm. Since it was marked as the start
|
||||
// function, it is invoked on instantiation. Ensure that worked: "random" was called!
|
||||
mod, err := r.InstantiateModuleFromCode([]byte(`(module $wasi
|
||||
(import "wasi_snapshot_preview1" "random_get"
|
||||
(func $wasi.random_get (param $buf i32) (param $buf_len i32) (result (;errno;) i32)))
|
||||
(import "" "random" (func $random))
|
||||
(memory 1)
|
||||
(start $random)
|
||||
)`))
|
||||
require.NoError(t, err)
|
||||
defer mod.Close()
|
||||
|
||||
require.Contains(t, stdout.String(), "random: ")
|
||||
}
|
||||
Reference in New Issue
Block a user