Runtime.NewModule -> InstantiateModule and adds ModuleBuilder (#349)
This reverts `Runtime.NewModule` back to `InstantiateModule` as it calls
more attention to the registration aspect of it, and also makes a chain
of `NewXX` more clear. This is particularly helpful as this change
introduces `ModuleBuilder` which is created by `NewModuleBuilder`.
`ModuleBuilder` is a way to define a WebAssembly 1.0 (20191205) in Go.
The first iteration allows setting the module name and exported
functions. The next PR will add globals.
Ex. Below defines and instantiates a module named "env" with one function:
```go
hello := func() {
fmt.Fprintln(stdout, "hello!")
}
_, err := r.NewModuleBuilder("env").ExportFunction("hello", hello).InstantiateModule()
```
If the same module may be instantiated multiple times, it is more efficient to separate steps. Ex.
```go
env, err := r.NewModuleBuilder("env").ExportFunction("get_random_string", getRandomString).Build()
_, err := r.InstantiateModule(env.WithName("env.1"))
_, err := r.InstantiateModule(env.WithName("env.2"))
```
Note: Builder methods do not return errors, to allow chaining. Any validation errors are deferred until Build.
Note: Insertion order is not retained. Anything defined by this builder is sorted lexicographically on Build.
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
@@ -18,7 +18,7 @@ func main() {
|
||||
source, _ := os.ReadFile("./tests/engine/testdata/fac.wasm")
|
||||
|
||||
// Instantiate the module and return its exported functions
|
||||
module, _ := wazero.NewRuntime().NewModuleFromSource(source)
|
||||
module, _ := wazero.NewRuntime().InstantiateModuleFromSource(source)
|
||||
|
||||
// Discover 7! is 5040
|
||||
fmt.Println(module.ExportedFunction("fac").Call(nil, 7))
|
||||
|
||||
124
builder.go
Normal file
124
builder.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package wazero
|
||||
|
||||
import (
|
||||
internalwasm "github.com/tetratelabs/wazero/internal/wasm"
|
||||
"github.com/tetratelabs/wazero/wasm"
|
||||
)
|
||||
|
||||
// ModuleBuilder is a way to define a WebAssembly 1.0 (20191205) in Go.
|
||||
//
|
||||
// Ex. Below defines and instantiates a module named "env" with one function:
|
||||
//
|
||||
// hello := func() {
|
||||
// fmt.Fprintln(stdout, "hello!")
|
||||
// }
|
||||
// _, err := r.NewModuleBuilder("env").ExportFunction("hello", hello).Instantiate()
|
||||
//
|
||||
// If the same module may be instantiated multiple times, it is more efficient to separate steps. Ex.
|
||||
//
|
||||
// env, err := r.NewModuleBuilder("env").ExportFunction("get_random_string", getRandomString).Build()
|
||||
//
|
||||
// _, err := r.InstantiateModule(env.WithName("env.1"))
|
||||
// _, err := r.InstantiateModule(env.WithName("env.2"))
|
||||
//
|
||||
// Note: Builder methods do not return errors, to allow chaining. Any validation errors are deferred until Build.
|
||||
// Note: Insertion order is not retained. Anything defined by this builder is sorted lexicographically on Build.
|
||||
type ModuleBuilder interface {
|
||||
|
||||
// ExportFunction adds a function written in Go, which a WebAssembly Module can import.
|
||||
//
|
||||
// * name - the name to export. Ex "random_get"
|
||||
// * goFunc - the `func` to export.
|
||||
//
|
||||
// Noting a context exception described later, all parameters or result types must match WebAssembly 1.0 (20191205) value
|
||||
// types. This means uint32, uint64, float32 or float64. Up to one result can be returned.
|
||||
//
|
||||
// Ex. This is a valid host function:
|
||||
//
|
||||
// addInts := func(x uint32, uint32) uint32 {
|
||||
// return x + y
|
||||
// }
|
||||
//
|
||||
// Host functions may also have an initial parameter (param[0]) of type context.Context or wasm.Module.
|
||||
//
|
||||
// Ex. This uses a Go Context:
|
||||
//
|
||||
// addInts := func(ctx context.Context, x uint32, uint32) uint32 {
|
||||
// // add a little extra if we put some in the context!
|
||||
// return x + y + ctx.Value(extraKey).(uint32)
|
||||
// }
|
||||
//
|
||||
// The most sophisticated context is wasm.Module, which allows access to the Go context, but also
|
||||
// allows writing to memory. This is important because there are only numeric types in Wasm. The only way to share other
|
||||
// data is via writing memory and sharing offsets.
|
||||
//
|
||||
// Ex. This reads the parameters from!
|
||||
//
|
||||
// addInts := func(ctx wasm.Module, offset uint32) uint32 {
|
||||
// x, _ := ctx.Memory().ReadUint32Le(offset)
|
||||
// y, _ := ctx.Memory().ReadUint32Le(offset + 4) // 32 bits == 4 bytes!
|
||||
// return x + y
|
||||
// }
|
||||
//
|
||||
// Note: If a function is already exported with the same name, this overwrites it.
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#host-functions%E2%91%A2
|
||||
ExportFunction(name string, goFunc interface{}) ModuleBuilder
|
||||
|
||||
// ExportFunctions is a convenience that calls ExportFunction for each key/value in the provided map.
|
||||
ExportFunctions(nameToGoFunc map[string]interface{}) ModuleBuilder
|
||||
|
||||
// Build returns a Module to instantiate, or returns an error if any of the configuration is invalid.
|
||||
Build() (*Module, error)
|
||||
|
||||
// Instantiate is a convenience that calls Build, then Runtime.InstantiateModule
|
||||
Instantiate() (wasm.Module, error)
|
||||
}
|
||||
|
||||
// moduleBuilder implements ModuleBuilder
|
||||
type moduleBuilder struct {
|
||||
r *runtime
|
||||
moduleName string
|
||||
nameToGoFunc map[string]interface{}
|
||||
}
|
||||
|
||||
// NewModuleBuilder implements Runtime.NewModuleBuilder
|
||||
func (r *runtime) NewModuleBuilder(moduleName string) ModuleBuilder {
|
||||
return &moduleBuilder{
|
||||
r: r,
|
||||
moduleName: moduleName,
|
||||
nameToGoFunc: map[string]interface{}{},
|
||||
}
|
||||
}
|
||||
|
||||
// ExportFunction implements ModuleBuilder.ExportFunction
|
||||
func (b *moduleBuilder) ExportFunction(name string, goFunc interface{}) ModuleBuilder {
|
||||
b.nameToGoFunc[name] = goFunc
|
||||
return b
|
||||
}
|
||||
|
||||
// ExportFunctions implements ModuleBuilder.ExportFunctions
|
||||
func (b *moduleBuilder) ExportFunctions(nameToGoFunc map[string]interface{}) ModuleBuilder {
|
||||
for k, v := range nameToGoFunc {
|
||||
b.ExportFunction(k, v)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Build implements ModuleBuilder.Build
|
||||
func (b *moduleBuilder) Build() (*Module, error) {
|
||||
// TODO: we can use r.enabledFeatures to fail early on things like mutable globals
|
||||
if module, err := internalwasm.NewHostModule(b.moduleName, b.nameToGoFunc); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return &Module{name: b.moduleName, module: module}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// InstantiateModule implements ModuleBuilder.InstantiateModule
|
||||
func (b *moduleBuilder) Instantiate() (wasm.Module, error) {
|
||||
if module, err := b.Build(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return b.r.InstantiateModule(module)
|
||||
}
|
||||
}
|
||||
208
builder_test.go
Normal file
208
builder_test.go
Normal file
@@ -0,0 +1,208 @@
|
||||
package wazero
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
internalwasm "github.com/tetratelabs/wazero/internal/wasm"
|
||||
"github.com/tetratelabs/wazero/wasm"
|
||||
)
|
||||
|
||||
// TestNewModuleBuilder_Build only covers a few scenarios to avoid duplicating tests in internal/wasm/host_test.go
|
||||
func TestNewModuleBuilder_Build(t *testing.T) {
|
||||
i32, i64 := wasm.ValueTypeI32, wasm.ValueTypeI64
|
||||
|
||||
uint32_uint32 := func(uint32) uint32 {
|
||||
return 0
|
||||
}
|
||||
fnUint32_uint32 := reflect.ValueOf(uint32_uint32)
|
||||
uint64_uint32 := func(uint64) uint32 {
|
||||
return 0
|
||||
}
|
||||
fnUint64_uint32 := reflect.ValueOf(uint64_uint32)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input func(Runtime) ModuleBuilder
|
||||
expected *internalwasm.Module
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
input: func(r Runtime) ModuleBuilder {
|
||||
return r.NewModuleBuilder("")
|
||||
},
|
||||
expected: &internalwasm.Module{},
|
||||
},
|
||||
{
|
||||
name: "only name",
|
||||
input: func(r Runtime) ModuleBuilder {
|
||||
return r.NewModuleBuilder("env")
|
||||
},
|
||||
expected: &internalwasm.Module{NameSection: &internalwasm.NameSection{ModuleName: "env"}},
|
||||
},
|
||||
{
|
||||
name: "ExportFunction",
|
||||
input: func(r Runtime) ModuleBuilder {
|
||||
return r.NewModuleBuilder("").ExportFunction("1", uint32_uint32)
|
||||
},
|
||||
expected: &internalwasm.Module{
|
||||
TypeSection: []*internalwasm.FunctionType{
|
||||
{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}},
|
||||
},
|
||||
FunctionSection: []internalwasm.Index{0},
|
||||
HostFunctionSection: []*reflect.Value{&fnUint32_uint32},
|
||||
ExportSection: map[string]*internalwasm.Export{
|
||||
"1": {Name: "1", Type: internalwasm.ExternTypeFunc, Index: 0},
|
||||
},
|
||||
NameSection: &internalwasm.NameSection{
|
||||
FunctionNames: internalwasm.NameMap{{Index: 0, Name: "1"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ExportFunction overwrites existing",
|
||||
input: func(r Runtime) ModuleBuilder {
|
||||
return r.NewModuleBuilder("").ExportFunction("1", uint32_uint32).ExportFunction("1", uint64_uint32)
|
||||
},
|
||||
expected: &internalwasm.Module{
|
||||
TypeSection: []*internalwasm.FunctionType{
|
||||
{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i32}},
|
||||
},
|
||||
FunctionSection: []internalwasm.Index{0},
|
||||
HostFunctionSection: []*reflect.Value{&fnUint64_uint32},
|
||||
ExportSection: map[string]*internalwasm.Export{
|
||||
"1": {Name: "1", Type: internalwasm.ExternTypeFunc, Index: 0},
|
||||
},
|
||||
NameSection: &internalwasm.NameSection{
|
||||
FunctionNames: internalwasm.NameMap{{Index: 0, Name: "1"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ExportFunction twice",
|
||||
input: func(r Runtime) ModuleBuilder {
|
||||
// Intentionally out of order
|
||||
return r.NewModuleBuilder("").ExportFunction("2", uint64_uint32).ExportFunction("1", uint32_uint32)
|
||||
},
|
||||
expected: &internalwasm.Module{
|
||||
TypeSection: []*internalwasm.FunctionType{
|
||||
{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}},
|
||||
{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i32}},
|
||||
},
|
||||
FunctionSection: []internalwasm.Index{0, 1},
|
||||
HostFunctionSection: []*reflect.Value{&fnUint32_uint32, &fnUint64_uint32},
|
||||
ExportSection: map[string]*internalwasm.Export{
|
||||
"1": {Name: "1", Type: internalwasm.ExternTypeFunc, Index: 0},
|
||||
"2": {Name: "2", Type: internalwasm.ExternTypeFunc, Index: 1},
|
||||
},
|
||||
NameSection: &internalwasm.NameSection{
|
||||
FunctionNames: internalwasm.NameMap{{Index: 0, Name: "1"}, {Index: 1, Name: "2"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ExportFunctions",
|
||||
input: func(r Runtime) ModuleBuilder {
|
||||
return r.NewModuleBuilder("").ExportFunctions(map[string]interface{}{
|
||||
"1": uint32_uint32,
|
||||
"2": uint64_uint32,
|
||||
})
|
||||
},
|
||||
expected: &internalwasm.Module{
|
||||
TypeSection: []*internalwasm.FunctionType{
|
||||
{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}},
|
||||
{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i32}},
|
||||
},
|
||||
FunctionSection: []internalwasm.Index{0, 1},
|
||||
HostFunctionSection: []*reflect.Value{&fnUint32_uint32, &fnUint64_uint32},
|
||||
ExportSection: map[string]*internalwasm.Export{
|
||||
"1": {Name: "1", Type: internalwasm.ExternTypeFunc, Index: 0},
|
||||
"2": {Name: "2", Type: internalwasm.ExternTypeFunc, Index: 1},
|
||||
},
|
||||
NameSection: &internalwasm.NameSection{
|
||||
FunctionNames: internalwasm.NameMap{{Index: 0, Name: "1"}, {Index: 1, Name: "2"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ExportFunctions overwrites",
|
||||
input: func(r Runtime) ModuleBuilder {
|
||||
b := r.NewModuleBuilder("").ExportFunction("1", uint64_uint32)
|
||||
return b.ExportFunctions(map[string]interface{}{
|
||||
"1": uint32_uint32,
|
||||
"2": uint64_uint32,
|
||||
})
|
||||
},
|
||||
expected: &internalwasm.Module{
|
||||
TypeSection: []*internalwasm.FunctionType{
|
||||
{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}},
|
||||
{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i32}},
|
||||
},
|
||||
FunctionSection: []internalwasm.Index{0, 1},
|
||||
HostFunctionSection: []*reflect.Value{&fnUint32_uint32, &fnUint64_uint32},
|
||||
ExportSection: map[string]*internalwasm.Export{
|
||||
"1": {Name: "1", Type: internalwasm.ExternTypeFunc, Index: 0},
|
||||
"2": {Name: "2", Type: internalwasm.ExternTypeFunc, Index: 1},
|
||||
},
|
||||
NameSection: &internalwasm.NameSection{
|
||||
FunctionNames: internalwasm.NameMap{{Index: 0, Name: "1"}, {Index: 1, Name: "2"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
m, e := tc.input(NewRuntime()).Build()
|
||||
require.NoError(t, e)
|
||||
requireHostModuleEquals(t, tc.expected, m.module)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewModuleBuilder_InstantiateModule ensures Runtime.InstantiateModule is called on success.
|
||||
func TestNewModuleBuilder_InstantiateModule(t *testing.T) {
|
||||
r := NewRuntime()
|
||||
m, err := r.NewModuleBuilder("env").Instantiate()
|
||||
require.NoError(t, err)
|
||||
|
||||
// If this was instantiated, it would be added to the store under the same name
|
||||
require.Equal(t, r.(*runtime).store.Module("env"), m)
|
||||
}
|
||||
|
||||
// TestNewModuleBuilder_InstantiateModule_Errors ensures errors propagate from Runtime.InstantiateModule
|
||||
func TestNewModuleBuilder_InstantiateModule_Errors(t *testing.T) {
|
||||
r := NewRuntime()
|
||||
_, err := r.NewModuleBuilder("env").Instantiate()
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = r.NewModuleBuilder("env").Instantiate()
|
||||
require.EqualError(t, err, "module env has already been instantiated")
|
||||
}
|
||||
|
||||
// requireHostModuleEquals is redefined from internal/wasm/host_test.go to avoid an import cycle extracting it.
|
||||
func requireHostModuleEquals(t *testing.T, expected, actual *internalwasm.Module) {
|
||||
// `require.Equal(t, expected, actual)` fails reflect pointers don't match, so brute compare:
|
||||
require.Equal(t, expected.TypeSection, actual.TypeSection)
|
||||
require.Equal(t, expected.ImportSection, actual.ImportSection)
|
||||
require.Equal(t, expected.FunctionSection, actual.FunctionSection)
|
||||
require.Equal(t, expected.TableSection, actual.TableSection)
|
||||
require.Equal(t, expected.MemorySection, actual.MemorySection)
|
||||
require.Equal(t, expected.GlobalSection, actual.GlobalSection)
|
||||
require.Equal(t, expected.ExportSection, actual.ExportSection)
|
||||
require.Equal(t, expected.StartSection, actual.StartSection)
|
||||
require.Equal(t, expected.ElementSection, actual.ElementSection)
|
||||
require.Nil(t, actual.CodeSection) // Host functions are implemented in Go, not Wasm!
|
||||
require.Equal(t, expected.DataSection, actual.DataSection)
|
||||
require.Equal(t, expected.NameSection, actual.NameSection)
|
||||
|
||||
// Special case because reflect.Value can't be compared with Equals
|
||||
require.Equal(t, len(expected.HostFunctionSection), len(actual.HostFunctionSection))
|
||||
for i := range expected.HostFunctionSection {
|
||||
require.Equal(t, (*expected.HostFunctionSection[i]).Type(), (*actual.HostFunctionSection[i]).Type())
|
||||
}
|
||||
}
|
||||
51
config.go
51
config.go
@@ -71,56 +71,13 @@ func (r *RuntimeConfig) WithFeatureSignExtensionOps(enabled bool) *RuntimeConfig
|
||||
return &RuntimeConfig{engine: r.engine, ctx: r.ctx, enabledFeatures: enabledFeatures}
|
||||
}
|
||||
|
||||
// DecodedModule is a WebAssembly 1.0 (20191205) text or binary encoded module to instantiate.
|
||||
type DecodedModule struct {
|
||||
// Module is a WebAssembly 1.0 (20191205) module to instantiate.
|
||||
type Module struct {
|
||||
name string
|
||||
module *internalwasm.Module
|
||||
}
|
||||
|
||||
// WithName returns a new instance which overrides the name.
|
||||
func (m *DecodedModule) WithName(moduleName string) *DecodedModule {
|
||||
return &DecodedModule{name: moduleName, module: m.module}
|
||||
}
|
||||
|
||||
// HostModuleConfig are WebAssembly 1.0 (20191205) exports from the host bound to a module name used by InstantiateHostModule.
|
||||
type HostModuleConfig struct {
|
||||
// Name is the module name that these exports can be imported with. Ex. wasi.ModuleSnapshotPreview1
|
||||
Name string
|
||||
|
||||
// Functions adds functions written in Go, which a WebAssembly Module can import.
|
||||
//
|
||||
// The key is the name to export and the value is the func. Ex. WASISnapshotPreview1
|
||||
//
|
||||
// Noting a context exception described later, all parameters or result types must match WebAssembly 1.0 (20191205) value
|
||||
// types. This means uint32, uint64, float32 or float64. Up to one result can be returned.
|
||||
//
|
||||
// Ex. This is a valid host function:
|
||||
//
|
||||
// addInts := func(x uint32, uint32) uint32 {
|
||||
// return x + y
|
||||
// }
|
||||
//
|
||||
// Host functions may also have an initial parameter (param[0]) of type context.Context or wasm.Module.
|
||||
//
|
||||
// Ex. This uses a Go Context:
|
||||
//
|
||||
// addInts := func(ctx context.Context, x uint32, uint32) uint32 {
|
||||
// // add a little extra if we put some in the context!
|
||||
// return x + y + ctx.Value(extraKey).(uint32)
|
||||
// }
|
||||
//
|
||||
// The most sophisticated context is wasm.Module, which allows access to the Go context, but also
|
||||
// allows writing to memory. This is important because there are only numeric types in Wasm. The only way to share other
|
||||
// data is via writing memory and sharing offsets.
|
||||
//
|
||||
// Ex. This reads the parameters from!
|
||||
//
|
||||
// addInts := func(ctx wasm.Module, offset uint32) uint32 {
|
||||
// x, _ := ctx.Memory().ReadUint32Le(offset)
|
||||
// y, _ := ctx.Memory().ReadUint32Le(offset + 4) // 32 bits == 4 bytes!
|
||||
// return x + y
|
||||
// }
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#host-functions%E2%91%A2
|
||||
Functions map[string]interface{}
|
||||
func (m *Module) WithName(moduleName string) *Module {
|
||||
return &Module{name: moduleName, module: m.module}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
// 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().NewModuleFromSource([]byte(`(module $test
|
||||
module, err := wazero.NewRuntime().InstantiateModuleFromSource([]byte(`(module $test
|
||||
(func $addInt ;; TODO: function module (export "AddInt")
|
||||
(param $value_1 i32) (param $value_2 i32)
|
||||
(result i32)
|
||||
|
||||
@@ -17,7 +17,7 @@ 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.
|
||||
_, err := r.NewHostModuleFromConfig(wazero.WASISnapshotPreview1())
|
||||
_, err := r.InstantiateModule(wazero.WASISnapshotPreview1())
|
||||
require.NoError(t, err)
|
||||
|
||||
module, err := wazero.StartWASICommandFromSource(r, fibWasm)
|
||||
|
||||
@@ -52,7 +52,7 @@ func Test_file_system(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
wasiConfig := &wazero.WASIConfig{Preopens: map[string]wasi.FS{".": memFS}}
|
||||
_, err = r.NewHostModuleFromConfig(wazero.WASISnapshotPreview1WithConfig(wasiConfig))
|
||||
_, err = r.InstantiateModule(wazero.WASISnapshotPreview1WithConfig(wasiConfig))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Note: TinyGo binaries must be treated as WASI Commands to initialize memory.
|
||||
|
||||
@@ -56,13 +56,12 @@ func Test_hostFunc(t *testing.T) {
|
||||
|
||||
r := wazero.NewRuntime()
|
||||
|
||||
env := &wazero.HostModuleConfig{Name: "env", Functions: map[string]interface{}{"get_random_bytes": getRandomBytes}}
|
||||
_, err := r.NewHostModuleFromConfig(env)
|
||||
_, err := r.NewModuleBuilder("env").ExportFunction("get_random_bytes", getRandomBytes).Instantiate()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Note: host_func.go doesn't directly use WASI, but TinyGo needs to be initialized as a WASI Command.
|
||||
stdout := bytes.NewBuffer(nil)
|
||||
_, err = r.NewHostModuleFromConfig(wazero.WASISnapshotPreview1WithConfig(&wazero.WASIConfig{Stdout: stdout}))
|
||||
_, err = r.InstantiateModule(wazero.WASISnapshotPreview1WithConfig(&wazero.WASIConfig{Stdout: stdout}))
|
||||
require.NoError(t, err)
|
||||
|
||||
module, err := wazero.StartWASICommandFromSource(r, hostFuncWasm)
|
||||
|
||||
@@ -13,20 +13,19 @@ import (
|
||||
// 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)
|
||||
goFunc := func() {
|
||||
hello := func() {
|
||||
_, _ = fmt.Fprintln(stdout, "hello!")
|
||||
}
|
||||
|
||||
r := wazero.NewRuntime()
|
||||
|
||||
// Host functions can be exported as any module name, including the empty string.
|
||||
env := &wazero.HostModuleConfig{Name: "", Functions: map[string]interface{}{"hello": goFunc}}
|
||||
_, err := r.NewHostModuleFromConfig(env)
|
||||
_, err := r.NewModuleBuilder("").ExportFunction("hello", hello).Instantiate()
|
||||
require.NoError(t, err)
|
||||
|
||||
// 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!
|
||||
_, err = r.NewModuleFromSource([]byte(`(module $test
|
||||
_, err = r.InstantiateModuleFromSource([]byte(`(module $test
|
||||
(import "" "hello" (func $hello))
|
||||
(start $hello)
|
||||
)`))
|
||||
|
||||
@@ -24,7 +24,7 @@ func Test_stdio(t *testing.T) {
|
||||
|
||||
// Configure WASI host functions with the IO buffers
|
||||
wasiConfig := &wazero.WASIConfig{Stdin: stdinBuf, Stdout: stdoutBuf, Stderr: stderrBuf}
|
||||
_, err := r.NewHostModuleFromConfig(wazero.WASISnapshotPreview1WithConfig(wasiConfig))
|
||||
_, err := r.InstantiateModule(wazero.WASISnapshotPreview1WithConfig(wasiConfig))
|
||||
require.NoError(t, err)
|
||||
|
||||
// StartWASICommand runs the "_start" function which is what TinyGo compiles "main" to
|
||||
|
||||
@@ -19,7 +19,7 @@ func Test_WASI(t *testing.T) {
|
||||
}
|
||||
|
||||
stdout := new(bytes.Buffer)
|
||||
goFunc := func(ctx wasm.Module) {
|
||||
random := func(ctx wasm.Module) {
|
||||
// Write 8 random bytes to memory using WASI.
|
||||
errno := randomGet(ctx, 0, 8)
|
||||
require.Equal(t, wasi.ErrnoSuccess, errno)
|
||||
@@ -33,11 +33,11 @@ func Test_WASI(t *testing.T) {
|
||||
r := wazero.NewRuntime()
|
||||
|
||||
// Host functions can be exported as any module name, including the empty string.
|
||||
env := &wazero.HostModuleConfig{Name: "", Functions: map[string]interface{}{"random": goFunc}}
|
||||
_, err := r.NewHostModuleFromConfig(env)
|
||||
_, err := r.NewModuleBuilder("").ExportFunction("random", random).Instantiate()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Configure WASI and implement the function to use it
|
||||
we, err := r.NewHostModuleFromConfig(wazero.WASISnapshotPreview1())
|
||||
we, err := r.InstantiateModule(wazero.WASISnapshotPreview1())
|
||||
require.NoError(t, err)
|
||||
randomGetFn := we.ExportedFunction("random_get")
|
||||
|
||||
@@ -50,7 +50,7 @@ func Test_WASI(t *testing.T) {
|
||||
|
||||
// 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!
|
||||
_, err = r.NewModuleFromSource([]byte(`(module $wasi
|
||||
_, err = r.InstantiateModuleFromSource([]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))
|
||||
|
||||
@@ -80,30 +80,33 @@ func TestNewHostModule(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
m, e := NewHostModule(tc.moduleName, tc.goFuncs)
|
||||
require.NoError(t, e)
|
||||
|
||||
// `require.Equal(t, tc.expected, m)` fails reflect pointers don't match, so brute compare:
|
||||
require.Equal(t, tc.expected.TypeSection, m.TypeSection)
|
||||
require.Equal(t, tc.expected.ImportSection, m.ImportSection)
|
||||
require.Equal(t, tc.expected.FunctionSection, m.FunctionSection)
|
||||
require.Equal(t, tc.expected.TableSection, m.TableSection)
|
||||
require.Equal(t, tc.expected.MemorySection, m.MemorySection)
|
||||
require.Equal(t, tc.expected.GlobalSection, m.GlobalSection)
|
||||
require.Equal(t, tc.expected.ExportSection, m.ExportSection)
|
||||
require.Equal(t, tc.expected.StartSection, m.StartSection)
|
||||
require.Equal(t, tc.expected.ElementSection, m.ElementSection)
|
||||
require.Nil(t, m.CodeSection) // Host functions are implemented in Go, not Wasm!
|
||||
require.Equal(t, tc.expected.DataSection, m.DataSection)
|
||||
require.Equal(t, tc.expected.NameSection, m.NameSection)
|
||||
|
||||
// Special case because reflect.Value can't be compared with Equals
|
||||
require.Equal(t, len(tc.expected.HostFunctionSection), len(m.HostFunctionSection))
|
||||
for i := range tc.expected.HostFunctionSection {
|
||||
require.Equal(t, (*tc.expected.HostFunctionSection[i]).Type(), (*m.HostFunctionSection[i]).Type())
|
||||
}
|
||||
requireHostModuleEquals(t, tc.expected, m)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func requireHostModuleEquals(t *testing.T, expected, actual *Module) {
|
||||
// `require.Equal(t, expected, actual)` fails reflect pointers don't match, so brute compare:
|
||||
require.Equal(t, expected.TypeSection, actual.TypeSection)
|
||||
require.Equal(t, expected.ImportSection, actual.ImportSection)
|
||||
require.Equal(t, expected.FunctionSection, actual.FunctionSection)
|
||||
require.Equal(t, expected.TableSection, actual.TableSection)
|
||||
require.Equal(t, expected.MemorySection, actual.MemorySection)
|
||||
require.Equal(t, expected.GlobalSection, actual.GlobalSection)
|
||||
require.Equal(t, expected.ExportSection, actual.ExportSection)
|
||||
require.Equal(t, expected.StartSection, actual.StartSection)
|
||||
require.Equal(t, expected.ElementSection, actual.ElementSection)
|
||||
require.Nil(t, actual.CodeSection) // Host functions are implemented in Go, not Wasm!
|
||||
require.Equal(t, expected.DataSection, actual.DataSection)
|
||||
require.Equal(t, expected.NameSection, actual.NameSection)
|
||||
|
||||
// Special case because reflect.Value can't be compared with Equals
|
||||
require.Equal(t, len(expected.HostFunctionSection), len(actual.HostFunctionSection))
|
||||
for i := range expected.HostFunctionSection {
|
||||
require.Equal(t, (*expected.HostFunctionSection[i]).Type(), (*actual.HostFunctionSection[i]).Type())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewHostModule_Errors(t *testing.T) {
|
||||
t.Run("Adds export name to error message", func(t *testing.T) {
|
||||
_, err := NewHostModule("test", map[string]interface{}{"fn": "hello"})
|
||||
|
||||
@@ -131,15 +131,14 @@ func instantiateHostFunctionModuleWithEngine(b *testing.B, engine *wazero.Runtim
|
||||
|
||||
r := wazero.NewRuntimeWithConfig(engine)
|
||||
|
||||
env := &wazero.HostModuleConfig{Name: "env", Functions: map[string]interface{}{"get_random_string": getRandomString}}
|
||||
_, err := r.NewHostModuleFromConfig(env)
|
||||
_, err := r.NewModuleBuilder("env").ExportFunction("get_random_string", getRandomString).Instantiate()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
// Note: host_func.go doesn't directly use WASI, but TinyGo needs to be initialized as a WASI Command.
|
||||
// Add WASI to satisfy import tests
|
||||
_, err = r.NewHostModuleFromConfig(wazero.WASISnapshotPreview1())
|
||||
_, err = r.InstantiateModule(wazero.WASISnapshotPreview1())
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ func runAdhocTests(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig)
|
||||
|
||||
func testHugeStack(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
|
||||
r := wazero.NewRuntimeWithConfig(newRuntimeConfig())
|
||||
module, err := r.NewModuleFromSource(hugestackWasm)
|
||||
module, err := r.InstantiateModuleFromSource(hugestackWasm)
|
||||
require.NoError(t, err)
|
||||
|
||||
fn := module.ExportedFunction("main")
|
||||
@@ -81,7 +81,7 @@ func testHugeStack(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig)
|
||||
|
||||
func testFibonacci(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
|
||||
r := wazero.NewRuntimeWithConfig(newRuntimeConfig())
|
||||
module, err := r.NewModuleFromSource(fibWasm)
|
||||
module, err := r.InstantiateModuleFromSource(fibWasm)
|
||||
require.NoError(t, err)
|
||||
|
||||
fib := module.ExportedFunction("fib")
|
||||
@@ -94,7 +94,7 @@ func testFibonacci(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig)
|
||||
|
||||
func testFac(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
|
||||
r := wazero.NewRuntimeWithConfig(newRuntimeConfig())
|
||||
module, err := r.NewModuleFromSource(facWasm)
|
||||
module, err := r.InstantiateModuleFromSource(facWasm)
|
||||
require.NoError(t, err)
|
||||
for _, name := range []string{
|
||||
"fac-rec",
|
||||
@@ -127,11 +127,10 @@ func testUnreachable(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig
|
||||
|
||||
r := wazero.NewRuntimeWithConfig(newRuntimeConfig())
|
||||
|
||||
hostModule := &wazero.HostModuleConfig{Name: "host", Functions: map[string]interface{}{"cause_unreachable": callUnreachable}}
|
||||
_, err := r.NewHostModuleFromConfig(hostModule)
|
||||
_, err := r.NewModuleBuilder("host").ExportFunction("cause_unreachable", callUnreachable).Instantiate()
|
||||
require.NoError(t, err)
|
||||
|
||||
module, err := r.NewModuleFromSource(unreachableWasm)
|
||||
module, err := r.InstantiateModuleFromSource(unreachableWasm)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = module.ExportedFunction("main").Call(nil)
|
||||
@@ -146,7 +145,7 @@ wasm backtrace:
|
||||
|
||||
func testMemory(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
|
||||
r := wazero.NewRuntimeWithConfig(newRuntimeConfig())
|
||||
module, err := r.NewModuleFromSource(memoryWasm)
|
||||
module, err := r.InstantiateModuleFromSource(memoryWasm)
|
||||
require.NoError(t, err)
|
||||
|
||||
size := module.ExportedFunction("size")
|
||||
@@ -185,11 +184,10 @@ func testRecursiveEntry(t *testing.T, newRuntimeConfig func() *wazero.RuntimeCon
|
||||
|
||||
r := wazero.NewRuntimeWithConfig(newRuntimeConfig())
|
||||
|
||||
hostModule := &wazero.HostModuleConfig{Name: "env", Functions: map[string]interface{}{"host_func": hostfunc}}
|
||||
_, err := r.NewHostModuleFromConfig(hostModule)
|
||||
_, err := r.NewModuleBuilder("env").ExportFunction("host_func", hostfunc).Instantiate()
|
||||
require.NoError(t, err)
|
||||
|
||||
module, err := r.NewModuleFromSource(recursiveWasm)
|
||||
module, err := r.InstantiateModuleFromSource(recursiveWasm)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = module.ExportedFunction("main").Call(nil, 1)
|
||||
@@ -211,11 +209,10 @@ func testImportedAndExportedFunc(t *testing.T, newRuntimeConfig func() *wazero.R
|
||||
|
||||
r := wazero.NewRuntimeWithConfig(newRuntimeConfig())
|
||||
|
||||
hostModule := &wazero.HostModuleConfig{Name: "", Functions: map[string]interface{}{"store_int": storeInt}}
|
||||
_, err := r.NewHostModuleFromConfig(hostModule)
|
||||
_, err := r.NewModuleBuilder("").ExportFunction("store_int", storeInt).Instantiate()
|
||||
require.NoError(t, err)
|
||||
|
||||
module, err := r.NewModuleFromSource([]byte(`(module $test
|
||||
module, err := r.InstantiateModuleFromSource([]byte(`(module $test
|
||||
(import "" "store_int"
|
||||
(func $store_int (param $offset i32) (param $val i64) (result (;errno;) i32)))
|
||||
(memory $memory 1 1)
|
||||
@@ -279,11 +276,10 @@ func testHostFunctions(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConf
|
||||
} {
|
||||
r := wazero.NewRuntimeWithConfig(newRuntimeConfig())
|
||||
|
||||
hostModule := &wazero.HostModuleConfig{Name: "host", Functions: v}
|
||||
_, err := r.NewHostModuleFromConfig(hostModule)
|
||||
_, err := r.NewModuleBuilder("host").ExportFunctions(v).Instantiate()
|
||||
require.NoError(t, err)
|
||||
|
||||
m, err = r.NewModuleFromSource([]byte(`(module $test
|
||||
m, err = r.InstantiateModuleFromSource([]byte(`(module $test
|
||||
;; these imports return the input param
|
||||
(import "host" "identity_f32" (func $test.identity_f32 (param f32) (result f32)))
|
||||
(import "host" "identity_f64" (func $test.identity_f64 (param f64) (result f64)))
|
||||
|
||||
@@ -56,12 +56,12 @@ func testSignExtensionOps(t *testing.T, newRuntimeConfig func() *wazero.RuntimeC
|
||||
t.Run("disabled", func(t *testing.T) {
|
||||
// Sign-extension is disabled by default.
|
||||
r := wazero.NewRuntimeWithConfig(newRuntimeConfig())
|
||||
_, err := r.NewModuleFromSource(signExtend)
|
||||
_, err := r.InstantiateModuleFromSource(signExtend)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("enabled", func(t *testing.T) {
|
||||
r := wazero.NewRuntimeWithConfig(newRuntimeConfig().WithFeatureSignExtensionOps(true))
|
||||
module, err := r.NewModuleFromSource(signExtend)
|
||||
module, err := r.InstantiateModuleFromSource(signExtend)
|
||||
require.NoError(t, err)
|
||||
|
||||
signExtend32from8Name, signExtend32from16Name := "i32.extend8_s", "i32.extend16_s"
|
||||
|
||||
@@ -243,7 +243,7 @@ func wasmtimeGoFacIterInvoke(b *testing.B) {
|
||||
func newWazeroFacIterBench(engine *wazero.RuntimeConfig) (wasm.Function, error) {
|
||||
r := wazero.NewRuntimeWithConfig(engine)
|
||||
|
||||
m, err := r.NewModuleFromSource(facWasm)
|
||||
m, err := r.InstantiateModuleFromSource(facWasm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -108,11 +108,11 @@ func TestExampleUpToDate(t *testing.T) {
|
||||
r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfig().WithFeatureSignExtensionOps(true))
|
||||
|
||||
// Add WASI to satisfy import tests
|
||||
_, err := r.NewHostModuleFromConfig(wazero.WASISnapshotPreview1())
|
||||
_, err := r.InstantiateModule(wazero.WASISnapshotPreview1())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Decode and instantiate the module
|
||||
module, err := r.NewModuleFromSource(exampleBinary)
|
||||
module, err := r.InstantiateModuleFromSource(exampleBinary)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Call the add function as a smoke test
|
||||
|
||||
14
wasi.go
14
wasi.go
@@ -5,6 +5,7 @@ import (
|
||||
"io"
|
||||
|
||||
internalwasi "github.com/tetratelabs/wazero/internal/wasi"
|
||||
internalwasm "github.com/tetratelabs/wazero/internal/wasm"
|
||||
"github.com/tetratelabs/wazero/wasi"
|
||||
"github.com/tetratelabs/wazero/wasm"
|
||||
)
|
||||
@@ -32,12 +33,12 @@ type WASIConfig struct {
|
||||
}
|
||||
|
||||
// WASISnapshotPreview1 are functions importable as the module name wasi.ModuleSnapshotPreview1
|
||||
func WASISnapshotPreview1() *HostModuleConfig {
|
||||
func WASISnapshotPreview1() *Module {
|
||||
return WASISnapshotPreview1WithConfig(&WASIConfig{})
|
||||
}
|
||||
|
||||
// WASISnapshotPreview1WithConfig are functions importable as the module name wasi.ModuleSnapshotPreview1
|
||||
func WASISnapshotPreview1WithConfig(c *WASIConfig) *HostModuleConfig {
|
||||
func WASISnapshotPreview1WithConfig(c *WASIConfig) *Module {
|
||||
// TODO: delete the internalwasi.Option types as they are not accessible as they are internal!
|
||||
var opts []internalwasi.Option
|
||||
if c.Stdin != nil {
|
||||
@@ -72,10 +73,11 @@ func WASISnapshotPreview1WithConfig(c *WASIConfig) *HostModuleConfig {
|
||||
opts = append(opts, internalwasi.Preopen(k, v))
|
||||
}
|
||||
}
|
||||
return &HostModuleConfig{
|
||||
Name: wasi.ModuleSnapshotPreview1,
|
||||
Functions: internalwasi.SnapshotPreview1Functions(opts...),
|
||||
m, err := internalwasm.NewHostModule(wasi.ModuleSnapshotPreview1, internalwasi.SnapshotPreview1Functions(opts...))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("BUG: %w", err))
|
||||
}
|
||||
return &Module{name: wasi.ModuleSnapshotPreview1, module: m}
|
||||
}
|
||||
|
||||
// StartWASICommandFromSource instantiates a module from the WebAssembly 1.0 (20191205) text or binary source or errs if
|
||||
@@ -114,7 +116,7 @@ func StartWASICommandFromSource(r Runtime, source []byte) (wasm.Module, error) {
|
||||
// Note: The wasm.Functions return value does not restrict exports after "_start" as allowed in the specification.
|
||||
// Note: All TinyGo Wasm are WASI commands. They initialize memory on "_start" and import "fd_write" to implement panic.
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/design/application-abi.md#current-unstable-abi
|
||||
func StartWASICommand(r Runtime, module *DecodedModule) (wasm.Module, error) {
|
||||
func StartWASICommand(r Runtime, module *Module) (wasm.Module, error) {
|
||||
if err := internalwasi.ValidateWASICommand(module.module, module.name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@ func TestStartWASICommand_UsesStoreContext(t *testing.T) {
|
||||
require.Equal(t, config.ctx, ctx.Context())
|
||||
}
|
||||
|
||||
_, err := r.NewHostModuleFromConfig(&HostModuleConfig{Functions: map[string]interface{}{"start": start}})
|
||||
_, err := r.NewModuleBuilder("").ExportFunction("start", start).Instantiate()
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = r.NewHostModuleFromConfig(WASISnapshotPreview1())
|
||||
_, err = r.InstantiateModule(WASISnapshotPreview1())
|
||||
require.NoError(t, err)
|
||||
|
||||
decoded, err := r.DecodeModule([]byte(`(module $wasi_test.go
|
||||
|
||||
63
wasm.go
63
wasm.go
@@ -15,10 +15,20 @@ import (
|
||||
// Ex.
|
||||
// r := wazero.NewRuntime()
|
||||
// decoded, _ := r.DecodeModule(source)
|
||||
// module, _ := r.NewModule(decoded)
|
||||
// module, _ := r.InstantiateModule(decoded)
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/
|
||||
type Runtime interface {
|
||||
// NewModuleBuilder lets you create modules out of functions defined in Go.
|
||||
//
|
||||
// Ex. Below defines and instantiates a module named "env" with one function:
|
||||
//
|
||||
// hello := func() {
|
||||
// fmt.Fprintln(stdout, "hello!")
|
||||
// }
|
||||
// _, err := r.NewModuleBuilder("env").ExportFunction("hello", hello).Instantiate()
|
||||
NewModuleBuilder(moduleName string) ModuleBuilder
|
||||
|
||||
// Module returns exports from an instantiated module or nil if there aren't any.
|
||||
Module(moduleName string) wasm.Module
|
||||
|
||||
@@ -26,33 +36,27 @@ type Runtime interface {
|
||||
//
|
||||
// Note: the name defaults to what was decoded from the custom name section.
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0
|
||||
DecodeModule(source []byte) (*DecodedModule, error)
|
||||
DecodeModule(source []byte) (*Module, error)
|
||||
|
||||
// NewModuleFromSource instantiates a module from the WebAssembly 1.0 (20191205) text or binary source or errs if
|
||||
// invalid.
|
||||
// InstantiateModuleFromSource instantiates a module from the WebAssembly 1.0 (20191205) text or binary source or
|
||||
// errs if invalid.
|
||||
//
|
||||
// Ex.
|
||||
// module, _ := wazero.NewRuntime().NewModuleFromSource(source)
|
||||
// module, _ := wazero.NewRuntime().InstantiateModuleFromSource(source)
|
||||
//
|
||||
// Note: This is a convenience utility that chains DecodeModule with NewModule. To instantiate the same source
|
||||
// multiple times, use DecodeModule as NewModule avoids redundant decoding and/or compilation.
|
||||
NewModuleFromSource(source []byte) (wasm.Module, error)
|
||||
// Note: This is a convenience utility that chains DecodeModule with InstantiateModule. To instantiate the same source
|
||||
// multiple times, use DecodeModule as InstantiateModule avoids redundant decoding and/or compilation.
|
||||
InstantiateModuleFromSource(source []byte) (wasm.Module, error)
|
||||
|
||||
// NewModule instantiates the module namespace or errs if the configuration was invalid.
|
||||
// InstantiateModule instantiates the module namespace or errs if the configuration was invalid.
|
||||
//
|
||||
// Ex.
|
||||
// r := wazero.NewRuntime()
|
||||
// decoded, _ := r.DecodeModule(source)
|
||||
// module, _ := r.NewModule(decoded)
|
||||
// module, _ := r.InstantiateModule(decoded)
|
||||
//
|
||||
// Note: The last value of RuntimeConfig.WithContext is used for any WebAssembly 1.0 (20191205) Start ExportedFunction.
|
||||
NewModule(module *DecodedModule) (wasm.Module, error)
|
||||
|
||||
// NewHostModuleFromConfig instantiates the module namespace from the host or errs if the configuration was invalid.
|
||||
//
|
||||
// Ex.
|
||||
// module, _ := wazero.NewRuntime().NewHostModuleFromConfig(wazero.WASISnapshotPreview1())
|
||||
NewHostModuleFromConfig(hostModule *HostModuleConfig) (wasm.Module, error)
|
||||
InstantiateModule(module *Module) (wasm.Module, error)
|
||||
|
||||
// TODO: RemoveModule
|
||||
}
|
||||
@@ -75,13 +79,13 @@ type runtime struct {
|
||||
enabledFeatures internalwasm.Features
|
||||
}
|
||||
|
||||
// Module implements wasm.Store Module
|
||||
// Module implements Runtime.Module
|
||||
func (r *runtime) Module(moduleName string) wasm.Module {
|
||||
return r.store.Module(moduleName)
|
||||
}
|
||||
|
||||
// DecodeModule implements Runtime.DecodeModule
|
||||
func (r *runtime) DecodeModule(source []byte) (*DecodedModule, error) {
|
||||
func (r *runtime) DecodeModule(source []byte) (*Module, error) {
|
||||
if source == nil {
|
||||
return nil, errors.New("source == nil")
|
||||
}
|
||||
@@ -107,7 +111,7 @@ func (r *runtime) DecodeModule(source []byte) (*DecodedModule, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := &DecodedModule{module: internal}
|
||||
result := &Module{module: internal}
|
||||
if internal.NameSection != nil {
|
||||
result.name = internal.NameSection.ModuleName
|
||||
}
|
||||
@@ -115,25 +119,16 @@ func (r *runtime) DecodeModule(source []byte) (*DecodedModule, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// NewModuleFromSource implements Runtime.NewModuleFromSource
|
||||
func (r *runtime) NewModuleFromSource(source []byte) (wasm.Module, error) {
|
||||
// InstantiateModuleFromSource implements Runtime.InstantiateModuleFromSource
|
||||
func (r *runtime) InstantiateModuleFromSource(source []byte) (wasm.Module, error) {
|
||||
if decoded, err := r.DecodeModule(source); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return r.NewModule(decoded)
|
||||
return r.InstantiateModule(decoded)
|
||||
}
|
||||
}
|
||||
|
||||
// NewModule implements Runtime.NewModule
|
||||
func (r *runtime) NewModule(module *DecodedModule) (wasm.Module, error) {
|
||||
// InstantiateModule implements Runtime.InstantiateModule
|
||||
func (r *runtime) InstantiateModule(module *Module) (wasm.Module, error) {
|
||||
return r.store.Instantiate(module.module, module.name)
|
||||
}
|
||||
|
||||
// NewHostModuleFromConfig implements Runtime.NewHostModuleFromConfig
|
||||
func (r *runtime) NewHostModuleFromConfig(hostModule *HostModuleConfig) (wasm.Module, error) {
|
||||
if m, err := internalwasm.NewHostModule(hostModule.Name, hostModule.Functions); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return r.store.Instantiate(m, hostModule.Name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ type Module interface {
|
||||
ExportedGlobal(name string) Global
|
||||
}
|
||||
|
||||
// Function is a WebAssembly 1.0 (20191205) function exported from an instantiated module (wazero.Runtime NewModule).
|
||||
// Function is a WebAssembly 1.0 (20191205) function exported from an instantiated module (wazero.Runtime InstantiateModule).
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-func
|
||||
type Function interface {
|
||||
// ParamTypes are the possibly empty sequence of value types accepted by a function with this signature.
|
||||
@@ -122,7 +122,7 @@ type Function interface {
|
||||
Call(ctx Module, params ...uint64) ([]uint64, error)
|
||||
}
|
||||
|
||||
// Global is a WebAssembly 1.0 (20191205) global exported from an instantiated module (wazero.Runtime NewModule).
|
||||
// Global is a WebAssembly 1.0 (20191205) global exported from an instantiated module (wazero.Runtime InstantiateModule).
|
||||
//
|
||||
// Ex. If the value is not mutable, you can read it once:
|
||||
//
|
||||
|
||||
20
wasm_test.go
20
wasm_test.go
@@ -103,12 +103,12 @@ func TestDecodedModule_WithName(t *testing.T) {
|
||||
|
||||
// Use the same runtime to instantiate multiple modules
|
||||
internal := r.(*runtime).store
|
||||
m1, err := r.NewModule(base.WithName("1"))
|
||||
m1, err := r.InstantiateModule(base.WithName("1"))
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, internal.Module("0"))
|
||||
require.Equal(t, internal.Module("1"), m1)
|
||||
|
||||
m2, err := r.NewModule(base.WithName("2"))
|
||||
m2, err := r.InstantiateModule(base.WithName("2"))
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, internal.Module("0"))
|
||||
require.Equal(t, internal.Module("2"), m2)
|
||||
@@ -142,7 +142,7 @@ func TestModule_Memory(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Instantiate the module and get the export of the above hostFn
|
||||
module, err := r.NewModule(decoded)
|
||||
module, err := r.InstantiateModule(decoded)
|
||||
require.NoError(t, err)
|
||||
|
||||
mem := module.ExportedMemory("memory")
|
||||
@@ -216,7 +216,7 @@ func TestModule_Global(t *testing.T) {
|
||||
r := NewRuntime()
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Instantiate the module and get the export of the above global
|
||||
module, err := r.NewModule(&DecodedModule{module: tc.module})
|
||||
module, err := r.InstantiateModule(&Module{module: tc.module})
|
||||
require.NoError(t, err)
|
||||
|
||||
global := module.ExportedGlobal("global")
|
||||
@@ -279,7 +279,7 @@ func TestFunction_Context(t *testing.T) {
|
||||
decoded, err := r.DecodeModule(source)
|
||||
require.NoError(t, err)
|
||||
|
||||
module, err := r.NewModule(decoded)
|
||||
module, err := r.InstantiateModule(decoded)
|
||||
require.NoError(t, err)
|
||||
|
||||
// This fails if the function wasn't invoked, or had an unexpected context.
|
||||
@@ -303,7 +303,7 @@ func TestRuntime_NewModule_UsesStoreContext(t *testing.T) {
|
||||
require.Equal(t, runtimeCtx, ctx.Context())
|
||||
}
|
||||
|
||||
_, err := r.NewHostModuleFromConfig(&HostModuleConfig{Functions: map[string]interface{}{"start": start}})
|
||||
_, err := r.NewModuleBuilder("").ExportFunction("start", start).Instantiate()
|
||||
require.NoError(t, err)
|
||||
|
||||
decoded, err := r.DecodeModule([]byte(`(module $runtime_test.go
|
||||
@@ -313,16 +313,14 @@ func TestRuntime_NewModule_UsesStoreContext(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Instantiate the module, which calls the start function. This will fail if the context wasn't as intended.
|
||||
_, err = r.NewModule(decoded)
|
||||
_, err = r.InstantiateModule(decoded)
|
||||
require.NoError(t, err)
|
||||
require.True(t, calledStart)
|
||||
}
|
||||
|
||||
// requireImportAndExportFunction re-module a host function because only host functions can see the propagated context.
|
||||
// requireImportAndExportFunction re-exports a host function because only host functions can see the propagated context.
|
||||
func requireImportAndExportFunction(t *testing.T, r Runtime, hostFn func(ctx wasm.Module) uint64, functionName string) []byte {
|
||||
_, err := r.NewHostModuleFromConfig(&HostModuleConfig{
|
||||
Name: "host", Functions: map[string]interface{}{functionName: hostFn},
|
||||
})
|
||||
_, err := r.NewModuleBuilder("host").ExportFunction(functionName, hostFn).Instantiate()
|
||||
require.NoError(t, err)
|
||||
|
||||
return []byte(fmt.Sprintf(
|
||||
|
||||
Reference in New Issue
Block a user