Repository: tetratelabs/wazero
Pull request #1361 (2023-04-13 02:04:14)

wasm: reduces allocs during import resolution
```
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero/internal/integration_test/bench
                                       │   old.txt   │              new.txt               │
                                       │   sec/op    │   sec/op     vs base               │
Initialization/interpreter-10            14.48µ ± 0%   14.23µ ± 1%  -1.76% (p=0.000 n=30)
Initialization/interpreter-multiple-10   17.90µ ± 5%   17.82µ ± 2%       ~ (p=0.139 n=30)
Initialization/compiler-10               16.69µ ± 1%   15.87µ ± 1%  -4.89% (p=0.000 n=30)
Initialization/compiler-multiple-10      19.53µ ± 1%   18.73µ ± 1%  -4.10% (p=0.000 n=30)
geomean                                  17.05µ        16.57µ       -2.83%

                                       │   old.txt    │               new.txt               │
                                       │     B/op     │     B/op      vs base               │
Initialization/interpreter-10            132.5Ki ± 0%   132.3Ki ± 0%  -0.19% (p=0.000 n=30)
Initialization/interpreter-multiple-10   132.6Ki ± 0%   132.3Ki ± 0%  -0.19% (p=0.000 n=30)
Initialization/compiler-10               132.5Ki ± 0%   132.3Ki ± 0%  -0.19% (p=0.000 n=30)
Initialization/compiler-multiple-10      132.6Ki ± 0%   132.3Ki ± 0%  -0.19% (p=0.000 n=30)
geomean                                  132.6Ki        132.3Ki       -0.19%

                                       │  old.txt   │              new.txt              │
                                       │ allocs/op  │ allocs/op   vs base               │
Initialization/interpreter-10            25.00 ± 0%   23.00 ± 0%  -8.00% (p=0.000 n=30)
Initialization/interpreter-multiple-10   25.00 ± 0%   23.00 ± 0%  -8.00% (p=0.000 n=30)
Initialization/compiler-10               25.00 ± 0%   23.00 ± 0%  -8.00% (p=0.000 n=30)
Initialization/compiler-multiple-10      25.00 ± 0%   23.00 ± 0%  -8.00% (p=0.000 n=30)
geomean                                  25.00        23.00       -8.00%

```


Patch:
diff --git a/internal/wasm/binary/decoder.go b/internal/wasm/binary/decoder.go
index f1abada3..62000484 100644
--- a/internal/wasm/binary/decoder.go
+++ b/internal/wasm/binary/decoder.go
@@ -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.
 			}
diff --git a/internal/wasm/binary/section.go b/internal/wasm/binary/section.go
index 55e7ab3c..a4111572 100644
--- a/internal/wasm/binary/section.go
+++ b/internal/wasm/binary/section.go
@@ -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
 }
diff --git a/internal/wasm/module.go b/internal/wasm/module.go
index 16a39630..8cbd8555 100644
--- a/internal/wasm/module.go
+++ b/internal/wasm/module.go
@@ -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.
diff --git a/internal/wasm/store.go b/internal/wasm/store.go
index df6fa04e..35549ff6 100644
--- a/internal/wasm/store.go
+++ b/internal/wasm/store.go
@@ -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
-			}
+			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
+				}
 
-			if expected.Min > importedTable.Min {
-				err = errorMinSizeMismatch(i, idx, expected.Min, importedTable.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 != nil {
-				expectedMax := *expected.Max
-				if importedTable.Max == nil {
-					err = errorNoMax(i, idx, expectedMax)
+				if expected.Min > importedTable.Min {
+					err = errorMinSizeMismatch(i, expected.Min, importedTable.Min)
 					return
-				} else if expectedMax < *importedTable.Max {
-					err = errorMaxSizeMismatch(i, idx, expectedMax, *importedTable.Max)
+				}
+
+				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.Min > memoryBytesNumToPages(uint64(len(importedMemory.Buffer))) {
+					err = errorMinSizeMismatch(i, expected.Min, importedMemory.Min)
 					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
-			}
 
-			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.Mutable != importedGlobal.Type.Mutable {
-				err = errorInvalidImport(i, idx, fmt.Errorf("mutability mismatch: %t != %t",
-					expected.Mutable, importedGlobal.Type.Mutable))
-				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, idx, fmt.Errorf("value type mismatch: %s != %s",
-					ValueTypeName(expected.ValType), ValueTypeName(importedGlobal.Type.ValType)))
-				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.
diff --git a/internal/wasm/store_module_list.go b/internal/wasm/store_module_list.go
index 7f39a708..8c61e51a 100644
--- a/internal/wasm/store_module_list.go
+++ b/internal/wasm/store_module_list.go
@@ -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 {


Test patch:
diff --git a/internal/engine/compiler/engine_test.go b/internal/engine/compiler/engine_test.go
index 57613bb9..093fa84a 100644
--- a/internal/engine/compiler/engine_test.go
+++ b/internal/engine/compiler/engine_test.go
@@ -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},
diff --git a/internal/testing/enginetest/enginetest.go b/internal/testing/enginetest/enginetest.go
index a36cf880..f02faba3 100644
--- a/internal/testing/enginetest/enginetest.go
+++ b/internal/testing/enginetest/enginetest.go
@@ -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()
diff --git a/internal/wasm/binary/decoder_test.go b/internal/wasm/binary/decoder_test.go
index b1f86aac..b3381494 100644
--- a/internal/wasm/binary/decoder_test.go
+++ b/internal/wasm/binary/decoder_test.go
@@ -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)
 		})
 	}
diff --git a/internal/wasm/store_module_list_test.go b/internal/wasm/store_module_list_test.go
index b5ca500c..ff5b763a 100644
--- a/internal/wasm/store_module_list_test.go
+++ b/internal/wasm/store_module_list_test.go
@@ -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"}
diff --git a/internal/wasm/store_test.go b/internal/wasm/store_test.go
index bc282733..f6524704 100644
--- a/internal/wasm/store_test.go
+++ b/internal/wasm/store_test.go
@@ -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")
 		})
 	})
 }
diff --git a/internal/wasm/table_test.go b/internal/wasm/table_test.go
index fe28f3e2..5d343a32 100644
--- a/internal/wasm/table_test.go
+++ b/internal/wasm/table_test.go
@@ -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")
 	})
 }