wasm: reduces allocs during import resolution (#1361)

Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
This commit is contained in:
Takeshi Yoneda
2023-04-12 20:35:04 -07:00
committed by GitHub
parent a979e0f643
commit 8ac9a54923
11 changed files with 283 additions and 282 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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