Repository: tetratelabs/wazero
Pull request #319 (2022-03-03 07:39:31)

Make store's fields private
reworkd of #305 

mathetake: almost all here is mechanical! 

Patch:
diff --git a/README.md b/README.md
index b065cffb..aa3cf309 100644
--- a/README.md
+++ b/README.md
@@ -31,12 +31,12 @@ wazero is an early project, so APIs are subject to change until version 1.0.
 
 There are two runtime configurations supported in wazero, where _JIT_ is default:
 
-1. _Interpreter_: a naive interpreter-based implementation of Wasm virtual machine. Its implementation doesn't have any platform (GOARCH, GOOS) specific code, therefore _interpreter_ engine can be used for any compilation target available for Go (such as `riscv64`).
-2. _JIT_: compiles WebAssembly modules, generates the machine code, and executing it all at runtime. Currently wazero implements the JIT compiler for `amd64` and `arm64` target. Generally speaking, _JIT engine_ is faster than _Interpreter_ by order of magnitude. However, the implementation is immature and has a bunch of aspects that could be improved (for example, it just does a singlepass compilation and doesn't do any optimizations, etc.). Please refer to [internal/wasm/jit/RATIONALE.md](internal/wasm/jit/RATIONALE.md) for the design choices and considerations in our JIT engine.
+1. _Interpreter_: a naive interpreter-based implementation of Wasm virtual machine. Its implementation doesn't have any platform (GOARCH, GOOS) specific code, therefore _interpreter_ can be used for any compilation target available for Go (such as `riscv64`).
+2. _JIT_: compiles WebAssembly modules, generates the machine code, and executing it all at runtime. Currently wazero implements the JIT compiler for `amd64` and `arm64` target. Generally speaking, _JIT_ is faster than _Interpreter_ by order of magnitude. However, the implementation is immature and has a bunch of aspects that could be improved (for example, it just does a singlepass compilation and doesn't do any optimizations, etc.). Please refer to [internal/wasm/jit/RATIONALE.md](internal/wasm/jit/RATIONALE.md) for the design choices and considerations in our JIT compiler.
 
 Both configurations pass 100% of [WebAssembly spec test suites]((https://github.com/WebAssembly/spec/tree/wg-1.0/test/core)) (on supported platforms).
 
-| Engine     | Usage| amd64 | arm64 | others |
+| Runtime     | Usage| amd64 | arm64 | others |
 |:---:|:---:|:---:|:---:|:---:|
 | Interpreter|`wazero.NewRuntimeConfigInterpreter()`|✅ |✅|✅|
 | JIT |`wazero.NewRuntimeConfigJIT()`|✅|✅ |❌|
diff --git a/internal/wasm/global.go b/internal/wasm/global.go
index 7ac90a52..1906fa47 100644
--- a/internal/wasm/global.go
+++ b/internal/wasm/global.go
@@ -124,7 +124,7 @@ func (g globalF64) String() string {
 
 // Global implements wasm.Module Global
 func (m *PublicModule) Global(name string) publicwasm.Global {
-	exp, err := m.Context.Module.GetExport(name, ExternTypeGlobal)
+	exp, err := m.Instance.GetExport(name, ExternTypeGlobal)
 	if err != nil {
 		return nil
 	}
diff --git a/internal/wasm/host.go b/internal/wasm/host.go
index 5c9c00f9..53f83ba3 100644
--- a/internal/wasm/host.go
+++ b/internal/wasm/host.go
@@ -98,9 +98,12 @@ func (s *Store) NewHostModule(moduleName string, nameToGoFunc map[string]interfa
 
 	exportCount := len(nameToGoFunc)
 
-	hostModule := &ModuleInstance{Name: moduleName, Exports: make(map[string]*ExportInstance, exportCount)}
-	s.ModuleInstances[moduleName] = hostModule
-	ret := HostModule{NameToFunctionInstance: make(map[string]*FunctionInstance, exportCount)}
+	ret := &HostModule{NameToFunctionInstance: make(map[string]*FunctionInstance, exportCount)}
+	hostModule := &ModuleInstance{
+		Name: moduleName, Exports: make(map[string]*ExportInstance, exportCount),
+		hostModule: ret,
+	}
+	s.moduleInstances[moduleName] = hostModule
 	for name, goFunc := range nameToGoFunc {
 		if hf, err := NewGoFunc(name, goFunc); err != nil {
 			return nil, err
@@ -110,14 +113,95 @@ func (s *Store) NewHostModule(moduleName string, nameToGoFunc map[string]interfa
 			ret.NameToFunctionInstance[name] = function
 		}
 	}
-	return &ret, nil
+	return ret, nil
 }
 
-func (s *Store) requireModuleUnused(moduleName string) error {
-	if _, ok := s.hostModules[moduleName]; ok {
-		return fmt.Errorf("module %s has already been exported by this host", moduleName)
+// AddHostFunction exports a function so that it can be imported under the given module and name. If a function already
+// exists for this module and name it is ignored rather than overwritten.
+//
+// Note: The wasm.Memory of the fn will be from the importing module.
+func (s *Store) AddHostFunction(m *ModuleInstance, hf *GoFunc) (*FunctionInstance, error) {
+	typeInstance, err := s.getTypeInstance(hf.functionType)
+	if err != nil {
+		return nil, err
+	}
+
+	f := &FunctionInstance{
+		Name:           fmt.Sprintf("%s.%s", m.Name, hf.wasmFunctionName),
+		HostFunction:   hf.goFunc,
+		FunctionKind:   hf.functionKind,
+		FunctionType:   typeInstance,
+		ModuleInstance: m,
+	}
+
+	s.addFunctionInstances(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 nil, err
+		}
+		return nil, fmt.Errorf("failed to compile %s: %v", f.Name, err)
+	}
+
+	if err = m.addExport(hf.wasmFunctionName, &ExportInstance{Type: ExternTypeFunc, Function: f}); err != nil {
+		// On failure, we must release the function instance.
+		if err := s.releaseFunctionInstances(f); err != nil {
+			return nil, err
+		}
+		return nil, err
+	}
+
+	m.Functions = append(m.Functions, f)
+	return f, nil
+}
+
+// Only used in spectest.
+func (s *Store) AddHostGlobal(m *ModuleInstance, name string, value uint64, valueType ValueType, mutable bool) error {
+	if mutable {
+		if err := s.EnabledFeatures.Require(FeatureMutableGlobal); err != nil {
+			return err
+		}
+	}
+	g := &GlobalInstance{
+		Val:  value,
+		Type: &GlobalType{Mutable: mutable, ValType: valueType},
+	}
+
+	m.Globals = append(m.Globals, g)
+	s.addGlobalInstances(g)
+
+	return m.addExport(name, &ExportInstance{Type: ExternTypeGlobal, Global: g})
+}
+
+// Only used in spectest.
+func (s *Store) AddHostTableInstance(m *ModuleInstance, name string, min uint32, max *uint32) error {
+	t := newTableInstance(min, max)
+
+	// TODO: check if the module already has memory, and if so, returns error.
+	m.TableInstance = t
+	s.addTableInstance(t)
+
+	return m.addExport(name, &ExportInstance{Type: ExternTypeTable, Table: t})
+}
+
+// Only used in spectest.
+func (s *Store) AddHostMemoryInstance(m *ModuleInstance, name string, min uint32, max *uint32) error {
+	memory := &MemoryInstance{
+		Buffer: make([]byte, MemoryPagesToBytesNum(min)),
+		Min:    min,
+		Max:    max,
 	}
-	if _, ok := s.ModuleContexts[moduleName]; ok {
+
+	// TODO: check if the module already has memory, and if so, returns error.
+	m.MemoryInstance = memory
+	s.addMemoryInstance(memory)
+
+	return m.addExport(name, &ExportInstance{Type: ExternTypeMemory, Memory: memory})
+}
+
+func (s *Store) requireModuleUnused(moduleName string) error {
+	if _, ok := s.moduleInstances[moduleName]; ok {
 		return fmt.Errorf("module %s has already been instantiated", moduleName)
 	}
 	return nil
diff --git a/internal/wasm/memory.go b/internal/wasm/memory.go
index 3f309933..f75110ad 100644
--- a/internal/wasm/memory.go
+++ b/internal/wasm/memory.go
@@ -124,11 +124,11 @@ func (m *MemoryInstance) Write(offset uint32, val []byte) bool {
 }
 
 // MemoryPagesToBytesNum converts the given pages into the number of bytes contained in these pages.
-func memoryPagesToBytesNum(pages uint32) (bytesNum uint64) {
+func MemoryPagesToBytesNum(pages uint32) (bytesNum uint64) {
 	return uint64(pages) << MemoryPageSizeInBits
 }
 
-// MemoryPagesToBytesNum converts the given number of bytes into the number of pages.
+// memoryBytesNumToPages converts the given number of bytes into the number of pages.
 func memoryBytesNumToPages(bytesNum uint64) (pages uint32) {
 	return uint32(bytesNum >> MemoryPageSizeInBits)
 }
@@ -151,7 +151,7 @@ func (m *MemoryInstance) Grow(newPages uint32) (result uint32) {
 		return 0xffffffff // = -1 in signed 32 bit integer.
 	} else {
 		// Otherwise, grow the memory.
-		m.Buffer = append(m.Buffer, make([]byte, memoryPagesToBytesNum(newPages))...)
+		m.Buffer = append(m.Buffer, make([]byte, MemoryPagesToBytesNum(newPages))...)
 		return currentPages
 	}
 }
diff --git a/internal/wasm/module.go b/internal/wasm/module.go
index f475ae08..e9d72b16 100644
--- a/internal/wasm/module.go
+++ b/internal/wasm/module.go
@@ -426,7 +426,7 @@ func (m *Module) buildFunctionInstances() (functions []*FunctionInstance) {
 func (m *Module) buildMemoryInstance() (mem *MemoryInstance) {
 	for _, memSec := range m.MemorySection {
 		mem = &MemoryInstance{
-			Buffer: make([]byte, memoryPagesToBytesNum(memSec.Min)),
+			Buffer: make([]byte, MemoryPagesToBytesNum(memSec.Min)),
 			Min:    memSec.Min,
 			Max:    memSec.Max,
 		}
diff --git a/internal/wasm/store.go b/internal/wasm/store.go
index 7cac2682..60d22062 100644
--- a/internal/wasm/store.go
+++ b/internal/wasm/store.go
@@ -31,23 +31,17 @@ type (
 		ctx context.Context
 
 		// Engine is a global context for a Store which is in responsible for compilation and execution of Wasm modules.
-		Engine Engine
+		engine Engine
 
 		// EnabledFeatures are read-only to allow optimizations.
 		EnabledFeatures Features
 
 		// ModuleInstances holds the instantiated Wasm modules by module name from Instantiate.
-		ModuleInstances map[string]*ModuleInstance
-
-		// hostModules holds host functions by module name from NewHostModule.
-		hostModules map[string]*HostModule
-
-		// ModuleContexts holds default host function call contexts keyed by module name.
-		ModuleContexts map[string]*ModuleContext
+		moduleInstances map[string]*ModuleInstance
 
 		// 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
+		typeIDs map[string]FunctionTypeID
 
 		// maximumFunctionIndex represents the limit on the number of function addresses (= function instances) in a store.
 		// Note: this is fixed to 2^27 but have this a field for testability.
@@ -81,19 +75,19 @@ type (
 		// 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
+		functions []*FunctionInstance
 		// 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
+		globals []*GlobalInstance
 		// 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
+		memories []*MemoryInstance
 		// 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
+		tables []*TableInstance
 	}
 
 	// ModuleInstance represents instantiated wasm module.
@@ -112,7 +106,13 @@ type (
 		TableInstance  *TableInstance
 		Types          []*TypeInstance
 
-		// TODO
+		// Ctx holds default function call context from this function instance.
+		Ctx *ModuleContext
+
+		// hostModule holds HostModule if this is a "host module" which is created in store.NewHostModule.
+		hostModule *HostModule
+
+		// TODO per https://github.com/tetratelabs/wazero/issues/293
 		refCount      int
 		moduleImports map[*ModuleInstance]struct{}
 	}
@@ -387,11 +387,10 @@ 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,
+		engine:               engine,
 		EnabledFeatures:      enabledFeatures,
-		ModuleInstances:      map[string]*ModuleInstance{},
-		ModuleContexts:       map[string]*ModuleContext{},
-		TypeIDs:              map[string]FunctionTypeID{},
+		moduleInstances:      map[string]*ModuleInstance{},
+		typeIDs:              map[string]FunctionTypeID{},
 		maximumFunctionIndex: maximumFunctionIndex,
 		maximumFunctionTypes: maximumFunctionTypes,
 	}
@@ -399,7 +398,7 @@ func NewStore(ctx context.Context, engine Engine, enabledFeatures Features) *Sto
 
 // checkFunctionIndexOverflow checks if there would be too many function instances in a store.
 func (s *Store) checkFunctionIndexOverflow(newInstanceNum int) error {
-	if len(s.Functions) > int(s.maximumFunctionIndex)-newInstanceNum {
+	if len(s.functions) > int(s.maximumFunctionIndex)-newInstanceNum {
 		return fmt.Errorf("too many functions in a store")
 	}
 	return nil
@@ -442,7 +441,7 @@ 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.
 	for i, f := range functions {
-		if err := s.Engine.Compile(f); err != nil {
+		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 {
 				return nil, err
@@ -467,18 +466,18 @@ func (s *Store) Instantiate(module *Module, name string) (*PublicModule, error)
 	}
 
 	// Build the default context for calls to this module.
-	modCtx := NewModuleContext(s.ctx, s.Engine, instance)
-	s.ModuleContexts[instance.Name] = modCtx
-	s.ModuleInstances[instance.Name] = instance
+	instance.Ctx = NewModuleContext(s.ctx, s.engine, instance)
+
+	s.moduleInstances[instance.Name] = instance
 
 	// Execute the start function.
 	if module.StartSection != nil {
 		funcIdx := *module.StartSection
-		if _, err := s.Engine.Call(modCtx, instance.Functions[funcIdx]); err != nil {
+		if _, err := s.engine.Call(instance.Ctx, instance.Functions[funcIdx]); err != nil {
 			return nil, fmt.Errorf("module[%s] start function failed: %w", name, err)
 		}
 	}
-	return &PublicModule{s, modCtx}, nil
+	return &PublicModule{s, instance}, nil
 }
 
 func (s *Store) ReleaseModuleInstance(instance *ModuleInstance) error {
@@ -512,19 +511,18 @@ func (s *Store) ReleaseModuleInstance(instance *ModuleInstance) error {
 	instance.MemoryInstance = nil
 	instance.Types = nil
 
-	s.ModuleContexts[instance.Name] = nil
-	s.ModuleInstances[instance.Name] = nil
+	delete(s.moduleInstances, instance.Name)
 	return nil
 }
 
 func (s *Store) releaseFunctionInstances(fs ...*FunctionInstance) error {
 	for _, f := range fs {
-		if err := s.Engine.Release(f); err != nil {
+		if err := s.engine.Release(f); err != nil {
 			return err
 		}
 
 		// Release refernce to the function instance.
-		s.Functions[f.Index] = nil
+		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)
@@ -539,10 +537,10 @@ func (s *Store) addFunctionInstances(fs ...*FunctionInstance) {
 			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
+			s.functions[f.Index] = f
 		} else {
-			addr = FunctionIndex(len(s.Functions))
-			s.Functions = append(s.Functions, f)
+			addr = FunctionIndex(len(s.functions))
+			s.functions = append(s.functions, f)
 		}
 		f.Index = addr
 	}
@@ -551,7 +549,7 @@ func (s *Store) addFunctionInstances(fs ...*FunctionInstance) {
 func (s *Store) releaseGlobalInstances(gs ...*GlobalInstance) {
 	for _, g := range gs {
 		// Release reference to the global instance.
-		s.Globals[g.index] = nil
+		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)
@@ -565,10 +563,10 @@ func (s *Store) addGlobalInstances(gs ...*GlobalInstance) {
 			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
+			s.globals[g.index] = g
 		} else {
-			addr = globalIndex(len(s.Globals))
-			s.Globals = append(s.Globals, g)
+			addr = globalIndex(len(s.globals))
+			s.globals = append(s.globals, g)
 		}
 		g.index = addr
 	}
@@ -576,7 +574,7 @@ func (s *Store) addGlobalInstances(gs ...*GlobalInstance) {
 
 func (s *Store) releaseTableInstance(t *TableInstance) {
 	// Release refernce to the table instance.
-	s.Tables[t.index] = nil
+	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)
@@ -592,17 +590,17 @@ func (s *Store) addTableInstance(t *TableInstance) {
 		id := len(s.releasedTableIndex) - 1
 		// Pop one index from releasedTableIndex slice.
 		addr, s.releasedTableIndex = s.releasedTableIndex[id], s.releasedTableIndex[:id]
-		s.Tables[addr] = t
+		s.tables[addr] = t
 	} else {
-		addr = tableIndex(len(s.Tables))
-		s.Tables = append(s.Tables, t)
+		addr = tableIndex(len(s.tables))
+		s.tables = append(s.tables, t)
 	}
 	t.index = addr
 }
 
 func (s *Store) releaseMemoryInstance(m *MemoryInstance) {
 	// Release refernce to the memory instance.
-	s.Memories[m.index] = nil
+	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)
@@ -618,17 +616,17 @@ func (s *Store) addMemoryInstance(m *MemoryInstance) {
 		id := len(s.releasedMemoryIndex) - 1
 		// Pop one index from releasedMemoryIndex slice.
 		addr, s.releasedMemoryIndex = s.releasedMemoryIndex[id], s.releasedMemoryIndex[:id]
-		s.Memories[addr] = m
+		s.memories[addr] = m
 	} else {
-		addr = memoryIndex(len(s.Memories))
-		s.Memories = append(s.Memories, m)
+		addr = memoryIndex(len(s.memories))
+		s.memories = append(s.memories, m)
 	}
 	m.index = addr
 }
 
 // Module implements wasm.Store Module
 func (s *Store) Module(moduleName string) publicwasm.Module {
-	if m, ok := s.ModuleContexts[moduleName]; !ok {
+	if m, ok := s.moduleInstances[moduleName]; !ok {
 		return nil
 	} else {
 		return &PublicModule{s, m}
@@ -639,21 +637,21 @@ func (s *Store) Module(moduleName string) publicwasm.Module {
 type PublicModule struct {
 	s *Store
 	// Context is exported for /wasi.go
-	Context *ModuleContext
+	Instance *ModuleInstance
 }
 
 // Function implements wasm.Module Function
 func (m *PublicModule) Function(name string) publicwasm.Function {
-	exp, err := m.Context.Module.GetExport(name, ExternTypeFunc)
+	exp, err := m.Instance.GetExport(name, ExternTypeFunc)
 	if err != nil {
 		return nil
 	}
-	return &exportedFunction{module: m.Context, function: exp.Function}
+	return &exportedFunction{module: m.Instance.Ctx, function: exp.Function}
 }
 
 // Memory implements wasm.Module Memory
 func (m *PublicModule) Memory(name string) publicwasm.Memory {
-	exp, err := m.Context.Module.GetExport(name, ExternTypeMemory)
+	exp, err := m.Instance.GetExport(name, ExternTypeMemory)
 	if err != nil {
 		return nil
 	}
@@ -662,11 +660,11 @@ func (m *PublicModule) Memory(name string) publicwasm.Memory {
 
 // HostModule implements wasm.Store HostModule
 func (s *Store) HostModule(moduleName string) publicwasm.HostModule {
-	return s.hostModules[moduleName]
+	return s.moduleInstances[moduleName].hostModule
 }
 
 func (s *Store) getExport(moduleName string, name string, et ExternType) (exp *ExportInstance, err error) {
-	if m, ok := s.ModuleInstances[moduleName]; !ok {
+	if m, ok := s.moduleInstances[moduleName]; !ok {
 		return nil, fmt.Errorf("module %s not instantiated", moduleName)
 	} else if exp, err = m.GetExport(name, et); err != nil {
 		return
@@ -682,7 +680,7 @@ func (s *Store) resolveImports(module *Module) (
 ) {
 	moduleImports = map[*ModuleInstance]struct{}{}
 	for _, is := range module.ImportSection {
-		m, ok := s.ModuleInstances[is.Module]
+		m, ok := s.moduleInstances[is.Module]
 		if !ok {
 			err = fmt.Errorf("module \"%s\" not instantiated", is.Module)
 			return
@@ -792,85 +790,9 @@ func executeConstExpression(globals []*GlobalInstance, expr *ConstantExpression)
 	return
 }
 
-// AddHostFunction exports a function so that it can be imported under the given module and name. If a function already
-// exists for this module and name it is ignored rather than overwritten.
-//
-// Note: The wasm.Memory of the fn will be from the importing module.
-func (s *Store) AddHostFunction(m *ModuleInstance, hf *GoFunc) (*FunctionInstance, error) {
-	typeInstance, err := s.getTypeInstance(hf.functionType)
-	if err != nil {
-		return nil, err
-	}
-
-	f := &FunctionInstance{
-		Name:           fmt.Sprintf("%s.%s", m.Name, hf.wasmFunctionName),
-		HostFunction:   hf.goFunc,
-		FunctionKind:   hf.functionKind,
-		FunctionType:   typeInstance,
-		ModuleInstance: m,
-	}
-
-	s.addFunctionInstances(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 nil, err
-		}
-		return nil, fmt.Errorf("failed to compile %s: %v", f.Name, err)
-	}
-
-	if err = m.addExport(hf.wasmFunctionName, &ExportInstance{Type: ExternTypeFunc, Function: f}); err != nil {
-		// On failure, we must release the function instance.
-		if err := s.releaseFunctionInstances(f); err != nil {
-			return nil, err
-		}
-		return nil, err
-	}
-
-	m.Functions = append(m.Functions, f)
-	return f, nil
-}
-
-func (s *Store) AddGlobal(m *ModuleInstance, name string, value uint64, valueType ValueType, mutable bool) error {
-	if mutable {
-		if err := s.EnabledFeatures.Require(FeatureMutableGlobal); err != nil {
-			return err
-		}
-	}
-	g := &GlobalInstance{
-		Val:  value,
-		Type: &GlobalType{Mutable: mutable, ValType: valueType},
-	}
-
-	m.Globals = append(m.Globals, g)
-	s.addGlobalInstances(g)
-
-	return m.addExport(name, &ExportInstance{Type: ExternTypeGlobal, Global: g})
-}
-
-func (s *Store) AddTableInstance(m *ModuleInstance, name string, min uint32, max *uint32) error {
-	t := newTableInstance(min, max)
-
-	// TODO: check if the module already has memory, and if so, returns error.
-	m.TableInstance = t
-	s.addTableInstance(t)
-
-	return m.addExport(name, &ExportInstance{Type: ExternTypeTable, Table: t})
-}
-
-func (s *Store) AddMemoryInstance(m *ModuleInstance, name string, min uint32, max *uint32) error {
-	memory := &MemoryInstance{
-		Buffer: make([]byte, memoryPagesToBytesNum(min)),
-		Min:    min,
-		Max:    max,
-	}
-
-	// TODO: check if the module already has memory, and if so, returns error.
-	m.MemoryInstance = memory
-	s.addMemoryInstance(memory)
-
-	return m.addExport(name, &ExportInstance{Type: ExternTypeMemory, Memory: memory})
+// Only used in spectests.
+func (s *Store) AliasModuleInstance(src, dst string) {
+	s.moduleInstances[dst] = s.moduleInstances[src]
 }
 
 func (s *Store) getTypeInstances(ts []*FunctionType) ([]*TypeInstance, error) {
@@ -888,14 +810,14 @@ 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]
+	id, ok := s.typeIDs[key]
 	if !ok {
-		l := len(s.TypeIDs)
+		l := len(s.typeIDs)
 		if l >= s.maximumFunctionTypes {
 			return nil, fmt.Errorf("too many function types in a store")
 		}
-		id = FunctionTypeID(len(s.TypeIDs))
-		s.TypeIDs[key] = id
+		id = FunctionTypeID(len(s.typeIDs))
+		s.typeIDs[key] = id
 	}
 	return &TypeInstance{Type: t, TypeID: id}, nil
 }
diff --git a/wasi.go b/wasi.go
index 1170b6a9..118459e1 100644
--- a/wasi.go
+++ b/wasi.go
@@ -121,15 +121,14 @@ func StartWASICommand(r Runtime, module *DecodedModule) (wasm.Module, error) {
 		return nil, fmt.Errorf("unsupported Runtime implementation: %s", r)
 	}
 
-	instantiated, err := internal.store.Instantiate(module.module, module.name)
+	mod, err := internal.store.Instantiate(module.module, module.name)
 	if err != nil {
 		return nil, err
 	}
-	ctx := instantiated.Context
 
-	start := ctx.Function(internalwasi.FunctionStart)
-	if _, err = start.Call(ctx.Context()); err != nil {
+	start := mod.Function(internalwasi.FunctionStart)
+	if _, err = start.Call(mod.Instance.Ctx.Context()); err != nil {
 		return nil, fmt.Errorf("module[%s] function[%s] failed: %w", module.name, internalwasi.FunctionStart, err)
 	}
-	return instantiated, nil
+	return mod, nil
 }


Test patch:
diff --git a/internal/wasi/wasi_test.go b/internal/wasi/wasi_test.go
index 1e1654a7..9f9b0022 100644
--- a/internal/wasi/wasi_test.go
+++ b/internal/wasi/wasi_test.go
@@ -54,33 +54,40 @@ func TestSnapshotPreview1_ArgsGet(t *testing.T) {
 		'?', // stopped after encoding
 	}
 
-	store, ctx, fn := instantiateWasmStore(t, FunctionArgsGet, ImportArgsGet, moduleName, args)
+	mod, fn := instantiateModule(t, FunctionArgsGet, ImportArgsGet, moduleName, args)
+	mem := mod.Memory("memory")
 
 	t.Run("SnapshotPreview1.ArgsGet", func(t *testing.T) {
-		maskMemory(store, len(expectedMemory))
+		maskMemory(t, mem, len(expectedMemory))
 
 		// Invoke ArgsGet directly and check the memory side effects.
-		errno := NewAPI(args).ArgsGet(ctx, argv, argvBuf)
+		errno := NewAPI(args).ArgsGet(mod.Instance.Ctx, argv, argvBuf)
 		require.Equal(t, wasi.ErrnoSuccess, errno)
-		require.Equal(t, expectedMemory, store.Memories[0].Buffer)
+
+		actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+		require.True(t, ok)
+		require.Equal(t, expectedMemory, actual)
 	})
 
 	t.Run(FunctionArgsGet, func(t *testing.T) {
-		maskMemory(store, len(expectedMemory))
+		maskMemory(t, mem, len(expectedMemory))
 
-		results, err := store.Engine.Call(ctx, fn, uint64(argv), uint64(argvBuf))
+		results, err := fn.Call(context.Background(), uint64(argv), uint64(argvBuf))
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoSuccess, wasi.Errno(results[0])) // cast because results are always uint64
-		require.Equal(t, expectedMemory, store.Memories[0].Buffer)
+
+		actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+		require.True(t, ok)
+		require.Equal(t, expectedMemory, actual)
 	})
 }
 
 func TestSnapshotPreview1_ArgsGet_Errors(t *testing.T) {
 	args, err := Args("a", "bc")
 	require.NoError(t, err)
-	store, ctx, fn := instantiateWasmStore(t, FunctionArgsGet, ImportArgsGet, moduleName, args)
+	mod, fn := instantiateModule(t, FunctionArgsGet, ImportArgsGet, moduleName, args)
 
-	memorySize := uint32(len(store.Memories[0].Buffer))
+	memorySize := mod.Memory("memory").Size()
 	validAddress := uint32(0) // arbitrary valid address as arguments to args_get. We chose 0 here.
 
 	tests := []struct {
@@ -116,7 +123,7 @@ func TestSnapshotPreview1_ArgsGet_Errors(t *testing.T) {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			results, err := store.Engine.Call(ctx, fn, uint64(tc.argv), uint64(tc.argvBuf))
+			results, err := fn.Call(context.Background(), uint64(tc.argv), uint64(tc.argvBuf))
 			require.NoError(t, err)
 			require.Equal(t, uint64(wasi.ErrnoFault), results[0]) // results[0] is the errno
 		})
@@ -136,24 +143,32 @@ func TestSnapshotPreview1_ArgsSizesGet(t *testing.T) {
 		'?', // stopped after encoding
 	}
 
-	store, ctx, fn := instantiateWasmStore(t, FunctionArgsSizesGet, ImportArgsSizesGet, moduleName, args)
+	mod, fn := instantiateModule(t, FunctionArgsSizesGet, ImportArgsSizesGet, moduleName, args)
+
+	mem := mod.Memory("memory")
 
 	t.Run("SnapshotPreview1.ArgsSizesGet", func(t *testing.T) {
-		maskMemory(store, len(expectedMemory))
+		maskMemory(t, mem, len(expectedMemory))
 
 		// Invoke ArgsSizesGet directly and check the memory side effects.
-		errno := NewAPI(args).ArgsSizesGet(ctx, resultArgc, resultArgvBufSize)
+		errno := NewAPI(args).ArgsSizesGet(mod.Instance.Ctx, resultArgc, resultArgvBufSize)
 		require.Equal(t, wasi.ErrnoSuccess, errno)
-		require.Equal(t, expectedMemory, store.Memories[0].Buffer)
+
+		actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+		require.True(t, ok)
+		require.Equal(t, expectedMemory, actual)
 	})
 
 	t.Run(FunctionArgsSizesGet, func(t *testing.T) {
-		maskMemory(store, len(expectedMemory))
+		maskMemory(t, mem, len(expectedMemory))
 
-		results, err := store.Engine.Call(ctx, fn, uint64(resultArgc), uint64(resultArgvBufSize))
+		results, err := fn.Call(context.Background(), uint64(resultArgc), uint64(resultArgvBufSize))
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoSuccess, wasi.Errno(results[0])) // cast because results are always uint64
-		require.Equal(t, expectedMemory, store.Memories[0].Buffer)
+
+		actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+		require.True(t, ok)
+		require.Equal(t, expectedMemory, actual)
 	})
 }
 
@@ -161,8 +176,9 @@ func TestSnapshotPreview1_ArgsSizesGet_Errors(t *testing.T) {
 	args, err := Args("a", "bc")
 	require.NoError(t, err)
 
-	store, ctx, fn := instantiateWasmStore(t, FunctionArgsSizesGet, ImportArgsSizesGet, moduleName, args)
-	memorySize := uint32(len(store.Memories[0].Buffer))
+	mod, fn := instantiateModule(t, FunctionArgsSizesGet, ImportArgsSizesGet, moduleName, args)
+
+	memorySize := mod.Memory("memory").Size()
 	validAddress := uint32(0) // arbitrary valid address as arguments to args_sizes_get. We chose 0 here.
 
 	tests := []struct {
@@ -196,7 +212,7 @@ func TestSnapshotPreview1_ArgsSizesGet_Errors(t *testing.T) {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			results, err := store.Engine.Call(ctx, fn, uint64(tc.argc), uint64(tc.argvBufSize))
+			results, err := fn.Call(context.Background(), uint64(tc.argc), uint64(tc.argvBufSize))
 			require.NoError(t, err)
 			require.Equal(t, uint64(wasi.ErrnoFault), results[0]) // results[0] is the errno
 		})
@@ -254,24 +270,31 @@ func TestSnapshotPreview1_EnvironGet(t *testing.T) {
 		'?', // stopped after encoding
 	}
 
-	store, ctx, fn := instantiateWasmStore(t, FunctionEnvironGet, ImportEnvironGet, moduleName, envOpt)
+	mod, fn := instantiateModule(t, FunctionEnvironGet, ImportEnvironGet, moduleName, envOpt)
+	mem := mod.Memory("memory")
 
 	t.Run("SnapshotPreview1.EnvironGet", func(t *testing.T) {
-		maskMemory(store, len(expectedMemory))
+		maskMemory(t, mem, len(expectedMemory))
 
 		// Invoke EnvironGet directly and check the memory side effects.
-		errno := NewAPI(envOpt).EnvironGet(ctx, resultEnviron, resultEnvironBuf)
+		errno := NewAPI(envOpt).EnvironGet(mod.Instance.Ctx, resultEnviron, resultEnvironBuf)
 		require.Equal(t, wasi.ErrnoSuccess, errno)
-		require.Equal(t, expectedMemory, store.Memories[0].Buffer)
+
+		actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+		require.True(t, ok)
+		require.Equal(t, expectedMemory, actual)
 	})
 
 	t.Run(FunctionEnvironGet, func(t *testing.T) {
-		maskMemory(store, len(expectedMemory))
+		maskMemory(t, mem, len(expectedMemory))
 
-		results, err := store.Engine.Call(ctx, fn, uint64(resultEnviron), uint64(resultEnvironBuf))
+		results, err := fn.Call(context.Background(), uint64(resultEnviron), uint64(resultEnvironBuf))
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoSuccess, wasi.Errno(results[0])) // cast because results are always uint64
-		require.Equal(t, expectedMemory, store.Memories[0].Buffer)
+
+		actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+		require.True(t, ok)
+		require.Equal(t, expectedMemory, actual)
 	})
 }
 
@@ -279,8 +302,9 @@ func TestSnapshotPreview1_EnvironGet_Errors(t *testing.T) {
 	envOpt, err := Environ("a=bc", "b=cd")
 	require.NoError(t, err)
 
-	store, ctx, fn := instantiateWasmStore(t, FunctionEnvironGet, ImportEnvironGet, moduleName, envOpt)
-	memorySize := uint32(len(store.Memories[0].Buffer))
+	mod, fn := instantiateModule(t, FunctionEnvironGet, ImportEnvironGet, moduleName, envOpt)
+
+	memorySize := mod.Memory("memory").Size()
 	validAddress := uint32(0) // arbitrary valid address as arguments to environ_get. We chose 0 here.
 
 	tests := []struct {
@@ -316,7 +340,7 @@ func TestSnapshotPreview1_EnvironGet_Errors(t *testing.T) {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			results, err := store.Engine.Call(ctx, fn, uint64(tc.environ), uint64(tc.environBuf))
+			results, err := fn.Call(context.Background(), uint64(tc.environ), uint64(tc.environBuf))
 			require.NoError(t, err)
 			require.Equal(t, uint64(wasi.ErrnoFault), results[0]) // results[0] is the errno
 		})
@@ -336,24 +360,31 @@ func TestSnapshotPreview1_EnvironSizesGet(t *testing.T) {
 		'?', // stopped after encoding
 	}
 
-	store, ctx, fn := instantiateWasmStore(t, FunctionEnvironSizesGet, ImportEnvironSizesGet, moduleName, envOpt)
+	mod, fn := instantiateModule(t, FunctionEnvironSizesGet, ImportEnvironSizesGet, moduleName, envOpt)
+	mem := mod.Memory("memory")
 
 	t.Run("SnapshotPreview1.EnvironSizesGet", func(t *testing.T) {
-		maskMemory(store, len(expectedMemory))
+		maskMemory(t, mem, len(expectedMemory))
 
 		// Invoke EnvironSizesGet directly and check the memory side effects.
-		errno := NewAPI(envOpt).EnvironSizesGet(ctx, resultEnvironc, resultEnvironBufSize)
+		errno := NewAPI(envOpt).EnvironSizesGet(mod.Instance.Ctx, resultEnvironc, resultEnvironBufSize)
 		require.Equal(t, wasi.ErrnoSuccess, errno)
-		require.Equal(t, expectedMemory, store.Memories[0].Buffer)
+
+		actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+		require.True(t, ok)
+		require.Equal(t, expectedMemory, actual)
 	})
 
 	t.Run(FunctionEnvironSizesGet, func(t *testing.T) {
-		maskMemory(store, len(expectedMemory))
+		maskMemory(t, mem, len(expectedMemory))
 
-		results, err := store.Engine.Call(ctx, fn, uint64(resultEnvironc), uint64(resultEnvironBufSize))
+		results, err := fn.Call(context.Background(), uint64(resultEnvironc), uint64(resultEnvironBufSize))
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoSuccess, wasi.Errno(results[0])) // cast because results are always uint64
-		require.Equal(t, expectedMemory, store.Memories[0].Buffer)
+
+		actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+		require.True(t, ok)
+		require.Equal(t, expectedMemory, actual)
 	})
 }
 
@@ -361,8 +392,9 @@ func TestSnapshotPreview1_EnvironSizesGet_Errors(t *testing.T) {
 	envOpt, err := Environ("a=b", "b=cd")
 	require.NoError(t, err)
 
-	store, ctx, fn := instantiateWasmStore(t, FunctionEnvironSizesGet, ImportEnvironSizesGet, moduleName, envOpt)
-	memorySize := uint32(len(store.Memories[0].Buffer))
+	mod, fn := instantiateModule(t, FunctionEnvironSizesGet, ImportEnvironSizesGet, moduleName, envOpt)
+
+	memorySize := mod.Memory("memory").Size()
 	validAddress := uint32(0) // arbitrary valid address as arguments to environ_sizes_get. We chose 0 here.
 
 	tests := []struct {
@@ -396,7 +428,7 @@ func TestSnapshotPreview1_EnvironSizesGet_Errors(t *testing.T) {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			results, err := store.Engine.Call(ctx, fn, uint64(tc.environc), uint64(tc.environBufSize))
+			results, err := fn.Call(context.Background(), uint64(tc.environc), uint64(tc.environBufSize))
 			require.NoError(t, err)
 			require.Equal(t, uint64(wasi.ErrnoFault), results[0]) // results[0] is the errno
 		})
@@ -417,34 +449,42 @@ func TestSnapshotPreview1_ClockTimeGet(t *testing.T) {
 	clockOpt := func(api *wasiAPI) {
 		api.timeNowUnixNano = func() uint64 { return epochNanos }
 	}
-	store, ctx, fn := instantiateWasmStore(t, FunctionClockTimeGet, ImportClockTimeGet, moduleName, clockOpt)
+	mod, fn := instantiateModule(t, FunctionClockTimeGet, ImportClockTimeGet, moduleName, clockOpt)
+	mem := mod.Memory("memory")
 
 	t.Run("SnapshotPreview1.ClockTimeGet", func(t *testing.T) {
-		maskMemory(store, len(expectedMemory))
+		maskMemory(t, mem, len(expectedMemory))
 
 		// invoke ClockTimeGet directly and check the memory side effects!
-		errno := NewAPI(clockOpt).ClockTimeGet(ctx, 0 /* TODO: id */, 0 /* TODO: precision */, resultTimestamp)
+		errno := NewAPI(clockOpt).ClockTimeGet(mod.Instance.Ctx, 0 /* TODO: id */, 0 /* TODO: precision */, resultTimestamp)
 		require.Equal(t, wasi.ErrnoSuccess, errno)
-		require.Equal(t, expectedMemory, store.Memories[0].Buffer)
+
+		actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+		require.True(t, ok)
+		require.Equal(t, expectedMemory, actual)
 	})
 
 	t.Run(FunctionClockTimeGet, func(t *testing.T) {
-		maskMemory(store, len(expectedMemory))
+		maskMemory(t, mem, len(expectedMemory))
 
-		results, err := store.Engine.Call(ctx, fn, 0 /* TODO: id */, 0 /* TODO: precision */, uint64(resultTimestamp))
+		results, err := fn.Call(context.Background(), 0 /* TODO: id */, 0 /* TODO: precision */, uint64(resultTimestamp))
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoSuccess, wasi.Errno(results[0])) // cast because results are always uint64
-		require.Equal(t, expectedMemory, store.Memories[0].Buffer)
+
+		actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+		require.True(t, ok)
+		require.Equal(t, expectedMemory, actual)
 	})
 }
 
 func TestSnapshotPreview1_ClockTimeGet_Errors(t *testing.T) {
 	epochNanos := uint64(1640995200000000000) // midnight UTC 2022-01-01
 
-	store, ctx, fn := instantiateWasmStore(t, FunctionClockTimeGet, ImportClockTimeGet, moduleName, func(api *wasiAPI) {
+	mod, fn := instantiateModule(t, FunctionClockTimeGet, ImportClockTimeGet, moduleName, func(api *wasiAPI) {
 		api.timeNowUnixNano = func() uint64 { return epochNanos }
 	})
-	memorySize := uint32(len(store.Memories[0].Buffer))
+
+	memorySize := mod.Memory("memory").Size()
 
 	tests := []struct {
 		name            string
@@ -466,7 +506,7 @@ func TestSnapshotPreview1_ClockTimeGet_Errors(t *testing.T) {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			results, err := store.Engine.Call(ctx, fn, 0 /* TODO: id */, 0 /* TODO: precision */, uint64(tc.resultTimestamp))
+			results, err := fn.Call(context.Background(), 0 /* TODO: id */, 0 /* TODO: precision */, uint64(tc.resultTimestamp))
 			require.NoError(t, err)
 			require.Equal(t, uint64(wasi.ErrnoFault), results[0]) // results[0] is the errno
 		})
@@ -480,9 +520,9 @@ func TestSnapshotPreview1_FdClose(t *testing.T) {
 	fdToClose := uint32(3) // arbitrary fd
 	fdToKeep := uint32(4)  // another arbitrary fd
 
-	setupFD := func() (*wasm.Store, *wasm.ModuleContext, *wasm.FunctionInstance, *wasiAPI) {
+	setupFD := func() (*wasm.PublicModule, publicwasm.Function, *wasiAPI) {
 		var api *wasiAPI
-		store, ctx, fn := instantiateWasmStore(t, FunctionFdClose, ImportFdClose, moduleName, func(a *wasiAPI) {
+		mod, fn := instantiateModule(t, FunctionFdClose, ImportFdClose, moduleName, func(a *wasiAPI) {
 			memFs := &MemFS{}
 			a.opened = map[uint32]fileEntry{
 				fdToClose: {
@@ -496,27 +536,29 @@ func TestSnapshotPreview1_FdClose(t *testing.T) {
 			}
 			api = a // for later tests
 		})
-		return store, ctx, fn, api
+		return mod, fn, api
 	}
 
 	t.Run("SnapshotPreview1.FdClose", func(t *testing.T) {
-		_, ctx, _, api := setupFD()
-		errno := api.FdClose(ctx, fdToClose)
+		mod, _, api := setupFD()
+
+		errno := api.FdClose(mod.Instance.Ctx, fdToClose)
 		require.Equal(t, wasi.ErrnoSuccess, errno)
 		require.NotContains(t, api.opened, fdToClose) // Fd is closed and removed from the opened FDs.
 		require.Contains(t, api.opened, fdToKeep)
 	})
 	t.Run(FunctionFdClose, func(t *testing.T) {
-		store, ctx, fn, api := setupFD()
-		ret, err := store.Engine.Call(ctx, fn, uint64(fdToClose))
+		_, fn, api := setupFD()
+
+		ret, err := fn.Call(context.Background(), uint64(fdToClose))
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoSuccess, wasi.Errno(ret[0])) // cast because results are always uint64
 		require.NotContains(t, api.opened, fdToClose)           // Fd is closed and removed from the opened FDs.
 		require.Contains(t, api.opened, fdToKeep)
 	})
 	t.Run("ErrnoBadF for an invalid FD", func(t *testing.T) {
-		_, ctx, _, api := setupFD()
-		errno := api.FdClose(ctx, 42) // 42 is an arbitrary invalid FD
+		mod, _, api := setupFD()
+		errno := api.FdClose(mod.Instance.Ctx, 42) // 42 is an arbitrary invalid FD
 		require.Equal(t, wasi.ErrnoBadf, errno)
 	})
 }
@@ -534,13 +576,14 @@ func TestSnapshotPreview1_FdClose(t *testing.T) {
 func TestSnapshotPreview1_FdPrestatDirName(t *testing.T) {
 	fd := uint32(3) // arbitrary fd after 0, 1, and 2, that are stdin/out/err
 	var api *wasiAPI
-	store, ctx, fn := instantiateWasmStore(t, FunctionFdPrestatDirName, ImportFdPrestatDirName, moduleName, func(a *wasiAPI) {
+	mod, fn := instantiateModule(t, FunctionFdPrestatDirName, ImportFdPrestatDirName, moduleName, func(a *wasiAPI) {
 		a.opened[fd] = fileEntry{
 			path:    "/tmp",
 			fileSys: &MemFS{},
 		}
 		api = a // for later tests
 	})
+	mem := mod.Memory("memory")
 
 	path := uint32(1)    // arbitrary offset
 	pathLen := uint32(3) // shorter than len("/tmp") to test the path is written for the length of pathLen
@@ -551,29 +594,37 @@ func TestSnapshotPreview1_FdPrestatDirName(t *testing.T) {
 	}
 
 	t.Run("SnapshotPreview1.FdPrestatDirName", func(t *testing.T) {
-		maskMemory(store, len(expectedMemory))
+		maskMemory(t, mem, len(expectedMemory))
 
-		errno := api.FdPrestatDirName(ctx, fd, path, pathLen)
+		errno := api.FdPrestatDirName(mod.Instance.Ctx, fd, path, pathLen)
 		require.Equal(t, wasi.ErrnoSuccess, errno)
-		require.Equal(t, expectedMemory, store.Memories[0].Buffer)
+
+		actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+		require.True(t, ok)
+		require.Equal(t, expectedMemory, actual)
 	})
 
 	t.Run(FunctionFdPrestatDirName, func(t *testing.T) {
-		maskMemory(store, len(expectedMemory))
+		maskMemory(t, mem, len(expectedMemory))
 
-		ret, err := store.Engine.Call(ctx, fn, uint64(fd), uint64(path), uint64(pathLen))
+		ret, err := fn.Call(context.Background(), uint64(fd), uint64(path), uint64(pathLen))
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoSuccess, wasi.Errno(ret[0])) // cast because results are always uint64
-		require.Equal(t, expectedMemory, store.Memories[0].Buffer)
+
+		actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+		require.True(t, ok)
+		require.Equal(t, expectedMemory, actual)
 	})
 }
 
 func TestSnapshotPreview1_FdPrestatDirName_Errors(t *testing.T) {
 	dirName := "/tmp"
 	opt := Preopen(dirName, &MemFS{})
-	store, ctx, fn := instantiateWasmStore(t, FunctionFdPrestatDirName, ImportFdPrestatDirName, moduleName, opt)
+	mod, fn := instantiateModule(t, FunctionFdPrestatDirName, ImportFdPrestatDirName, moduleName, opt)
+
+	mem := mod.Memory("memory")
 
-	memorySize := uint32(len(store.Memories[0].Buffer))
+	memorySize := mem.Size()
 	validAddress := uint32(0) // Arbitrary valid address as arguments to fd_prestat_dir_name. We chose 0 here.
 	fd := uint32(3)           // fd 3 will be opened for the "/tmp" directory after 0, 1, and 2, that are stdin/out/err
 
@@ -618,7 +669,7 @@ func TestSnapshotPreview1_FdPrestatDirName_Errors(t *testing.T) {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			results, err := store.Engine.Call(ctx, fn, uint64(tc.fd), uint64(tc.path), uint64(tc.pathLen))
+			results, err := fn.Call(context.Background(), uint64(tc.fd), uint64(tc.path), uint64(tc.pathLen))
 			require.NoError(t, err)
 			require.Equal(t, tc.expectedErrno, wasi.Errno(results[0])) // results[0] is the errno
 		})
@@ -649,10 +700,12 @@ func TestSnapshotPreview1_FdRead(t *testing.T) {
 	)
 
 	var api *wasiAPI
-	store, ctx, fn := instantiateWasmStore(t, FunctionFdRead, ImportFdRead, moduleName, func(a *wasiAPI) {
+	mod, fn := instantiateModule(t, FunctionFdRead, ImportFdRead, moduleName, func(a *wasiAPI) {
 		api = a // for later tests
 	})
 
+	mem := mod.Memory("memory")
+
 	// TestSnapshotPreview1_FdRead uses a matrix because setting up test files is complicated and has to be clean each time.
 	type fdReadFn func(ctx publicwasm.ModuleContext, fd, iovs, iovsLen, resultSize uint32) wasi.Errno
 	tests := []struct {
@@ -664,7 +717,7 @@ func TestSnapshotPreview1_FdRead(t *testing.T) {
 		}},
 		{FunctionFdRead, func() fdReadFn {
 			return func(ctx publicwasm.ModuleContext, fd, iovs, iovsLen, resultSize uint32) wasi.Errno {
-				ret, err := store.Engine.Call(ctx.(*wasm.ModuleContext), fn, uint64(fd), uint64(iovs), uint64(iovsLen), uint64(resultSize))
+				ret, err := fn.Call(context.Background(), uint64(fd), uint64(iovs), uint64(iovsLen), uint64(resultSize))
 				require.NoError(t, err)
 				return wasi.Errno(ret[0])
 			}
@@ -681,12 +734,17 @@ func TestSnapshotPreview1_FdRead(t *testing.T) {
 				fileSys: memFS,
 				file:    file,
 			}
-			maskMemory(store, len(expectedMemory))
-			copy(store.Memories[0].Buffer[0:], initialMemory)
+			maskMemory(t, mem, len(expectedMemory))
+
+			ok := mem.Write(0, initialMemory)
+			require.True(t, ok)
 
-			errno := tc.fdRead()(ctx, fd, iovs, iovsLen, resultSize)
+			errno := tc.fdRead()(mod.Instance.Ctx, fd, iovs, iovsLen, resultSize)
 			require.Equal(t, wasi.ErrnoSuccess, errno)
-			require.Equal(t, expectedMemory, store.Memories[0].Buffer)
+
+			actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+			require.True(t, ok)
+			require.Equal(t, expectedMemory, actual)
 		})
 	}
 }
@@ -694,7 +752,7 @@ func TestSnapshotPreview1_FdRead(t *testing.T) {
 func TestSnapshotPreview1_FdRead_Errors(t *testing.T) {
 	validFD := uint64(3)                                // arbitrary valid fd after 0, 1, and 2, that are stdin/out/err
 	file, memFS := createFile(t, "test_path", []byte{}) // file with empty contents
-	store, ctx, fn := instantiateWasmStore(t, FunctionFdRead, ImportFdRead, moduleName, func(a *wasiAPI) {
+	mod, fn := instantiateModule(t, FunctionFdRead, ImportFdRead, moduleName, func(a *wasiAPI) {
 		a.opened[uint32(validFD)] = fileEntry{
 			path:    "test_path",
 			fileSys: memFS,
@@ -702,6 +760,8 @@ func TestSnapshotPreview1_FdRead_Errors(t *testing.T) {
 		}
 	})
 
+	mem := mod.Memory("memory")
+
 	tests := []struct {
 		name                          string
 		fd, iovs, iovsLen, resultSize uint64
@@ -735,8 +795,8 @@ func TestSnapshotPreview1_FdRead_Errors(t *testing.T) {
 			fd:   validFD,
 			iovs: 1, iovsLen: 1,
 			memory: []byte{
-				'?',        // `iovs` is after this
-				9, 0, 0, 0, // = iovs[0].offset = one past the size of this memory
+				'?',          // `iovs` is after this
+				0, 0, 0x1, 0, // = iovs[0].offset on the secod page
 				1, 0, 0, 0, // = iovs[0].length
 			},
 			expectedErrno: wasi.ErrnoFault,
@@ -748,7 +808,7 @@ func TestSnapshotPreview1_FdRead_Errors(t *testing.T) {
 			memory: []byte{
 				'?',        // `iovs` is after this
 				9, 0, 0, 0, // = iovs[0].offset
-				2, 0, 0, 0, // = iovs[0].length = one past the size of this memory
+				0, 0, 0x1, 0, // = iovs[0].length on the secod page
 				'?',
 			},
 			expectedErrno: wasi.ErrnoFault,
@@ -771,9 +831,12 @@ func TestSnapshotPreview1_FdRead_Errors(t *testing.T) {
 	for _, tt := range tests {
 		tc := tt
 		t.Run(tc.name, func(t *testing.T) {
-			store.Memories[0].Buffer = tc.memory
+			offset := uint64(wasm.MemoryPagesToBytesNum(testMemoryPageSize)) - uint64(len(tc.memory))
+
+			memoryWriteOK := mem.Write(uint32(offset), tc.memory)
+			require.True(t, memoryWriteOK)
 
-			results, err := store.Engine.Call(ctx, fn, tc.fd, tc.iovs, tc.iovsLen, tc.resultSize)
+			results, err := fn.Call(context.Background(), tc.fd, tc.iovs+offset, tc.iovsLen+offset, tc.resultSize+offset)
 			require.NoError(t, err)
 			require.Equal(t, tc.expectedErrno, wasi.Errno(results[0])) // results[0] is the errno
 		})
@@ -810,10 +873,12 @@ func TestSnapshotPreview1_FdWrite(t *testing.T) {
 	)
 
 	var api *wasiAPI
-	store, ctx, fn := instantiateWasmStore(t, FunctionFdWrite, ImportFdWrite, moduleName, func(a *wasiAPI) {
+	mod, fn := instantiateModule(t, FunctionFdWrite, ImportFdWrite, moduleName, func(a *wasiAPI) {
 		api = a // for later tests
 	})
 
+	mem := mod.Memory("memory")
+
 	// TestSnapshotPreview1_FdWrite uses a matrix because setting up test files is complicated and has to be clean each time.
 	type fdWriteFn func(ctx publicwasm.ModuleContext, fd, iovs, iovsLen, resultSize uint32) wasi.Errno
 	tests := []struct {
@@ -825,7 +890,7 @@ func TestSnapshotPreview1_FdWrite(t *testing.T) {
 		}},
 		{FunctionFdWrite, func() fdWriteFn {
 			return func(ctx publicwasm.ModuleContext, fd, iovs, iovsLen, resultSize uint32) wasi.Errno {
-				ret, err := store.Engine.Call(ctx.(*wasm.ModuleContext), fn, uint64(fd), uint64(iovs), uint64(iovsLen), uint64(resultSize))
+				ret, err := fn.Call(context.Background(), uint64(fd), uint64(iovs), uint64(iovsLen), uint64(resultSize))
 				require.NoError(t, err)
 				return wasi.Errno(ret[0])
 			}
@@ -842,12 +907,16 @@ func TestSnapshotPreview1_FdWrite(t *testing.T) {
 				fileSys: memFS,
 				file:    file,
 			}
-			maskMemory(store, len(expectedMemory))
-			copy(store.Memories[0].Buffer[0:], initialMemory)
+			maskMemory(t, mem, len(expectedMemory))
+			ok := mem.Write(0, initialMemory)
+			require.True(t, ok)
 
-			errno := tc.fdWrite()(ctx, fd, iovs, iovsLen, resultSize)
+			errno := tc.fdWrite()(mod.Instance.Ctx, fd, iovs, iovsLen, resultSize)
 			require.Equal(t, wasi.ErrnoSuccess, errno)
-			require.Equal(t, expectedMemory, store.Memories[0].Buffer)
+
+			actual, ok := mem.Read(0, uint32(len(expectedMemory)))
+			require.True(t, ok)
+			require.Equal(t, expectedMemory, actual)
 			require.Equal(t, []byte("wazero"), file.buf.Bytes()) // verify the file was actually written
 		})
 	}
@@ -856,7 +925,7 @@ func TestSnapshotPreview1_FdWrite(t *testing.T) {
 func TestSnapshotPreview1_FdWrite_Errors(t *testing.T) {
 	validFD := uint64(3)                                // arbitrary valid fd after 0, 1, and 2, that are stdin/out/err
 	file, memFS := createFile(t, "test_path", []byte{}) // file with empty contents
-	store, ctx, fn := instantiateWasmStore(t, FunctionFdWrite, ImportFdWrite, moduleName, func(a *wasiAPI) {
+	mod, fn := instantiateModule(t, FunctionFdWrite, ImportFdWrite, moduleName, func(a *wasiAPI) {
 		a.opened[uint32(validFD)] = fileEntry{
 			path:    "test_path",
 			fileSys: memFS,
@@ -864,6 +933,8 @@ func TestSnapshotPreview1_FdWrite_Errors(t *testing.T) {
 		}
 	})
 
+	mem := mod.Memory("memory")
+
 	tests := []struct {
 		name                          string
 		fd, iovs, iovsLen, resultSize uint64
@@ -933,9 +1004,12 @@ func TestSnapshotPreview1_FdWrite_Errors(t *testing.T) {
 	for _, tt := range tests {
 		tc := tt
 		t.Run(tc.name, func(t *testing.T) {
-			store.Memories[0].Buffer = tc.memory
+			offset := uint64(wasm.MemoryPagesToBytesNum(testMemoryPageSize)) - uint64(len(tc.memory))
+
+			memoryWriteOK := mem.Write(uint32(offset), tc.memory)
+			require.True(t, memoryWriteOK)
 
-			results, err := store.Engine.Call(ctx, fn, tc.fd, tc.iovs, tc.iovsLen, tc.resultSize)
+			results, err := fn.Call(context.Background(), tc.fd, tc.iovs+offset, tc.iovsLen+offset, tc.resultSize+offset)
 			require.NoError(t, err)
 			require.Equal(t, tc.expectedErrno, wasi.Errno(results[0])) // results[0] is the errno
 		})
@@ -982,14 +1056,14 @@ func TestSnapshotPreview1_ProcExit(t *testing.T) {
 		},
 	}
 
-	store, ctx, fn := instantiateWasmStore(t, FunctionProcExit, ImportProcExit, moduleName)
+	_, fn := instantiateModule(t, FunctionProcExit, ImportProcExit, moduleName)
 
 	for _, tt := range tests {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
 			// When ProcExit is called, store.CallFunction returns immediately, returning the exit code as the error.
-			_, err := store.Engine.Call(ctx, fn, uint64(tc.exitCode))
+			_, err := fn.Call(context.Background(), uint64(tc.exitCode))
 			var code wasi.ExitCode
 			require.ErrorAs(t, err, &code)
 			require.Equal(t, code, wasi.ExitCode(tc.exitCode))
@@ -1021,32 +1095,38 @@ func TestSnapshotPreview1_RandomGet(t *testing.T) {
 		}
 	}
 
-	store, ctx, fn := instantiateWasmStore(t, FunctionRandomGet, ImportRandomGet, moduleName, randOpt)
-
+	mod, fn := instantiateModule(t, FunctionRandomGet, ImportRandomGet, moduleName, randOpt)
+	mem := mod.Memory("memory")
 	t.Run("SnapshotPreview1.RandomGet", func(t *testing.T) {
-		maskMemory(store, len(expectedMemory))
+		maskMemory(t, mem, len(expectedMemory))
 
-		// invoke RandomGet directly and check the memory side effects!
-		errno := NewAPI(randOpt).RandomGet(ctx, offset, length)
+		// Invoke RandomGet directly and check the memory side effects!
+		errno := NewAPI(randOpt).RandomGet(mod.Instance.Ctx, offset, length)
 		require.Equal(t, wasi.ErrnoSuccess, errno)
-		require.Equal(t, expectedMemory, store.Memories[0].Buffer)
+
+		actual, ok := mem.Read(0, offset+length+1)
+		require.True(t, ok)
+		require.Equal(t, expectedMemory, actual)
 	})
 
 	t.Run(FunctionRandomGet, func(t *testing.T) {
-		maskMemory(store, len(expectedMemory))
+		maskMemory(t, mem, len(expectedMemory))
 
-		results, err := store.Engine.Call(ctx, fn, uint64(offset), uint64(length))
+		results, err := fn.Call(context.Background(), uint64(offset), uint64(length))
 		require.NoError(t, err)
 		require.Equal(t, wasi.ErrnoSuccess, wasi.Errno(results[0])) // cast because results are always uint64
-		require.Equal(t, expectedMemory, store.Memories[0].Buffer)
+
+		actual, ok := mem.Read(0, offset+length+1)
+		require.True(t, ok)
+		require.Equal(t, expectedMemory, actual)
 	})
 }
 
 func TestSnapshotPreview1_RandomGet_Errors(t *testing.T) {
 	validAddress := uint32(0) // arbitrary valid address
 
-	store, ctx, fn := instantiateWasmStore(t, FunctionRandomGet, ImportRandomGet, moduleName)
-	memorySize := uint32(len(store.Memories[0].Buffer))
+	mod, fn := instantiateModule(t, FunctionRandomGet, ImportRandomGet, moduleName)
+	memorySize := mod.Memory("memory").Size()
 
 	tests := []struct {
 		name   string
@@ -1070,7 +1150,7 @@ func TestSnapshotPreview1_RandomGet_Errors(t *testing.T) {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			results, err := store.Engine.Call(ctx, fn, uint64(tc.offset), uint64(tc.length))
+			results, err := fn.Call(context.Background(), uint64(tc.offset), uint64(tc.length))
 			require.NoError(t, err)
 			require.Equal(t, uint64(wasi.ErrnoFault), results[0]) // results[0] is the errno
 		})
@@ -1078,13 +1158,13 @@ func TestSnapshotPreview1_RandomGet_Errors(t *testing.T) {
 }
 
 func TestSnapshotPreview1_RandomGet_SourceError(t *testing.T) {
-	store, ctx, fn := instantiateWasmStore(t, FunctionRandomGet, ImportRandomGet, moduleName, func(api *wasiAPI) {
+	_, fn := instantiateModule(t, FunctionRandomGet, ImportRandomGet, moduleName, func(api *wasiAPI) {
 		api.randSource = func(p []byte) error {
 			return errors.New("random source error")
 		}
 	})
 
-	results, err := store.Engine.Call(ctx, fn, uint64(1), uint64(5)) // arbitrary offset and length
+	results, err := fn.Call(context.Background(), uint64(1), uint64(5)) // arbitrary offset and length
 	require.NoError(t, err)
 	require.Equal(t, uint64(wasi.ErrnoIo), results[0]) // results[0] is the errno
 }
@@ -1093,7 +1173,9 @@ func TestSnapshotPreview1_RandomGet_SourceError(t *testing.T) {
 // TODO: TestSnapshotPreview1_SockSend TestSnapshotPreview1_SockSend_Errors
 // TODO: TestSnapshotPreview1_SockShutdown TestSnapshotPreview1_SockShutdown_Errors
 
-func instantiateWasmStore(t *testing.T, wasiFunction, wasiImport, moduleName string, opts ...Option) (*wasm.Store, *wasm.ModuleContext, *wasm.FunctionInstance) {
+const testMemoryPageSize = 1
+
+func instantiateModule(t *testing.T, wasiFunction, wasiImport, moduleName string, opts ...Option) (*wasm.PublicModule, publicwasm.Function) {
 	enabledFeatures := wasm.Features20191205
 	mod, err := text.DecodeModule([]byte(fmt.Sprintf(`(module
   %[2]s
@@ -1106,29 +1188,21 @@ func instantiateWasmStore(t *testing.T, wasiFunction, wasiImport, moduleName str
 	store := wasm.NewStore(context.Background(), interpreter.NewEngine(), enabledFeatures)
 
 	snapshotPreview1Functions := SnapshotPreview1Functions(opts...)
-	goFunc := snapshotPreview1Functions[wasiFunction]
-	fn, err := wasm.NewGoFunc(wasiFunction, goFunc)
-	require.NoError(t, err)
-
-	// Add the host module
-	hostModule := &wasm.ModuleInstance{Name: wasi.ModuleSnapshotPreview1, Exports: map[string]*wasm.ExportInstance{}}
-	store.ModuleInstances[hostModule.Name] = hostModule
-
-	wasiFn, err := store.AddHostFunction(hostModule, fn)
+	_, err = store.NewHostModule(wasi.ModuleSnapshotPreview1, snapshotPreview1Functions)
 	require.NoError(t, err)
 
 	instantiated, err := store.Instantiate(mod, moduleName)
 	require.NoError(t, err)
 
-	return store, instantiated.Context, wasiFn
+	fn := instantiated.Function(wasiFunction)
+	require.NotNil(t, fn)
+	return instantiated, fn
 }
 
 // maskMemory sets the first memory in the store to '?' * size, so tests can see what's written.
 //
-// Note: WebAssembly 1.0 (20191205) can have only up to one memory, so index zero is unambiguous.
-func maskMemory(store *wasm.Store, size int) {
-	store.Memories[0].Buffer = make([]byte, size)
-	for i := 0; i < size; i++ {
-		store.Memories[0].Buffer[i] = '?'
+func maskMemory(t *testing.T, mem publicwasm.Memory, size int) {
+	for i := uint32(0); i < uint32(size); i++ {
+		require.True(t, mem.WriteByte(i, '?'))
 	}
 }
diff --git a/internal/wasm/interpreter/interpreter_test.go b/internal/wasm/interpreter/interpreter_test.go
index 1c7ae501..a48c27f7 100644
--- a/internal/wasm/interpreter/interpreter_test.go
+++ b/internal/wasm/interpreter/interpreter_test.go
@@ -49,26 +49,30 @@ func TestEngine_Call(t *testing.T) {
 		TypeSection:     []*wasm.FunctionType{{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i64}}},
 		FunctionSection: []wasm.Index{wasm.Index(0)},
 		CodeSection:     []*wasm.Code{{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeEnd}}},
+		ExportSection:   map[string]*wasm.Export{"fn": {Type: wasm.ExternTypeFunc, Index: 0, Name: "fn"}},
 	}
 
 	// Use exported functions to simplify instantiation of a Wasm function
 	e := NewEngine()
 	store := wasm.NewStore(context.Background(), e, wasm.Features20191205)
-	_, err := store.Instantiate(m, "")
+	mod, err := store.Instantiate(m, "")
 	require.NoError(t, err)
 
+	fn := mod.Function("fn")
+	require.NotNil(t, fn)
+
 	// ensure base case doesn't fail
-	results, err := e.Call(store.ModuleContexts[""], store.Functions[0], 3)
+	results, err := fn.Call(context.Background(), 3)
 	require.NoError(t, err)
 	require.Equal(t, uint64(3), results[0])
 
 	t.Run("errs when not enough parameters", func(t *testing.T) {
-		_, err := e.Call(store.ModuleContexts[""], store.Functions[0])
+		_, err := fn.Call(context.Background())
 		require.EqualError(t, err, "expected 1 params, but passed 0")
 	})
 
 	t.Run("errs when too many parameters", func(t *testing.T) {
-		_, err := e.Call(store.ModuleContexts[""], store.Functions[0], 1, 2)
+		_, err := fn.Call(context.Background(), 1, 2)
 		require.EqualError(t, err, "expected 1 params, but passed 2")
 	})
 }
diff --git a/internal/wasm/jit/engine_test.go b/internal/wasm/jit/engine_test.go
index 4c13980b..689af27c 100644
--- a/internal/wasm/jit/engine_test.go
+++ b/internal/wasm/jit/engine_test.go
@@ -93,26 +93,30 @@ func TestEngine_Call(t *testing.T) {
 		TypeSection:     []*wasm.FunctionType{{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i64}}},
 		FunctionSection: []wasm.Index{wasm.Index(0)},
 		CodeSection:     []*wasm.Code{{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeEnd}}},
+		ExportSection:   map[string]*wasm.Export{"fn": {Type: wasm.ExternTypeFunc, Index: 0, Name: "fn"}},
 	}
 
 	// Use exported functions to simplify instantiation of a Wasm function
 	e := NewEngine()
 	store := wasm.NewStore(context.Background(), e, wasm.Features20191205)
-	_, err := store.Instantiate(m, "")
+	mod, err := store.Instantiate(m, "")
 	require.NoError(t, err)
 
+	fn := mod.Function("fn")
+	require.NotNil(t, fn)
+
 	// ensure base case doesn't fail
-	results, err := e.Call(store.ModuleContexts[""], store.Functions[0], 3)
+	results, err := fn.Call(context.Background(), 3)
 	require.NoError(t, err)
 	require.Equal(t, uint64(3), results[0])
 
 	t.Run("errs when not enough parameters", func(t *testing.T) {
-		_, err := e.Call(store.ModuleContexts[""], store.Functions[0])
+		_, err := fn.Call(context.Background())
 		require.EqualError(t, err, "expected 1 params, but passed 0")
 	})
 
 	t.Run("errs when too many parameters", func(t *testing.T) {
-		_, err := e.Call(store.ModuleContexts[""], store.Functions[0], 1, 2)
+		_, err := fn.Call(context.Background(), 1, 2)
 		require.EqualError(t, err, "expected 1 params, but passed 2")
 	})
 }
diff --git a/internal/wasm/memory_test.go b/internal/wasm/memory_test.go
index f966005f..073e665a 100644
--- a/internal/wasm/memory_test.go
+++ b/internal/wasm/memory_test.go
@@ -14,7 +14,7 @@ func TestMemoryPageConsts(t *testing.T) {
 
 func Test_MemoryPagesToBytesNum(t *testing.T) {
 	for _, numPage := range []uint32{0, 1, 5, 10} {
-		require.Equal(t, uint64(numPage*MemoryPageSize), memoryPagesToBytesNum(numPage))
+		require.Equal(t, uint64(numPage*MemoryPageSize), MemoryPagesToBytesNum(numPage))
 	}
 }
 
diff --git a/internal/wasm/store_test.go b/internal/wasm/store_test.go
index 98978df1..ef33b07e 100644
--- a/internal/wasm/store_test.go
+++ b/internal/wasm/store_test.go
@@ -95,14 +95,14 @@ func TestStore_AddHostFunction(t *testing.T) {
 
 	// Add the host module
 	hostModule := &ModuleInstance{Name: "test", Exports: make(map[string]*ExportInstance, 1)}
-	s.ModuleInstances[hostModule.Name] = hostModule
+	s.moduleInstances[hostModule.Name] = hostModule
 
 	_, err = s.AddHostFunction(hostModule, hf)
 	require.NoError(t, err)
 
 	// The function was added to the store, prefixed by the owning module name
-	require.Equal(t, 1, len(s.Functions))
-	fn := s.Functions[0]
+	require.Equal(t, 1, len(s.functions))
+	fn := s.functions[0]
 	require.Equal(t, "test.fn", fn.Name)
 
 	// The function was exported in the module
@@ -115,7 +115,7 @@ func TestStore_AddHostFunction(t *testing.T) {
 	require.EqualError(t, err, `"fn" is already exported in module "test"`)
 
 	// Any side effects should be reverted
-	require.Equal(t, []*FunctionInstance{fn, nil}, s.Functions)
+	require.Equal(t, []*FunctionInstance{fn, nil}, s.functions)
 	require.Equal(t, map[string]*ExportInstance{"fn": exp}, hostModule.Exports)
 }
 
@@ -127,7 +127,7 @@ func TestStore_ExportImportedHostFunction(t *testing.T) {
 
 	// Add the host module
 	hostModule := &ModuleInstance{Name: "", Exports: make(map[string]*ExportInstance, 1)}
-	s.ModuleInstances[hostModule.Name] = hostModule
+	s.moduleInstances[hostModule.Name] = hostModule
 	_, err = s.AddHostFunction(hostModule, hf)
 	require.NoError(t, err)
 
@@ -146,7 +146,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.ModuleInstance)
+		require.Equal(t, s.moduleInstances["test"], ei.Function.ModuleInstance)
 	})
 }
 
@@ -189,7 +189,7 @@ func TestFunctionInstance_Call(t *testing.T) {
 
 			// Add the host module
 			hostModule := &ModuleInstance{Name: "host", Exports: map[string]*ExportInstance{}}
-			store.ModuleInstances[hostModule.Name] = hostModule
+			store.moduleInstances[hostModule.Name] = hostModule
 			_, err = store.AddHostFunction(hostModule, fn)
 			require.NoError(t, err)
 
@@ -258,9 +258,9 @@ func TestStore_getTypeInstance(t *testing.T) {
 		s := newStore()
 		const max = 10
 		s.maximumFunctionTypes = max
-		s.TypeIDs = make(map[string]FunctionTypeID)
+		s.typeIDs = make(map[string]FunctionTypeID)
 		for i := 0; i < max; i++ {
-			s.TypeIDs[strconv.Itoa(i)] = 0
+			s.typeIDs[strconv.Itoa(i)] = 0
 		}
 		_, err := s.getTypeInstance(&FunctionType{})
 		require.Error(t, err)
@@ -278,7 +278,7 @@ func TestStore_getTypeInstance(t *testing.T) {
 				actual, err := s.getTypeInstance(tc)
 				require.NoError(t, err)
 
-				expectedTypeID, ok := s.TypeIDs[tc.String()]
+				expectedTypeID, ok := s.typeIDs[tc.String()]
 				require.True(t, ok)
 				require.Equal(t, expectedTypeID, actual.TypeID)
 				require.Equal(t, tc, actual.Type)
@@ -373,7 +373,7 @@ func TestExecuteConstExpression(t *testing.T) {
 	})
 }
 
-func TestStore_AddGlobal(t *testing.T) {
+func TestStore_AddHostGlobal(t *testing.T) {
 	tests := []struct {
 		name            string
 		enabledFeatures Features
@@ -404,7 +404,7 @@ func TestStore_AddGlobal(t *testing.T) {
 			s := NewStore(context.Background(), &catchContext{}, tc.enabledFeatures)
 			m := &ModuleInstance{Exports: map[string]*ExportInstance{}}
 
-			err := s.AddGlobal(m, "test", 1, ValueTypeI64, tc.mutable)
+			err := s.AddHostGlobal(m, "test", 1, ValueTypeI64, tc.mutable)
 			if tc.expectedErr == "" {
 				require.NoError(t, err)
 				e, err := m.GetExport("test", ExternTypeGlobal)
@@ -425,15 +425,15 @@ func TestStore_releaseFunctionInstances(t *testing.T) {
 	s := newStore()
 	nonReleaseTargetAddr := FunctionIndex(0)
 	maxAddr := FunctionIndex(10)
-	s.Functions = make([]*FunctionInstance, maxAddr+1)
+	s.functions = make([]*FunctionInstance, maxAddr+1)
 
-	s.Functions[nonReleaseTargetAddr] = &FunctionInstance{} // Non-nil!
+	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!
+		s.functions[f.Index] = &FunctionInstance{} // Non-nil!
 		expectedReleasedAddr = append(expectedReleasedAddr, f.Index)
 	}
 
@@ -442,20 +442,20 @@ func TestStore_releaseFunctionInstances(t *testing.T) {
 
 	// Ensure the release targets become nil.
 	for _, f := range fs {
-		require.Nil(t, s.Functions[f.Index])
+		require.Nil(t, s.functions[f.Index])
 	}
 
 	require.Equal(t, s.releasedFunctionIndex, expectedReleasedAddr)
 
 	// Plus non-target should remain intact.
-	require.NotNil(t, s.Functions[nonReleaseTargetAddr])
+	require.NotNil(t, s.functions[nonReleaseTargetAddr])
 }
 
 func TestStore_addFunctionInstances(t *testing.T) {
 	t.Run("no released index", func(t *testing.T) {
 		s := newStore()
 		prevMaxAddr := FunctionIndex(10)
-		s.Functions = make([]*FunctionInstance, prevMaxAddr+1)
+		s.functions = make([]*FunctionInstance, prevMaxAddr+1)
 
 		for i := FunctionIndex(0); i < 10; i++ {
 			expectedIndex := prevMaxAddr + 1 + i
@@ -474,8 +474,8 @@ func TestStore_addFunctionInstances(t *testing.T) {
 
 		maxAddr := expectedAddr * 10
 		tailInstance := &FunctionInstance{}
-		s.Functions = make([]*FunctionInstance, maxAddr+1)
-		s.Functions[maxAddr] = tailInstance
+		s.functions = make([]*FunctionInstance, maxAddr+1)
+		s.functions[maxAddr] = tailInstance
 
 		f := &FunctionInstance{}
 		s.addFunctionInstances(f)
@@ -484,7 +484,7 @@ func TestStore_addFunctionInstances(t *testing.T) {
 		require.Equal(t, expectedAddr, f.Index)
 
 		// And the others must be intact.
-		require.Equal(t, tailInstance, s.Functions[maxAddr])
+		require.Equal(t, tailInstance, s.functions[maxAddr])
 
 		require.Equal(t, expectedReleasedAddr, s.releasedFunctionIndex)
 	})
@@ -494,15 +494,15 @@ func TestStore_releaseGlobalInstances(t *testing.T) {
 	s := newStore()
 	nonReleaseTargetAddr := globalIndex(0)
 	maxAddr := globalIndex(10)
-	s.Globals = make([]*GlobalInstance, maxAddr+1)
+	s.globals = make([]*GlobalInstance, maxAddr+1)
 
-	s.Globals[nonReleaseTargetAddr] = &GlobalInstance{} // Non-nil!
+	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!
+		s.globals[g.index] = &GlobalInstance{} // Non-nil!
 		expectedReleasedAddr = append(expectedReleasedAddr, g.index)
 	}
 
@@ -510,20 +510,20 @@ func TestStore_releaseGlobalInstances(t *testing.T) {
 
 	// Ensure the release targets become nil.
 	for _, g := range gs {
-		require.Nil(t, s.Globals[g.index])
+		require.Nil(t, s.globals[g.index])
 	}
 
 	require.Equal(t, s.releasedGlobalIndex, expectedReleasedAddr)
 
 	// Plus non-target should remain intact.
-	require.NotNil(t, s.Globals[nonReleaseTargetAddr])
+	require.NotNil(t, s.globals[nonReleaseTargetAddr])
 }
 
 func TestStore_addGlobalInstances(t *testing.T) {
 	t.Run("no released index", func(t *testing.T) {
 		s := newStore()
 		prevMaxAddr := globalIndex(10)
-		s.Globals = make([]*GlobalInstance, prevMaxAddr+1)
+		s.globals = make([]*GlobalInstance, prevMaxAddr+1)
 
 		for i := globalIndex(0); i < 10; i++ {
 			expectedIndex := prevMaxAddr + 1 + i
@@ -542,8 +542,8 @@ func TestStore_addGlobalInstances(t *testing.T) {
 
 		maxAddr := expectedAddr * 10
 		tailInstance := &GlobalInstance{}
-		s.Globals = make([]*GlobalInstance, maxAddr+1)
-		s.Globals[maxAddr] = tailInstance
+		s.globals = make([]*GlobalInstance, maxAddr+1)
+		s.globals[maxAddr] = tailInstance
 
 		g := &GlobalInstance{}
 		s.addGlobalInstances(g)
@@ -552,7 +552,7 @@ func TestStore_addGlobalInstances(t *testing.T) {
 		require.Equal(t, expectedAddr, g.index)
 
 		// And the others must be intact.
-		require.Equal(t, tailInstance, s.Globals[maxAddr])
+		require.Equal(t, tailInstance, s.globals[maxAddr])
 
 		require.Equal(t, expectedReleasedGlobalIndex, s.releasedGlobalIndex)
 	})
@@ -562,28 +562,28 @@ func TestStore_releaseTableInstance(t *testing.T) {
 	s := newStore()
 	nonReleaseTargetAddr := tableIndex(0)
 	maxAddr := tableIndex(10)
-	s.Tables = make([]*TableInstance, maxAddr+1)
+	s.tables = make([]*TableInstance, maxAddr+1)
 
-	s.Tables[nonReleaseTargetAddr] = &TableInstance{} // Non-nil!
+	s.tables[nonReleaseTargetAddr] = &TableInstance{} // Non-nil!
 
 	table := &TableInstance{index: 1}
 
 	s.releaseTableInstance(table)
 
 	// Ensure the release targets become nil.
-	require.Nil(t, s.Tables[table.index])
+	require.Nil(t, s.tables[table.index])
 
 	require.Contains(t, s.releasedTableIndex, table.index)
 
 	// Plus non-target should remain intact.
-	require.NotNil(t, s.Tables[nonReleaseTargetAddr])
+	require.NotNil(t, s.tables[nonReleaseTargetAddr])
 }
 
 func TestStore_addTableInstance(t *testing.T) {
 	t.Run("no released index", func(t *testing.T) {
 		s := newStore()
 		prevMaxAddr := tableIndex(10)
-		s.Tables = make([]*TableInstance, prevMaxAddr+1)
+		s.tables = make([]*TableInstance, prevMaxAddr+1)
 
 		for i := tableIndex(0); i < 10; i++ {
 			expectedIndex := prevMaxAddr + 1 + i
@@ -602,8 +602,8 @@ func TestStore_addTableInstance(t *testing.T) {
 
 		maxAddr := expectedAddr * 10
 		tailInstance := &TableInstance{}
-		s.Tables = make([]*TableInstance, maxAddr+1)
-		s.Tables[maxAddr] = tailInstance
+		s.tables = make([]*TableInstance, maxAddr+1)
+		s.tables[maxAddr] = tailInstance
 
 		table := &TableInstance{}
 		s.addTableInstance(table)
@@ -612,7 +612,7 @@ func TestStore_addTableInstance(t *testing.T) {
 		require.Equal(t, expectedAddr, table.index)
 
 		// And the others must be intact.
-		require.Equal(t, tailInstance, s.Tables[maxAddr])
+		require.Equal(t, tailInstance, s.tables[maxAddr])
 
 		require.Equal(t, expectedReleasedTableIndex, s.releasedTableIndex)
 	})
@@ -622,28 +622,28 @@ func TestStore_releaseMemoryInstance(t *testing.T) {
 	s := newStore()
 	nonReleaseTargetAddr := memoryIndex(0)
 	releaseTargetAddr := memoryIndex(10)
-	s.Memories = make([]*MemoryInstance, releaseTargetAddr+1)
+	s.memories = make([]*MemoryInstance, releaseTargetAddr+1)
 
-	s.Memories[nonReleaseTargetAddr] = &MemoryInstance{} // Non-nil!
+	s.memories[nonReleaseTargetAddr] = &MemoryInstance{} // Non-nil!
 	mem := &MemoryInstance{index: releaseTargetAddr}
-	s.Memories[releaseTargetAddr] = mem // Non-nil!
+	s.memories[releaseTargetAddr] = mem // Non-nil!
 
 	s.releaseMemoryInstance(mem)
 
 	// Ensure the release targets become nil.
-	require.Nil(t, s.Memories[mem.index])
+	require.Nil(t, s.memories[mem.index])
 
 	require.Equal(t, s.releasedMemoryIndex, []memoryIndex{mem.index})
 
 	// Plus non-target should remain intact.
-	require.NotNil(t, s.Memories[nonReleaseTargetAddr])
+	require.NotNil(t, s.memories[nonReleaseTargetAddr])
 }
 
 func TestStore_addMemoryInstance(t *testing.T) {
 	t.Run("no released index", func(t *testing.T) {
 		s := newStore()
 		prevMaxAddr := memoryIndex(10)
-		s.Memories = make([]*MemoryInstance, prevMaxAddr+1)
+		s.memories = make([]*MemoryInstance, prevMaxAddr+1)
 
 		for i := memoryIndex(0); i < 10; i++ {
 			expectedIndex := prevMaxAddr + 1 + i
@@ -662,8 +662,8 @@ func TestStore_addMemoryInstance(t *testing.T) {
 
 		maxAddr := expectedAddr * 10
 		tailInstance := &MemoryInstance{}
-		s.Memories = make([]*MemoryInstance, maxAddr+1)
-		s.Memories[maxAddr] = tailInstance
+		s.memories = make([]*MemoryInstance, maxAddr+1)
+		s.memories[maxAddr] = tailInstance
 
 		mem := &MemoryInstance{}
 		s.addMemoryInstance(mem)
@@ -672,7 +672,7 @@ func TestStore_addMemoryInstance(t *testing.T) {
 		require.Equal(t, expectedAddr, mem.index)
 
 		// And the others must be intact.
-		require.Equal(t, tailInstance, s.Memories[maxAddr])
+		require.Equal(t, tailInstance, s.memories[maxAddr])
 
 		require.Equal(t, expectedReleasedMemoryIndex, s.releasedMemoryIndex)
 	})
@@ -690,7 +690,7 @@ func TestStore_resolveImports(t *testing.T) {
 	})
 	t.Run("export instance not found", func(t *testing.T) {
 		s := newStore()
-		s.ModuleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{}, Name: moduleName}
+		s.moduleInstances[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\"")
@@ -698,14 +698,14 @@ func TestStore_resolveImports(t *testing.T) {
 	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.moduleInstances[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")
 		})
 		t.Run("signature mismatch", func(t *testing.T) {
 			s := newStore()
-			s.ModuleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
+			s.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
 				Function: &FunctionInstance{FunctionType: &TypeInstance{Type: &FunctionType{}}},
 			}}, Name: moduleName}
 			m := &Module{
@@ -719,7 +719,7 @@ func TestStore_resolveImports(t *testing.T) {
 		t.Run("ok", func(t *testing.T) {
 			s := newStore()
 			f := &FunctionInstance{FunctionType: &TypeInstance{Type: &FunctionType{Results: []ValueType{ValueTypeF32}}}}
-			s.ModuleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
+			s.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
 				Function: f,
 			}}, Name: moduleName}
 			m := &Module{
@@ -728,14 +728,14 @@ 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.moduleInstances[moduleName])
 			require.Contains(t, functions, f)
 		})
 	})
 	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.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
 				Type:   ExternTypeGlobal,
 				Global: &GlobalInstance{Type: &GlobalType{Mutable: false}},
 			}}, Name: moduleName}
@@ -745,7 +745,7 @@ func TestStore_resolveImports(t *testing.T) {
 		})
 		t.Run("type mismatch", func(t *testing.T) {
 			s := newStore()
-			s.ModuleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
+			s.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
 				Type:   ExternTypeGlobal,
 				Global: &GlobalInstance{Type: &GlobalType{ValType: ValueTypeI32}},
 			}}, Name: moduleName}
@@ -756,7 +756,7 @@ func TestStore_resolveImports(t *testing.T) {
 		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.moduleInstances[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)
@@ -765,7 +765,7 @@ func TestStore_resolveImports(t *testing.T) {
 	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.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
 				Type:  ExternTypeTable,
 				Table: &TableInstance{ElemType: 0x00}, // Unknown!
 			}}, Name: moduleName}
@@ -776,7 +776,7 @@ func TestStore_resolveImports(t *testing.T) {
 		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.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
 				Type:  ExternTypeTable,
 				Table: &TableInstance{Min: importTableType.Limit.Min - 1},
 			}}, Name: moduleName}
@@ -788,7 +788,7 @@ func TestStore_resolveImports(t *testing.T) {
 			s := newStore()
 			max := uint32(10)
 			importTableType := &TableType{Limit: &LimitsType{Max: &max}}
-			s.ModuleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
+			s.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
 				Type:  ExternTypeTable,
 				Table: &TableInstance{Min: importTableType.Limit.Min - 1},
 			}}, Name: moduleName}
@@ -800,7 +800,7 @@ func TestStore_resolveImports(t *testing.T) {
 			s := newStore()
 			max := uint32(10)
 			tableInst := &TableInstance{Max: &max}
-			s.ModuleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
+			s.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
 				Type:  ExternTypeTable,
 				Table: tableInst,
 			}}, Name: moduleName}
@@ -813,7 +813,7 @@ func TestStore_resolveImports(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.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
 				Type:   ExternTypeMemory,
 				Memory: &MemoryInstance{Min: importMemoryType.Min - 1},
 			}}, Name: moduleName}
@@ -825,7 +825,7 @@ func TestStore_resolveImports(t *testing.T) {
 			s := newStore()
 			max := uint32(10)
 			importMemoryType := &MemoryType{Max: &max}
-			s.ModuleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
+			s.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
 				Type:   ExternTypeMemory,
 				Memory: &MemoryInstance{},
 			}}, Name: moduleName}
@@ -837,7 +837,7 @@ func TestStore_resolveImports(t *testing.T) {
 			s := newStore()
 			max := uint32(10)
 			memoryInst := &MemoryInstance{Max: &max}
-			s.ModuleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
+			s.moduleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
 				Type:   ExternTypeMemory,
 				Memory: memoryInst,
 			}}, Name: moduleName}
@@ -967,7 +967,7 @@ func TestModuleInstance_applyElements(t *testing.T) {
 }
 
 func Test_newModuleInstance(t *testing.T) {
-
+	// TODO
 }
 
 func newStore() *Store {
diff --git a/tests/spectest/spec_test.go b/tests/spectest/spec_test.go
index 3df58c5f..5b0ac821 100644
--- a/tests/spectest/spec_test.go
+++ b/tests/spectest/spec_test.go
@@ -185,32 +185,22 @@ func (c command) expectedError() (err error) {
 }
 
 func addSpectestModule(t *testing.T, store *wasm.Store) {
-	// Add the host module
-	spectest := &wasm.ModuleInstance{Name: "spectest", Exports: map[string]*wasm.ExportInstance{}}
-	store.ModuleInstances[spectest.Name] = spectest
-
-	var printV = func() {}
-	var printI32 = func(uint32) {}
-	var printF32 = func(float32) {}
-	var printI64 = func(uint64) {}
-	var printF64 = func(float64) {}
-	var printI32F32 = func(uint32, float32) {}
-	var printF64F64 = func(float64, float64) {}
-
-	for n, v := range map[string]interface{}{
-		"print":         printV,
-		"print_i32":     printI32,
-		"print_f32":     printF32,
-		"print_i64":     printI64,
-		"print_f64":     printF64,
-		"print_i32_f32": printI32F32,
-		"print_f64_f64": printF64F64,
-	} {
-		fn, err := wasm.NewGoFunc(n, v)
-		require.NoError(t, err)
-		_, err = store.AddHostFunction(spectest, fn)
-		require.NoError(t, err, "AddHostFunction(%s)", n)
-	}
+	const name = "spectest"
+	_, err := store.NewHostModule(name, map[string]interface{}{
+		"print":         func() {},
+		"print_i32":     func(uint32) {},
+		"print_f32":     func(float32) {},
+		"print_i64":     func(uint64) {},
+		"print_f64":     func(float64) {},
+		"print_i32_f32": func(uint32, float32) {},
+		"print_f64_f64": func(float64, float64) {},
+	})
+	require.NoError(t, err)
+
+	pubMod, ok := store.Module(name).(*wasm.PublicModule)
+	require.True(t, ok)
+
+	mod := pubMod.Instance
 
 	for _, g := range []struct {
 		name      string
@@ -222,14 +212,14 @@ func addSpectestModule(t *testing.T, store *wasm.Store) {
 		{name: "global_f32", valueType: wasm.ValueTypeF32, value: uint64(uint32(0x44268000))},
 		{name: "global_f64", valueType: wasm.ValueTypeF64, value: uint64(0x4084d00000000000)},
 	} {
-		require.NoError(t, store.AddGlobal(spectest, g.name, g.value, g.valueType, false), "AddGlobal(%s)", g.name)
+		require.NoError(t, store.AddHostGlobal(mod, g.name, g.value, g.valueType, false), "AddGlobal(%s)", g.name)
 	}
 
 	tableLimitMax := uint32(20)
-	require.NoError(t, store.AddTableInstance(spectest, "table", 10, &tableLimitMax))
+	require.NoError(t, store.AddHostTableInstance(mod, "table", 10, &tableLimitMax))
 
 	memoryLimitMax := uint32(2)
-	require.NoError(t, store.AddMemoryInstance(spectest, "memory", 1, &memoryLimitMax))
+	require.NoError(t, store.AddHostMemoryInstance(mod, "memory", 1, &memoryLimitMax))
 }
 
 func TestJIT(t *testing.T) {
@@ -295,7 +285,7 @@ func runTest(t *testing.T, newEngine func() wasm.Engine) {
 						if c.Name != "" {
 							name = c.Name
 						}
-						store.ModuleInstances[c.As] = store.ModuleInstances[name]
+						store.AliasModuleInstance(name, c.As)
 					case "assert_return", "action":
 						moduleName := lastInstanceName
 						if c.Action.Module != "" {
diff --git a/wasm_test.go b/wasm_test.go
index 43331164..be0b5401 100644
--- a/wasm_test.go
+++ b/wasm_test.go
@@ -105,13 +105,13 @@ func TestDecodedModule_WithName(t *testing.T) {
 	internal := r.(*runtime).store
 	m1, err := r.NewModule(base.WithName("1"))
 	require.NoError(t, err)
-	require.Nil(t, internal.ModuleContexts["0"])
-	require.Equal(t, internal.ModuleContexts["1"], m1.(*internalwasm.PublicModule).Context)
+	require.Nil(t, internal.Module("0"))
+	require.Equal(t, internal.Module("1"), m1.(*internalwasm.PublicModule))
 
 	m2, err := r.NewModule(base.WithName("2"))
 	require.NoError(t, err)
-	require.Nil(t, internal.ModuleContexts["0"])
-	require.Equal(t, internal.ModuleContexts["2"], m2.(*internalwasm.PublicModule).Context)
+	require.Nil(t, internal.Module("0"))
+	require.Equal(t, internal.Module("2"), m2.(*internalwasm.PublicModule))
 }
 
 // TestModule_Memory only covers a couple cases to avoid duplication of internal/wasm/runtime_test.go