From 50d9fa58a1778ffc36d2eef1d926ddcb1ae911e1 Mon Sep 17 00:00:00 2001 From: Crypt Keeper <64215+codefromthecrypt@users.noreply.github.com> Date: Wed, 9 Mar 2022 10:39:13 +0800 Subject: [PATCH] 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 --- README.md | 2 +- builder.go | 124 ++++++++++++++++++++ builder_test.go | 208 ++++++++++++++++++++++++++++++++++ config.go | 51 +-------- examples/add_test.go | 2 +- examples/fibonacci_test.go | 2 +- examples/file_system_test.go | 2 +- examples/host_func_test.go | 5 +- examples/simple_test.go | 7 +- examples/stdio_test.go | 2 +- examples/wasi_test.go | 10 +- internal/wasm/host_test.go | 43 +++---- tests/bench/bench_test.go | 5 +- tests/engine/adhoc_test.go | 28 ++--- tests/post1_0/post1_0_test.go | 4 +- vs/bench_fac_iter_test.go | 2 +- vs/codec_test.go | 4 +- wasi.go | 14 ++- wasi_test.go | 4 +- wasm.go | 63 +++++----- wasm/wasm.go | 4 +- wasm_test.go | 20 ++-- 22 files changed, 443 insertions(+), 163 deletions(-) create mode 100644 builder.go create mode 100644 builder_test.go diff --git a/README.md b/README.md index f42c7362..2478fced 100644 --- a/README.md +++ b/README.md @@ -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)) diff --git a/builder.go b/builder.go new file mode 100644 index 00000000..1836f90a --- /dev/null +++ b/builder.go @@ -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) + } +} diff --git a/builder_test.go b/builder_test.go new file mode 100644 index 00000000..e66a49d4 --- /dev/null +++ b/builder_test.go @@ -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()) + } +} diff --git a/config.go b/config.go index b230463f..9a34bc6d 100644 --- a/config.go +++ b/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} } diff --git a/examples/add_test.go b/examples/add_test.go index 80fb2349..733ef775 100644 --- a/examples/add_test.go +++ b/examples/add_test.go @@ -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) diff --git a/examples/fibonacci_test.go b/examples/fibonacci_test.go index a5a1c6bc..0bf5b78f 100644 --- a/examples/fibonacci_test.go +++ b/examples/fibonacci_test.go @@ -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) diff --git a/examples/file_system_test.go b/examples/file_system_test.go index 125eda47..37ef87fe 100644 --- a/examples/file_system_test.go +++ b/examples/file_system_test.go @@ -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. diff --git a/examples/host_func_test.go b/examples/host_func_test.go index eee7f79e..1dd60f31 100644 --- a/examples/host_func_test.go +++ b/examples/host_func_test.go @@ -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) diff --git a/examples/simple_test.go b/examples/simple_test.go index c824e065..20b7e6c9 100644 --- a/examples/simple_test.go +++ b/examples/simple_test.go @@ -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) )`)) diff --git a/examples/stdio_test.go b/examples/stdio_test.go index 746ccfb2..22158ae2 100644 --- a/examples/stdio_test.go +++ b/examples/stdio_test.go @@ -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 diff --git a/examples/wasi_test.go b/examples/wasi_test.go index 92c52041..2dbc2c95 100644 --- a/examples/wasi_test.go +++ b/examples/wasi_test.go @@ -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)) diff --git a/internal/wasm/host_test.go b/internal/wasm/host_test.go index 029d3599..e1ee7ff4 100644 --- a/internal/wasm/host_test.go +++ b/internal/wasm/host_test.go @@ -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"}) diff --git a/tests/bench/bench_test.go b/tests/bench/bench_test.go index cab9e787..d85f2483 100644 --- a/tests/bench/bench_test.go +++ b/tests/bench/bench_test.go @@ -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) } diff --git a/tests/engine/adhoc_test.go b/tests/engine/adhoc_test.go index 7f043af3..d4c5e02a 100644 --- a/tests/engine/adhoc_test.go +++ b/tests/engine/adhoc_test.go @@ -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))) diff --git a/tests/post1_0/post1_0_test.go b/tests/post1_0/post1_0_test.go index 040d6be5..ea8fc547 100644 --- a/tests/post1_0/post1_0_test.go +++ b/tests/post1_0/post1_0_test.go @@ -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" diff --git a/vs/bench_fac_iter_test.go b/vs/bench_fac_iter_test.go index f4565f91..cab83c19 100644 --- a/vs/bench_fac_iter_test.go +++ b/vs/bench_fac_iter_test.go @@ -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 } diff --git a/vs/codec_test.go b/vs/codec_test.go index ac6e5d33..21e5200e 100644 --- a/vs/codec_test.go +++ b/vs/codec_test.go @@ -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 diff --git a/wasi.go b/wasi.go index b09ef55c..f312f9e9 100644 --- a/wasi.go +++ b/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 } diff --git a/wasi_test.go b/wasi_test.go index e5acbe9c..9b8096c1 100644 --- a/wasi_test.go +++ b/wasi_test.go @@ -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 diff --git a/wasm.go b/wasm.go index 1ac59443..92903302 100644 --- a/wasm.go +++ b/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) - } -} diff --git a/wasm/wasm.go b/wasm/wasm.go index 92f49b8c..ab9ee4b4 100644 --- a/wasm/wasm.go +++ b/wasm/wasm.go @@ -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: // diff --git a/wasm_test.go b/wasm_test.go index 7d2e7213..638410c5 100644 --- a/wasm_test.go +++ b/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(