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:
Crypt Keeper
2022-04-13 16:03:50 +08:00
committed by GitHub
parent 4cd4d8a590
commit 958ce19c0b
36 changed files with 306 additions and 532 deletions

View File

@@ -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:

View File

@@ -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'

View File

@@ -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
View 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.

View File

@@ -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
View File

@@ -0,0 +1 @@
add

3
examples/basic/README.md Normal file
View 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
View 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
}

View 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
}

View File

@@ -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])
}
}

View File

@@ -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())
}

View File

@@ -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
View File

@@ -0,0 +1 @@
multiple-results

View File

@@ -0,0 +1,3 @@
## Multiple results example
This example shows how to return more than one result from WebAssembly or Go-defined functions.

View File

@@ -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

View 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
View File

@@ -0,0 +1 @@
replace-import

View File

@@ -0,0 +1,3 @@
## Replace import example
This example shows how to override a module name hard-coded in a WebAssembly module.

View File

@@ -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)
}

View 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)
}

View File

@@ -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())
}

View File

@@ -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()))
}

View File

@@ -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)
}

Binary file not shown.

View File

@@ -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)
}

Binary file not shown.

View File

@@ -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)
}
}
}

Binary file not shown.

1
examples/wasi/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
wasi

3
examples/wasi/README.md Normal file
View 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
View 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
View 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
}

View File

@@ -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++ {

1
examples/wasi/testdata/test.txt vendored Normal file
View File

@@ -0,0 +1 @@
hello filesystem

View File

@@ -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: ")
}