Files
wazero/examples/multiple-results/multiple-results.go
Crypt Keeper 45ff2fe12f Propagates context to all api interface methods that aren't constant (#502)
This prepares for exposing operations like Memory.Grow while keeping the
ability to trace what did that, by adding a `context.Context` initial
parameter. This adds this to all API methods that mutate or return
mutated data.

Before, we made a change to trace functions and general lifecycle
commands, but we missed this part. Ex. We track functions, but can't
track what closed the module, changed memory or a mutable constant.
Changing to do this now is not only more consistent, but helps us
optimize at least the interpreter to help users identify otherwise
opaque code that can cause harm. This is critical before we add more
functions that can cause harm, such as Memory.Grow.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
2022-04-25 08:13:18 +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: You can enable all features via WithFinishedFeatures.
)
// 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)
}