wasm: reduces allocs during import resolution (#1361)
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
This commit is contained in:
@@ -302,6 +302,9 @@ func TestCompiler_SliceAllocatedOnHeap(t *testing.T) {
|
||||
{Body: []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd}},
|
||||
},
|
||||
ImportSection: []wasm.Import{{Module: hostModuleName, Name: hostFnName, DescFunc: 1}},
|
||||
ImportPerModule: map[string][]*wasm.Import{
|
||||
hostModuleName: {{Module: hostModuleName, Name: hostFnName, DescFunc: 1}},
|
||||
},
|
||||
ExportSection: []wasm.Export{
|
||||
{Type: wasm.ExternTypeFunc, Index: 1, Name: stackCorruption},
|
||||
{Type: wasm.ExternTypeFunc, Index: 2, Name: callStackCorruption},
|
||||
|
||||
@@ -95,8 +95,9 @@ func RunTestEngine_MemoryGrowInRecursiveCall(t *testing.T, et EngineTester) {
|
||||
Body: []byte{wasm.OpcodeI32Const, 1, wasm.OpcodeMemoryGrow, wasm.OpcodeDrop, wasm.OpcodeEnd},
|
||||
},
|
||||
},
|
||||
MemorySection: &wasm.Memory{Max: 1000},
|
||||
ImportSection: []wasm.Import{{Module: hostModuleName, Name: hostFnName, DescFunc: 0}},
|
||||
MemorySection: &wasm.Memory{Max: 1000},
|
||||
ImportSection: []wasm.Import{{Module: hostModuleName, Name: hostFnName, DescFunc: 0}},
|
||||
ImportPerModule: map[string][]*wasm.Import{hostModuleName: {{Module: hostModuleName, Name: hostFnName, DescFunc: 0}}},
|
||||
}
|
||||
m.BuildFunctionDefinitions()
|
||||
m.BuildMemoryDefinitions()
|
||||
|
||||
@@ -106,7 +106,7 @@ func DecodeModule(
|
||||
case wasm.SectionIDType:
|
||||
m.TypeSection, err = decodeTypeSection(enabledFeatures, r)
|
||||
case wasm.SectionIDImport:
|
||||
m.ImportSection, m.ImportFunctionCount, m.ImportGlobalCount, m.ImportMemoryCount, m.ImportTableCount, err = decodeImportSection(r, memSizer, memoryLimitPages, enabledFeatures)
|
||||
m.ImportSection, m.ImportPerModule, m.ImportFunctionCount, m.ImportGlobalCount, m.ImportMemoryCount, m.ImportTableCount, err = decodeImportSection(r, memSizer, memoryLimitPages, enabledFeatures)
|
||||
if err != nil {
|
||||
return nil, err // avoid re-wrapping the error.
|
||||
}
|
||||
|
||||
@@ -52,38 +52,45 @@ func TestDecodeModule(t *testing.T) {
|
||||
ImportSection: []wasm.Import{
|
||||
{
|
||||
Module: "Math", Name: "Mul",
|
||||
Type: wasm.ExternTypeFunc,
|
||||
DescFunc: 1,
|
||||
Type: wasm.ExternTypeFunc,
|
||||
DescFunc: 1,
|
||||
IndexPerType: 0,
|
||||
},
|
||||
{
|
||||
Module: "foo", Name: "bar",
|
||||
Type: wasm.ExternTypeTable,
|
||||
DescTable: wasm.Table{Type: wasm.ValueTypeFuncref},
|
||||
Type: wasm.ExternTypeTable,
|
||||
DescTable: wasm.Table{Type: wasm.ValueTypeFuncref},
|
||||
IndexPerType: 0,
|
||||
},
|
||||
{
|
||||
Module: "Math", Name: "Add",
|
||||
Type: wasm.ExternTypeFunc,
|
||||
DescFunc: 0,
|
||||
Type: wasm.ExternTypeFunc,
|
||||
DescFunc: 0,
|
||||
IndexPerType: 1,
|
||||
},
|
||||
{
|
||||
Module: "bar", Name: "mem",
|
||||
Type: wasm.ExternTypeMemory,
|
||||
DescMem: &wasm.Memory{IsMaxEncoded: true},
|
||||
Type: wasm.ExternTypeMemory,
|
||||
DescMem: &wasm.Memory{IsMaxEncoded: true},
|
||||
IndexPerType: 0,
|
||||
},
|
||||
{
|
||||
Module: "foo", Name: "bar2",
|
||||
Type: wasm.ExternTypeGlobal,
|
||||
DescGlobal: wasm.GlobalType{ValType: wasm.ValueTypeI32},
|
||||
Type: wasm.ExternTypeGlobal,
|
||||
DescGlobal: wasm.GlobalType{ValType: wasm.ValueTypeI32},
|
||||
IndexPerType: 0,
|
||||
},
|
||||
{
|
||||
Module: "foo", Name: "bar3",
|
||||
Type: wasm.ExternTypeGlobal,
|
||||
DescGlobal: wasm.GlobalType{ValType: wasm.ValueTypeI32},
|
||||
Type: wasm.ExternTypeGlobal,
|
||||
DescGlobal: wasm.GlobalType{ValType: wasm.ValueTypeI32},
|
||||
IndexPerType: 1,
|
||||
},
|
||||
{
|
||||
Module: "foo", Name: "bar4",
|
||||
Type: wasm.ExternTypeGlobal,
|
||||
DescGlobal: wasm.GlobalType{ValType: wasm.ValueTypeI32},
|
||||
Type: wasm.ExternTypeGlobal,
|
||||
DescGlobal: wasm.GlobalType{ValType: wasm.ValueTypeI32},
|
||||
IndexPerType: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -121,6 +128,14 @@ func TestDecodeModule(t *testing.T) {
|
||||
tp := &(tc.input.TypeSection)[i]
|
||||
_ = tp.String()
|
||||
}
|
||||
if len(tc.input.ImportSection) > 0 {
|
||||
expImportPerModule := make(map[string][]*wasm.Import)
|
||||
for i := range m.ImportSection {
|
||||
imp := &m.ImportSection[i]
|
||||
expImportPerModule[imp.Module] = append(expImportPerModule[imp.Module], imp)
|
||||
}
|
||||
tc.input.ImportPerModule = expImportPerModule
|
||||
}
|
||||
require.Equal(t, tc.input, m)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -31,13 +31,17 @@ func decodeImportSection(
|
||||
memorySizer memorySizer,
|
||||
memoryLimitPages uint32,
|
||||
enabledFeatures api.CoreFeatures,
|
||||
) (result []wasm.Import, funcCount, globalCount, memoryCount, tableCount wasm.Index, err error) {
|
||||
) (result []wasm.Import,
|
||||
perModule map[string][]*wasm.Import,
|
||||
funcCount, globalCount, memoryCount, tableCount wasm.Index, err error,
|
||||
) {
|
||||
vs, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("get size of vector: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
perModule = make(map[string][]*wasm.Import)
|
||||
result = make([]wasm.Import, vs)
|
||||
for i := uint32(0); i < vs; i++ {
|
||||
imp := &result[i]
|
||||
@@ -46,14 +50,19 @@ func decodeImportSection(
|
||||
}
|
||||
switch imp.Type {
|
||||
case wasm.ExternTypeFunc:
|
||||
imp.IndexPerType = funcCount
|
||||
funcCount++
|
||||
case wasm.ExternTypeGlobal:
|
||||
imp.IndexPerType = globalCount
|
||||
globalCount++
|
||||
case wasm.ExternTypeMemory:
|
||||
imp.IndexPerType = memoryCount
|
||||
memoryCount++
|
||||
case wasm.ExternTypeTable:
|
||||
imp.IndexPerType = tableCount
|
||||
tableCount++
|
||||
}
|
||||
perModule[imp.Module] = append(perModule[imp.Module], imp)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -48,6 +48,9 @@ type Module struct {
|
||||
ImportGlobalCount,
|
||||
ImportMemoryCount,
|
||||
ImportTableCount Index
|
||||
// ImportPerModule maps a module name to the list of Import to be imported from the module.
|
||||
// This is used to do fast import resolution during instantiation.
|
||||
ImportPerModule map[string][]*Import
|
||||
|
||||
// FunctionSection contains the index in TypeSection of each function defined in this module.
|
||||
//
|
||||
@@ -718,6 +721,8 @@ type Import struct {
|
||||
DescMem *Memory
|
||||
// DescGlobal is the inlined GlobalType when Type equals ExternTypeGlobal
|
||||
DescGlobal GlobalType
|
||||
// IndexPerType has the index of this import per ExternType.
|
||||
IndexPerType Index
|
||||
}
|
||||
|
||||
// Memory describes the limits of pages (64KB) in a memory.
|
||||
|
||||
@@ -281,21 +281,8 @@ func (s *Store) Instantiate(
|
||||
sys *internalsys.Context,
|
||||
typeIDs []FunctionTypeID,
|
||||
) (*ModuleInstance, error) {
|
||||
// Collect any imported modules to avoid locking the store too long.
|
||||
importedModuleNames := map[string]struct{}{}
|
||||
for i := range module.ImportSection {
|
||||
imp := &module.ImportSection[i]
|
||||
importedModuleNames[imp.Module] = struct{}{}
|
||||
}
|
||||
|
||||
// Read-Lock the store and ensure imports needed are present.
|
||||
importedModules, err := s.requireModules(importedModuleNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Instantiate the module and add it to the store so that other modules can import it.
|
||||
m, err := s.instantiate(ctx, module, name, sys, importedModules, typeIDs)
|
||||
m, err := s.instantiate(ctx, module, name, sys, typeIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -313,7 +300,6 @@ func (s *Store) instantiate(
|
||||
module *Module,
|
||||
name string,
|
||||
sysCtx *internalsys.Context,
|
||||
modules map[string]*ModuleInstance,
|
||||
typeIDs []FunctionTypeID,
|
||||
) (m *ModuleInstance, err error) {
|
||||
m = &ModuleInstance{ModuleName: name, TypeIDs: typeIDs, Sys: sysCtx, s: s, Definitions: module.FunctionDefinitionSection}
|
||||
@@ -325,7 +311,7 @@ func (s *Store) instantiate(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = m.resolveImports(module, modules); err != nil {
|
||||
if err = m.resolveImports(module); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -373,109 +359,106 @@ func (s *Store) instantiate(
|
||||
return
|
||||
}
|
||||
|
||||
func (m *ModuleInstance) resolveImports(module *Module, importedModules map[string]*ModuleInstance) (err error) {
|
||||
var fs, gs, tables Index
|
||||
for idx := range module.ImportSection {
|
||||
i := &module.ImportSection[idx]
|
||||
importedModule, ok := importedModules[i.Module]
|
||||
if !ok {
|
||||
err = fmt.Errorf("module[%s] not instantiated", i.Module)
|
||||
return
|
||||
}
|
||||
|
||||
var imported *Export
|
||||
imported, err = importedModule.getExport(i.Name, i.Type)
|
||||
func (m *ModuleInstance) resolveImports(module *Module) (err error) {
|
||||
for moduleName, imports := range module.ImportPerModule {
|
||||
var importedModule *ModuleInstance
|
||||
importedModule, err = m.s.module(moduleName)
|
||||
if err != nil {
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
switch i.Type {
|
||||
case ExternTypeFunc:
|
||||
expectedType := &module.TypeSection[i.DescFunc]
|
||||
actual := &importedModule.Definitions[imported.Index]
|
||||
if !actual.funcType.EqualsSignature(expectedType.Params, expectedType.Results) {
|
||||
err = errorInvalidImport(i, idx, fmt.Errorf("signature mismatch: %s != %s", expectedType, actual.funcType))
|
||||
for _, i := range imports {
|
||||
var imported *Export
|
||||
imported, err = importedModule.getExport(i.Name, i.Type)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.Engine.ResolveImportedFunction(fs, imported.Index, importedModule.Engine)
|
||||
fs++
|
||||
case ExternTypeTable:
|
||||
expected := i.DescTable
|
||||
importedTable := importedModule.Tables[imported.Index]
|
||||
if expected.Type != importedTable.Type {
|
||||
err = errorInvalidImport(i, idx, fmt.Errorf("table type mismatch: %s != %s",
|
||||
RefTypeName(expected.Type), RefTypeName(importedTable.Type)))
|
||||
return
|
||||
}
|
||||
|
||||
if expected.Min > importedTable.Min {
|
||||
err = errorMinSizeMismatch(i, idx, expected.Min, importedTable.Min)
|
||||
return
|
||||
}
|
||||
|
||||
if expected.Max != nil {
|
||||
expectedMax := *expected.Max
|
||||
if importedTable.Max == nil {
|
||||
err = errorNoMax(i, idx, expectedMax)
|
||||
return
|
||||
} else if expectedMax < *importedTable.Max {
|
||||
err = errorMaxSizeMismatch(i, idx, expectedMax, *importedTable.Max)
|
||||
switch i.Type {
|
||||
case ExternTypeFunc:
|
||||
expectedType := &module.TypeSection[i.DescFunc]
|
||||
actual := &importedModule.Definitions[imported.Index]
|
||||
if !actual.funcType.EqualsSignature(expectedType.Params, expectedType.Results) {
|
||||
err = errorInvalidImport(i, fmt.Errorf("signature mismatch: %s != %s", expectedType, actual.funcType))
|
||||
return
|
||||
}
|
||||
}
|
||||
m.Tables[tables] = importedTable
|
||||
tables++
|
||||
case ExternTypeMemory:
|
||||
expected := i.DescMem
|
||||
importedMemory := importedModule.MemoryInstance
|
||||
|
||||
if expected.Min > memoryBytesNumToPages(uint64(len(importedMemory.Buffer))) {
|
||||
err = errorMinSizeMismatch(i, idx, expected.Min, importedMemory.Min)
|
||||
return
|
||||
}
|
||||
m.Engine.ResolveImportedFunction(i.IndexPerType, imported.Index, importedModule.Engine)
|
||||
case ExternTypeTable:
|
||||
expected := i.DescTable
|
||||
importedTable := importedModule.Tables[imported.Index]
|
||||
if expected.Type != importedTable.Type {
|
||||
err = errorInvalidImport(i, fmt.Errorf("table type mismatch: %s != %s",
|
||||
RefTypeName(expected.Type), RefTypeName(importedTable.Type)))
|
||||
return
|
||||
}
|
||||
|
||||
if expected.Max < importedMemory.Max {
|
||||
err = errorMaxSizeMismatch(i, idx, expected.Max, importedMemory.Max)
|
||||
return
|
||||
}
|
||||
m.MemoryInstance = importedMemory
|
||||
case ExternTypeGlobal:
|
||||
expected := i.DescGlobal
|
||||
importedGlobal := importedModule.Globals[imported.Index]
|
||||
if expected.Min > importedTable.Min {
|
||||
err = errorMinSizeMismatch(i, expected.Min, importedTable.Min)
|
||||
return
|
||||
}
|
||||
|
||||
if expected.Mutable != importedGlobal.Type.Mutable {
|
||||
err = errorInvalidImport(i, idx, fmt.Errorf("mutability mismatch: %t != %t",
|
||||
expected.Mutable, importedGlobal.Type.Mutable))
|
||||
return
|
||||
}
|
||||
if expected.Max != nil {
|
||||
expectedMax := *expected.Max
|
||||
if importedTable.Max == nil {
|
||||
err = errorNoMax(i, expectedMax)
|
||||
return
|
||||
} else if expectedMax < *importedTable.Max {
|
||||
err = errorMaxSizeMismatch(i, expectedMax, *importedTable.Max)
|
||||
return
|
||||
}
|
||||
}
|
||||
m.Tables[i.IndexPerType] = importedTable
|
||||
case ExternTypeMemory:
|
||||
expected := i.DescMem
|
||||
importedMemory := importedModule.MemoryInstance
|
||||
|
||||
if expected.ValType != importedGlobal.Type.ValType {
|
||||
err = errorInvalidImport(i, idx, fmt.Errorf("value type mismatch: %s != %s",
|
||||
ValueTypeName(expected.ValType), ValueTypeName(importedGlobal.Type.ValType)))
|
||||
return
|
||||
if expected.Min > memoryBytesNumToPages(uint64(len(importedMemory.Buffer))) {
|
||||
err = errorMinSizeMismatch(i, expected.Min, importedMemory.Min)
|
||||
return
|
||||
}
|
||||
|
||||
if expected.Max < importedMemory.Max {
|
||||
err = errorMaxSizeMismatch(i, expected.Max, importedMemory.Max)
|
||||
return
|
||||
}
|
||||
m.MemoryInstance = importedMemory
|
||||
case ExternTypeGlobal:
|
||||
expected := i.DescGlobal
|
||||
importedGlobal := importedModule.Globals[imported.Index]
|
||||
|
||||
if expected.Mutable != importedGlobal.Type.Mutable {
|
||||
err = errorInvalidImport(i, fmt.Errorf("mutability mismatch: %t != %t",
|
||||
expected.Mutable, importedGlobal.Type.Mutable))
|
||||
return
|
||||
}
|
||||
|
||||
if expected.ValType != importedGlobal.Type.ValType {
|
||||
err = errorInvalidImport(i, fmt.Errorf("value type mismatch: %s != %s",
|
||||
ValueTypeName(expected.ValType), ValueTypeName(importedGlobal.Type.ValType)))
|
||||
return
|
||||
}
|
||||
m.Globals[i.IndexPerType] = importedGlobal
|
||||
}
|
||||
m.Globals[gs] = importedGlobal
|
||||
gs++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func errorMinSizeMismatch(i *Import, idx int, expected, actual uint32) error {
|
||||
return errorInvalidImport(i, idx, fmt.Errorf("minimum size mismatch: %d > %d", expected, actual))
|
||||
func errorMinSizeMismatch(i *Import, expected, actual uint32) error {
|
||||
return errorInvalidImport(i, fmt.Errorf("minimum size mismatch: %d > %d", expected, actual))
|
||||
}
|
||||
|
||||
func errorNoMax(i *Import, idx int, expected uint32) error {
|
||||
return errorInvalidImport(i, idx, fmt.Errorf("maximum size mismatch: %d, but actual has no max", expected))
|
||||
func errorNoMax(i *Import, expected uint32) error {
|
||||
return errorInvalidImport(i, fmt.Errorf("maximum size mismatch: %d, but actual has no max", expected))
|
||||
}
|
||||
|
||||
func errorMaxSizeMismatch(i *Import, idx int, expected, actual uint32) error {
|
||||
return errorInvalidImport(i, idx, fmt.Errorf("maximum size mismatch: %d < %d", expected, actual))
|
||||
func errorMaxSizeMismatch(i *Import, expected, actual uint32) error {
|
||||
return errorInvalidImport(i, fmt.Errorf("maximum size mismatch: %d < %d", expected, actual))
|
||||
}
|
||||
|
||||
func errorInvalidImport(i *Import, idx int, err error) error {
|
||||
return fmt.Errorf("import[%d] %s[%s.%s]: %w", idx, ExternTypeName(i.Type), i.Module, i.Name, err)
|
||||
func errorInvalidImport(i *Import, err error) error {
|
||||
return fmt.Errorf("import %s[%s.%s]: %w", ExternTypeName(i.Type), i.Module, i.Name, err)
|
||||
}
|
||||
|
||||
// executeConstExpressionI32 executes the ConstantExpression which returns ValueTypeI32.
|
||||
|
||||
@@ -39,32 +39,11 @@ func (s *Store) module(moduleName string) (*ModuleInstance, error) {
|
||||
defer s.mux.RUnlock()
|
||||
m, ok := s.nameToModule[moduleName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("module[%s] not in store", moduleName)
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
return nil, fmt.Errorf("module[%s] not set in store", moduleName)
|
||||
return nil, fmt.Errorf("module[%s] not instantiated", moduleName)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// requireModules returns all instantiated modules whose names equal the keys in the input, or errs if any are missing.
|
||||
func (s *Store) requireModules(moduleNames map[string]struct{}) (map[string]*ModuleInstance, error) {
|
||||
ret := make(map[string]*ModuleInstance, len(moduleNames))
|
||||
|
||||
s.mux.RLock()
|
||||
defer s.mux.RUnlock()
|
||||
|
||||
for n := range moduleNames {
|
||||
module, ok := s.nameToModule[n]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("module[%s] not instantiated", n)
|
||||
}
|
||||
ret[n] = module
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// registerModule registers a ModuleInstance into the store.
|
||||
// This makes the ModuleInstance visible for import if it's not anonymous, and ensures it is closed when the store is.
|
||||
func (s *Store) registerModule(m *ModuleInstance) error {
|
||||
|
||||
@@ -102,29 +102,6 @@ func TestStore_module(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestStore_requireModules(t *testing.T) {
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
s, m1, _ := newTestStore()
|
||||
|
||||
modules, err := s.requireModules(map[string]struct{}{m1.ModuleName: {}})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, map[string]*ModuleInstance{m1.ModuleName: m1}, modules)
|
||||
})
|
||||
t.Run("module not instantiated", func(t *testing.T) {
|
||||
s, _, _ := newTestStore()
|
||||
|
||||
_, err := s.requireModules(map[string]struct{}{"unknown": {}})
|
||||
require.EqualError(t, err, "module[unknown] not instantiated")
|
||||
})
|
||||
t.Run("store closed", func(t *testing.T) {
|
||||
s, _, _ := newTestStore()
|
||||
require.NoError(t, s.CloseWithExitCode(context.Background(), 0))
|
||||
|
||||
_, err := s.requireModules(map[string]struct{}{"unknown": {}})
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStore_AliasModule(t *testing.T) {
|
||||
s := newStore()
|
||||
m1 := &ModuleInstance{ModuleName: "m1"}
|
||||
|
||||
@@ -328,6 +328,10 @@ func TestStore_Instantiate_Errors(t *testing.T) {
|
||||
// But the second one tries to import uninitialized-module ->
|
||||
{Type: ExternTypeFunc, Module: "non-exist", Name: "fn", DescFunc: 0},
|
||||
},
|
||||
ImportPerModule: map[string][]*Import{
|
||||
importedModuleName: {{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0}},
|
||||
"non-exist": {{Name: "fn", DescFunc: 0}},
|
||||
},
|
||||
}, importingModuleName, nil, nil)
|
||||
require.EqualError(t, err, "module[non-exist] not instantiated")
|
||||
})
|
||||
@@ -653,22 +657,20 @@ func Test_resolveImports(t *testing.T) {
|
||||
const name = "target"
|
||||
|
||||
t.Run("module not instantiated", func(t *testing.T) {
|
||||
modules := map[string]*ModuleInstance{}
|
||||
m := &ModuleInstance{}
|
||||
err := m.resolveImports(&Module{ImportSection: []Import{{Module: "unknown", Name: "unknown"}}}, modules)
|
||||
m := &ModuleInstance{s: newStore()}
|
||||
err := m.resolveImports(&Module{ImportPerModule: map[string][]*Import{"unknown": {{}}}})
|
||||
require.EqualError(t, err, "module[unknown] not instantiated")
|
||||
})
|
||||
t.Run("export instance not found", func(t *testing.T) {
|
||||
modules := map[string]*ModuleInstance{
|
||||
moduleName: {Exports: map[string]*Export{}, ModuleName: moduleName},
|
||||
}
|
||||
m := &ModuleInstance{}
|
||||
err := m.resolveImports(&Module{ImportSection: []Import{{Module: moduleName, Name: "unknown"}}}, modules)
|
||||
m := &ModuleInstance{s: newStore()}
|
||||
m.s.nameToModule[moduleName] = &ModuleInstance{Exports: map[string]*Export{}, ModuleName: moduleName}
|
||||
err := m.resolveImports(&Module{ImportPerModule: map[string][]*Import{moduleName: {{Name: "unknown"}}}})
|
||||
require.EqualError(t, err, "\"unknown\" is not exported in module \"test\"")
|
||||
})
|
||||
t.Run("func", func(t *testing.T) {
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
externMod := &ModuleInstance{
|
||||
s := newStore()
|
||||
s.nameToModule[moduleName] = &ModuleInstance{
|
||||
Exports: map[string]*Export{
|
||||
name: {Type: ExternTypeFunc, Index: 2},
|
||||
"": {Type: ExternTypeFunc, Index: 4},
|
||||
@@ -682,22 +684,21 @@ func Test_resolveImports(t *testing.T) {
|
||||
{funcType: &FunctionType{Params: []ValueType{ExternTypeFunc}, Results: []ValueType{}}},
|
||||
},
|
||||
}
|
||||
importedModules := map[string]*ModuleInstance{
|
||||
moduleName: externMod,
|
||||
}
|
||||
module := &Module{
|
||||
TypeSection: []FunctionType{
|
||||
{Params: []ValueType{i32}, Results: []ValueType{ValueTypeV128}},
|
||||
{Params: []ValueType{ExternTypeFunc}},
|
||||
},
|
||||
ImportSection: []Import{
|
||||
{Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 0},
|
||||
{Module: moduleName, Name: "", Type: ExternTypeFunc, DescFunc: 1},
|
||||
ImportPerModule: map[string][]*Import{
|
||||
moduleName: {
|
||||
{Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 0, IndexPerType: 0},
|
||||
{Module: moduleName, Name: "", Type: ExternTypeFunc, DescFunc: 1, IndexPerType: 1},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
m := &ModuleInstance{Engine: &mockModuleEngine{resolveImportsCalled: map[Index]Index{}}}
|
||||
err := m.resolveImports(module, importedModules)
|
||||
m := &ModuleInstance{Engine: &mockModuleEngine{resolveImportsCalled: map[Index]Index{}}, s: s}
|
||||
err := m.resolveImports(module)
|
||||
require.NoError(t, err)
|
||||
|
||||
me := m.Engine.(*mockModuleEngine)
|
||||
@@ -705,7 +706,8 @@ func Test_resolveImports(t *testing.T) {
|
||||
require.Equal(t, me.resolveImportsCalled[1], Index(4))
|
||||
})
|
||||
t.Run("signature mismatch", func(t *testing.T) {
|
||||
externMod := &ModuleInstance{
|
||||
s := newStore()
|
||||
s.nameToModule[moduleName] = &ModuleInstance{
|
||||
Exports: map[string]*Export{
|
||||
name: {Type: ExternTypeFunc, Index: 0},
|
||||
},
|
||||
@@ -714,110 +716,127 @@ func Test_resolveImports(t *testing.T) {
|
||||
Definitions: []FunctionDefinition{{funcType: &FunctionType{}}},
|
||||
}
|
||||
module := &Module{
|
||||
TypeSection: []FunctionType{{Results: []ValueType{ValueTypeF32}}},
|
||||
ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 0}},
|
||||
TypeSection: []FunctionType{{Results: []ValueType{ValueTypeF32}}},
|
||||
ImportPerModule: map[string][]*Import{
|
||||
moduleName: {{Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 0}},
|
||||
},
|
||||
}
|
||||
|
||||
m := &ModuleInstance{Engine: &mockModuleEngine{resolveImportsCalled: map[Index]Index{}}}
|
||||
err := m.resolveImports(module, map[string]*ModuleInstance{moduleName: externMod})
|
||||
require.EqualError(t, err, "import[0] func[test.target]: signature mismatch: v_f32 != v_v")
|
||||
m := &ModuleInstance{Engine: &mockModuleEngine{resolveImportsCalled: map[Index]Index{}}, s: s}
|
||||
err := m.resolveImports(module)
|
||||
require.EqualError(t, err, "import func[test.target]: signature mismatch: v_f32 != v_v")
|
||||
})
|
||||
})
|
||||
t.Run("global", func(t *testing.T) {
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
s := newStore()
|
||||
g := &GlobalInstance{Type: GlobalType{ValType: ValueTypeI32}}
|
||||
m := &ModuleInstance{Globals: make([]*GlobalInstance, 1)}
|
||||
m := &ModuleInstance{Globals: make([]*GlobalInstance, 1), s: s}
|
||||
s.nameToModule[moduleName] = &ModuleInstance{
|
||||
Globals: []*GlobalInstance{g},
|
||||
Exports: map[string]*Export{name: {Type: ExternTypeGlobal, Index: 0}}, ModuleName: moduleName,
|
||||
}
|
||||
err := m.resolveImports(
|
||||
&Module{ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: g.Type}}},
|
||||
map[string]*ModuleInstance{
|
||||
moduleName: {
|
||||
Globals: []*GlobalInstance{g},
|
||||
Exports: map[string]*Export{name: {Type: ExternTypeGlobal, Index: 0}}, ModuleName: moduleName,
|
||||
},
|
||||
&Module{
|
||||
ImportPerModule: map[string][]*Import{moduleName: {{Name: name, Type: ExternTypeGlobal, DescGlobal: g.Type}}},
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.True(t, globalsContain(m.Globals, g), "expected to find %v in %v", g, m.Globals)
|
||||
})
|
||||
t.Run("mutability mismatch", func(t *testing.T) {
|
||||
importedModules := map[string]*ModuleInstance{
|
||||
moduleName: {
|
||||
Globals: []*GlobalInstance{{Type: GlobalType{Mutable: false}}},
|
||||
Exports: map[string]*Export{name: {
|
||||
Type: ExternTypeGlobal,
|
||||
Index: 0,
|
||||
}},
|
||||
ModuleName: moduleName,
|
||||
},
|
||||
s := newStore()
|
||||
s.nameToModule[moduleName] = &ModuleInstance{
|
||||
Globals: []*GlobalInstance{{Type: GlobalType{Mutable: false}}},
|
||||
Exports: map[string]*Export{name: {
|
||||
Type: ExternTypeGlobal,
|
||||
Index: 0,
|
||||
}},
|
||||
ModuleName: moduleName,
|
||||
}
|
||||
m := &ModuleInstance{Globals: make([]*GlobalInstance, 1)}
|
||||
err := m.resolveImports(&Module{ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: GlobalType{Mutable: true}}}}, importedModules)
|
||||
require.EqualError(t, err, "import[0] global[test.target]: mutability mismatch: true != false")
|
||||
m := &ModuleInstance{Globals: make([]*GlobalInstance, 1), s: s}
|
||||
err := m.resolveImports(&Module{
|
||||
ImportPerModule: map[string][]*Import{moduleName: {
|
||||
{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: GlobalType{Mutable: true}},
|
||||
}},
|
||||
})
|
||||
require.EqualError(t, err, "import global[test.target]: mutability mismatch: true != false")
|
||||
})
|
||||
t.Run("type mismatch", func(t *testing.T) {
|
||||
importedModules := map[string]*ModuleInstance{
|
||||
moduleName: {
|
||||
Globals: []*GlobalInstance{{Type: GlobalType{ValType: ValueTypeI32}}},
|
||||
Exports: map[string]*Export{name: {
|
||||
Type: ExternTypeGlobal,
|
||||
Index: 0,
|
||||
}},
|
||||
ModuleName: moduleName,
|
||||
},
|
||||
s := newStore()
|
||||
s.nameToModule[moduleName] = &ModuleInstance{
|
||||
Globals: []*GlobalInstance{{Type: GlobalType{ValType: ValueTypeI32}}},
|
||||
Exports: map[string]*Export{name: {
|
||||
Type: ExternTypeGlobal,
|
||||
Index: 0,
|
||||
}},
|
||||
ModuleName: moduleName,
|
||||
}
|
||||
m := &ModuleInstance{Globals: make([]*GlobalInstance, 1)}
|
||||
err := m.resolveImports(&Module{ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeF64}}}}, importedModules)
|
||||
require.EqualError(t, err, "import[0] global[test.target]: value type mismatch: f64 != i32")
|
||||
m := &ModuleInstance{Globals: make([]*GlobalInstance, 1), s: s}
|
||||
err := m.resolveImports(&Module{
|
||||
ImportPerModule: map[string][]*Import{moduleName: {
|
||||
{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeF64}},
|
||||
}},
|
||||
})
|
||||
require.EqualError(t, err, "import global[test.target]: value type mismatch: f64 != i32")
|
||||
})
|
||||
})
|
||||
t.Run("memory", func(t *testing.T) {
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
max := uint32(10)
|
||||
memoryInst := &MemoryInstance{Max: max}
|
||||
importedModules := map[string]*ModuleInstance{
|
||||
moduleName: {
|
||||
MemoryInstance: memoryInst,
|
||||
Exports: map[string]*Export{name: {
|
||||
Type: ExternTypeMemory,
|
||||
}},
|
||||
ModuleName: moduleName,
|
||||
},
|
||||
s := newStore()
|
||||
s.nameToModule[moduleName] = &ModuleInstance{
|
||||
MemoryInstance: memoryInst,
|
||||
Exports: map[string]*Export{name: {
|
||||
Type: ExternTypeMemory,
|
||||
}},
|
||||
ModuleName: moduleName,
|
||||
}
|
||||
m := &ModuleInstance{}
|
||||
err := m.resolveImports(&Module{ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: &Memory{Max: max}}}}, importedModules)
|
||||
m := &ModuleInstance{s: s}
|
||||
err := m.resolveImports(&Module{
|
||||
ImportPerModule: map[string][]*Import{
|
||||
moduleName: {{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: &Memory{Max: max}}},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, m.MemoryInstance, memoryInst)
|
||||
})
|
||||
t.Run("minimum size mismatch", func(t *testing.T) {
|
||||
importMemoryType := &Memory{Min: 2, Cap: 2}
|
||||
importedModules := map[string]*ModuleInstance{
|
||||
moduleName: {
|
||||
MemoryInstance: &MemoryInstance{Min: importMemoryType.Min - 1, Cap: 2},
|
||||
Exports: map[string]*Export{name: {
|
||||
Type: ExternTypeMemory,
|
||||
}},
|
||||
ModuleName: moduleName,
|
||||
},
|
||||
s := newStore()
|
||||
s.nameToModule[moduleName] = &ModuleInstance{
|
||||
MemoryInstance: &MemoryInstance{Min: importMemoryType.Min - 1, Cap: 2},
|
||||
Exports: map[string]*Export{name: {
|
||||
Type: ExternTypeMemory,
|
||||
}},
|
||||
ModuleName: moduleName,
|
||||
}
|
||||
m := &ModuleInstance{}
|
||||
err := m.resolveImports(&Module{ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: importMemoryType}}}, importedModules)
|
||||
require.EqualError(t, err, "import[0] memory[test.target]: minimum size mismatch: 2 > 1")
|
||||
m := &ModuleInstance{s: s}
|
||||
err := m.resolveImports(&Module{
|
||||
ImportPerModule: map[string][]*Import{
|
||||
moduleName: {{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: importMemoryType}},
|
||||
},
|
||||
})
|
||||
require.EqualError(t, err, "import memory[test.target]: minimum size mismatch: 2 > 1")
|
||||
})
|
||||
t.Run("maximum size mismatch", func(t *testing.T) {
|
||||
s := newStore()
|
||||
s.nameToModule[moduleName] = &ModuleInstance{
|
||||
MemoryInstance: &MemoryInstance{Max: MemoryLimitPages},
|
||||
Exports: map[string]*Export{name: {
|
||||
Type: ExternTypeMemory,
|
||||
}},
|
||||
ModuleName: moduleName,
|
||||
}
|
||||
|
||||
max := uint32(10)
|
||||
importMemoryType := &Memory{Max: max}
|
||||
modules := map[string]*ModuleInstance{
|
||||
moduleName: {
|
||||
MemoryInstance: &MemoryInstance{Max: MemoryLimitPages},
|
||||
Exports: map[string]*Export{name: {
|
||||
Type: ExternTypeMemory,
|
||||
}},
|
||||
ModuleName: moduleName,
|
||||
},
|
||||
}
|
||||
m := &ModuleInstance{}
|
||||
err := m.resolveImports(&Module{ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: importMemoryType}}}, modules)
|
||||
require.EqualError(t, err, "import[0] memory[test.target]: maximum size mismatch: 10 < 65536")
|
||||
m := &ModuleInstance{s: s}
|
||||
err := m.resolveImports(&Module{
|
||||
ImportPerModule: map[string][]*Import{moduleName: {{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: importMemoryType}}},
|
||||
})
|
||||
require.EqualError(t, err, "import memory[test.target]: maximum size mismatch: 10 < 65536")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -22,58 +22,68 @@ func Test_resolveImports_table(t *testing.T) {
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
max := uint32(10)
|
||||
tableInst := &TableInstance{Max: &max}
|
||||
importedModules := map[string]*ModuleInstance{
|
||||
moduleName: {
|
||||
Tables: []*TableInstance{tableInst},
|
||||
Exports: map[string]*Export{name: {Type: ExternTypeTable, Index: 0}},
|
||||
ModuleName: moduleName,
|
||||
},
|
||||
s := newStore()
|
||||
s.nameToModule[moduleName] = &ModuleInstance{
|
||||
Tables: []*TableInstance{tableInst},
|
||||
Exports: map[string]*Export{name: {Type: ExternTypeTable, Index: 0}},
|
||||
ModuleName: moduleName,
|
||||
}
|
||||
m := &ModuleInstance{Tables: make([]*TableInstance, 1)}
|
||||
err := m.resolveImports(&Module{ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: Table{Max: &max}}}}, importedModules)
|
||||
m := &ModuleInstance{Tables: make([]*TableInstance, 1), s: s}
|
||||
err := m.resolveImports(&Module{
|
||||
ImportPerModule: map[string][]*Import{
|
||||
moduleName: {{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: Table{Max: &max}}},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, m.Tables[0], tableInst)
|
||||
})
|
||||
t.Run("minimum size mismatch", func(t *testing.T) {
|
||||
s := newStore()
|
||||
importTableType := Table{Min: 2}
|
||||
importedModules := map[string]*ModuleInstance{
|
||||
moduleName: {
|
||||
Tables: []*TableInstance{{Min: importTableType.Min - 1}},
|
||||
Exports: map[string]*Export{name: {Type: ExternTypeTable}},
|
||||
ModuleName: moduleName,
|
||||
},
|
||||
s.nameToModule[moduleName] = &ModuleInstance{
|
||||
Tables: []*TableInstance{{Min: importTableType.Min - 1}},
|
||||
Exports: map[string]*Export{name: {Type: ExternTypeTable}},
|
||||
ModuleName: moduleName,
|
||||
}
|
||||
m := &ModuleInstance{Tables: make([]*TableInstance, 1)}
|
||||
err := m.resolveImports(&Module{ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: importTableType}}}, importedModules)
|
||||
require.EqualError(t, err, "import[0] table[test.target]: minimum size mismatch: 2 > 1")
|
||||
m := &ModuleInstance{Tables: make([]*TableInstance, 1), s: s}
|
||||
err := m.resolveImports(&Module{
|
||||
ImportPerModule: map[string][]*Import{
|
||||
moduleName: {{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: importTableType}},
|
||||
},
|
||||
})
|
||||
require.EqualError(t, err, "import table[test.target]: minimum size mismatch: 2 > 1")
|
||||
})
|
||||
t.Run("maximum size mismatch", func(t *testing.T) {
|
||||
max := uint32(10)
|
||||
importTableType := Table{Max: &max}
|
||||
importedModules := map[string]*ModuleInstance{
|
||||
moduleName: {
|
||||
Tables: []*TableInstance{{Min: importTableType.Min - 1}},
|
||||
Exports: map[string]*Export{name: {Type: ExternTypeTable}},
|
||||
ModuleName: moduleName,
|
||||
},
|
||||
s := newStore()
|
||||
s.nameToModule[moduleName] = &ModuleInstance{
|
||||
Tables: []*TableInstance{{Min: importTableType.Min - 1}},
|
||||
Exports: map[string]*Export{name: {Type: ExternTypeTable}},
|
||||
ModuleName: moduleName,
|
||||
}
|
||||
m := &ModuleInstance{Tables: make([]*TableInstance, 1)}
|
||||
err := m.resolveImports(&Module{ImportSection: []Import{{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: importTableType}}}, importedModules)
|
||||
require.EqualError(t, err, "import[0] table[test.target]: maximum size mismatch: 10, but actual has no max")
|
||||
m := &ModuleInstance{Tables: make([]*TableInstance, 1), s: s}
|
||||
err := m.resolveImports(&Module{
|
||||
ImportPerModule: map[string][]*Import{
|
||||
moduleName: {{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: importTableType}},
|
||||
},
|
||||
})
|
||||
require.EqualError(t, err, "import table[test.target]: maximum size mismatch: 10, but actual has no max")
|
||||
})
|
||||
t.Run("type mismatch", func(t *testing.T) {
|
||||
importedModules := map[string]*ModuleInstance{
|
||||
moduleName: {
|
||||
Tables: []*TableInstance{{Type: RefTypeFuncref}},
|
||||
Exports: map[string]*Export{name: {Type: ExternTypeTable}},
|
||||
ModuleName: moduleName,
|
||||
},
|
||||
s := newStore()
|
||||
s.nameToModule[moduleName] = &ModuleInstance{
|
||||
Tables: []*TableInstance{{Type: RefTypeFuncref}},
|
||||
Exports: map[string]*Export{name: {Type: ExternTypeTable}},
|
||||
ModuleName: moduleName,
|
||||
}
|
||||
m := &ModuleInstance{Tables: make([]*TableInstance, 1)}
|
||||
err := m.resolveImports(&Module{ImportSection: []Import{
|
||||
{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: Table{Type: RefTypeExternref}},
|
||||
}}, importedModules)
|
||||
require.EqualError(t, err, "import[0] table[test.target]: table type mismatch: externref != funcref")
|
||||
m := &ModuleInstance{Tables: make([]*TableInstance, 1), s: s}
|
||||
err := m.resolveImports(&Module{
|
||||
ImportPerModule: map[string][]*Import{
|
||||
moduleName: {{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: Table{Type: RefTypeExternref}}},
|
||||
},
|
||||
})
|
||||
require.EqualError(t, err, "import table[test.target]: table type mismatch: externref != funcref")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user