Repository: tetratelabs/wazero
Pull request #1669 (2023-08-29 00:11:10)

wazevo: passes traps, conversions spectest
#1496 


Patch:
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr.go b/internal/engine/wazevo/backend/isa/arm64/instr.go
index 67acb766..119a9855 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr.go
@@ -103,6 +103,8 @@ var defKinds = [numInstructionKinds]defKind{
 	fpuToInt:        defKindRD,
 	intToFpu:        defKindRD,
 	cCmpImm:         defKindNone,
+	movToFPSR:       defKindNone,
+	movFromFPSR:     defKindRD,
 }
 
 // defs returns the list of regalloc.VReg that are defined by the instruction.
@@ -206,6 +208,8 @@ var useKinds = [numInstructionKinds]useKind{
 	vecRRR:          useKindRNRM,
 	fpuToInt:        useKindRN,
 	intToFpu:        useKindRN,
+	movToFPSR:       useKindRN,
+	movFromFPSR:     useKindNone,
 }
 
 // uses returns the list of regalloc.VReg that are used by the instruction.
@@ -656,6 +660,16 @@ func (i *instruction) asALUBitmaskImm(aluOp aluOp, rn, rd regalloc.VReg, imm uin
 	}
 }
 
+func (i *instruction) asMovToFPSR(rn regalloc.VReg) {
+	i.kind = movToFPSR
+	i.rn = operandNR(rn)
+}
+
+func (i *instruction) asMovFromFPSR(rd regalloc.VReg) {
+	i.kind = movFromFPSR
+	i.rd = operandNR(rd)
+}
+
 func (i *instruction) asBitRR(bitOp bitOp, rd, rn regalloc.VReg, is64bit bool) {
 	i.kind = bitRR
 	i.rn, i.rd = operandNR(rn), operandNR(rd)
@@ -992,8 +1006,6 @@ func (i *instruction) String() (str string) {
 			formatVRegSized(i.rm.nr(), size),
 			condFlag(i.u1),
 		)
-	case movToFpu:
-		panic("TODO")
 	case movToVec:
 		var size byte
 		arr := vecArrangement(i.u1)
@@ -1066,10 +1078,10 @@ func (i *instruction) String() (str string) {
 		panic("TODO")
 	case vecTbl2:
 		panic("TODO")
-	case movToNZCV:
-		panic("TODO")
-	case movFromNZCV:
-		panic("TODO")
+	case movToFPSR:
+		str = fmt.Sprintf("msr fpsr, %s", formatVRegSized(i.rn.nr(), 64))
+	case movFromFPSR:
+		str = fmt.Sprintf("mrs %s fpsr", formatVRegSized(i.rd.nr(), 64))
 	case call:
 		if i.u2 > 0 {
 			str = fmt.Sprintf("bl #%#x", i.u2)
@@ -1077,11 +1089,9 @@ func (i *instruction) String() (str string) {
 			str = fmt.Sprintf("bl %s", ssa.FuncRef(i.u1))
 		}
 	case callInd:
-		str = fmt.Sprintf("bl %s", formatVRegSized(i.rn.nr(), 32))
+		str = fmt.Sprintf("bl %s", formatVRegSized(i.rn.nr(), 64))
 	case ret:
 		str = "ret"
-	case epiloguePlaceholder:
-		panic("TODO")
 	case br:
 		target := label(i.u1)
 		if i.u3 != 0 {
@@ -1117,14 +1127,8 @@ func (i *instruction) String() (str string) {
 				str = fmt.Sprintf("b.%s %s", c.flag(), target.String())
 			}
 		}
-	case indirectBr:
-		panic("TODO")
 	case adr:
 		str = fmt.Sprintf("adr %s, #%#x", formatVRegSized(i.rd.nr(), 64), int64(i.u1))
-	case word4:
-		panic("TODO")
-	case word8:
-		panic("TODO")
 	case brTableSequence:
 		if i.u3 == 0 { // The offsets haven't been resolved yet.
 			labels := make([]string, len(i.targets))
@@ -1148,8 +1152,6 @@ func (i *instruction) String() (str string) {
 				offsets,
 			)
 		}
-	case loadAddr:
-		panic("TODO")
 	case exitSequence:
 		str = fmt.Sprintf("exit_sequence %s", formatVRegSized(i.rn.nr(), 64))
 	case udf:
@@ -1270,8 +1272,6 @@ const (
 	intToFpu
 	// fpuCSel represents a 32/64-bit FP conditional select.
 	fpuCSel
-	// movToFpu represents a move from a GPR to a scalar FP register.
-	movToFpu
 	// movToVec represents a move to a vector element from a GPR.
 	movToVec
 	// movFromVec represents an unsigned move from a vector element to a GPR.
@@ -1298,36 +1298,24 @@ const (
 	vecTbl
 	// vecTbl2 represents a table vector lookup - two register table.
 	vecTbl2
-	// movToNZCV represents a move to the NZCV flags.
-	movToNZCV
-	// movFromNZCV represents a move from the NZCV flags.
-	movFromNZCV
+	// movToNZCV represents a move to the FPSR.
+	movToFPSR
+	// movFromNZCV represents a move from the FPSR.
+	movFromFPSR
 	// call represents a machine call instruction.
 	call
 	// callInd represents a machine indirect-call instruction.
 	callInd
 	// ret represents a machine return instruction.
 	ret
-	// epiloguePlaceholder is a placeholder instruction, generating no code, meaning that a function epilogue must be
-	// inserted there.
-	epiloguePlaceholder
 	// br represents an unconditional branch.
 	br
 	// condBr represents a conditional branch.
 	condBr
-	// trapIf represents a conditional trap.
-	// indirectBr represents an indirect branch through a register.
-	indirectBr
 	// adr represents a compute the address (using a PC-relative offset) of a memory location.
 	adr
-	// word4 represents a raw 32-bit word.
-	word4
-	// word8 represents a raw 64-bit word.
-	word8
 	// brTableSequence represents a jump-table sequence.
 	brTableSequence
-	// loadAddr represents a load address instruction.
-	loadAddr
 	// exitSequence consists of multiple instructions, and exits the execution immediately.
 	// See encodeExitSequence.
 	exitSequence
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go b/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
index 1b1ab41b..59ea3b33 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
@@ -284,11 +284,29 @@ func (i *instruction) encode(c backend.Compiler) {
 		c.Emit4Bytes(
 			sf<<31 | 0b111101001<<22 | imm<<16 | cond<<12 | 0b1<<11 | rn<<5 | nzcv,
 		)
+	case movFromFPSR:
+		rt := regNumberInEncoding[i.rd.realReg()]
+		c.Emit4Bytes(encodeSystemRegisterMove(rt, true))
+	case movToFPSR:
+		rt := regNumberInEncoding[i.rn.realReg()]
+		c.Emit4Bytes(encodeSystemRegisterMove(rt, false))
 	default:
 		panic(i.String())
 	}
 }
 
+// encodeSystemRegisterMove encodes as "System register move" in
+// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Branches--Exception-Generating-and-System-instructions?lang=en
+//
+// Note that currently we only supports read/write of FPSR.
+func encodeSystemRegisterMove(rt uint32, fromSystem bool) uint32 {
+	ret := 0b11010101<<24 | 0b11011<<16 | 0b01000100<<8 | 0b001<<5 | rt
+	if fromSystem {
+		ret |= 0b1 << 21
+	}
+	return ret
+}
+
 // encodeVecRRR encodes as either "Advanced SIMD three *" in
 // https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Scalar-Floating-Point-and-Advanced-SIMD?lang=en
 func encodeVecRRR(op vecOp, rd, rn, rm uint32, arr vecArrangement) uint32 {
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_instr.go b/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
index 5cf140b2..416fa6ad 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
@@ -206,21 +206,31 @@ func (m *machine) LowerInstr(instr *ssa.Instruction) {
 		result := instr.Return()
 		m.lowerPopcnt(x, result)
 	case ssa.OpcodeFcvtToSint:
-		x := instr.Arg()
+		x, ctx := instr.Arg2()
 		result := instr.Return()
-		m.lowerFpuToInt(result, x, true, x.Type() == ssa.TypeF64, result.Type().Bits() == 64)
+		rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone)
+		rd := operandNR(m.compiler.VRegOf(result))
+		ctxVReg := m.compiler.VRegOf(ctx)
+		m.lowerFpuToInt(rd, rn, ctxVReg, true, x.Type() == ssa.TypeF64, result.Type().Bits() == 64)
 	case ssa.OpcodeFcvtToUint:
-		x := instr.Arg()
+		x, ctx := instr.Arg2()
 		result := instr.Return()
-		m.lowerFpuToInt(result, x, false, x.Type() == ssa.TypeF64, result.Type().Bits() == 64)
+		rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone)
+		rd := operandNR(m.compiler.VRegOf(result))
+		ctxVReg := m.compiler.VRegOf(ctx)
+		m.lowerFpuToInt(rd, rn, ctxVReg, false, x.Type() == ssa.TypeF64, result.Type().Bits() == 64)
 	case ssa.OpcodeFcvtFromSint:
 		x := instr.Arg()
 		result := instr.Return()
-		m.lowerIntToFpu(result, x, true, x.Type() == ssa.TypeI64, result.Type().Bits() == 64)
+		rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone)
+		rd := operandNR(m.compiler.VRegOf(result))
+		m.lowerIntToFpu(rd, rn, true, x.Type() == ssa.TypeI64, result.Type().Bits() == 64)
 	case ssa.OpcodeFcvtFromUint:
 		x := instr.Arg()
 		result := instr.Return()
-		m.lowerIntToFpu(result, x, false, x.Type() == ssa.TypeI64, result.Type().Bits() == 64)
+		rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone)
+		rd := operandNR(m.compiler.VRegOf(result))
+		m.lowerIntToFpu(rd, rn, false, x.Type() == ssa.TypeI64, result.Type().Bits() == 64)
 	case ssa.OpcodeFdemote:
 		v := instr.Arg()
 		rn := m.getOperand_NR(m.compiler.ValueDefinition(v), extModeNone)
@@ -334,6 +344,8 @@ func (m *machine) lowerIDiv(execCtxVReg regalloc.VReg, rd, rn, rm operand, _64bi
 	}
 }
 
+const exitIfNotSequenceEncodingSize = 4 + exitWithCodeEncodingSize
+
 func (m *machine) exitIfNot(execCtxVReg regalloc.VReg, c cond, code wazevoapi.ExitCode) {
 	cbr := m.allocateInstr()
 	cbr.asCondBr(c, invalidLabel, false /* ignored */)
@@ -415,17 +427,47 @@ func (m *machine) lowerFpuUniOp(op fpuUniOp, in, out ssa.Value) {
 	m.insert(neg)
 }
 
-func (m *machine) lowerFpuToInt(dst, src ssa.Value, signed, src64bit, dst64bit bool) {
-	rn := m.getOperand_NR(m.compiler.ValueDefinition(src), extModeNone)
-	rd := operandNR(m.compiler.VRegOf(dst))
+func (m *machine) lowerFpuToInt(rd, rn operand, ctx regalloc.VReg, signed, src64bit, dst64bit bool) {
+	// First of all, we have to clear the FPU flags.
+	flagClear := m.allocateInstr()
+	flagClear.asMovToFPSR(xzrVReg)
+	m.insert(flagClear)
+
+	// Then, do the conversion which doesn't trap inherently.
 	cvt := m.allocateInstr()
 	cvt.asFpuToInt(rd, rn, signed, src64bit, dst64bit)
 	m.insert(cvt)
+
+	// After the conversion, check the FPU flags.
+	getFlag := m.allocateInstr()
+	getFlag.asMovFromFPSR(tmpRegVReg)
+	m.insert(getFlag)
+
+	// Check if the conversion was undefined by comparing the status with 1.
+	// See https://developer.arm.com/documentation/ddi0595/2020-12/AArch64-Registers/FPSR--Floating-point-Status-Register
+	alu := m.allocateInstr()
+	alu.asALU(aluOpSubS, operandNR(xzrVReg), operandNR(tmpRegVReg), operandImm12(1, 0), true)
+	m.insert(alu)
+
+	// If it is not undefined, we can return the result.
+	ok := m.allocateInstr()
+	ok.asCondBr(ne.asCond(), invalidLabel, false /* ignored */)
+	ok.condBrOffsetResolve(4 /* fpuCmp */ + exitIfNotSequenceEncodingSize + exitWithCodeEncodingSize + 4)
+	m.insert(ok)
+
+	// Otherwise, we have to choose the status depending on it is overflow or NaN conversion.
+
+	// Comparing itself to check if it is a NaN.
+	fpuCmp := m.allocateInstr()
+	fpuCmp.asFpuCmp(rn, rn, src64bit)
+	m.insert(fpuCmp)
+	// If the VC flag is not set (== VS flag is set), it is a NaN.
+	m.exitIfNot(ctx, vc.asCond(), wazevoapi.ExitCodeInvalidConversionToInteger)
+	// Otherwise, it is an overflow.
+	m.lowerExitWithCode(ctx, wazevoapi.ExitCodeIntegerOverflow)
 }
 
-func (m *machine) lowerIntToFpu(dst, src ssa.Value, signed, src64bit, dst64bit bool) {
-	rn := m.getOperand_NR(m.compiler.ValueDefinition(src), extModeNone)
-	rd := operandNR(m.compiler.VRegOf(dst))
+func (m *machine) lowerIntToFpu(rd, rn operand, signed, src64bit, dst64bit bool) {
 	cvt := m.allocateInstr()
 	cvt.asIntToFpu(rd, rn, signed, src64bit, dst64bit)
 	m.insert(cvt)
diff --git a/internal/engine/wazevo/call_engine.go b/internal/engine/wazevo/call_engine.go
index d4baf236..3d9fc869 100644
--- a/internal/engine/wazevo/call_engine.go
+++ b/internal/engine/wazevo/call_engine.go
@@ -176,6 +176,8 @@ func (c *callEngine) CallWithStack(ctx context.Context, paramResultStack []uint6
 			return wasmruntime.ErrRuntimeIntegerOverflow
 		case wazevoapi.ExitCodeIntegerDivisionByZero:
 			return wasmruntime.ErrRuntimeIntegerDivideByZero
+		case wazevoapi.ExitCodeInvalidConversionToInteger:
+			return wasmruntime.ErrRuntimeInvalidConversionToInteger
 		default:
 			panic("BUG")
 		}
diff --git a/internal/engine/wazevo/frontend/lower.go b/internal/engine/wazevo/frontend/lower.go
index 5770e350..9d8dcade 100644
--- a/internal/engine/wazevo/frontend/lower.go
+++ b/internal/engine/wazevo/frontend/lower.go
@@ -479,13 +479,13 @@ func (c *Compiler) lowerOpcode(op wasm.Opcode) {
 		if state.unreachable {
 			return
 		}
-		x := state.pop()
-		trunc := builder.AllocateInstruction()
-		trunc.AsFcvtToInt(x,
+		ret := builder.AllocateInstruction().AsFcvtToInt(
+			state.pop(),
+			c.execCtxPtrValue,
 			op == wasm.OpcodeI64TruncF64S || op == wasm.OpcodeI64TruncF32S || op == wasm.OpcodeI32TruncF32S || op == wasm.OpcodeI32TruncF64S,
 			op == wasm.OpcodeI64TruncF64S || op == wasm.OpcodeI64TruncF32S || op == wasm.OpcodeI64TruncF64U || op == wasm.OpcodeI64TruncF32U,
-		)
-		state.push(trunc.Insert(builder).Return())
+		).Insert(builder).Return()
+		state.push(ret)
 	case wasm.OpcodeI32ReinterpretF32:
 		if state.unreachable {
 			return
@@ -1246,24 +1246,22 @@ func (c *Compiler) lowerOpcode(op wasm.Opcode) {
 		if state.unreachable {
 			return
 		}
-		cvt := builder.AllocateInstruction()
-		cvt.AsFcvtFromInt(state.pop(),
+		result := builder.AllocateInstruction().AsFcvtFromInt(
+			state.pop(),
 			op == wasm.OpcodeF64ConvertI32S || op == wasm.OpcodeF64ConvertI64S,
 			true,
-		)
-		builder.InsertInstruction(cvt)
-		state.push(cvt.Return())
+		).Insert(builder).Return()
+		state.push(result)
 	case wasm.OpcodeF32ConvertI32S, wasm.OpcodeF32ConvertI64S, wasm.OpcodeF32ConvertI32U, wasm.OpcodeF32ConvertI64U:
 		if state.unreachable {
 			return
 		}
-		cvt := builder.AllocateInstruction()
-		cvt.AsFcvtFromInt(state.pop(),
+		result := builder.AllocateInstruction().AsFcvtFromInt(
+			state.pop(),
 			op == wasm.OpcodeF32ConvertI32S || op == wasm.OpcodeF32ConvertI64S,
 			false,
-		)
-		builder.InsertInstruction(cvt)
-		state.push(cvt.Return())
+		).Insert(builder).Return()
+		state.push(result)
 	case wasm.OpcodeF32DemoteF64:
 		if state.unreachable {
 			return
diff --git a/internal/engine/wazevo/ssa/instructions.go b/internal/engine/wazevo/ssa/instructions.go
index 3aacf8a9..ee47b959 100644
--- a/internal/engine/wazevo/ssa/instructions.go
+++ b/internal/engine/wazevo/ssa/instructions.go
@@ -738,12 +738,10 @@ const (
 	// `v = fcvt_to_uint x`.
 	OpcodeFcvtToUint
 
-	// OpcodeFcvtToSint ...
-	// `v = fcvt_to_sint x`.
+	// OpcodeFcvtToSint converts a floating point value to a signed integer: `v = FcvtToSint x`.
 	OpcodeFcvtToSint
 
-	// OpcodeFcvtToUintSat ...
-	// `v = fcvt_to_uint_sat x`.
+	// OpcodeFcvtToUintSat converts a floating point value to an unsigned integer: `v = FcvtToUintSat x`.
 	OpcodeFcvtToUintSat
 
 	// OpcodeFcvtToSintSat ...
@@ -871,8 +869,8 @@ var instructionSideEffects = [opcodeEnd]sideEffect{
 	OpcodeSelect:             sideEffectNone,
 	OpcodeFmin:               sideEffectNone,
 	OpcodeFneg:               sideEffectNone,
-	OpcodeFcvtToSint:         sideEffectNone,
-	OpcodeFcvtToUint:         sideEffectNone,
+	OpcodeFcvtToSint:         sideEffectTraps,
+	OpcodeFcvtToUint:         sideEffectTraps,
 	OpcodeFcvtFromSint:       sideEffectNone,
 	OpcodeFcvtFromUint:       sideEffectNone,
 	OpcodeFdemote:            sideEffectNone,
@@ -1588,7 +1586,7 @@ func (i *Instruction) AsFpromote(x Value) {
 }
 
 // AsFcvtFromInt initializes this instruction as an instruction with either OpcodeFcvtFromUint or OpcodeFcvtFromSint
-func (i *Instruction) AsFcvtFromInt(x Value, signed bool, dst64bit bool) {
+func (i *Instruction) AsFcvtFromInt(x Value, signed bool, dst64bit bool) *Instruction {
 	if signed {
 		i.opcode = OpcodeFcvtFromSint
 	} else {
@@ -1600,21 +1598,24 @@ func (i *Instruction) AsFcvtFromInt(x Value, signed bool, dst64bit bool) {
 	} else {
 		i.typ = TypeF32
 	}
+	return i
 }
 
 // AsFcvtToInt initializes this instruction as an instruction with either OpcodeFcvtToUint or OpcodeFcvtToSint
-func (i *Instruction) AsFcvtToInt(x Value, signed bool, dst64bit bool) {
+func (i *Instruction) AsFcvtToInt(x, ctx Value, signed bool, dst64bit bool) *Instruction {
 	if signed {
 		i.opcode = OpcodeFcvtToSint
 	} else {
 		i.opcode = OpcodeFcvtToUint
 	}
 	i.v = x
+	i.v2 = ctx
 	if dst64bit {
 		i.typ = TypeI64
 	} else {
 		i.typ = TypeI32
 	}
+	return i
 }
 
 // AsSExtend initializes this instruction as a sign extension instruction with OpcodeSExtend.
diff --git a/internal/engine/wazevo/wazevoapi/exitcode.go b/internal/engine/wazevo/wazevoapi/exitcode.go
index 08ca7661..8fc4687a 100644
--- a/internal/engine/wazevo/wazevoapi/exitcode.go
+++ b/internal/engine/wazevo/wazevoapi/exitcode.go
@@ -18,6 +18,7 @@ const (
 	ExitCodeIndirectCallTypeMismatch
 	ExitCodeIntegerDivisionByZero
 	ExitCodeIntegerOverflow
+	ExitCodeInvalidConversionToInteger
 	exitCodeMax
 )
 
@@ -48,6 +49,8 @@ func (e ExitCode) String() string {
 		return "integer_division_by_zero"
 	case ExitCodeIntegerOverflow:
 		return "integer_overflow"
+	case ExitCodeInvalidConversionToInteger:
+		return "invalid_conversion_to_integer"
 	}
 	panic("TODO")
 }


Test patch:
diff --git a/internal/engine/wazevo/backend/backend_test.go b/internal/engine/wazevo/backend/backend_test.go
index b19cc0a7..24ed8a09 100644
--- a/internal/engine/wazevo/backend/backend_test.go
+++ b/internal/engine/wazevo/backend/backend_test.go
@@ -1601,43 +1601,114 @@ L1 (SSA Block: blk0):
 		{
 			name: "float_conversions",
 			m:    testcases.FloatConversions.Module,
-			afterLoweringARM64: `
-L1 (SSA Block: blk0):
-	mov v2?.8b, v0.8b
-	mov v3?.8b, v1.8b
-	fcvtzs x4?, d2?
-	fcvtzs x5?, s3?
-	fcvtzs w6?, d2?
-	fcvtzs w7?, s3?
-	fcvtzu x8?, d2?
-	fcvtzu x9?, s3?
-	fcvtzu w10?, d2?
-	fcvtzu w11?, s3?
-	fcvt s12?, d2?
-	fcvt d13?, s3?
-	mov v1.8b, v13?.8b
-	mov v0.8b, v12?.8b
-	mov x7, x11?
-	mov x6, x10?
-	mov x5, x9?
-	mov x4, x8?
-	mov x3, x7?
-	mov x2, x6?
-	mov x1, x5?
-	mov x0, x4?
-	ret
-`,
 			afterFinalizeARM64: `
 L1 (SSA Block: blk0):
 	str x30, [sp, #-0x10]!
+	mov x8, x0
+	msr fpsr, xzr
 	fcvtzs x0, d0
+	mrs x27 fpsr
+	subs xzr, x27, #0x1
+	b.ne #0x44
+	fcmp d0, d0
+	b.vc #0x20
+	movz x27, #0xc, lsl 0
+	str w27, [x8]
+	exit_sequence x8
+	movz x27, #0xb, lsl 0
+	str w27, [x8]
+	exit_sequence x8
+	msr fpsr, xzr
 	fcvtzs x1, s1
+	mrs x27 fpsr
+	subs xzr, x27, #0x1
+	b.ne #0x44
+	fcmp s1, s1
+	b.vc #0x20
+	movz x27, #0xc, lsl 0
+	str w27, [x8]
+	exit_sequence x8
+	movz x27, #0xb, lsl 0
+	str w27, [x8]
+	exit_sequence x8
+	msr fpsr, xzr
 	fcvtzs w2, d0
+	mrs x27 fpsr
+	subs xzr, x27, #0x1
+	b.ne #0x44
+	fcmp d0, d0
+	b.vc #0x20
+	movz x27, #0xc, lsl 0
+	str w27, [x8]
+	exit_sequence x8
+	movz x27, #0xb, lsl 0
+	str w27, [x8]
+	exit_sequence x8
+	msr fpsr, xzr
 	fcvtzs w3, s1
+	mrs x27 fpsr
+	subs xzr, x27, #0x1
+	b.ne #0x44
+	fcmp s1, s1
+	b.vc #0x20
+	movz x27, #0xc, lsl 0
+	str w27, [x8]
+	exit_sequence x8
+	movz x27, #0xb, lsl 0
+	str w27, [x8]
+	exit_sequence x8
+	msr fpsr, xzr
 	fcvtzu x4, d0
+	mrs x27 fpsr
+	subs xzr, x27, #0x1
+	b.ne #0x44
+	fcmp d0, d0
+	b.vc #0x20
+	movz x27, #0xc, lsl 0
+	str w27, [x8]
+	exit_sequence x8
+	movz x27, #0xb, lsl 0
+	str w27, [x8]
+	exit_sequence x8
+	msr fpsr, xzr
 	fcvtzu x5, s1
+	mrs x27 fpsr
+	subs xzr, x27, #0x1
+	b.ne #0x44
+	fcmp s1, s1
+	b.vc #0x20
+	movz x27, #0xc, lsl 0
+	str w27, [x8]
+	exit_sequence x8
+	movz x27, #0xb, lsl 0
+	str w27, [x8]
+	exit_sequence x8
+	msr fpsr, xzr
 	fcvtzu w6, d0
+	mrs x27 fpsr
+	subs xzr, x27, #0x1
+	b.ne #0x44
+	fcmp d0, d0
+	b.vc #0x20
+	movz x27, #0xc, lsl 0
+	str w27, [x8]
+	exit_sequence x8
+	movz x27, #0xb, lsl 0
+	str w27, [x8]
+	exit_sequence x8
+	msr fpsr, xzr
 	fcvtzu w7, s1
+	mrs x27 fpsr
+	subs xzr, x27, #0x1
+	b.ne #0x44
+	fcmp s1, s1
+	b.vc #0x20
+	movz x27, #0xc, lsl 0
+	str w27, [x8]
+	exit_sequence x8
+	movz x27, #0xb, lsl 0
+	str w27, [x8]
+	exit_sequence x8
 	fcvt s0, d0
 	fcvt d1, s1
 	ldr x30, [sp], #0x10
@@ -2023,7 +2094,7 @@ L1 (SSA Block: blk0):
 	mov x1, x4?
 	mov x2, x2?
 	mov x3, x2?
-	bl w3?
+	bl x3?
 	mov x5?, x0
 	mov x0, x5?
 	ret
@@ -2036,7 +2107,7 @@ L1 (SSA Block: blk0):
 	ldr x8, [x1, #0x8]
 	ldr x1, [x1, #0x10]
 	mov x2, x3
-	bl w8
+	bl x8
 	ldr x30, [sp], #0x10
 	ret
 `,
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go b/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
index d839b743..d250ffb3 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
@@ -19,6 +19,9 @@ func TestInstruction_encode(t *testing.T) {
 		setup func(*instruction)
 		want  string
 	}{
+		{want: "3f441bd5", setup: func(i *instruction) { i.asMovToFPSR(xzrVReg) }},
+		{want: "21441bd5", setup: func(i *instruction) { i.asMovToFPSR(x1VReg) }},
+		{want: "21443bd5", setup: func(i *instruction) { i.asMovFromFPSR(x1VReg) }},
 		{want: "2f08417a", setup: func(i *instruction) { i.asCCmpImm(operandNR(x1VReg), 1, eq, 0b1111, false) }},
 		{want: "201841fa", setup: func(i *instruction) { i.asCCmpImm(operandNR(x1VReg), 1, ne, 0, true) }},
 		{want: "411ca32e", setup: func(i *instruction) {
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_instr_test.go b/internal/engine/wazevo/backend/isa/arm64/lower_instr_test.go
index 3250f9b9..473c13a6 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_instr_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_instr_test.go
@@ -1,6 +1,7 @@
 package arm64
 
 import (
+	"encoding/hex"
 	"fmt"
 	"strings"
 	"testing"
@@ -8,6 +9,7 @@ import (
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend"
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc"
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
 	"github.com/tetratelabs/wazero/internal/testing/require"
 )
 
@@ -340,3 +342,48 @@ exit_sequence x65535?
 		})
 	}
 }
+
+func Test_exitWithCodeEncodingSize(t *testing.T) {
+	_, _, m := newSetupWithMockContext()
+	m.lowerExitWithCode(x1VReg, wazevoapi.ExitCodeGrowStack)
+	m.FlushPendingInstructions()
+	m.encode(m.perBlockHead)
+	buf := m.compiler.Buf()
+	require.Equal(t, "3b0080d23b0000b93d0840f93b0c40f97f0300913e1040f9c0035fd6", hex.EncodeToString(buf))
+	require.Equal(t, exitWithCodeEncodingSize, len(buf))
+}
+
+func Test_exitIfNotSequenceEncodingSize(t *testing.T) {
+	_, _, m := newSetupWithMockContext()
+	m.exitIfNot(x1VReg, ne.asCond(), wazevoapi.ExitCodeGrowStack)
+	m.FlushPendingInstructions()
+	m.encode(m.perBlockHead)
+	buf := m.compiler.Buf()
+	require.Equal(t, "010100543b0080d23b0000b93d0840f93b0c40f97f0300913e1040f9c0035fd6", hex.EncodeToString(buf))
+	require.Equal(t, exitIfNotSequenceEncodingSize, len(buf))
+}
+
+func TestMachine_lowerFpuToInt(t *testing.T) {
+	_, _, m := newSetupWithMockContext()
+	m.lowerFpuToInt(operandNR(x1VReg), operandNR(x2VReg), x15VReg, false, false, false)
+	require.Equal(t, `
+msr fpsr, xzr
+fcvtzu w1, s2
+mrs x27 fpsr
+subs xzr, x27, #0x1
+b.ne #0x44
+fcmp w2, w2
+b.vc #0x20
+movz x27, #0xc, lsl 0
+str w27, [x15]
+exit_sequence x15
+movz x27, #0xb, lsl 0
+str w27, [x15]
+exit_sequence x15
+`, "\n"+formatEmittedInstructionsInCurrentBlock(m)+"\n")
+
+	m.FlushPendingInstructions()
+	m.encode(m.perBlockHead)
+	buf := m.compiler.Buf()
+	require.Equal(t, "3f441bd54100391e3b443bd57f0700f1210200544020221e070100549b0180d2fb0100b9fd0940f9fb0d40f97f030091fe1140f9c0035fd67b0180d2fb0100b9fd0940f9fb0d40f97f030091fe1140f9c0035fd6", hex.EncodeToString(buf))
+}
diff --git a/internal/engine/wazevo/backend/isa/arm64/machine_pro_epi_logue_test.go b/internal/engine/wazevo/backend/isa/arm64/machine_pro_epi_logue_test.go
index 0a89bf11..21e398b7 100644
--- a/internal/engine/wazevo/backend/isa/arm64/machine_pro_epi_logue_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/machine_pro_epi_logue_test.go
@@ -178,7 +178,7 @@ func TestMachine_insertStackBoundsCheck(t *testing.T) {
 	movz x27, #0xfff0, lsl 0
 	str x27, [x0, #0x40]
 	ldr x27, [x0, #0x50]
-	bl w27
+	bl x27
 `,
 		},
 		{
@@ -191,7 +191,7 @@ func TestMachine_insertStackBoundsCheck(t *testing.T) {
 	orr x27, xzr, #0x10
 	str x27, [x0, #0x40]
 	ldr x27, [x0, #0x50]
-	bl w27
+	bl x27
 `,
 		},
 	} {
diff --git a/internal/engine/wazevo/e2e_test.go b/internal/engine/wazevo/e2e_test.go
index bb96080e..85a25057 100644
--- a/internal/engine/wazevo/e2e_test.go
+++ b/internal/engine/wazevo/e2e_test.go
@@ -50,6 +50,7 @@ func TestSpectestV1(t *testing.T) {
 		//{name: "call_indirect"},
 		{name: "comments"},
 		{name: "custom"},
+		{name: "conversions"},
 		{name: "const"},
 		{name: "data"},
 		{name: "elem"},
@@ -100,7 +101,7 @@ func TestSpectestV1(t *testing.T) {
 		{name: "switch"},
 		{name: "token"},
 		{name: "type"},
-		//{name: "traps"},
+		{name: "traps"},
 		{name: "unreachable"},
 		{name: "unreached-invalid"},
 		{name: "unwind"},