diff --git a/builder.go b/builder.go index 82eecd30..caf08d3c 100644 --- a/builder.go +++ b/builder.go @@ -146,7 +146,7 @@ type HostFunctionBuilder interface { // defer r.Close(ctx) // This closes everything this Runtime created. // // hello := func() { -// fmt.Fprintln(stdout, "hello!") +// println("hello!") // } // env, _ := r.NewHostModuleBuilder("env"). // NewFunctionBuilder().WithFunc(hello).Export("hello"). @@ -170,8 +170,8 @@ type HostFunctionBuilder interface { // chaining. // - methods do not return errors, to allow chaining. Any validation errors // are deferred until Compile. -// - Insertion order is not retained. Anything defined by this builder is -// sorted lexicographically on Compile. +// - Functions are indexed in order of calls to NewFunctionBuilder as +// insertion ordering is needed by ABI such as Emscripten (invoke_*). type HostModuleBuilder interface { // Note: until golang/go#5860, we can't use example tests to embed code in interface godocs. @@ -191,7 +191,7 @@ type HostModuleBuilder interface { // defer r.Close(ctx) // This closes everything this Runtime created. // // hello := func() { - // fmt.Fprintln(stdout, "hello!") + // println("hello!") // } // env, _ := r.NewHostModuleBuilder("env"). // NewFunctionBuilder().WithFunc(hello).Export("hello"). @@ -207,19 +207,18 @@ type HostModuleBuilder interface { // hostModuleBuilder implements HostModuleBuilder type hostModuleBuilder struct { - r *runtime - moduleName string - nameToGoFunc map[string]interface{} - funcToNames map[string]*wasm.HostFuncNames + r *runtime + moduleName string + exportNames []string + nameToHostFunc map[string]*wasm.HostFunc } // NewHostModuleBuilder implements Runtime.NewHostModuleBuilder func (r *runtime) NewHostModuleBuilder(moduleName string) HostModuleBuilder { return &hostModuleBuilder{ - r: r, - moduleName: moduleName, - nameToGoFunc: map[string]interface{}{}, - funcToNames: map[string]*wasm.HostFuncNames{}, + r: r, + moduleName: moduleName, + nameToHostFunc: map[string]*wasm.HostFunc{}, } } @@ -234,21 +233,13 @@ type hostFunctionBuilder struct { // WithGoFunction implements HostFunctionBuilder.WithGoFunction func (h *hostFunctionBuilder) WithGoFunction(fn api.GoFunction, params, results []api.ValueType) HostFunctionBuilder { - h.fn = &wasm.HostFunc{ - ParamTypes: params, - ResultTypes: results, - Code: wasm.Code{GoFunc: fn}, - } + h.fn = &wasm.HostFunc{ParamTypes: params, ResultTypes: results, Code: wasm.Code{GoFunc: fn}} return h } // WithGoModuleFunction implements HostFunctionBuilder.WithGoModuleFunction func (h *hostFunctionBuilder) WithGoModuleFunction(fn api.GoModuleFunction, params, results []api.ValueType) HostFunctionBuilder { - h.fn = &wasm.HostFunc{ - ParamTypes: params, - ResultTypes: results, - Code: wasm.Code{GoFunc: fn}, - } + h.fn = &wasm.HostFunc{ParamTypes: params, ResultTypes: results, Code: wasm.Code{GoFunc: fn}} return h } @@ -278,30 +269,35 @@ func (h *hostFunctionBuilder) WithResultNames(names ...string) HostFunctionBuild // Export implements HostFunctionBuilder.Export func (h *hostFunctionBuilder) Export(exportName string) HostModuleBuilder { - if h.name == "" { - h.name = exportName - } - names := &wasm.HostFuncNames{ - Name: h.name, - ParamNames: h.paramNames, - ResultNames: h.resultNames, - } + var hostFn *wasm.HostFunc if fn, ok := h.fn.(*wasm.HostFunc); ok { - if fn.Name == "" { - fn.Name = names.Name - } - fn.ParamNames = names.ParamNames - fn.ResultNames = names.ResultNames - fn.ExportNames = []string{exportName} + hostFn = fn + } else { + hostFn = &wasm.HostFunc{Code: wasm.Code{GoFunc: h.fn}} } - h.b.nameToGoFunc[exportName] = h.fn - h.b.funcToNames[exportName] = names + + // Assign any names from the builder + hostFn.ExportName = exportName + if h.name != "" { + hostFn.Name = h.name + } + if len(h.paramNames) != 0 { + hostFn.ParamNames = h.paramNames + } + if len(h.resultNames) != 0 { + hostFn.ResultNames = h.resultNames + } + + h.b.ExportHostFunc(hostFn) return h.b } // ExportHostFunc implements wasm.HostFuncExporter func (b *hostModuleBuilder) ExportHostFunc(fn *wasm.HostFunc) { - b.nameToGoFunc[fn.ExportNames[0]] = fn + if _, ok := b.nameToHostFunc[fn.ExportName]; !ok { // add a new name + b.exportNames = append(b.exportNames, fn.ExportName) + } + b.nameToHostFunc[fn.ExportName] = fn } // NewFunctionBuilder implements HostModuleBuilder.NewFunctionBuilder @@ -311,7 +307,7 @@ func (b *hostModuleBuilder) NewFunctionBuilder() HostFunctionBuilder { // Compile implements HostModuleBuilder.Compile func (b *hostModuleBuilder) Compile(ctx context.Context) (CompiledModule, error) { - module, err := wasm.NewHostModule(b.moduleName, b.nameToGoFunc, b.funcToNames, b.r.enabledFeatures) + module, err := wasm.NewHostModule(b.moduleName, b.exportNames, b.nameToHostFunc, b.r.enabledFeatures) if err != nil { return nil, err } else if err = module.Validate(b.r.enabledFeatures); err != nil { diff --git a/builder_test.go b/builder_test.go index 7deee604..2e80fe48 100644 --- a/builder_test.go +++ b/builder_test.go @@ -159,21 +159,21 @@ func TestNewHostModuleBuilder_Compile(t *testing.T) { }, expected: &wasm.Module{ TypeSection: []wasm.FunctionType{ - {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}}, {Params: []api.ValueType{i64}, Results: []api.ValueType{i32}}, + {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}}, }, FunctionSection: []wasm.Index{0, 1}, - CodeSection: []wasm.Code{wasm.MustParseGoReflectFuncCode(uint32_uint32), wasm.MustParseGoReflectFuncCode(uint64_uint32)}, + CodeSection: []wasm.Code{wasm.MustParseGoReflectFuncCode(uint64_uint32), wasm.MustParseGoReflectFuncCode(uint32_uint32)}, ExportSection: []wasm.Export{ - {Name: "1", Type: wasm.ExternTypeFunc, Index: 0}, - {Name: "2", Type: wasm.ExternTypeFunc, Index: 1}, + {Name: "2", Type: wasm.ExternTypeFunc, Index: 0}, + {Name: "1", Type: wasm.ExternTypeFunc, Index: 1}, }, Exports: map[string]*wasm.Export{ - "1": {Name: "1", Type: wasm.ExternTypeFunc, Index: 0}, - "2": {Name: "2", Type: wasm.ExternTypeFunc, Index: 1}, + "2": {Name: "2", Type: wasm.ExternTypeFunc, Index: 0}, + "1": {Name: "1", Type: wasm.ExternTypeFunc, Index: 1}, }, NameSection: &wasm.NameSection{ - FunctionNames: wasm.NameMap{{Index: 0, Name: "1"}, {Index: 1, Name: "2"}}, + FunctionNames: wasm.NameMap{{Index: 0, Name: "2"}, {Index: 1, Name: "1"}}, ModuleName: "host", }, }, @@ -269,7 +269,7 @@ func TestNewHostModuleBuilder_Compile(t *testing.T) { { name: "WithGoFunction twice", input: func(r Runtime) HostModuleBuilder { - // Intentionally out of order + // Intentionally not in lexicographic order return r.NewHostModuleBuilder("host"). NewFunctionBuilder(). WithGoFunction(gofunc2, []api.ValueType{i64}, []api.ValueType{i32}). @@ -280,24 +280,24 @@ func TestNewHostModuleBuilder_Compile(t *testing.T) { }, expected: &wasm.Module{ TypeSection: []wasm.FunctionType{ - {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}}, {Params: []api.ValueType{i64}, Results: []api.ValueType{i32}}, + {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}}, }, FunctionSection: []wasm.Index{0, 1}, CodeSection: []wasm.Code{ - {GoFunc: gofunc1}, {GoFunc: gofunc2}, + {GoFunc: gofunc1}, }, ExportSection: []wasm.Export{ - {Name: "1", Type: wasm.ExternTypeFunc, Index: 0}, - {Name: "2", Type: wasm.ExternTypeFunc, Index: 1}, + {Name: "2", Type: wasm.ExternTypeFunc, Index: 0}, + {Name: "1", Type: wasm.ExternTypeFunc, Index: 1}, }, Exports: map[string]*wasm.Export{ - "1": {Name: "1", Type: wasm.ExternTypeFunc, Index: 0}, - "2": {Name: "2", Type: wasm.ExternTypeFunc, Index: 1}, + "2": {Name: "2", Type: wasm.ExternTypeFunc, Index: 0}, + "1": {Name: "1", Type: wasm.ExternTypeFunc, Index: 1}, }, NameSection: &wasm.NameSection{ - FunctionNames: wasm.NameMap{{Index: 0, Name: "1"}, {Index: 1, Name: "2"}}, + FunctionNames: wasm.NameMap{{Index: 0, Name: "2"}, {Index: 1, Name: "1"}}, ModuleName: "host", }, }, @@ -342,18 +342,13 @@ func TestNewHostModuleBuilder_Compile_Errors(t *testing.T) { expectedErr string }{ { - name: "error compiling", // should fail due to missing result. + name: "error compiling", // should fail due to invalid param. input: func(rt Runtime) HostModuleBuilder { return rt.NewHostModuleBuilder("host").NewFunctionBuilder(). - WithFunc(&wasm.HostFunc{ - ExportNames: []string{"fn"}, - ResultTypes: []wasm.ValueType{wasm.ValueTypeI32}, - Code: wasm.Code{Body: []byte{wasm.OpcodeEnd}}, - }).Export("fn") + WithFunc(&wasm.HostFunc{ExportName: "fn", Code: wasm.Code{GoFunc: func(string) {}}}). + Export("fn") }, - expectedErr: `invalid function[0] export["fn"]: not enough results - have () - want (i32)`, + expectedErr: `func[host.fn] param[0] is unsupported: string`, }, } diff --git a/imports/assemblyscript/assemblyscript.go b/imports/assemblyscript/assemblyscript.go index 92226ce6..19b465b8 100644 --- a/imports/assemblyscript/assemblyscript.go +++ b/imports/assemblyscript/assemblyscript.go @@ -138,11 +138,11 @@ func (e *functionExporter) ExportFunctions(builder wazero.HostModuleBuilder) { // // See https://github.com/AssemblyScript/assemblyscript/blob/v0.26.7/std/assembly/builtins.ts#L2508 var abortMessageEnabled = &wasm.HostFunc{ - ExportNames: []string{AbortName}, - Name: "~lib/builtins/abort", - ParamTypes: []api.ValueType{i32, i32, i32, i32}, - ParamNames: []string{"message", "fileName", "lineNumber", "columnNumber"}, - Code: wasm.Code{GoFunc: api.GoModuleFunc(abortWithMessage)}, + ExportName: AbortName, + Name: "~lib/builtins/abort", + ParamTypes: []api.ValueType{i32, i32, i32, i32}, + ParamNames: []string{"message", "fileName", "lineNumber", "columnNumber"}, + Code: wasm.Code{GoFunc: api.GoModuleFunc(abortWithMessage)}, } var abortMessageDisabled = abortMessageEnabled.WithGoModuleFunc(abort) @@ -185,10 +185,10 @@ var traceDisabled = traceStdout.WithGoModuleFunc(func(context.Context, api.Modul // traceStdout implements trace to the configured Stdout. var traceStdout = &wasm.HostFunc{ - ExportNames: []string{TraceName}, - Name: "~lib/builtins/trace", - ParamTypes: []api.ValueType{i32, i32, f64, f64, f64, f64, f64}, - ParamNames: []string{"message", "nArgs", "arg0", "arg1", "arg2", "arg3", "arg4"}, + ExportName: TraceName, + Name: "~lib/builtins/trace", + ParamTypes: []api.ValueType{i32, i32, f64, f64, f64, f64, f64}, + ParamNames: []string{"message", "nArgs", "arg0", "arg1", "arg2", "arg3", "arg4"}, Code: wasm.Code{ GoFunc: api.GoModuleFunc(func(_ context.Context, mod api.Module, stack []uint64) { fsc := mod.(*wasm.ModuleInstance).Sys.FS() @@ -270,7 +270,7 @@ func formatFloat(f float64) string { // // See https://github.com/AssemblyScript/assemblyscript/blob/v0.26.7/std/assembly/builtins.ts#L2531 var seed = &wasm.HostFunc{ - ExportNames: []string{SeedName}, + ExportName: SeedName, Name: "~lib/builtins/seed", ResultTypes: []api.ValueType{f64}, ResultNames: []string{"rand"}, diff --git a/imports/emscripten/emscripten.go b/imports/emscripten/emscripten.go index b013572c..a74034b9 100644 --- a/imports/emscripten/emscripten.go +++ b/imports/emscripten/emscripten.go @@ -94,11 +94,10 @@ func (functionExporter) ExportFunctions(builder wazero.HostModuleBuilder) { const functionNotifyMemoryGrowth = "emscripten_notify_memory_growth" var notifyMemoryGrowth = &wasm.HostFunc{ - ExportNames: []string{functionNotifyMemoryGrowth}, - Name: functionNotifyMemoryGrowth, - ParamTypes: []wasm.ValueType{wasm.ValueTypeI32}, - ParamNames: []string{"memory_index"}, - Code: wasm.Code{GoFunc: api.GoModuleFunc(func(context.Context, api.Module, []uint64) {})}, + ExportName: functionNotifyMemoryGrowth, + ParamTypes: []wasm.ValueType{wasm.ValueTypeI32}, + ParamNames: []string{"memory_index"}, + Code: wasm.Code{GoFunc: api.GoModuleFunc(func(context.Context, api.Module, []uint64) {})}, } // All `invoke_` functions have an initial "index" parameter of @@ -134,8 +133,7 @@ const ( ) var invokeI = &wasm.HostFunc{ - ExportNames: []string{functionInvokeI}, - Name: functionInvokeI, + ExportName: functionInvokeI, ParamTypes: []api.ValueType{i32}, ParamNames: []string{"index"}, ResultTypes: []api.ValueType{i32}, @@ -151,8 +149,7 @@ func invokeIFn(ctx context.Context, mod api.Module, stack []uint64) { } var invokeIi = &wasm.HostFunc{ - ExportNames: []string{functionInvokeIi}, - Name: functionInvokeIi, + ExportName: functionInvokeIi, ParamTypes: []api.ValueType{i32, i32}, ParamNames: []string{"index", "a1"}, ResultTypes: []api.ValueType{i32}, @@ -168,8 +165,7 @@ func invokeIiFn(ctx context.Context, mod api.Module, stack []uint64) { } var invokeIii = &wasm.HostFunc{ - ExportNames: []string{functionInvokeIii}, - Name: functionInvokeIii, + ExportName: functionInvokeIii, ParamTypes: []api.ValueType{i32, i32, i32}, ParamNames: []string{"index", "a1", "a2"}, ResultTypes: []api.ValueType{i32}, @@ -185,8 +181,7 @@ func invokeIiiFn(ctx context.Context, mod api.Module, stack []uint64) { } var invokeIiii = &wasm.HostFunc{ - ExportNames: []string{functionInvokeIiii}, - Name: functionInvokeIiii, + ExportName: functionInvokeIiii, ParamTypes: []api.ValueType{i32, i32, i32, i32}, ParamNames: []string{"index", "a1", "a2", "a3"}, ResultTypes: []api.ValueType{i32}, @@ -202,8 +197,7 @@ func invokeIiiiFn(ctx context.Context, mod api.Module, stack []uint64) { } var invokeIiiii = &wasm.HostFunc{ - ExportNames: []string{functionInvokeIiiii}, - Name: functionInvokeIiiii, + ExportName: functionInvokeIiiii, ParamTypes: []api.ValueType{i32, i32, i32, i32, i32}, ParamNames: []string{"index", "a1", "a2", "a3", "a4"}, ResultTypes: []api.ValueType{i32}, @@ -219,8 +213,7 @@ func invokeIiiiiFn(ctx context.Context, mod api.Module, stack []uint64) { } var invokeV = &wasm.HostFunc{ - ExportNames: []string{functionInvokeV}, - Name: functionInvokeV, + ExportName: functionInvokeV, ParamTypes: []api.ValueType{i32}, ParamNames: []string{"index"}, ResultTypes: []api.ValueType{}, @@ -235,8 +228,7 @@ func invokeVFn(ctx context.Context, mod api.Module, stack []uint64) { } var invokeVi = &wasm.HostFunc{ - ExportNames: []string{functionInvokeVi}, - Name: functionInvokeVi, + ExportName: functionInvokeVi, ParamTypes: []api.ValueType{i32, i32}, ParamNames: []string{"index", "a1"}, ResultTypes: []api.ValueType{}, @@ -251,8 +243,7 @@ func invokeViFn(ctx context.Context, mod api.Module, stack []uint64) { } var invokeVii = &wasm.HostFunc{ - ExportNames: []string{functionInvokeVii}, - Name: functionInvokeVii, + ExportName: functionInvokeVii, ParamTypes: []api.ValueType{i32, i32, i32}, ParamNames: []string{"index", "a1", "a2"}, ResultTypes: []api.ValueType{}, @@ -267,8 +258,7 @@ func invokeViiFn(ctx context.Context, mod api.Module, stack []uint64) { } var invokeViii = &wasm.HostFunc{ - ExportNames: []string{functionInvokeViii}, - Name: functionInvokeViii, + ExportName: functionInvokeViii, ParamTypes: []api.ValueType{i32, i32, i32, i32}, ParamNames: []string{"index", "a1", "a2", "a3"}, ResultTypes: []api.ValueType{}, @@ -283,8 +273,7 @@ func invokeViiiFn(ctx context.Context, mod api.Module, stack []uint64) { } var invokeViiii = &wasm.HostFunc{ - ExportNames: []string{functionInvokeViiii}, - Name: functionInvokeViiii, + ExportName: functionInvokeViiii, ParamTypes: []api.ValueType{i32, i32, i32, i32, i32}, ParamNames: []string{"index", "a1", "a2", "a3", "a4"}, ResultTypes: []api.ValueType{}, diff --git a/imports/wasi_snapshot_preview1/proc.go b/imports/wasi_snapshot_preview1/proc.go index 07c963ef..d1b5fced 100644 --- a/imports/wasi_snapshot_preview1/proc.go +++ b/imports/wasi_snapshot_preview1/proc.go @@ -19,13 +19,10 @@ import ( // // See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#proc_exit var procExit = &wasm.HostFunc{ - ExportNames: []string{wasip1.ProcExitName}, - Name: wasip1.ProcExitName, - ParamTypes: []api.ValueType{i32}, - ParamNames: []string{"rval"}, - Code: wasm.Code{ - GoFunc: api.GoModuleFunc(procExitFn), - }, + ExportName: wasip1.ProcExitName, + ParamTypes: []api.ValueType{i32}, + ParamNames: []string{"rval"}, + Code: wasm.Code{GoFunc: api.GoModuleFunc(procExitFn)}, } func procExitFn(ctx context.Context, mod api.Module, params []uint64) { diff --git a/imports/wasi_snapshot_preview1/wasi.go b/imports/wasi_snapshot_preview1/wasi.go index f8498e07..04177908 100644 --- a/imports/wasi_snapshot_preview1/wasi.go +++ b/imports/wasi_snapshot_preview1/wasi.go @@ -263,8 +263,7 @@ func newHostFunc( paramNames ...string, ) *wasm.HostFunc { return &wasm.HostFunc{ - ExportNames: []string{name}, - Name: name, + ExportName: name, ParamTypes: paramTypes, ParamNames: paramNames, ResultTypes: []api.ValueType{i32}, @@ -291,8 +290,7 @@ func (f wasiFunc) Call(ctx context.Context, mod api.Module, stack []uint64) { // stubFunction stubs for GrainLang per #271. func stubFunction(name string, paramTypes []wasm.ValueType, paramNames ...string) *wasm.HostFunc { return &wasm.HostFunc{ - Name: name, - ExportNames: []string{name}, + ExportName: name, ParamTypes: paramTypes, ParamNames: paramNames, ResultTypes: []api.ValueType{i32}, diff --git a/internal/engine/compiler/engine_test.go b/internal/engine/compiler/engine_test.go index 093fa84a..74d51342 100644 --- a/internal/engine/compiler/engine_test.go +++ b/internal/engine/compiler/engine_test.go @@ -231,7 +231,7 @@ func TestCompiler_SliceAllocatedOnHeap(t *testing.T) { const hostModuleName = "env" const hostFnName = "grow_and_shrink_goroutine_stack" - hm, err := wasm.NewHostModule(hostModuleName, map[string]interface{}{hostFnName: func() { + hostFn := func() { // This function aggressively grow the goroutine stack by recursively // calling the function many times. callNum := 1000 @@ -247,7 +247,13 @@ func TestCompiler_SliceAllocatedOnHeap(t *testing.T) { // Trigger relocation of goroutine stack because at this point we have the majority of // goroutine stack unused after recursive call. runtime.GC() - }}, map[string]*wasm.HostFuncNames{hostFnName: {}}, enabledFeatures) + } + hm, err := wasm.NewHostModule( + hostModuleName, + []string{hostFnName}, + map[string]*wasm.HostFunc{hostFnName: {ExportName: hostFnName, Code: wasm.Code{GoFunc: hostFn}}}, + enabledFeatures, + ) require.NoError(t, err) err = s.Engine.CompileModule(testCtx, hm, nil, false) diff --git a/internal/gojs/goarch/wasm.go b/internal/gojs/goarch/wasm.go index dab0a333..82b52abe 100644 --- a/internal/gojs/goarch/wasm.go +++ b/internal/gojs/goarch/wasm.go @@ -16,11 +16,10 @@ import ( // This traps (unreachable opcode) to ensure the function is never called. func StubFunction(name string) *wasm.HostFunc { return &wasm.HostFunc{ - ExportNames: []string{name}, - Name: name, - ParamTypes: []wasm.ValueType{wasm.ValueTypeI32}, - ParamNames: []string{"sp"}, - Code: wasm.Code{GoFunc: api.GoModuleFunc(func(ctx context.Context, _ api.Module, stack []uint64) {})}, + ExportName: name, + ParamTypes: []wasm.ValueType{wasm.ValueTypeI32}, + ParamNames: []string{"sp"}, + Code: wasm.Code{GoFunc: api.GoModuleFunc(func(ctx context.Context, _ api.Module, stack []uint64) {})}, } } diff --git a/internal/gojs/util/util.go b/internal/gojs/util/util.go index 21db4c92..a8bfba0d 100644 --- a/internal/gojs/util/util.go +++ b/internal/gojs/util/util.go @@ -38,11 +38,10 @@ func MustRead(mem api.Memory, funcName string, paramIdx int, offset, byteCount u func NewFunc(name string, goFunc api.GoModuleFunc) *wasm.HostFunc { return &wasm.HostFunc{ - ExportNames: []string{name}, - Name: name, - ParamTypes: []api.ValueType{api.ValueTypeI32}, - ParamNames: []string{"sp"}, - Code: wasm.Code{GoFunc: goFunc}, + ExportName: name, + ParamTypes: []api.ValueType{api.ValueTypeI32}, + ParamNames: []string{"sp"}, + Code: wasm.Code{GoFunc: goFunc}, } } diff --git a/internal/testing/enginetest/enginetest.go b/internal/testing/enginetest/enginetest.go index f02faba3..b736a962 100644 --- a/internal/testing/enginetest/enginetest.go +++ b/internal/testing/enginetest/enginetest.go @@ -58,11 +58,21 @@ func RunTestEngine_MemoryGrowInRecursiveCall(t *testing.T, et EngineTester) { const hostModuleName = "env" const hostFnName = "grow_memory" var growFn api.Function - hm, err := wasm.NewHostModule(hostModuleName, map[string]interface{}{hostFnName: func() { - // Does the recursive call into Wasm, which grows memory. - _, err := growFn.Call(context.Background()) - require.NoError(t, err) - }}, map[string]*wasm.HostFuncNames{hostFnName: {}}, enabledFeatures) + hm, err := wasm.NewHostModule( + hostModuleName, + []string{hostFnName}, + map[string]*wasm.HostFunc{ + hostFnName: { + ExportName: hostFnName, + Code: wasm.Code{GoFunc: func() { + // Does the recursive call into Wasm, which grows memory. + _, err := growFn.Call(context.Background()) + require.NoError(t, err) + }}, + }, + }, + enabledFeatures, + ) require.NoError(t, err) err = s.Engine.CompileModule(testCtx, hm, nil, false) diff --git a/internal/wasm/host.go b/internal/wasm/host.go index e0a1c190..cb4ca65e 100644 --- a/internal/wasm/host.go +++ b/internal/wasm/host.go @@ -3,7 +3,6 @@ package wasm import ( "errors" "fmt" - "sort" "github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/internal/wasmdebug" @@ -16,8 +15,8 @@ type HostFuncExporter interface { // HostFunc is a function with an inlined type, used for NewHostModule. // Any corresponding FunctionType will be reused or added to the Module. type HostFunc struct { - // ExportNames is equivalent to the same method on api.FunctionDefinition. - ExportNames []string + // ExportName is the only value returned by api.FunctionDefinition. + ExportName string // Name is equivalent to the same method on api.FunctionDefinition. Name string @@ -45,17 +44,11 @@ func (f *HostFunc) WithGoModuleFunc(fn api.GoModuleFunc) *HostFunc { return &ret } -type HostFuncNames struct { - Name string - ParamNames []string - ResultNames []string -} - // NewHostModule is defined internally for use in WASI tests and to keep the code size in the root directory small. func NewHostModule( moduleName string, - nameToGoFunc map[string]interface{}, - funcToNames map[string]*HostFuncNames, + exportNames []string, + nameToHostFunc map[string]*HostFunc, enabledFeatures api.CoreFeatures, ) (m *Module, err error) { if moduleName != "" { @@ -64,10 +57,10 @@ func NewHostModule( return nil, errors.New("a module name must not be empty") } - if exportCount := uint32(len(nameToGoFunc)); exportCount > 0 { + if exportCount := uint32(len(nameToHostFunc)); exportCount > 0 { m.ExportSection = make([]Export, 0, exportCount) m.Exports = make(map[string]*Export, exportCount) - if err = addFuncs(m, nameToGoFunc, funcToNames, enabledFeatures); err != nil { + if err = addFuncs(m, exportNames, nameToHostFunc, enabledFeatures); err != nil { return } } @@ -85,73 +78,55 @@ func NewHostModule( func addFuncs( m *Module, - nameToGoFunc map[string]interface{}, - funcToNames map[string]*HostFuncNames, + exportNames []string, + nameToHostFunc map[string]*HostFunc, enabledFeatures api.CoreFeatures, ) (err error) { if m.NameSection == nil { m.NameSection = &NameSection{} } moduleName := m.NameSection.ModuleName - nameToFunc := make(map[string]*HostFunc, len(nameToGoFunc)) - sortedExportNames := make([]string, len(nameToFunc)) - for k := range nameToGoFunc { - sortedExportNames = append(sortedExportNames, k) - } - // Sort names for consistent iteration - sort.Strings(sortedExportNames) + for _, k := range exportNames { + hf := nameToHostFunc[k] + if hf.Name == "" { + hf.Name = k // default name to export name + } + switch hf.Code.GoFunc.(type) { + case api.GoModuleFunction, api.GoFunction: + continue // already parsed + } - funcNames := make([]string, len(nameToFunc)) - for _, k := range sortedExportNames { - v := nameToGoFunc[k] - if hf, ok := v.(*HostFunc); ok { - nameToFunc[hf.Name] = hf - funcNames = append(funcNames, hf.Name) - } else { // reflection - params, results, code, ftErr := parseGoReflectFunc(v) - if ftErr != nil { - return fmt.Errorf("func[%s.%s] %w", moduleName, k, ftErr) - } - hf = &HostFunc{ - ExportNames: []string{k}, - Name: k, - ParamTypes: params, - ResultTypes: results, - Code: code, - } + // Resolve the code using reflection + hf.ParamTypes, hf.ResultTypes, hf.Code, err = parseGoReflectFunc(hf.Code.GoFunc) + if err != nil { + return fmt.Errorf("func[%s.%s] %w", moduleName, k, err) + } - // Assign names to the function, if they exist. - ns := funcToNames[k] - if name := ns.Name; name != "" { - hf.Name = ns.Name - } - if paramNames := ns.ParamNames; paramNames != nil { - if paramNamesLen := len(paramNames); paramNamesLen != len(params) { - return fmt.Errorf("func[%s.%s] has %d params, but %d params names", moduleName, k, paramNamesLen, len(params)) - } - hf.ParamNames = paramNames - } - if resultNames := ns.ResultNames; resultNames != nil { - if resultNamesLen := len(resultNames); resultNamesLen != len(results) { - return fmt.Errorf("func[%s.%s] has %d results, but %d results names", moduleName, k, resultNamesLen, len(results)) - } - hf.ResultNames = resultNames + // Assign names to the function, if they exist. + params := hf.ParamTypes + if paramNames := hf.ParamNames; paramNames != nil { + if paramNamesLen := len(paramNames); paramNamesLen != len(params) { + return fmt.Errorf("func[%s.%s] has %d params, but %d params names", moduleName, k, paramNamesLen, len(params)) } + } - nameToFunc[k] = hf - funcNames = append(funcNames, k) + results := hf.ResultTypes + if resultNames := hf.ResultNames; resultNames != nil { + if resultNamesLen := len(resultNames); resultNamesLen != len(results) { + return fmt.Errorf("func[%s.%s] has %d results, but %d results names", moduleName, k, resultNamesLen, len(results)) + } } } - funcCount := uint32(len(nameToFunc)) + funcCount := uint32(len(exportNames)) m.NameSection.FunctionNames = make([]NameAssoc, 0, funcCount) m.FunctionSection = make([]Index, 0, funcCount) m.CodeSection = make([]Code, 0, funcCount) idx := Index(0) - for _, name := range funcNames { - hf := nameToFunc[name] + for _, name := range exportNames { + hf := nameToHostFunc[name] debugName := wasmdebug.FuncName(moduleName, name, idx) typeIdx, typeErr := m.maybeAddType(hf.ParamTypes, hf.ResultTypes, enabledFeatures) if typeErr != nil { @@ -159,10 +134,10 @@ func addFuncs( } m.FunctionSection = append(m.FunctionSection, typeIdx) m.CodeSection = append(m.CodeSection, hf.Code) - for _, export := range hf.ExportNames { - m.ExportSection = append(m.ExportSection, Export{Type: ExternTypeFunc, Name: export, Index: idx}) - m.Exports[export] = &m.ExportSection[len(m.ExportSection)-1] - } + + export := hf.ExportName + m.ExportSection = append(m.ExportSection, Export{Type: ExternTypeFunc, Name: export, Index: idx}) + m.Exports[export] = &m.ExportSection[len(m.ExportSection)-1] m.NameSection.FunctionNames = append(m.NameSection.FunctionNames, NameAssoc{Index: idx, Name: hf.Name}) if len(hf.ParamNames) > 0 { diff --git a/internal/wasm/host_test.go b/internal/wasm/host_test.go index cde01651..55c881cd 100644 --- a/internal/wasm/host_test.go +++ b/internal/wasm/host_test.go @@ -30,8 +30,8 @@ func TestNewHostModule(t *testing.T) { swapName := "swap" tests := []struct { name, moduleName string - nameToGoFunc map[string]interface{} - funcToNames map[string]*HostFuncNames + exportNames []string + nameToHostFunc map[string]*HostFunc expected *Module }{ { @@ -40,22 +40,21 @@ func TestNewHostModule(t *testing.T) { expected: &Module{NameSection: &NameSection{ModuleName: "test"}}, }, { - name: "funcs", - moduleName: InternalModuleName, - nameToGoFunc: map[string]interface{}{ - ArgsSizesGetName: argsSizesGet, - FdWriteName: fdWrite, - }, - funcToNames: map[string]*HostFuncNames{ + name: "funcs", + moduleName: InternalModuleName, + exportNames: []string{ArgsSizesGetName, FdWriteName}, + nameToHostFunc: map[string]*HostFunc{ ArgsSizesGetName: { - Name: ArgsSizesGetName, + ExportName: ArgsSizesGetName, ParamNames: []string{"result.argc", "result.argv_len"}, ResultNames: []string{"errno"}, + Code: Code{GoFunc: argsSizesGet}, }, FdWriteName: { - Name: FdWriteName, + ExportName: FdWriteName, ParamNames: []string{"fd", "iovs", "iovs_len", "result.size"}, ResultNames: []string{"errno"}, + Code: Code{GoFunc: fdWrite}, }, }, expected: &Module{ @@ -99,12 +98,10 @@ func TestNewHostModule(t *testing.T) { }, }, { - name: "multi-value", - moduleName: "swapper", - nameToGoFunc: map[string]interface{}{ - swapName: swap, - }, - funcToNames: map[string]*HostFuncNames{swapName: {}}, + name: "multi-value", + moduleName: "swapper", + exportNames: []string{swapName}, + nameToHostFunc: map[string]*HostFunc{swapName: {ExportName: swapName, Code: Code{GoFunc: swap}}}, expected: &Module{ TypeSection: []FunctionType{{Params: []ValueType{i32, i32}, Results: []ValueType{i32, i32}}}, FunctionSection: []Index{0}, @@ -120,7 +117,7 @@ func TestNewHostModule(t *testing.T) { tc := tt t.Run(tc.name, func(t *testing.T) { - m, e := NewHostModule(tc.moduleName, tc.nameToGoFunc, tc.funcToNames, api.CoreFeaturesV2) + m, e := NewHostModule(tc.moduleName, tc.exportNames, tc.nameToHostFunc, api.CoreFeaturesV2) require.NoError(t, e) requireHostModuleEquals(t, tc.expected, m) require.True(t, m.IsHostModule) @@ -158,23 +155,23 @@ func requireHostModuleEquals(t *testing.T, expected, actual *Module) { func TestNewHostModule_Errors(t *testing.T) { tests := []struct { name, moduleName string - nameToGoFunc map[string]interface{} - funcToNames map[string]*HostFuncNames + exportNames []string + nameToHostFunc map[string]*HostFunc expectedErr string }{ { - name: "not a function", - moduleName: "modname", - nameToGoFunc: map[string]interface{}{"fn": t}, - funcToNames: map[string]*HostFuncNames{"fn": {}}, - expectedErr: "func[modname.fn] kind != func: ptr", + name: "not a function", + moduleName: "modname", + exportNames: []string{"fn"}, + nameToHostFunc: map[string]*HostFunc{"fn": {ExportName: "fn", Code: Code{GoFunc: t}}}, + expectedErr: "func[modname.fn] kind != func: ptr", }, { - name: "function has multiple results", - moduleName: "yetanother", - nameToGoFunc: map[string]interface{}{"fn": func() (uint32, uint32) { return 0, 0 }}, - funcToNames: map[string]*HostFuncNames{"fn": {}}, - expectedErr: "func[yetanother.fn] multiple result types invalid as feature \"multi-value\" is disabled", + name: "function has multiple results", + moduleName: "yetanother", + exportNames: []string{"fn"}, + nameToHostFunc: map[string]*HostFunc{"fn": {ExportName: "fn", Code: Code{GoFunc: func() (uint32, uint32) { return 0, 0 }}}}, + expectedErr: "func[yetanother.fn] multiple result types invalid as feature \"multi-value\" is disabled", }, } @@ -182,7 +179,7 @@ func TestNewHostModule_Errors(t *testing.T) { tc := tt t.Run(tc.name, func(t *testing.T) { - _, e := NewHostModule(tc.moduleName, tc.nameToGoFunc, tc.funcToNames, api.CoreFeaturesV1) + _, e := NewHostModule(tc.moduleName, tc.exportNames, tc.nameToHostFunc, api.CoreFeaturesV1) require.EqualError(t, e, tc.expectedErr) }) } diff --git a/internal/wasm/store_test.go b/internal/wasm/store_test.go index f6524704..2bb1af1c 100644 --- a/internal/wasm/store_test.go +++ b/internal/wasm/store_test.go @@ -105,7 +105,12 @@ func TestNewStore(t *testing.T) { func TestStore_Instantiate(t *testing.T) { s := newStore() - m, err := NewHostModule("foo", map[string]interface{}{"fn": func() {}}, map[string]*HostFuncNames{"fn": {}}, api.CoreFeaturesV1) + m, err := NewHostModule( + "foo", + []string{"fn"}, + map[string]*HostFunc{"fn": {ExportName: "fn", Code: Code{GoFunc: func() {}}}}, + api.CoreFeaturesV1, + ) require.NoError(t, err) sysCtx := sys.DefaultContext(nil) @@ -184,7 +189,12 @@ func TestStore_CloseWithExitCode(t *testing.T) { func TestStore_hammer(t *testing.T) { const importedModuleName = "imported" - m, err := NewHostModule(importedModuleName, map[string]interface{}{"fn": func() {}}, map[string]*HostFuncNames{"fn": {}}, api.CoreFeaturesV1) + m, err := NewHostModule( + importedModuleName, + []string{"fn"}, + map[string]*HostFunc{"fn": {ExportName: "fn", Code: Code{GoFunc: func() {}}}}, + api.CoreFeaturesV1, + ) require.NoError(t, err) s := newStore() @@ -239,7 +249,12 @@ func TestStore_hammer(t *testing.T) { func TestStore_hammer_close(t *testing.T) { const importedModuleName = "imported" - m, err := NewHostModule(importedModuleName, map[string]interface{}{"fn": func() {}}, map[string]*HostFuncNames{"fn": {}}, api.CoreFeaturesV1) + m, err := NewHostModule( + importedModuleName, + []string{"fn"}, + map[string]*HostFunc{"fn": {ExportName: "fn", Code: Code{GoFunc: func() {}}}}, + api.CoreFeaturesV1, + ) require.NoError(t, err) s := newStore() @@ -299,7 +314,12 @@ func TestStore_Instantiate_Errors(t *testing.T) { const importedModuleName = "imported" const importingModuleName = "test" - m, err := NewHostModule(importedModuleName, map[string]interface{}{"fn": func() {}}, map[string]*HostFuncNames{"fn": {}}, api.CoreFeaturesV1) + m, err := NewHostModule( + importedModuleName, + []string{"fn"}, + map[string]*HostFunc{"fn": {ExportName: "fn", Code: Code{GoFunc: func() {}}}}, + api.CoreFeaturesV1, + ) require.NoError(t, err) t.Run("Fails if module name already in use", func(t *testing.T) {