This shows how to define, export and import functions written in Go. Fixes #464 Signed-off-by: Adrian Cole <adrian@tetrate.io> Co-authored-by: Takeshi Yoneda <takeshi@tetrate.io>
172 lines
6.8 KiB
Go
172 lines
6.8 KiB
Go
package multiple_results
|
|
|
|
import (
|
|
_ "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() {
|
|
// Create a portable WebAssembly Runtime.
|
|
runtime := wazero.NewRuntime()
|
|
|
|
// 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()
|
|
|
|
// 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()
|
|
|
|
// 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])
|
|
}
|
|
}
|
|
|
|
// 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(r wazero.Runtime) (api.Module, error) {
|
|
return r.InstantiateModuleFromCode([]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(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(m api.Module, resultOffsetAge uint32) (errno uint32) {
|
|
if m.Memory().WriteUint64Le(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(m api.Module) (age uint64) {
|
|
resultOffsetAge := uint32(8) // arbitrary memory offset (in bytes)
|
|
_, _ = m.ExportedFunction("get_age").Call(m, uint64(resultOffsetAge))
|
|
age, _ = m.Memory().ReadUint64Le(resultOffsetAge)
|
|
return
|
|
}).Instantiate()
|
|
}
|
|
|
|
// 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
|
|
|
|
;; 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(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(m api.Module) (age uint64) {
|
|
results, _ := m.ExportedFunction("get_age").Call(m)
|
|
return results[0]
|
|
}).Instantiate()
|
|
}
|