Makes host function index insertion order (#1370)

This makes host functions index consistently on insertion order, rather
than lexicographic order. This helps with ABI such as Emscripten, which
need an expected order.

This also constrains the internal code around host functions to only one
export name. More than one was never used. By restricting this, logic is
simpler and smaller.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Crypt Keeper
2023-04-18 00:18:11 +08:00
committed by GitHub
parent 27b405f17b
commit b9e03dc691
13 changed files with 208 additions and 227 deletions

View File

@@ -146,7 +146,7 @@ type HostFunctionBuilder interface {
// defer r.Close(ctx) // This closes everything this Runtime created. // defer r.Close(ctx) // This closes everything this Runtime created.
// //
// hello := func() { // hello := func() {
// fmt.Fprintln(stdout, "hello!") // println("hello!")
// } // }
// env, _ := r.NewHostModuleBuilder("env"). // env, _ := r.NewHostModuleBuilder("env").
// NewFunctionBuilder().WithFunc(hello).Export("hello"). // NewFunctionBuilder().WithFunc(hello).Export("hello").
@@ -170,8 +170,8 @@ type HostFunctionBuilder interface {
// chaining. // chaining.
// - methods do not return errors, to allow chaining. Any validation errors // - methods do not return errors, to allow chaining. Any validation errors
// are deferred until Compile. // are deferred until Compile.
// - Insertion order is not retained. Anything defined by this builder is // - Functions are indexed in order of calls to NewFunctionBuilder as
// sorted lexicographically on Compile. // insertion ordering is needed by ABI such as Emscripten (invoke_*).
type HostModuleBuilder interface { type HostModuleBuilder interface {
// Note: until golang/go#5860, we can't use example tests to embed code in interface godocs. // 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. // defer r.Close(ctx) // This closes everything this Runtime created.
// //
// hello := func() { // hello := func() {
// fmt.Fprintln(stdout, "hello!") // println("hello!")
// } // }
// env, _ := r.NewHostModuleBuilder("env"). // env, _ := r.NewHostModuleBuilder("env").
// NewFunctionBuilder().WithFunc(hello).Export("hello"). // NewFunctionBuilder().WithFunc(hello).Export("hello").
@@ -207,19 +207,18 @@ type HostModuleBuilder interface {
// hostModuleBuilder implements HostModuleBuilder // hostModuleBuilder implements HostModuleBuilder
type hostModuleBuilder struct { type hostModuleBuilder struct {
r *runtime r *runtime
moduleName string moduleName string
nameToGoFunc map[string]interface{} exportNames []string
funcToNames map[string]*wasm.HostFuncNames nameToHostFunc map[string]*wasm.HostFunc
} }
// NewHostModuleBuilder implements Runtime.NewHostModuleBuilder // NewHostModuleBuilder implements Runtime.NewHostModuleBuilder
func (r *runtime) NewHostModuleBuilder(moduleName string) HostModuleBuilder { func (r *runtime) NewHostModuleBuilder(moduleName string) HostModuleBuilder {
return &hostModuleBuilder{ return &hostModuleBuilder{
r: r, r: r,
moduleName: moduleName, moduleName: moduleName,
nameToGoFunc: map[string]interface{}{}, nameToHostFunc: map[string]*wasm.HostFunc{},
funcToNames: map[string]*wasm.HostFuncNames{},
} }
} }
@@ -234,21 +233,13 @@ type hostFunctionBuilder struct {
// WithGoFunction implements HostFunctionBuilder.WithGoFunction // WithGoFunction implements HostFunctionBuilder.WithGoFunction
func (h *hostFunctionBuilder) WithGoFunction(fn api.GoFunction, params, results []api.ValueType) HostFunctionBuilder { func (h *hostFunctionBuilder) WithGoFunction(fn api.GoFunction, params, results []api.ValueType) HostFunctionBuilder {
h.fn = &wasm.HostFunc{ h.fn = &wasm.HostFunc{ParamTypes: params, ResultTypes: results, Code: wasm.Code{GoFunc: fn}}
ParamTypes: params,
ResultTypes: results,
Code: wasm.Code{GoFunc: fn},
}
return h return h
} }
// WithGoModuleFunction implements HostFunctionBuilder.WithGoModuleFunction // WithGoModuleFunction implements HostFunctionBuilder.WithGoModuleFunction
func (h *hostFunctionBuilder) WithGoModuleFunction(fn api.GoModuleFunction, params, results []api.ValueType) HostFunctionBuilder { func (h *hostFunctionBuilder) WithGoModuleFunction(fn api.GoModuleFunction, params, results []api.ValueType) HostFunctionBuilder {
h.fn = &wasm.HostFunc{ h.fn = &wasm.HostFunc{ParamTypes: params, ResultTypes: results, Code: wasm.Code{GoFunc: fn}}
ParamTypes: params,
ResultTypes: results,
Code: wasm.Code{GoFunc: fn},
}
return h return h
} }
@@ -278,30 +269,35 @@ func (h *hostFunctionBuilder) WithResultNames(names ...string) HostFunctionBuild
// Export implements HostFunctionBuilder.Export // Export implements HostFunctionBuilder.Export
func (h *hostFunctionBuilder) Export(exportName string) HostModuleBuilder { func (h *hostFunctionBuilder) Export(exportName string) HostModuleBuilder {
if h.name == "" { var hostFn *wasm.HostFunc
h.name = exportName
}
names := &wasm.HostFuncNames{
Name: h.name,
ParamNames: h.paramNames,
ResultNames: h.resultNames,
}
if fn, ok := h.fn.(*wasm.HostFunc); ok { if fn, ok := h.fn.(*wasm.HostFunc); ok {
if fn.Name == "" { hostFn = fn
fn.Name = names.Name } else {
} hostFn = &wasm.HostFunc{Code: wasm.Code{GoFunc: h.fn}}
fn.ParamNames = names.ParamNames
fn.ResultNames = names.ResultNames
fn.ExportNames = []string{exportName}
} }
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 return h.b
} }
// ExportHostFunc implements wasm.HostFuncExporter // ExportHostFunc implements wasm.HostFuncExporter
func (b *hostModuleBuilder) ExportHostFunc(fn *wasm.HostFunc) { 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 // NewFunctionBuilder implements HostModuleBuilder.NewFunctionBuilder
@@ -311,7 +307,7 @@ func (b *hostModuleBuilder) NewFunctionBuilder() HostFunctionBuilder {
// Compile implements HostModuleBuilder.Compile // Compile implements HostModuleBuilder.Compile
func (b *hostModuleBuilder) Compile(ctx context.Context) (CompiledModule, error) { 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 { if err != nil {
return nil, err return nil, err
} else if err = module.Validate(b.r.enabledFeatures); err != nil { } else if err = module.Validate(b.r.enabledFeatures); err != nil {

View File

@@ -159,21 +159,21 @@ func TestNewHostModuleBuilder_Compile(t *testing.T) {
}, },
expected: &wasm.Module{ expected: &wasm.Module{
TypeSection: []wasm.FunctionType{ TypeSection: []wasm.FunctionType{
{Params: []api.ValueType{i32}, Results: []api.ValueType{i32}},
{Params: []api.ValueType{i64}, 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}, 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{ ExportSection: []wasm.Export{
{Name: "1", Type: wasm.ExternTypeFunc, Index: 0}, {Name: "2", Type: wasm.ExternTypeFunc, Index: 0},
{Name: "2", Type: wasm.ExternTypeFunc, Index: 1}, {Name: "1", Type: wasm.ExternTypeFunc, Index: 1},
}, },
Exports: map[string]*wasm.Export{ Exports: map[string]*wasm.Export{
"1": {Name: "1", Type: wasm.ExternTypeFunc, Index: 0}, "2": {Name: "2", Type: wasm.ExternTypeFunc, Index: 0},
"2": {Name: "2", Type: wasm.ExternTypeFunc, Index: 1}, "1": {Name: "1", Type: wasm.ExternTypeFunc, Index: 1},
}, },
NameSection: &wasm.NameSection{ 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", ModuleName: "host",
}, },
}, },
@@ -269,7 +269,7 @@ func TestNewHostModuleBuilder_Compile(t *testing.T) {
{ {
name: "WithGoFunction twice", name: "WithGoFunction twice",
input: func(r Runtime) HostModuleBuilder { input: func(r Runtime) HostModuleBuilder {
// Intentionally out of order // Intentionally not in lexicographic order
return r.NewHostModuleBuilder("host"). return r.NewHostModuleBuilder("host").
NewFunctionBuilder(). NewFunctionBuilder().
WithGoFunction(gofunc2, []api.ValueType{i64}, []api.ValueType{i32}). WithGoFunction(gofunc2, []api.ValueType{i64}, []api.ValueType{i32}).
@@ -280,24 +280,24 @@ func TestNewHostModuleBuilder_Compile(t *testing.T) {
}, },
expected: &wasm.Module{ expected: &wasm.Module{
TypeSection: []wasm.FunctionType{ TypeSection: []wasm.FunctionType{
{Params: []api.ValueType{i32}, Results: []api.ValueType{i32}},
{Params: []api.ValueType{i64}, 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}, FunctionSection: []wasm.Index{0, 1},
CodeSection: []wasm.Code{ CodeSection: []wasm.Code{
{GoFunc: gofunc1},
{GoFunc: gofunc2}, {GoFunc: gofunc2},
{GoFunc: gofunc1},
}, },
ExportSection: []wasm.Export{ ExportSection: []wasm.Export{
{Name: "1", Type: wasm.ExternTypeFunc, Index: 0}, {Name: "2", Type: wasm.ExternTypeFunc, Index: 0},
{Name: "2", Type: wasm.ExternTypeFunc, Index: 1}, {Name: "1", Type: wasm.ExternTypeFunc, Index: 1},
}, },
Exports: map[string]*wasm.Export{ Exports: map[string]*wasm.Export{
"1": {Name: "1", Type: wasm.ExternTypeFunc, Index: 0}, "2": {Name: "2", Type: wasm.ExternTypeFunc, Index: 0},
"2": {Name: "2", Type: wasm.ExternTypeFunc, Index: 1}, "1": {Name: "1", Type: wasm.ExternTypeFunc, Index: 1},
}, },
NameSection: &wasm.NameSection{ 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", ModuleName: "host",
}, },
}, },
@@ -342,18 +342,13 @@ func TestNewHostModuleBuilder_Compile_Errors(t *testing.T) {
expectedErr string 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 { input: func(rt Runtime) HostModuleBuilder {
return rt.NewHostModuleBuilder("host").NewFunctionBuilder(). return rt.NewHostModuleBuilder("host").NewFunctionBuilder().
WithFunc(&wasm.HostFunc{ WithFunc(&wasm.HostFunc{ExportName: "fn", Code: wasm.Code{GoFunc: func(string) {}}}).
ExportNames: []string{"fn"}, Export("fn")
ResultTypes: []wasm.ValueType{wasm.ValueTypeI32},
Code: wasm.Code{Body: []byte{wasm.OpcodeEnd}},
}).Export("fn")
}, },
expectedErr: `invalid function[0] export["fn"]: not enough results expectedErr: `func[host.fn] param[0] is unsupported: string`,
have ()
want (i32)`,
}, },
} }

View File

@@ -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 // See https://github.com/AssemblyScript/assemblyscript/blob/v0.26.7/std/assembly/builtins.ts#L2508
var abortMessageEnabled = &wasm.HostFunc{ var abortMessageEnabled = &wasm.HostFunc{
ExportNames: []string{AbortName}, ExportName: AbortName,
Name: "~lib/builtins/abort", Name: "~lib/builtins/abort",
ParamTypes: []api.ValueType{i32, i32, i32, i32}, ParamTypes: []api.ValueType{i32, i32, i32, i32},
ParamNames: []string{"message", "fileName", "lineNumber", "columnNumber"}, ParamNames: []string{"message", "fileName", "lineNumber", "columnNumber"},
Code: wasm.Code{GoFunc: api.GoModuleFunc(abortWithMessage)}, Code: wasm.Code{GoFunc: api.GoModuleFunc(abortWithMessage)},
} }
var abortMessageDisabled = abortMessageEnabled.WithGoModuleFunc(abort) 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. // traceStdout implements trace to the configured Stdout.
var traceStdout = &wasm.HostFunc{ var traceStdout = &wasm.HostFunc{
ExportNames: []string{TraceName}, ExportName: TraceName,
Name: "~lib/builtins/trace", Name: "~lib/builtins/trace",
ParamTypes: []api.ValueType{i32, i32, f64, f64, f64, f64, f64}, ParamTypes: []api.ValueType{i32, i32, f64, f64, f64, f64, f64},
ParamNames: []string{"message", "nArgs", "arg0", "arg1", "arg2", "arg3", "arg4"}, ParamNames: []string{"message", "nArgs", "arg0", "arg1", "arg2", "arg3", "arg4"},
Code: wasm.Code{ Code: wasm.Code{
GoFunc: api.GoModuleFunc(func(_ context.Context, mod api.Module, stack []uint64) { GoFunc: api.GoModuleFunc(func(_ context.Context, mod api.Module, stack []uint64) {
fsc := mod.(*wasm.ModuleInstance).Sys.FS() 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 // See https://github.com/AssemblyScript/assemblyscript/blob/v0.26.7/std/assembly/builtins.ts#L2531
var seed = &wasm.HostFunc{ var seed = &wasm.HostFunc{
ExportNames: []string{SeedName}, ExportName: SeedName,
Name: "~lib/builtins/seed", Name: "~lib/builtins/seed",
ResultTypes: []api.ValueType{f64}, ResultTypes: []api.ValueType{f64},
ResultNames: []string{"rand"}, ResultNames: []string{"rand"},

View File

@@ -94,11 +94,10 @@ func (functionExporter) ExportFunctions(builder wazero.HostModuleBuilder) {
const functionNotifyMemoryGrowth = "emscripten_notify_memory_growth" const functionNotifyMemoryGrowth = "emscripten_notify_memory_growth"
var notifyMemoryGrowth = &wasm.HostFunc{ var notifyMemoryGrowth = &wasm.HostFunc{
ExportNames: []string{functionNotifyMemoryGrowth}, ExportName: functionNotifyMemoryGrowth,
Name: functionNotifyMemoryGrowth, ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32}, ParamNames: []string{"memory_index"},
ParamNames: []string{"memory_index"}, Code: wasm.Code{GoFunc: api.GoModuleFunc(func(context.Context, api.Module, []uint64) {})},
Code: wasm.Code{GoFunc: api.GoModuleFunc(func(context.Context, api.Module, []uint64) {})},
} }
// All `invoke_` functions have an initial "index" parameter of // All `invoke_` functions have an initial "index" parameter of
@@ -134,8 +133,7 @@ const (
) )
var invokeI = &wasm.HostFunc{ var invokeI = &wasm.HostFunc{
ExportNames: []string{functionInvokeI}, ExportName: functionInvokeI,
Name: functionInvokeI,
ParamTypes: []api.ValueType{i32}, ParamTypes: []api.ValueType{i32},
ParamNames: []string{"index"}, ParamNames: []string{"index"},
ResultTypes: []api.ValueType{i32}, ResultTypes: []api.ValueType{i32},
@@ -151,8 +149,7 @@ func invokeIFn(ctx context.Context, mod api.Module, stack []uint64) {
} }
var invokeIi = &wasm.HostFunc{ var invokeIi = &wasm.HostFunc{
ExportNames: []string{functionInvokeIi}, ExportName: functionInvokeIi,
Name: functionInvokeIi,
ParamTypes: []api.ValueType{i32, i32}, ParamTypes: []api.ValueType{i32, i32},
ParamNames: []string{"index", "a1"}, ParamNames: []string{"index", "a1"},
ResultTypes: []api.ValueType{i32}, ResultTypes: []api.ValueType{i32},
@@ -168,8 +165,7 @@ func invokeIiFn(ctx context.Context, mod api.Module, stack []uint64) {
} }
var invokeIii = &wasm.HostFunc{ var invokeIii = &wasm.HostFunc{
ExportNames: []string{functionInvokeIii}, ExportName: functionInvokeIii,
Name: functionInvokeIii,
ParamTypes: []api.ValueType{i32, i32, i32}, ParamTypes: []api.ValueType{i32, i32, i32},
ParamNames: []string{"index", "a1", "a2"}, ParamNames: []string{"index", "a1", "a2"},
ResultTypes: []api.ValueType{i32}, ResultTypes: []api.ValueType{i32},
@@ -185,8 +181,7 @@ func invokeIiiFn(ctx context.Context, mod api.Module, stack []uint64) {
} }
var invokeIiii = &wasm.HostFunc{ var invokeIiii = &wasm.HostFunc{
ExportNames: []string{functionInvokeIiii}, ExportName: functionInvokeIiii,
Name: functionInvokeIiii,
ParamTypes: []api.ValueType{i32, i32, i32, i32}, ParamTypes: []api.ValueType{i32, i32, i32, i32},
ParamNames: []string{"index", "a1", "a2", "a3"}, ParamNames: []string{"index", "a1", "a2", "a3"},
ResultTypes: []api.ValueType{i32}, ResultTypes: []api.ValueType{i32},
@@ -202,8 +197,7 @@ func invokeIiiiFn(ctx context.Context, mod api.Module, stack []uint64) {
} }
var invokeIiiii = &wasm.HostFunc{ var invokeIiiii = &wasm.HostFunc{
ExportNames: []string{functionInvokeIiiii}, ExportName: functionInvokeIiiii,
Name: functionInvokeIiiii,
ParamTypes: []api.ValueType{i32, i32, i32, i32, i32}, ParamTypes: []api.ValueType{i32, i32, i32, i32, i32},
ParamNames: []string{"index", "a1", "a2", "a3", "a4"}, ParamNames: []string{"index", "a1", "a2", "a3", "a4"},
ResultTypes: []api.ValueType{i32}, ResultTypes: []api.ValueType{i32},
@@ -219,8 +213,7 @@ func invokeIiiiiFn(ctx context.Context, mod api.Module, stack []uint64) {
} }
var invokeV = &wasm.HostFunc{ var invokeV = &wasm.HostFunc{
ExportNames: []string{functionInvokeV}, ExportName: functionInvokeV,
Name: functionInvokeV,
ParamTypes: []api.ValueType{i32}, ParamTypes: []api.ValueType{i32},
ParamNames: []string{"index"}, ParamNames: []string{"index"},
ResultTypes: []api.ValueType{}, ResultTypes: []api.ValueType{},
@@ -235,8 +228,7 @@ func invokeVFn(ctx context.Context, mod api.Module, stack []uint64) {
} }
var invokeVi = &wasm.HostFunc{ var invokeVi = &wasm.HostFunc{
ExportNames: []string{functionInvokeVi}, ExportName: functionInvokeVi,
Name: functionInvokeVi,
ParamTypes: []api.ValueType{i32, i32}, ParamTypes: []api.ValueType{i32, i32},
ParamNames: []string{"index", "a1"}, ParamNames: []string{"index", "a1"},
ResultTypes: []api.ValueType{}, ResultTypes: []api.ValueType{},
@@ -251,8 +243,7 @@ func invokeViFn(ctx context.Context, mod api.Module, stack []uint64) {
} }
var invokeVii = &wasm.HostFunc{ var invokeVii = &wasm.HostFunc{
ExportNames: []string{functionInvokeVii}, ExportName: functionInvokeVii,
Name: functionInvokeVii,
ParamTypes: []api.ValueType{i32, i32, i32}, ParamTypes: []api.ValueType{i32, i32, i32},
ParamNames: []string{"index", "a1", "a2"}, ParamNames: []string{"index", "a1", "a2"},
ResultTypes: []api.ValueType{}, ResultTypes: []api.ValueType{},
@@ -267,8 +258,7 @@ func invokeViiFn(ctx context.Context, mod api.Module, stack []uint64) {
} }
var invokeViii = &wasm.HostFunc{ var invokeViii = &wasm.HostFunc{
ExportNames: []string{functionInvokeViii}, ExportName: functionInvokeViii,
Name: functionInvokeViii,
ParamTypes: []api.ValueType{i32, i32, i32, i32}, ParamTypes: []api.ValueType{i32, i32, i32, i32},
ParamNames: []string{"index", "a1", "a2", "a3"}, ParamNames: []string{"index", "a1", "a2", "a3"},
ResultTypes: []api.ValueType{}, ResultTypes: []api.ValueType{},
@@ -283,8 +273,7 @@ func invokeViiiFn(ctx context.Context, mod api.Module, stack []uint64) {
} }
var invokeViiii = &wasm.HostFunc{ var invokeViiii = &wasm.HostFunc{
ExportNames: []string{functionInvokeViiii}, ExportName: functionInvokeViiii,
Name: functionInvokeViiii,
ParamTypes: []api.ValueType{i32, i32, i32, i32, i32}, ParamTypes: []api.ValueType{i32, i32, i32, i32, i32},
ParamNames: []string{"index", "a1", "a2", "a3", "a4"}, ParamNames: []string{"index", "a1", "a2", "a3", "a4"},
ResultTypes: []api.ValueType{}, ResultTypes: []api.ValueType{},

View File

@@ -19,13 +19,10 @@ import (
// //
// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#proc_exit // See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#proc_exit
var procExit = &wasm.HostFunc{ var procExit = &wasm.HostFunc{
ExportNames: []string{wasip1.ProcExitName}, ExportName: wasip1.ProcExitName,
Name: wasip1.ProcExitName, ParamTypes: []api.ValueType{i32},
ParamTypes: []api.ValueType{i32}, ParamNames: []string{"rval"},
ParamNames: []string{"rval"}, Code: wasm.Code{GoFunc: api.GoModuleFunc(procExitFn)},
Code: wasm.Code{
GoFunc: api.GoModuleFunc(procExitFn),
},
} }
func procExitFn(ctx context.Context, mod api.Module, params []uint64) { func procExitFn(ctx context.Context, mod api.Module, params []uint64) {

View File

@@ -263,8 +263,7 @@ func newHostFunc(
paramNames ...string, paramNames ...string,
) *wasm.HostFunc { ) *wasm.HostFunc {
return &wasm.HostFunc{ return &wasm.HostFunc{
ExportNames: []string{name}, ExportName: name,
Name: name,
ParamTypes: paramTypes, ParamTypes: paramTypes,
ParamNames: paramNames, ParamNames: paramNames,
ResultTypes: []api.ValueType{i32}, 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. // stubFunction stubs for GrainLang per #271.
func stubFunction(name string, paramTypes []wasm.ValueType, paramNames ...string) *wasm.HostFunc { func stubFunction(name string, paramTypes []wasm.ValueType, paramNames ...string) *wasm.HostFunc {
return &wasm.HostFunc{ return &wasm.HostFunc{
Name: name, ExportName: name,
ExportNames: []string{name},
ParamTypes: paramTypes, ParamTypes: paramTypes,
ParamNames: paramNames, ParamNames: paramNames,
ResultTypes: []api.ValueType{i32}, ResultTypes: []api.ValueType{i32},

View File

@@ -231,7 +231,7 @@ func TestCompiler_SliceAllocatedOnHeap(t *testing.T) {
const hostModuleName = "env" const hostModuleName = "env"
const hostFnName = "grow_and_shrink_goroutine_stack" 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 // This function aggressively grow the goroutine stack by recursively
// calling the function many times. // calling the function many times.
callNum := 1000 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 // Trigger relocation of goroutine stack because at this point we have the majority of
// goroutine stack unused after recursive call. // goroutine stack unused after recursive call.
runtime.GC() 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) require.NoError(t, err)
err = s.Engine.CompileModule(testCtx, hm, nil, false) err = s.Engine.CompileModule(testCtx, hm, nil, false)

View File

@@ -16,11 +16,10 @@ import (
// This traps (unreachable opcode) to ensure the function is never called. // This traps (unreachable opcode) to ensure the function is never called.
func StubFunction(name string) *wasm.HostFunc { func StubFunction(name string) *wasm.HostFunc {
return &wasm.HostFunc{ return &wasm.HostFunc{
ExportNames: []string{name}, ExportName: name,
Name: name, ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32}, ParamNames: []string{"sp"},
ParamNames: []string{"sp"}, Code: wasm.Code{GoFunc: api.GoModuleFunc(func(ctx context.Context, _ api.Module, stack []uint64) {})},
Code: wasm.Code{GoFunc: api.GoModuleFunc(func(ctx context.Context, _ api.Module, stack []uint64) {})},
} }
} }

View File

@@ -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 { func NewFunc(name string, goFunc api.GoModuleFunc) *wasm.HostFunc {
return &wasm.HostFunc{ return &wasm.HostFunc{
ExportNames: []string{name}, ExportName: name,
Name: name, ParamTypes: []api.ValueType{api.ValueTypeI32},
ParamTypes: []api.ValueType{api.ValueTypeI32}, ParamNames: []string{"sp"},
ParamNames: []string{"sp"}, Code: wasm.Code{GoFunc: goFunc},
Code: wasm.Code{GoFunc: goFunc},
} }
} }

View File

@@ -58,11 +58,21 @@ func RunTestEngine_MemoryGrowInRecursiveCall(t *testing.T, et EngineTester) {
const hostModuleName = "env" const hostModuleName = "env"
const hostFnName = "grow_memory" const hostFnName = "grow_memory"
var growFn api.Function var growFn api.Function
hm, err := wasm.NewHostModule(hostModuleName, map[string]interface{}{hostFnName: func() { hm, err := wasm.NewHostModule(
// Does the recursive call into Wasm, which grows memory. hostModuleName,
_, err := growFn.Call(context.Background()) []string{hostFnName},
require.NoError(t, err) map[string]*wasm.HostFunc{
}}, map[string]*wasm.HostFuncNames{hostFnName: {}}, enabledFeatures) 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) require.NoError(t, err)
err = s.Engine.CompileModule(testCtx, hm, nil, false) err = s.Engine.CompileModule(testCtx, hm, nil, false)

View File

@@ -3,7 +3,6 @@ package wasm
import ( import (
"errors" "errors"
"fmt" "fmt"
"sort"
"github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/wasmdebug" "github.com/tetratelabs/wazero/internal/wasmdebug"
@@ -16,8 +15,8 @@ type HostFuncExporter interface {
// HostFunc is a function with an inlined type, used for NewHostModule. // HostFunc is a function with an inlined type, used for NewHostModule.
// Any corresponding FunctionType will be reused or added to the Module. // Any corresponding FunctionType will be reused or added to the Module.
type HostFunc struct { type HostFunc struct {
// ExportNames is equivalent to the same method on api.FunctionDefinition. // ExportName is the only value returned by api.FunctionDefinition.
ExportNames []string ExportName string
// Name is equivalent to the same method on api.FunctionDefinition. // Name is equivalent to the same method on api.FunctionDefinition.
Name string Name string
@@ -45,17 +44,11 @@ func (f *HostFunc) WithGoModuleFunc(fn api.GoModuleFunc) *HostFunc {
return &ret 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. // NewHostModule is defined internally for use in WASI tests and to keep the code size in the root directory small.
func NewHostModule( func NewHostModule(
moduleName string, moduleName string,
nameToGoFunc map[string]interface{}, exportNames []string,
funcToNames map[string]*HostFuncNames, nameToHostFunc map[string]*HostFunc,
enabledFeatures api.CoreFeatures, enabledFeatures api.CoreFeatures,
) (m *Module, err error) { ) (m *Module, err error) {
if moduleName != "" { if moduleName != "" {
@@ -64,10 +57,10 @@ func NewHostModule(
return nil, errors.New("a module name must not be empty") 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.ExportSection = make([]Export, 0, exportCount)
m.Exports = make(map[string]*Export, 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 return
} }
} }
@@ -85,73 +78,55 @@ func NewHostModule(
func addFuncs( func addFuncs(
m *Module, m *Module,
nameToGoFunc map[string]interface{}, exportNames []string,
funcToNames map[string]*HostFuncNames, nameToHostFunc map[string]*HostFunc,
enabledFeatures api.CoreFeatures, enabledFeatures api.CoreFeatures,
) (err error) { ) (err error) {
if m.NameSection == nil { if m.NameSection == nil {
m.NameSection = &NameSection{} m.NameSection = &NameSection{}
} }
moduleName := m.NameSection.ModuleName 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 for _, k := range exportNames {
sort.Strings(sortedExportNames) 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)) // Resolve the code using reflection
for _, k := range sortedExportNames { hf.ParamTypes, hf.ResultTypes, hf.Code, err = parseGoReflectFunc(hf.Code.GoFunc)
v := nameToGoFunc[k] if err != nil {
if hf, ok := v.(*HostFunc); ok { return fmt.Errorf("func[%s.%s] %w", moduleName, k, err)
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,
}
// Assign names to the function, if they exist. // Assign names to the function, if they exist.
ns := funcToNames[k] params := hf.ParamTypes
if name := ns.Name; name != "" { if paramNames := hf.ParamNames; paramNames != nil {
hf.Name = ns.Name 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))
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
} }
}
nameToFunc[k] = hf results := hf.ResultTypes
funcNames = append(funcNames, k) 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.NameSection.FunctionNames = make([]NameAssoc, 0, funcCount)
m.FunctionSection = make([]Index, 0, funcCount) m.FunctionSection = make([]Index, 0, funcCount)
m.CodeSection = make([]Code, 0, funcCount) m.CodeSection = make([]Code, 0, funcCount)
idx := Index(0) idx := Index(0)
for _, name := range funcNames { for _, name := range exportNames {
hf := nameToFunc[name] hf := nameToHostFunc[name]
debugName := wasmdebug.FuncName(moduleName, name, idx) debugName := wasmdebug.FuncName(moduleName, name, idx)
typeIdx, typeErr := m.maybeAddType(hf.ParamTypes, hf.ResultTypes, enabledFeatures) typeIdx, typeErr := m.maybeAddType(hf.ParamTypes, hf.ResultTypes, enabledFeatures)
if typeErr != nil { if typeErr != nil {
@@ -159,10 +134,10 @@ func addFuncs(
} }
m.FunctionSection = append(m.FunctionSection, typeIdx) m.FunctionSection = append(m.FunctionSection, typeIdx)
m.CodeSection = append(m.CodeSection, hf.Code) m.CodeSection = append(m.CodeSection, hf.Code)
for _, export := range hf.ExportNames {
m.ExportSection = append(m.ExportSection, Export{Type: ExternTypeFunc, Name: export, Index: idx}) export := hf.ExportName
m.Exports[export] = &m.ExportSection[len(m.ExportSection)-1] 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}) m.NameSection.FunctionNames = append(m.NameSection.FunctionNames, NameAssoc{Index: idx, Name: hf.Name})
if len(hf.ParamNames) > 0 { if len(hf.ParamNames) > 0 {

View File

@@ -30,8 +30,8 @@ func TestNewHostModule(t *testing.T) {
swapName := "swap" swapName := "swap"
tests := []struct { tests := []struct {
name, moduleName string name, moduleName string
nameToGoFunc map[string]interface{} exportNames []string
funcToNames map[string]*HostFuncNames nameToHostFunc map[string]*HostFunc
expected *Module expected *Module
}{ }{
{ {
@@ -40,22 +40,21 @@ func TestNewHostModule(t *testing.T) {
expected: &Module{NameSection: &NameSection{ModuleName: "test"}}, expected: &Module{NameSection: &NameSection{ModuleName: "test"}},
}, },
{ {
name: "funcs", name: "funcs",
moduleName: InternalModuleName, moduleName: InternalModuleName,
nameToGoFunc: map[string]interface{}{ exportNames: []string{ArgsSizesGetName, FdWriteName},
ArgsSizesGetName: argsSizesGet, nameToHostFunc: map[string]*HostFunc{
FdWriteName: fdWrite,
},
funcToNames: map[string]*HostFuncNames{
ArgsSizesGetName: { ArgsSizesGetName: {
Name: ArgsSizesGetName, ExportName: ArgsSizesGetName,
ParamNames: []string{"result.argc", "result.argv_len"}, ParamNames: []string{"result.argc", "result.argv_len"},
ResultNames: []string{"errno"}, ResultNames: []string{"errno"},
Code: Code{GoFunc: argsSizesGet},
}, },
FdWriteName: { FdWriteName: {
Name: FdWriteName, ExportName: FdWriteName,
ParamNames: []string{"fd", "iovs", "iovs_len", "result.size"}, ParamNames: []string{"fd", "iovs", "iovs_len", "result.size"},
ResultNames: []string{"errno"}, ResultNames: []string{"errno"},
Code: Code{GoFunc: fdWrite},
}, },
}, },
expected: &Module{ expected: &Module{
@@ -99,12 +98,10 @@ func TestNewHostModule(t *testing.T) {
}, },
}, },
{ {
name: "multi-value", name: "multi-value",
moduleName: "swapper", moduleName: "swapper",
nameToGoFunc: map[string]interface{}{ exportNames: []string{swapName},
swapName: swap, nameToHostFunc: map[string]*HostFunc{swapName: {ExportName: swapName, Code: Code{GoFunc: swap}}},
},
funcToNames: map[string]*HostFuncNames{swapName: {}},
expected: &Module{ expected: &Module{
TypeSection: []FunctionType{{Params: []ValueType{i32, i32}, Results: []ValueType{i32, i32}}}, TypeSection: []FunctionType{{Params: []ValueType{i32, i32}, Results: []ValueType{i32, i32}}},
FunctionSection: []Index{0}, FunctionSection: []Index{0},
@@ -120,7 +117,7 @@ func TestNewHostModule(t *testing.T) {
tc := tt tc := tt
t.Run(tc.name, func(t *testing.T) { 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) require.NoError(t, e)
requireHostModuleEquals(t, tc.expected, m) requireHostModuleEquals(t, tc.expected, m)
require.True(t, m.IsHostModule) require.True(t, m.IsHostModule)
@@ -158,23 +155,23 @@ func requireHostModuleEquals(t *testing.T, expected, actual *Module) {
func TestNewHostModule_Errors(t *testing.T) { func TestNewHostModule_Errors(t *testing.T) {
tests := []struct { tests := []struct {
name, moduleName string name, moduleName string
nameToGoFunc map[string]interface{} exportNames []string
funcToNames map[string]*HostFuncNames nameToHostFunc map[string]*HostFunc
expectedErr string expectedErr string
}{ }{
{ {
name: "not a function", name: "not a function",
moduleName: "modname", moduleName: "modname",
nameToGoFunc: map[string]interface{}{"fn": t}, exportNames: []string{"fn"},
funcToNames: map[string]*HostFuncNames{"fn": {}}, nameToHostFunc: map[string]*HostFunc{"fn": {ExportName: "fn", Code: Code{GoFunc: t}}},
expectedErr: "func[modname.fn] kind != func: ptr", expectedErr: "func[modname.fn] kind != func: ptr",
}, },
{ {
name: "function has multiple results", name: "function has multiple results",
moduleName: "yetanother", moduleName: "yetanother",
nameToGoFunc: map[string]interface{}{"fn": func() (uint32, uint32) { return 0, 0 }}, exportNames: []string{"fn"},
funcToNames: map[string]*HostFuncNames{"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", 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 tc := tt
t.Run(tc.name, func(t *testing.T) { 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) require.EqualError(t, e, tc.expectedErr)
}) })
} }

View File

@@ -105,7 +105,12 @@ func TestNewStore(t *testing.T) {
func TestStore_Instantiate(t *testing.T) { func TestStore_Instantiate(t *testing.T) {
s := newStore() 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) require.NoError(t, err)
sysCtx := sys.DefaultContext(nil) sysCtx := sys.DefaultContext(nil)
@@ -184,7 +189,12 @@ func TestStore_CloseWithExitCode(t *testing.T) {
func TestStore_hammer(t *testing.T) { func TestStore_hammer(t *testing.T) {
const importedModuleName = "imported" 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) require.NoError(t, err)
s := newStore() s := newStore()
@@ -239,7 +249,12 @@ func TestStore_hammer(t *testing.T) {
func TestStore_hammer_close(t *testing.T) { func TestStore_hammer_close(t *testing.T) {
const importedModuleName = "imported" 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) require.NoError(t, err)
s := newStore() s := newStore()
@@ -299,7 +314,12 @@ func TestStore_Instantiate_Errors(t *testing.T) {
const importedModuleName = "imported" const importedModuleName = "imported"
const importingModuleName = "test" 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) require.NoError(t, err)
t.Run("Fails if module name already in use", func(t *testing.T) { t.Run("Fails if module name already in use", func(t *testing.T) {