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.
//
// 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 {

View File

@@ -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`,
},
}

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
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"},

View File

@@ -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{},

View File

@@ -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) {

View File

@@ -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},

View File

@@ -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)

View File

@@ -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) {})},
}
}

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 {
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},
}
}

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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)
})
}

View File

@@ -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) {