Repository: tetratelabs/wazero
Pull request #342 (2022-03-07 07:08:47)

Enable concurrent instantiation, and release
This makes sure that concurrent use of Instantiate and
ReleaseModuleInstance is goroutine-safe. 

Note that it is still not safe to expose Release in the public API
as we haven't taken into account the outstanding calls. That
would be addressed in the follow-up commit.


part of #293.
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>

mathetake: ok cool no race detected 

```
$ go test -race -run ^TestStore_concurrent$ github.com/tetratelabs/wazero/internal/wasm -count=100 
ok      github.com/tetratelabs/wazero/internal/wasm     19.425s
```
codefromthecrypt: I'm going to add some commits instead of making review comments (or at least to reduce them)
mathetake: > why the store can't partition data by module as it seems the locks would be less blinding.

Theoretically can be partitioned, but a live instance must be assigned a unique addr (funcaddr, memaddr, etc in the spec https://www.w3.org/TR/wasm-core-1/#addresses%E2%91%A0), and notably the function calls were made via funcaddr (which engine maps to the a *compiledFunction).  In the spec rather aggressively, a module instance only holds addresses (https://www.w3.org/TR/wasm-core-1/#module-instances%E2%91%A0).

But yeah I believe there's some way to work around this (especially once we are able to do precompilation namely after we could decouple compiledFunction from runtime representations).

Patch:
diff --git a/internal/wasm/host.go b/internal/wasm/host.go
index 082289ba..0f2c7dd0 100644
--- a/internal/wasm/host.go
+++ b/internal/wasm/host.go
@@ -14,7 +14,7 @@ func NewModuleContext(ctx context.Context, engine Engine, instance *ModuleInstan
 	return &ModuleContext{
 		ctx:    ctx,
 		engine: engine,
-		memory: instance.MemoryInstance,
+		memory: instance.Memory,
 		Module: instance,
 	}
 }
@@ -91,13 +91,15 @@ func (f *exportedFunction) Call(ctx context.Context, params ...uint64) ([]uint64
 }
 
 // NewHostModule is defined internally for use in WASI tests and to keep the code size in the root directory small.
+//
+// TOOD(adrian): make this goroutine-safe like store.Instantiate.
 func (s *Store) NewHostModule(moduleName string, nameToGoFunc map[string]interface{}) (*HostModule, error) {
 	if err := s.requireModuleUnused(moduleName); err != nil {
 		return nil, err
 	}
 
 	exportCount := len(nameToGoFunc)
-	ret := &HostModule{name: moduleName, NameToFunctionInstance: make(map[string]*FunctionInstance, exportCount)}
+	ret := &HostModule{name: moduleName, NameToFunction: make(map[string]*FunctionInstance, exportCount)}
 	hostModule := &ModuleInstance{
 		Name:       moduleName,
 		Exports:    make(map[string]*ExportInstance, exportCount),
@@ -119,14 +121,14 @@ func (s *Store) NewHostModule(moduleName string, nameToGoFunc map[string]interfa
 		}
 		hostModule.Exports[name] = &ExportInstance{Type: ExternTypeFunc, Function: f}
 		hostModule.Functions = append(hostModule.Functions, f)
-		ret.NameToFunctionInstance[name] = f
+		ret.NameToFunction[name] = f
 
 		if err = s.compileHostFunction(f); err != nil {
 			return nil, err
 		}
 	}
 
-	s.moduleInstances[moduleName] = hostModule
+	s.modules[moduleName] = hostModule
 	return ret, nil
 }
 
@@ -136,20 +138,21 @@ func (s *Store) compileHostFunction(f *FunctionInstance) (err error) {
 		return err
 	}
 	f.TypeID = ti.TypeID
-	s.addFunctionInstances(f)
+	s.addFunctions(f)
 
 	if err = s.engine.Compile(f); err != nil {
 		// On failure, we must release the function instance.
-		if err = s.releaseFunctionInstances(f); err != nil {
-			return err
+		if err = s.releaseFunctions(f); err != nil {
+			return fmt.Errorf("failed to compile %s: %v", f.Name, err)
 		}
-		return fmt.Errorf("failed to compile %s: %v", f.Name, err)
 	}
 	return nil
 }
 
 func (s *Store) requireModuleUnused(moduleName string) error {
-	if _, ok := s.moduleInstances[moduleName]; ok {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	if _, ok := s.modules[moduleName]; ok {
 		return fmt.Errorf("module %s has already been instantiated", moduleName)
 	}
 	return nil
@@ -157,9 +160,9 @@ func (s *Store) requireModuleUnused(moduleName string) error {
 
 // HostModule implements wasm.HostModule
 type HostModule struct {
-	// name is for String and Store.ReleaseModuleInstance
-	name                   string
-	NameToFunctionInstance map[string]*FunctionInstance
+	// name is for String and Store.ReleaseModule
+	name           string
+	NameToFunction map[string]*FunctionInstance
 }
 
 // String implements fmt.Stringer
@@ -188,5 +191,5 @@ func (f *FunctionInstance) Call(ctx publicwasm.ModuleContext, params ...uint64)
 
 // Function implements wasm.HostModule Function
 func (m *HostModule) Function(name string) publicwasm.HostFunction {
-	return m.NameToFunctionInstance[name]
+	return m.NameToFunction[name]
 }
diff --git a/internal/wasm/interpreter/interpreter.go b/internal/wasm/interpreter/interpreter.go
index 66e31eac..4e4f1c20 100644
--- a/internal/wasm/interpreter/interpreter.go
+++ b/internal/wasm/interpreter/interpreter.go
@@ -535,7 +535,7 @@ func (ce *callEngine) callHostFunc(ctx *wasm.ModuleContext, f *compiledFunction)
 
 	// A host function is invoked with the calling frame's memory, which may be different if in another module.
 	if len(ce.frames) > 0 {
-		ctx = ctx.WithMemory(ce.frames[len(ce.frames)-1].f.funcInstance.Module.MemoryInstance)
+		ctx = ctx.WithMemory(ce.frames[len(ce.frames)-1].f.funcInstance.Module.Memory)
 	}
 
 	// Handle any special parameter zero
@@ -565,9 +565,9 @@ func (ce *callEngine) callHostFunc(ctx *wasm.ModuleContext, f *compiledFunction)
 func (ce *callEngine) callNativeFunc(ctx *wasm.ModuleContext, f *compiledFunction) {
 	frame := &callFrame{f: f}
 	moduleInst := f.funcInstance.Module
-	memoryInst := moduleInst.MemoryInstance
+	memoryInst := moduleInst.Memory
 	globals := moduleInst.Globals
-	table := moduleInst.TableInstance
+	table := moduleInst.Table
 	ce.pushFrame(frame)
 	bodyLen := uint64(len(frame.f.body))
 	for frame.pc < bodyLen {
diff --git a/internal/wasm/jit/engine.go b/internal/wasm/jit/engine.go
index 86ee02a6..e6398e35 100644
--- a/internal/wasm/jit/engine.go
+++ b/internal/wasm/jit/engine.go
@@ -100,9 +100,9 @@ type (
 		// i.e. &ModuleInstance.Globals[0] as uintptr.
 		globalElement0Address uintptr
 		// memoryElement0Address is the address of the first element in the global slice,
-		// i.e. &ModuleInstance.MemoryInstance.Buffer[0] as uintptr.
+		// i.e. &ModuleInstance.Memory.Buffer[0] as uintptr.
 		memoryElement0Address uintptr
-		// memorySliceLen is the length of the memory buffer, i.e. len(ModuleInstance.MemoryInstance.Buffer).
+		// memorySliceLen is the length of the memory buffer, i.e. len(ModuleInstance.Memory.Buffer).
 		memorySliceLen uint64
 		// tableElement0Address is the address of the first item in the global slice,
 		// i.e. &ModuleInstance.Tables[0].Table[0] as uintptr.
@@ -235,11 +235,11 @@ const (
 	moduleInstanceMemoryOffset  = 72
 	moduleInstanceTableOffset   = 80
 
-	// Offsets for wasm.TableInstance.
+	// Offsets for wasm.Table.
 	tableInstanceTableOffset    = 0
 	tableInstanceTableLenOffset = 8
 
-	// Offsets for wasm.MemoryInstance.
+	// Offsets for wasm.Memory.
 	memoryInstanceBufferOffset    = 0
 	memoryInstanceBufferLenOffset = 8
 
@@ -583,14 +583,14 @@ jitentry:
 			callerCompiledFunction := ce.callFrameAt(1).compiledFunction
 			// A host function is invoked with the calling frame's memory, which may be different if in another module.
 			ce.execHostFunction(fn.source.Kind, fn.source.GoFunc,
-				ctx.WithMemory(callerCompiledFunction.source.Module.MemoryInstance),
+				ctx.WithMemory(callerCompiledFunction.source.Module.Memory),
 			)
 			goto jitentry
 		case jitCallStatusCodeCallBuiltInFunction:
 			switch ce.exitContext.functionCallAddress {
 			case builtinFunctionIndexMemoryGrow:
 				callerCompiledFunction := ce.callFrameTop().compiledFunction
-				ce.builtinFunctionMemoryGrow(callerCompiledFunction.source.Module.MemoryInstance)
+				ce.builtinFunctionMemoryGrow(callerCompiledFunction.source.Module.Memory)
 			case builtinFunctionIndexGrowValueStack:
 				callerCompiledFunction := ce.callFrameTop().compiledFunction
 				ce.builtinFunctionGrowValueStack(callerCompiledFunction.stackPointerCeil)
diff --git a/internal/wasm/jit/jit_amd64.go b/internal/wasm/jit/jit_amd64.go
index a928fe6a..f60026d5 100644
--- a/internal/wasm/jit/jit_amd64.go
+++ b/internal/wasm/jit/jit_amd64.go
@@ -980,7 +980,7 @@ func (c *amd64Compiler) compileCallIndirect(o *wazeroir.OperationCallIndirect) e
 	// Next we check if the target's type matches the operation's one.
 	// In order to get the type instance's address, we have to multiply the offset
 	// by 16 as the offset is the "length" of table in Go's "[]wasm.TableElement",
-	// and size of wasm.TableInstance equals 128 bit (64-bit wasm.FunctionIndex and 64-bit wasm.TypeID).
+	// and size of wasm.Table equals 128 bit (64-bit wasm.FunctionIndex and 64-bit wasm.TypeID).
 	getTypeInstanceAddress := c.newProg()
 	notLengthExceedJump.To.SetTarget(getTypeInstanceAddress)
 	getTypeInstanceAddress.As = x86.ASHLQ
@@ -990,7 +990,7 @@ func (c *amd64Compiler) compileCallIndirect(o *wazeroir.OperationCallIndirect) e
 	getTypeInstanceAddress.From.Offset = 4
 	c.addInstruction(getTypeInstanceAddress)
 
-	// Adds the address of wasm.TableInstance[0] stored as callEngine.tableSliceAddress to the offset.
+	// Adds the address of wasm.Table[0] stored as callEngine.tableSliceAddress to the offset.
 	movTableSliceAddress := c.newProg()
 	movTableSliceAddress.As = x86.AADDQ
 	movTableSliceAddress.To.Type = obj.TYPE_REG
@@ -1000,7 +1000,7 @@ func (c *amd64Compiler) compileCallIndirect(o *wazeroir.OperationCallIndirect) e
 	movTableSliceAddress.From.Offset = callEngineModuleContextTableElement0AddressOffset
 	c.addInstruction(movTableSliceAddress)
 
-	// At this point offset.register holds the address of wasm.TableElement at wasm.TableInstance[offset].
+	// At this point offset.register holds the address of wasm.TableElement at wasm.Table[offset].
 	ti := c.f.Module.Types[o.TypeIndex]
 	targetFunctionType := ti.Type
 	checkIfTypeMatch := c.newProg()
@@ -5289,7 +5289,7 @@ func (c *amd64Compiler) initializeReservedStackBasePointer() {
 }
 
 func (c *amd64Compiler) initializeReservedMemoryPointer() {
-	if c.f.Module.MemoryInstance != nil {
+	if c.f.Module.Memory != nil {
 		setupMemoryRegister := c.newProg()
 		setupMemoryRegister.As = x86.AMOVQ
 		setupMemoryRegister.To.Type = obj.TYPE_REG
@@ -5445,8 +5445,8 @@ func (c *amd64Compiler) initializeModuleContext() error {
 	// Note: if there's table instruction in the function, the existence of the table
 	// is ensured by function validation at module instantiation phase, and that's
 	// why it is ok to skip the initialization if the module's table doesn't exist.
-	if c.f.Module.TableInstance != nil {
-		// First, we need to read the *wasm.TableInstance.
+	if c.f.Module.Table != nil {
+		// First, we need to read the *wasm.Table.
 		readTableInstancePointer := c.newProg()
 		readTableInstancePointer.As = x86.AMOVQ
 		readTableInstancePointer.To.Type = obj.TYPE_REG
@@ -5456,8 +5456,8 @@ func (c *amd64Compiler) initializeModuleContext() error {
 		readTableInstancePointer.From.Offset = moduleInstanceTableOffset
 		c.addInstruction(readTableInstancePointer)
 
-		// At this point, tmpRegister holds the address of ModuleInstance.TableInstance.
-		// So we are ready to read and put the first item's address stored in TableInstance.Table.
+		// At this point, tmpRegister holds the address of ModuleInstance.Table.
+		// So we are ready to read and put the first item's address stored in Table.Table.
 		// Here we read the value into tmpRegister2.
 		readTableElement0Address := c.newProg()
 		readTableElement0Address.As = x86.AMOVQ // Note this is LEA instruction.
@@ -5503,7 +5503,7 @@ func (c *amd64Compiler) initializeModuleContext() error {
 	// Note: if there's memory instruction in the function, memory instance must be non-nil.
 	// That is ensured by function validation at module instantiation phase, and that's
 	// why it is ok to skip the initialization if the module's memory instance is nil.
-	if c.f.Module.MemoryInstance != nil {
+	if c.f.Module.Memory != nil {
 		getMemoryInstanceAddress := c.newProg()
 		getMemoryInstanceAddress.As = x86.AMOVQ
 		getMemoryInstanceAddress.To.Type = obj.TYPE_REG
diff --git a/internal/wasm/jit/jit_arm64.go b/internal/wasm/jit/jit_arm64.go
index 064c9621..6aa23f09 100644
--- a/internal/wasm/jit/jit_arm64.go
+++ b/internal/wasm/jit/jit_arm64.go
@@ -473,7 +473,7 @@ func (c *arm64Compiler) compileMaybeGrowValueStack() error {
 		math.MaxInt32,
 		tmpY,
 	)
-	// At this point of compilation, we don't know the value of stack pointe ceil,
+	// At this point of compilation, we don't know the value of stack point ceil,
 	// so we layzily resolve the value later.
 	c.onStackPointerCeilDeterminedCallBack = func(stackPointerCeil uint64) { loadStackPointerCeil.From.Offset = int64(stackPointerCeil) }
 
@@ -3380,7 +3380,7 @@ func (c *arm64Compiler) compileReservedStackBasePointerRegisterInitialization()
 }
 
 func (c *arm64Compiler) compileReservedMemoryRegisterInitialization() {
-	if c.f.Module.MemoryInstance != nil {
+	if c.f.Module.Memory != nil {
 		// "reservedRegisterForMemory = ce.MemoryElement0Address"
 		c.compileMemoryToRegisterInstruction(
 			arm64.AMOVD,
@@ -3446,7 +3446,7 @@ func (c *arm64Compiler) compileModuleContextInitialization() error {
 	// Note: if there's memory instruction in the function, memory instance must be non-nil.
 	// That is ensured by function validation at module instantiation phase, and that's
 	// why it is ok to skip the initialization if the module's memory instance is nil.
-	if c.f.Module.MemoryInstance != nil {
+	if c.f.Module.Memory != nil {
 		// "tmpX = moduleInstance.Memory"
 		c.compileMemoryToRegisterInstruction(
 			arm64.AMOVD,
@@ -3490,8 +3490,8 @@ func (c *arm64Compiler) compileModuleContextInitialization() error {
 	// Note: if there's table instruction in the function, the existence of the table
 	// is ensured by function validation at module instantiation phase, and that's
 	// why it is ok to skip the initialization if the module's table doesn't exist.
-	if c.f.Module.TableInstance != nil {
-		// "tmpX = &tables[0] (type of **wasm.TableInstance)"
+	if c.f.Module.Table != nil {
+		// "tmpX = &tables[0] (type of **wasm.Table)"
 		c.compileMemoryToRegisterInstruction(
 			arm64.AMOVD,
 			moduleInstanceAddressRegister, moduleInstanceTableOffset,
diff --git a/internal/wasm/module.go b/internal/wasm/module.go
index 4ff28881..094e7023 100644
--- a/internal/wasm/module.go
+++ b/internal/wasm/module.go
@@ -367,7 +367,7 @@ func validateConstExpression(globals []*GlobalType, expr *ConstantExpression, ex
 	return nil
 }
 
-func (m *Module) buildGlobalInstances(importedGlobals []*GlobalInstance) (globals []*GlobalInstance) {
+func (m *Module) buildGlobals(importedGlobals []*GlobalInstance) (globals []*GlobalInstance) {
 	for _, gs := range m.GlobalSection {
 		var gv uint64
 		// Global's initialization constant expression can only reference the imported globals.
@@ -390,7 +390,7 @@ func (m *Module) buildGlobalInstances(importedGlobals []*GlobalInstance) (global
 	return
 }
 
-func (m *Module) buildFunctionInstances() (functions []*FunctionInstance) {
+func (m *Module) buildFunctions() (functions []*FunctionInstance) {
 	var functionNames NameMap
 	if m.NameSection != nil {
 		functionNames = m.NameSection.FunctionNames
@@ -424,7 +424,7 @@ func (m *Module) buildFunctionInstances() (functions []*FunctionInstance) {
 	return
 }
 
-func (m *Module) buildMemoryInstance() (mem *MemoryInstance) {
+func (m *Module) buildMemory() (mem *MemoryInstance) {
 	for _, memSec := range m.MemorySection {
 		mem = &MemoryInstance{
 			Buffer: make([]byte, MemoryPagesToBytesNum(memSec.Min)),
@@ -435,7 +435,7 @@ func (m *Module) buildMemoryInstance() (mem *MemoryInstance) {
 	return
 }
 
-func (m *Module) buildTableInstance() (table *TableInstance) {
+func (m *Module) buildTable() (table *TableInstance) {
 	for _, tableSeg := range m.TableSection {
 		table = newTableInstance(tableSeg.Limit.Min, tableSeg.Limit.Max)
 	}
diff --git a/internal/wasm/store.go b/internal/wasm/store.go
index 0156f682..366c1b8d 100644
--- a/internal/wasm/store.go
+++ b/internal/wasm/store.go
@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"math"
 	"reflect"
+	"sync"
 
 	"github.com/tetratelabs/wazero/internal/ieee754"
 	"github.com/tetratelabs/wazero/internal/leb128"
@@ -36,10 +37,10 @@ type (
 		// EnabledFeatures are read-only to allow optimizations.
 		EnabledFeatures Features
 
-		// ModuleInstances holds the instantiated Wasm modules by module name from Instantiate.
-		moduleInstances map[string]*ModuleInstance
+		// modules holds the instantiated Wasm modules by module name from Instantiate.
+		modules map[string]*ModuleInstance
 
-		// TypeIDs maps each FunctionType.String() to a unique FunctionTypeID. This is used at runtime to
+		// typeIDs maps each FunctionType.String() to a unique FunctionTypeID. This is used at runtime to
 		// do type-checks on indirect function calls.
 		typeIDs map[string]FunctionTypeID
 
@@ -52,43 +53,43 @@ type (
 		maximumFunctionTypes int
 
 		// releasedFunctionIndex holds reusable FunctionIndexes. An index is added when
-		// a function instance is released in releaseFunctionInstances, and is popped when
-		// a new instance is added in addFunctionInstances.
-		releasedFunctionIndex []FunctionIndex
+		// a function instance is released in releaseFunctions, and is popped when
+		// a new instance is added in addFunctions.
+		releasedFunctionIndex map[FunctionIndex]struct{}
 
-		// releasedMemoryIndex holds reusable memoryIndexes. An index is added when
-		// an memory instance is released in releaseMemoryInstance, and is popped when
-		// a new instance is added in addMemoryInstance.
-		releasedMemoryIndex []memoryIndex
+		// releasedMemoryIndex holds reusable memoryIndexes. An index is added when a memory is released in
+		// releaseMemory, and is popped when a new instance is added in addMemory.
+		releasedMemoryIndex map[memoryIndex]struct{}
 
-		// releasedTableIndex holds reusable tableIndexes. An index is added when
-		// an table instance is released in releaseTableInstance, and is popped when
-		// a new instance is added in addTableInstance.
-		releasedTableIndex []tableIndex
+		// releasedTableIndex holds reusable tableIndexes. An index is added when a table is released in releaseTable,
+		// and is popped when a new instance is added in addTable.
+		releasedTableIndex map[tableIndex]struct{}
 
-		// releasedGlobalIndex holds reusable globalIndexes. An index is added when
-		// an global instance is released in releaseGlobalInstances, and is popped when
-		// a new instance is added in addGlobalInstances.
-		releasedGlobalIndex []globalIndex
+		// releasedGlobalIndex holds reusable globalIndexes. An index is added when a global is released in
+		// releaseGlobal, and is popped when a new instance is added in addGlobals.
+		releasedGlobalIndex map[globalIndex]struct{}
 
 		// The followings fields match the definition of Store in the specification.
 
-		// Functions holds function instances (https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#function-instances%E2%91%A0),
+		// functions holds function instances (https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#function-instances%E2%91%A0),
 		// in this store.
 		// The slice index is to be interpreted as funcaddr (https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-funcaddr).
 		functions []*FunctionInstance
-		// Globals holds global instances (https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#global-instances%E2%91%A0),
+		// globals holds global instances (https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#global-instances%E2%91%A0),
 		// in this store.
 		// The slice index is to be interpreted as globaladdr (https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-globaladdr).
 		globals []*GlobalInstance
-		// Memories holds memory instances (https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0),
+		// memories holds memory instances (https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0),
 		// in this store.
 		// The slice index is to be interpreted as memaddr (https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-memaddr).
 		memories []*MemoryInstance
-		// Tables holds table instances (https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#table-instances%E2%91%A0),
+		// tables holds table instances (https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#table-instances%E2%91%A0),
 		// in this store.
 		// The slice index is to be interpreted as tableaddr (https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-tableaddr).
 		tables []*TableInstance
+
+		// mux is used to guard the fields from concurrent access.
+		mux sync.RWMutex
 	}
 
 	// ModuleInstance represents instantiated wasm module.
@@ -101,11 +102,10 @@ type (
 		Exports   map[string]*ExportInstance
 		Functions []*FunctionInstance
 		Globals   []*GlobalInstance
-		// MemoryInstance is set when Module.MemorySection had a memory, regardless of whether it was exported.
-		// Note: This avoids the name "Memory" which is an interface method name.
-		MemoryInstance *MemoryInstance
-		TableInstance  *TableInstance
-		Types          []*TypeInstance
+		// Memory is set when Module.MemorySection had a memory, regardless of whether it was exported.
+		Memory *MemoryInstance
+		Table  *TableInstance
+		Types  []*TypeInstance
 
 		// Ctx holds default function call context from this function instance.
 		Ctx *ModuleContext
@@ -113,9 +113,16 @@ type (
 		// hostModule holds HostModule if this is a "host module" which is created in store.NewHostModule.
 		hostModule *HostModule
 
-		// TODO: https://github.com/tetratelabs/wazero/issues/293
-		refCount      int
-		moduleImports map[*ModuleInstance]struct{}
+		// mux is used to guard the fields from concurrent access.
+		mux sync.Mutex
+
+		// dependentCount is the current number of modules which import this module. On Store.ReleaseModule, this number
+		// must be zero otherwise it fails.
+		dependentCount int // guarded by mux
+
+		// dependencies holds imported modules. This is used when releasing this module instance, or decrementing the
+		// dependentCount of the imported modules.
+		dependencies map[*ModuleInstance]struct{}
 	}
 
 	// ExportInstance represents an exported instance in a Store.
@@ -267,7 +274,7 @@ func newModuleInstance(name string, module *Module, importedFunctions, functions
 	importedGlobals, globals []*GlobalInstance, importedTable, table *TableInstance,
 	memory, importedMemory *MemoryInstance, typeInstances []*TypeInstance, moduleImports map[*ModuleInstance]struct{}) *ModuleInstance {
 
-	instance := &ModuleInstance{Name: name, Types: typeInstances, moduleImports: moduleImports}
+	instance := &ModuleInstance{Name: name, Types: typeInstances, dependencies: moduleImports}
 
 	instance.Functions = append(instance.Functions, importedFunctions...)
 	for i, f := range functions {
@@ -281,15 +288,15 @@ func newModuleInstance(name string, module *Module, importedFunctions, functions
 	instance.Globals = append(instance.Globals, globals...)
 
 	if importedTable != nil {
-		instance.TableInstance = importedTable
+		instance.Table = importedTable
 	} else {
-		instance.TableInstance = table
+		instance.Table = table
 	}
 
 	if importedMemory != nil {
-		instance.MemoryInstance = importedMemory
+		instance.Memory = importedMemory
 	} else {
-		instance.MemoryInstance = memory
+		instance.Memory = memory
 	}
 
 	instance.buildExportInstances(module.ExportSection)
@@ -313,9 +320,9 @@ func (m *ModuleInstance) buildExportInstances(exports map[string]*Export) {
 		case ExternTypeGlobal:
 			ei = &ExportInstance{Type: exp.Type, Global: m.Globals[index]}
 		case ExternTypeMemory:
-			ei = &ExportInstance{Type: exp.Type, Memory: m.MemoryInstance}
+			ei = &ExportInstance{Type: exp.Type, Memory: m.Memory}
 		case ExternTypeTable:
-			ei = &ExportInstance{Type: exp.Type, Table: m.TableInstance}
+			ei = &ExportInstance{Type: exp.Type, Table: m.Table}
 		}
 
 		// We already validated the duplicates during module validation phase.
@@ -328,7 +335,7 @@ func (m *ModuleInstance) validateData(data []*DataSegment) (err error) {
 		offset := int(executeConstExpression(m.Globals, d.OffsetExpression).(int32))
 
 		ceil := offset + len(d.Init)
-		if offset < 0 || ceil > len(m.MemoryInstance.Buffer) {
+		if offset < 0 || ceil > len(m.Memory.Buffer) {
 			return fmt.Errorf("out of bounds memory access")
 		}
 	}
@@ -338,7 +345,7 @@ func (m *ModuleInstance) validateData(data []*DataSegment) (err error) {
 func (m *ModuleInstance) applyData(data []*DataSegment) {
 	for _, d := range data {
 		offset := executeConstExpression(m.Globals, d.OffsetExpression).(int32)
-		copy(m.MemoryInstance.Buffer[offset:], d.Init)
+		copy(m.Memory.Buffer[offset:], d.Init)
 	}
 }
 
@@ -347,7 +354,7 @@ func (m *ModuleInstance) validateElements(elements []*ElementSegment) (err error
 		offset := int(executeConstExpression(m.Globals, elem.OffsetExpr).(int32))
 		ceil := offset + len(elem.Init)
 
-		if offset < 0 || ceil > len(m.TableInstance.Table) {
+		if offset < 0 || ceil > len(m.Table.Table) {
 			return fmt.Errorf("out of bounds table access")
 		}
 		for _, elm := range elem.Init {
@@ -362,7 +369,7 @@ func (m *ModuleInstance) validateElements(elements []*ElementSegment) (err error
 func (m *ModuleInstance) applyElements(elements []*ElementSegment) {
 	for _, elem := range elements {
 		offset := int(executeConstExpression(m.Globals, elem.OffsetExpr).(int32))
-		table := m.TableInstance.Table
+		table := m.Table.Table
 		for i, elm := range elem.Init {
 			pos := i + offset
 			targetFunc := m.Functions[elm]
@@ -388,18 +395,24 @@ func (m *ModuleInstance) getExport(name string, et ExternType) (*ExportInstance,
 
 func NewStore(ctx context.Context, engine Engine, enabledFeatures Features) *Store {
 	return &Store{
-		ctx:                  ctx,
-		engine:               engine,
-		EnabledFeatures:      enabledFeatures,
-		moduleInstances:      map[string]*ModuleInstance{},
-		typeIDs:              map[string]FunctionTypeID{},
-		maximumFunctionIndex: maximumFunctionIndex,
-		maximumFunctionTypes: maximumFunctionTypes,
+		ctx:                   ctx,
+		engine:                engine,
+		EnabledFeatures:       enabledFeatures,
+		modules:               map[string]*ModuleInstance{},
+		typeIDs:               map[string]FunctionTypeID{},
+		maximumFunctionIndex:  maximumFunctionIndex,
+		maximumFunctionTypes:  maximumFunctionTypes,
+		releasedFunctionIndex: map[FunctionIndex]struct{}{},
+		releasedMemoryIndex:   map[memoryIndex]struct{}{},
+		releasedTableIndex:    map[tableIndex]struct{}{},
+		releasedGlobalIndex:   map[globalIndex]struct{}{},
 	}
 }
 
 // checkFunctionIndexOverflow checks if there would be too many function instances in a store.
 func (s *Store) checkFunctionIndexOverflow(newInstanceNum int) error {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
 	if len(s.functions) > int(s.maximumFunctionIndex)-newInstanceNum {
 		return fmt.Errorf("too many functions in a store")
 	}
@@ -407,6 +420,9 @@ func (s *Store) checkFunctionIndexOverflow(newInstanceNum int) error {
 }
 
 func (s *Store) Instantiate(module *Module, name string) (*PublicModule, error) {
+	// Note: we do not take lock here in order to enable concurrent instantiation and compilation
+	// of multiuple modules. When necessary, we take read or write locks in each method of store used here.
+
 	if err := s.requireModuleUnused(name); err != nil {
 		return nil, err
 	}
@@ -415,19 +431,27 @@ func (s *Store) Instantiate(module *Module, name string) (*PublicModule, error)
 		return nil, err
 	}
 
-	importedFunctions, importedGlobals, importedTable, importedMemory, moduleImports, err := s.resolveImports(module)
+	types, err := s.getTypes(module.TypeSection)
 	if err != nil {
 		return nil, err
 	}
 
-	functions, globals, table, memory :=
-		module.buildFunctionInstances(), module.buildGlobalInstances(importedGlobals), module.buildTableInstance(), module.buildMemoryInstance()
-
-	types, err := s.getTypeInstances(module.TypeSection)
+	moduleImportsFinalized := false
+	importedFunctions, importedGlobals, importedTable, importedMemory, moduleImports, err := s.resolveImports(module)
+	defer func() {
+		if !moduleImportsFinalized {
+			for moduleImport := range moduleImports {
+				moduleImport.decImportedCount()
+			}
+		}
+	}()
 	if err != nil {
 		return nil, err
 	}
 
+	functions, globals, table, memory :=
+		module.buildFunctions(), module.buildGlobals(importedGlobals), module.buildTable(), module.buildMemory()
+
 	// Now we have all instances from imports and local ones, so ready to create a new ModuleInstance.
 	instance := newModuleInstance(name, module, importedFunctions, functions, importedGlobals,
 		globals, importedTable, table, importedMemory, memory, types, moduleImports)
@@ -441,11 +465,12 @@ func (s *Store) Instantiate(module *Module, name string) (*PublicModule, error)
 	}
 
 	// Now we are ready to compile functions.
-	s.addFunctionInstances(functions...) // Need to assign funcaddr to each instance before compilation.
+	s.addFunctions(functions...) // Need to assign funcaddr to each instance before compilation.
 	for i, f := range functions {
+		// TODO: maybe better consider spawning multiple goroutines for compilations to accelerate.
 		if err := s.engine.Compile(f); err != nil {
 			// On the failure, release the assigned funcaddr and already compiled functions.
-			if err := s.releaseFunctionInstances(functions...); err != nil {
+			if err := s.releaseFunctions(functions[:i]...); err != nil {
 				return nil, err
 			}
 			idx := module.SectionElementCount(SectionIDFunction) - 1
@@ -457,20 +482,14 @@ func (s *Store) Instantiate(module *Module, name string) (*PublicModule, error)
 	instance.applyElements(module.ElementSection)
 	instance.applyData(module.DataSection)
 
-	// Persist the instances other tha functions (which we already persisted before compilation).
-	s.addGlobalInstances(globals...)
-	s.addTableInstance(table)
-	s.addMemoryInstance(instance.MemoryInstance)
+	// Persist the instances except functions (which we already persisted before compilation).
+	s.addGlobals(globals...)
+	s.addTable(table)
+	s.addMemory(instance.Memory)
+	s.addModule(instance)
 
-	// Increase the reference count of imported modules.
-	for imported := range instance.moduleImports {
-		imported.refCount++
-	}
-
-	// Build the default context for calls to this module.
-	instance.Ctx = NewModuleContext(s.ctx, s.engine, instance)
-
-	s.moduleInstances[instance.Name] = instance
+	// Plus, we can finalize the module import reference count.
+	moduleImportsFinalized = true
 
 	// Execute the start function.
 	if module.StartSection != nil {
@@ -482,56 +501,61 @@ func (s *Store) Instantiate(module *Module, name string) (*PublicModule, error)
 	return &PublicModule{s: s, instance: instance}, nil
 }
 
-// ReleaseModuleInstance deallocates resources if a module with the given name exists.
-func (s *Store) ReleaseModuleInstance(moduleName string) error {
-	// TODO: none of this is goroutine safe
-	instance := s.moduleInstances[moduleName]
-	if instance == nil {
+// ReleaseModule deallocates resources if a module with the given name exists.
+func (s *Store) ReleaseModule(moduleName string) error {
+	m := s.module(moduleName)
+	if m == nil {
 		return nil // already released
 	}
 
-	instance.refCount--
-	if instance.refCount > 0 {
+	m.mux.Lock()
+	defer m.mux.Unlock()
+
+	if m.dependentCount > 0 {
 		// This case other modules are importing this module instance and still alive.
-		return fmt.Errorf("%d modules import this and need to be closed first", instance.refCount)
+		return fmt.Errorf("%d modules import this and need to be closed first", m.dependentCount)
 	}
 
 	// TODO: check outstanding calls and wait until they exit.
 
-	// Recursively release the imported instances.
-	for mod := range instance.moduleImports {
-		if err := s.ReleaseModuleInstance(mod.Name); err != nil {
-			return fmt.Errorf("unable to release imported module [%s]: %w", mod.Name, err)
-		}
+	for mod := range m.dependencies {
+		mod.decImportedCount()
 	}
 
-	if err := s.releaseFunctionInstances(instance.Functions...); err != nil {
+	if err := s.releaseFunctions(m.Functions...); err != nil {
 		return fmt.Errorf("unable to release function instance: %w", err)
 	}
 
-	if instance.MemoryInstance != nil {
-		s.releaseMemoryInstance(instance.MemoryInstance)
+	if m.Memory != nil {
+		s.releaseMemory(m.Memory)
 	}
 
-	if instance.TableInstance != nil {
-		s.releaseTableInstance(instance.TableInstance)
+	if m.Table != nil {
+		s.releaseTable(m.Table)
 	}
 
-	s.releaseGlobalInstances(instance.Globals...)
-
-	// Explicitly assign nil so that we ensure this moduleInstance no longer holds reference to instances.
-	instance.Exports = nil
-	instance.Globals = nil
-	instance.Functions = nil
-	instance.TableInstance = nil
-	instance.MemoryInstance = nil
-	instance.Types = nil
+	s.releaseGlobal(m.Globals...)
 
-	delete(s.moduleInstances, instance.Name)
+	s.deleteModule(m)
 	return nil
 }
 
-func (s *Store) releaseFunctionInstances(fs ...*FunctionInstance) error {
+func (m *ModuleInstance) decImportedCount() {
+	m.mux.Lock()
+	defer m.mux.Unlock()
+	m.dependentCount--
+}
+
+func (m *ModuleInstance) incImportedCount() {
+	m.mux.Lock()
+	defer m.mux.Unlock()
+	m.dependentCount++
+}
+
+func (s *Store) releaseFunctions(fs ...*FunctionInstance) error {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+
 	for _, f := range fs {
 		if err := s.engine.Release(f); err != nil {
 			return err
@@ -541,114 +565,160 @@ func (s *Store) releaseFunctionInstances(fs ...*FunctionInstance) error {
 		s.functions[f.Index] = nil
 
 		// Append the address so that we can reuse it in order to avoid index space explosion.
-		s.releasedFunctionIndex = append(s.releasedFunctionIndex, f.Index)
+		s.releasedFunctionIndex[f.Index] = struct{}{}
 	}
 	return nil
 }
 
-func (s *Store) addFunctionInstances(fs ...*FunctionInstance) {
+func (s *Store) addFunctions(fs ...*FunctionInstance) {
+	s.mux.Lock()
+	defer s.mux.Unlock()
 	for _, f := range fs {
-		var addr FunctionIndex
+		var index FunctionIndex
 		if len(s.releasedFunctionIndex) > 0 {
-			id := len(s.releasedFunctionIndex) - 1
-			// Pop one address from releasedFunctionIndex slice.
-			addr, s.releasedFunctionIndex = s.releasedFunctionIndex[id], s.releasedFunctionIndex[:id]
-			s.functions[f.Index] = f
+			for popped := range s.releasedFunctionIndex {
+				index = popped
+				break
+			}
+			s.functions[index] = f
+			delete(s.releasedFunctionIndex, index)
 		} else {
-			addr = FunctionIndex(len(s.functions))
+			index = FunctionIndex(len(s.functions))
 			s.functions = append(s.functions, f)
 		}
-		f.Index = addr
+		f.Index = index
 	}
 }
 
-func (s *Store) releaseGlobalInstances(gs ...*GlobalInstance) {
+func (s *Store) releaseGlobal(gs ...*GlobalInstance) {
+	s.mux.Lock()
+	defer s.mux.Unlock()
 	for _, g := range gs {
 		// Release reference to the global instance.
 		s.globals[g.index] = nil
 
 		// Append the address so that we can reuse it in order to avoid index space explosion.
-		s.releasedGlobalIndex = append(s.releasedGlobalIndex, g.index)
+		s.releasedGlobalIndex[g.index] = struct{}{}
 	}
 }
 
-func (s *Store) addGlobalInstances(gs ...*GlobalInstance) {
+func (s *Store) addGlobals(gs ...*GlobalInstance) {
+	s.mux.Lock()
+	defer s.mux.Unlock()
 	for _, g := range gs {
-		var addr globalIndex
+		var index globalIndex
 		if len(s.releasedGlobalIndex) > 0 {
-			id := len(s.releasedGlobalIndex) - 1
-			// Pop one address from releasedGlobalIndex slice.
-			addr, s.releasedGlobalIndex = s.releasedGlobalIndex[id], s.releasedGlobalIndex[:id]
-			s.globals[g.index] = g
+			for popped := range s.releasedGlobalIndex {
+				index = popped
+				break
+			}
+			s.globals[index] = g
+			delete(s.releasedGlobalIndex, index)
 		} else {
-			addr = globalIndex(len(s.globals))
+			index = globalIndex(len(s.globals))
 			s.globals = append(s.globals, g)
 		}
-		g.index = addr
+		g.index = index
 	}
 }
 
-func (s *Store) releaseTableInstance(t *TableInstance) {
+func (s *Store) releaseTable(t *TableInstance) {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+
 	// Release reference to the table instance.
 	s.tables[t.index] = nil
 
 	// Append the index so that we can reuse it in order to avoid index space explosion.
-	s.releasedTableIndex = append(s.releasedTableIndex, t.index)
+	s.releasedTableIndex[t.index] = struct{}{}
 }
 
-func (s *Store) addTableInstance(t *TableInstance) {
+func (s *Store) addTable(t *TableInstance) {
 	if t == nil {
 		return
 	}
 
-	var addr tableIndex
+	s.mux.Lock()
+	defer s.mux.Unlock()
+
+	var index tableIndex
 	if len(s.releasedTableIndex) > 0 {
-		id := len(s.releasedTableIndex) - 1
-		// Pop one index from releasedTableIndex slice.
-		addr, s.releasedTableIndex = s.releasedTableIndex[id], s.releasedTableIndex[:id]
-		s.tables[addr] = t
+		for popped := range s.releasedTableIndex {
+			index = popped
+			break
+		}
+		s.tables[index] = t
+		delete(s.releasedTableIndex, index)
 	} else {
-		addr = tableIndex(len(s.tables))
+		index = tableIndex(len(s.tables))
 		s.tables = append(s.tables, t)
 	}
-	t.index = addr
+	t.index = index
 }
 
-func (s *Store) releaseMemoryInstance(m *MemoryInstance) {
+func (s *Store) releaseMemory(m *MemoryInstance) {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+
 	// Release reference to the memory instance.
 	s.memories[m.index] = nil
 
 	// Append the index so that we can reuse it in order to avoid index space explosion.
-	s.releasedMemoryIndex = append(s.releasedMemoryIndex, m.index)
+	s.releasedMemoryIndex[m.index] = struct{}{}
 }
 
-func (s *Store) addMemoryInstance(m *MemoryInstance) {
+func (s *Store) addMemory(m *MemoryInstance) {
 	if m == nil {
 		return
 	}
 
-	var addr memoryIndex
+	s.mux.Lock()
+	defer s.mux.Unlock()
+
+	var index memoryIndex
 	if len(s.releasedMemoryIndex) > 0 {
-		id := len(s.releasedMemoryIndex) - 1
-		// Pop one index from releasedMemoryIndex slice.
-		addr, s.releasedMemoryIndex = s.releasedMemoryIndex[id], s.releasedMemoryIndex[:id]
-		s.memories[addr] = m
+		for popped := range s.releasedMemoryIndex {
+			index = popped
+			break
+		}
+		s.memories[index] = m
+		delete(s.releasedMemoryIndex, index)
 	} else {
-		addr = memoryIndex(len(s.memories))
+		index = memoryIndex(len(s.memories))
 		s.memories = append(s.memories, m)
 	}
-	m.index = addr
+	m.index = index
+}
+func (s *Store) deleteModule(m *ModuleInstance) {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+	delete(s.modules, m.Name)
+}
+
+func (s *Store) addModule(m *ModuleInstance) {
+	// Build the default context for calls to this module.
+	m.Ctx = NewModuleContext(s.ctx, s.engine, m)
+
+	s.mux.Lock()
+	defer s.mux.Unlock()
+	s.modules[m.Name] = m
 }
 
 // Module implements wasm.Store Module
 func (s *Store) Module(moduleName string) publicwasm.Module {
-	if m, ok := s.moduleInstances[moduleName]; !ok {
-		return nil
-	} else {
+	if m := s.module(moduleName); m != nil {
 		return &PublicModule{s: s, instance: m}
+	} else {
+		return nil
 	}
 }
 
+func (s *Store) module(moduleName string) *ModuleInstance {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	return s.modules[moduleName]
+}
+
 // PublicModule implements wasm.Module
 type PublicModule struct {
 	s        *Store
@@ -680,7 +750,9 @@ func (m *PublicModule) Memory(name string) publicwasm.Memory {
 
 // HostModule implements wasm.Store HostModule
 func (s *Store) HostModule(moduleName string) publicwasm.HostModule {
-	return s.moduleInstances[moduleName].hostModule
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	return s.modules[moduleName].hostModule
 }
 
 func (s *Store) resolveImports(module *Module) (
@@ -689,15 +761,18 @@ func (s *Store) resolveImports(module *Module) (
 	moduleImports map[*ModuleInstance]struct{},
 	err error,
 ) {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+
 	moduleImports = map[*ModuleInstance]struct{}{}
 	for _, is := range module.ImportSection {
-		m, ok := s.moduleInstances[is.Module]
+		m, ok := s.modules[is.Module]
 		if !ok {
 			err = fmt.Errorf("module \"%s\" not instantiated", is.Module)
 			return
 		}
 
-		// Note: at this point we don't increase the ref count.
+		m.incImportedCount()
 		moduleImports[m] = struct{}{}
 
 		var exp *ExportInstance
@@ -724,7 +799,7 @@ func (s *Store) resolveImports(module *Module) (
 			tableType := is.DescTable
 			table = exp.Table
 			if table.ElemType != tableType.ElemType {
-				err = fmt.Errorf("incompatible table improt: element type mismatch")
+				err = fmt.Errorf("incompatible table import: element type mismatch")
 				return
 			}
 			if table.Min < tableType.Limit.Min {
@@ -801,7 +876,10 @@ func executeConstExpression(globals []*GlobalInstance, expr *ConstantExpression)
 	return
 }
 
-func (s *Store) getTypeInstances(ts []*FunctionType) ([]*TypeInstance, error) {
+func (s *Store) getTypes(ts []*FunctionType) ([]*TypeInstance, error) {
+	// We take write-lock here as the follwing might end up mutating typeIDs map.
+	s.mux.Lock()
+	defer s.mux.Unlock()
 	ret := make([]*TypeInstance, len(ts))
 	for i, t := range ts {
 		inst, err := s.getTypeInstance(t)
@@ -814,7 +892,6 @@ func (s *Store) getTypeInstances(ts []*FunctionType) ([]*TypeInstance, error) {
 }
 
 func (s *Store) getTypeInstance(t *FunctionType) (*TypeInstance, error) {
-	// TODO: take mutex
 	key := t.String()
 	id, ok := s.typeIDs[key]
 	if !ok {


Test patch:
diff --git a/internal/wasm/host_test.go b/internal/wasm/host_test.go
index ccaf4773..9109bddf 100644
--- a/internal/wasm/host_test.go
+++ b/internal/wasm/host_test.go
@@ -459,7 +459,7 @@ func TestStore_NewHostModule(t *testing.T) {
 	require.NoError(t, err)
 
 	// Ensure it was added to module instances
-	hm := s.moduleInstances["test"]
+	hm := s.modules["test"]
 	require.NotNil(t, hm)
 
 	// The function was added to the store, prefixed by the owning module name
diff --git a/internal/wasm/interpreter/interpreter_test.go b/internal/wasm/interpreter/interpreter_test.go
index 2aeb372b..9c395a74 100644
--- a/internal/wasm/interpreter/interpreter_test.go
+++ b/internal/wasm/interpreter/interpreter_test.go
@@ -89,7 +89,7 @@ func TestEngine_Call_HostFn(t *testing.T) {
 	})
 
 	e := NewEngine()
-	module := &wasm.ModuleInstance{MemoryInstance: memory}
+	module := &wasm.ModuleInstance{Memory: memory}
 	modCtx := wasm.NewModuleContext(context.Background(), e, module)
 	f := &wasm.FunctionInstance{
 		GoFunc: &hostFn,
diff --git a/internal/wasm/jit/engine_test.go b/internal/wasm/jit/engine_test.go
index 72dcbdb5..717dbb2c 100644
--- a/internal/wasm/jit/engine_test.go
+++ b/internal/wasm/jit/engine_test.go
@@ -64,17 +64,17 @@ func TestVerifyOffsetValue(t *testing.T) {
 	// Offsets for wasm.ModuleInstance.
 	var moduleInstance wasm.ModuleInstance
 	require.Equal(t, int(unsafe.Offsetof(moduleInstance.Globals)), moduleInstanceGlobalsOffset)
-	require.Equal(t, int(unsafe.Offsetof(moduleInstance.MemoryInstance)), moduleInstanceMemoryOffset)
-	require.Equal(t, int(unsafe.Offsetof(moduleInstance.TableInstance)), moduleInstanceTableOffset)
+	require.Equal(t, int(unsafe.Offsetof(moduleInstance.Memory)), moduleInstanceMemoryOffset)
+	require.Equal(t, int(unsafe.Offsetof(moduleInstance.Table)), moduleInstanceTableOffset)
 
-	// Offsets for wasm.TableInstance.
+	// Offsets for wasm.Table.
 	var tableInstance wasm.TableInstance
 	require.Equal(t, int(unsafe.Offsetof(tableInstance.Table)), tableInstanceTableOffset)
 	// We add "+8" to get the length of Tables[0].Table
 	// since the slice header is laid out as {Data uintptr, Len int64, Cap int64} on memory.
 	require.Equal(t, int(unsafe.Offsetof(tableInstance.Table)+8), tableInstanceTableLenOffset)
 
-	// Offsets for wasm.MemoryInstance
+	// Offsets for wasm.Memory
 	var memoryInstance wasm.MemoryInstance
 	require.Equal(t, int(unsafe.Offsetof(memoryInstance.Buffer)), memoryInstanceBufferOffset)
 	// "+8" because the slice header is laid out as {Data uintptr, Len int64, Cap int64} on memory.
@@ -132,7 +132,7 @@ func TestEngine_Call_HostFn(t *testing.T) {
 	})
 
 	e := NewEngine()
-	module := &wasm.ModuleInstance{MemoryInstance: memory}
+	module := &wasm.ModuleInstance{Memory: memory}
 	modCtx := wasm.NewModuleContext(context.Background(), e, module)
 	f := &wasm.FunctionInstance{
 		GoFunc: &hostFn,
diff --git a/internal/wasm/jit/jit_amd64_test.go b/internal/wasm/jit/jit_amd64_test.go
index 8cdeb682..35a27d07 100644
--- a/internal/wasm/jit/jit_amd64_test.go
+++ b/internal/wasm/jit/jit_amd64_test.go
@@ -222,62 +222,62 @@ func TestAmd64Compiler_initializeModuleContext(t *testing.T) {
 		{
 			name: "no nil",
 			moduleInstance: &wasm.ModuleInstance{
-				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
-				TableInstance:  &wasm.TableInstance{Table: make([]wasm.TableElement, 20)},
-				Globals:        []*wasm.GlobalInstance{{Val: 100}},
+				Memory:  &wasm.MemoryInstance{Buffer: make([]byte, 10)},
+				Table:   &wasm.TableInstance{Table: make([]wasm.TableElement, 20)},
+				Globals: []*wasm.GlobalInstance{{Val: 100}},
 			},
 		},
 		{
 			name: "memory nil",
 			moduleInstance: &wasm.ModuleInstance{
-				TableInstance: &wasm.TableInstance{Table: make([]wasm.TableElement, 20)},
-				Globals:       []*wasm.GlobalInstance{{Val: 100}},
+				Table:   &wasm.TableInstance{Table: make([]wasm.TableElement, 20)},
+				Globals: []*wasm.GlobalInstance{{Val: 100}},
 			},
 		},
 		{
 			name: "memory zero length",
 			moduleInstance: &wasm.ModuleInstance{
-				TableInstance:  &wasm.TableInstance{Table: make([]wasm.TableElement, 20)},
-				Globals:        []*wasm.GlobalInstance{{Val: 100}},
-				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 0)},
+				Table:   &wasm.TableInstance{Table: make([]wasm.TableElement, 20)},
+				Globals: []*wasm.GlobalInstance{{Val: 100}},
+				Memory:  &wasm.MemoryInstance{Buffer: make([]byte, 0)},
 			},
 		},
 		{
 			name: "table length zero",
 			moduleInstance: &wasm.ModuleInstance{
-				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
-				TableInstance:  &wasm.TableInstance{Table: nil},
-				Globals:        []*wasm.GlobalInstance{{Val: 100}},
+				Memory:  &wasm.MemoryInstance{Buffer: make([]byte, 10)},
+				Table:   &wasm.TableInstance{Table: nil},
+				Globals: []*wasm.GlobalInstance{{Val: 100}},
 			},
 		},
 		{
 			name: "table length zero part2",
 			moduleInstance: &wasm.ModuleInstance{
-				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
-				TableInstance:  &wasm.TableInstance{Table: make([]wasm.TableElement, 0)},
-				Globals:        []*wasm.GlobalInstance{{Val: 100}},
+				Memory:  &wasm.MemoryInstance{Buffer: make([]byte, 10)},
+				Table:   &wasm.TableInstance{Table: make([]wasm.TableElement, 0)},
+				Globals: []*wasm.GlobalInstance{{Val: 100}},
 			},
 		},
 		{
 			name: "table nil",
 			moduleInstance: &wasm.ModuleInstance{
-				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
-				TableInstance:  &wasm.TableInstance{},
-				Globals:        []*wasm.GlobalInstance{{Val: 100}},
+				Memory:  &wasm.MemoryInstance{Buffer: make([]byte, 10)},
+				Table:   &wasm.TableInstance{},
+				Globals: []*wasm.GlobalInstance{{Val: 100}},
 			},
 		},
 		{
 			name: "table nil part2",
 			moduleInstance: &wasm.ModuleInstance{
-				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
-				Globals:        []*wasm.GlobalInstance{{Val: 100}},
+				Memory:  &wasm.MemoryInstance{Buffer: make([]byte, 10)},
+				Globals: []*wasm.GlobalInstance{{Val: 100}},
 			},
 		},
 		{
 			name: "globals nil",
 			moduleInstance: &wasm.ModuleInstance{
-				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
-				TableInstance:  &wasm.TableInstance{Table: make([]wasm.TableElement, 20)},
+				Memory: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
+				Table:  &wasm.TableInstance{Table: make([]wasm.TableElement, 20)},
 			},
 		},
 	} {
@@ -312,14 +312,14 @@ func TestAmd64Compiler_initializeModuleContext(t *testing.T) {
 			bufSliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&tc.moduleInstance.Globals))
 			require.Equal(t, bufSliceHeader.Data, ce.moduleContext.globalElement0Address)
 
-			if tc.moduleInstance.MemoryInstance != nil {
-				bufSliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&tc.moduleInstance.MemoryInstance.Buffer))
+			if tc.moduleInstance.Memory != nil {
+				bufSliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&tc.moduleInstance.Memory.Buffer))
 				require.Equal(t, uint64(bufSliceHeader.Len), ce.moduleContext.memorySliceLen)
 				require.Equal(t, bufSliceHeader.Data, ce.moduleContext.memoryElement0Address)
 			}
 
-			if tc.moduleInstance.TableInstance != nil {
-				tableHeader := (*reflect.SliceHeader)(unsafe.Pointer(&tc.moduleInstance.TableInstance.Table))
+			if tc.moduleInstance.Table != nil {
+				tableHeader := (*reflect.SliceHeader)(unsafe.Pointer(&tc.moduleInstance.Table.Table))
 				require.Equal(t, uint64(tableHeader.Len), ce.moduleContext.tableSliceLen)
 				require.Equal(t, tableHeader.Data, ce.moduleContext.tableElement0Address)
 			}
@@ -6073,7 +6073,7 @@ func TestAmd64Compiler_callFunction(t *testing.T) {
 					// Each function takes one argument, adds the value with 100 + i and returns the result.
 					addTargetValue := uint32(100 + i)
 					moduleInstance := &wasm.ModuleInstance{
-						MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 1024)},
+						Memory: &wasm.MemoryInstance{Buffer: make([]byte, 1024)},
 					}
 					moduleInstanceToExpectedValueInMemory[moduleInstance] = addTargetValue
 
@@ -6167,7 +6167,7 @@ func TestAmd64Compiler_callFunction(t *testing.T) {
 
 				// Also, in the middle of function call, we write the added value into each memory instance.
 				for mod, expValue := range moduleInstanceToExpectedValueInMemory {
-					require.Equal(t, expValue, binary.LittleEndian.Uint32(mod.MemoryInstance.Buffer[0:]))
+					require.Equal(t, expValue, binary.LittleEndian.Uint32(mod.Memory.Buffer[0:]))
 				}
 			})
 		})
diff --git a/internal/wasm/jit/jit_arm64_test.go b/internal/wasm/jit/jit_arm64_test.go
index e8e5cdf8..fff9bbbf 100644
--- a/internal/wasm/jit/jit_arm64_test.go
+++ b/internal/wasm/jit/jit_arm64_test.go
@@ -2142,47 +2142,47 @@ func TestArm64Compiler_compileModuleContextInitialization(t *testing.T) {
 		{
 			name: "no nil",
 			moduleInstance: &wasm.ModuleInstance{
-				Globals:        []*wasm.GlobalInstance{{Val: 100}},
-				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
-				TableInstance:  &wasm.TableInstance{Table: make([]wasm.TableElement, 20)},
+				Globals: []*wasm.GlobalInstance{{Val: 100}},
+				Memory:  &wasm.MemoryInstance{Buffer: make([]byte, 10)},
+				Table:   &wasm.TableInstance{Table: make([]wasm.TableElement, 20)},
 			},
 		},
 		{
 			name: "globals nil",
 			moduleInstance: &wasm.ModuleInstance{
-				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
-				TableInstance:  &wasm.TableInstance{Table: make([]wasm.TableElement, 20)},
+				Memory: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
+				Table:  &wasm.TableInstance{Table: make([]wasm.TableElement, 20)},
 			},
 		},
 		{
 			name: "memory nil",
 			moduleInstance: &wasm.ModuleInstance{
-				Globals:       []*wasm.GlobalInstance{{Val: 100}},
-				TableInstance: &wasm.TableInstance{Table: make([]wasm.TableElement, 20)},
+				Globals: []*wasm.GlobalInstance{{Val: 100}},
+				Table:   &wasm.TableInstance{Table: make([]wasm.TableElement, 20)},
 			},
 		},
 		{
 			name: "table nil",
 			moduleInstance: &wasm.ModuleInstance{
-				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
-				TableInstance:  &wasm.TableInstance{Table: nil},
-				Globals:        []*wasm.GlobalInstance{{Val: 100}},
+				Memory:  &wasm.MemoryInstance{Buffer: make([]byte, 10)},
+				Table:   &wasm.TableInstance{Table: nil},
+				Globals: []*wasm.GlobalInstance{{Val: 100}},
 			},
 		},
 		{
 			name: "table empty",
 			moduleInstance: &wasm.ModuleInstance{
-				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
-				TableInstance:  &wasm.TableInstance{Table: make([]wasm.TableElement, 0)},
-				Globals:        []*wasm.GlobalInstance{{Val: 100}},
+				Memory:  &wasm.MemoryInstance{Buffer: make([]byte, 10)},
+				Table:   &wasm.TableInstance{Table: make([]wasm.TableElement, 0)},
+				Globals: []*wasm.GlobalInstance{{Val: 100}},
 			},
 		},
 		{
 			name: "memory zero length",
 			moduleInstance: &wasm.ModuleInstance{
-				Globals:        []*wasm.GlobalInstance{{Val: 100}},
-				TableInstance:  &wasm.TableInstance{Table: make([]wasm.TableElement, 0)},
-				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 0)},
+				Globals: []*wasm.GlobalInstance{{Val: 100}},
+				Table:   &wasm.TableInstance{Table: make([]wasm.TableElement, 0)},
+				Memory:  &wasm.MemoryInstance{Buffer: make([]byte, 0)},
 			},
 		},
 		{
@@ -2222,14 +2222,14 @@ func TestArm64Compiler_compileModuleContextInitialization(t *testing.T) {
 			bufSliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&tc.moduleInstance.Globals))
 			require.Equal(t, bufSliceHeader.Data, ce.moduleContext.globalElement0Address)
 
-			if tc.moduleInstance.MemoryInstance != nil {
-				bufSliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&tc.moduleInstance.MemoryInstance.Buffer))
+			if tc.moduleInstance.Memory != nil {
+				bufSliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&tc.moduleInstance.Memory.Buffer))
 				require.Equal(t, uint64(bufSliceHeader.Len), ce.moduleContext.memorySliceLen)
 				require.Equal(t, bufSliceHeader.Data, ce.moduleContext.memoryElement0Address)
 			}
 
-			if tc.moduleInstance.TableInstance != nil {
-				tableHeader := (*reflect.SliceHeader)(unsafe.Pointer(&tc.moduleInstance.TableInstance.Table))
+			if tc.moduleInstance.Table != nil {
+				tableHeader := (*reflect.SliceHeader)(unsafe.Pointer(&tc.moduleInstance.Table.Table))
 				require.Equal(t, uint64(tableHeader.Len), ce.moduleContext.tableSliceLen)
 				require.Equal(t, tableHeader.Data, ce.moduleContext.tableElement0Address)
 			}
diff --git a/internal/wasm/jit/jit_test.go b/internal/wasm/jit/jit_test.go
index 45ed877b..9256209e 100644
--- a/internal/wasm/jit/jit_test.go
+++ b/internal/wasm/jit/jit_test.go
@@ -40,7 +40,7 @@ func (j *jitEnv) stackTopAsFloat64() float64 {
 }
 
 func (j *jitEnv) memory() []byte {
-	return j.moduleInstance.MemoryInstance.Buffer
+	return j.moduleInstance.Memory.Buffer
 }
 
 func (j *jitEnv) stack() []uint64 {
@@ -76,7 +76,7 @@ func (j *jitEnv) getGlobal(index uint32) uint64 {
 }
 
 func (j *jitEnv) setTable(table []wasm.TableElement) {
-	j.moduleInstance.TableInstance = &wasm.TableInstance{Table: table}
+	j.moduleInstance.Table = &wasm.TableInstance{Table: table}
 }
 
 func (j *jitEnv) callFrameStackPeek() *callFrame {
@@ -132,9 +132,9 @@ func newJITEnvironment() *jitEnv {
 	return &jitEnv{
 		eng: eng,
 		moduleInstance: &wasm.ModuleInstance{
-			MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, wasm.MemoryPageSize*defaultMemoryPageNumInTest)},
-			TableInstance:  &wasm.TableInstance{},
-			Globals:        []*wasm.GlobalInstance{},
+			Memory:  &wasm.MemoryInstance{Buffer: make([]byte, wasm.MemoryPageSize*defaultMemoryPageNumInTest)},
+			Table:   &wasm.TableInstance{},
+			Globals: []*wasm.GlobalInstance{},
 		},
 		ce: eng.newCallEngine(),
 	}
diff --git a/internal/wasm/module_test.go b/internal/wasm/module_test.go
index 3911e5e6..924b8b0d 100644
--- a/internal/wasm/module_test.go
+++ b/internal/wasm/module_test.go
@@ -674,7 +674,7 @@ func TestModule_buildGlobalInstances(t *testing.T) {
 		},
 	}}
 
-	globals := m.buildGlobalInstances(nil)
+	globals := m.buildGlobals(nil)
 	expectedGlobals := []*GlobalInstance{
 		{Type: &GlobalType{ValType: ValueTypeF64, Mutable: true}, Val: math.Float64bits(1.0)},
 		{Type: &GlobalType{ValType: ValueTypeI32, Mutable: false}, Val: uint64(1)},
@@ -701,7 +701,7 @@ func TestModule_buildFunctionInstances(t *testing.T) {
 		CodeSection: []*Code{nopCode, nopCode, nopCode, nopCode, nopCode},
 	}
 
-	actual := m.buildFunctionInstances()
+	actual := m.buildFunctions()
 	expectedNames := []string{"unknown", "two", "unknown", "four", "five"}
 	for i, f := range actual {
 		require.Equal(t, expectedNames[i], f.Name)
@@ -712,14 +712,14 @@ func TestModule_buildFunctionInstances(t *testing.T) {
 func TestModule_buildMemoryInstance(t *testing.T) {
 	t.Run("nil", func(t *testing.T) {
 		m := Module{MemorySection: []*MemoryType{}}
-		mem := m.buildMemoryInstance()
+		mem := m.buildMemory()
 		require.Nil(t, mem)
 	})
 	t.Run("non-nil", func(t *testing.T) {
 		min := uint32(1)
 		max := uint32(10)
 		m := Module{MemorySection: []*MemoryType{&LimitsType{Min: min, Max: &max}}}
-		mem := m.buildMemoryInstance()
+		mem := m.buildMemory()
 		require.Equal(t, min, mem.Min)
 		require.Equal(t, max, *mem.Max)
 	})
@@ -727,6 +727,6 @@ func TestModule_buildMemoryInstance(t *testing.T) {
 
 func TestModule_buildTableInstance(t *testing.T) {
 	m := Module{TableSection: []*TableType{{Limit: &LimitsType{Min: 1}}}}
-	table := m.buildTableInstance()
+	table := m.buildTable()
 	require.Equal(t, uint32(1), table.Min)
 }
diff --git a/internal/wasm/store_test.go b/internal/wasm/store_test.go
index e24325a5..ea10a699 100644
--- a/internal/wasm/store_test.go
+++ b/internal/wasm/store_test.go
@@ -3,8 +3,10 @@ package internalwasm
 import (
 	"context"
 	"encoding/binary"
+	"fmt"
 	"math"
 	"strconv"
+	"sync"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -96,30 +98,263 @@ func TestPublicModule_String(t *testing.T) {
 }
 
 func TestStore_ReleaseModule(t *testing.T) {
-	s := newStore()
+	const importedModuleName = "imported"
+	const importingModuleName = "test"
 
-	// Add the host module
-	hm, err := s.NewHostModule("", map[string]interface{}{"host_fn": func(wasm.ModuleContext) {}})
+	for _, tc := range []struct {
+		name        string
+		initializer func(t *testing.T, s *Store)
+	}{
+		{
+			name: "Module imports HostModule",
+			initializer: func(t *testing.T, s *Store) {
+				_, err := s.NewHostModule(importedModuleName, map[string]interface{}{"fn": func(wasm.ModuleContext) {}})
+				require.NoError(t, err)
+			},
+		},
+		{
+			name: "Module imports Moudle",
+			initializer: func(t *testing.T, s *Store) {
+				_, err := s.Instantiate(&Module{
+					TypeSection:     []*FunctionType{{}},
+					FunctionSection: []uint32{0},
+					CodeSection:     []*Code{{Body: []byte{OpcodeEnd}}},
+					ExportSection:   map[string]*Export{"fn": {Type: ExternTypeFunc, Index: 0, Name: "fn"}},
+				}, importedModuleName)
+				require.NoError(t, err)
+			},
+		},
+	} {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			s := newStore()
+			tc.initializer(t, s)
+
+			_, err := s.Instantiate(&Module{
+				TypeSection:   []*FunctionType{{}},
+				ImportSection: []*Import{{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0}},
+				MemorySection: []*MemoryType{{1, nil}},
+				GlobalSection: []*Global{{Type: &GlobalType{}, Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x1}}}},
+				TableSection:  []*TableType{{Limit: &LimitsType{Min: 10}}},
+			}, importingModuleName)
+			require.NoError(t, err)
+
+			// We shouldn't be able to release the imported module as it is in use!
+			require.Error(t, s.ReleaseModule(importedModuleName))
+
+			// Can release the importing module
+			require.NoError(t, s.ReleaseModule(importingModuleName))
+			require.Nil(t, s.modules[importingModuleName])
+			require.NotContains(t, s.modules, importingModuleName)
+
+			// Can re-release the importing module
+			require.NoError(t, s.ReleaseModule(importingModuleName))
+
+			// Now we should be able to release the imported module.
+			require.NoError(t, s.ReleaseModule(importedModuleName))
+			require.Nil(t, s.modules[importedModuleName])
+			require.NotContains(t, s.modules, importedModuleName)
+
+			// At this point, everything should be freed.
+			require.Len(t, s.modules, 0)
+			for _, m := range s.memories {
+				require.Nil(t, m)
+			}
+			for _, table := range s.tables {
+				require.Nil(t, table)
+			}
+			for _, f := range s.functions {
+				require.Nil(t, f)
+			}
+			for _, g := range s.globals {
+				require.Nil(t, g)
+			}
+
+			// One function, globa, memory and table instance was created and freed,
+			// therefore, one released index must be captured by store.
+			require.Len(t, s.releasedFunctionIndex, 1)
+			require.Len(t, s.releasedTableIndex, 1)
+			require.Len(t, s.releasedMemoryIndex, 1)
+			require.Len(t, s.releasedGlobalIndex, 1)
+		})
+	}
+}
+
+func TestStore_concurrent(t *testing.T) {
+	const importedModuleName = "imported"
+	const goroutines = 1000
+
+	var wg sync.WaitGroup
+
+	s := newStore()
+	_, err := s.NewHostModule(importedModuleName, map[string]interface{}{"fn": func(wasm.ModuleContext) {}})
 	require.NoError(t, err)
 
-	t.Run("Module imports HostModule", func(t *testing.T) {
-		name := "test"
+	hm, ok := s.modules[importedModuleName]
+	require.True(t, ok)
+
+	importingModule := &Module{
+		TypeSection:     []*FunctionType{{}},
+		FunctionSection: []uint32{0},
+		CodeSection:     []*Code{{Body: []byte{OpcodeEnd}}},
+		MemorySection:   []*MemoryType{{1, nil}},
+		GlobalSection:   []*Global{{Type: &GlobalType{}, Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x1}}}},
+		TableSection:    []*TableType{{Limit: &LimitsType{Min: 10}}},
+		ImportSection: []*Import{
+			{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0},
+		},
+	}
+
+	// Concurrent instantiation.
+	wg.Add(goroutines)
+	for i := 0; i < goroutines; i++ {
+		go func(i int) {
+			defer wg.Done()
+			_, err := s.Instantiate(importingModule, strconv.Itoa(i))
+			require.NoError(t, err)
+
+			if i == goroutines/2 {
+				// Trying to release the imported module concurrently, but should fail as it's in use.
+				err := s.ReleaseModule(importedModuleName)
+				require.Error(t, err)
+			}
+		}(i)
+
+		// TODO: in addition to the normal instantiation, we should try to instantiate host module in conjunction.
+		// after making store.NewHostModule goroutine-safe.
+	}
+	wg.Wait()
+
+	// At this point 1000 modules import host modules.
+	require.Equal(t, goroutines, hm.dependentCount)
+
+	require.Len(t, s.functions, goroutines+1) // Instantiated + imported one.
+	for _, f := range s.functions {
+		require.NotNil(t, f)
+	}
+
+	require.Len(t, s.tables, goroutines)
+	for _, table := range s.tables {
+		require.NotNil(t, table)
+	}
+
+	require.Len(t, s.globals, goroutines)
+	for _, g := range s.globals {
+		require.NotNil(t, g)
+	}
+
+	require.Len(t, s.memories, goroutines)
+	for _, m := range s.memories {
+		require.NotNil(t, m)
+	}
+
+	// Concurrent release.
+	wg.Add(goroutines)
+	for i := 0; i < goroutines; i++ {
+		go func(i int) {
+			defer wg.Done()
+			err := s.ReleaseModule(strconv.Itoa(i))
+			require.NoError(t, err)
+		}(i)
+	}
+	wg.Wait()
+
+	// No all the importing instances were released, the imported module can be freed.
+	require.Zero(t, hm.dependentCount)
+	require.NoError(t, s.ReleaseModule(hm.Name))
+
+	// All instances are freed.
+	require.Len(t, s.modules, 0)
+}
+
+func TestSotre_Instantiate_Errors(t *testing.T) {
+	const importedModuleName = "imported"
+	const importingModuleName = "test"
+
+	t.Run("fail resolve import", func(t *testing.T) {
+		s := newStore()
+		_, err := s.NewHostModule(importedModuleName, map[string]interface{}{"fn": func(wasm.ModuleContext) {}})
+		require.NoError(t, err)
+
+		hm := s.modules[importedModuleName]
+		require.NotNil(t, hm)
+
 		_, err = s.Instantiate(&Module{
-			TypeSection:   []*FunctionType{{}},
-			ImportSection: []*Import{{Type: ExternTypeFunc, Name: "host_fn", DescFunc: 0}},
-			MemorySection: []*MemoryType{{1, nil}},
-		}, name)
+			TypeSection: []*FunctionType{{}},
+			ImportSection: []*Import{
+				// The fisrt import resolve succeeds -> increment hm.dependentCount.
+				{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0},
+				// But the second one tries to import uninitialized-module ->
+				{Type: ExternTypeFunc, Module: "non-exist", Name: "fn", DescFunc: 0},
+			},
+		}, importingModuleName)
+		require.EqualError(t, err, "module \"non-exist\" not instantiated")
+
+		// hm.dependentCount must be intact as the instantiation failed.
+		require.Zero(t, hm.dependentCount)
+	})
+
+	t.Run("compilation failed", func(t *testing.T) {
+		s := newStore()
+		catch := s.engine.(*catchContext)
+		catch.compilationFailIndex = 3
+
+		_, err := s.NewHostModule(importedModuleName, map[string]interface{}{"fn": func(wasm.ModuleContext) {}})
+		require.NoError(t, err)
+
+		hm := s.modules[importedModuleName]
+		require.NotNil(t, hm)
+
+		_, err = s.Instantiate(&Module{
+			TypeSection:     []*FunctionType{{}},
+			FunctionSection: []uint32{0, 0, 0 /* compilation failing function */},
+			CodeSection: []*Code{
+				{Body: []byte{OpcodeEnd}}, // FunctionIndex = 1
+				{Body: []byte{OpcodeEnd}}, // FunctionIndex = 2
+				{Body: []byte{OpcodeEnd}}, // FunctionIndex = 3 == compilation failing function.
+				// Functions after failrued must not be passed to engine.Release.
+				{Body: []byte{OpcodeEnd}},
+				{Body: []byte{OpcodeEnd}},
+				{Body: []byte{OpcodeEnd}},
+				{Body: []byte{OpcodeEnd}},
+			},
+			ImportSection: []*Import{
+				{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0},
+			},
+		}, importingModuleName)
+		require.EqualError(t, err, "compilation failed at index 2/2: compilation failed")
+
+		// hm.dependentCount must be intact as the instantiation failed.
+		require.Zero(t, hm.dependentCount)
+
+		require.Equal(t, catch.releasedCalledFunctionIndex, []FunctionIndex{0x1, 0x2})
+	})
+
+	t.Run("start func failed", func(t *testing.T) {
+		s := newStore()
+		catch := s.engine.(*catchContext)
+		catch.callFailIndex = 1
+
+		_, err := s.NewHostModule(importedModuleName, map[string]interface{}{"fn": func(wasm.ModuleContext) {}})
 		require.NoError(t, err)
 
-		// TODO: We shouldn't be able to release the host module as it is in use!
-		require.NoError(t, s.ReleaseModuleInstance(hm.name))
+		hm := s.modules[importedModuleName]
+		require.NotNil(t, hm)
 
-		// Can release the importing module
-		require.NoError(t, s.ReleaseModuleInstance(name))
-		require.Nil(t, s.moduleInstances[name])
+		startFuncIndex := uint32(1)
+		_, err = s.Instantiate(&Module{
+			TypeSection:     []*FunctionType{{}},
+			FunctionSection: []uint32{0},
+			CodeSection:     []*Code{{Body: []byte{OpcodeEnd}}},
+			StartSection:    &startFuncIndex,
+			ImportSection: []*Import{
+				{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0},
+			},
+		}, importingModuleName)
+		require.EqualError(t, err, "module[test] start function failed: call failed")
 
-		// Can re-release the importing module
-		require.NoError(t, s.ReleaseModuleInstance(name))
+		// hm.dependentCount must stay incremented as the instantiation itself has already succeeded.
+		require.Equal(t, 1, hm.dependentCount)
 	})
 }
 
@@ -139,7 +374,7 @@ func TestStore_ExportImportedHostFunction(t *testing.T) {
 		}, "test")
 		require.NoError(t, err)
 
-		mod, ok := s.moduleInstances["test"]
+		mod, ok := s.modules["test"]
 		require.True(t, ok)
 
 		ei, err := mod.getExport("host.fn", ExternTypeFunc)
@@ -147,7 +382,7 @@ func TestStore_ExportImportedHostFunction(t *testing.T) {
 		// We expect the host function to be called in context of the importing module.
 		// Otherwise, it would be the pseudo-module of the host, which only includes types and function definitions.
 		// Notably, this ensures the host function call context has the correct memory (from the importing module).
-		require.Equal(t, s.moduleInstances["test"], ei.Function.Module)
+		require.Equal(t, s.modules["test"], ei.Function.Module)
 	})
 }
 
@@ -178,7 +413,7 @@ func TestFunctionInstance_Call(t *testing.T) {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			engine := &catchContext{}
+			engine := &catchContext{callFailIndex: -1, compilationFailIndex: -1}
 			store := NewStore(storeCtx, engine, Features20191205)
 
 			// Add the host module
@@ -215,19 +450,28 @@ func TestFunctionInstance_Call(t *testing.T) {
 }
 
 type catchContext struct {
-	ctx *ModuleContext
+	ctx                                 *ModuleContext
+	compilationFailIndex, callFailIndex int
+	releasedCalledFunctionIndex         []FunctionIndex
 }
 
-func (e *catchContext) Call(ctx *ModuleContext, _ *FunctionInstance, _ ...uint64) (results []uint64, err error) {
+func (e *catchContext) Call(ctx *ModuleContext, f *FunctionInstance, _ ...uint64) (results []uint64, err error) {
+	if e.callFailIndex >= 0 && f.Index == FunctionIndex(e.callFailIndex) {
+		return nil, fmt.Errorf("call failed")
+	}
 	e.ctx = ctx
 	return
 }
 
-func (e *catchContext) Compile(_ *FunctionInstance) error {
+func (e *catchContext) Compile(f *FunctionInstance) error {
+	if e.compilationFailIndex >= 0 && f.Index == FunctionIndex(e.compilationFailIndex) {
+		return fmt.Errorf("compilation failed")
+	}
 	return nil
 }
 
-func (e *catchContext) Release(_ *FunctionInstance) error {
+func (e *catchContext) Release(f *FunctionInstance) error {
+	e.releasedCalledFunctionIndex = append(e.releasedCalledFunctionIndex, f.Index)
 	return nil
 }
 
@@ -377,23 +621,20 @@ func TestStore_releaseFunctionInstances(t *testing.T) {
 	s.functions[nonReleaseTargetAddr] = &FunctionInstance{} // Non-nil!
 
 	// Set up existing function instances.
-	var expectedReleasedAddr []FunctionIndex
 	fs := []*FunctionInstance{{Index: 1}, {Index: 2}, {Index: 3}, {Index: 4}, {Index: maxAddr}}
 	for _, f := range fs {
 		s.functions[f.Index] = &FunctionInstance{} // Non-nil!
-		expectedReleasedAddr = append(expectedReleasedAddr, f.Index)
 	}
 
-	err := s.releaseFunctionInstances(fs...)
+	err := s.releaseFunctions(fs...)
 	require.NoError(t, err)
 
 	// Ensure the release targets become nil.
 	for _, f := range fs {
 		require.Nil(t, s.functions[f.Index])
+		require.Contains(t, s.releasedFunctionIndex, f.Index)
 	}
 
-	require.Equal(t, s.releasedFunctionIndex, expectedReleasedAddr)
-
 	// Plus non-target should remain intact.
 	require.NotNil(t, s.functions[nonReleaseTargetAddr])
 }
@@ -407,7 +648,7 @@ func TestStore_addFunctionInstances(t *testing.T) {
 		for i := FunctionIndex(0); i < 10; i++ {
 			expectedIndex := prevMaxAddr + 1 + i
 			f := &FunctionInstance{}
-			s.addFunctionInstances(f)
+			s.addFunctions(f)
 
 			// After adding function intance to store, an funcaddr must be assigned.
 			require.Equal(t, expectedIndex, f.Index)
@@ -416,8 +657,7 @@ func TestStore_addFunctionInstances(t *testing.T) {
 	t.Run("reuse released index", func(t *testing.T) {
 		s := newStore()
 		expectedAddr := FunctionIndex(10)
-		s.releasedFunctionIndex = []FunctionIndex{1, expectedAddr}
-		expectedReleasedAddr := s.releasedFunctionIndex[:1]
+		s.releasedFunctionIndex[expectedAddr] = struct{}{}
 
 		maxAddr := expectedAddr * 10
 		tailInstance := &FunctionInstance{}
@@ -425,15 +665,16 @@ func TestStore_addFunctionInstances(t *testing.T) {
 		s.functions[maxAddr] = tailInstance
 
 		f := &FunctionInstance{}
-		s.addFunctionInstances(f)
+		s.addFunctions(f)
 
 		// Index must be reused.
 		require.Equal(t, expectedAddr, f.Index)
+		require.Equal(t, f, s.functions[expectedAddr])
 
 		// And the others must be intact.
 		require.Equal(t, tailInstance, s.functions[maxAddr])
 
-		require.Equal(t, expectedReleasedAddr, s.releasedFunctionIndex)
+		require.Len(t, s.releasedFunctionIndex, 0)
 	})
 }
 
@@ -446,22 +687,19 @@ func TestStore_releaseGlobalInstances(t *testing.T) {
 	s.globals[nonReleaseTargetAddr] = &GlobalInstance{} // Non-nil!
 
 	// Set up existing function instances.
-	var expectedReleasedAddr []globalIndex
 	gs := []*GlobalInstance{{index: 1}, {index: 2}, {index: 3}, {index: 4}, {index: maxAddr}}
 	for _, g := range gs {
 		s.globals[g.index] = &GlobalInstance{} // Non-nil!
-		expectedReleasedAddr = append(expectedReleasedAddr, g.index)
 	}
 
-	s.releaseGlobalInstances(gs...)
+	s.releaseGlobal(gs...)
 
 	// Ensure the release targets become nil.
 	for _, g := range gs {
 		require.Nil(t, s.globals[g.index])
+		require.Contains(t, s.releasedGlobalIndex, g.index)
 	}
 
-	require.Equal(t, s.releasedGlobalIndex, expectedReleasedAddr)
-
 	// Plus non-target should remain intact.
 	require.NotNil(t, s.globals[nonReleaseTargetAddr])
 }
@@ -475,7 +713,7 @@ func TestStore_addGlobalInstances(t *testing.T) {
 		for i := globalIndex(0); i < 10; i++ {
 			expectedIndex := prevMaxAddr + 1 + i
 			g := &GlobalInstance{}
-			s.addGlobalInstances(g)
+			s.addGlobals(g)
 
 			// After adding function intance to store, an funcaddr must be assigned.
 			require.Equal(t, expectedIndex, g.index)
@@ -484,8 +722,7 @@ func TestStore_addGlobalInstances(t *testing.T) {
 	t.Run("reuse released index", func(t *testing.T) {
 		s := newStore()
 		expectedAddr := globalIndex(10)
-		s.releasedGlobalIndex = []globalIndex{1, expectedAddr}
-		expectedReleasedGlobalIndex := s.releasedGlobalIndex[:1]
+		s.releasedGlobalIndex[expectedAddr] = struct{}{}
 
 		maxAddr := expectedAddr * 10
 		tailInstance := &GlobalInstance{}
@@ -493,15 +730,16 @@ func TestStore_addGlobalInstances(t *testing.T) {
 		s.globals[maxAddr] = tailInstance
 
 		g := &GlobalInstance{}
-		s.addGlobalInstances(g)
+		s.addGlobals(g)
 
 		// Index must be reused.
 		require.Equal(t, expectedAddr, g.index)
+		require.Equal(t, g, s.globals[expectedAddr])
 
 		// And the others must be intact.
 		require.Equal(t, tailInstance, s.globals[maxAddr])
 
-		require.Equal(t, expectedReleasedGlobalIndex, s.releasedGlobalIndex)
+		require.Len(t, s.releasedGlobalIndex, 0)
 	})
 }
 
@@ -515,7 +753,7 @@ func TestStore_releaseTableInstance(t *testing.T) {
 
 	table := &TableInstance{index: 1}
 
-	s.releaseTableInstance(table)
+	s.releaseTable(table)
 
 	// Ensure the release targets become nil.
 	require.Nil(t, s.tables[table.index])
@@ -535,7 +773,7 @@ func TestStore_addTableInstance(t *testing.T) {
 		for i := tableIndex(0); i < 10; i++ {
 			expectedIndex := prevMaxAddr + 1 + i
 			g := &TableInstance{}
-			s.addTableInstance(g)
+			s.addTable(g)
 
 			// After adding function intance to store, an funcaddr must be assigned.
 			require.Equal(t, expectedIndex, g.index)
@@ -544,8 +782,7 @@ func TestStore_addTableInstance(t *testing.T) {
 	t.Run("reuse released index", func(t *testing.T) {
 		s := newStore()
 		expectedAddr := tableIndex(10)
-		s.releasedTableIndex = []tableIndex{1000, expectedAddr}
-		expectedReleasedTableIndex := s.releasedTableIndex[:1]
+		s.releasedTableIndex[expectedAddr] = struct{}{}
 
 		maxAddr := expectedAddr * 10
 		tailInstance := &TableInstance{}
@@ -553,15 +790,16 @@ func TestStore_addTableInstance(t *testing.T) {
 		s.tables[maxAddr] = tailInstance
 
 		table := &TableInstance{}
-		s.addTableInstance(table)
+		s.addTable(table)
 
 		// Index must be reused.
 		require.Equal(t, expectedAddr, table.index)
+		require.Equal(t, table, s.tables[expectedAddr])
 
 		// And the others must be intact.
 		require.Equal(t, tailInstance, s.tables[maxAddr])
 
-		require.Equal(t, expectedReleasedTableIndex, s.releasedTableIndex)
+		require.Len(t, s.releasedTableIndex, 0)
 	})
 }
 
@@ -575,12 +813,12 @@ func TestStore_releaseMemoryInstance(t *testing.T) {
 	mem := &MemoryInstance{index: releaseTargetAddr}
 	s.memories[releaseTargetAddr] = mem // Non-nil!
 
-	s.releaseMemoryInstance(mem)
+	s.releaseMemory(mem)
 
 	// Ensure the release targets become nil.
 	require.Nil(t, s.memories[mem.index])
 
-	require.Equal(t, s.releasedMemoryIndex, []memoryIndex{mem.index})
+	require.Contains(t, s.releasedMemoryIndex, mem.index)
 
 	// Plus non-target should remain intact.
 	require.NotNil(t, s.memories[nonReleaseTargetAddr])
@@ -595,7 +833,7 @@ func TestStore_addMemoryInstance(t *testing.T) {
 		for i := memoryIndex(0); i < 10; i++ {
 			expectedIndex := prevMaxAddr + 1 + i
 			mem := &MemoryInstance{}
-			s.addMemoryInstance(mem)
+			s.addMemory(mem)
 
 			// After adding function intance to store, an funcaddr must be assigned.
 			require.Equal(t, expectedIndex, mem.index)
@@ -604,8 +842,7 @@ func TestStore_addMemoryInstance(t *testing.T) {
 	t.Run("reuse released index", func(t *testing.T) {
 		s := newStore()
 		expectedAddr := memoryIndex(10)
-		s.releasedMemoryIndex = []memoryIndex{1000, expectedAddr}
-		expectedReleasedMemoryIndex := s.releasedMemoryIndex[:1]
+		s.releasedMemoryIndex[expectedAddr] = struct{}{}
 
 		maxAddr := expectedAddr * 10
 		tailInstance := &MemoryInstance{}
@@ -613,15 +850,16 @@ func TestStore_addMemoryInstance(t *testing.T) {
 		s.memories[maxAddr] = tailInstance
 
 		mem := &MemoryInstance{}
-		s.addMemoryInstance(mem)
+		s.addMemory(mem)
 
 		// Index must be reused.
 		require.Equal(t, expectedAddr, mem.index)
+		require.Equal(t, mem, s.memories[expectedAddr])
 
 		// And the others must be intact.
 		require.Equal(t, tailInstance, s.memories[maxAddr])
 
-		require.Equal(t, expectedReleasedMemoryIndex, s.releasedMemoryIndex)
+		require.Len(t, s.releasedMemoryIndex, 0)
 	})
 }
 
@@ -632,27 +870,24 @@ func TestStore_resolveImports(t *testing.T) {
 	t.Run("module not instantiated", func(t *testing.T) {
 		s := newStore()
 		_, _, _, _, _, err := s.resolveImports(&Module{ImportSection: []*Import{{Module: "unknown", Name: "unknown"}}})
-		require.Error(t, err)
-		require.Contains(t, err.Error(), "module \"unknown\" not instantiated")
+		require.EqualError(t, err, "module \"unknown\" not instantiated")
 	})
 	t.Run("export instance not found", func(t *testing.T) {
 		s := newStore()
-		s.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{}, Name: moduleName}
+		s.modules[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{}, Name: moduleName}
 		_, _, _, _, _, err := s.resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: "unknown"}}})
-		require.Error(t, err)
-		require.Contains(t, err.Error(), "\"unknown\" is not exported in module \"test\"")
+		require.EqualError(t, err, "\"unknown\" is not exported in module \"test\"")
 	})
 	t.Run("func", func(t *testing.T) {
 		t.Run("unknwon type", func(t *testing.T) {
 			s := newStore()
-			s.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {}}, Name: moduleName}
+			s.modules[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {}}, Name: moduleName}
 			_, _, _, _, _, err := s.resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 100}}})
-			require.Error(t, err)
-			require.Contains(t, err.Error(), "unknown type for function import")
+			require.EqualError(t, err, "unknown type for function import")
 		})
 		t.Run("signature mismatch", func(t *testing.T) {
 			s := newStore()
-			s.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
+			s.modules[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
 				Function: &FunctionInstance{Type: &FunctionType{}},
 			}}, Name: moduleName}
 			m := &Module{
@@ -660,13 +895,12 @@ func TestStore_resolveImports(t *testing.T) {
 				ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 0}},
 			}
 			_, _, _, _, _, err := s.resolveImports(m)
-			require.Error(t, err)
-			require.Contains(t, err.Error(), "signature mimatch: v_f32 != v_v")
+			require.EqualError(t, err, "signature mimatch: v_f32 != v_v")
 		})
 		t.Run("ok", func(t *testing.T) {
 			s := newStore()
 			f := &FunctionInstance{Type: &FunctionType{Results: []ValueType{ValueTypeF32}}}
-			s.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
+			s.modules[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
 				Function: f,
 			}}, Name: moduleName}
 			m := &Module{
@@ -675,128 +909,125 @@ func TestStore_resolveImports(t *testing.T) {
 			}
 			functions, _, _, _, moduleImports, err := s.resolveImports(m)
 			require.NoError(t, err)
-			require.Contains(t, moduleImports, s.moduleInstances[moduleName])
+			require.Contains(t, moduleImports, s.modules[moduleName])
 			require.Contains(t, functions, f)
+			require.Equal(t, 1, s.modules[moduleName].dependentCount)
 		})
 	})
 	t.Run("global", func(t *testing.T) {
 		t.Run("mutability mismatch", func(t *testing.T) {
 			s := newStore()
-			s.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
+			s.modules[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
 				Type:   ExternTypeGlobal,
 				Global: &GlobalInstance{Type: &GlobalType{Mutable: false}},
 			}}, Name: moduleName}
 			_, _, _, _, _, err := s.resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: &GlobalType{Mutable: true}}}})
-			require.Error(t, err)
-			require.Contains(t, err.Error(), "incompatible global import: mutability mismatch")
+			require.EqualError(t, err, "incompatible global import: mutability mismatch")
 		})
 		t.Run("type mismatch", func(t *testing.T) {
 			s := newStore()
-			s.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
+			s.modules[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
 				Type:   ExternTypeGlobal,
 				Global: &GlobalInstance{Type: &GlobalType{ValType: ValueTypeI32}},
 			}}, Name: moduleName}
 			_, _, _, _, _, err := s.resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: &GlobalType{ValType: ValueTypeF64}}}})
-			require.Error(t, err)
-			require.Contains(t, err.Error(), "incompatible global import: value type mismatch")
+			require.EqualError(t, err, "incompatible global import: value type mismatch")
 		})
 		t.Run("ok", func(t *testing.T) {
 			s := newStore()
 			inst := &GlobalInstance{Type: &GlobalType{ValType: ValueTypeI32}}
-			s.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {Type: ExternTypeGlobal, Global: inst}}, Name: moduleName}
+			s.modules[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {Type: ExternTypeGlobal, Global: inst}}, Name: moduleName}
 			_, globals, _, _, _, err := s.resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: inst.Type}}})
 			require.NoError(t, err)
 			require.Contains(t, globals, inst)
+			require.Equal(t, 1, s.modules[moduleName].dependentCount)
 		})
 	})
 	t.Run("table", func(t *testing.T) {
 		t.Run("element type", func(t *testing.T) {
 			s := newStore()
-			s.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
+			s.modules[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
 				Type:  ExternTypeTable,
 				Table: &TableInstance{ElemType: 0x00}, // Unknown!
 			}}, Name: moduleName}
 			_, _, _, _, _, err := s.resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: &TableType{ElemType: 0x1}}}})
-			require.Error(t, err)
-			require.Contains(t, err.Error(), "incompatible table improt: element type mismatch")
+			require.EqualError(t, err, "incompatible table import: element type mismatch")
 		})
 		t.Run("minimum size mismatch", func(t *testing.T) {
 			s := newStore()
 			importTableType := &TableType{Limit: &LimitsType{Min: 2}}
-			s.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
+			s.modules[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
 				Type:  ExternTypeTable,
 				Table: &TableInstance{Min: importTableType.Limit.Min - 1},
 			}}, Name: moduleName}
 			_, _, _, _, _, err := s.resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: importTableType}}})
-			require.Error(t, err)
-			require.Contains(t, err.Error(), "incompatible table import: minimum size mismatch")
+			require.EqualError(t, err, "incompatible table import: minimum size mismatch")
 		})
 		t.Run("maximum size mismatch", func(t *testing.T) {
 			s := newStore()
 			max := uint32(10)
 			importTableType := &TableType{Limit: &LimitsType{Max: &max}}
-			s.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
+			s.modules[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
 				Type:  ExternTypeTable,
 				Table: &TableInstance{Min: importTableType.Limit.Min - 1},
 			}}, Name: moduleName}
 			_, _, _, _, _, err := s.resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: importTableType}}})
-			require.Error(t, err)
-			require.Contains(t, err.Error(), "incompatible table import: maximum size mismatch")
+			require.EqualError(t, err, "incompatible table import: maximum size mismatch")
 		})
 		t.Run("ok", func(t *testing.T) {
 			s := newStore()
 			max := uint32(10)
 			tableInst := &TableInstance{Max: &max}
-			s.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
+			s.modules[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
 				Type:  ExternTypeTable,
 				Table: tableInst,
 			}}, Name: moduleName}
 			_, _, table, _, _, err := s.resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: &TableType{Limit: &LimitsType{Max: &max}}}}})
 			require.NoError(t, err)
 			require.Equal(t, table, tableInst)
+			require.Equal(t, 1, s.modules[moduleName].dependentCount)
 		})
 	})
 	t.Run("memory", func(t *testing.T) {
 		t.Run("minimum size mismatch", func(t *testing.T) {
 			s := newStore()
 			importMemoryType := &MemoryType{Min: 2}
-			s.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
+			s.modules[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
 				Type:   ExternTypeMemory,
 				Memory: &MemoryInstance{Min: importMemoryType.Min - 1},
 			}}, Name: moduleName}
 			_, _, _, _, _, err := s.resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: importMemoryType}}})
-			require.Error(t, err)
-			require.Contains(t, err.Error(), "incompatible memory import: minimum size mismatch")
+			require.EqualError(t, err, "incompatible memory import: minimum size mismatch")
 		})
 		t.Run("maximum size mismatch", func(t *testing.T) {
 			s := newStore()
 			max := uint32(10)
 			importMemoryType := &MemoryType{Max: &max}
-			s.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
+			s.modules[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
 				Type:   ExternTypeMemory,
 				Memory: &MemoryInstance{},
 			}}, Name: moduleName}
 			_, _, _, _, _, err := s.resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: importMemoryType}}})
-			require.Error(t, err)
-			require.Contains(t, err.Error(), "incompatible memory import: maximum size mismatch")
+			require.EqualError(t, err, "incompatible memory import: maximum size mismatch")
 		})
 		t.Run("ok", func(t *testing.T) {
 			s := newStore()
 			max := uint32(10)
 			memoryInst := &MemoryInstance{Max: &max}
-			s.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
+			s.modules[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
 				Type:   ExternTypeMemory,
 				Memory: memoryInst,
 			}}, Name: moduleName}
 			_, _, _, memory, _, err := s.resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: &MemoryType{Max: &max}}}})
 			require.NoError(t, err)
 			require.Equal(t, memory, memoryInst)
+			require.Equal(t, 1, s.modules[moduleName].dependentCount)
 		})
 	})
 }
 
 func TestModuleInstance_validateData(t *testing.T) {
-	m := &ModuleInstance{MemoryInstance: &MemoryInstance{Buffer: make([]byte, 5)}}
+	m := &ModuleInstance{Memory: &MemoryInstance{Buffer: make([]byte, 5)}}
 	for _, tc := range []struct {
 		name   string
 		data   []*DataSegment
@@ -837,19 +1068,19 @@ func TestModuleInstance_validateData(t *testing.T) {
 }
 
 func TestModuleInstance_applyData(t *testing.T) {
-	m := &ModuleInstance{MemoryInstance: &MemoryInstance{Buffer: make([]byte, 10)}}
+	m := &ModuleInstance{Memory: &MemoryInstance{Buffer: make([]byte, 10)}}
 	m.applyData([]*DataSegment{
 		{OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x0}}, Init: []byte{0xa, 0xf}},
 		{OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x8}}, Init: []byte{0x1, 0x5}},
 	})
-	require.Equal(t, []byte{0xa, 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5}, m.MemoryInstance.Buffer)
+	require.Equal(t, []byte{0xa, 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5}, m.Memory.Buffer)
 }
 
 func TestModuleInstance_validateElements(t *testing.T) {
 	functionCounts := uint32(0xa)
 	m := &ModuleInstance{
-		TableInstance: &TableInstance{Table: make([]TableElement, 10)},
-		Functions:     make([]*FunctionInstance, 10),
+		Table:     &TableInstance{Table: make([]TableElement, 10)},
+		Functions: make([]*FunctionInstance, 10),
 	}
 	for _, tc := range []struct {
 		name     string
@@ -898,8 +1129,8 @@ func TestModuleInstance_validateElements(t *testing.T) {
 func TestModuleInstance_applyElements(t *testing.T) {
 	functionCounts := uint32(0xa)
 	m := &ModuleInstance{
-		TableInstance: &TableInstance{Table: make([]TableElement, 10)},
-		Functions:     make([]*FunctionInstance, 10),
+		Table:     &TableInstance{Table: make([]TableElement, 10)},
+		Functions: make([]*FunctionInstance, 10),
 	}
 	targetAddr, targetOffset := uint32(1), byte(0)
 	targetAddr2, targetOffset2 := functionCounts-1, byte(0x8)
@@ -909,18 +1140,41 @@ func TestModuleInstance_applyElements(t *testing.T) {
 		{OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{targetOffset}}, Init: []uint32{uint32(targetAddr)}},
 		{OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{targetOffset2}}, Init: []uint32{targetAddr2}},
 	})
-	require.Equal(t, FunctionIndex(targetAddr), m.TableInstance.Table[targetOffset].FunctionIndex)
-	require.Equal(t, FunctionIndex(targetAddr2), m.TableInstance.Table[targetOffset2].FunctionIndex)
+	require.Equal(t, FunctionIndex(targetAddr), m.Table.Table[targetOffset].FunctionIndex)
+	require.Equal(t, FunctionIndex(targetAddr2), m.Table.Table[targetOffset2].FunctionIndex)
 }
 
-func Test_newModuleInstance(t *testing.T) {
-	// TODO
+func TestModuleInstance_decImportedCount(t *testing.T) {
+	count := 100
+	m := ModuleInstance{dependentCount: count}
+
+	wg := sync.WaitGroup{}
+	wg.Add(count)
+	for i := 0; i < count; i++ {
+		go func() {
+			defer wg.Done()
+			m.decImportedCount()
+		}()
+	}
+	wg.Wait()
+	require.Zero(t, m.dependentCount)
 }
 
-func TestStore_releaseModuleInstance(t *testing.T) {
-	// TODO:
+func TestModuleInstance_incImportedCount(t *testing.T) {
+	count := 100
+	m := ModuleInstance{}
+	wg := sync.WaitGroup{}
+	wg.Add(count)
+	for i := 0; i < count; i++ {
+		go func() {
+			defer wg.Done()
+			m.incImportedCount()
+		}()
+	}
+	wg.Wait()
+	require.Equal(t, count, m.dependentCount)
 }
 
 func newStore() *Store {
-	return NewStore(context.Background(), &catchContext{}, Features20191205)
+	return NewStore(context.Background(), &catchContext{compilationFailIndex: -1, callFailIndex: -1}, Features20191205)
 }
diff --git a/tests/engine/concurrency_test.go b/tests/engine/concurrency_test.go
index 868f166c..bbb2c161 100644
--- a/tests/engine/concurrency_test.go
+++ b/tests/engine/concurrency_test.go
@@ -12,13 +12,13 @@ func TestJITConcurrency(t *testing.T) {
 		t.Skip()
 	}
 	runAdhocTestsUnderHighConcurrency(t, wazero.NewRuntimeConfigJIT)
-	// TODO: Add conccurent instantiation, invocation and release on a single store test case in https://github.com/tetratelabs/wazero/issues/293
+	// TODO: Add concurrent instantiation, invocation and release on a single store test case in https://github.com/tetratelabs/wazero/issues/293
 
 }
 
 func TestInterpreterConcurrency(t *testing.T) {
 	runAdhocTestsUnderHighConcurrency(t, wazero.NewRuntimeConfigInterpreter)
-	// TODO: Add conccurent instantiation, invocation and release on a single store test case in https://github.com/tetratelabs/wazero/issues/293
+	// TODO: Add concurrent instantiation, invocation and release on a single store test case in https://github.com/tetratelabs/wazero/issues/293
 }
 
 func runAdhocTestsUnderHighConcurrency(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {