Files
wazero/examples/multiple-results/multiple-results.go
Crypt Keeper a91140f7f7 Changes RuntimeConfig to an interface and exposes WithWasmCore2 (#518)
WebAssembly Core Working Draft 1 recently came out. Before that, we had
a toe-hold feature bucked called FinishedFeatures. This replaces
`RuntimeConfig.WithFinishedFeatures` with `RuntimeConfig.WithWasmCore2`.
This also adds `WithWasmCore1` for those who want to lock into 1.0
features as opposed to relying on defaults.

This also fixes some design debt where we hadn't finished migrating
public types that require constructor functions (NewXxx) to interfaces.
By using interfaces, we prevent people from accidentally initializing
key configuration directly (via &Xxx), causing nil field refs. This also
helps prevent confusion about how to use the type (ex pointer or not) as
there's only one way (as an interface).

See https://github.com/tetratelabs/wazero/issues/516

Signed-off-by: Adrian Cole <adrian@tetrate.io>
2022-05-02 10:29:38 +08:00

176 lines
7.1 KiB
Go

package multiple_results
import (
"context"
_ "embed"
"fmt"
"log"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
)
// 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.
//
// 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
//
// 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() {
// Choose the context to use for function calls.
ctx := context.Background()
// Create a new WebAssembly Runtime.
runtime := wazero.NewRuntime()
// Add a module that uses offset parameters for multiple results, with functions defined in WebAssembly.
wasm, err := resultOffsetWasmFunctions(ctx, runtime)
if err != nil {
log.Fatal(err)
}
defer wasm.Close(ctx)
// Add a module that uses offset parameters for multiple results, with functions defined in Go.
host, err := resultOffsetHostFunctions(ctx, runtime)
if err != nil {
log.Fatal(err)
}
defer host.Close(ctx)
// wazero enables only W3C recommended features by default. Opt-in to other features like so:
runtimeWithMultiValue := wazero.NewRuntimeWithConfig(
wazero.NewRuntimeConfig().WithFeatureMultiValue(true),
// ^^ Note: WebAssembly 2.0 (WithWasmCore2) includes "multi-value".
)
// Add a module that uses multiple results values, with functions defined in WebAssembly.
wasmWithMultiValue, err := multiValueWasmFunctions(ctx, runtimeWithMultiValue)
if err != nil {
log.Fatal(err)
}
defer wasmWithMultiValue.Close(ctx)
// Add a module that uses multiple results values, with functions defined in Go.
hostWithMultiValue, err := multiValueHostFunctions(ctx, runtimeWithMultiValue)
if err != nil {
log.Fatal(err)
}
defer hostWithMultiValue.Close(ctx)
// 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(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s: age=%d\n", mod.Name(), results[0])
}
}
// resultOffsetWasmFunctions defines Wasm functions that illustrate multiple results using a technique compatible
// with any WebAssembly 1.0 (20191205) runtime.
//
// To return a value in WASM written to a result parameter, you have to define memory and pass a location to write
// the result. At the end of your function, you load that location.
func resultOffsetWasmFunctions(ctx context.Context, r wazero.Runtime) (api.Module, error) {
return r.InstantiateModuleFromCode(ctx, []byte(`(module $result-offset/wasm
;; To use result parameters, we need scratch memory. Allocate the least possible: 1 page (64KB).
(memory 1 1)
;; Define a function that returns a result, while a second result is written to memory.
(func $get_age (param $result_offset.age i32) (result (;errno;) i32)
local.get 0 ;; stack = [$result_offset.age]
i64.const 37 ;; stack = [$result_offset.age, 37]
i64.store ;; stack = []
i32.const 0 ;; stack = [0]
)
;; Now, define a function that shows the Wasm mechanics returning something written to a result parameter.
;; The caller provides a memory offset to the callee, so that it knows where to write the second result.
(func $call_get_age (result i64)
i32.const 8 ;; stack = [8] $result_offset.age parameter to get_age (arbitrary memory offset 8)
call $get_age ;; stack = [errno] result of get_age
drop ;; stack = []
i32.const 8 ;; stack = [8] same value as the $result_offset.age parameter
i64.load ;; stack = [age]
)
;; Export the function, so that we can test it!
(export "call_get_age" (func $call_get_age))
)`))
}
// resultOffsetHostFunctions defines host functions that illustrate multiple results using a technique compatible
// with any WebAssembly 1.0 (20191205) runtime.
//
// To return a value in WASM written to a result parameter, you have to define memory and pass a location to write
// the result. At the end of your function, you load that location.
func resultOffsetHostFunctions(ctx context.Context, r wazero.Runtime) (api.Module, error) {
return r.NewModuleBuilder("result-offset/host").
// To use result parameters, we need scratch memory. Allocate the least possible: 1 page (64KB).
ExportMemoryWithMax("mem", 1, 1).
// Define a function that returns a result, while a second result is written to memory.
ExportFunction("get_age", func(ctx context.Context, m api.Module, resultOffsetAge uint32) (errno uint32) {
if m.Memory().WriteUint64Le(ctx, resultOffsetAge, 37) {
return 0
}
return 1 // overflow
}).
// Now, define a function that shows the Wasm mechanics returning something written to a result parameter.
// The caller provides a memory offset to the callee, so that it knows where to write the second result.
ExportFunction("call_get_age", func(ctx context.Context, m api.Module) (age uint64) {
resultOffsetAge := uint32(8) // arbitrary memory offset (in bytes)
_, _ = m.ExportedFunction("get_age").Call(ctx, uint64(resultOffsetAge))
age, _ = m.Memory().ReadUint64Le(ctx, resultOffsetAge)
return
}).Instantiate(ctx)
}
// multiValueWasmFunctions defines Wasm functions that illustrate multiple results using the "multiple-results" feature.
func multiValueWasmFunctions(ctx context.Context, r wazero.Runtime) (api.Module, error) {
return r.InstantiateModuleFromCode(ctx, []byte(`(module $multi-value/wasm
;; Define a function that returns two results
(func $get_age (result (;age;) i64 (;errno;) i32)
i64.const 37 ;; stack = [37]
i32.const 0 ;; stack = [37, 0]
)
;; Now, define a function that returns only the first result.
(func $call_get_age (result i64)
call $get_age ;; stack = [37, errno] result of get_age
drop ;; stack = [37]
)
;; Export the function, so that we can test it!
(export "call_get_age" (func $call_get_age))
)`))
}
// multiValueHostFunctions defines Wasm functions that illustrate multiple results using the "multiple-results" feature.
func multiValueHostFunctions(ctx context.Context, r wazero.Runtime) (api.Module, error) {
return r.NewModuleBuilder("multi-value/host").
// Define a function that returns two results
ExportFunction("get_age", func() (age uint64, errno uint32) {
age = 37
errno = 0
return
}).
// Now, define a function that returns only the first result.
ExportFunction("call_get_age", func(ctx context.Context, m api.Module) (age uint64) {
results, _ := m.ExportedFunction("get_age").Call(ctx)
return results[0]
}).Instantiate(ctx)
}