Repository: tetratelabs/wazero
Pull request #152 (2022-01-21 01:29:47)

Consolidate runtime errors
This commit centralizes which runtime errors are possibly returned by engines, 
and document all of them on under which condition they occur.

Notably, now our spectest for `assert_trap` not only checks if the runtime returns error,
but also checks the type of error. This way, we can ensure that 
interpreter and JIT engines return exactly the same error for the same condition.
In order to achieve that, I found some discrepancies in the semantics between 
JIT and interpreter and resolved them. Notably, I refactored the code for division and 
remainder calculation in JIT so that they are alined with interpreter and spectests.




Patch:
diff --git a/wasm/errors.go b/wasm/errors.go
index 26b046d6..e59dacd8 100644
--- a/wasm/errors.go
+++ b/wasm/errors.go
@@ -2,4 +2,30 @@ package wasm
 
 import "errors"
 
-var ErrCallStackOverflow = errors.New("callstack overflow")
+// All the erros are returned by Engine during the excecution of Wasm functions,
+// and they indicate that the Wasm virtual machine's state is unrecoverable.
+var (
+	// ErrRuntimeCallStackOverflow indicates that there are too many function calls,
+	// and the engine terminated the execution.
+	ErrRuntimeCallStackOverflow = errors.New("callstack overflow")
+	// ErrRuntimeInvalidConversionToInteger indicates the Wasm function tries to
+	// convert NaN floating point value to integers during trunc variant instructions.
+	ErrRuntimeInvalidConversionToInteger = errors.New("invalid conversion to integer")
+	// ErrRuntimeIntegerOverflow indicates that an integer arithmatic resulted in
+	// overflow value. For example, when the program tried to truncate a float value
+	// which doesn't fit in the range of target integer.
+	ErrRuntimeIntegerOverflow = errors.New("integer overflow")
+	// ErrRuntimeIntegerDivideByZero indicates that an integer div or rem instructions
+	// was executed with 0 as the divisor.
+	ErrRuntimeIntegerDivideByZero = errors.New("integer divide by zero")
+	// ErrRuntimeUnreachable means "unreachable" instruction was executed by the program.
+	ErrRuntimeUnreachable = errors.New("unreachable")
+	// ErrRuntimeOutOfBoundsMemoryAccess indicates that the program tried to access the
+	// region beyond the linear memory.
+	ErrRuntimeOutOfBoundsMemoryAccess = errors.New("out of bounds memory access")
+	// ErrRuntimeInvalidTableAcces means either offset to the table was out of bounds of table, or
+	// the target element in the table was uninitialized during call_indirect instruction.
+	ErrRuntimeInvalidTableAcces = errors.New("invalid table access")
+	// ErrRuntimeIndirectCallTypeMismatch indicates that the type check failed during call_indirect.
+	ErrRuntimeIndirectCallTypeMismatch = errors.New("indirect call type mismatch")
+)
diff --git a/wasm/jit/engine.go b/wasm/jit/engine.go
index d81085a3..b78859de 100644
--- a/wasm/jit/engine.go
+++ b/wasm/jit/engine.go
@@ -200,7 +200,7 @@ var callStackCeiling = uint64(buildoptions.CallStackCeiling)
 func (e *engine) callFramePush(callee *callFrame) {
 	e.callFrameNum++
 	if callStackCeiling < e.callFrameNum {
-		panic(wasm.ErrCallStackOverflow)
+		panic(wasm.ErrRuntimeCallStackOverflow)
 	}
 
 	// Push the new frame to the top of stack.
@@ -264,12 +264,15 @@ const (
 	jitCallStatusCodeUnreachable
 	// jitCallStatusCodeInvalidFloatToIntConversion means a invalid conversion of integer to floats happened.
 	jitCallStatusCodeInvalidFloatToIntConversion
-	// jitCallStatusCodeMemoryOutOfBounds means a out of bounds memory access happened.
+	// jitCallStatusCodeMemoryOutOfBounds means an out of bounds memory access happened.
 	jitCallStatusCodeMemoryOutOfBounds
-	// jitCallStatusCodeTableOutOfBounds means the offset to table exceeds the length of table during call_indirect.
-	jitCallStatusCodeTableOutOfBounds
+	// jitCallStatusCodeInvalidTableAccess means either offset to the table was out of bounds of table, or
+	// the target element in the table was uninitialized during call_indirect instruction.
+	jitCallStatusCodeInvalidTableAccess
 	// jitCallStatusCodeTypeMismatchOnIndirectCall means the type check failed during call_indirect.
 	jitCallStatusCodeTypeMismatchOnIndirectCall
+	jitCallStatusIntegerOverflow
+	jitCallStatusIntegerDivisionByZero
 )
 
 func (s jitCallStatusCode) String() (ret string) {
@@ -473,21 +476,20 @@ func (e *engine) execFunction(f *compiledFunction) {
 				}
 			}
 			currentFrame.continuationAddress = currentFrame.compiledFunction.codeInitialAddress + e.continuationAddressOffset
+		case jitCallStatusIntegerOverflow:
+			panic(wasm.ErrRuntimeIntegerOverflow)
+		case jitCallStatusIntegerDivisionByZero:
+			panic(wasm.ErrRuntimeIntegerDivideByZero)
 		case jitCallStatusCodeInvalidFloatToIntConversion:
-			// TODO: have wasm.ErrInvalidFloatToIntConversion and use it here.
-			panic("invalid float to int conversion")
+			panic(wasm.ErrRuntimeInvalidConversionToInteger)
 		case jitCallStatusCodeUnreachable:
-			// TODO: have wasm.ErrUnreachable and use it here.
-			panic("unreachable")
+			panic(wasm.ErrRuntimeUnreachable)
 		case jitCallStatusCodeMemoryOutOfBounds:
-			// TODO: have wasm.ErrMemoryOutOfBounds and use it here.
-			panic("out of bounds memory access")
-		case jitCallStatusCodeTableOutOfBounds:
-			// TODO: have wasm.ErrTableOutOfBounds and use it here.
-			panic("out of bounds table access")
+			panic(wasm.ErrRuntimeOutOfBoundsMemoryAccess)
+		case jitCallStatusCodeInvalidTableAccess:
+			panic(wasm.ErrRuntimeInvalidTableAcces)
 		case jitCallStatusCodeTypeMismatchOnIndirectCall:
-			// TODO: have wasm.ErrTypeMismatchOnIndirectCall and use it here.
-			panic("type mismatch on indirect function call")
+			panic(wasm.ErrRuntimeIndirectCallTypeMismatch)
 		}
 	}
 }
diff --git a/wasm/jit/jit_amd64.go b/wasm/jit/jit_amd64.go
index 3e0e2b34..f9ddb9b0 100644
--- a/wasm/jit/jit_amd64.go
+++ b/wasm/jit/jit_amd64.go
@@ -28,6 +28,10 @@ import (
 var (
 	zero64Bit                                     uint64 = 0
 	zero64BitAddress                              uintptr
+	minimum32BitSignedInt                         int32 = math.MinInt32
+	minimum32BitSignedIntAddress                  uintptr
+	minimum64BitSignedInt                         int64 = math.MinInt64
+	minimum64BitSignedIntAddress                  uintptr
 	float32SignBitMask                            uint32 = 1 << 31
 	float32RestBitMask                            uint32 = ^float32SignBitMask
 	float32SignBitMaskAddress                     uintptr
@@ -56,6 +60,8 @@ var (
 
 func init() {
 	zero64BitAddress = uintptr(unsafe.Pointer(&zero64Bit))
+	minimum32BitSignedIntAddress = uintptr(unsafe.Pointer(&minimum32BitSignedInt))
+	minimum64BitSignedIntAddress = uintptr(unsafe.Pointer(&minimum64BitSignedInt))
 	float32SignBitMaskAddress = uintptr(unsafe.Pointer(&float32SignBitMask))
 	float32RestBitMaskAddress = uintptr(unsafe.Pointer(&float32RestBitMask))
 	float64SignBitMaskAddress = uintptr(unsafe.Pointer(&float64SignBitMask))
@@ -1027,11 +1033,11 @@ func (c *amd64Compiler) compileCallIndirect(o *wazeroir.OperationCallIndirect) e
 
 	notLengthExceedJump := c.newProg()
 	notLengthExceedJump.To.Type = obj.TYPE_BRANCH
-	notLengthExceedJump.As = x86.AJCC
+	notLengthExceedJump.As = x86.AJHI
 	c.addInstruction(notLengthExceedJump)
 
-	// If it exceeds, we return the function with jitCallStatusCodeTableOutOfBounds.
-	c.setJITStatus(jitCallStatusCodeTableOutOfBounds)
+	// If it exceeds, we return the function with jitCallStatusCodeInvalidTableAccess.
+	c.setJITStatus(jitCallStatusCodeInvalidTableAccess)
 	c.returnFunction()
 
 	// Next we check if the target's type matches the operation's one.
@@ -1059,22 +1065,42 @@ func (c *amd64Compiler) compileCallIndirect(o *wazeroir.OperationCallIndirect) e
 
 	// At this point offset.register holds the address of wasm.TableElement at wasm.TableInstance[offset]
 	// So the target type ID lives at offset+tableElementTypeIDOffest, and we compare it
-	// with targetFunctionType.TypeID.
+	// with wasm.UninitializedTableElelemtTypeID to check if the element is initialized.
+	checkIfInitialized := c.newProg()
+	checkIfInitialized.As = x86.ACMPQ
+	checkIfInitialized.From.Type = obj.TYPE_MEM
+	checkIfInitialized.From.Reg = offset.register
+	checkIfInitialized.From.Offset = tableElementTypeIDOffest
+	checkIfInitialized.To.Type = obj.TYPE_CONST
+	checkIfInitialized.To.Offset = int64(wasm.UninitializedTableElelemtTypeID)
+	c.addInstruction(checkIfInitialized)
+
+	// Jump if the target is initialized element.
+	jumpIfInitialized := c.newProg()
+	jumpIfInitialized.To.Type = obj.TYPE_BRANCH
+	jumpIfInitialized.As = x86.AJNE
+	c.addInstruction(jumpIfInitialized)
+
+	// Otherwise, we return the function with jitCallStatusCodeInvalidTableAccess.
+	c.setJITStatus(jitCallStatusCodeInvalidTableAccess)
+	c.returnFunction()
+
 	targetFunctionType := c.f.ModuleInstance.Types[o.TypeIndex]
-	cmpTypeID := c.newProg()
-	cmpTypeID.As = x86.ACMPQ
-	cmpTypeID.From.Type = obj.TYPE_MEM
-	cmpTypeID.From.Reg = offset.register
-	cmpTypeID.From.Offset = tableElementTypeIDOffest
-	cmpTypeID.To.Type = obj.TYPE_CONST
-	cmpTypeID.To.Offset = int64(targetFunctionType.TypeID)
-	c.addInstruction(cmpTypeID)
+	checkIfTypeMatch := c.newProg()
+	jumpIfInitialized.To.SetTarget(checkIfTypeMatch)
+	checkIfTypeMatch.As = x86.ACMPQ
+	checkIfTypeMatch.From.Type = obj.TYPE_MEM
+	checkIfTypeMatch.From.Reg = offset.register
+	checkIfTypeMatch.From.Offset = tableElementTypeIDOffest
+	checkIfTypeMatch.To.Type = obj.TYPE_CONST
+	checkIfTypeMatch.To.Offset = int64(targetFunctionType.TypeID)
+	c.addInstruction(checkIfTypeMatch)
 
 	// Jump if the type matches.
-	typeMatchJump := c.newProg()
-	typeMatchJump.To.Type = obj.TYPE_BRANCH
-	typeMatchJump.As = x86.AJEQ
-	c.addInstruction(typeMatchJump)
+	jumpIfTypeMatch := c.newProg()
+	jumpIfTypeMatch.To.Type = obj.TYPE_BRANCH
+	jumpIfTypeMatch.As = x86.AJEQ
+	c.addInstruction(jumpIfTypeMatch)
 
 	// Otherwise, we return the function with jitCallStatusCodeTypeMismatchOnIndirectCall.
 	c.setJITStatus(jitCallStatusCodeTypeMismatchOnIndirectCall)
@@ -1082,7 +1108,7 @@ func (c *amd64Compiler) compileCallIndirect(o *wazeroir.OperationCallIndirect) e
 
 	// Now all checks passeed, so we start making function call.
 	readValue := c.newProg()
-	typeMatchJump.To.SetTarget(readValue)
+	jumpIfTypeMatch.To.SetTarget(readValue)
 	readValue.As = x86.AMOVQ
 	readValue.To.Type = obj.TYPE_REG
 	readValue.To.Reg = offset.register
@@ -1732,9 +1758,6 @@ func (c *amd64Compiler) compileDivForInts(is32Bit bool, signed bool) error {
 	// so we record it.
 	result := c.locationStack.pushValueOnRegister(x86.REG_AX)
 	result.setRegisterType(generalPurposeRegisterTypeInt)
-
-	// Make sure that the DX register (holding remainder of the division) is marked unused.
-	c.locationStack.markRegisterUnused(x86.REG_DX)
 	return nil
 }
 
@@ -1761,9 +1784,6 @@ func (c *amd64Compiler) compileRem(o *wazeroir.OperationRem) (err error) {
 	// so we record it.
 	result := c.locationStack.pushValueOnRegister(x86.REG_DX)
 	result.setRegisterType(generalPurposeRegisterTypeInt)
-
-	// Make sure that the AX register (holding quotient of the division) is marked unused.
-	c.locationStack.markRegisterUnused(x86.REG_AX)
 	return
 }
 
@@ -1787,18 +1807,81 @@ func (c *amd64Compiler) performDivisionOnInts(isRem, is32Bit, signed bool) error
 		remainderRegister = x86.REG_DX
 	)
 
+	if err := c.maybeMoveTopConditionalToFreeGeneralPurposeRegister(); err != nil {
+		return err
+	}
+
+	// Ensures that previous values on thses registers are saved to memory.
+	c.onValueReleaseRegisterToStack(quotientRegister)
+	c.onValueReleaseRegisterToStack(remainderRegister)
+
+	// In order to ensure x2 is placed on a temporary register for x2 value other than AX and DX,
+	// we mark them as used here.
+	c.locationStack.markRegisterUsed(quotientRegister)
+	c.locationStack.markRegisterUsed(remainderRegister)
+
+	// Ensure that x2 is placed on a register which is not either AX or DX.
 	x2 := c.locationStack.pop()
 	if err := c.ensureOnGeneralPurposeRegister(x2); err != nil {
 		return err
 	}
 
-	// We have to save the existing value on DX as the division instruction
-	// place the remainder of the result there.
-	if x2.register != remainderRegister {
-		c.onValueReleaseRegisterToStack(remainderRegister)
+	// Now we successfully place x2 on a temp register, so we no longer need to
+	// mark these regiseters used.
+	c.locationStack.markRegisterUnused(quotientRegister)
+	c.locationStack.markRegisterUnused(remainderRegister)
+
+	// Check if the x2 equals zero.
+	checkDivisorZero := c.newProg()
+	if is32Bit {
+		checkDivisorZero.As = x86.ACMPL
+	} else {
+		checkDivisorZero.As = x86.ACMPQ
 	}
+	checkDivisorZero.From.Type = obj.TYPE_REG
+	checkDivisorZero.From.Reg = x2.register
+	checkDivisorZero.To.Type = obj.TYPE_CONST
+	checkDivisorZero.To.Offset = 0
+	c.addInstruction(checkDivisorZero)
+
+	// Jump if the divisor is not zero.
+	jmpIfNotZero := c.newProg()
+	jmpIfNotZero.As = x86.AJNE
+	jmpIfNotZero.To.Type = obj.TYPE_BRANCH
+	c.addInstruction(jmpIfNotZero)
+
+	// Otherwise, we return with jitCallStatusIntegerDivisionByZero status.
+	c.setJITStatus(jitCallStatusIntegerDivisionByZero)
+	c.returnFunction()
+
+	c.addSetJmpOrigins(jmpIfNotZero)
+
+	// Next, we ensure that x1 is placed on AX.
+	x1 := c.locationStack.pop()
+	if x1.onRegister() && x1.register != quotientRegister {
+		// Move x1 to quotientRegister.
+		movX1ToDX := c.newProg()
+		movX1ToDX.To.Type = obj.TYPE_REG
+		movX1ToDX.To.Reg = quotientRegister
+		movX1ToDX.From.Type = obj.TYPE_REG
+		movX1ToDX.From.Reg = x1.register
+		if is32Bit {
+			movX1ToDX.As = x86.AMOVL
+		} else {
+			movX1ToDX.As = x86.AMOVQ
+		}
+		c.addInstruction(movX1ToDX)
+		c.locationStack.markRegisterUnused(x1.register)
+		x1.setRegister(quotientRegister)
+	} else if x1.onStack() {
+		x1.setRegister(quotientRegister)
+		c.moveStackToRegister(x1)
+	}
+
+	// Note: at this point, x1 is placed on AX, x2 is on a register which is not AX or DX.
 
 	isSignedRem := isRem && signed
+	isSignedDiv := !isRem && signed
 	var signedRemMinusOneDivisorJmp *obj.Prog
 	if isSignedRem {
 		// If this is for getting remainder of signed division,
@@ -1851,50 +1934,66 @@ func (c *amd64Compiler) performDivisionOnInts(isRem, is32Bit, signed bool) error
 
 		// Set the normal case's jump target.
 		c.addSetJmpOrigins(okJmp)
-	}
+	} else if isSignedDiv {
+		// For sigined division, we have to have branches for "math.MinInt{32,64} / -1"
+		// case which results in the floating point exception via division error as
+		// the resulting value exceeds the maximum of signed int.
 
-	// If x2 is placed in the quotient (AX) register, we just release it to the memory stack
-	// as AX must be set to the x1's value below.
-	if x2.register == quotientRegister {
-		c.releaseRegisterToStack(x2)
-	}
+		// First we compare the division with -1.
+		cmpDivisorWithMinusOne := c.newProg()
+		if is32Bit {
+			cmpDivisorWithMinusOne.As = x86.ACMPL
+		} else {
+			cmpDivisorWithMinusOne.As = x86.ACMPQ
+		}
+		cmpDivisorWithMinusOne.From.Type = obj.TYPE_REG
+		cmpDivisorWithMinusOne.From.Reg = x2.register
+		cmpDivisorWithMinusOne.To.Type = obj.TYPE_CONST
+		cmpDivisorWithMinusOne.To.Offset = -1
+		c.addInstruction(cmpDivisorWithMinusOne)
 
-	x1 := c.locationStack.pop()
-	// Ensure that x1 is placed on the quotient (AX) register.
-	if x1.register != quotientRegister {
-		c.onValueReleaseRegisterToStack(quotientRegister)
-		if x2.onConditionalRegister() {
-			c.moveConditionalToGeneralPurposeRegister(x1, quotientRegister)
-		} else if x1.onStack() {
-			x1.setRegister(quotientRegister)
-			c.moveStackToRegister(x1)
-			c.locationStack.markRegisterUsed(quotientRegister)
+		// If it doesn't equal minus one, we jump to the normal case.
+		nonMinusOneDivisorJmp := c.newProg()
+		nonMinusOneDivisorJmp.As = x86.AJNE
+		nonMinusOneDivisorJmp.To.Type = obj.TYPE_BRANCH
+		c.addInstruction(nonMinusOneDivisorJmp)
+
+		// Next we check if the quotient is the most negative value for the signed integer.
+		// That means whether or not we try to do (math.MaxInt32 / -1) or (math.Math.Int64 / -1) respectively.
+		cmpQuotientWithMinInt := c.newProg()
+		cmpQuotientWithMinInt.To.Type = obj.TYPE_MEM
+		if is32Bit {
+			cmpQuotientWithMinInt.As = x86.ACMPL
+			cmpQuotientWithMinInt.To.Offset = int64(minimum32BitSignedIntAddress)
 		} else {
-			moveX1ToQuotientRegister := c.newProg()
-			if is32Bit {
-				moveX1ToQuotientRegister.As = x86.AMOVL
-			} else {
-				moveX1ToQuotientRegister.As = x86.AMOVQ
-			}
-			moveX1ToQuotientRegister.To.Reg = quotientRegister
-			moveX1ToQuotientRegister.To.Type = obj.TYPE_REG
-			moveX1ToQuotientRegister.From.Reg = x1.register
-			moveX1ToQuotientRegister.From.Type = obj.TYPE_REG
-			c.addInstruction(moveX1ToQuotientRegister)
-			// We no longer uses the prev register of x1.
-			c.locationStack.releaseRegister(x1)
-			x1.setRegister(quotientRegister)
-			c.locationStack.markRegisterUsed(quotientRegister)
+			cmpQuotientWithMinInt.As = x86.ACMPQ
+			cmpQuotientWithMinInt.To.Offset = int64(minimum64BitSignedIntAddress)
 		}
-	}
+		cmpQuotientWithMinInt.From.Type = obj.TYPE_REG
+		cmpQuotientWithMinInt.From.Reg = x1.register
+		c.addInstruction(cmpQuotientWithMinInt)
+
+		// If it doesn't equal, we jump to the normal case.
+		jmpOK := c.newProg()
+		jmpOK.As = x86.AJNE
+		jmpOK.To.Type = obj.TYPE_BRANCH
+		c.addInstruction(jmpOK)
+
+		// Otherwise, we are trying to do (math.MaxInt32 / -1) or (math.Math.Int64 / -1),
+		// and that is the overflow in division as the result becomes 2^31 which is larger than
+		// the maximum of signed 32-bit int (2^31-1).
+		c.setJITStatus(jitCallStatusIntegerOverflow)
+		c.returnFunction()
 
-	// We have to save the existing value on DX as the division instruction
-	// place the remainder of the result there.
-	c.onValueReleaseRegisterToStack(remainderRegister)
+		// Set the normal case's jump target.
+		c.addSetJmpOrigins(nonMinusOneDivisorJmp, jmpOK)
+	}
 
 	// Now ready to emit the div instruction.
 	div := c.newProg()
 	div.To.Type = obj.TYPE_NONE
+	div.From.Reg = x2.register
+	div.From.Type = obj.TYPE_REG
 	// Since the div instructions takes 2n byte dividend placed in DX:AX registers...
 	// * signed case - we need to sign-extend the dividend into DX register via CDQ (32 bit) or CQO (64 bit).
 	// * unsigned case - we need to zero DX register via "XOR DX DX"
@@ -1932,16 +2031,6 @@ func (c *amd64Compiler) performDivisionOnInts(isRem, is32Bit, signed bool) error
 		c.addInstruction(zerosDX)
 	}
 
-	if x2.onRegister() {
-		div.From.Reg = x2.register
-		div.From.Type = obj.TYPE_REG
-		c.locationStack.markRegisterUnused(x2.register)
-	} else {
-		// On stack case.
-		div.From.Type = obj.TYPE_MEM
-		div.From.Reg = reservedRegisterForStackBasePointer
-		div.From.Offset = int64(x2.stackPointer) * 8
-	}
 	c.addInstruction(div)
 
 	// If this is signed rem instruction, we must set the jump target of
@@ -1953,6 +2042,7 @@ func (c *amd64Compiler) performDivisionOnInts(isRem, is32Bit, signed bool) error
 	// We mark them as unused so that we can push one of them onto the location stack at call sites.
 	c.locationStack.markRegisterUnused(remainderRegister)
 	c.locationStack.markRegisterUnused(quotientRegister)
+	c.locationStack.markRegisterUnused(x2.register)
 	return nil
 }
 
@@ -2641,19 +2731,19 @@ func (c *amd64Compiler) emitUnsignedI32TruncFromFloat(isFloat32Bit bool) error {
 	cmpWithMaxInt32PlusOne.To.Reg = source.register
 	c.addInstruction(cmpWithMaxInt32PlusOne)
 
-	// Jump if the source float values is above or equal math.MaxInt32+1.
-	jmpAboveOrEqualMaxIn32PlusOne := c.newProg()
-	jmpAboveOrEqualMaxIn32PlusOne.As = x86.AJCC
-	jmpAboveOrEqualMaxIn32PlusOne.To.Type = obj.TYPE_BRANCH
-	c.addInstruction(jmpAboveOrEqualMaxIn32PlusOne)
-
 	// Check the parity flag (set when the value is NaN), and if it is set, we should raise an exception.
 	jmpIfNaN := c.newProg()
 	jmpIfNaN.As = x86.AJPS // jump if parity is set.
 	jmpIfNaN.To.Type = obj.TYPE_BRANCH
 	c.addInstruction(jmpIfNaN)
 
-	// Next we conver the value as a signed integer.
+	// Jump if the source float value is above or equal math.MaxInt32+1.
+	jmpAboveOrEqualMaxIn32PlusOne := c.newProg()
+	jmpAboveOrEqualMaxIn32PlusOne.As = x86.AJCC
+	jmpAboveOrEqualMaxIn32PlusOne.To.Type = obj.TYPE_BRANCH
+	c.addInstruction(jmpAboveOrEqualMaxIn32PlusOne)
+
+	// Next we convert the value as a signed integer.
 	convert := c.newProg()
 	if isFloat32Bit {
 		convert.As = x86.ACVTTSS2SL
@@ -2728,10 +2818,10 @@ func (c *amd64Compiler) emitUnsignedI32TruncFromFloat(isFloat32Bit bool) error {
 	c.addInstruction(testIfMinus)
 
 	// If the result is minus, the conversion is invalid (from NaN or +Inf)
-	jmpIfNaNOrPlusInf := c.newProg()
-	jmpIfNaNOrPlusInf.As = x86.AJMI
-	jmpIfNaNOrPlusInf.To.Type = obj.TYPE_BRANCH
-	c.addInstruction(jmpIfNaNOrPlusInf)
+	jmpIfPlusInf := c.newProg()
+	jmpIfPlusInf.As = x86.AJMI
+	jmpIfPlusInf.To.Type = obj.TYPE_BRANCH
+	c.addInstruction(jmpIfPlusInf)
 
 	// Otherwise, we successfully converted the the source float minus (math.MaxInt32+1) to int.
 	// So, we retrieve the original source float value by adding the sign mask.
@@ -2748,12 +2838,14 @@ func (c *amd64Compiler) emitUnsignedI32TruncFromFloat(isFloat32Bit bool) error {
 	okJmpForAboveOrEqualMaxInt32PlusOne.To.Type = obj.TYPE_BRANCH
 	c.addInstruction(okJmpForAboveOrEqualMaxInt32PlusOne)
 
-	// Start emitting the error handling. These invalid float conversions are unrecoverable,
-	// so we must exit from the function with the dedicated status code.
-	c.addSetJmpOrigins(jmpIfMinusOrMinusInf, jmpIfNaN, jmpIfNaNOrPlusInf)
+	c.addSetJmpOrigins(jmpIfNaN)
 	c.setJITStatus(jitCallStatusCodeInvalidFloatToIntConversion)
 	c.returnFunction()
 
+	c.addSetJmpOrigins(jmpIfMinusOrMinusInf, jmpIfPlusInf)
+	c.setJITStatus(jitCallStatusIntegerOverflow)
+	c.returnFunction()
+
 	// We jump to the next instructions for valid cases.
 	c.addSetJmpOrigins(okJmpForLessThanMaxInt32PlusOne, okJmpForAboveOrEqualMaxInt32PlusOne)
 
@@ -2791,18 +2883,18 @@ func (c *amd64Compiler) emitUnsignedI64TruncFromFloat(isFloat32Bit bool) error {
 	cmpWithMaxInt32PlusOne.To.Reg = source.register
 	c.addInstruction(cmpWithMaxInt32PlusOne)
 
-	// Jump if the source float values is above or equal math.MaxInt64+1.
-	jmpAboveOrEqualMaxIn32PlusOne := c.newProg()
-	jmpAboveOrEqualMaxIn32PlusOne.As = x86.AJCC
-	jmpAboveOrEqualMaxIn32PlusOne.To.Type = obj.TYPE_BRANCH
-	c.addInstruction(jmpAboveOrEqualMaxIn32PlusOne)
-
 	// Check the parity flag (set when the value is NaN), and if it is set, we should raise an exception.
 	jmpIfNaN := c.newProg()
 	jmpIfNaN.As = x86.AJPS // jump if parity is set.
 	jmpIfNaN.To.Type = obj.TYPE_BRANCH
 	c.addInstruction(jmpIfNaN)
 
+	// Jump if the source float values is above or equal math.MaxInt64+1.
+	jmpAboveOrEqualMaxIn32PlusOne := c.newProg()
+	jmpAboveOrEqualMaxIn32PlusOne.As = x86.AJCC
+	jmpAboveOrEqualMaxIn32PlusOne.To.Type = obj.TYPE_BRANCH
+	c.addInstruction(jmpAboveOrEqualMaxIn32PlusOne)
+
 	// Next we convert the value as a signed integer.
 	convert := c.newProg()
 	if isFloat32Bit {
@@ -2878,10 +2970,10 @@ func (c *amd64Compiler) emitUnsignedI64TruncFromFloat(isFloat32Bit bool) error {
 	c.addInstruction(testIfMinus)
 
 	// If the result is minus, the conversion is invalid (from NaN or +Inf)
-	jmpIfNaNOrPlusInf := c.newProg()
-	jmpIfNaNOrPlusInf.As = x86.AJMI
-	jmpIfNaNOrPlusInf.To.Type = obj.TYPE_BRANCH
-	c.addInstruction(jmpIfNaNOrPlusInf)
+	jmpIfPlusInf := c.newProg()
+	jmpIfPlusInf.As = x86.AJMI
+	jmpIfPlusInf.To.Type = obj.TYPE_BRANCH
+	c.addInstruction(jmpIfPlusInf)
 
 	// Otherwise, we successfully converted the the source float minus (math.MaxInt64+1) to int.
 	// So, we retrieve the original source float value by adding the sign mask.
@@ -2898,12 +2990,14 @@ func (c *amd64Compiler) emitUnsignedI64TruncFromFloat(isFloat32Bit bool) error {
 	okJmpForAboveOrEqualMaxInt64PlusOne.To.Type = obj.TYPE_BRANCH
 	c.addInstruction(okJmpForAboveOrEqualMaxInt64PlusOne)
 
-	// Start emitting the error handling. These invalid flaot conversions are unrecoverable,
-	// so we must exit from the function with the dedicated status code.
-	c.addSetJmpOrigins(jmpIfMinusOrMinusInf, jmpIfNaN, jmpIfNaNOrPlusInf)
+	c.addSetJmpOrigins(jmpIfNaN)
 	c.setJITStatus(jitCallStatusCodeInvalidFloatToIntConversion)
 	c.returnFunction()
 
+	c.addSetJmpOrigins(jmpIfMinusOrMinusInf, jmpIfPlusInf)
+	c.setJITStatus(jitCallStatusIntegerOverflow)
+	c.returnFunction()
+
 	// We jump to the next instructions for valid cases.
 	c.addSetJmpOrigins(okJmpForLessThanMaxInt64PlusOne, okJmpForAboveOrEqualMaxInt64PlusOne)
 
@@ -2973,14 +3067,19 @@ func (c *amd64Compiler) emitSignedI32TruncFromFloat(isFloat32Bit bool) error {
 	c.addInstruction(checkIfNaN)
 
 	// Check the parity flag (set when the value is NaN), and if it is set, we should raise an exception.
-	jmpIfNaN := c.newProg()
-	jmpIfNaN.As = x86.AJPS // jump if parity is set.
-	jmpIfNaN.To.Type = obj.TYPE_BRANCH
-	c.addInstruction(jmpIfNaN)
+	jmpIfNotNaN := c.newProg()
+	jmpIfNotNaN.As = x86.AJPC // jump if parity is not set.
+	jmpIfNotNaN.To.Type = obj.TYPE_BRANCH
+	c.addInstruction(jmpIfNotNaN)
+
+	// If the value is NaN, we return the function with jitCallStatusCodeInvalidFloatToIntConversion.
+	c.setJITStatus(jitCallStatusCodeInvalidFloatToIntConversion)
+	c.returnFunction()
 
 	// Check if the value is larger than or equal the minimum 32-bit integer value,
 	// meaning that the value exceeds the lower bound of 32-bit signed integer range.
 	checkIfExceedsLowerBound := c.newProg()
+	jmpIfNotNaN.To.SetTarget(checkIfExceedsLowerBound)
 	if isFloat32Bit {
 		checkIfExceedsLowerBound.As = x86.AUCOMISS
 		checkIfExceedsLowerBound.From.Offset = int64(float32ForMinimumSigned32bitIntegerAdddress)
@@ -2993,9 +3092,13 @@ func (c *amd64Compiler) emitSignedI32TruncFromFloat(isFloat32Bit bool) error {
 	checkIfExceedsLowerBound.To.Reg = source.register
 	c.addInstruction(checkIfExceedsLowerBound)
 
-	// Jump if the value is -Inf.
+	// Jump if the value exceeds the lower bound.
 	jmpIfExceedsLowerBound := c.newProg()
-	jmpIfExceedsLowerBound.As = x86.AJCS
+	if isFloat32Bit {
+		jmpIfExceedsLowerBound.As = x86.AJCS
+	} else {
+		jmpIfExceedsLowerBound.As = x86.AJLS
+	}
 	jmpIfExceedsLowerBound.To.Type = obj.TYPE_BRANCH
 	c.addInstruction(jmpIfExceedsLowerBound)
 
@@ -3014,21 +3117,12 @@ func (c *amd64Compiler) emitSignedI32TruncFromFloat(isFloat32Bit bool) error {
 	c.addInstruction(checkIfMinimumSignedInt)
 
 	jmpIfMinimumSignedInt := c.newProg()
-	if isFloat32Bit {
-		jmpIfExceedsLowerBound.As = x86.AJCS
-	} else {
-		jmpIfExceedsLowerBound.As = x86.AJLS
-	}
 	jmpIfMinimumSignedInt.As = x86.AJCS // jump if the value is minus (= the minimum signed 32-bit int).
 	jmpIfMinimumSignedInt.To.Type = obj.TYPE_BRANCH
 	c.addInstruction(jmpIfMinimumSignedInt)
 
-	// Start emitting the error handling. These invalid flaot conversions are unrecoverable,
-	// so we must exit from the function with the dedicated status code.
-	// These existing code are jumped from cases where the original float value
-	// is either +-Inf, NaN or not in representative ranges of 32bit signed integer
-	c.addSetJmpOrigins(jmpIfExceedsLowerBound, jmpIfNaN)
-	c.setJITStatus(jitCallStatusCodeInvalidFloatToIntConversion)
+	c.addSetJmpOrigins(jmpIfExceedsLowerBound)
+	c.setJITStatus(jitCallStatusIntegerOverflow)
 	c.returnFunction()
 
 	// We jump to the next instructions for valid cases.
@@ -3100,14 +3194,18 @@ func (c *amd64Compiler) emitSignedI64TruncFromFloat(isFloat32Bit bool) error {
 	c.addInstruction(checkIfNaN)
 
 	// Check the parity flag (set when the value is NaN), and if it is set, we should raise an exception.
-	jmpIfNaN := c.newProg()
-	jmpIfNaN.As = x86.AJPS // jump if parity is set.
-	jmpIfNaN.To.Type = obj.TYPE_BRANCH
-	c.addInstruction(jmpIfNaN)
+	jmpIfNotNaN := c.newProg()
+	jmpIfNotNaN.As = x86.AJPC // jump if parity is not set.
+	jmpIfNotNaN.To.Type = obj.TYPE_BRANCH
+	c.addInstruction(jmpIfNotNaN)
+
+	c.setJITStatus(jitCallStatusCodeInvalidFloatToIntConversion)
+	c.returnFunction()
 
 	// Check if the value is larger than or equal the minimum 64-bit integer value,
 	// meaning that the value exceeds the lower bound of 64-bit signed integer range.
 	checkIfExceedsLowerBound := c.newProg()
+	jmpIfNotNaN.To.SetTarget(checkIfExceedsLowerBound)
 	if isFloat32Bit {
 		checkIfExceedsLowerBound.As = x86.AUCOMISS
 		checkIfExceedsLowerBound.From.Offset = int64(float32ForMinimumSigned64bitIntegerAdddress)
@@ -3145,12 +3243,8 @@ func (c *amd64Compiler) emitSignedI64TruncFromFloat(isFloat32Bit bool) error {
 	jmpIfMinimumSignedInt.To.Type = obj.TYPE_BRANCH
 	c.addInstruction(jmpIfMinimumSignedInt)
 
-	// Start emitting the error handling. These invalid flaot conversions are unrecoverable,
-	// so we must exit from the function with the dedicated status code.
-	// These existing code are jumped from cases where the original float value
-	// is either +-Inf, NaN or not in representative ranges of 64 bit signed integer
-	c.addSetJmpOrigins(jmpIfExceedsLowerBound, jmpIfNaN)
-	c.setJITStatus(jitCallStatusCodeInvalidFloatToIntConversion)
+	c.addSetJmpOrigins(jmpIfExceedsLowerBound)
+	c.setJITStatus(jitCallStatusIntegerOverflow)
 	c.returnFunction()
 
 	// We jump to the next instructions for valid cases.
diff --git a/wasm/store.go b/wasm/store.go
index 2f819768..27b0bbb8 100644
--- a/wasm/store.go
+++ b/wasm/store.go
@@ -1847,9 +1847,11 @@ func newTableInstance(min uint32, max *uint32) *TableInstance {
 	}
 	for i := range tableInst.Table {
 		tableInst.Table[i] = TableElement{
-			// We use math.MaxUint64 to represent the uninitialized elements.
-			FunctionAddress: math.MaxUint32,
+			FunctionTypeID: UninitializedTableElelemtTypeID,
 		}
 	}
 	return tableInst
 }
+
+// We use math.MaxUint64 to represent the uninitialized elements.
+var UninitializedTableElelemtTypeID FunctionTypeID = math.MaxUint64
diff --git a/wasm/wazeroir/interpreter.go b/wasm/wazeroir/interpreter.go
index 7545ddd9..36f3df50 100644
--- a/wasm/wazeroir/interpreter.go
+++ b/wasm/wazeroir/interpreter.go
@@ -74,7 +74,7 @@ func (it *interpreter) drop(r *InclusiveRange) {
 
 func (it *interpreter) pushFrame(frame *interpreterFrame) {
 	if callStackCeiling <= len(it.frames) {
-		panic(wasm.ErrCallStackOverflow)
+		panic(wasm.ErrRuntimeCallStackOverflow)
 	}
 	it.frames = append(it.frames, frame)
 }
@@ -443,6 +443,9 @@ func (it *interpreter) Call(f *wasm.FunctionInstance, params ...uint64) (results
 			it.frames = it.frames[:prevFrameLen]
 			err2, ok := v.(error)
 			if ok {
+				if err2.Error() == "runtime error: integer divide by zero" {
+					err2 = wasm.ErrRuntimeIntegerDivideByZero
+				}
 				err = fmt.Errorf("wasm runtime error: %w", err2)
 			} else {
 				err = fmt.Errorf("wasm runtime error: %v", v)
@@ -536,7 +539,7 @@ func (it *interpreter) callNativeFunc(f *interpreterFunction) {
 		// how the stack is modified, etc.
 		switch op.kind {
 		case OperationKindUnreachable:
-			panic("unreachable")
+			panic(wasm.ErrRuntimeUnreachable)
 		case OperationKindBr:
 			{
 				frame.pc = op.us[0]
@@ -574,15 +577,21 @@ func (it *interpreter) callNativeFunc(f *interpreterFunction) {
 		case OperationKindCallIndirect:
 			{
 				offset := it.pop()
-				target := it.functions[table.Table[offset].FunctionAddress]
+				if offset >= uint64(len(table.Table)) {
+					panic(wasm.ErrRuntimeInvalidTableAcces)
+				}
+				tableElement := table.Table[offset]
 				// Type check.
-				expType := target.funcInstance.FunctionType
-				if uint64(expType.TypeID) != op.us[1] {
-					panic("function type mismatch on call_indirect")
+				if uint64(tableElement.FunctionTypeID) != op.us[1] {
+					if tableElement.FunctionTypeID == wasm.UninitializedTableElelemtTypeID {
+						panic(wasm.ErrRuntimeInvalidTableAcces)
+					}
+					panic(wasm.ErrRuntimeIndirectCallTypeMismatch)
 				}
+				target := it.functions[table.Table[offset].FunctionAddress]
 				// Call in.
 				if target.hostFn != nil {
-					it.callHostFunc(target, it.stack[len(it.stack)-len(expType.Type.Params):]...)
+					it.callHostFunc(target, it.stack[len(it.stack)-len(target.funcInstance.FunctionType.Type.Params):]...)
 				} else {
 					it.callNativeFunc(target)
 				}
@@ -631,8 +640,14 @@ func (it *interpreter) callNativeFunc(f *interpreterFunction) {
 				base := op.us[1] + it.pop()
 				switch UnsignedType(op.b1) {
 				case UnsignedTypeI32, UnsignedTypeF32:
+					if uint64(len(memoryInst.Buffer)) < base+4 {
+						panic(wasm.ErrRuntimeOutOfBoundsMemoryAccess)
+					}
 					it.push(uint64(binary.LittleEndian.Uint32(memoryInst.Buffer[base:])))
 				case UnsignedTypeI64, UnsignedTypeF64:
+					if uint64(len(memoryInst.Buffer)) < base+8 {
+						panic(wasm.ErrRuntimeOutOfBoundsMemoryAccess)
+					}
 					it.push(binary.LittleEndian.Uint64(memoryInst.Buffer[base:]))
 				}
 				frame.pc++
@@ -640,6 +655,9 @@ func (it *interpreter) callNativeFunc(f *interpreterFunction) {
 		case OperationKindLoad8:
 			{
 				base := op.us[1] + it.pop()
+				if uint64(len(memoryInst.Buffer)) < base+1 {
+					panic(wasm.ErrRuntimeOutOfBoundsMemoryAccess)
+				}
 				switch SignedInt(op.b1) {
 				case SignedInt32, SignedInt64:
 					it.push(uint64(int8(memoryInst.Buffer[base])))
@@ -651,6 +669,9 @@ func (it *interpreter) callNativeFunc(f *interpreterFunction) {
 		case OperationKindLoad16:
 			{
 				base := op.us[1] + it.pop()
+				if uint64(len(memoryInst.Buffer)) < base+2 {
+					panic(wasm.ErrRuntimeOutOfBoundsMemoryAccess)
+				}
 				switch SignedInt(op.b1) {
 				case SignedInt32, SignedInt64:
 					it.push(uint64(int16(binary.LittleEndian.Uint16(memoryInst.Buffer[base:]))))
@@ -662,6 +683,9 @@ func (it *interpreter) callNativeFunc(f *interpreterFunction) {
 		case OperationKindLoad32:
 			{
 				base := op.us[1] + it.pop()
+				if uint64(len(memoryInst.Buffer)) < base+4 {
+					panic(wasm.ErrRuntimeOutOfBoundsMemoryAccess)
+				}
 				if op.b1 == 1 {
 					it.push(uint64(int32(binary.LittleEndian.Uint32(memoryInst.Buffer[base:]))))
 				} else {
@@ -675,8 +699,14 @@ func (it *interpreter) callNativeFunc(f *interpreterFunction) {
 				base := op.us[1] + it.pop()
 				switch UnsignedType(op.b1) {
 				case UnsignedTypeI32, UnsignedTypeF32:
+					if uint64(len(memoryInst.Buffer)) < base+4 {
+						panic(wasm.ErrRuntimeOutOfBoundsMemoryAccess)
+					}
 					binary.LittleEndian.PutUint32(memoryInst.Buffer[base:], uint32(val))
 				case UnsignedTypeI64, UnsignedTypeF64:
+					if uint64(len(memoryInst.Buffer)) < base+8 {
+						panic(wasm.ErrRuntimeOutOfBoundsMemoryAccess)
+					}
 					binary.LittleEndian.PutUint64(memoryInst.Buffer[base:], val)
 				}
 				frame.pc++
@@ -685,6 +715,9 @@ func (it *interpreter) callNativeFunc(f *interpreterFunction) {
 			{
 				val := byte(it.pop())
 				base := op.us[1] + it.pop()
+				if uint64(len(memoryInst.Buffer)) < base+1 {
+					panic(wasm.ErrRuntimeOutOfBoundsMemoryAccess)
+				}
 				memoryInst.Buffer[base] = val
 				frame.pc++
 			}
@@ -692,6 +725,9 @@ func (it *interpreter) callNativeFunc(f *interpreterFunction) {
 			{
 				val := uint16(it.pop())
 				base := op.us[1] + it.pop()
+				if uint64(len(memoryInst.Buffer)) < base+2 {
+					panic(wasm.ErrRuntimeOutOfBoundsMemoryAccess)
+				}
 				binary.LittleEndian.PutUint16(memoryInst.Buffer[base:], val)
 				frame.pc++
 			}
@@ -699,6 +735,9 @@ func (it *interpreter) callNativeFunc(f *interpreterFunction) {
 			{
 				val := uint32(it.pop())
 				base := op.us[1] + it.pop()
+				if uint64(len(memoryInst.Buffer)) < base+4 {
+					panic(wasm.ErrRuntimeOutOfBoundsMemoryAccess)
+				}
 				binary.LittleEndian.PutUint32(memoryInst.Buffer[base:], val)
 				frame.pc++
 			}
@@ -974,15 +1013,15 @@ func (it *interpreter) callNativeFunc(f *interpreterFunction) {
 				case SignedTypeInt32:
 					v2 := int32(it.pop())
 					v1 := int32(it.pop())
-					if v2 == 0 || (v1 == math.MinInt32 && v2 == -1) {
-						panic("undefined")
+					if v1 == math.MinInt32 && v2 == -1 {
+						panic(wasm.ErrRuntimeIntegerOverflow)
 					}
 					it.push(uint64(uint32(v1 / v2)))
 				case SignedTypeInt64:
 					v2 := int64(it.pop())
 					v1 := int64(it.pop())
-					if v2 == 0 || (v1 == math.MinInt64 && v2 == -1) {
-						panic("undefined")
+					if v1 == math.MinInt64 && v2 == -1 {
+						panic(wasm.ErrRuntimeIntegerOverflow)
 					}
 					it.push(uint64(v1 / v2))
 				case SignedTypeUint32:
@@ -1300,35 +1339,35 @@ func (it *interpreter) callNativeFunc(f *interpreterFunction) {
 					case SignedInt32:
 						v := math.Trunc(float64(math.Float32frombits(uint32(it.pop()))))
 						if math.IsNaN(v) {
-							panic("invalid conversion")
+							panic(wasm.ErrRuntimeInvalidConversionToInteger)
 						} else if v < math.MinInt32 || v > math.MaxInt32 {
-							panic("integer overflow")
+							panic(wasm.ErrRuntimeIntegerOverflow)
 						}
 						it.push(uint64(int32(v)))
 					case SignedInt64:
 						v := math.Trunc(float64(math.Float32frombits(uint32(it.pop()))))
 						res := int64(v)
 						if math.IsNaN(v) {
-							panic("invalid conversion")
+							panic(wasm.ErrRuntimeInvalidConversionToInteger)
 						} else if v < math.MinInt64 || v > 0 && res < 0 {
-							panic("integer overflow")
+							panic(wasm.ErrRuntimeIntegerOverflow)
 						}
 						it.push(uint64(res))
 					case SignedUint32:
 						v := math.Trunc(float64(math.Float32frombits(uint32(it.pop()))))
 						if math.IsNaN(v) {
-							panic("invalid conversion")
+							panic(wasm.ErrRuntimeInvalidConversionToInteger)
 						} else if v < 0 || v > math.MaxUint32 {
-							panic("integer overflow")
+							panic(wasm.ErrRuntimeIntegerOverflow)
 						}
 						it.push(uint64(uint32(v)))
 					case SignedUint64:
 						v := math.Trunc(float64(math.Float32frombits(uint32(it.pop()))))
 						res := uint64(v)
 						if math.IsNaN(v) {
-							panic("invalid conversion")
+							panic(wasm.ErrRuntimeInvalidConversionToInteger)
 						} else if v < 0 || v > float64(res) {
-							panic("integer overflow")
+							panic(wasm.ErrRuntimeIntegerOverflow)
 						}
 						it.push(res)
 					}
@@ -1338,35 +1377,35 @@ func (it *interpreter) callNativeFunc(f *interpreterFunction) {
 					case SignedInt32:
 						v := math.Trunc(math.Float64frombits(it.pop()))
 						if math.IsNaN(v) {
-							panic("invalid conversion")
+							panic(wasm.ErrRuntimeInvalidConversionToInteger)
 						} else if v < math.MinInt32 || v > math.MaxInt32 {
-							panic("integer overflow")
+							panic(wasm.ErrRuntimeIntegerOverflow)
 						}
 						it.push(uint64(int32(v)))
 					case SignedInt64:
 						v := math.Trunc(math.Float64frombits(it.pop()))
 						res := int64(v)
 						if math.IsNaN(v) {
-							panic("invalid conversion")
+							panic(wasm.ErrRuntimeInvalidConversionToInteger)
 						} else if v < math.MinInt64 || v > 0 && res < 0 {
-							panic("integer overflow")
+							panic(wasm.ErrRuntimeIntegerOverflow)
 						}
 						it.push(uint64(res))
 					case SignedUint32:
 						v := math.Trunc(math.Float64frombits(it.pop()))
 						if math.IsNaN(v) {
-							panic("invalid conversion")
+							panic(wasm.ErrRuntimeInvalidConversionToInteger)
 						} else if v < 0 || v > math.MaxUint32 {
-							panic("integer overflow")
+							panic(wasm.ErrRuntimeIntegerOverflow)
 						}
 						it.push(uint64(uint32(v)))
 					case SignedUint64:
 						v := math.Trunc(math.Float64frombits(it.pop()))
 						res := uint64(v)
 						if math.IsNaN(v) {
-							panic("invalid conversion")
+							panic(wasm.ErrRuntimeInvalidConversionToInteger)
 						} else if v < 0 || v > float64(res) {
-							panic("integer overflow")
+							panic(wasm.ErrRuntimeIntegerOverflow)
 						}
 						it.push(res)
 					}


Test patch:
diff --git a/wasm/jit/engine_test.go b/wasm/jit/engine_test.go
index d0014a7b..871b3617 100644
--- a/wasm/jit/engine_test.go
+++ b/wasm/jit/engine_test.go
@@ -1,7 +1,6 @@
 package jit
 
 import (
-	"errors"
 	"os"
 	"reflect"
 	"sync"
@@ -109,7 +108,7 @@ func TestEngine_fac(t *testing.T) {
 	}
 
 	_, _, err = store.CallFunction("test", "fac-rec", 1073741824)
-	require.True(t, errors.Is(err, wasm.ErrCallStackOverflow))
+	require.ErrorIs(t, err, wasm.ErrRuntimeCallStackOverflow)
 }
 
 func TestEngine_unreachable(t *testing.T) {
diff --git a/wasm/jit/jit_amd64_test.go b/wasm/jit/jit_amd64_test.go
index 4876617e..c36fcd4b 100644
--- a/wasm/jit/jit_amd64_test.go
+++ b/wasm/jit/jit_amd64_test.go
@@ -2799,17 +2799,6 @@ func TestAmd64Compiler_compilPopcnt(t *testing.T) {
 	})
 }
 
-// The division by zero error must be caught by Go's runtime via x86's exception caught by kernel.
-func getDivisionByZeroErrorRecoverFunc(t *testing.T) func() {
-	return func() {
-		if e := recover(); e != nil {
-			err, ok := e.(error)
-			require.True(t, ok)
-			require.Equal(t, "runtime error: integer divide by zero", err.Error())
-		}
-	}
-}
-
 func TestAmd64Compiler_compile_and_or_xor_shl_shr_rotl_rotr(t *testing.T) {
 	for _, tc := range []struct {
 		name string
@@ -3107,7 +3096,7 @@ func TestAmd64Compiler_compileDiv(t *testing.T) {
 					tc := tc
 					t.Run(tc.name, func(t *testing.T) {
 						const dxValue uint64 = 111111
-						for i, vs := range []struct {
+						for _, vs := range []struct {
 							x1Value, x2Value uint32
 						}{
 							{x1Value: 2, x2Value: 1},
@@ -3115,12 +3104,13 @@ func TestAmd64Compiler_compileDiv(t *testing.T) {
 							{x1Value: 0, x2Value: 2},
 							{x1Value: 1, x2Value: 0},
 							{x1Value: 0, x2Value: 0},
+							{x1Value: 0x80000000, x2Value: 0xffffffff}, // This is equivalent to (-2^31 / -1) and results in overflow.
 							// Following cases produce different resulting bit patterns for signed and unsigned.
 							{x1Value: 0xffffffff /* -1 in signed 32bit */, x2Value: 1},
 							{x1Value: 0xffffffff /* -1 in signed 32bit */, x2Value: 0xfffffffe /* -2 in signed 32bit */},
 						} {
 							vs := vs
-							t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
+							t.Run(fmt.Sprintf("%d/%d", vs.x1Value, vs.x2Value), func(t *testing.T) {
 
 								env := newJITEnvironment()
 								compiler := requireNewCompiler(t)
@@ -3180,10 +3170,16 @@ func TestAmd64Compiler_compileDiv(t *testing.T) {
 								require.NoError(t, err)
 
 								// Run code.
+								env.exec(code)
+
 								if vs.x2Value == 0 {
-									defer getDivisionByZeroErrorRecoverFunc(t)()
+									require.Equal(t, jitCallStatusIntegerDivisionByZero, env.jitStatus())
+									return
+								} else if signed.signed && int32(vs.x2Value) == -1 && int32(vs.x1Value) == int32(math.MinInt32) {
+									// (-2^31 / -1) = 2 ^31 is larger than the upper limit of 32-bit signed integer.
+									require.Equal(t, jitCallStatusIntegerOverflow, env.jitStatus())
+									return
 								}
-								env.exec(code)
 
 								// Verify the stack is in the form of ["any value previously used by DX" + x1 / x2]
 								require.Equal(t, uint64(1), env.stackPointer())
@@ -3258,7 +3254,7 @@ func TestAmd64Compiler_compileDiv(t *testing.T) {
 					tc := tc
 					t.Run(tc.name, func(t *testing.T) {
 						const dxValue uint64 = 111111
-						for i, vs := range []struct {
+						for _, vs := range []struct {
 							x1Value, x2Value uint64
 						}{
 							{x1Value: 2, x2Value: 1},
@@ -3266,12 +3262,13 @@ func TestAmd64Compiler_compileDiv(t *testing.T) {
 							{x1Value: 0, x2Value: 1},
 							{x1Value: 1, x2Value: 0},
 							{x1Value: 0, x2Value: 0},
+							{x1Value: 0x8000000000000000, x2Value: 0xffffffffffffffff}, // This is equivalent to (-2^63 / -1) and results in overflow.
 							// Following cases produce different resulting bit patterns for signed and unsigned.
 							{x1Value: 0xffffffffffffffff /* -1 in signed 64bit */, x2Value: 1},
 							{x1Value: 0xffffffffffffffff /* -1 in signed 64bit */, x2Value: 0xfffffffffffffffe /* -2 in signed 64bit */},
 						} {
 							vs := vs
-							t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
+							t.Run(fmt.Sprintf("%d/%d", vs.x1Value, vs.x2Value), func(t *testing.T) {
 
 								env := newJITEnvironment()
 								compiler := requireNewCompiler(t)
@@ -3329,11 +3326,18 @@ func TestAmd64Compiler_compileDiv(t *testing.T) {
 								// Generate the code under test.
 								code, _, _, err := compiler.generate()
 								require.NoError(t, err)
+
 								// Run code.
+								env.exec(code)
+
 								if vs.x2Value == 0 {
-									defer getDivisionByZeroErrorRecoverFunc(t)()
+									require.Equal(t, jitCallStatusIntegerDivisionByZero, env.jitStatus())
+									return
+								} else if signed.signed && int64(vs.x2Value) == -1 && int64(vs.x1Value) == int64(math.MinInt64) {
+									// (-2^63 / -1) = 2 ^63 is larger than the upper limit of 64-bit signed integer.
+									require.Equal(t, jitCallStatusIntegerOverflow, env.jitStatus())
+									return
 								}
-								env.exec(code)
 
 								// Verify the stack is in the form of ["any value previously used by DX" + x1 / x2]
 								require.Equal(t, uint64(1), env.stackPointer())
@@ -3626,10 +3630,11 @@ func TestAmd64Compiler_compileRem(t *testing.T) {
 								require.NoError(t, err)
 
 								// Run code.
+								env.exec(code)
 								if vs.x2Value == 0 {
-									defer getDivisionByZeroErrorRecoverFunc(t)()
+									require.Equal(t, jitCallStatusIntegerDivisionByZero, env.jitStatus())
+									return
 								}
-								env.exec(code)
 
 								// Verify the stack is in the form of ["any value previously used by DX" + x1 / x2]
 								require.Equal(t, uint64(1), env.stackPointer())
@@ -3780,13 +3785,13 @@ func TestAmd64Compiler_compileRem(t *testing.T) {
 								// Generate the code under test.
 								code, _, _, err := compiler.generate()
 								require.NoError(t, err)
-								// Run code.
-								if vs.x2Value == 0 {
-									defer getDivisionByZeroErrorRecoverFunc(t)()
-								}
 
 								// Run code.
 								env.exec(code)
+								if vs.x2Value == 0 {
+									require.Equal(t, jitCallStatusIntegerDivisionByZero, env.jitStatus())
+									return
+								}
 
 								// Verify the stack is in the form of ["any value previously used by DX" + x1 / x2]
 								require.Equal(t, uint64(1), env.stackPointer())
@@ -4057,6 +4062,7 @@ func TestAmd64Compiler_compileITruncFromF(t *testing.T) {
 				1.37438953472e+11, /* = 1 << 37 */
 				-1.37438953472e+11,
 				-2147483649.0,
+				2147483648.0,
 				math.MinInt32,
 				math.MaxInt32,
 				math.MaxUint32,
@@ -4116,49 +4122,69 @@ func TestAmd64Compiler_compileITruncFromF(t *testing.T) {
 					env.exec(code)
 
 					// Check the result.
-					var shouldInvalidStatus bool
+					expStatus := jitCallStatusCodeReturned
+					if math.IsNaN(v) {
+						expStatus = jitCallStatusCodeInvalidFloatToIntConversion
+					}
 					if tc.inputType == wazeroir.Float32 && tc.outputType == wazeroir.SignedInt32 {
 						f32 := float32(v)
-						shouldInvalidStatus = math.IsNaN(v) || f32 < math.MinInt32 || f32 >= math.MaxInt32
-						if !shouldInvalidStatus {
+						if f32 < math.MinInt32 || f32 >= math.MaxInt32 {
+							expStatus = jitCallStatusIntegerOverflow
+						}
+						if expStatus == jitCallStatusCodeReturned {
 							require.Equal(t, int32(math.Trunc(float64(f32))), env.stackTopAsInt32())
 						}
 					} else if tc.inputType == wazeroir.Float32 && tc.outputType == wazeroir.SignedInt64 {
 						f32 := float32(v)
-						shouldInvalidStatus = math.IsNaN(v) || f32 < math.MinInt64 || f32 >= math.MaxInt64
-						if !shouldInvalidStatus {
+						if f32 < math.MinInt64 || f32 >= math.MaxInt64 {
+							expStatus = jitCallStatusIntegerOverflow
+						}
+						if expStatus == jitCallStatusCodeReturned {
 							require.Equal(t, int64(math.Trunc(float64(f32))), env.stackTopAsInt64())
 						}
 					} else if tc.inputType == wazeroir.Float64 && tc.outputType == wazeroir.SignedInt32 {
-						shouldInvalidStatus = math.IsNaN(v) || v < math.MinInt32 || v > math.MaxInt32
-						if !shouldInvalidStatus {
+						// ???????
+						if v < math.MinInt32 || v > math.MaxInt32 {
+							expStatus = jitCallStatusIntegerOverflow
+						}
+						if expStatus == jitCallStatusCodeReturned {
 							require.Equal(t, int32(math.Trunc(v)), env.stackTopAsInt32())
 						}
 					} else if tc.inputType == wazeroir.Float64 && tc.outputType == wazeroir.SignedInt64 {
-						shouldInvalidStatus = math.IsNaN(v) || v < math.MinInt64 || v >= math.MaxInt64
-						if !shouldInvalidStatus {
+						if v < math.MinInt64 || v >= math.MaxInt64 {
+							expStatus = jitCallStatusIntegerOverflow
+						}
+						if expStatus == jitCallStatusCodeReturned {
 							require.Equal(t, int64(math.Trunc(v)), env.stackTopAsInt64())
 						}
 					} else if tc.inputType == wazeroir.Float32 && tc.outputType == wazeroir.SignedUint32 {
 						f32 := float32(v)
-						shouldInvalidStatus = math.IsNaN(v) || f32 < 0 || f32 >= math.MaxUint32
-						if !shouldInvalidStatus {
+						if f32 < 0 || f32 >= math.MaxUint32 {
+							expStatus = jitCallStatusIntegerOverflow
+						}
+						if expStatus == jitCallStatusCodeReturned {
 							require.Equal(t, uint32(math.Trunc(float64(f32))), env.stackTopAsUint32())
 						}
 					} else if tc.inputType == wazeroir.Float64 && tc.outputType == wazeroir.SignedUint32 {
-						shouldInvalidStatus = math.IsNaN(v) || v < 0 || v > math.MaxUint32
-						if !shouldInvalidStatus {
+						if v < 0 || v > math.MaxUint32 {
+							expStatus = jitCallStatusIntegerOverflow
+						}
+						if expStatus == jitCallStatusCodeReturned {
 							require.Equal(t, uint32(math.Trunc(v)), env.stackTopAsUint32())
 						}
 					} else if tc.inputType == wazeroir.Float32 && tc.outputType == wazeroir.SignedUint64 {
 						f32 := float32(v)
-						shouldInvalidStatus = math.IsNaN(v) || f32 < 0 || f32 >= math.MaxUint64
-						if !shouldInvalidStatus {
+						if f32 < 0 || f32 >= math.MaxUint64 {
+							expStatus = jitCallStatusIntegerOverflow
+						}
+						if expStatus == jitCallStatusCodeReturned {
 							require.Equal(t, uint64(math.Trunc(float64(f32))), env.stackTopAsUint64())
 						}
 					} else if tc.inputType == wazeroir.Float64 && tc.outputType == wazeroir.SignedUint64 {
-						shouldInvalidStatus = math.IsNaN(v) || v < 0 || v >= math.MaxUint64
-						if !shouldInvalidStatus {
+						if v < 0 || v >= math.MaxUint64 {
+							expStatus = jitCallStatusIntegerOverflow
+						}
+						if expStatus == jitCallStatusCodeReturned {
 							require.Equal(t, uint64(math.Trunc(v)), env.stackTopAsUint64())
 						}
 					} else {
@@ -4166,7 +4192,7 @@ func TestAmd64Compiler_compileITruncFromF(t *testing.T) {
 					}
 
 					// Check the jit status code if necessary.
-					require.True(t, !shouldInvalidStatus || env.jitStatus() == jitCallStatusCodeInvalidFloatToIntConversion)
+					require.Equal(t, expStatus, env.jitStatus())
 				})
 			}
 		})
@@ -5719,7 +5745,7 @@ func TestAmd64Compiler_compileCallIndirect(t *testing.T) {
 
 		// Place the offfset value.
 		loc := compiler.locationStack.pushValueOnStack()
-		env.stack()[loc.stackPointer] = 1000000000
+		env.stack()[loc.stackPointer] = 10
 
 		// Now emit the code.
 		compiler.initializeReservedRegisters()
@@ -5733,7 +5759,40 @@ func TestAmd64Compiler_compileCallIndirect(t *testing.T) {
 		// Run code.
 		env.exec(code)
 
-		require.Equal(t, jitCallStatusCodeTableOutOfBounds, env.jitStatus())
+		require.Equal(t, jitCallStatusCodeInvalidTableAccess, env.jitStatus())
+	})
+
+	t.Run("uninitialized", func(t *testing.T) {
+		env := newJITEnvironment()
+		table := make([]wasm.TableElement, 10)
+		env.setTable(table)
+
+		compiler := requireNewCompiler(t)
+		targetOperation := &wazeroir.OperationCallIndirect{}
+		targetOffset := &wazeroir.OperationConstI32{Value: uint32(0)}
+		// Ensure that the module instance has the type information for targetOperation.TypeIndex,
+		compiler.f = &wasm.FunctionInstance{ModuleInstance: &wasm.ModuleInstance{Types: []*wasm.TypeInstance{{
+			Type: &wasm.FunctionType{}, TypeID: 1000}}}}
+		// and the typeID doesn't match the table[targetOffset]'s type ID.
+		table[0] = wasm.TableElement{FunctionTypeID: wasm.UninitializedTableElelemtTypeID}
+
+		// Place the offfset value.
+		err := compiler.compileConstI32(targetOffset)
+		require.NoError(t, err)
+
+		// Now emit the code.
+		compiler.initializeReservedRegisters()
+		require.NoError(t, compiler.compileCallIndirect(targetOperation))
+
+		// Generate the code under test.
+		compiler.returnFunction()
+		code, _, _, err := compiler.generate()
+		require.NoError(t, err)
+
+		// Run code.
+		env.exec(code)
+
+		require.Equal(t, jitCallStatusCodeInvalidTableAccess, env.jitStatus())
 	})
 
 	t.Run("type not match", func(t *testing.T) {
diff --git a/wasm/spectests/spec_test.go b/wasm/spectests/spec_test.go
index 3347ecdd..1fcb2f55 100644
--- a/wasm/spectests/spec_test.go
+++ b/wasm/spectests/spec_test.go
@@ -2,7 +2,6 @@ package spectests
 
 import (
 	"encoding/json"
-	"errors"
 	"fmt"
 	"math"
 	"os"
@@ -12,7 +11,6 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
 	"github.com/tetratelabs/wazero/wasm"
@@ -45,6 +43,9 @@ type (
 
 		// Set when type == "assert_malformed"
 		ModuleType string `json:"module_type"`
+
+		// Set when type == "assert_trap"
+		Text string `json:"text"`
 	}
 
 	commandAction struct {
@@ -100,7 +101,7 @@ func (c command) String() string {
 	case "assert_malformed":
 		// TODO:
 	case "assert_trap":
-		// TODO:
+		msg += fmt.Sprintf(", args: %v, error text:  %s", c.Action.Args, c.Text)
 	case "assert_invalid":
 		// TODO:
 	case "assert_exhaustion":
@@ -149,6 +150,35 @@ func (v commandActionVal) toUint64() uint64 {
 	}
 }
 
+// expectedError returns the expected runtime error when the command type equals assert_trap
+// which expectes engines to emit the errors corresponding command.Text field.
+func (c command) expectedError() (err error) {
+	if c.CommandType != "assert_trap" {
+		panic("unreachable")
+	}
+	switch c.Text {
+	case "out of bounds memory access":
+		err = wasm.ErrRuntimeOutOfBoundsMemoryAccess
+	case "indirect call type mismatch", "indirect call":
+		err = wasm.ErrRuntimeIndirectCallTypeMismatch
+	case "undefined element", "undefined":
+		err = wasm.ErrRuntimeInvalidTableAcces
+	case "integer overflow":
+		err = wasm.ErrRuntimeIntegerOverflow
+	case "invalid conversion to integer":
+		err = wasm.ErrRuntimeInvalidConversionToInteger
+	case "integer divide by zero":
+		err = wasm.ErrRuntimeIntegerDivideByZero
+	case "unreachable":
+		err = wasm.ErrRuntimeUnreachable
+	default:
+		if strings.HasPrefix(c.Text, "uninitialized") {
+			err = wasm.ErrRuntimeInvalidTableAcces
+		}
+	}
+	return
+}
+
 func addSpectestModule(t *testing.T, store *wasm.Store) {
 	for n, v := range map[string]reflect.Value{
 		"print":         reflect.ValueOf(func(*wasm.HostFunctionCallContext) {}),
@@ -249,12 +279,11 @@ func runTest(t *testing.T, newEngine func() wasm.Engine) {
 								msg += " in module " + c.Action.Module
 							}
 							vals, types, err := store.CallFunction(moduleName, c.Action.Field, args...)
-							if assert.NoError(t, err, msg) &&
-								assert.Equal(t, len(exps), len(vals), msg) &&
-								assert.Equal(t, len(exps), len(types), msg) {
-								for i, exp := range exps {
-									assertValueEq(t, vals[i], exp, types[i], msg)
-								}
+							require.NoError(t, err, msg)
+							require.Equal(t, len(exps), len(vals), msg)
+							require.Equal(t, len(exps), len(types), msg)
+							for i, exp := range exps {
+								requireValueEq(t, vals[i], exp, types[i], msg)
 							}
 						case "get":
 							_, exps := c.getAssertReturnArgsExps()
@@ -282,8 +311,8 @@ func runTest(t *testing.T, newEngine func() wasm.Engine) {
 								expType = wasm.ValueTypeF64
 							}
 							require.NotNil(t, actual, msg)
-							assert.Equal(t, expType, actual.Type.ValType, msg)
-							assert.Equal(t, exps[0], actual.Val, expType, msg)
+							require.Equal(t, expType, actual.Type.ValType, msg)
+							require.Equal(t, exps[0], actual.Val, expType, msg)
 						default:
 							t.Fatalf("unsupported action type type: %v", c)
 						}
@@ -312,7 +341,7 @@ func runTest(t *testing.T, newEngine func() wasm.Engine) {
 								msg += " in module " + c.Action.Module
 							}
 							_, _, err := store.CallFunction(moduleName, c.Action.Field, args...)
-							assert.Error(t, err, msg)
+							require.ErrorIs(t, err, c.expectedError(), msg)
 						default:
 							t.Fatalf("unsupported action type type: %v", c)
 						}
@@ -341,8 +370,7 @@ func runTest(t *testing.T, newEngine func() wasm.Engine) {
 								msg += " in module " + c.Action.Module
 							}
 							_, _, err := store.CallFunction(moduleName, c.Action.Field, args...)
-							assert.Error(t, err, msg)
-							assert.True(t, errors.Is(err, wasm.ErrCallStackOverflow), msg)
+							require.ErrorIs(t, err, wasm.ErrRuntimeCallStackOverflow, msg)
 						default:
 							t.Fatalf("unsupported action type type: %v", c)
 						}
@@ -376,27 +404,27 @@ func runTest(t *testing.T, newEngine func() wasm.Engine) {
 	}
 }
 
-func assertValueEq(t *testing.T, actual, expected uint64, valType wasm.ValueType, msg string) {
+func requireValueEq(t *testing.T, actual, expected uint64, valType wasm.ValueType, msg string) {
 	switch valType {
 	case wasm.ValueTypeI32:
-		assert.Equal(t, uint32(expected), uint32(actual), msg)
+		require.Equal(t, uint32(expected), uint32(actual), msg)
 	case wasm.ValueTypeI64:
-		assert.Equal(t, expected, actual, msg)
+		require.Equal(t, expected, actual, msg)
 	case wasm.ValueTypeF32:
 		expF := math.Float32frombits(uint32(expected))
 		actualF := math.Float32frombits(uint32(actual))
 		if math.IsNaN(float64(expF)) {
-			assert.True(t, math.IsNaN(float64(actualF)), msg)
+			require.True(t, math.IsNaN(float64(actualF)), msg)
 		} else {
-			assert.Equal(t, expF, actualF, msg)
+			require.Equal(t, expF, actualF, msg)
 		}
 	case wasm.ValueTypeF64:
 		expF := math.Float64frombits(expected)
 		actualF := math.Float64frombits(actual)
 		if math.IsNaN(expF) {
-			assert.True(t, math.IsNaN(actualF), msg)
+			require.True(t, math.IsNaN(actualF), msg)
 		} else {
-			assert.Equal(t, expF, actualF, msg)
+			require.Equal(t, expF, actualF, msg)
 		}
 	default:
 		t.Fail()