Repository: tetratelabs/wazero
Pull request #294 (2022-02-25 00:43:21)

Refactors store and adds ReleaseModuleInstance.
This PR refactors store.go and adds store.ReleaseModuleInstance.
Notably, this removes the "rollback" functions in InstantiateModule
which we had used to rollback the mutated state when we encounter
the initialization failure. Instead, we separate out the validation from
initialization and migrate most of the validation logics into module.go

As for ReleaseModuleInstance, that is necessary to complete #293,
will be leveraged to make it possible for store to be used for long-running
processes and environment.


mathetake: note to myself: move refcounters to store
mathetake: I will rework a bit so that we have refcount per moduleInstance rather than per each instance (memory, function, etc) as the latter needlessly makes the code complex while we benefit a little 
mathetake: so I need to do backfilling tests a lot..
mathetake: some assert_invalid failing but almost there
mathetake: ok passed spectests.. will polish
mathetake: so I will stop caring about concurrency in this PR as it bloats further more

mathetake: so I will stop caring about concurrency in this PR as it bloats further more

mathetake: ok needs to fix amd64 then..
mathetake: some of the funcs lacks godoc but eagerly mark this ready for review

Patch:
diff --git a/internal/wasm/engine.go b/internal/wasm/engine.go
index daad13fe..c1729383 100644
--- a/internal/wasm/engine.go
+++ b/internal/wasm/engine.go
@@ -9,4 +9,10 @@ type Engine interface {
 
 	// Compile compiles down the function instance.
 	Compile(f *FunctionInstance) error
+
+	// Release releases the resources allocated by a function instance.
+	// Note: this is only called after ensuring that no existing function will call the release target.
+	// Therefore, it is safe to reuse the resource, for example reallocate the new compiled function
+	// for the same FunctionIndex.
+	Release(f *FunctionInstance) error
 }
diff --git a/internal/wasm/func_validation.go b/internal/wasm/func_validation.go
index fca6a521..59a4f361 100644
--- a/internal/wasm/func_validation.go
+++ b/internal/wasm/func_validation.go
@@ -3,12 +3,13 @@ package internalwasm
 import (
 	"bytes"
 	"fmt"
+	"io"
 	"strings"
 
 	"github.com/tetratelabs/wazero/internal/leb128"
 )
 
-// validateFunctionInstance validates the instruction sequence of a function instance.
+// validateFunction validates the instruction sequence of a function.
 // following the specification https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#instructions%E2%91%A2.
 //
 // f is the validation target function instance.
@@ -21,8 +22,10 @@ import (
 //
 // Returns an error if the instruction sequence is not valid,
 // or potentially it can exceed the maximum number of values on the stack.
-func validateFunctionInstance(
-	f *FunctionInstance,
+func validateFunction(
+	functionType *FunctionType,
+	body []byte,
+	localTypes []ValueType,
 	functions []Index,
 	globals []*GlobalType,
 	memories []*MemoryType,
@@ -36,20 +39,20 @@ func validateFunctionInstance(
 	hasTable := len(tables) > 0
 
 	// We start with the outermost control block which is for function return if the code branches into it.
-	controlBloclStack := []*controlBlock{{blockType: f.FunctionType.Type}}
+	controlBloclStack := []*controlBlock{{blockType: functionType}}
 	// Create the valueTypeStack to track the state of Wasm value stacks at anypoint of execution.
 	valueTypeStack := &valueTypeStack{}
 
 	// Now start walking through all the instructions in the body while tracking
 	// control blocks and value types to check the validity of all instructions.
-	for pc := uint64(0); pc < uint64(len(f.Body)); pc++ {
-		op := f.Body[pc]
+	for pc := uint64(0); pc < uint64(len(body)); pc++ {
+		op := body[pc]
 		if OpcodeI32Load <= op && op <= OpcodeI64Store32 {
 			if !hasMemory {
 				return fmt.Errorf("unknown memory access")
 			}
 			pc++
-			align, num, err := leb128.DecodeUint32(bytes.NewBuffer(f.Body[pc:]))
+			align, num, err := leb128.DecodeUint32(bytes.NewBuffer(body[pc:]))
 			if err != nil {
 				return fmt.Errorf("read memory align: %v", err)
 			}
@@ -227,7 +230,7 @@ func validateFunctionInstance(
 			}
 			pc += num
 			// offset
-			_, num, err = leb128.DecodeUint32(bytes.NewBuffer(f.Body[pc:]))
+			_, num, err = leb128.DecodeUint32(bytes.NewBuffer(body[pc:]))
 			if err != nil {
 				return fmt.Errorf("read memory offset: %v", err)
 			}
@@ -237,7 +240,7 @@ func validateFunctionInstance(
 				return fmt.Errorf("unknown memory access")
 			}
 			pc++
-			val, num, err := leb128.DecodeUint32(bytes.NewBuffer(f.Body[pc:]))
+			val, num, err := leb128.DecodeUint32(bytes.NewBuffer(body[pc:]))
 			if err != nil {
 				return fmt.Errorf("read immediate: %v", err)
 			}
@@ -258,14 +261,14 @@ func validateFunctionInstance(
 			pc++
 			switch Opcode(op) {
 			case OpcodeI32Const:
-				_, num, err := leb128.DecodeInt32(bytes.NewBuffer(f.Body[pc:]))
+				_, num, err := leb128.DecodeInt32(bytes.NewBuffer(body[pc:]))
 				if err != nil {
 					return fmt.Errorf("read i32 immediate: %s", err)
 				}
 				pc += num - 1
 				valueTypeStack.push(ValueTypeI32)
 			case OpcodeI64Const:
-				_, num, err := leb128.DecodeInt64(bytes.NewBuffer(f.Body[pc:]))
+				_, num, err := leb128.DecodeInt64(bytes.NewBuffer(body[pc:]))
 				if err != nil {
 					return fmt.Errorf("read i64 immediate: %v", err)
 				}
@@ -280,46 +283,46 @@ func validateFunctionInstance(
 			}
 		} else if OpcodeLocalGet <= op && op <= OpcodeGlobalSet {
 			pc++
-			index, num, err := leb128.DecodeUint32(bytes.NewBuffer(f.Body[pc:]))
+			index, num, err := leb128.DecodeUint32(bytes.NewBuffer(body[pc:]))
 			if err != nil {
 				return fmt.Errorf("read immediate: %v", err)
 			}
 			pc += num - 1
 			switch op {
 			case OpcodeLocalGet:
-				inputLen := uint32(len(f.FunctionType.Type.Params))
-				if l := uint32(len(f.LocalTypes)) + inputLen; index >= l {
+				inputLen := uint32(len(functionType.Params))
+				if l := uint32(len(localTypes)) + inputLen; index >= l {
 					return fmt.Errorf("invalid local index for local.get %d >= %d(=len(locals)+len(parameters))", index, l)
 				}
 				if index < inputLen {
-					valueTypeStack.push(f.FunctionType.Type.Params[index])
+					valueTypeStack.push(functionType.Params[index])
 				} else {
-					valueTypeStack.push(f.LocalTypes[index-inputLen])
+					valueTypeStack.push(localTypes[index-inputLen])
 				}
 			case OpcodeLocalSet:
-				inputLen := uint32(len(f.FunctionType.Type.Params))
-				if l := uint32(len(f.LocalTypes)) + inputLen; index >= l {
+				inputLen := uint32(len(functionType.Params))
+				if l := uint32(len(localTypes)) + inputLen; index >= l {
 					return fmt.Errorf("invalid local index for local.set %d >= %d(=len(locals)+len(parameters))", index, l)
 				}
 				var expType ValueType
 				if index < inputLen {
-					expType = f.FunctionType.Type.Params[index]
+					expType = functionType.Params[index]
 				} else {
-					expType = f.LocalTypes[index-inputLen]
+					expType = localTypes[index-inputLen]
 				}
 				if err := valueTypeStack.popAndVerifyType(expType); err != nil {
 					return err
 				}
 			case OpcodeLocalTee:
-				inputLen := uint32(len(f.FunctionType.Type.Params))
-				if l := uint32(len(f.LocalTypes)) + inputLen; index >= l {
+				inputLen := uint32(len(functionType.Params))
+				if l := uint32(len(localTypes)) + inputLen; index >= l {
 					return fmt.Errorf("invalid local index for local.tee %d >= %d(=len(locals)+len(parameters))", index, l)
 				}
 				var expType ValueType
 				if index < inputLen {
-					expType = f.FunctionType.Type.Params[index]
+					expType = functionType.Params[index]
 				} else {
-					expType = f.LocalTypes[index-inputLen]
+					expType = localTypes[index-inputLen]
 				}
 				if err := valueTypeStack.popAndVerifyType(expType); err != nil {
 					return err
@@ -342,7 +345,7 @@ func validateFunctionInstance(
 			}
 		} else if op == OpcodeBr {
 			pc++
-			index, num, err := leb128.DecodeUint32(bytes.NewBuffer(f.Body[pc:]))
+			index, num, err := leb128.DecodeUint32(bytes.NewBuffer(body[pc:]))
 			if err != nil {
 				return fmt.Errorf("read immediate: %v", err)
 			} else if int(index) >= len(controlBloclStack) {
@@ -364,7 +367,7 @@ func validateFunctionInstance(
 			valueTypeStack.unreachable()
 		} else if op == OpcodeBrIf {
 			pc++
-			index, num, err := leb128.DecodeUint32(bytes.NewBuffer(f.Body[pc:]))
+			index, num, err := leb128.DecodeUint32(bytes.NewBuffer(body[pc:]))
 			if err != nil {
 				return fmt.Errorf("read immediate: %v", err)
 			} else if int(index) >= len(controlBloclStack) {
@@ -393,7 +396,7 @@ func validateFunctionInstance(
 			}
 		} else if op == OpcodeBrTable {
 			pc++
-			r := bytes.NewBuffer(f.Body[pc:])
+			r := bytes.NewBuffer(body[pc:])
 			nl, num, err := leb128.DecodeUint32(r)
 			if err != nil {
 				return fmt.Errorf("read immediate: %w", err)
@@ -455,7 +458,7 @@ func validateFunctionInstance(
 			valueTypeStack.unreachable()
 		} else if op == OpcodeCall {
 			pc++
-			index, num, err := leb128.DecodeUint32(bytes.NewBuffer(f.Body[pc:]))
+			index, num, err := leb128.DecodeUint32(bytes.NewBuffer(body[pc:]))
 			if err != nil {
 				return fmt.Errorf("read immediate: %v", err)
 			}
@@ -474,14 +477,14 @@ func validateFunctionInstance(
 			}
 		} else if op == OpcodeCallIndirect {
 			pc++
-			typeIndex, num, err := leb128.DecodeUint32(bytes.NewBuffer(f.Body[pc:]))
+			typeIndex, num, err := leb128.DecodeUint32(bytes.NewBuffer(body[pc:]))
 			if err != nil {
 				return fmt.Errorf("read immediate: %v", err)
 			}
 			pc += num - 1
 			pc++
-			if f.Body[pc] != 0x00 {
-				return fmt.Errorf("call_indirect reserved bytes not zero but got %d", f.Body[pc])
+			if body[pc] != 0x00 {
+				return fmt.Errorf("call_indirect reserved bytes not zero but got %d", body[pc])
 			}
 			if !hasTable {
 				return fmt.Errorf("table not given while having call_indirect")
@@ -699,7 +702,7 @@ func validateFunctionInstance(
 				return fmt.Errorf("invalid numeric instruction 0x%x", op)
 			}
 		} else if op == OpcodeBlock {
-			bt, num, err := DecodeBlockType(f.ModuleInstance.Types, bytes.NewBuffer(f.Body[pc+1:]))
+			bt, num, err := decodeBlockType(types, bytes.NewBuffer(body[pc+1:]))
 			if err != nil {
 				return fmt.Errorf("read block: %w", err)
 			}
@@ -711,7 +714,7 @@ func validateFunctionInstance(
 			valueTypeStack.pushStackLimit()
 			pc += num
 		} else if op == OpcodeLoop {
-			bt, num, err := DecodeBlockType(f.ModuleInstance.Types, bytes.NewBuffer(f.Body[pc+1:]))
+			bt, num, err := decodeBlockType(types, bytes.NewBuffer(body[pc+1:]))
 			if err != nil {
 				return fmt.Errorf("read block: %w", err)
 			}
@@ -724,7 +727,7 @@ func validateFunctionInstance(
 			valueTypeStack.pushStackLimit()
 			pc += num
 		} else if op == OpcodeIf {
-			bt, num, err := DecodeBlockType(f.ModuleInstance.Types, bytes.NewBuffer(f.Body[pc+1:]))
+			bt, num, err := decodeBlockType(types, bytes.NewBuffer(body[pc+1:]))
 			if err != nil {
 				return fmt.Errorf("read block: %w", err)
 			}
@@ -775,7 +778,7 @@ func validateFunctionInstance(
 			// on values previously pushed by outer blocks.
 			valueTypeStack.popStackLimit()
 		} else if op == OpcodeReturn {
-			expTypes := f.FunctionType.Type.Results
+			expTypes := functionType.Results
 			for i := 0; i < len(expTypes); i++ {
 				if err := valueTypeStack.popAndVerifyType(expTypes[len(expTypes)-1-i]); err != nil {
 					return fmt.Errorf("return type mismatch on return: %v; want %v", err, expTypes)
@@ -944,3 +947,46 @@ type controlBlock struct {
 	isLoop                 bool
 	isIf                   bool
 }
+
+func decodeBlockType(types []*FunctionType, r io.Reader) (*FunctionType, uint64, error) {
+	return decodeBlockTypeImpl(func(index int64) (*FunctionType, error) {
+		if index < 0 || (index >= int64(len(types))) {
+			return nil, fmt.Errorf("type index out of range: %d", index)
+		}
+		return types[index], nil
+	}, r)
+}
+
+// DecodeBlockType is exported for use in the compiler
+func DecodeBlockType(types []*TypeInstance, r io.Reader) (*FunctionType, uint64, error) {
+	return decodeBlockTypeImpl(func(index int64) (*FunctionType, error) {
+		if index < 0 || (index >= int64(len(types))) {
+			return nil, fmt.Errorf("type index out of range: %d", index)
+		}
+		return types[index].Type, nil
+	}, r)
+}
+
+func decodeBlockTypeImpl(functionTypeResolver func(index int64) (*FunctionType, error), r io.Reader) (*FunctionType, uint64, error) {
+	raw, num, err := leb128.DecodeInt33AsInt64(r)
+	if err != nil {
+		return nil, 0, fmt.Errorf("decode int33: %w", err)
+	}
+
+	var ret *FunctionType
+	switch raw {
+	case -64: // 0x40 in original byte = nil
+		ret = &FunctionType{}
+	case -1: // 0x7f in original byte = i32
+		ret = &FunctionType{Results: []ValueType{ValueTypeI32}}
+	case -2: // 0x7e in original byte = i64
+		ret = &FunctionType{Results: []ValueType{ValueTypeI64}}
+	case -3: // 0x7d in original byte = f32
+		ret = &FunctionType{Results: []ValueType{ValueTypeF32}}
+	case -4: // 0x7c in original byte = f64
+		ret = &FunctionType{Results: []ValueType{ValueTypeF64}}
+	default:
+		ret, err = functionTypeResolver(raw)
+	}
+	return ret, num, err
+}
diff --git a/internal/wasm/interpreter/interpreter.go b/internal/wasm/interpreter/interpreter.go
index d79c1195..0178faba 100644
--- a/internal/wasm/interpreter/interpreter.go
+++ b/internal/wasm/interpreter/interpreter.go
@@ -20,7 +20,7 @@ var callStackCeiling = buildoptions.CallStackCeiling
 
 // engine is an interpreter implementation of internalwasm.Engine
 type engine struct {
-	// compiledFunctions stores compiled functions where the index means wasm.FunctionAddress.
+	// compiledFunctions stores compiled functions where the index means wasm.FunctionIndex.
 	compiledFunctions []*compiledFunction
 
 	// mux is used for read/write access to functions slice.
@@ -132,9 +132,17 @@ type interpreterOp struct {
 	rs     []*wazeroir.InclusiveRange
 }
 
+// Release implements wasm.Engine Release
+func (e *engine) Release(f *wasm.FunctionInstance) error {
+	e.mux.Lock()
+	defer e.mux.Unlock()
+	e.compiledFunctions[f.Index] = nil
+	return nil
+}
+
 // Compile Implements wasm.Engine for engine.
 func (e *engine) Compile(f *wasm.FunctionInstance) error {
-	funcaddr := f.Address
+	funcIndex := f.Index
 	var compiled *compiledFunction
 	if f.FunctionKind == wasm.FunctionKindWasm {
 		ir, err := wazeroir.Compile(f)
@@ -152,7 +160,7 @@ func (e *engine) Compile(f *wasm.FunctionInstance) error {
 		}
 	}
 
-	if l := len(e.compiledFunctions); l <= int(funcaddr) {
+	if l := len(e.compiledFunctions); l <= int(funcIndex) {
 		// This case compiledFunctions slice needs to grow to store a new compiledFunction.
 		// However, it is read in newCallEngine, so we have to take write lock (via .Unlock)
 		// rather than read lock (via .RLock).
@@ -161,7 +169,7 @@ func (e *engine) Compile(f *wasm.FunctionInstance) error {
 		// Double the size of compiled functions.
 		e.compiledFunctions = append(e.compiledFunctions, make([]*compiledFunction, l)...)
 	}
-	e.compiledFunctions[funcaddr] = compiled
+	e.compiledFunctions[funcIndex] = compiled
 	return nil
 }
 
@@ -260,7 +268,7 @@ func (e *engine) lowerIROps(f *wasm.FunctionInstance,
 		case *wazeroir.OperationCall:
 			target := f.ModuleInstance.Functions[o.FunctionIndex]
 			op.us = make([]uint64, 1)
-			op.us[0] = uint64(target.Address)
+			op.us[0] = uint64(target.Index)
 		case *wazeroir.OperationCallIndirect:
 			op.us = make([]uint64, 2)
 			op.us[0] = uint64(o.TableIndex)
@@ -477,7 +485,7 @@ func (e *engine) Call(ctx *wasm.ModuleContext, f *wasm.FunctionInstance, params
 		}
 	}()
 
-	compiled := e.compiledFunctions[f.Address]
+	compiled := e.compiledFunctions[f.Index]
 	if compiled == nil {
 		err = fmt.Errorf("function not compiled")
 		return
@@ -557,10 +565,7 @@ func (vm *callEngine) callNativeFunc(ctx *wasm.ModuleContext, f *compiledFunctio
 	moduleInst := f.funcInstance.ModuleInstance
 	memoryInst := moduleInst.MemoryInstance
 	globals := moduleInst.Globals
-	var table *wasm.TableInstance
-	if len(moduleInst.Tables) > 0 {
-		table = moduleInst.Tables[0] // WebAssembly 1.0 (20191205) defines at most one table
-	}
+	table := moduleInst.TableInstance
 	vm.pushFrame(frame)
 	bodyLen := uint64(len(frame.f.body))
 	for frame.pc < bodyLen {
@@ -620,7 +625,7 @@ func (vm *callEngine) callNativeFunc(ctx *wasm.ModuleContext, f *compiledFunctio
 					}
 					panic(wasm.ErrRuntimeIndirectCallTypeMismatch)
 				}
-				target := vm.compiledFunctions[table.Table[offset].FunctionAddress]
+				target := vm.compiledFunctions[table.Table[offset].FunctionIndex]
 				// Call in.
 				if target.hostFn != nil {
 					vm.callHostFunc(ctx, target)
diff --git a/internal/wasm/jit/compiler.go b/internal/wasm/jit/compiler.go
index 1c376ca1..733de1b1 100644
--- a/internal/wasm/jit/compiler.go
+++ b/internal/wasm/jit/compiler.go
@@ -19,7 +19,7 @@ type compiler interface {
 	compile() (code []byte, staticData compiledFunctionStaticData, stackPointerCeil uint64, err error)
 	// compileHostFunction emits the trampoline code from which native code can jump into the host function.
 	// TODO: maybe we wouldn't need to have trampoline for host functions.
-	compileHostFunction(address wasm.FunctionAddress) error
+	compileHostFunction(address wasm.FunctionIndex) error
 	// compileLabel notify compilers of the beginning of a label.
 	// Return true if the compiler decided to skip the entire label.
 	// See wazeroir.OperationLabel
diff --git a/internal/wasm/jit/engine.go b/internal/wasm/jit/engine.go
index bb392f7a..5d9eeb12 100644
--- a/internal/wasm/jit/engine.go
+++ b/internal/wasm/jit/engine.go
@@ -19,7 +19,7 @@ type (
 	// engine is an JIT implementation of internalwasm.Engine
 	engine struct {
 		// compiledFunctions are the currently compiled functions.
-		// The index means wasm.FunctionAddress, but we intentionally avoid using map
+		// The index means wasm.FunctionIndex, but we intentionally avoid using map
 		// as the underlying memory region is accessed by assembly directly by
 		// using compiledFunctionsElement0Address.
 		compiledFunctions []*compiledFunction
@@ -141,7 +141,7 @@ type (
 
 		// Set when statusCode == jitStatusCall{HostFunction,BuiltInFunction}
 		// Indicating the function call index.
-		functionCallAddress wasm.FunctionAddress
+		functionCallAddress wasm.FunctionIndex
 	}
 
 	// callFrame holds the information to which the caller function can return.
@@ -227,13 +227,13 @@ const (
 	compiledFunctionStackPointerCeilOffset   = 8
 
 	// Offsets for wasm.TableElement
-	tableElementFunctionAddressOffset = 0
-	tableElementFunctionTypeIDOffset  = 8
+	tableElementFunctionIndexOffset  = 0
+	tableElementFunctionTypeIDOffset = 8
 
 	// Offsets for wasm.ModuleInstance
 	moduleInstanceGlobalsOffset = 48
 	moduleInstanceMemoryOffset  = 72
-	moduleInstanceTablesOffset  = 80
+	moduleInstanceTableOffset   = 80
 
 	// Offsets for wasm.TableInstance.
 	tableInstanceTableOffset    = 0
@@ -316,6 +316,16 @@ func (c *callFrame) String() string {
 	)
 }
 
+// Release implements wasm.Engine Release
+func (e *engine) Release(f *wasm.FunctionInstance) error {
+	e.mux.Lock()
+	defer e.mux.Unlock()
+	e.compiledFunctions[f.Index] = nil
+
+	// TODO: UnMap memory region.
+	return nil
+}
+
 func (e *engine) Compile(f *wasm.FunctionInstance) (err error) {
 	var compiled *compiledFunction
 	if f.FunctionKind == wasm.FunctionKindWasm {
@@ -327,12 +337,12 @@ func (e *engine) Compile(f *wasm.FunctionInstance) (err error) {
 		return fmt.Errorf("failed to compile function: %w", err)
 	}
 
-	e.addCompiledFunction(f.Address, compiled)
+	e.addCompiledFunction(f.Index, compiled)
 	return
 }
 
-func (e *engine) addCompiledFunction(addr wasm.FunctionAddress, compiled *compiledFunction) {
-	if len(e.compiledFunctions) <= int(addr) {
+func (e *engine) addCompiledFunction(index wasm.FunctionIndex, compiled *compiledFunction) {
+	if len(e.compiledFunctions) <= int(index) {
 		// This case compiledFunctions slice needs to grow to store a new compiledFunction.
 		// However, it is read in newCallEngine, so we have to take write lock (via .Unlock)
 		// rather than read lock (via .RLock).
@@ -341,7 +351,7 @@ func (e *engine) addCompiledFunction(addr wasm.FunctionAddress, compiled *compil
 		// Double the size of compiled functions.
 		e.compiledFunctions = append(e.compiledFunctions, make([]*compiledFunction, len(e.compiledFunctions))...)
 	}
-	e.compiledFunctions[addr] = compiled
+	e.compiledFunctions[index] = compiled
 }
 
 func (e *engine) Call(ctx *wasm.ModuleContext, f *wasm.FunctionInstance, params ...uint64) (results []uint64, err error) {
@@ -387,7 +397,7 @@ func (e *engine) Call(ctx *wasm.ModuleContext, f *wasm.FunctionInstance, params
 		vm.pushValue(param)
 	}
 
-	compiled := vm.compiledFunctions[f.Address]
+	compiled := vm.compiledFunctions[f.Index]
 	if compiled == nil {
 		err = fmt.Errorf("function not compiled")
 		return
@@ -475,11 +485,11 @@ func (vm *callEngine) valueStackTopIndex() uint64 {
 }
 
 const (
-	builtinFunctionAddressMemoryGrow wasm.FunctionAddress = iota
-	builtinFunctionAddressGrowValueStack
-	builtinFunctionAddressGrowCallFrameStack
-	// builtinFunctionAddressBreakPoint is internal (only for wazero developers). Disabled by default.
-	builtinFunctionAddressBreakPoint
+	builtinFunctionIndexMemoryGrow wasm.FunctionIndex = iota
+	builtinFunctionIndexGrowValueStack
+	builtinFunctionIndexGrowCallFrameStack
+	// builtinFunctionIndexBreakPoint is internal (only for wazero developers). Disabled by default.
+	builtinFunctionIndexBreakPoint
 )
 
 // execHostFunction executes the given host function represented as *reflect.Value.
@@ -574,17 +584,17 @@ jitentry:
 			goto jitentry
 		case jitCallStatusCodeCallBuiltInFunction:
 			switch vm.exitContext.functionCallAddress {
-			case builtinFunctionAddressMemoryGrow:
+			case builtinFunctionIndexMemoryGrow:
 				callerCompiledFunction := vm.callFrameTop().compiledFunction
 				vm.builtinFunctionMemoryGrow(callerCompiledFunction.source.ModuleInstance.MemoryInstance)
-			case builtinFunctionAddressGrowValueStack:
+			case builtinFunctionIndexGrowValueStack:
 				callerCompiledFunction := vm.callFrameTop().compiledFunction
 				vm.builtinFunctionGrowValueStack(callerCompiledFunction.stackPointerCeil)
-			case builtinFunctionAddressGrowCallFrameStack:
+			case builtinFunctionIndexGrowCallFrameStack:
 				vm.builtinFunctionGrowCallFrameStack()
 			}
 			if buildoptions.IsDebugMode {
-				if vm.exitContext.functionCallAddress == builtinFunctionAddressBreakPoint {
+				if vm.exitContext.functionCallAddress == builtinFunctionIndexBreakPoint {
 					runtime.Breakpoint()
 				}
 			}
@@ -671,7 +681,7 @@ func compileHostFunction(f *wasm.FunctionInstance) (*compiledFunction, error) {
 		return nil, err
 	}
 
-	if err = compiler.compileHostFunction(f.Address); err != nil {
+	if err = compiler.compileHostFunction(f.Index); err != nil {
 		return nil, err
 	}
 
diff --git a/internal/wasm/jit/jit_amd64.go b/internal/wasm/jit/jit_amd64.go
index 10cdd608..c3f38a3b 100644
--- a/internal/wasm/jit/jit_amd64.go
+++ b/internal/wasm/jit/jit_amd64.go
@@ -171,7 +171,7 @@ func (c *amd64Compiler) label(labelKey string) *labelInfo {
 
 // compileHostFunction constructs the entire code to enter the host function implementation,
 // and return back to the caller.
-func (c *amd64Compiler) compileHostFunction(address wasm.FunctionAddress) error {
+func (c *amd64Compiler) compileHostFunction(address wasm.FunctionIndex) error {
 	// First we must update the location stack to reflect the number of host function inputs.
 	c.pushFunctionParams()
 
@@ -931,7 +931,7 @@ func (c *amd64Compiler) compileCall(o *wazeroir.OperationCall) error {
 	}
 
 	target := c.f.ModuleInstance.Functions[o.FunctionIndex]
-	if err := c.callFunctionFromAddress(target.Address, target.FunctionType.Type); err != nil {
+	if err := c.callFunctionFromConst(target.Index, target.FunctionType.Type); err != nil {
 		return err
 	}
 
@@ -981,7 +981,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.FunctionAddress and 64-bit wasm.TypeID).
+	// and size of wasm.TableInstance equals 128 bit (64-bit wasm.FunctionIndex and 64-bit wasm.TypeID).
 	getTypeInstanceAddress := c.newProg()
 	notLengthExceedJump.To.SetTarget(getTypeInstanceAddress)
 	getTypeInstanceAddress.As = x86.ASHLQ
@@ -1046,7 +1046,7 @@ func (c *amd64Compiler) compileCallIndirect(o *wazeroir.OperationCallIndirect) e
 	readValue.As = x86.AMOVQ
 	readValue.To.Type = obj.TYPE_REG
 	readValue.To.Reg = offset.register
-	readValue.From.Offset = tableElementFunctionAddressOffset
+	readValue.From.Offset = tableElementFunctionIndexOffset
 	readValue.From.Type = obj.TYPE_MEM
 	readValue.From.Reg = offset.register
 	c.addInstruction(readValue)
@@ -4184,7 +4184,7 @@ func (c *amd64Compiler) compileMemoryGrow() error {
 		return err
 	}
 
-	if err := c.callGoFunction(jitCallStatusCodeCallBuiltInFunction, builtinFunctionAddressMemoryGrow); err != nil {
+	if err := c.callGoFunction(jitCallStatusCodeCallBuiltInFunction, builtinFunctionIndexMemoryGrow); err != nil {
 		return err
 	}
 
@@ -4534,39 +4534,39 @@ func (c *amd64Compiler) callFunctionFromRegister(register int16, functype *wasm.
 
 }
 
-// callFunctionFromAddress adds instructions to call a function whose address equals addr constant.
-func (c *amd64Compiler) callFunctionFromAddress(addr wasm.FunctionAddress, functype *wasm.FunctionType) error {
+// callFunctionFromConst adds instructions to call a function whose address equals addr constant.
+func (c *amd64Compiler) callFunctionFromConst(addr wasm.FunctionIndex, functype *wasm.FunctionType) error {
 	return c.callFunction(addr, nilRegister, functype)
 }
 
-// callFunction adds instructions to call a function whose address equals either addr parameter or the value on addrReg.
-// Pass addrReg == nilRegister to indicate that use addr argument as the source of target function's address.
-// Otherwise, the added code tries to read the function address from the register for addrReg argument.
+// callFunction adds instructions to call a function whose address equals either addr parameter or the value on indexReg.
+// Pass indexReg == nilRegister to indicate that use addr argument as the source of target function's address.
+// Otherwise, the added code tries to read the function address from the register for indexReg argument.
 //
 // Note: this is the counter part for returnFunction, and see the comments there as well
 // to understand how the function calls are achieved.
-func (c *amd64Compiler) callFunction(addr wasm.FunctionAddress, addrReg int16, functype *wasm.FunctionType) error {
+func (c *amd64Compiler) callFunction(index wasm.FunctionIndex, indexReg int16, functype *wasm.FunctionType) error {
 	// Release all the registers as our calling convention requires the caller-save.
 	if err := c.releaseAllRegistersToStack(); err != nil {
 		return err
 	}
 
 	// First, we have to make sure that
-	if !isNilRegister(addrReg) {
-		c.locationStack.markRegisterUsed(addrReg)
+	if !isNilRegister(indexReg) {
+		c.locationStack.markRegisterUsed(indexReg)
 	}
 
 	// Obtain the temporary registers to be used in the followings.
 	regs, found := c.locationStack.takeFreeRegisters(generalPurposeRegisterTypeInt, 5)
 	if !found {
-		// This in theory never happen as all the registers must be free except addrReg.
+		// This in theory never happen as all the registers must be free except indexReg.
 		return fmt.Errorf("could not find enough free registers")
 	}
 	c.locationStack.markRegisterUsed(regs...)
 
 	// Alias these free tmp registers for readability.
 	callFrameStackPointerRegister, tmpRegister, targetAddressRegister,
-		callFrameStackTopAddressRegister, compiledFunctionAddressRegister := regs[0], regs[1], regs[2], regs[3], regs[4]
+		callFrameStackTopAddressRegister, compiledFunctionIndexRegister := regs[0], regs[1], regs[2], regs[3], regs[4]
 
 	// First, we read the current call frame stack pointer.
 	c.getCallFrameStackPointer(callFrameStackPointerRegister)
@@ -4588,26 +4588,26 @@ func (c *amd64Compiler) callFunction(addr wasm.FunctionAddress, addrReg int16, f
 	c.addInstruction(jmpIfNotCallFrameStackNeedsGrow)
 
 	// Otherwise, we have to make the builtin function call to grow the call frame stack.
-	if !isNilRegister(addrReg) {
+	if !isNilRegister(indexReg) {
 		// If we need to get the target funcaddr from register (call_indirect case), we must save it before growing callframe stack,
 		// as the register is not saved across function calls.
-		savedOffsetLocation := c.locationStack.pushValueLocationOnRegister(addrReg)
+		savedOffsetLocation := c.locationStack.pushValueLocationOnRegister(indexReg)
 		c.releaseRegisterToStack(savedOffsetLocation)
 	}
 
 	// Grow the stack.
-	if err := c.callGoFunction(jitCallStatusCodeCallBuiltInFunction, builtinFunctionAddressGrowCallFrameStack); err != nil {
+	if err := c.callGoFunction(jitCallStatusCodeCallBuiltInFunction, builtinFunctionIndexGrowCallFrameStack); err != nil {
 		return err
 	}
 
 	// For call_indirect, we need to push the value back to the register.
-	if !isNilRegister(addrReg) {
+	if !isNilRegister(indexReg) {
 		// Since this is right after callGoFunction, we have to initialize the stack base pointer
 		// to properly load the value on memory stack.
 		c.initializeReservedStackBasePointer()
 
 		savedOffsetLocation := c.locationStack.pop()
-		savedOffsetLocation.setRegister(addrReg)
+		savedOffsetLocation.setRegister(indexReg)
 		c.moveStackToRegister(savedOffsetLocation)
 	}
 
@@ -4740,32 +4740,32 @@ func (c *amd64Compiler) callFunction(addr wasm.FunctionAddress, addrReg int16, f
 		c.addInstruction(readCopmiledFunctionsElement0Address)
 
 		// Next, read the address of the target function (= &callEngine.compiledFunctions[offset])
-		// into compiledFunctionAddressRegister.
-		readCompiledFunctionAddressAddress := c.newProg()
-		readCompiledFunctionAddressAddress.As = x86.AMOVQ
-		readCompiledFunctionAddressAddress.To.Type = obj.TYPE_REG
-		readCompiledFunctionAddressAddress.To.Reg = compiledFunctionAddressRegister
-		readCompiledFunctionAddressAddress.From.Type = obj.TYPE_MEM
-		readCompiledFunctionAddressAddress.From.Reg = tmpRegister
-		if !isNilRegister(addrReg) {
-			readCompiledFunctionAddressAddress.From.Index = addrReg
-			readCompiledFunctionAddressAddress.From.Scale = 8 // because the size of *compiledFunction equals 8 bytes.
+		// into compiledFunctionIndexRegister.
+		readCompiledFunctionIndexAddress := c.newProg()
+		readCompiledFunctionIndexAddress.As = x86.AMOVQ
+		readCompiledFunctionIndexAddress.To.Type = obj.TYPE_REG
+		readCompiledFunctionIndexAddress.To.Reg = compiledFunctionIndexRegister
+		readCompiledFunctionIndexAddress.From.Type = obj.TYPE_MEM
+		readCompiledFunctionIndexAddress.From.Reg = tmpRegister
+		if !isNilRegister(indexReg) {
+			readCompiledFunctionIndexAddress.From.Index = indexReg
+			readCompiledFunctionIndexAddress.From.Scale = 8 // because the size of *compiledFunction equals 8 bytes.
 		} else {
-			// Note: Funcaddr is limited up to 2^27 so this offset never exceeds 32-bit integer.
-			readCompiledFunctionAddressAddress.From.Offset = int64(addr) * 8 // because the size of *compiledFunction equals 8 bytes.
+			// Note: FunctionIndex is limited up to 2^27 so this offset never exceeds 32-bit integer.
+			readCompiledFunctionIndexAddress.From.Offset = int64(index) * 8 // because the size of *compiledFunction equals 8 bytes.
 		}
-		c.addInstruction(readCompiledFunctionAddressAddress)
+		c.addInstruction(readCompiledFunctionIndexAddress)
 
 		// Finally, we are ready to place the address of the target function's *compiledFunction into the new callframe.
 		// In the example, this is equivalent to set "rc.next".
-		putCompiledFunctionFunctionAddressOnNewCallFrame := c.newProg()
-		putCompiledFunctionFunctionAddressOnNewCallFrame.As = x86.AMOVQ
-		putCompiledFunctionFunctionAddressOnNewCallFrame.To.Type = obj.TYPE_MEM
-		putCompiledFunctionFunctionAddressOnNewCallFrame.To.Reg = callFrameStackTopAddressRegister
-		putCompiledFunctionFunctionAddressOnNewCallFrame.To.Offset = callFrameCompiledFunctionOffset
-		putCompiledFunctionFunctionAddressOnNewCallFrame.From.Type = obj.TYPE_REG
-		putCompiledFunctionFunctionAddressOnNewCallFrame.From.Reg = compiledFunctionAddressRegister
-		c.addInstruction(putCompiledFunctionFunctionAddressOnNewCallFrame)
+		putCompiledFunctionFunctionIndexOnNewCallFrame := c.newProg()
+		putCompiledFunctionFunctionIndexOnNewCallFrame.As = x86.AMOVQ
+		putCompiledFunctionFunctionIndexOnNewCallFrame.To.Type = obj.TYPE_MEM
+		putCompiledFunctionFunctionIndexOnNewCallFrame.To.Reg = callFrameStackTopAddressRegister
+		putCompiledFunctionFunctionIndexOnNewCallFrame.To.Offset = callFrameCompiledFunctionOffset
+		putCompiledFunctionFunctionIndexOnNewCallFrame.From.Type = obj.TYPE_REG
+		putCompiledFunctionFunctionIndexOnNewCallFrame.From.Reg = compiledFunctionIndexRegister
+		c.addInstruction(putCompiledFunctionFunctionIndexOnNewCallFrame)
 	}
 
 	// 4) Set ra.1 so that we can return back to this function properly.
@@ -4799,14 +4799,14 @@ func (c *amd64Compiler) callFunction(addr wasm.FunctionAddress, addrReg int16, f
 	jmpToTargetFunction := c.newProg()
 	jmpToTargetFunction.As = obj.AJMP
 	jmpToTargetFunction.To.Type = obj.TYPE_MEM
-	jmpToTargetFunction.To.Reg = compiledFunctionAddressRegister
+	jmpToTargetFunction.To.Reg = compiledFunctionIndexRegister
 	jmpToTargetFunction.To.Offset = compiledFunctionCodeInitialAddressOffset
 	c.addInstruction(jmpToTargetFunction)
 
 	// All the registers used are temporary so we mark them unused.
 	c.locationStack.markRegisterUnused(
 		tmpRegister, targetAddressRegister, callFrameStackTopAddressRegister,
-		callFrameStackPointerRegister, compiledFunctionAddressRegister, addrReg,
+		callFrameStackPointerRegister, compiledFunctionIndexRegister, indexReg,
 	)
 
 	// On the function return, we have to initialize the state.
@@ -4990,7 +4990,7 @@ func (c *amd64Compiler) returnFunction() error {
 // callGoFunction adds instructions to call a Go function whose address equals the addr parameter.
 // jitStatus is set before making call, and it should be either jitCallStatusCodeCallBuiltInFunction or
 // jitCallStatusCodeCallHostFunction.
-func (c *amd64Compiler) callGoFunction(jitStatus jitCallStatusCode, addr wasm.FunctionAddress) error {
+func (c *amd64Compiler) callGoFunction(jitStatus jitCallStatusCode, addr wasm.FunctionIndex) error {
 	// Set the functionAddress to the callEngine.exitContext functionCallAddress.
 	setFunctionCallAddress := c.newProg()
 	setFunctionCallAddress.As = x86.AMOVQ
@@ -5009,7 +5009,7 @@ func (c *amd64Compiler) callGoFunction(jitStatus jitCallStatusCode, addr wasm.Fu
 	// Obtain the temporary registers to be used in the followings.
 	regs, found := c.locationStack.takeFreeRegisters(generalPurposeRegisterTypeInt, 2)
 	if !found {
-		// This in theory never happen as all the registers must be free except addrReg.
+		// This in theory never happen as all the registers must be free except indexReg.
 		return fmt.Errorf("could not find enough free registers")
 	}
 	c.locationStack.markRegisterUsed(regs...)
@@ -5312,7 +5312,7 @@ func (c *amd64Compiler) maybeGrowValueStack() error {
 	c.addInstruction(jmpIfNoNeedToGrowStack)
 
 	// Otherwise, we have to make the builtin function call to grow the call stack.
-	if err := c.callGoFunction(jitCallStatusCodeCallBuiltInFunction, builtinFunctionAddressGrowValueStack); err != nil {
+	if err := c.callGoFunction(jitCallStatusCodeCallBuiltInFunction, builtinFunctionIndexGrowValueStack); err != nil {
 		return err
 	}
 
@@ -5328,7 +5328,7 @@ func (c *amd64Compiler) initializeModuleContext() error {
 	// Obtain the temporary registers to be used in the followings.
 	regs, found := c.locationStack.takeFreeRegisters(generalPurposeRegisterTypeInt, 3)
 	if !found {
-		// This in theory never happen as all the registers must be free except addrReg.
+		// This in theory never happen as all the registers must be free except indexReg.
 		return fmt.Errorf("could not find enough free registers")
 	}
 	c.locationStack.markRegisterUsed(regs...)
@@ -5414,7 +5414,7 @@ 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 len(c.f.ModuleInstance.Tables) > 0 {
+	if c.f.ModuleInstance.TableInstance != nil {
 		// First, we need to read the *wasm.TableInstance.
 		readTableInstancePointer := c.newProg()
 		readTableInstancePointer.As = x86.AMOVQ
@@ -5422,19 +5422,11 @@ func (c *amd64Compiler) initializeModuleContext() error {
 		readTableInstancePointer.To.Reg = tmpRegister
 		readTableInstancePointer.From.Type = obj.TYPE_MEM
 		readTableInstancePointer.From.Reg = moduleInstanceAddressRegister
-		readTableInstancePointer.From.Offset = moduleInstanceTablesOffset
+		readTableInstancePointer.From.Offset = moduleInstanceTableOffset
 		c.addInstruction(readTableInstancePointer)
 
-		resolveTableInstanceAddressFromPointer := c.newProg()
-		resolveTableInstanceAddressFromPointer.As = x86.AMOVQ
-		resolveTableInstanceAddressFromPointer.To.Type = obj.TYPE_REG
-		resolveTableInstanceAddressFromPointer.To.Reg = tmpRegister
-		resolveTableInstanceAddressFromPointer.From.Type = obj.TYPE_MEM
-		resolveTableInstanceAddressFromPointer.From.Reg = tmpRegister
-		c.addInstruction(resolveTableInstanceAddressFromPointer)
-
-		// At this point, tmpRegister holds the address of ModuleIntance.Tables[0].
-		// So we are ready to read and put the first item's address stored in Tables[0].Table.
+		// 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.
 		// Here we read the value into tmpRegister2.
 		readTableElement0Address := c.newProg()
 		readTableElement0Address.As = x86.AMOVQ // Note this is LEA instruction.
diff --git a/internal/wasm/jit/jit_arm64.go b/internal/wasm/jit/jit_arm64.go
index a5f695b4..dc74aca8 100644
--- a/internal/wasm/jit/jit_arm64.go
+++ b/internal/wasm/jit/jit_arm64.go
@@ -483,7 +483,7 @@ func (c *arm64Compiler) compileMaybeGrowValueStack() error {
 
 	// If ceil > valueStackLen - stack base pointer, we need to grow the stack by calling builtin Go function.
 	brIfValueStackOK := c.compilelBranchInstruction(arm64.ABLS)
-	if err := c.compileCallGoFunction(jitCallStatusCodeCallBuiltInFunction, builtinFunctionAddressGrowValueStack); err != nil {
+	if err := c.compileCallGoFunction(jitCallStatusCodeCallBuiltInFunction, builtinFunctionIndexGrowValueStack); err != nil {
 		return err
 	}
 
@@ -608,7 +608,7 @@ func (c *arm64Compiler) compileExitFromNativeCode(status jitCallStatusCode) erro
 }
 
 // compileHostFunction implements compiler.compileHostFunction for the arm64 architecture.
-func (c *arm64Compiler) compileHostFunction(address wasm.FunctionAddress) error {
+func (c *arm64Compiler) compileHostFunction(address wasm.FunctionIndex) error {
 	// The assembler skips the first instruction so we intentionally add NOP here.
 	// TODO: delete after #233
 	c.compileNOP()
@@ -1090,11 +1090,11 @@ func (c *arm64Compiler) compileBrTable(o *wazeroir.OperationBrTable) error {
 // compileCall implements compiler.compileCall for the arm64 architecture.
 func (c *arm64Compiler) compileCall(o *wazeroir.OperationCall) error {
 	target := c.f.ModuleInstance.Functions[o.FunctionIndex]
-	return c.compileCallImpl(target.Address, nilRegister, target.FunctionType.Type)
+	return c.compileCallImpl(target.Index, nilRegister, target.FunctionType.Type)
 }
 
 // compileCallImpl implements compiler.compileCall and compiler.compileCallIndirect for the arm64 architecture.
-func (c *arm64Compiler) compileCallImpl(addr wasm.FunctionAddress, addrRegister int16, functype *wasm.FunctionType) error {
+func (c *arm64Compiler) compileCallImpl(addr wasm.FunctionIndex, indexRegister int16, functype *wasm.FunctionType) error {
 	// Release all the registers as our calling convention requires the caller-save.
 	if err := c.compileReleaseAllRegistersToStack(); err != nil {
 		return err
@@ -1102,12 +1102,12 @@ func (c *arm64Compiler) compileCallImpl(addr wasm.FunctionAddress, addrRegister
 
 	freeRegisters, found := c.locationStack.takeFreeRegisters(generalPurposeRegisterTypeInt, 5)
 	if !found {
-		return fmt.Errorf("BUG: all registers except addrReg should be free at this point")
+		return fmt.Errorf("BUG: all registers except indexReg should be free at this point")
 	}
 	c.locationStack.markRegisterUsed(freeRegisters...)
 
 	// Alias for readability.
-	callFrameStackPointerRegister, callFrameStackTopAddressRegister, compiledFunctionAddressRegister, oldStackBasePointer,
+	callFrameStackPointerRegister, callFrameStackTopAddressRegister, compiledFunctionIndexRegister, oldStackBasePointer,
 		tmp := freeRegisters[0], freeRegisters[1], freeRegisters[2], freeRegisters[3], freeRegisters[4]
 
 	// First, we have to check if we need to grow the callFrame stack.
@@ -1128,27 +1128,27 @@ func (c *arm64Compiler) compileCallImpl(addr wasm.FunctionAddress, addrRegister
 
 	// If these values equal, we need to grow the callFrame stack.
 	// For call_indirect, we need to push the value back to the register.
-	if !isNilRegister(addrRegister) {
+	if !isNilRegister(indexRegister) {
 		// If we need to get the target funcaddr from register (call_indirect case), we must save it before growing callframe stack,
 		// as the register is not saved across function calls.
-		savedOffsetLocation := c.locationStack.pushValueLocationOnRegister(addrRegister)
+		savedOffsetLocation := c.locationStack.pushValueLocationOnRegister(indexRegister)
 		if err := c.compileReleaseRegisterToStack(savedOffsetLocation); err != nil {
 			return err
 		}
 	}
 
-	if err := c.compileCallGoFunction(jitCallStatusCodeCallBuiltInFunction, builtinFunctionAddressGrowCallFrameStack); err != nil {
+	if err := c.compileCallGoFunction(jitCallStatusCodeCallBuiltInFunction, builtinFunctionIndexGrowCallFrameStack); err != nil {
 		return err
 	}
 
 	// For call_indirect, we need to push the value back to the register.
-	if !isNilRegister(addrRegister) {
+	if !isNilRegister(indexRegister) {
 		// Since this is right after callGoFunction, we have to initialize the stack base pointer
 		// to properly load the value on memory stack.
 		c.compileReservedStackBasePointerRegisterInitialization()
 
 		savedOffsetLocation := c.locationStack.pop()
-		savedOffsetLocation.setRegister(addrRegister)
+		savedOffsetLocation.setRegister(indexRegister)
 		if err := c.compileLoadValueOnStackToRegister(savedOffsetLocation); err != nil {
 			return err
 		}
@@ -1214,26 +1214,26 @@ func (c *arm64Compiler) compileCallImpl(addr wasm.FunctionAddress, addrRegister
 		reservedRegisterForCallEngine, callEngineGlobalContextCompiledFunctionsElement0AddressOffset,
 		tmp)
 
-	// Next, read the address of the target function (= &vm.compiledFunctions[offset])
-	// into compiledFunctionAddressRegister.
-	if isNilRegister(addrRegister) {
+	// Next, read the index of the target function (= &vm.compiledFunctions[offset])
+	// into compiledFunctionIndexRegister.
+	if isNilRegister(indexRegister) {
 		c.compileMemoryToRegisterInstruction(
 			arm64.AMOVD,
 			tmp, int64(addr)*8, // * 8 because the size of *compiledFunction equals 8 bytes.
-			compiledFunctionAddressRegister)
+			compiledFunctionIndexRegister)
 	} else {
-		// Shift addrRegister by 3 because the size of *compiledFunction equals 8 bytes.
-		c.compileConstToRegisterInstruction(arm64.ALSLW, 3, addrRegister)
+		// Shift indexRegister by 3 because the size of *compiledFunction equals 8 bytes.
+		c.compileConstToRegisterInstruction(arm64.ALSLW, 3, indexRegister)
 		c.compileMemoryWithRegisterOffsetToRegisterInstruction(
 			arm64.AMOVD,
-			tmp, addrRegister,
-			compiledFunctionAddressRegister,
+			tmp, indexRegister,
+			compiledFunctionIndexRegister,
 		)
 	}
 
 	// Finally, we are ready to write the address of the target function's *compiledFunction into the new callframe.
 	c.compileRegisterToMemoryInstruction(arm64.AMOVD,
-		compiledFunctionAddressRegister,
+		compiledFunctionIndexRegister,
 		callFrameStackTopAddressRegister, callFrameCompiledFunctionOffset)
 
 	// 4) Set ra.current so that we can return back to this function properly.
@@ -1259,7 +1259,7 @@ func (c *arm64Compiler) compileCallImpl(addr wasm.FunctionAddress, addrRegister
 
 	// Then, br into the target function's initial address.
 	c.compileMemoryToRegisterInstruction(arm64.AMOVD,
-		compiledFunctionAddressRegister, compiledFunctionCodeInitialAddressOffset,
+		compiledFunctionIndexRegister, compiledFunctionCodeInitialAddressOffset,
 		tmp)
 
 	c.compileUnconditionalBranchToAddressOnRegister(tmp)
@@ -1465,7 +1465,7 @@ func (c *arm64Compiler) compileCallIndirect(o *wazeroir.OperationCallIndirect) e
 	// Now all checks passed, so read the target's function address, and make call.
 	c.compileMemoryToRegisterInstruction(
 		arm64.AMOVW,
-		offset.register, tableElementFunctionAddressOffset,
+		offset.register, tableElementFunctionIndexOffset,
 		offset.register,
 	)
 
@@ -2984,7 +2984,7 @@ func (c *arm64Compiler) compileMemoryAccessOffsetSetup(offsetArg uint32, targetS
 func (c *arm64Compiler) compileMemoryGrow() error {
 	c.maybeCompileMoveTopConditionalToFreeGeneralPurposeRegister()
 
-	if err := c.compileCallGoFunction(jitCallStatusCodeCallBuiltInFunction, builtinFunctionAddressMemoryGrow); err != nil {
+	if err := c.compileCallGoFunction(jitCallStatusCodeCallBuiltInFunction, builtinFunctionIndexMemoryGrow); err != nil {
 		return err
 	}
 
@@ -3025,7 +3025,7 @@ func (c *arm64Compiler) compileMemorySize() error {
 // compileCallGoFunction adds instructions to call a Go function whose address equals the addr parameter.
 // jitStatus is set before making call, and it should be either jitCallStatusCodeCallBuiltInFunction or
 // jitCallStatusCodeCallHostFunction.
-func (c *arm64Compiler) compileCallGoFunction(jitStatus jitCallStatusCode, addr wasm.FunctionAddress) error {
+func (c *arm64Compiler) compileCallGoFunction(jitStatus jitCallStatusCode, addr wasm.FunctionIndex) error {
 	// Release all the registers as our calling convention requires the caller-save.
 	if err := c.compileReleaseAllRegistersToStack(); err != nil {
 		return err
@@ -3033,7 +3033,7 @@ func (c *arm64Compiler) compileCallGoFunction(jitStatus jitCallStatusCode, addr
 
 	freeRegs, found := c.locationStack.takeFreeRegisters(generalPurposeRegisterTypeInt, 4)
 	if !found {
-		return fmt.Errorf("BUG: all registers except addrReg should be free at this point")
+		return fmt.Errorf("BUG: all registers except indexReg should be free at this point")
 	}
 	c.locationStack.markRegisterUsed(freeRegs...)
 
@@ -3466,15 +3466,13 @@ 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 len(c.f.ModuleInstance.Tables) > 0 {
+	if c.f.ModuleInstance.TableInstance != nil {
 		// "tmpX = &tables[0] (type of **wasm.TableInstance)"
 		c.compileMemoryToRegisterInstruction(
 			arm64.AMOVD,
-			moduleInstanceAddressRegister, moduleInstanceTablesOffset,
+			moduleInstanceAddressRegister, moduleInstanceTableOffset,
 			tmpX,
 		)
-		// "tmpX = *tmpX (tables[0])"
-		c.compileMemoryToRegisterInstruction(arm64.AMOVD, tmpX, 0, tmpX)
 
 		// Update vm.tableElement0Address.
 		// "tmpY = &tables[0].Table[0]"
diff --git a/internal/wasm/module.go b/internal/wasm/module.go
index 609693cf..9d79d4fb 100644
--- a/internal/wasm/module.go
+++ b/internal/wasm/module.go
@@ -1,8 +1,11 @@
 package internalwasm
 
 import (
+	"bytes"
 	"fmt"
 
+	"github.com/tetratelabs/wazero/internal/ieee754"
+	"github.com/tetratelabs/wazero/internal/leb128"
 	publicwasm "github.com/tetratelabs/wazero/wasm"
 )
 
@@ -170,6 +173,274 @@ func (m *Module) TypeOfFunction(funcIdx Index) *FunctionType {
 	return m.TypeSection[typeIdx]
 }
 
+func (m *Module) Validate() error {
+	if err := m.validateStartSection(); err != nil {
+		return err
+	}
+
+	functions, globals, memories, tables := m.allDeclarations()
+
+	// The wazero specific limitation described at RATIONALE.md.
+	const maximumGlobals = 1 << 27
+	if err := m.validateGlobals(globals, maximumGlobals); err != nil {
+		return err
+	}
+
+	if err := m.validateTables(tables, globals); err != nil {
+		return err
+	}
+
+	if err := m.validateMemories(memories, globals); err != nil {
+		return err
+	}
+
+	if err := m.validateFunctions(functions, globals, memories, tables); err != nil {
+		return err
+	}
+
+	if err := m.validateExports(functions, globals, memories, tables); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *Module) validateStartSection() error {
+	// Check the start function is valid.
+	// TODO: this should be verified during decode so that errors have the correct source positions
+	if m.StartSection != nil {
+		startIndex := *m.StartSection
+		ft := m.TypeOfFunction(startIndex)
+		if ft == nil { // TODO: move this check to decoder so that a module can never be decoded invalidly
+			return fmt.Errorf("start function has an invalid type")
+		}
+		if len(ft.Params) > 0 || len(ft.Results) > 0 {
+			return fmt.Errorf("start function must have an empty (nullary) signature: %s", ft.String())
+		}
+	}
+	return nil
+}
+
+func (m *Module) validateGlobals(globals []*GlobalType, maxGlobals int) error {
+	if len(globals) > maxGlobals {
+		return fmt.Errorf("too many globals in a module")
+	}
+
+	// Global's initialization constant expression can only reference the imported globals.
+	// See the note on https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#constant-expressions%E2%91%A0
+	importedGlobals := globals[:m.ImportGlobalCount()]
+	for _, g := range m.GlobalSection {
+		if err := validateConstExpression(importedGlobals, g.Init, g.Type.ValType); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (m *Module) validateFunctions(functions []Index, globals []*GlobalType, memories []*MemoryType, tables []*TableType) error {
+	// The wazero specific limitation described at RATIONALE.md.
+	const maximumValuesOnStack = 1 << 27
+
+	for codeIndex, typeIndex := range m.FunctionSection {
+		if typeIndex >= m.SectionElementCount(SectionIDType) {
+			return fmt.Errorf("function type index out of range")
+		} else if uint32(codeIndex) >= m.SectionElementCount(SectionIDCode) {
+			return fmt.Errorf("code index out of range")
+		}
+
+		if err := validateFunction(
+			m.TypeSection[typeIndex],
+			m.CodeSection[codeIndex].Body,
+			m.CodeSection[codeIndex].LocalTypes,
+			functions, globals, memories, tables, m.TypeSection, maximumValuesOnStack); err != nil {
+			idx := m.SectionElementCount(SectionIDFunction) - 1
+			return fmt.Errorf("invalid function (%d/%d): %v", codeIndex, idx, err)
+		}
+	}
+	return nil
+}
+
+func (m *Module) validateTables(tables []*TableType, globals []*GlobalType) error {
+	if len(tables) > 1 {
+		return fmt.Errorf("multiple tables are not supported")
+	}
+
+	for _, elem := range m.ElementSection {
+		if int(elem.TableIndex) >= len(tables) {
+			return fmt.Errorf("table index out of range")
+		}
+		err := validateConstExpression(globals, elem.OffsetExpr, ValueTypeI32)
+		if err != nil {
+			return fmt.Errorf("invalid const expression for element: %w", err)
+		}
+	}
+	return nil
+}
+
+func (m *Module) validateMemories(memories []*MemoryType, globals []*GlobalType) error {
+	if len(memories) > 1 {
+		return fmt.Errorf("multiple memories are not supported")
+	} else if len(m.DataSection) > 0 && len(memories) == 0 {
+		return fmt.Errorf("unknown memory")
+	}
+
+	for _, d := range m.DataSection {
+		if d.MemoryIndex != 0 {
+			return fmt.Errorf("memory index must be zero")
+		}
+		if err := validateConstExpression(globals, d.OffsetExpression, ValueTypeI32); err != nil {
+			return fmt.Errorf("calculate offset: %w", err)
+		}
+	}
+	return nil
+}
+
+func (m *Module) validateExports(functions []Index, globals []*GlobalType, memories []*MemoryType, tables []*TableType) error {
+	for name, exp := range m.ExportSection {
+		index := exp.Index
+		switch exp.Type {
+		case ExternTypeFunc:
+			if index >= uint32(len(functions)) {
+				return fmt.Errorf("unknown function for export[%s]", name)
+			}
+		case ExternTypeGlobal:
+			if index >= uint32(len(globals)) {
+				return fmt.Errorf("unknown global for export[%s]", name)
+			}
+		case ExternTypeMemory:
+			if index != 0 || len(memories) == 0 {
+				return fmt.Errorf("unknown memory for export[%s]", name)
+			}
+		case ExternTypeTable:
+			if index >= uint32(len(tables)) {
+				return fmt.Errorf("unknown table for export[%s]", name)
+			}
+		}
+	}
+	return nil
+}
+
+func validateConstExpression(globals []*GlobalType, expr *ConstantExpression, expectedType ValueType) (err error) {
+	var actualType ValueType
+	r := bytes.NewBuffer(expr.Data)
+	switch expr.Opcode {
+	case OpcodeI32Const:
+		_, _, err = leb128.DecodeInt32(r)
+		if err != nil {
+			return fmt.Errorf("read i32: %w", err)
+		}
+		actualType = ValueTypeI32
+	case OpcodeI64Const:
+		_, _, err = leb128.DecodeInt64(r)
+		if err != nil {
+			return fmt.Errorf("read i64: %w", err)
+		}
+		actualType = ValueTypeI64
+	case OpcodeF32Const:
+		_, err = ieee754.DecodeFloat32(r)
+		if err != nil {
+			return fmt.Errorf("read f32: %w", err)
+		}
+		actualType = ValueTypeF32
+	case OpcodeF64Const:
+		_, err = ieee754.DecodeFloat64(r)
+		if err != nil {
+			return fmt.Errorf("read f64: %w", err)
+		}
+		actualType = ValueTypeF64
+	case OpcodeGlobalGet:
+		id, _, err := leb128.DecodeUint32(r)
+		if err != nil {
+			return fmt.Errorf("read index of global: %w", err)
+		}
+		if uint32(len(globals)) <= id {
+			return fmt.Errorf("global index out of range")
+		}
+		actualType = globals[id].ValType
+	default:
+		return fmt.Errorf("invalid opcode for const expression: 0x%x", expr.Opcode)
+	}
+
+	if actualType != expectedType {
+		return fmt.Errorf("const expression type mismatch")
+	}
+	return nil
+}
+
+func (m *Module) buildGlobalInstances(importedGlobals []*GlobalInstance) (globals []*GlobalInstance) {
+	for _, gs := range m.GlobalSection {
+		var gv uint64
+		// Global's initialization constant expression can only reference the imported globals.
+		// See the note on https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#constant-expressions%E2%91%A0
+		switch v := executeConstExpression(importedGlobals, gs.Init).(type) {
+		case int32:
+			gv = uint64(v)
+		case int64:
+			gv = uint64(v)
+		case float32:
+			gv = publicwasm.EncodeF32(v)
+		case float64:
+			gv = publicwasm.EncodeF64(v)
+		}
+		globals = append(globals, &GlobalInstance{
+			Type: gs.Type,
+			Val:  gv,
+		})
+	}
+	return
+}
+
+func (m *Module) buildFunctionInstances() (functions []*FunctionInstance) {
+	var functionNames NameMap
+	if m.NameSection != nil {
+		functionNames = m.NameSection.FunctionNames
+	}
+
+	importCount := m.ImportFuncCount()
+	n, nLen := 0, len(functionNames)
+	for codeIndex := range m.FunctionSection {
+		funcIdx := Index(importCount + uint32(len(functions)))
+		// Seek to see if there's a better name than "unknown"
+		name := "unknown"
+		for ; n < nLen; n++ {
+			next := functionNames[n]
+			if next.Index > funcIdx {
+				break // we have function names, but starting at a later index
+			} else if next.Index == funcIdx {
+				name = next.Name
+				break
+			}
+		}
+
+		f := &FunctionInstance{
+			Name:         name,
+			FunctionKind: FunctionKindWasm,
+			Body:         m.CodeSection[codeIndex].Body,
+			LocalTypes:   m.CodeSection[codeIndex].LocalTypes,
+		}
+		functions = append(functions, f)
+	}
+	return
+}
+
+func (module *Module) buildMemoryInstance() (mem *MemoryInstance) {
+	for _, memSec := range module.MemorySection {
+		mem = &MemoryInstance{
+			Buffer: make([]byte, memoryPagesToBytesNum(memSec.Min)),
+			Min:    memSec.Min,
+			Max:    memSec.Max,
+		}
+	}
+	return
+}
+
+func (module *Module) buildTableInstance() (table *TableInstance) {
+	for _, tableSeg := range module.TableSection {
+		table = newTableInstance(tableSeg.Limit.Min, tableSeg.Limit.Max)
+	}
+	return
+}
+
 // Index is the offset in an index namespace, not necessarily an absolute position in a Module section. This is because
 // index namespaces are often preceded by a corresponding type in the Module.ImportSection.
 //
diff --git a/internal/wasm/store.go b/internal/wasm/store.go
index 4fc788e8..5af305f1 100644
--- a/internal/wasm/store.go
+++ b/internal/wasm/store.go
@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"context"
 	"fmt"
-	"io"
 	"math"
 	"reflect"
 
@@ -47,15 +46,32 @@ type (
 		// do type-checks on indirect function calls.
 		TypeIDs map[string]FunctionTypeID
 
-		// maximumFunctionAddress represents the limit on the number of function addresses (= function instances) in a store.
+		// 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.
-		maximumFunctionAddress FunctionAddress
+		maximumFunctionIndex FunctionIndex
 		//  maximumFunctionTypes represents the limit on the number of function types in a store.
 		// Note: this is fixed to 2^27 but have this a field for testability.
 		maximumFunctionTypes int
-		// maximumGlobals is the maximum number of globals that can be declared in a module.
-		// Note: this is fixed to 2^27 but have this a field for testability.
-		maximumGlobals int
+
+		// releasedFunctionIndex holds reusable FunctionIndexes. An index is added when
+		// an function instance is released in releaseFunctionInstances, and is popped when
+		// an new instance is added in store.addFunctionInstances.
+		releasedFunctionIndex []FunctionIndex
+
+		// releasedMemoryIndex holds reusable memoryIndexes. An index is added when
+		// an memory instance is released in releaseMemoryInstance, and is popped when
+		// an new instance is added in store.addMemoryInstance.
+		releasedMemoryIndex []memoryIndex
+
+		// releasedTableIndex holds reusable tableIndexes. An index is added when
+		// an table instance is released in releaseTableInstance, and is popped when
+		// an new instance is added in store.addTableInstance.
+		releasedTableIndex []tableIndex
+
+		// releasedGlobalIndex holds reusable globalIndexes. An index is added when
+		// an global instance is released in releaseGlobalInstances, and is popped when
+		// an new instance is added in store.addGlobalInstances.
+		releasedGlobalIndex []globalIndex
 
 		// The followings fields match the definition of Store in the specification.
 
@@ -90,8 +106,12 @@ type (
 		// 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
-		Tables         []*TableInstance
+		TableInstance  *TableInstance
 		Types          []*TypeInstance
+
+		// TODO
+		refCount      int
+		moduleImports map[*ModuleInstance]struct{}
 	}
 
 	// ExportInstance represents an exported instance in a Store.
@@ -124,12 +144,11 @@ type (
 		// This is nil when FunctionKind == FunctionKindWasm. Otherwise, all the above fields are ignored as they are
 		// specific to Wasm functions.
 		HostFunction *reflect.Value
-		// Address is the funcaddr(https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-funcaddr) of this function instance.
-		// More precisely, this equals the index of this function instance in store.FunctionInstances.
-		// All function calls are made via funcaddr at runtime, not the index (scoped to a module).
+		// Index is the index of this function instance in store.Functions, and is exported because
+		// all function calls are made via funcaddr at runtime, not the index (scoped to a module).
 		//
 		// This is used by both host and non-host functions.
-		Address FunctionAddress
+		Index FunctionIndex
 		// Name is for debugging purpose, and is used to argument the stack traces.
 		//
 		// When HostFunction is not nil, this returns dot-delimited parameters given to
@@ -152,7 +171,8 @@ type (
 	GlobalInstance struct {
 		Type *GlobalType
 		// Val holds a 64-bit representation of the actual value.
-		Val uint64
+		Val   uint64
+		index globalIndex
 	}
 
 	// TableInstance represents a table instance in a store.
@@ -172,18 +192,19 @@ type (
 		Max   *uint32
 		// Currently fixed to 0x70 (funcref type).
 		ElemType byte
+		index    tableIndex
 	}
 
 	// TableElement represents an item in a table instance.
 	//
 	// Note: this is fixed to function type as it is the only supported type in WebAssembly 1.0 (20191205)
 	TableElement struct {
-		// FunctionAddress is funcaddr (https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-funcaddr)
+		// FunctionIndex is funcaddr (https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-funcaddr)
 		// of the target function instance. More precisely, this equals the index of
 		// the target function instance in Store.FunctionInstances.
-		FunctionAddress FunctionAddress
+		FunctionIndex FunctionIndex
 		// FunctionTypeID is the type ID of the target function's type, which
-		// equals store.Functions[FunctionAddress].FunctionType.TypeID.
+		// equals store.Functions[FunctionIndex].FunctionType.TypeID.
 		FunctionTypeID FunctionTypeID
 	}
 
@@ -196,11 +217,24 @@ type (
 		Buffer []byte
 		Min    uint32
 		Max    *uint32
+		index  memoryIndex
 	}
 
-	// FunctionAddress is funcaddr (https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-funcaddr),
+	// FunctionIndex is funcaddr (https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-funcaddr),
 	// and the index to Store.Functions.
-	FunctionAddress uint64
+	FunctionIndex storeIndex
+	// memoryIndex is memaddr in the spec(https://www.w3.org/TR/wasm-core-1/#syntax-memaddr),
+	// and the index to Store.Functions.
+	memoryIndex storeIndex
+	// globalIndex is memaddr (https://www.w3.org/TR/wasm-core-1/#syntax-globaladdr),
+	// and the index to Store.Globals.
+	globalIndex storeIndex
+	// tableIndex is tableaddr (https://www.w3.org/TR/wasm-core-1/#syntax-tableaddr),
+	// and the index to Store.Tables.
+	tableIndex storeIndex
+
+	// storeIndex represents the offset in of an instance in a store.
+	storeIndex uint64
 
 	// FunctionTypeID is a uniquely assigned integer for a function type.
 	// This is wazero specific runtime object and specific to a store,
@@ -210,12 +244,122 @@ type (
 
 // The wazero specific limitations described at RATIONALE.md.
 const (
-	maximumFunctionAddress = 1 << 27
-	maximumFunctionTypes   = 1 << 27
-	maximumGlobals         = 1 << 27
-	maximumValuesOnStack   = 1 << 27
+	maximumFunctionIndex = 1 << 27
+	maximumFunctionTypes = 1 << 27
 )
 
+// newModuleInstance bundles all the instances for a module and creates a new module instance.
+func newModuleInstance(name string, module *Module, importedFunctions, functions []*FunctionInstance,
+	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.Functions = append(instance.Functions, importedFunctions...)
+	for i, f := range functions {
+		// Associate each function with the type instance and the module instance's pointer.
+		f.FunctionType = typeInstances[module.FunctionSection[i]]
+		f.ModuleInstance = instance
+		instance.Functions = append(instance.Functions, f)
+	}
+
+	instance.Globals = append(instance.Globals, importedGlobals...)
+	instance.Globals = append(instance.Globals, globals...)
+
+	if importedTable != nil {
+		instance.TableInstance = importedTable
+	} else {
+		instance.TableInstance = table
+	}
+
+	if importedMemory != nil {
+		instance.MemoryInstance = importedMemory
+	} else {
+		instance.MemoryInstance = memory
+	}
+
+	instance.buildExportInstances(module.ExportSection)
+	return instance
+}
+
+func (m *ModuleInstance) buildExportInstances(exports map[string]*Export) {
+	m.Exports = make(map[string]*ExportInstance, len(exports))
+	for _, exp := range exports {
+		index := exp.Index
+		var ei *ExportInstance
+		switch exp.Type {
+		case ExternTypeFunc:
+			ei = &ExportInstance{Type: exp.Type, Function: m.Functions[index]}
+			// The module instance of the host function is a fake that only includes the function and its types.
+			// We need to assign the ModuleInstance when re-exporting so that any memory defined in the target is
+			// available to the wasm.ModuleContext Memory.
+			if ei.Function.HostFunction != nil {
+				ei.Function.ModuleInstance = m
+			}
+		case ExternTypeGlobal:
+			ei = &ExportInstance{Type: exp.Type, Global: m.Globals[index]}
+		case ExternTypeMemory:
+			ei = &ExportInstance{Type: exp.Type, Memory: m.MemoryInstance}
+		case ExternTypeTable:
+			ei = &ExportInstance{Type: exp.Type, Table: m.TableInstance}
+		}
+
+		// We already validated the duplicates during module validation phase.
+		_ = m.addExport(exp.Name, ei)
+	}
+}
+
+func (m *ModuleInstance) validateData(data []*DataSegment) (err error) {
+	for _, d := range data {
+		offset := int(executeConstExpression(m.Globals, d.OffsetExpression).(int32))
+
+		ceil := offset + len(d.Init)
+		if offset < 0 || ceil > len(m.MemoryInstance.Buffer) {
+			return fmt.Errorf("out of bounds memory access")
+		}
+	}
+	return
+}
+
+func (m *ModuleInstance) applyData(data []*DataSegment) {
+	for _, d := range data {
+		offset := executeConstExpression(m.Globals, d.OffsetExpression).(int32)
+		copy(m.MemoryInstance.Buffer[offset:], d.Init)
+	}
+}
+
+func (m *ModuleInstance) validateElements(elements []*ElementSegment) (err error) {
+	for _, elem := range elements {
+		offset := int(executeConstExpression(m.Globals, elem.OffsetExpr).(int32))
+		ceil := offset + len(elem.Init)
+
+		if offset < 0 || ceil > len(m.TableInstance.Table) {
+			return fmt.Errorf("out of bounds table access")
+		}
+		for _, elm := range elem.Init {
+			if elm >= uint32(len(m.Functions)) {
+				return fmt.Errorf("unknown function specified by element")
+			}
+		}
+	}
+	return
+}
+
+func (m *ModuleInstance) applyElements(elements []*ElementSegment) {
+	for _, elem := range elements {
+		offset := int(executeConstExpression(m.Globals, elem.OffsetExpr).(int32))
+		table := m.TableInstance.Table
+		for i, elm := range elem.Init {
+			pos := i + offset
+			targetFunc := m.Functions[elm]
+			table[pos] = TableElement{
+				FunctionIndex:  targetFunc.Index,
+				FunctionTypeID: targetFunc.FunctionType.TypeID,
+			}
+		}
+	}
+}
+
 // addExport adds and indexes the given export or errs if the name is already exported.
 func (m *ModuleInstance) addExport(name string, e *ExportInstance) error {
 	if _, ok := m.Exports[name]; ok {
@@ -239,15 +383,22 @@ func (m *ModuleInstance) GetExport(name string, et ExternType) (*ExportInstance,
 
 func NewStore(ctx context.Context, engine Engine) *Store {
 	return &Store{
-		ctx:                    ctx,
-		ModuleInstances:        map[string]*ModuleInstance{},
-		ModuleContexts:         map[string]*ModuleContext{},
-		TypeIDs:                map[string]FunctionTypeID{},
-		Engine:                 engine,
-		maximumFunctionAddress: maximumFunctionAddress,
-		maximumFunctionTypes:   maximumFunctionTypes,
-		maximumGlobals:         maximumGlobals,
+		ctx:                  ctx,
+		ModuleInstances:      map[string]*ModuleInstance{},
+		ModuleContexts:       map[string]*ModuleContext{},
+		TypeIDs:              map[string]FunctionTypeID{},
+		Engine:               engine,
+		maximumFunctionIndex: maximumFunctionIndex,
+		maximumFunctionTypes: maximumFunctionTypes,
+	}
+}
+
+// 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 {
+		return fmt.Errorf("too many functions in a store")
 	}
+	return nil
 }
 
 func (s *Store) Instantiate(module *Module, name string) (*ModuleExports, error) {
@@ -255,92 +406,222 @@ func (s *Store) Instantiate(module *Module, name string) (*ModuleExports, error)
 		return nil, err
 	}
 
-	// Check the start function is valid.
-	// TODO: this should be verified during decode so that errors have the correct source positions
-	if module.StartSection != nil {
-		startIndex := *module.StartSection
-		ft := module.TypeOfFunction(startIndex)
-		if ft == nil { // TODO: move this check to that a module can never be decoded invalidly
-			return nil, fmt.Errorf("module[%s] start function has an invalid type", name)
-		}
-		if len(ft.Params) > 0 || len(ft.Results) > 0 {
-			return nil, fmt.Errorf("module[%s] start function must have an empty (nullary) signature: %s", name, ft.String())
-		}
-	}
-
-	instance := &ModuleInstance{Name: name}
-	for _, t := range module.TypeSection {
-		typeInstance, err := s.getTypeInstance(t)
-		if err != nil {
-			return nil, err
-		}
-		instance.Types = append(instance.Types, typeInstance)
+	if err := s.checkFunctionIndexOverflow(len(module.FunctionSection)); err != nil {
+		return nil, err
 	}
 
-	s.ModuleInstances[name] = instance
-	// Resolve the imports before doing the actual instantiation (mutating store).
-	if err := s.resolveImports(module, instance); err != nil {
-		return nil, fmt.Errorf("resolve imports: %w", err)
-	}
-	// Instantiation.
-	// Note that some of them mutate the store, so
-	// in the case of errors, we must rollback the state of store.
-	var rollbackFuncs []func()
-	defer func() {
-		for _, f := range rollbackFuncs {
-			f()
-		}
-	}()
-	rs, err := s.buildGlobalInstances(module, instance)
-	rollbackFuncs = append(rollbackFuncs, rs...)
+	importedFunctions, importedGlobals, importedTable, importedMemory, moduleImports, err := s.resolveImports(module)
 	if err != nil {
-		return nil, fmt.Errorf("globals: %w", err)
-	}
-	rs, err = s.buildFunctionInstances(module, instance)
-	rollbackFuncs = append(rollbackFuncs, rs...)
-	if err != nil {
-		return nil, fmt.Errorf("functions: %w", err)
+		return nil, err
 	}
-	rs, err = s.buildTableInstances(module, instance)
-	rollbackFuncs = append(rollbackFuncs, rs...)
+
+	functions, globals, table, memory :=
+		module.buildFunctionInstances(), module.buildGlobalInstances(importedGlobals), module.buildTableInstance(), module.buildMemoryInstance()
+
+	types, err := s.getTypeInstances(module.TypeSection)
 	if err != nil {
-		return nil, fmt.Errorf("tables: %w", err)
+		return nil, err
 	}
-	rs, err = s.buildMemoryInstances(module, instance)
-	rollbackFuncs = append(rollbackFuncs, rs...)
-	if err != nil {
-		return nil, fmt.Errorf("memories: %w", err)
+
+	// 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)
+
+	if err = instance.validateElements(module.ElementSection); err != nil {
+		return nil, err
 	}
-	rs, err = s.buildExportInstances(module, instance)
-	rollbackFuncs = append(rollbackFuncs, rs...)
-	if err != nil {
-		return nil, fmt.Errorf("exports: %w", err)
+
+	if err := instance.validateData(module.DataSection); err != nil {
+		return nil, err
 	}
 
-	for i, f := range instance.Functions {
+	// 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 {
+			// On the failure, release the assigned funcaddr and already compiled functions.
+			if err := s.releaseFunctionInstances(functions...); err != nil {
+				return nil, err
+			}
 			idx := module.SectionElementCount(SectionIDFunction) - 1
-			return nil, fmt.Errorf("compilation failed at index %d/%d: %v", i, idx, err)
+			return nil, fmt.Errorf("compilation failed at index %d/%d: %w", i, idx, err)
 		}
 	}
 
-	// Build the default context for calls to this module
-	modCtx := NewModuleContext(s.ctx, s.Engine, instance)
-	s.ModuleContexts[name] = modCtx
+	// Now all the validation passes, we are safe to mutate memory/table instances (possibly imported ones).
+	instance.applyElements(module.ElementSection)
+	instance.applyData(module.DataSection)
+
+	// Persist the instances other than functions (which we already persisted before compilation).
+	s.addGlobalInstances(globals...)
+	s.addTableInstance(table)
+	s.addMemoryInstance(instance.MemoryInstance)
 
-	// Now we are safe to finalize the state.
-	rollbackFuncs = nil
+	// Increase the reference count of imported modules.
+	for imported := range instance.moduleImports {
+		imported.refCount++
+	}
+
+	// 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
 
 	// 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(modCtx, instance.Functions[funcIdx]); err != nil {
 			return nil, fmt.Errorf("module[%s] start function failed: %w", name, err)
 		}
 	}
 	return &ModuleExports{s, modCtx}, nil
 }
 
+func (s *Store) ReleaseModuleInstance(instance *ModuleInstance) error {
+	instance.refCount--
+	if instance.refCount > 0 {
+		// This case other modules are importing this module instance and still alive.
+		return nil
+	}
+
+	// TODO: check outstanding calls and wait until they exit.
+
+	// Recursively release the imported instances.
+	for mod := range instance.moduleImports {
+		if err := s.ReleaseModuleInstance(mod); err != nil {
+			return fmt.Errorf("unable to release imported module [%s]: %w", mod.Name, err)
+		}
+	}
+
+	if err := s.releaseFunctionInstances(instance.Functions...); err != nil {
+		return fmt.Errorf("unable to release function instance: %w", err)
+	}
+	s.releaseMemoryInstance(instance.MemoryInstance)
+	s.releaseTableInstance(instance.TableInstance)
+	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.ModuleContexts[instance.Name] = nil
+	s.ModuleInstances[instance.Name] = nil
+	return nil
+}
+
+func (s *Store) releaseFunctionInstances(fs ...*FunctionInstance) error {
+	for _, f := range fs {
+		if err := s.Engine.Release(f); err != nil {
+			return err
+		}
+
+		// Release refernce to the function instance.
+		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)
+	}
+	return nil
+}
+
+func (s *Store) addFunctionInstances(fs ...*FunctionInstance) {
+	for _, f := range fs {
+		var addr 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
+		} else {
+			addr = FunctionIndex(len(s.Functions))
+			s.Functions = append(s.Functions, f)
+		}
+		f.Index = addr
+	}
+}
+
+func (s *Store) releaseGlobalInstances(gs ...*GlobalInstance) {
+	for _, g := range gs {
+		// Release refernce 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)
+	}
+}
+
+func (s *Store) addGlobalInstances(gs ...*GlobalInstance) {
+	for _, g := range gs {
+		var addr 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
+		} else {
+			addr = globalIndex(len(s.Globals))
+			s.Globals = append(s.Globals, g)
+		}
+		g.index = addr
+	}
+}
+
+func (s *Store) releaseTableInstance(t *TableInstance) {
+	// Release refernce 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)
+}
+
+func (s *Store) addTableInstance(t *TableInstance) {
+	if t == nil {
+		return
+	}
+
+	var addr 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
+	} else {
+		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
+
+	// Append the index so that we can reuse it in order to avoid index space explosion.
+	s.releasedMemoryIndex = append(s.releasedMemoryIndex, m.index)
+}
+
+func (s *Store) addMemoryInstance(m *MemoryInstance) {
+	if m == nil {
+		return
+	}
+
+	var addr 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
+	} else {
+		addr = memoryIndex(len(s.Memories))
+		s.Memories = append(s.Memories, m)
+	}
+	m.index = addr
+}
+
 // ModuleExports implements wasm.Store ModuleExports
 func (s *Store) ModuleExports(moduleName string) publicwasm.ModuleExports {
 	if m, ok := s.ModuleContexts[moduleName]; !ok {
@@ -389,493 +670,124 @@ func (s *Store) getExport(moduleName string, name string, et ExternType) (exp *E
 	return
 }
 
-func (s *Store) addFunctionInstance(f *FunctionInstance) error {
-	if f.Address >= s.maximumFunctionAddress {
-		return fmt.Errorf("too many functions in a store")
-	}
-	s.Functions = append(s.Functions, f)
-	return nil
-}
-
-func (s *Store) nextFunctionAddress() FunctionAddress {
-	return FunctionAddress(len(s.Functions))
-}
-
-func (s *Store) resolveImports(module *Module, target *ModuleInstance) error {
+func (s *Store) resolveImports(module *Module) (
+	functions []*FunctionInstance, globals []*GlobalInstance,
+	table *TableInstance, memory *MemoryInstance,
+	moduleImports map[*ModuleInstance]struct{},
+	err error,
+) {
+	moduleImports = map[*ModuleInstance]struct{}{}
 	for _, is := range module.ImportSection {
-		if err := s.resolveImport(target, is); err != nil {
-			return fmt.Errorf("%s: %w", is.Name, err)
+		m, ok := s.ModuleInstances[is.Module]
+		if !ok {
+			err = fmt.Errorf("module \"%s\" not instantiated", is.Module)
+			return
 		}
-	}
-	return nil
-}
 
-func (s *Store) resolveImport(target *ModuleInstance, is *Import) error {
-	exp, err := s.getExport(is.Module, is.Name, is.Type)
-	if err != nil {
-		return err
-	}
+		// Note: at this point we don't increase the ref count.
+		moduleImports[m] = struct{}{}
 
-	switch is.Type {
-	case ExternTypeFunc:
-		if err = s.applyFunctionImport(target, is.DescFunc, exp); err != nil {
-			return fmt.Errorf("applyFunctionImport: %w", err)
-		}
-	case ExternTypeTable:
-		if err = s.applyTableImport(target, is.DescTable, exp); err != nil {
-			return fmt.Errorf("applyTableImport: %w", err)
-		}
-	case ExternTypeMemory:
-		if err = s.applyMemoryImport(target, is.DescMem, exp); err != nil {
-			return fmt.Errorf("applyMemoryImport: %w", err)
-		}
-	case ExternTypeGlobal:
-		if err = s.applyGlobalImport(target, is.DescGlobal, exp); err != nil {
-			return fmt.Errorf("applyGlobalImport: %w", err)
+		var exp *ExportInstance
+		exp, err = m.GetExport(is.Name, is.Type)
+		if err != nil {
+			return
 		}
-	default:
-		return fmt.Errorf("invalid externtype: %s", ExternTypeName(is.Type))
-	}
-
-	return nil
-}
 
-func (s *Store) applyFunctionImport(target *ModuleInstance, typeIndex Index, externModuleExportInstance *ExportInstance) error {
-	f := externModuleExportInstance.Function
-	if int(typeIndex) >= len(target.Types) {
-		return fmt.Errorf("unknown type for function import")
-	}
-	expectedType := target.Types[typeIndex].Type
-	if !bytes.Equal(expectedType.Results, f.FunctionType.Type.Results) {
-		return fmt.Errorf("return signature mimatch: %#x != %#x", expectedType.Results, f.FunctionType.Type.Results)
-	} else if !bytes.Equal(expectedType.Params, f.FunctionType.Type.Params) {
-		return fmt.Errorf("input signature mimatch: %#x != %#x", expectedType.Params, f.FunctionType.Type.Params)
-	}
-	target.Functions = append(target.Functions, f)
-	return nil
-}
-
-func (s *Store) applyTableImport(target *ModuleInstance, tableTypePtr *TableType, externModuleExportInstance *ExportInstance) error {
-	table := externModuleExportInstance.Table
-	if tableTypePtr == nil {
-		return fmt.Errorf("table type is invalid")
-	}
-	if table.ElemType != tableTypePtr.ElemType {
-		return fmt.Errorf("incompatible table imports: element type mismatch")
-	}
-	if table.Min < tableTypePtr.Limit.Min {
-		return fmt.Errorf("incompatible table imports: minimum size mismatch")
-	}
-
-	if tableTypePtr.Limit.Max != nil {
-		if table.Max == nil {
-			return fmt.Errorf("incompatible table imports: maximum size mismatch")
-		} else if *table.Max > *tableTypePtr.Limit.Max {
-			return fmt.Errorf("incompatible table imports: maximum size mismatch")
-		}
-	}
-	target.Tables = append(target.Tables, table)
-	return nil
-}
+		switch is.Type {
+		case ExternTypeFunc:
+			typeIndex := is.DescFunc
+			if int(typeIndex) >= len(module.TypeSection) {
+				err = fmt.Errorf("unknown type for function import")
+				return
+			}
+			expectedType := module.TypeSection[typeIndex]
+			f := exp.Function
+			if !bytes.Equal(expectedType.Results, f.FunctionType.Type.Results) || !bytes.Equal(expectedType.Params, f.FunctionType.Type.Params) {
+				err = fmt.Errorf("signature mimatch: %s != %s", expectedType, f.FunctionType.Type)
+				return
+			}
+			functions = append(functions, f)
+		case ExternTypeTable:
+			tableType := is.DescTable
+			table = exp.Table
+			if table.ElemType != tableType.ElemType {
+				err = fmt.Errorf("incompatible table improt: element type mismatch")
+				return
+			}
+			if table.Min < tableType.Limit.Min {
+				err = fmt.Errorf("incompatible table import: minimum size mismatch")
+				return
+			}
 
-func (s *Store) applyMemoryImport(target *ModuleInstance, memoryTypePtr *MemoryType, externModuleExportInstance *ExportInstance) error {
-	if target.MemoryInstance != nil {
-		// The current Wasm spec doesn't allow multiple memories.
-		return fmt.Errorf("multiple memories are not supported")
-	} else if memoryTypePtr == nil {
-		return fmt.Errorf("memory type is invalid")
-	}
-	memory := externModuleExportInstance.Memory
-	if memory.Min < memoryTypePtr.Min {
-		return fmt.Errorf("incompatible memory imports: minimum size mismatch")
-	}
-	if memoryTypePtr.Max != nil {
-		if memory.Max == nil {
-			return fmt.Errorf("incompatible memory imports: maximum size mismatch")
-		} else if *memory.Max > *memoryTypePtr.Max {
-			return fmt.Errorf("incompatible memory imports: maximum size mismatch")
+			if tableType.Limit.Max != nil {
+				if table.Max == nil {
+					err = fmt.Errorf("incompatible table import: maximum size mismatch")
+					return
+				} else if *table.Max > *tableType.Limit.Max {
+					err = fmt.Errorf("incompatible table import: maximum size mismatch")
+					return
+				}
+			}
+		case ExternTypeMemory:
+			memoryType := is.DescMem
+			memory = exp.Memory
+			if memory.Min < memoryType.Min {
+				err = fmt.Errorf("incompatible memory import: minimum size mismatch")
+				return
+			}
+			if memoryType.Max != nil {
+				if memory.Max == nil {
+					err = fmt.Errorf("incompatible memory import: maximum size mismatch")
+					return
+				} else if *memory.Max > *memoryType.Max {
+					err = fmt.Errorf("incompatible memory import: maximum size mismatch")
+					return
+				}
+			}
+		case ExternTypeGlobal:
+			globalType := is.DescGlobal
+			g := exp.Global
+			if globalType.Mutable != g.Type.Mutable {
+				err = fmt.Errorf("incompatible global import: mutability mismatch")
+				return
+			} else if globalType.ValType != g.Type.ValType {
+				err = fmt.Errorf("incompatible global import: value type mismatch")
+				return
+			}
+			globals = append(globals, g)
 		}
 	}
-	target.MemoryInstance = memory
-	return nil
-}
-
-func (s *Store) applyGlobalImport(target *ModuleInstance, globalTypePtr *GlobalType, externModuleExportInstance *ExportInstance) error {
-	if globalTypePtr == nil {
-		return fmt.Errorf("global type is invalid")
-	}
-	g := externModuleExportInstance.Global
-	if globalTypePtr.Mutable != g.Type.Mutable {
-		return fmt.Errorf("incompatible global import: mutability mismatch")
-	} else if globalTypePtr.ValType != g.Type.ValType {
-		return fmt.Errorf("incompatible global import: value type mismatch")
-	}
-	target.Globals = append(target.Globals, g)
-	return nil
+	return
 }
 
-func executeConstExpression(globals []*GlobalInstance, expr *ConstantExpression) (v interface{}, valueType ValueType, err error) {
+func executeConstExpression(globals []*GlobalInstance, expr *ConstantExpression) (v interface{}) {
 	r := bytes.NewBuffer(expr.Data)
 	switch expr.Opcode {
 	case OpcodeI32Const:
-		v, _, err = leb128.DecodeInt32(r)
-		if err != nil {
-			return nil, 0, fmt.Errorf("read i32: %w", err)
-		}
-		return v, ValueTypeI32, nil
+		v, _, _ = leb128.DecodeInt32(r)
 	case OpcodeI64Const:
-		v, _, err = leb128.DecodeInt64(r)
-		if err != nil {
-			return nil, 0, fmt.Errorf("read i64: %w", err)
-		}
-		return v, ValueTypeI64, nil
+		v, _, _ = leb128.DecodeInt64(r)
 	case OpcodeF32Const:
-		v, err = ieee754.DecodeFloat32(r)
-		if err != nil {
-			return nil, 0, fmt.Errorf("read f32: %w", err)
-		}
-		return v, ValueTypeF32, nil
+		v, _ = ieee754.DecodeFloat32(r)
 	case OpcodeF64Const:
-		v, err = ieee754.DecodeFloat64(r)
-		if err != nil {
-			return nil, 0, fmt.Errorf("read f64: %w", err)
-		}
-		return v, ValueTypeF64, nil
+		v, _ = ieee754.DecodeFloat64(r)
 	case OpcodeGlobalGet:
-		id, _, err := leb128.DecodeUint32(r)
-		if err != nil {
-			return nil, 0, fmt.Errorf("read index of global: %w", err)
-		}
-		if uint32(len(globals)) <= id {
-			return nil, 0, fmt.Errorf("global index out of range")
-		}
+		id, _, _ := leb128.DecodeUint32(r)
 		g := globals[id]
 		switch g.Type.ValType {
 		case ValueTypeI32:
 			v = int32(g.Val)
-			return v, ValueTypeI32, nil
 		case ValueTypeI64:
 			v = int64(g.Val)
-			return v, ValueTypeI64, nil
 		case ValueTypeF32:
 			v = publicwasm.DecodeF32(g.Val)
-			return v, ValueTypeF32, nil
 		case ValueTypeF64:
 			v = publicwasm.DecodeF64(g.Val)
-			return v, ValueTypeF64, nil
-		}
-	}
-	return nil, 0, fmt.Errorf("invalid opt code: %#x", expr.Opcode)
-}
-
-func (s *Store) buildGlobalInstances(module *Module, target *ModuleInstance) (rollbackFuncs []func(), err error) {
-	prevLen := len(s.Globals)
-	rollbackFuncs = append(rollbackFuncs, func() {
-		s.Globals = s.Globals[:prevLen]
-	})
-
-	// We limit the number of globals in a moudle to 2^27.
-	globalDecls := len(module.GlobalSection)
-	for _, imp := range module.ImportSection {
-		if imp.Type == ExternTypeGlobal {
-			globalDecls++
-		}
-	}
-	if globalDecls > s.maximumGlobals {
-		return rollbackFuncs, fmt.Errorf("too many globals in a module")
-	}
-
-	for _, gs := range module.GlobalSection {
-		raw, t, err := executeConstExpression(target.Globals, gs.Init)
-		if err != nil {
-			return rollbackFuncs, fmt.Errorf("execution failed: %w", err)
-		}
-		if gs.Type.ValType != t {
-			return rollbackFuncs, fmt.Errorf("global type mismatch")
-		}
-		var gv uint64
-		switch v := raw.(type) {
-		case int32:
-			gv = uint64(v)
-		case int64:
-			gv = uint64(v)
-		case float32:
-			gv = publicwasm.EncodeF32(v)
-		case float64:
-			gv = publicwasm.EncodeF64(v)
-		}
-		g := &GlobalInstance{
-			Type: gs.Type,
-			Val:  gv,
-		}
-		target.Globals = append(target.Globals, g)
-		s.Globals = append(s.Globals, g)
-	}
-	return rollbackFuncs, nil
-}
-
-func (s *Store) buildFunctionInstances(module *Module, target *ModuleInstance) (rollbackFuncs []func(), err error) {
-	prevLen := len(s.Functions)
-	rollbackFuncs = append(rollbackFuncs, func() {
-		s.Functions = s.Functions[:prevLen]
-	})
-
-	funcs, globals, mems, tables := module.allDeclarations()
-
-	var functionNames NameMap
-	if module.NameSection != nil {
-		functionNames = module.NameSection.FunctionNames
-	}
-
-	n, nLen := 0, len(functionNames)
-
-	for codeIndex, typeIndex := range module.FunctionSection {
-		if typeIndex >= module.SectionElementCount(SectionIDType) {
-			return rollbackFuncs, fmt.Errorf("function type index out of range")
-		} else if uint32(codeIndex) >= module.SectionElementCount(SectionIDCode) {
-			return rollbackFuncs, fmt.Errorf("code index out of range")
-		}
-
-		funcIdx := Index(len(target.Functions))
-		// Seek to see if there's a better name than "unknown"
-		name := "unknown"
-		for ; n < nLen; n++ {
-			next := functionNames[n]
-			if next.Index > funcIdx {
-				break // we have function names, but starting at a later index
-			} else if next.Index == funcIdx {
-				name = next.Name
-				break
-			}
-		}
-
-		typeInstance, err := s.getTypeInstance(module.TypeSection[typeIndex])
-		if err != nil {
-			return rollbackFuncs, err
-		}
-
-		f := &FunctionInstance{
-			Name:           name,
-			FunctionKind:   FunctionKindWasm,
-			FunctionType:   typeInstance,
-			Body:           module.CodeSection[codeIndex].Body,
-			LocalTypes:     module.CodeSection[codeIndex].LocalTypes,
-			ModuleInstance: target,
-			Address:        s.nextFunctionAddress(),
-		}
-
-		if err := validateFunctionInstance(f, funcs, globals, mems, tables, module.TypeSection, maximumValuesOnStack); err != nil {
-			idx := module.SectionElementCount(SectionIDFunction) - 1
-			return rollbackFuncs, fmt.Errorf("invalid function '%s' (%d/%d): %v", f.Name, codeIndex, idx, err)
-		}
-
-		err = s.addFunctionInstance(f)
-		if err != nil {
-			return rollbackFuncs, err
-		}
-		target.Functions = append(target.Functions, f)
-	}
-	return rollbackFuncs, nil
-}
-
-func (s *Store) buildMemoryInstances(module *Module, target *ModuleInstance) (rollbackFuncs []func(), err error) {
-	// Allocate memory instances.
-	for _, memSec := range module.MemorySection {
-		if target.MemoryInstance != nil {
-			// This case the memory instance is already imported,
-			// and the current Wasm spec doesn't allow multiple memories.
-			return rollbackFuncs, fmt.Errorf("multiple memories not supported")
-		}
-		target.MemoryInstance = &MemoryInstance{
-			Buffer: make([]byte, memoryPagesToBytesNum(memSec.Min)),
-			Min:    memSec.Min,
-			Max:    memSec.Max,
-		}
-		s.Memories = append(s.Memories, target.MemoryInstance)
-	}
-
-	// Initialize the memory instance according to the Data section.
-	for _, d := range module.DataSection {
-		if target.MemoryInstance == nil {
-			return rollbackFuncs, fmt.Errorf("unknown memory")
-		} else if d.MemoryIndex != 0 {
-			return rollbackFuncs, fmt.Errorf("memory index must be zero")
-		}
-
-		rawOffset, offsetType, err := executeConstExpression(target.Globals, d.OffsetExpression)
-		if err != nil {
-			return rollbackFuncs, fmt.Errorf("calculate offset: %w", err)
-		} else if offsetType != ValueTypeI32 {
-			return rollbackFuncs, fmt.Errorf("offset is not int32 but %T", offsetType)
-		}
-
-		offset, ok := rawOffset.(int32)
-		if !ok {
-			return rollbackFuncs, fmt.Errorf("offset is not int32 but 0x%x", offsetType)
-		} else if offset < 0 {
-			return rollbackFuncs, fmt.Errorf("offset must be positive int32: %d", offset)
-		}
-
-		size := uint64(offset) + uint64(len(d.Init))
-		maxPage := MemoryMaxPages
-		if d.MemoryIndex < module.SectionElementCount(SectionIDMemory) && module.MemorySection[d.MemoryIndex].Max != nil {
-			maxPage = *module.MemorySection[d.MemoryIndex].Max
-		}
-		if size > memoryPagesToBytesNum(maxPage) {
-			return rollbackFuncs, fmt.Errorf("memory size out of limit %d * 64Ki", int(*(module.MemorySection[d.MemoryIndex].Max)))
-		}
-
-		memoryInst := target.MemoryInstance
-		if size > uint64(len(memoryInst.Buffer)) {
-			return rollbackFuncs, fmt.Errorf("out of bounds memory access")
-		}
-		// Setup the rollback function before mutating the acutal memory.
-		original := make([]byte, len(d.Init))
-		copy(original, memoryInst.Buffer[offset:])
-		rollbackFuncs = append(rollbackFuncs, func() {
-			copy(memoryInst.Buffer[offset:], original)
-		})
-		copy(memoryInst.Buffer[offset:], d.Init)
-	}
-	return rollbackFuncs, nil
-}
-
-func (s *Store) buildTableInstances(module *Module, target *ModuleInstance) (rollbackFuncs []func(), err error) {
-	// Allocate table instances.
-	for _, tableSeg := range module.TableSection {
-		instance := newTableInstance(tableSeg.Limit.Min, tableSeg.Limit.Max)
-		target.Tables = append(target.Tables, instance)
-		s.Tables = append(s.Tables, instance)
-	}
-
-	for _, elem := range module.ElementSection {
-		if elem.TableIndex >= Index(len(target.Tables)) {
-			return rollbackFuncs, fmt.Errorf("index out of range of index space")
-		}
-
-		rawOffset, offsetType, err := executeConstExpression(target.Globals, elem.OffsetExpr)
-		if err != nil {
-			return rollbackFuncs, fmt.Errorf("calculate offset: %w", err)
-		} else if offsetType != ValueTypeI32 {
-			return rollbackFuncs, fmt.Errorf("offset is not int32 but %T", offsetType)
-		}
-
-		offset32, ok := rawOffset.(int32)
-		if !ok {
-			return rollbackFuncs, fmt.Errorf("offset is not int32 but %T", offsetType)
-		} else if offset32 < 0 {
-			return rollbackFuncs, fmt.Errorf("offset must be positive int32 but %d", offset32)
-		}
-
-		offset := int(offset32)
-		size := offset + len(elem.Init)
-
-		max := uint32(math.MaxUint32)
-		if elem.TableIndex < module.SectionElementCount(SectionIDTable) && module.TableSection[elem.TableIndex].Limit.Max != nil {
-			max = *module.TableSection[elem.TableIndex].Limit.Max
-		}
-
-		if size > int(max) {
-			return rollbackFuncs, fmt.Errorf("table size out of limit of %d", max)
-		}
-
-		tableInst := target.Tables[elem.TableIndex]
-		if size > len(tableInst.Table) {
-			return rollbackFuncs, fmt.Errorf("out of bounds table access %d > %v", size, tableInst.Min)
-		}
-		for i := range elem.Init {
-			i := i
-			elm := elem.Init[i]
-			if elm >= uint32(len(target.Functions)) {
-				return rollbackFuncs, fmt.Errorf("unknown function specified by element")
-			}
-			// Setup the rollback function before mutating the table instance.
-			pos := i + offset
-			original := tableInst.Table[pos]
-			rollbackFuncs = append(rollbackFuncs, func() {
-				tableInst.Table[pos] = original
-			})
-			targetFunc := target.Functions[elm]
-			tableInst.Table[pos] = TableElement{
-				FunctionAddress: targetFunc.Address,
-				FunctionTypeID:  targetFunc.FunctionType.TypeID,
-			}
-		}
-	}
-	if len(target.Tables) > 1 {
-		return rollbackFuncs, fmt.Errorf("multiple tables not supported")
-	}
-	return rollbackFuncs, nil
-}
-
-func (s *Store) buildExportInstances(module *Module, target *ModuleInstance) (rollbackFuncs []func(), err error) {
-	target.Exports = make(map[string]*ExportInstance, module.SectionElementCount(SectionIDExport))
-	for name, exp := range module.ExportSection {
-		index := exp.Index
-		var ei *ExportInstance
-		switch exp.Type {
-		case ExternTypeFunc:
-			if index >= uint32(len(target.Functions)) {
-				return nil, fmt.Errorf("unknown function for export[%s]", name)
-			}
-			ei = &ExportInstance{Type: exp.Type, Function: target.Functions[index]}
-			// The module instance of the host function is a fake that only includes the function and its types.
-			// We need to assign the ModuleInstance when re-exporting so that any memory defined in the target is
-			// available to the wasm.ModuleContext Memory.
-			if ei.Function.HostFunction != nil {
-				ei.Function.ModuleInstance = target
-			}
-		case ExternTypeGlobal:
-			if index >= uint32(len(target.Globals)) {
-				return nil, fmt.Errorf("unknown global for export[%s]", name)
-			}
-			ei = &ExportInstance{Type: exp.Type, Global: target.Globals[index]}
-		case ExternTypeMemory:
-			if index != 0 || target.MemoryInstance == nil {
-				return nil, fmt.Errorf("unknown memory for export[%s]", name)
-			}
-			ei = &ExportInstance{Type: exp.Type, Memory: target.MemoryInstance}
-		case ExternTypeTable:
-			if index >= uint32(len(target.Tables)) {
-				return nil, fmt.Errorf("unknown table for export[%s]", name)
-			}
-			ei = &ExportInstance{Type: exp.Type, Table: target.Tables[index]}
-		}
-		if err = target.addExport(exp.Name, ei); err != nil {
-			return nil, err
 		}
 	}
 	return
 }
 
-// DecodeBlockType is exported for use in the compiler
-func DecodeBlockType(types []*TypeInstance, r io.Reader) (*FunctionType, uint64, error) {
-	raw, num, err := leb128.DecodeInt33AsInt64(r)
-	if err != nil {
-		return nil, 0, fmt.Errorf("decode int33: %w", err)
-	}
-
-	var ret *FunctionType
-	switch raw {
-	case -64: // 0x40 in original byte = nil
-		ret = &FunctionType{}
-	case -1: // 0x7f in original byte = i32
-		ret = &FunctionType{Results: []ValueType{ValueTypeI32}}
-	case -2: // 0x7e in original byte = i64
-		ret = &FunctionType{Results: []ValueType{ValueTypeI64}}
-	case -3: // 0x7d in original byte = f32
-		ret = &FunctionType{Results: []ValueType{ValueTypeF32}}
-	case -4: // 0x7c in original byte = f64
-		ret = &FunctionType{Results: []ValueType{ValueTypeF64}}
-	default:
-		if raw < 0 || (raw >= int64(len(types))) {
-			return nil, 0, fmt.Errorf("invalid block type: %d", raw)
-		}
-		ret = types[raw].Type
-	}
-	return ret, num, nil
-}
-
 // 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.
 //
@@ -892,21 +804,27 @@ func (s *Store) AddHostFunction(m *ModuleInstance, hf *GoFunc) (*FunctionInstanc
 		FunctionKind:   hf.functionKind,
 		FunctionType:   typeInstance,
 		ModuleInstance: m,
-		Address:        s.nextFunctionAddress(),
 	}
 
+	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 = s.addFunctionInstance(f); err != nil {
-		return nil, err
-	}
-	// TODO: This races on adding export. A future design may be able to eliminate this race, possibly via a HostModule
-	// type.
+
 	if err = m.addExport(hf.wasmFunctionName, &ExportInstance{Type: ExternTypeFunc, Function: f}); err != nil {
-		s.Functions = s.Functions[:len(s.Functions)-1] // lost race: revert the add on conflict
+		// 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
 }
 
@@ -915,14 +833,19 @@ func (s *Store) AddGlobal(m *ModuleInstance, name string, value uint64, valueTyp
 		Val:  value,
 		Type: &GlobalType{Mutable: mutable, ValType: valueType},
 	}
-	s.Globals = append(s.Globals, g)
+
+	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)
-	s.Tables = append(s.Tables, t)
+
+	// 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})
 }
@@ -933,12 +856,28 @@ func (s *Store) AddMemoryInstance(m *ModuleInstance, name string, min uint32, ma
 		Min:    min,
 		Max:    max,
 	}
-	s.Memories = append(s.Memories, memory)
+
+	// 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) getTypeInstances(ts []*FunctionType) ([]*TypeInstance, error) {
+	ret := make([]*TypeInstance, len(ts))
+	for i, t := range ts {
+		inst, err := s.getTypeInstance(t)
+		if err != nil {
+			return nil, err
+		}
+		ret[i] = inst
+	}
+	return ret, nil
+}
+
 func (s *Store) getTypeInstance(t *FunctionType) (*TypeInstance, error) {
+	// TODO: take mutex
 	key := t.String()
 	id, ok := s.TypeIDs[key]
 	if !ok {
@@ -967,5 +906,5 @@ func newTableInstance(min uint32, max *uint32) *TableInstance {
 	return tableInst
 }
 
-// UninitializedTableElementTypeID math.MaxUint64 to represent the uninitialized elements.
-var UninitializedTableElementTypeID FunctionTypeID = math.MaxUint32
+// UninitializedTableElementTypeID math.MaxUint32 to represent the uninitialized elements.
+const UninitializedTableElementTypeID FunctionTypeID = math.MaxUint32
diff --git a/wasm.go b/wasm.go
index f7e2c5da..515fd0de 100644
--- a/wasm.go
+++ b/wasm.go
@@ -74,8 +74,14 @@ type ModuleConfig struct {
 //
 // This is used to pre-flight check and cache the module for later instantiation.
 func (m *ModuleConfig) Validate() (err error) {
-	_, err = decodeModule(m)
-	return err
+	mod, err := decodeModule(m)
+	if err != nil {
+		return
+	}
+	// TODO: decoders should validate before returning, as that allows
+	// them to err with the correct source position.
+	err = mod.Validate()
+	return
 }
 
 // WithName returns a new instance which overrides the Name, but keeps any internal cache made by Validate.
@@ -103,6 +109,10 @@ func InstantiateModule(store wasm.Store, module *ModuleConfig) (wasm.ModuleExpor
 	if err != nil {
 		return nil, err
 	}
+
+	if err = m.Validate(); err != nil {
+		return nil, err
+	}
 	return internal.Instantiate(m, getModuleName(module.Name, m))
 }
 


Test patch:
diff --git a/internal/wasm/func_validation_test.go b/internal/wasm/func_validation_test.go
index 5187800f..5160d1d4 100644
--- a/internal/wasm/func_validation_test.go
+++ b/internal/wasm/func_validation_test.go
@@ -12,25 +12,25 @@ func TestValidateFunction_valueStackLimit(t *testing.T) {
 	const valuesNum = max + 1
 
 	// Build a function which has max+1 const instructions.
-	f := &FunctionInstance{Body: []byte{}, FunctionType: &TypeInstance{Type: &FunctionType{}}}
+	var body []byte
 	for i := 0; i < valuesNum; i++ {
-		f.Body = append(f.Body, OpcodeI32Const, 1)
+		body = append(body, OpcodeI32Const, 1)
 	}
 
 	// Drop all the consts so that if the max is higher, this function body would be sound.
 	for i := 0; i < valuesNum; i++ {
-		f.Body = append(f.Body, OpcodeDrop)
+		body = append(body, OpcodeDrop)
 	}
 
 	// Plus all functions must end with End opcode.
-	f.Body = append(f.Body, OpcodeEnd)
+	body = append(body, OpcodeEnd)
 
 	t.Run("not exceed", func(t *testing.T) {
-		err := validateFunctionInstance(f, nil, nil, nil, nil, nil, max+1)
+		err := validateFunction(&FunctionType{}, body, nil, nil, nil, nil, nil, nil, max+1)
 		require.NoError(t, err)
 	})
 	t.Run("exceed", func(t *testing.T) {
-		err := validateFunctionInstance(f, nil, nil, nil, nil, nil, max)
+		err := validateFunction(&FunctionType{}, body, nil, nil, nil, nil, nil, nil, max)
 		require.Error(t, err)
 		expMsg := fmt.Sprintf("function may have %d stack values, which exceeds limit %d", valuesNum, max)
 		require.Equal(t, expMsg, err.Error())
diff --git a/internal/wasm/jit/engine_test.go b/internal/wasm/jit/engine_test.go
index 7eed550a..26d125fa 100644
--- a/internal/wasm/jit/engine_test.go
+++ b/internal/wasm/jit/engine_test.go
@@ -58,14 +58,14 @@ func TestVerifyOffsetValue(t *testing.T) {
 
 	// Offsets for wasm.TableElement.
 	var tableElement wasm.TableElement
-	require.Equal(t, int(unsafe.Offsetof(tableElement.FunctionAddress)), tableElementFunctionAddressOffset)
+	require.Equal(t, int(unsafe.Offsetof(tableElement.FunctionIndex)), tableElementFunctionIndexOffset)
 	require.Equal(t, int(unsafe.Offsetof(tableElement.FunctionTypeID)), tableElementFunctionTypeIDOffset)
 
 	// 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.Tables)), moduleInstanceTablesOffset)
+	require.Equal(t, int(unsafe.Offsetof(moduleInstance.TableInstance)), moduleInstanceTableOffset)
 
 	// Offsets for wasm.TableInstance.
 	var tableInstance wasm.TableInstance
diff --git a/internal/wasm/jit/jit_amd64_test.go b/internal/wasm/jit/jit_amd64_test.go
index c236be80..e5180775 100644
--- a/internal/wasm/jit/jit_amd64_test.go
+++ b/internal/wasm/jit/jit_amd64_test.go
@@ -164,7 +164,7 @@ func TestAmd64Compiler_returnFunction(t *testing.T) {
 		// Push the call frames.
 		const callFrameNums = 10
 		stackPointerToExpectedValue := map[uint64]uint32{}
-		for funcaddr := wasm.FunctionAddress(0); funcaddr < callFrameNums; funcaddr++ {
+		for funcaddr := wasm.FunctionIndex(0); funcaddr < callFrameNums; funcaddr++ {
 			//	Each function pushes its funcaddr and soon returns.
 			compiler := env.requireNewCompiler(t)
 			err := compiler.compilePreamble()
@@ -224,21 +224,21 @@ func TestAmd64Compiler_initializeModuleContext(t *testing.T) {
 			name: "no nil",
 			moduleInstance: &wasm.ModuleInstance{
 				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
-				Tables:         []*wasm.TableInstance{{Table: make([]wasm.TableElement, 20)}},
+				TableInstance:  &wasm.TableInstance{Table: make([]wasm.TableElement, 20)},
 				Globals:        []*wasm.GlobalInstance{{Val: 100}},
 			},
 		},
 		{
 			name: "memory nil",
 			moduleInstance: &wasm.ModuleInstance{
-				Tables:  []*wasm.TableInstance{{Table: make([]wasm.TableElement, 20)}},
-				Globals: []*wasm.GlobalInstance{{Val: 100}},
+				TableInstance: &wasm.TableInstance{Table: make([]wasm.TableElement, 20)},
+				Globals:       []*wasm.GlobalInstance{{Val: 100}},
 			},
 		},
 		{
 			name: "memory zero length",
 			moduleInstance: &wasm.ModuleInstance{
-				Tables:         []*wasm.TableInstance{{Table: make([]wasm.TableElement, 20)}},
+				TableInstance:  &wasm.TableInstance{Table: make([]wasm.TableElement, 20)},
 				Globals:        []*wasm.GlobalInstance{{Val: 100}},
 				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 0)},
 			},
@@ -247,7 +247,7 @@ func TestAmd64Compiler_initializeModuleContext(t *testing.T) {
 			name: "table length zero",
 			moduleInstance: &wasm.ModuleInstance{
 				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
-				Tables:         []*wasm.TableInstance{{Table: nil}},
+				TableInstance:  &wasm.TableInstance{Table: nil},
 				Globals:        []*wasm.GlobalInstance{{Val: 100}},
 			},
 		},
@@ -255,7 +255,7 @@ func TestAmd64Compiler_initializeModuleContext(t *testing.T) {
 			name: "table length zero part2",
 			moduleInstance: &wasm.ModuleInstance{
 				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
-				Tables:         []*wasm.TableInstance{{Table: make([]wasm.TableElement, 0)}},
+				TableInstance:  &wasm.TableInstance{Table: make([]wasm.TableElement, 0)},
 				Globals:        []*wasm.GlobalInstance{{Val: 100}},
 			},
 		},
@@ -263,7 +263,7 @@ func TestAmd64Compiler_initializeModuleContext(t *testing.T) {
 			name: "table nil",
 			moduleInstance: &wasm.ModuleInstance{
 				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
-				Tables:         []*wasm.TableInstance{},
+				TableInstance:  &wasm.TableInstance{},
 				Globals:        []*wasm.GlobalInstance{{Val: 100}},
 			},
 		},
@@ -278,7 +278,7 @@ func TestAmd64Compiler_initializeModuleContext(t *testing.T) {
 			name: "globals nil",
 			moduleInstance: &wasm.ModuleInstance{
 				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
-				Tables:         []*wasm.TableInstance{{Table: make([]wasm.TableElement, 20)}},
+				TableInstance:  &wasm.TableInstance{Table: make([]wasm.TableElement, 20)},
 			},
 		},
 	} {
@@ -319,8 +319,8 @@ func TestAmd64Compiler_initializeModuleContext(t *testing.T) {
 				require.Equal(t, bufSliceHeader.Data, vm.moduleContext.memoryElement0Address)
 			}
 
-			if len(tc.moduleInstance.Tables) > 0 {
-				tableHeader := (*reflect.SliceHeader)(unsafe.Pointer(&tc.moduleInstance.Tables[0].Table))
+			if tc.moduleInstance.TableInstance != nil {
+				tableHeader := (*reflect.SliceHeader)(unsafe.Pointer(&tc.moduleInstance.TableInstance.Table))
 				require.Equal(t, uint64(tableHeader.Len), vm.moduleContext.tableSliceLen)
 				require.Equal(t, tableHeader.Data, vm.moduleContext.tableElement0Address)
 			}
@@ -5304,7 +5304,7 @@ func TestAmd64Compiler_compileMemoryGrow(t *testing.T) {
 	env.exec(code)
 
 	require.Equal(t, jitCallStatusCodeCallBuiltInFunction, env.jitStatus())
-	require.Equal(t, builtinFunctionAddressMemoryGrow, env.functionCallAddress())
+	require.Equal(t, builtinFunctionIndexMemoryGrow, env.functionCallAddress())
 
 	returnAddress := env.callFrameStackPeek().returnAddress
 	require.NotZero(t, returnAddress)
@@ -5920,13 +5920,13 @@ func TestAmd64Compiler_callFunction(t *testing.T) {
 				if isAddressFromRegister {
 					err = compiler.callFunctionFromRegister(x86.REG_AX, &wasm.FunctionType{})
 				} else {
-					err = compiler.callFunctionFromAddress(11111 /* can be arbitrary*/, &wasm.FunctionType{})
+					err = compiler.callFunctionFromConst(11111 /* can be arbitrary*/, &wasm.FunctionType{})
 				}
 				require.NoError(t, err)
 				require.Empty(t, compiler.locationStack.usedRegisters)
 
 				// Because we must early return from the function this case,
-				// we emit the undefined instruction after the callFunctionFromAddress.
+				// we emit the undefined instruction after the callFunctionFromConst.
 				compiler.undefined()
 
 				// Generate the code under test.
@@ -5939,7 +5939,7 @@ func TestAmd64Compiler_callFunction(t *testing.T) {
 				// If the call frame stack pointer equals the length of call frame stack length,
 				// we have to call the builtin function to grow the slice.
 				require.Equal(t, jitCallStatusCodeCallBuiltInFunction, env.jitStatus())
-				require.Equal(t, builtinFunctionAddressGrowCallFrameStack, env.functionCallAddress())
+				require.Equal(t, builtinFunctionIndexGrowCallFrameStack, env.functionCallAddress())
 			})
 			t.Run("stack ok", func(t *testing.T) {
 				env := newJITEnvironment()
@@ -6007,7 +6007,7 @@ func TestAmd64Compiler_callFunction(t *testing.T) {
 						codeSegment:        code,
 						codeInitialAddress: uintptr(unsafe.Pointer(&code[0])),
 					}
-					engine.addCompiledFunction(wasm.FunctionAddress(i), compiledFunction)
+					engine.addCompiledFunction(wasm.FunctionIndex(i), compiledFunction)
 				}
 
 				// Now we start building the caller's code.
@@ -6029,7 +6029,7 @@ func TestAmd64Compiler_callFunction(t *testing.T) {
 						compiler.movIntConstToRegister(int64(i), tmpReg)
 						err = compiler.callFunctionFromRegister(tmpReg, targetFunctionType)
 					} else {
-						err = compiler.callFunctionFromAddress(wasm.FunctionAddress(i), targetFunctionType)
+						err = compiler.callFunctionFromConst(wasm.FunctionIndex(i), targetFunctionType)
 					}
 					require.NoError(t, err)
 				}
@@ -6063,7 +6063,7 @@ func TestAmd64Compiler_compileCall(t *testing.T) {
 	env := newJITEnvironment()
 	engine := env.engine()
 
-	const targetFunctionAddress wasm.FunctionAddress = 5 // arbitrary value for testing
+	const targetFunctionIndex wasm.FunctionIndex = 5 // arbitrary value for testing
 	targetFunctionType := &wasm.FunctionType{
 		Params:  []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32},
 		Results: []wasm.ValueType{wasm.ValueTypeI32},
@@ -6089,12 +6089,12 @@ func TestAmd64Compiler_compileCall(t *testing.T) {
 		code, _, _, err := compiler.compile()
 		require.NoError(t, err)
 
-		engine.addCompiledFunction(targetFunctionAddress, &compiledFunction{
+		engine.addCompiledFunction(targetFunctionIndex, &compiledFunction{
 			codeSegment:        code,
 			codeInitialAddress: uintptr(unsafe.Pointer(&code[0])),
 		})
 		env.module().Functions = append(env.module().Functions,
-			&wasm.FunctionInstance{FunctionType: &wasm.TypeInstance{Type: targetFunctionType}, Address: targetFunctionAddress})
+			&wasm.FunctionInstance{FunctionType: &wasm.TypeInstance{Type: targetFunctionType}, Index: targetFunctionIndex})
 	}
 
 	// Now we start building the caller's code.
@@ -6244,7 +6244,7 @@ func TestAmd64Compiler_compileCallIndirect(t *testing.T) {
 
 		table := make([]wasm.TableElement, 10)
 		for i := 0; i < len(table); i++ {
-			table[i] = wasm.TableElement{FunctionAddress: wasm.FunctionAddress(i), FunctionTypeID: targetTypeID}
+			table[i] = wasm.TableElement{FunctionIndex: wasm.FunctionIndex(i), FunctionTypeID: targetTypeID}
 		}
 
 		for i := 0; i < len(table); i++ {
@@ -6268,7 +6268,7 @@ func TestAmd64Compiler_compileCallIndirect(t *testing.T) {
 					code, _, _, err := compiler.compile()
 					require.NoError(t, err)
 
-					engine.addCompiledFunction(table[i].FunctionAddress, &compiledFunction{
+					engine.addCompiledFunction(table[i].FunctionIndex, &compiledFunction{
 						codeSegment:        code,
 						codeInitialAddress: uintptr(unsafe.Pointer(&code[0])),
 					})
@@ -6282,7 +6282,7 @@ func TestAmd64Compiler_compileCallIndirect(t *testing.T) {
 				compiler.f = &wasm.FunctionInstance{ModuleInstance: moduleInstance, FunctionKind: wasm.FunctionKindWasm}
 				// and the typeID  matches the table[targetOffset]'s type ID.
 
-				// Place the offfset value. Here we try calling a function of functionaddr == table[i].FunctionAddress.
+				// Place the offfset value. Here we try calling a function of functionaddr == table[i].FunctionIndex.
 				err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(i)})
 				require.NoError(t, err)
 
diff --git a/internal/wasm/jit/jit_arm64_test.go b/internal/wasm/jit/jit_arm64_test.go
index 7faacbd4..f89d632a 100644
--- a/internal/wasm/jit/jit_arm64_test.go
+++ b/internal/wasm/jit/jit_arm64_test.go
@@ -93,7 +93,7 @@ func TestArm64Compiler_returnFunction(t *testing.T) {
 		// Push the call frames.
 		const callFrameNums = 10
 		stackPointerToExpectedValue := map[uint64]uint32{}
-		for funcaddr := wasm.FunctionAddress(0); funcaddr < callFrameNums; funcaddr++ {
+		for funcaddr := wasm.FunctionIndex(0); funcaddr < callFrameNums; funcaddr++ {
 			// We have to do compilation in a separate subtest since each compilation takes
 			// the mutext lock and must release on the cleanup of each subtest.
 			// TODO: delete after https://github.com/tetratelabs/wazero/issues/233
@@ -1764,13 +1764,13 @@ func TestArm64Compiler_compileCall(t *testing.T) {
 
 					code, _, _, err := compiler.compile()
 					require.NoError(t, err)
-					addr := wasm.FunctionAddress(i)
-					eng.addCompiledFunction(addr, &compiledFunction{
+					index := wasm.FunctionIndex(i)
+					eng.addCompiledFunction(index, &compiledFunction{
 						codeSegment:        code,
 						codeInitialAddress: uintptr(unsafe.Pointer(&code[0])),
 					})
 					env.module().Functions = append(env.module().Functions,
-						&wasm.FunctionInstance{FunctionType: &wasm.TypeInstance{Type: targetFunctionType}, Address: addr})
+						&wasm.FunctionInstance{FunctionType: &wasm.TypeInstance{Type: targetFunctionType}, Index: index})
 				})
 			}
 
@@ -1803,7 +1803,7 @@ func TestArm64Compiler_compileCall(t *testing.T) {
 				// If the call frame stack pointer equals the length of call frame stack length,
 				// we have to call the builtin function to grow the slice.
 				require.Equal(t, jitCallStatusCodeCallBuiltInFunction, env.jitStatus())
-				require.Equal(t, builtinFunctionAddressGrowCallFrameStack, env.functionCallAddress(), env.functionCallAddress())
+				require.Equal(t, builtinFunctionIndexGrowCallFrameStack, env.functionCallAddress(), env.functionCallAddress())
 
 				// Grow the callFrame stack, and exec again from the return address.
 				vm := env.callEngine()
@@ -1942,7 +1942,7 @@ func TestArm64Compiler_compileCallIndirect(t *testing.T) {
 
 				table := make([]wasm.TableElement, 10)
 				for i := 0; i < len(table); i++ {
-					table[i] = wasm.TableElement{FunctionAddress: wasm.FunctionAddress(i), FunctionTypeID: targetTypeID}
+					table[i] = wasm.TableElement{FunctionIndex: wasm.FunctionIndex(i), FunctionTypeID: targetTypeID}
 				}
 
 				for i := 0; i < len(table); i++ {
@@ -1973,7 +1973,7 @@ func TestArm64Compiler_compileCallIndirect(t *testing.T) {
 							codeSegment:        code,
 							codeInitialAddress: uintptr(unsafe.Pointer(&code[0])),
 						}
-						eng.addCompiledFunction(table[i].FunctionAddress, cf)
+						eng.addCompiledFunction(table[i].FunctionIndex, cf)
 					})
 
 					t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
@@ -1987,7 +1987,7 @@ func TestArm64Compiler_compileCallIndirect(t *testing.T) {
 
 						compiler.f = &wasm.FunctionInstance{ModuleInstance: moduleInstance, FunctionKind: wasm.FunctionKindWasm}
 
-						// Place the offfset value. Here we try calling a function of functionaddr == table[i].FunctionAddress.
+						// Place the offfset value. Here we try calling a function of functionaddr == table[i].FunctionIndex.
 						err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(i)})
 						require.NoError(t, err)
 
@@ -2012,7 +2012,7 @@ func TestArm64Compiler_compileCallIndirect(t *testing.T) {
 							// If the call frame stack pointer equals the length of call frame stack length,
 							// we have to call the builtin function to grow the slice.
 							require.Equal(t, jitCallStatusCodeCallBuiltInFunction, env.jitStatus())
-							require.Equal(t, builtinFunctionAddressGrowCallFrameStack, env.functionCallAddress(), env.functionCallAddress())
+							require.Equal(t, builtinFunctionIndexGrowCallFrameStack, env.functionCallAddress(), env.functionCallAddress())
 
 							// Grow the callFrame stack, and exec again from the return address.
 							vm := env.callEngine()
@@ -2145,28 +2145,28 @@ func TestArm64Compiler_compileModuleContextInitialization(t *testing.T) {
 			moduleInstance: &wasm.ModuleInstance{
 				Globals:        []*wasm.GlobalInstance{{Val: 100}},
 				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
-				Tables:         []*wasm.TableInstance{{Table: make([]wasm.TableElement, 20)}},
+				TableInstance:  &wasm.TableInstance{Table: make([]wasm.TableElement, 20)},
 			},
 		},
 		{
 			name: "globals nil",
 			moduleInstance: &wasm.ModuleInstance{
 				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
-				Tables:         []*wasm.TableInstance{{Table: make([]wasm.TableElement, 20)}},
+				TableInstance:  &wasm.TableInstance{Table: make([]wasm.TableElement, 20)},
 			},
 		},
 		{
 			name: "memory nil",
 			moduleInstance: &wasm.ModuleInstance{
-				Globals: []*wasm.GlobalInstance{{Val: 100}},
-				Tables:  []*wasm.TableInstance{{Table: make([]wasm.TableElement, 20)}},
+				Globals:       []*wasm.GlobalInstance{{Val: 100}},
+				TableInstance: &wasm.TableInstance{Table: make([]wasm.TableElement, 20)},
 			},
 		},
 		{
 			name: "table nil",
 			moduleInstance: &wasm.ModuleInstance{
 				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
-				Tables:         []*wasm.TableInstance{{Table: nil}},
+				TableInstance:  &wasm.TableInstance{Table: nil},
 				Globals:        []*wasm.GlobalInstance{{Val: 100}},
 			},
 		},
@@ -2174,7 +2174,7 @@ func TestArm64Compiler_compileModuleContextInitialization(t *testing.T) {
 			name: "table empty",
 			moduleInstance: &wasm.ModuleInstance{
 				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
-				Tables:         []*wasm.TableInstance{{Table: make([]wasm.TableElement, 0)}},
+				TableInstance:  &wasm.TableInstance{Table: make([]wasm.TableElement, 0)},
 				Globals:        []*wasm.GlobalInstance{{Val: 100}},
 			},
 		},
@@ -2182,7 +2182,7 @@ func TestArm64Compiler_compileModuleContextInitialization(t *testing.T) {
 			name: "memory zero length",
 			moduleInstance: &wasm.ModuleInstance{
 				Globals:        []*wasm.GlobalInstance{{Val: 100}},
-				Tables:         []*wasm.TableInstance{{Table: make([]wasm.TableElement, 0)}},
+				TableInstance:  &wasm.TableInstance{Table: make([]wasm.TableElement, 0)},
 				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 0)},
 			},
 		},
@@ -2229,8 +2229,8 @@ func TestArm64Compiler_compileModuleContextInitialization(t *testing.T) {
 				require.Equal(t, bufSliceHeader.Data, vm.moduleContext.memoryElement0Address)
 			}
 
-			if len(tc.moduleInstance.Tables) > 0 {
-				tableHeader := (*reflect.SliceHeader)(unsafe.Pointer(&tc.moduleInstance.Tables[0].Table))
+			if tc.moduleInstance.TableInstance != nil {
+				tableHeader := (*reflect.SliceHeader)(unsafe.Pointer(&tc.moduleInstance.TableInstance.Table))
 				require.Equal(t, uint64(tableHeader.Len), vm.moduleContext.tableSliceLen)
 				require.Equal(t, tableHeader.Data, vm.moduleContext.tableElement0Address)
 			}
@@ -2755,7 +2755,7 @@ func TestArm64Compiler_compileMemoryGrow(t *testing.T) {
 
 	// After the initial exec, the code must exit with builtin function call status and funcaddress for memory grow.
 	require.Equal(t, jitCallStatusCodeCallBuiltInFunction, env.jitStatus())
-	require.Equal(t, builtinFunctionAddressMemoryGrow, env.functionCallAddress())
+	require.Equal(t, builtinFunctionIndexMemoryGrow, env.functionCallAddress())
 
 	// Reenter from the return address.
 	jitcall(env.callFrameStackPeek().returnAddress, uintptr(unsafe.Pointer(env.callEngine())))
@@ -2873,7 +2873,7 @@ func TestArm64Compiler_compileHostFunction(t *testing.T) {
 	// TODO: delete after #233
 	compiler.compileNOP()
 
-	addr := wasm.FunctionAddress(100)
+	addr := wasm.FunctionIndex(100)
 	err := compiler.compileHostFunction(addr)
 	require.NoError(t, err)
 
diff --git a/internal/wasm/jit/jit_test.go b/internal/wasm/jit/jit_test.go
index 875d292f..a083391a 100644
--- a/internal/wasm/jit/jit_test.go
+++ b/internal/wasm/jit/jit_test.go
@@ -51,7 +51,7 @@ func (j *jitEnv) jitStatus() jitCallStatusCode {
 	return j.vm.exitContext.statusCode
 }
 
-func (j *jitEnv) functionCallAddress() wasm.FunctionAddress {
+func (j *jitEnv) functionCallAddress() wasm.FunctionIndex {
 	return j.vm.exitContext.functionCallAddress
 }
 
@@ -76,7 +76,7 @@ func (j *jitEnv) getGlobal(index uint32) uint64 {
 }
 
 func (j *jitEnv) setTable(table []wasm.TableElement) {
-	j.moduleInstance.Tables[0] = &wasm.TableInstance{Table: table}
+	j.moduleInstance.TableInstance = &wasm.TableInstance{Table: table}
 }
 
 func (j *jitEnv) callFrameStackPeek() *callFrame {
@@ -133,7 +133,7 @@ func newJITEnvironment() *jitEnv {
 		eng: eng,
 		moduleInstance: &wasm.ModuleInstance{
 			MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, wasm.MemoryPageSize*defaultMemoryPageNumInTest)},
-			Tables:         []*wasm.TableInstance{{}},
+			TableInstance:  &wasm.TableInstance{},
 			Globals:        []*wasm.GlobalInstance{},
 		},
 		vm: eng.newCallEngine(),
diff --git a/internal/wasm/module_test.go b/internal/wasm/module_test.go
index f5b02230..d3534bf8 100644
--- a/internal/wasm/module_test.go
+++ b/internal/wasm/module_test.go
@@ -1,7 +1,9 @@
 package internalwasm
 
 import (
+	"encoding/binary"
 	"fmt"
+	"math"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -281,3 +283,448 @@ func TestModule_SectionSize(t *testing.T) {
 		})
 	}
 }
+
+func TestValidateConstExpression(t *testing.T) {
+	t.Run("invalid opcode", func(t *testing.T) {
+		expr := &ConstantExpression{Opcode: OpcodeNop}
+		err := validateConstExpression(nil, expr, valueTypeUnknown)
+		require.Error(t, err)
+	})
+	for _, vt := range []ValueType{ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64} {
+		t.Run(ValueTypeName(vt), func(t *testing.T) {
+			t.Run("valid", func(t *testing.T) {
+				// Allocate bytes with enough size for all types.
+				expr := &ConstantExpression{Data: make([]byte, 8)}
+				switch vt {
+				case ValueTypeI32:
+					expr.Data[0] = 1
+					expr.Opcode = OpcodeI32Const
+				case ValueTypeI64:
+					expr.Data[0] = 2
+					expr.Opcode = OpcodeI64Const
+				case ValueTypeF32:
+					binary.LittleEndian.PutUint32(expr.Data, math.Float32bits(math.MaxFloat32))
+					expr.Opcode = OpcodeF32Const
+				case ValueTypeF64:
+					binary.LittleEndian.PutUint64(expr.Data, math.Float64bits(math.MaxFloat64))
+					expr.Opcode = OpcodeF64Const
+				}
+
+				err := validateConstExpression(nil, expr, vt)
+				require.NoError(t, err)
+			})
+			t.Run("invalid", func(t *testing.T) {
+				// Empty data must be failure.
+				expr := &ConstantExpression{Data: make([]byte, 0)}
+				switch vt {
+				case ValueTypeI32:
+					expr.Opcode = OpcodeI32Const
+				case ValueTypeI64:
+					expr.Opcode = OpcodeI64Const
+				case ValueTypeF32:
+					expr.Opcode = OpcodeF32Const
+				case ValueTypeF64:
+					expr.Opcode = OpcodeF64Const
+				}
+				err := validateConstExpression(nil, expr, vt)
+				require.Error(t, err)
+			})
+		})
+	}
+	t.Run("global expr", func(t *testing.T) {
+		t.Run("failed to read global index", func(t *testing.T) {
+			// Empty data for global index is invalid.
+			expr := &ConstantExpression{Data: make([]byte, 0), Opcode: OpcodeGlobalGet}
+			err := validateConstExpression(nil, expr, valueTypeUnknown)
+			require.Error(t, err)
+		})
+		t.Run("global index out of range", func(t *testing.T) {
+			// Data holds the index in leb128 and this time the value exceeds len(globals) (=0).
+			expr := &ConstantExpression{Data: []byte{1}, Opcode: OpcodeGlobalGet}
+			var globals []*GlobalType
+			err := validateConstExpression(globals, expr, valueTypeUnknown)
+			require.Error(t, err)
+		})
+
+		t.Run("type mismatch", func(t *testing.T) {
+			for _, vt := range []ValueType{
+				ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64,
+			} {
+				t.Run(ValueTypeName(vt), func(t *testing.T) {
+					// The index specified in Data equals zero.
+					expr := &ConstantExpression{Data: []byte{0}, Opcode: OpcodeGlobalGet}
+					globals := []*GlobalType{{ValType: valueTypeUnknown}}
+
+					err := validateConstExpression(globals, expr, vt)
+					require.Error(t, err)
+				})
+			}
+		})
+		t.Run("ok", func(t *testing.T) {
+			for _, vt := range []ValueType{
+				ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64,
+			} {
+				t.Run(ValueTypeName(vt), func(t *testing.T) {
+					// The index specified in Data equals zero.
+					expr := &ConstantExpression{Data: []byte{0}, Opcode: OpcodeGlobalGet}
+					globals := []*GlobalType{{ValType: vt}}
+
+					err := validateConstExpression(globals, expr, vt)
+					require.NoError(t, err)
+				})
+			}
+		})
+	})
+}
+
+func TestModule_validateStartSection(t *testing.T) {
+	t.Run("no start section", func(t *testing.T) {
+		m := Module{}
+		err := m.validateStartSection()
+		require.NoError(t, err)
+	})
+
+	t.Run("invalid type", func(t *testing.T) {
+		for _, ft := range []*FunctionType{
+			{Params: []ValueType{ValueTypeI32}},
+			{Results: []ValueType{ValueTypeI32}},
+			{Params: []ValueType{ValueTypeI32}, Results: []ValueType{ValueTypeI32}},
+		} {
+			t.Run(ft.String(), func(t *testing.T) {
+				index := uint32(0)
+				m := Module{StartSection: &index, FunctionSection: []uint32{0}, TypeSection: []*FunctionType{ft}}
+				err := m.validateStartSection()
+				require.Error(t, err)
+			})
+		}
+	})
+}
+
+func TestModule_validateGlobals(t *testing.T) {
+	t.Run("too many globals", func(t *testing.T) {
+		m := Module{}
+		err := m.validateGlobals(make([]*GlobalType, 10), 9)
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "too many globals")
+	})
+	t.Run("global index out of range", func(t *testing.T) {
+		m := Module{GlobalSection: []*Global{
+			{
+				Type: &GlobalType{ValType: ValueTypeI32},
+				// Trying to reference globals[1] which is not imported.
+				Init: &ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{1}},
+			},
+		}}
+		err := m.validateGlobals(nil, 9)
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "global index out of range")
+	})
+	t.Run("invalid const expression", func(t *testing.T) {
+		m := Module{GlobalSection: []*Global{
+			{
+				Type: &GlobalType{ValType: valueTypeUnknown},
+				Init: &ConstantExpression{Opcode: OpcodeUnreachable},
+			},
+		}}
+		err := m.validateGlobals(nil, 9)
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "invalid opcode for const expression")
+	})
+	t.Run("ok", func(t *testing.T) {
+		m := Module{GlobalSection: []*Global{
+			{
+				Type: &GlobalType{ValType: ValueTypeI32},
+				Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0}},
+			},
+		}}
+		err := m.validateGlobals(nil, 9)
+		require.NoError(t, err)
+	})
+	t.Run("ok with imported global", func(t *testing.T) {
+		m := Module{
+			GlobalSection: []*Global{
+				{
+					Type: &GlobalType{ValType: ValueTypeI32},
+					// Trying to reference globals[1] which is imported.
+					Init: &ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0}},
+				},
+			},
+			ImportSection: []*Import{{Type: ExternTypeGlobal}},
+		}
+		globalDeclarations := []*GlobalType{
+			{ValType: ValueTypeI32}, // Imported one.
+			nil,                     // the local one trying to validate.
+		}
+		err := m.validateGlobals(globalDeclarations, 9)
+		require.NoError(t, err)
+	})
+}
+
+func TestModule_validateFunctions(t *testing.T) {
+	t.Run("type index out of range", func(t *testing.T) {
+		m := Module{FunctionSection: []uint32{1000 /* arbitrary large */}}
+		err := m.validateFunctions(nil, nil, nil, nil)
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "function type index out of range")
+	})
+	t.Run("insufficient code section", func(t *testing.T) {
+		m := Module{
+			FunctionSection: []uint32{0},
+			TypeSection:     []*FunctionType{{}},
+			// Code section not exists.
+		}
+		err := m.validateFunctions(nil, nil, nil, nil)
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "code index out of range")
+	})
+	t.Run("invalid function", func(t *testing.T) {
+		m := Module{
+			FunctionSection: []uint32{0},
+			TypeSection:     []*FunctionType{{}},
+			CodeSection: []*Code{
+				{Body: []byte{OpcodeF32Abs}},
+			},
+		}
+		err := m.validateFunctions(nil, nil, nil, nil)
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "invalid function (0/0): cannot pop the 1st f32 operand")
+	})
+	t.Run("ok", func(t *testing.T) {
+		m := Module{
+			FunctionSection: []uint32{0},
+			TypeSection:     []*FunctionType{{}},
+			CodeSection: []*Code{
+				{Body: []byte{OpcodeI32Const, 0, OpcodeDrop, OpcodeEnd}},
+			},
+		}
+		err := m.validateFunctions(nil, nil, nil, nil)
+		require.NoError(t, err)
+	})
+}
+
+func TestModule_validateTables(t *testing.T) {
+	t.Run("multiple tables", func(t *testing.T) {
+		m := Module{}
+		err := m.validateTables(make([]*TableType, 10), nil)
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "multiple tables are not supported")
+	})
+	t.Run("table index out of range", func(t *testing.T) {
+		m := Module{ElementSection: []*ElementSegment{{TableIndex: 1000}}}
+		err := m.validateTables(make([]*TableType, 1), nil)
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "table index out of range")
+	})
+	t.Run("invalid const expr", func(t *testing.T) {
+		m := Module{ElementSection: []*ElementSegment{{
+			TableIndex: 0,
+			OffsetExpr: &ConstantExpression{
+				Opcode: OpcodeUnreachable, // Invalid!
+			},
+		}}}
+		err := m.validateTables(make([]*TableType, 1), nil)
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "invalid opcode for const expression: 0x0")
+	})
+	t.Run("ok", func(t *testing.T) {
+		m := Module{ElementSection: []*ElementSegment{{
+			TableIndex: 0,
+			OffsetExpr: &ConstantExpression{
+				Opcode: OpcodeI32Const,
+				Data:   []byte{0x1},
+			},
+		}}}
+		err := m.validateTables(make([]*TableType, 1), nil)
+		require.NoError(t, err)
+	})
+}
+
+func TestModule_validateMemories(t *testing.T) {
+	t.Run("multiple memory", func(t *testing.T) {
+		m := Module{}
+		err := m.validateMemories(make([]*LimitsType, 10), nil)
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "multiple memories are not supported")
+	})
+	t.Run("data section exits but memory not declared", func(t *testing.T) {
+		m := Module{DataSection: make([]*DataSegment, 1)}
+		err := m.validateMemories(nil, nil)
+		require.Error(t, err)
+		require.Contains(t, "unknown memory", err.Error())
+	})
+	t.Run("non zero memory index data", func(t *testing.T) {
+		m := Module{DataSection: []*DataSegment{{MemoryIndex: 1}}}
+		err := m.validateMemories(make([]*LimitsType, 1), nil)
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "memory index must be zero")
+	})
+	t.Run("invalid const expr", func(t *testing.T) {
+		m := Module{DataSection: []*DataSegment{{
+			MemoryIndex: 0,
+			OffsetExpression: &ConstantExpression{
+				Opcode: OpcodeUnreachable, // Invalid!
+			},
+		}}}
+		err := m.validateMemories(make([]*LimitsType, 1), nil)
+		require.Contains(t, err.Error(), "invalid opcode for const expression: 0x0")
+	})
+	t.Run("ok", func(t *testing.T) {
+		m := Module{DataSection: []*DataSegment{{
+			MemoryIndex: 0, Init: []byte{0x1},
+			OffsetExpression: &ConstantExpression{
+				Opcode: OpcodeI32Const,
+				Data:   []byte{0x1},
+			},
+		}}}
+		err := m.validateMemories(make([]*LimitsType, 1), nil)
+		require.NoError(t, err)
+	})
+}
+
+func TestModule_validateExports(t *testing.T) {
+	for _, tc := range []struct {
+		name          string
+		exportSection map[string]*Export
+		functions     []Index
+		globals       []*GlobalType
+		memories      []*MemoryType
+		tables        []*TableType
+		expErr        bool
+	}{
+		{name: "empty export section", exportSection: map[string]*Export{}},
+		{
+			name:          "valid func",
+			exportSection: map[string]*Export{"": {Type: ExternTypeFunc, Index: 0}},
+			functions:     []Index{100 /* arbitrary type id*/},
+		},
+		{
+			name:          "invalid func",
+			exportSection: map[string]*Export{"": {Type: ExternTypeFunc, Index: 1}},
+			functions:     []Index{100 /* arbitrary type id*/},
+			expErr:        true,
+		},
+		{
+			name:          "valid global",
+			exportSection: map[string]*Export{"": {Type: ExternTypeGlobal, Index: 0}},
+			globals:       []*GlobalType{{}},
+		},
+		{
+			name:          "invalid global",
+			exportSection: map[string]*Export{"": {Type: ExternTypeFunc, Index: 1}},
+			globals:       []*GlobalType{{}},
+			expErr:        true,
+		},
+		{
+			name:          "valid table",
+			exportSection: map[string]*Export{"": {Type: ExternTypeTable, Index: 0}},
+			tables:        []*TableType{{}},
+		},
+		{
+			name:          "invalid table",
+			exportSection: map[string]*Export{"": {Type: ExternTypeTable, Index: 1}},
+			tables:        []*TableType{{}},
+			expErr:        true,
+		},
+		{
+			name:          "valid memory",
+			exportSection: map[string]*Export{"": {Type: ExternTypeMemory, Index: 0}},
+			memories:      []*MemoryType{&LimitsType{}},
+		},
+		{
+			name:          "invalid memory index",
+			exportSection: map[string]*Export{"": {Type: ExternTypeMemory, Index: 1}},
+			expErr:        true,
+		},
+		{
+			name:          "invalid memory index",
+			exportSection: map[string]*Export{"": {Type: ExternTypeMemory, Index: 1}},
+			// Multiple memories are not valid.
+			memories: []*MemoryType{&LimitsType{}, &LimitsType{}},
+			expErr:   true,
+		},
+	} {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			m := Module{ExportSection: tc.exportSection}
+			err := m.validateExports(tc.functions, tc.globals, tc.memories, tc.tables)
+			if tc.expErr {
+				require.Error(t, err)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestModule_buildGlobalInstances(t *testing.T) {
+	data := []byte{0, 0, 0, 0, 0, 0, 0, 0}
+	binary.LittleEndian.PutUint64(data, math.Float64bits(1.0))
+	m := Module{GlobalSection: []*Global{
+		{
+			Type: &GlobalType{Mutable: true, ValType: ValueTypeF64},
+			Init: &ConstantExpression{Opcode: OpcodeF64Const,
+				Data: []byte{0, 0, 0, 0, 0, 0, 0xf0, 0x3f}}, // == float64(1.0)
+		},
+		{
+			Type: &GlobalType{Mutable: false, ValType: ValueTypeI32},
+			Init: &ConstantExpression{Opcode: OpcodeI32Const,
+				Data: []byte{1}},
+		},
+	}}
+
+	globals := m.buildGlobalInstances(nil)
+	expectedGlobals := []*GlobalInstance{
+		{Type: &GlobalType{ValType: ValueTypeF64, Mutable: true}, Val: math.Float64bits(1.0)},
+		{Type: &GlobalType{ValType: ValueTypeI32, Mutable: false}, Val: uint64(1)},
+	}
+
+	require.Len(t, globals, len(expectedGlobals))
+	for i := range globals {
+		actual, expected := globals[i], expectedGlobals[i]
+		require.Equal(t, expected, actual)
+	}
+}
+
+func TestModule_buildFunctionInstances(t *testing.T) {
+	nopCode := &Code{nil, []byte{OpcodeEnd}}
+	m := Module{
+		ImportSection: []*Import{{Type: ExternTypeFunc}},
+		NameSection: &NameSection{
+			FunctionNames: NameMap{
+				{Index: Index(2), Name: "two"},
+				{Index: Index(4), Name: "four"},
+				{Index: Index(5), Name: "five"},
+			},
+		},
+		CodeSection: []*Code{nopCode, nopCode, nopCode, nopCode, nopCode},
+	}
+
+	actual := m.buildFunctionInstances()
+	expectedNames := []string{"unknown", "two", "unknown", "four", "five"}
+	for i, f := range actual {
+		require.Equal(t, expectedNames[i], f.Name)
+		require.Equal(t, nopCode.Body, f.Body)
+	}
+}
+
+func TestModule_buildMemoryInstance(t *testing.T) {
+	t.Run("nil", func(t *testing.T) {
+		m := Module{MemorySection: []*MemoryType{}}
+		mem := m.buildMemoryInstance()
+		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()
+		require.Equal(t, min, mem.Min)
+		require.Equal(t, max, *mem.Max)
+	})
+}
+
+func TestModule_buildTableInstance(t *testing.T) {
+	m := Module{TableSection: []*TableType{{Limit: &LimitsType{Min: 1}}}}
+	table := m.buildTableInstance()
+	require.Equal(t, uint32(1), table.Min)
+}
diff --git a/internal/wasm/store_test.go b/internal/wasm/store_test.go
index a19711da..0c306b76 100644
--- a/internal/wasm/store_test.go
+++ b/internal/wasm/store_test.go
@@ -1,7 +1,6 @@
 package internalwasm
 
 import (
-	"bytes"
 	"context"
 	"encoding/binary"
 	"math"
@@ -11,7 +10,6 @@ import (
 
 	"github.com/stretchr/testify/require"
 
-	"github.com/tetratelabs/wazero/internal/leb128"
 	"github.com/tetratelabs/wazero/wasm"
 )
 
@@ -91,8 +89,7 @@ func TestModuleInstance_Memory(t *testing.T) {
 func TestStore_AddHostFunction(t *testing.T) {
 	s := NewStore(context.Background(), &catchContext{})
 
-	hf, err := NewGoFunc("fn", func(wasm.ModuleContext) {
-	})
+	hf, err := NewGoFunc("fn", func(wasm.ModuleContext) {})
 	require.NoError(t, err)
 
 	// Add the host module
@@ -117,15 +114,14 @@ 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}, s.Functions)
+	require.Equal(t, []*FunctionInstance{fn, nil}, s.Functions)
 	require.Equal(t, map[string]*ExportInstance{"fn": exp}, hostModule.Exports)
 }
 
 func TestStore_ExportImportedHostFunction(t *testing.T) {
 	s := NewStore(context.Background(), &catchContext{})
 
-	hf, err := NewGoFunc("host_fn", func(wasm.ModuleContext) {
-	})
+	hf, err := NewGoFunc("host_fn", func(wasm.ModuleContext) {})
 	require.NoError(t, err)
 
 	// Add the host module
@@ -222,41 +218,6 @@ func TestFunctionInstance_Call(t *testing.T) {
 	}
 }
 
-func TestStore_BuildFunctionInstances_FunctionNames(t *testing.T) {
-	name := "test"
-	s := NewStore(context.Background(), &catchContext{})
-
-	zero := Index(0)
-	nopCode := &Code{nil, []byte{OpcodeEnd}}
-	m := &Module{
-		TypeSection:     []*FunctionType{{}},
-		FunctionSection: []Index{zero, zero, zero, zero, zero},
-		NameSection: &NameSection{
-			FunctionNames: NameMap{
-				{Index: Index(1), Name: "two"},
-				{Index: Index(3), Name: "four"},
-				{Index: Index(4), Name: "five"},
-			},
-		},
-		CodeSection: []*Code{nopCode, nopCode, nopCode, nopCode, nopCode},
-	}
-
-	// Make a fake module for test
-	mi := &ModuleInstance{Name: name}
-	s.ModuleInstances[mi.Name] = mi
-
-	_, err := s.buildFunctionInstances(m, mi)
-	require.NoError(t, err)
-
-	var names []string
-	for _, f := range mi.Functions {
-		names = append(names, f.Name)
-	}
-
-	// We expect unknown for any functions missing data in the NameSection
-	require.Equal(t, []string{"unknown", "two", "unknown", "four", "five"}, names)
-}
-
 type catchContext struct {
 	ctx *ModuleContext
 }
@@ -270,26 +231,24 @@ func (e *catchContext) Compile(_ *FunctionInstance) error {
 	return nil
 }
 
-func TestStore_addHostFunction(t *testing.T) {
+func (e *catchContext) Release(_ *FunctionInstance) error {
+	return nil
+}
+
+func TestStore_checkFuncAddrOverflow(t *testing.T) {
 	t.Run("too many functions", func(t *testing.T) {
 		s := NewStore(context.Background(), &catchContext{})
 		const max = 10
-		s.maximumFunctionAddress = max
-		err := s.addFunctionInstance(&FunctionInstance{Address: max + 1})
+		s.maximumFunctionIndex = max
+		err := s.checkFunctionIndexOverflow(max + 1)
 		require.Error(t, err)
 	})
 	t.Run("ok", func(t *testing.T) {
 		s := NewStore(context.Background(), &catchContext{})
-		for i := 0; i < 10; i++ {
-			f := &FunctionInstance{FunctionKind: FunctionKindGoNoContext}
-			require.Len(t, s.Functions, i)
-
-			err := s.addFunctionInstance(f)
-			require.NoError(t, err)
-
-			// After the addition, one instance is added.
-			require.Len(t, s.Functions, i+1)
-		}
+		const max = 10
+		s.maximumFunctionIndex = max
+		err := s.checkFunctionIndexOverflow(max)
+		require.NoError(t, err)
 	})
 }
 
@@ -327,186 +286,641 @@ func TestStore_getTypeInstance(t *testing.T) {
 	})
 }
 
-func TestStore_buildGlobalInstances(t *testing.T) {
-	t.Run("too many globals", func(t *testing.T) {
-		// Setup a store to have the reasonably low max on globals for testing.
+func TestExecuteConstExpression(t *testing.T) {
+	t.Run("non global expr", func(t *testing.T) {
+		for _, vt := range []ValueType{ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64} {
+			t.Run(ValueTypeName(vt), func(t *testing.T) {
+				// Allocate bytes with enough size for all types.
+				expr := &ConstantExpression{Data: make([]byte, 8)}
+				switch vt {
+				case ValueTypeI32:
+					expr.Data[0] = 1
+					expr.Opcode = OpcodeI32Const
+				case ValueTypeI64:
+					expr.Data[0] = 2
+					expr.Opcode = OpcodeI64Const
+				case ValueTypeF32:
+					binary.LittleEndian.PutUint32(expr.Data, math.Float32bits(math.MaxFloat32))
+					expr.Opcode = OpcodeF32Const
+				case ValueTypeF64:
+					binary.LittleEndian.PutUint64(expr.Data, math.Float64bits(math.MaxFloat64))
+					expr.Opcode = OpcodeF64Const
+				}
+
+				raw := executeConstExpression(nil, expr)
+				require.NotNil(t, raw)
+
+				switch vt {
+				case ValueTypeI32:
+					actual, ok := raw.(int32)
+					require.True(t, ok)
+					require.Equal(t, int32(1), actual)
+				case ValueTypeI64:
+					actual, ok := raw.(int64)
+					require.True(t, ok)
+					require.Equal(t, int64(2), actual)
+				case ValueTypeF32:
+					actual, ok := raw.(float32)
+					require.True(t, ok)
+					require.Equal(t, float32(math.MaxFloat32), actual)
+				case ValueTypeF64:
+					actual, ok := raw.(float64)
+					require.True(t, ok)
+					require.Equal(t, float64(math.MaxFloat64), actual)
+				}
+			})
+		}
+	})
+	t.Run("global expr", func(t *testing.T) {
+		for _, tc := range []struct {
+			valueType ValueType
+			val       uint64
+		}{
+			{valueType: ValueTypeI32, val: 10},
+			{valueType: ValueTypeI64, val: 20},
+			{valueType: ValueTypeF32, val: uint64(math.Float32bits(634634432.12311))},
+			{valueType: ValueTypeF64, val: math.Float64bits(1.12312311)},
+		} {
+			t.Run(ValueTypeName(tc.valueType), func(t *testing.T) {
+				// The index specified in Data equals zero.
+				expr := &ConstantExpression{Data: []byte{0}, Opcode: OpcodeGlobalGet}
+				globals := []*GlobalInstance{{Val: tc.val, Type: &GlobalType{ValType: tc.valueType}}}
+
+				val := executeConstExpression(globals, expr)
+				require.NotNil(t, val)
+
+				switch tc.valueType {
+				case ValueTypeI32:
+					actual, ok := val.(int32)
+					require.True(t, ok)
+					require.Equal(t, int32(tc.val), actual)
+				case ValueTypeI64:
+					actual, ok := val.(int64)
+					require.True(t, ok)
+					require.Equal(t, int64(tc.val), actual)
+				case ValueTypeF32:
+					actual, ok := val.(float32)
+					require.True(t, ok)
+					require.Equal(t, wasm.DecodeF32(tc.val), actual)
+				case ValueTypeF64:
+					actual, ok := val.(float64)
+					require.True(t, ok)
+					require.Equal(t, wasm.DecodeF64(tc.val), actual)
+				}
+			})
+		}
+	})
+}
+
+func TestStore_releaseModuleInstance(t *testing.T) {
+	// TODO:
+}
+
+func TestStore_releaseFunctionInstances(t *testing.T) {
+	s := NewStore(context.Background(), &catchContext{})
+	nonReleaseTargetAddr := FunctionIndex(0)
+	maxAddr := FunctionIndex(10)
+	s.Functions = make([]*FunctionInstance, maxAddr+1)
+
+	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...)
+	require.NoError(t, err)
+
+	// Ensure the release targets become nil.
+	for _, f := range fs {
+		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])
+}
+
+func TestStore_addFunctionInstances(t *testing.T) {
+	t.Run("no released index", func(t *testing.T) {
 		s := NewStore(context.Background(), &catchContext{})
-		const max = 10
-		s.maximumGlobals = max
+		prevMaxAddr := FunctionIndex(10)
+		s.Functions = make([]*FunctionInstance, prevMaxAddr+1)
 
-		// Module with max+1 globals must fail.
-		_, err := s.buildGlobalInstances(&Module{GlobalSection: make([]*Global, max+1)}, &ModuleInstance{})
-		require.Error(t, err)
+		for i := FunctionIndex(0); i < 10; i++ {
+			expectedIndex := prevMaxAddr + 1 + i
+			f := &FunctionInstance{}
+			s.addFunctionInstances(f)
+
+			// After adding function intance to store, an funcaddr must be assigned.
+			require.Equal(t, expectedIndex, f.Index)
+		}
 	})
-	t.Run("invalid constant expression", func(t *testing.T) {
+	t.Run("reuse released index", func(t *testing.T) {
 		s := NewStore(context.Background(), &catchContext{})
+		expectedAddr := FunctionIndex(10)
+		s.releasedFunctionIndex = []FunctionIndex{1, expectedAddr}
+		expectedReleasedAddr := s.releasedFunctionIndex[:1]
 
-		// Empty constant expression is invalid.
-		m := &Module{GlobalSection: []*Global{{Init: &ConstantExpression{}}}}
-		_, err := s.buildGlobalInstances(m, &ModuleInstance{})
-		require.Error(t, err)
+		maxAddr := expectedAddr * 10
+		tailInstance := &FunctionInstance{}
+		s.Functions = make([]*FunctionInstance, maxAddr+1)
+		s.Functions[maxAddr] = tailInstance
+
+		f := &FunctionInstance{}
+		s.addFunctionInstances(f)
+
+		// Index must be reused.
+		require.Equal(t, expectedAddr, f.Index)
+
+		// And the others must be intact.
+		require.Equal(t, tailInstance, s.Functions[maxAddr])
+
+		require.Equal(t, expectedReleasedAddr, s.releasedFunctionIndex)
 	})
+}
+
+func TestStore_releaseGlobalInstances(t *testing.T) {
+	s := NewStore(context.Background(), &catchContext{})
+	nonReleaseTargetAddr := globalIndex(0)
+	maxAddr := globalIndex(10)
+	s.Globals = make([]*GlobalInstance, maxAddr+1)
+
+	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...)
 
-	t.Run("global type mismatch", func(t *testing.T) {
+	// Ensure the release targets become nil.
+	for _, g := range gs {
+		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])
+}
+
+func TestStore_addGlobalInstances(t *testing.T) {
+	t.Run("no released index", func(t *testing.T) {
 		s := NewStore(context.Background(), &catchContext{})
-		m := &Module{GlobalSection: []*Global{{
-			// Global with i32.const initial value, but with type specified as f64 must be error.
-			Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0}},
-			Type: &GlobalType{ValType: ValueTypeF64},
-		}}}
-		_, err := s.buildGlobalInstances(m, &ModuleInstance{})
-		require.Error(t, err)
+		prevMaxAddr := globalIndex(10)
+		s.Globals = make([]*GlobalInstance, prevMaxAddr+1)
+
+		for i := globalIndex(0); i < 10; i++ {
+			expectedIndex := prevMaxAddr + 1 + i
+			g := &GlobalInstance{}
+			s.addGlobalInstances(g)
+
+			// After adding function intance to store, an funcaddr must be assigned.
+			require.Equal(t, expectedIndex, g.index)
+		}
 	})
-	t.Run("ok", func(t *testing.T) {
-		global := &Global{
-			Init: &ConstantExpression{Opcode: OpcodeI64Const, Data: []byte{0x11}},
-			Type: &GlobalType{ValType: ValueTypeI64},
+	t.Run("reuse released index", func(t *testing.T) {
+		s := NewStore(context.Background(), &catchContext{})
+		expectedAddr := globalIndex(10)
+		s.releasedGlobalIndex = []globalIndex{1, expectedAddr}
+		expectedReleasedGlobalIndex := s.releasedGlobalIndex[:1]
+
+		maxAddr := expectedAddr * 10
+		tailInstance := &GlobalInstance{}
+		s.Globals = make([]*GlobalInstance, maxAddr+1)
+		s.Globals[maxAddr] = tailInstance
+
+		g := &GlobalInstance{}
+		s.addGlobalInstances(g)
+
+		// Index must be reused.
+		require.Equal(t, expectedAddr, g.index)
+
+		// And the others must be intact.
+		require.Equal(t, tailInstance, s.Globals[maxAddr])
+
+		require.Equal(t, expectedReleasedGlobalIndex, s.releasedGlobalIndex)
+	})
+}
+
+func TestStore_releaseTableInstance(t *testing.T) {
+	s := NewStore(context.Background(), &catchContext{})
+	nonReleaseTargetAddr := tableIndex(0)
+	maxAddr := tableIndex(10)
+	s.Tables = make([]*TableInstance, maxAddr+1)
+
+	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.Contains(t, s.releasedTableIndex, table.index)
+
+	// Plus non-target should remain intact.
+	require.NotNil(t, s.Tables[nonReleaseTargetAddr])
+}
+
+func TestStore_addTableInstance(t *testing.T) {
+	t.Run("no released index", func(t *testing.T) {
+		s := NewStore(context.Background(), &catchContext{})
+		prevMaxAddr := tableIndex(10)
+		s.Tables = make([]*TableInstance, prevMaxAddr+1)
+
+		for i := tableIndex(0); i < 10; i++ {
+			expectedIndex := prevMaxAddr + 1 + i
+			g := &TableInstance{}
+			s.addTableInstance(g)
+
+			// After adding function intance to store, an funcaddr must be assigned.
+			require.Equal(t, expectedIndex, g.index)
 		}
-		expectedValue, _, err := leb128.DecodeUint64(bytes.NewReader(global.Init.Data))
-		require.NoError(t, err)
+	})
+	t.Run("reuse released index", func(t *testing.T) {
+		s := NewStore(context.Background(), &catchContext{})
+		expectedAddr := tableIndex(10)
+		s.releasedTableIndex = []tableIndex{1000, expectedAddr}
+		expectedReleasedTableIndex := s.releasedTableIndex[:1]
+
+		maxAddr := expectedAddr * 10
+		tailInstance := &TableInstance{}
+		s.Tables = make([]*TableInstance, maxAddr+1)
+		s.Tables[maxAddr] = tailInstance
 
-		m := &Module{GlobalSection: []*Global{global}}
+		table := &TableInstance{}
+		s.addTableInstance(table)
 
+		// Index must be reused.
+		require.Equal(t, expectedAddr, table.index)
+
+		// And the others must be intact.
+		require.Equal(t, tailInstance, s.Tables[maxAddr])
+
+		require.Equal(t, expectedReleasedTableIndex, s.releasedTableIndex)
+	})
+}
+
+func TestStore_releaseMemoryInstance(t *testing.T) {
+	s := NewStore(context.Background(), &catchContext{})
+	nonReleaseTargetAddr := memoryIndex(0)
+	releaseTargetAddr := memoryIndex(10)
+	s.Memories = make([]*MemoryInstance, releaseTargetAddr+1)
+
+	s.Memories[nonReleaseTargetAddr] = &MemoryInstance{} // Non-nil!
+	mem := &MemoryInstance{index: releaseTargetAddr}
+	s.Memories[releaseTargetAddr] = mem // Non-nil!
+
+	s.releaseMemoryInstance(mem)
+
+	// Ensure the release targets become nil.
+	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])
+}
+
+func TestStore_addMemoryInstance(t *testing.T) {
+	t.Run("no released index", func(t *testing.T) {
 		s := NewStore(context.Background(), &catchContext{})
-		target := &ModuleInstance{}
-		_, err = s.buildGlobalInstances(m, target)
-		require.NoError(t, err)
+		prevMaxAddr := memoryIndex(10)
+		s.Memories = make([]*MemoryInstance, prevMaxAddr+1)
 
-		// A global must be added to both store and module instance.
-		require.Len(t, s.Globals, 1)
-		require.Len(t, target.Globals, 1)
-		// Plus the added one must be same.
-		require.Equal(t, s.Globals[0], target.Globals[0])
+		for i := memoryIndex(0); i < 10; i++ {
+			expectedIndex := prevMaxAddr + 1 + i
+			mem := &MemoryInstance{}
+			s.addMemoryInstance(mem)
 
-		require.Equal(t, expectedValue, s.Globals[0].Val)
+			// After adding function intance to store, an funcaddr must be assigned.
+			require.Equal(t, expectedIndex, mem.index)
+		}
+	})
+	t.Run("reuse released index", func(t *testing.T) {
+		s := NewStore(context.Background(), &catchContext{})
+		expectedAddr := memoryIndex(10)
+		s.releasedMemoryIndex = []memoryIndex{1000, expectedAddr}
+		expectedReleasedMemoryIndex := s.releasedMemoryIndex[:1]
+
+		maxAddr := expectedAddr * 10
+		tailInstance := &MemoryInstance{}
+		s.Memories = make([]*MemoryInstance, maxAddr+1)
+		s.Memories[maxAddr] = tailInstance
+
+		mem := &MemoryInstance{}
+		s.addMemoryInstance(mem)
+
+		// Index must be reused.
+		require.Equal(t, expectedAddr, mem.index)
+
+		// And the others must be intact.
+		require.Equal(t, tailInstance, s.Memories[maxAddr])
+
+		require.Equal(t, expectedReleasedMemoryIndex, s.releasedMemoryIndex)
 	})
 }
 
-func TestStore_executeConstExpression(t *testing.T) {
-	t.Run("invalid optcode", func(t *testing.T) {
-		expr := &ConstantExpression{Opcode: OpcodeNop}
-		_, _, err := executeConstExpression(nil, expr)
+func TestStore_resolveImports(t *testing.T) {
+	const moduleName = "test"
+	const name = "target"
+
+	t.Run("module not instantiated", func(t *testing.T) {
+		s := NewStore(context.Background(), &catchContext{})
+		_, _, _, _, _, err := s.resolveImports(&Module{ImportSection: []*Import{{Module: "unknown", Name: "unknown"}}})
 		require.Error(t, err)
+		require.Contains(t, err.Error(), "module \"unknown\" not instantiated")
 	})
-	t.Run("non global expr", func(t *testing.T) {
-		for _, vt := range []ValueType{ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64} {
-			t.Run(ValueTypeName(vt), func(t *testing.T) {
-				t.Run("valid", func(t *testing.T) {
-					// Allocate bytes with enough size for all types.
-					expr := &ConstantExpression{Data: make([]byte, 8)}
-					switch vt {
-					case ValueTypeI32:
-						expr.Data[0] = 1
-						expr.Opcode = OpcodeI32Const
-					case ValueTypeI64:
-						expr.Data[0] = 2
-						expr.Opcode = OpcodeI64Const
-					case ValueTypeF32:
-						binary.LittleEndian.PutUint32(expr.Data, math.Float32bits(math.MaxFloat32))
-						expr.Opcode = OpcodeF32Const
-					case ValueTypeF64:
-						binary.LittleEndian.PutUint64(expr.Data, math.Float64bits(math.MaxFloat64))
-						expr.Opcode = OpcodeF64Const
-					}
-
-					raw, actualType, err := executeConstExpression(nil, expr)
-					require.NoError(t, err)
-					require.Equal(t, vt, actualType)
-					require.NotNil(t, raw)
-
-					switch vt {
-					case ValueTypeI32:
-						actual, ok := raw.(int32)
-						require.True(t, ok)
-						require.Equal(t, int32(1), actual)
-					case ValueTypeI64:
-						actual, ok := raw.(int64)
-						require.True(t, ok)
-						require.Equal(t, int64(2), actual)
-					case ValueTypeF32:
-						actual, ok := raw.(float32)
-						require.True(t, ok)
-						require.Equal(t, float32(math.MaxFloat32), actual)
-					case ValueTypeF64:
-						actual, ok := raw.(float64)
-						require.True(t, ok)
-						require.Equal(t, float64(math.MaxFloat64), actual)
-					}
-				})
-				t.Run("invalid", func(t *testing.T) {
-					// Empty data must be failure.
-					expr := &ConstantExpression{Data: make([]byte, 0)}
-					switch vt {
-					case ValueTypeI32:
-						expr.Opcode = OpcodeI32Const
-					case ValueTypeI64:
-						expr.Opcode = OpcodeI64Const
-					case ValueTypeF32:
-						expr.Opcode = OpcodeF32Const
-					case ValueTypeF64:
-						expr.Opcode = OpcodeF64Const
-					}
-					_, _, err := executeConstExpression(nil, expr)
-					require.Error(t, err)
-				})
-			})
-		}
+	t.Run("export instance not found", func(t *testing.T) {
+		s := NewStore(context.Background(), &catchContext{})
+		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\"")
 	})
-	t.Run("global expr", func(t *testing.T) {
-		t.Run("failed to read global index", func(t *testing.T) {
-			// Empty data for global index is invalid.
-			expr := &ConstantExpression{Data: make([]byte, 0), Opcode: OpcodeGlobalGet}
-			_, _, err := executeConstExpression(nil, expr)
+	t.Run("func", func(t *testing.T) {
+		t.Run("unknwon type", func(t *testing.T) {
+			s := NewStore(context.Background(), &catchContext{})
+			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("global index out of range", func(t *testing.T) {
-			// Data holds the index in leb128 and this time the value exceeds len(globals) (=0).
-			expr := &ConstantExpression{Data: []byte{1}, Opcode: OpcodeGlobalGet}
-			var globals []*GlobalInstance
-			_, _, err := executeConstExpression(globals, expr)
+		t.Run("signature mismatch", func(t *testing.T) {
+			s := NewStore(context.Background(), &catchContext{})
+			s.ModuleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
+				Function: &FunctionInstance{FunctionType: &TypeInstance{Type: &FunctionType{}}},
+			}}, Name: moduleName}
+			m := &Module{
+				TypeSection:   []*FunctionType{{Results: []ValueType{ValueTypeF32}}},
+				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: null_f32 != null_null")
 		})
-
 		t.Run("ok", func(t *testing.T) {
-			for _, tc := range []struct {
-				valueType ValueType
-				val       uint64
-			}{
-				{valueType: ValueTypeI32, val: 10},
-				{valueType: ValueTypeI64, val: 20},
-				{valueType: ValueTypeF32, val: uint64(math.Float32bits(634634432.12311))},
-				{valueType: ValueTypeF64, val: math.Float64bits(1.12312311)},
-			} {
-				t.Run(ValueTypeName(tc.valueType), func(t *testing.T) {
-					// The index specified in Data equals zero.
-					expr := &ConstantExpression{Data: []byte{0}, Opcode: OpcodeGlobalGet}
-					globals := []*GlobalInstance{{Val: tc.val, Type: &GlobalType{ValType: tc.valueType}}}
-
-					val, actualType, err := executeConstExpression(globals, expr)
-					require.NoError(t, err)
-					require.Equal(t, tc.valueType, actualType)
-					require.NotNil(t, val)
-
-					switch tc.valueType {
-					case ValueTypeI32:
-						actual, ok := val.(int32)
-						require.True(t, ok)
-						require.Equal(t, int32(tc.val), actual)
-					case ValueTypeI64:
-						actual, ok := val.(int64)
-						require.True(t, ok)
-						require.Equal(t, int64(tc.val), actual)
-					case ValueTypeF32:
-						actual, ok := val.(float32)
-						require.True(t, ok)
-						require.Equal(t, wasm.DecodeF32(tc.val), actual)
-					case ValueTypeF64:
-						actual, ok := val.(float64)
-						require.True(t, ok)
-						require.Equal(t, wasm.DecodeF64(tc.val), actual)
-					}
-				})
+			s := NewStore(context.Background(), &catchContext{})
+			f := &FunctionInstance{FunctionType: &TypeInstance{Type: &FunctionType{Results: []ValueType{ValueTypeF32}}}}
+			s.ModuleInstances[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: {
+				Function: f,
+			}}, Name: moduleName}
+			m := &Module{
+				TypeSection:   []*FunctionType{{Results: []ValueType{ValueTypeF32}}},
+				ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 0}},
 			}
+			functions, _, _, _, moduleImports, err := s.resolveImports(m)
+			require.NoError(t, err)
+			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(context.Background(), &catchContext{})
+			s.ModuleInstances[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")
+		})
+		t.Run("type mismatch", func(t *testing.T) {
+			s := NewStore(context.Background(), &catchContext{})
+			s.ModuleInstances[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")
+		})
+		t.Run("ok", func(t *testing.T) {
+			s := NewStore(context.Background(), &catchContext{})
+			inst := &GlobalInstance{Type: &GlobalType{ValType: ValueTypeI32}}
+			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)
+		})
+	})
+	t.Run("table", func(t *testing.T) {
+		t.Run("element type", func(t *testing.T) {
+			s := NewStore(context.Background(), &catchContext{})
+			s.ModuleInstances[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")
+		})
+		t.Run("minimum size mismatch", func(t *testing.T) {
+			s := NewStore(context.Background(), &catchContext{})
+			importTableType := &TableType{Limit: &LimitsType{Min: 2}}
+			s.ModuleInstances[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")
+		})
+		t.Run("maximum size mismatch", func(t *testing.T) {
+			s := NewStore(context.Background(), &catchContext{})
+			max := uint32(10)
+			importTableType := &TableType{Limit: &LimitsType{Max: &max}}
+			s.ModuleInstances[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")
+		})
+		t.Run("ok", func(t *testing.T) {
+			s := NewStore(context.Background(), &catchContext{})
+			max := uint32(10)
+			tableInst := &TableInstance{Max: &max}
+			s.ModuleInstances[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)
 		})
 	})
+	t.Run("memory", func(t *testing.T) {
+		t.Run("minimum size mismatch", func(t *testing.T) {
+			s := NewStore(context.Background(), &catchContext{})
+			importMemoryType := &MemoryType{Min: 2}
+			s.ModuleInstances[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")
+		})
+		t.Run("maximum size mismatch", func(t *testing.T) {
+			s := NewStore(context.Background(), &catchContext{})
+			max := uint32(10)
+			importMemoryType := &MemoryType{Max: &max}
+			s.ModuleInstances[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")
+		})
+		t.Run("ok", func(t *testing.T) {
+			s := NewStore(context.Background(), &catchContext{})
+			max := uint32(10)
+			memoryInst := &MemoryInstance{Max: &max}
+			s.ModuleInstances[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)
+		})
+	})
+}
+
+func TestModuleInstance_validateData(t *testing.T) {
+	m := &ModuleInstance{MemoryInstance: &MemoryInstance{Buffer: make([]byte, 5)}}
+	for _, tc := range []struct {
+		name   string
+		data   []*DataSegment
+		expErr bool
+	}{
+		{
+			name: "ok",
+			data: []*DataSegment{
+				{OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x1}}, Init: []byte{0}},
+				{OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x2}}, Init: []byte{0}},
+			},
+		},
+		{
+			name: "out of bounds - single one byte",
+			data: []*DataSegment{
+				{OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x5}}, Init: []byte{0}},
+			},
+			expErr: true,
+		},
+		{
+			name: "out of bounds - multi bytes",
+			data: []*DataSegment{
+				{OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x3}}, Init: []byte{0, 1, 2}},
+			},
+			expErr: true,
+		},
+	} {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			err := m.validateData(tc.data)
+			if tc.expErr {
+				require.Error(t, err)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestModuleInstance_applyData(t *testing.T) {
+	m := &ModuleInstance{MemoryInstance: &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)
+}
+
+func TestModuleInstance_validateElements(t *testing.T) {
+	functionCounts := uint32(0xa)
+	m := &ModuleInstance{
+		TableInstance: &TableInstance{Table: make([]TableElement, 10)},
+		Functions:     make([]*FunctionInstance, 10),
+	}
+	for _, tc := range []struct {
+		name     string
+		elements []*ElementSegment
+		expErr   bool
+	}{
+		{
+			name: "ok",
+			elements: []*ElementSegment{
+				{OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x0}}, Init: []uint32{0, functionCounts - 1}},
+			},
+		},
+		{
+			name: "ok on edge",
+			elements: []*ElementSegment{
+				{OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x8}}, Init: []uint32{0, functionCounts - 1}},
+			},
+		},
+		{
+			name: "out of bounds",
+			elements: []*ElementSegment{
+				{OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x9}}, Init: []uint32{0, functionCounts - 1}},
+			},
+			expErr: true,
+		},
+		{
+			name: "unknown function",
+			elements: []*ElementSegment{
+				{OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x0}}, Init: []uint32{0, functionCounts}},
+			},
+			expErr: true,
+		},
+	} {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			err := m.validateElements(tc.elements)
+			if tc.expErr {
+				require.Error(t, err)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestModuleInstance_applyElements(t *testing.T) {
+	functionCounts := uint32(0xa)
+	m := &ModuleInstance{
+		TableInstance: &TableInstance{Table: make([]TableElement, 10)},
+		Functions:     make([]*FunctionInstance, 10),
+	}
+	targetAddr, targetOffset := uint32(1), byte(0)
+	targetAddr2, targetOffset2 := functionCounts-1, byte(0x8)
+	m.Functions[targetAddr] = &FunctionInstance{FunctionType: &TypeInstance{}, Index: FunctionIndex(targetAddr)}
+	m.Functions[targetAddr2] = &FunctionInstance{FunctionType: &TypeInstance{}, Index: FunctionIndex(targetAddr2)}
+	m.applyElements([]*ElementSegment{
+		{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)
+}
+
+func Test_newModuleInstance(t *testing.T) {
+
 }
diff --git a/tests/spectest/spec_test.go b/tests/spectest/spec_test.go
index 7b55a067..bdc9755d 100644
--- a/tests/spectest/spec_test.go
+++ b/tests/spectest/spec_test.go
@@ -351,11 +351,7 @@ func runTest(t *testing.T, newEngine func() wasm.Engine) {
 						}
 						buf, err := testcases.ReadFile(testdataPath(c.Filename))
 						require.NoError(t, err, msg)
-						mod, err := binary.DecodeModule(buf)
-						if err == nil {
-							_, err = store.Instantiate(mod, "")
-						}
-						require.Error(t, err, msg)
+						requireInstantiationError(t, store, buf, msg)
 					case "assert_trap":
 						moduleName := lastInstanceName
 						if c.Action.Module != "" {
@@ -380,11 +376,7 @@ func runTest(t *testing.T, newEngine func() wasm.Engine) {
 						}
 						buf, err := testcases.ReadFile(testdataPath(c.Filename))
 						require.NoError(t, err, msg)
-						mod, err := binary.DecodeModule(buf)
-						if err == nil {
-							_, err = store.Instantiate(mod, "")
-						}
-						require.Error(t, err, msg)
+						requireInstantiationError(t, store, buf, msg)
 					case "assert_exhaustion":
 						moduleName := lastInstanceName
 						if c.Action.Module != "" {
@@ -409,20 +401,11 @@ func runTest(t *testing.T, newEngine func() wasm.Engine) {
 						}
 						buf, err := testcases.ReadFile(testdataPath(c.Filename))
 						require.NoError(t, err, msg)
-						mod, err := binary.DecodeModule(buf)
-						if err == nil {
-							_, err = store.Instantiate(mod, "")
-						}
-						require.Error(t, err, msg)
+						requireInstantiationError(t, store, buf, msg)
 					case "assert_uninstantiable":
 						buf, err := testcases.ReadFile(testdataPath(c.Filename))
 						require.NoError(t, err, msg)
-
-						mod, err := binary.DecodeModule(buf)
-						require.NoError(t, err, msg)
-
-						_, err = store.Instantiate(mod, "")
-						require.Error(t, err, msg)
+						requireInstantiationError(t, store, buf, msg)
 					default:
 						t.Fatalf("unsupported command type: %s", c)
 					}
@@ -432,6 +415,21 @@ func runTest(t *testing.T, newEngine func() wasm.Engine) {
 	}
 }
 
+func requireInstantiationError(t *testing.T, store *wasm.Store, buf []byte, msg string) {
+	mod, err := binary.DecodeModule(buf)
+	if err != nil {
+		return
+	}
+
+	err = mod.Validate()
+	if err != nil {
+		return
+	}
+
+	_, err = store.Instantiate(mod, "")
+	require.Error(t, err, msg)
+}
+
 // basename avoids filepath.Base to ensure a forward slash is used even in Windows.
 // See https://pkg.go.dev/embed#hdr-Directives
 func basename(path string) string {