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:
66
builder.go
66
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").
|
||||
@@ -209,8 +209,8 @@ type HostModuleBuilder interface {
|
||||
type hostModuleBuilder struct {
|
||||
r *runtime
|
||||
moduleName string
|
||||
nameToGoFunc map[string]interface{}
|
||||
funcToNames map[string]*wasm.HostFuncNames
|
||||
exportNames []string
|
||||
nameToHostFunc map[string]*wasm.HostFunc
|
||||
}
|
||||
|
||||
// NewHostModuleBuilder implements Runtime.NewHostModuleBuilder
|
||||
@@ -218,8 +218,7 @@ func (r *runtime) NewHostModuleBuilder(moduleName string) HostModuleBuilder {
|
||||
return &hostModuleBuilder{
|
||||
r: r,
|
||||
moduleName: moduleName,
|
||||
nameToGoFunc: map[string]interface{}{},
|
||||
funcToNames: map[string]*wasm.HostFuncNames{},
|
||||
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
|
||||
hostFn = fn
|
||||
} else {
|
||||
hostFn = &wasm.HostFunc{Code: wasm.Code{GoFunc: h.fn}}
|
||||
}
|
||||
fn.ParamNames = names.ParamNames
|
||||
fn.ResultNames = names.ResultNames
|
||||
fn.ExportNames = []string{exportName}
|
||||
|
||||
// Assign any names from the builder
|
||||
hostFn.ExportName = exportName
|
||||
if h.name != "" {
|
||||
hostFn.Name = h.name
|
||||
}
|
||||
h.b.nameToGoFunc[exportName] = h.fn
|
||||
h.b.funcToNames[exportName] = names
|
||||
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 {
|
||||
|
||||
@@ -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`,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ 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},
|
||||
ExportName: AbortName,
|
||||
Name: "~lib/builtins/abort",
|
||||
ParamTypes: []api.ValueType{i32, i32, i32, i32},
|
||||
ParamNames: []string{"message", "fileName", "lineNumber", "columnNumber"},
|
||||
@@ -185,7 +185,7 @@ var traceDisabled = traceStdout.WithGoModuleFunc(func(context.Context, api.Modul
|
||||
|
||||
// traceStdout implements trace to the configured Stdout.
|
||||
var traceStdout = &wasm.HostFunc{
|
||||
ExportNames: []string{TraceName},
|
||||
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"},
|
||||
@@ -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"},
|
||||
|
||||
@@ -94,8 +94,7 @@ func (functionExporter) ExportFunctions(builder wazero.HostModuleBuilder) {
|
||||
const functionNotifyMemoryGrowth = "emscripten_notify_memory_growth"
|
||||
|
||||
var notifyMemoryGrowth = &wasm.HostFunc{
|
||||
ExportNames: []string{functionNotifyMemoryGrowth},
|
||||
Name: functionNotifyMemoryGrowth,
|
||||
ExportName: functionNotifyMemoryGrowth,
|
||||
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
|
||||
ParamNames: []string{"memory_index"},
|
||||
Code: wasm.Code{GoFunc: api.GoModuleFunc(func(context.Context, api.Module, []uint64) {})},
|
||||
@@ -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{},
|
||||
|
||||
@@ -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,
|
||||
ExportName: wasip1.ProcExitName,
|
||||
ParamTypes: []api.ValueType{i32},
|
||||
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) {
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -16,8 +16,7 @@ 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,
|
||||
ExportName: name,
|
||||
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
|
||||
ParamNames: []string{"sp"},
|
||||
Code: wasm.Code{GoFunc: api.GoModuleFunc(func(ctx context.Context, _ api.Module, stack []uint64) {})},
|
||||
|
||||
@@ -38,8 +38,7 @@ 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,
|
||||
ExportName: name,
|
||||
ParamTypes: []api.ValueType{api.ValueTypeI32},
|
||||
ParamNames: []string{"sp"},
|
||||
Code: wasm.Code{GoFunc: goFunc},
|
||||
|
||||
@@ -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() {
|
||||
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)
|
||||
}}, map[string]*wasm.HostFuncNames{hostFnName: {}}, enabledFeatures)
|
||||
}},
|
||||
},
|
||||
},
|
||||
enabledFeatures,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = s.Engine.CompileModule(testCtx, hm, nil, false)
|
||||
|
||||
@@ -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)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Sort names for consistent iteration
|
||||
sort.Strings(sortedExportNames)
|
||||
|
||||
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 {
|
||||
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))
|
||||
}
|
||||
hf.ParamNames = paramNames
|
||||
}
|
||||
if resultNames := ns.ResultNames; resultNames != nil {
|
||||
|
||||
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))
|
||||
}
|
||||
hf.ResultNames = resultNames
|
||||
}
|
||||
|
||||
nameToFunc[k] = hf
|
||||
funcNames = append(funcNames, k)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
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 {
|
||||
|
||||
@@ -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
|
||||
}{
|
||||
{
|
||||
@@ -42,20 +42,19 @@ func TestNewHostModule(t *testing.T) {
|
||||
{
|
||||
name: "funcs",
|
||||
moduleName: InternalModuleName,
|
||||
nameToGoFunc: map[string]interface{}{
|
||||
ArgsSizesGetName: argsSizesGet,
|
||||
FdWriteName: fdWrite,
|
||||
},
|
||||
funcToNames: map[string]*HostFuncNames{
|
||||
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{
|
||||
@@ -101,10 +100,8 @@ func TestNewHostModule(t *testing.T) {
|
||||
{
|
||||
name: "multi-value",
|
||||
moduleName: "swapper",
|
||||
nameToGoFunc: map[string]interface{}{
|
||||
swapName: swap,
|
||||
},
|
||||
funcToNames: map[string]*HostFuncNames{swapName: {}},
|
||||
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,22 +155,22 @@ 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": {}},
|
||||
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": {}},
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user