Repository: tetratelabs/wazero
Pull request #1652 (2023-08-23 01:55:35)

wazevo: passes local_get.wast
#1496 


Patch:
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr.go b/internal/engine/wazevo/backend/isa/arm64/instr.go
index 6c039cb5..6c64c6c9 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr.go
@@ -98,6 +98,7 @@ var defKinds = [numInstructionKinds]defKind{
 	movFromVec:      defKindRD,
 	vecMisc:         defKindRD,
 	vecLanes:        defKindRD,
+	intToFpu:        defKindRD,
 }
 
 // defs returns the list of regalloc.VReg that are defined by the instruction.
@@ -196,6 +197,7 @@ var useKinds = [numInstructionKinds]useKind{
 	movFromVec:      useKindRN,
 	vecMisc:         useKindRN,
 	vecLanes:        useKindRN,
+	intToFpu:        useKindRN,
 }
 
 // uses returns the list of regalloc.VReg that are used by the instruction.
@@ -683,31 +685,31 @@ func (i *instruction) asFpuMov128(rd, rn regalloc.VReg) {
 	i.rn, i.rd = operandNR(rn), operandNR(rd)
 }
 
-func (i *instruction) asMovToVec(rd, rn regalloc.VReg, arr vecArrangement, index vecIndex) {
+func (i *instruction) asMovToVec(rd, rn operand, arr vecArrangement, index vecIndex) {
 	i.kind = movToVec
-	i.rd = operandNR(rd)
-	i.rn = operandNR(rn)
+	i.rd = rd
+	i.rn = rn
 	i.u1, i.u2 = uint64(arr), uint64(index)
 }
 
-func (i *instruction) asMovFromVec(rd, rn regalloc.VReg, arr vecArrangement, index vecIndex) {
+func (i *instruction) asMovFromVec(rd, rn operand, arr vecArrangement, index vecIndex) {
 	i.kind = movFromVec
-	i.rd = operandNR(rd)
-	i.rn = operandNR(rn)
+	i.rd = rd
+	i.rn = rn
 	i.u1, i.u2 = uint64(arr), uint64(index)
 }
 
-func (i *instruction) asVecMisc(op vecOp, rd, rn regalloc.VReg, arr vecArrangement) {
+func (i *instruction) asVecMisc(op vecOp, rd, rn operand, arr vecArrangement) {
 	i.kind = vecMisc
 	i.u1 = uint64(op)
-	i.rn, i.rd = operandNR(rn), operandNR(rd)
+	i.rn, i.rd = rn, rd
 	i.u2 = uint64(arr)
 }
 
-func (i *instruction) asVecLanes(op vecOp, rd, rn regalloc.VReg, arr vecArrangement) {
+func (i *instruction) asVecLanes(op vecOp, rd, rn operand, arr vecArrangement) {
 	i.kind = vecLanes
 	i.u1 = uint64(op)
-	i.rn, i.rd = operandNR(rn), operandNR(rd)
+	i.rn, i.rd = rn, rd
 	i.u2 = uint64(arr)
 }
 
@@ -905,7 +907,23 @@ func (i *instruction) String() (str string) {
 	case fpuToInt:
 		panic("TODO")
 	case intToFpu:
-		panic("TODO")
+		var op, src, dst string
+		if signed := i.u1 == 1; signed {
+			op = "scvtf"
+		} else {
+			op = "ucvtf"
+		}
+		if src64 := i.u2 == 1; src64 {
+			src = formatVRegSized(i.rn.nr(), 64)
+		} else {
+			src = formatVRegSized(i.rn.nr(), 32)
+		}
+		if dst64 := i.u3 == 1; dst64 {
+			dst = formatVRegWidthVec(i.rd.nr(), vecArrangementD)
+		} else {
+			dst = formatVRegWidthVec(i.rd.nr(), vecArrangementS)
+		}
+		str = fmt.Sprintf("%s %s, %s", op, dst, src)
 	case fpuCSel:
 		size := is64SizeBitToSize(i.u3)
 		str = fmt.Sprintf("fcsel %s, %s, %s, %s",
@@ -960,10 +978,19 @@ func (i *instruction) String() (str string) {
 	case vecRRR:
 		panic("TODO")
 	case vecMisc:
-		str = fmt.Sprintf("%s %s, %s",
-			vecOp(i.u1),
-			formatVRegVec(i.rd.nr(), vecArrangement(i.u2), vecIndexNone),
-			formatVRegVec(i.rn.nr(), vecArrangement(i.u2), vecIndexNone))
+		switch op := vecOp(i.u1); op {
+		case vecOpCvt32To64:
+			str = fmt.Sprintf("%s %s, %s",
+				vecOp(i.u1),
+				formatVRegSized(i.rd.nr(), 64),
+				formatVRegSized(i.rn.nr(), 32),
+			)
+		default:
+			str = fmt.Sprintf("%s %s, %s",
+				vecOp(i.u1),
+				formatVRegVec(i.rd.nr(), vecArrangement(i.u2), vecIndexNone),
+				formatVRegVec(i.rn.nr(), vecArrangement(i.u2), vecIndexNone))
+		}
 	case vecLanes:
 		arr := vecArrangement(i.u2)
 		var destArr vecArrangement
@@ -1263,6 +1290,21 @@ func (i *instruction) asUDF() {
 	i.kind = udf
 }
 
+func (i *instruction) asIntToFpu(rd, rn operand, rnSigned, src64bit, dst64bit bool) {
+	i.kind = intToFpu
+	i.rn = rn
+	i.rd = rd
+	if rnSigned {
+		i.u1 = 1
+	}
+	if src64bit {
+		i.u2 = 1
+	}
+	if dst64bit {
+		i.u3 = 1
+	}
+}
+
 func (i *instruction) asExitSequence(ctx regalloc.VReg) {
 	i.kind = exitSequence
 	i.rn = operandNR(ctx)
@@ -1365,6 +1407,8 @@ func (b vecOp) String() string {
 		return "cnt"
 	case vecOpUaddlv:
 		return "uaddlv"
+	case vecOpCvt32To64:
+		return "fcvt"
 	}
 	panic(int(b))
 }
@@ -1372,6 +1416,7 @@ func (b vecOp) String() string {
 const (
 	vecOpCnt vecOp = iota
 	vecOpUaddlv
+	vecOpCvt32To64
 )
 
 // bitOp determines the type of bitwise operation. Instructions whose kind is one of
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go b/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
index dd19a8b2..43b74ce7 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
@@ -241,12 +241,22 @@ func (i *instruction) encode(c backend.Compiler) {
 			vecIndex(i.u2),
 		))
 	case vecMisc:
-		c.Emit4Bytes(encodeVecMisc(
-			vecOp(i.u1),
-			regNumberInEncoding[i.rd.realReg()],
-			regNumberInEncoding[i.rn.realReg()],
-			vecArrangement(i.u2),
-		))
+		switch op := vecOp(i.u1); op {
+		case vecOpCnt:
+			c.Emit4Bytes(encodeAdvancedSIMDTwoMisc(
+				vecOp(i.u1),
+				regNumberInEncoding[i.rd.realReg()],
+				regNumberInEncoding[i.rn.realReg()],
+				vecArrangement(i.u2),
+			))
+		case vecOpCvt32To64:
+			c.Emit4Bytes(encodeFloatDataOneSource(
+				vecOp(i.u1),
+				regNumberInEncoding[i.rd.realReg()],
+				regNumberInEncoding[i.rn.realReg()],
+				vecArrangement(i.u2),
+			))
+		}
 	case vecLanes:
 		c.Emit4Bytes(encodeVecLanes(
 			vecOp(i.u1),
@@ -256,11 +266,62 @@ func (i *instruction) encode(c backend.Compiler) {
 		))
 	case brTableSequence:
 		encodeBrTableSequence(c, i.rn.reg(), i.targets)
+	case intToFpu:
+		c.Emit4Bytes(encodeCnvBetweenFloatInt(i))
 	default:
 		panic(i.String())
 	}
 }
 
+// encodeFloatDataOneSource encodes as "Floating-point data-processing (1 source)" in
+// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Scalar-Floating-Point-and-Advanced-SIMD?lang=en#simd-dp
+func encodeFloatDataOneSource(op vecOp, rd, rn uint32, _ vecArrangement) uint32 {
+	var opcode, ptype uint32
+	switch op {
+	case vecOpCvt32To64:
+		opcode = 0b000101
+	default:
+		panic("BUG")
+	}
+	return 0b1111<<25 | ptype<<22 | 0b1<<21 | opcode<<15 | 0b1<<14 | rn<<5 | rd
+}
+
+// encodeCnvBetweenFloatInt encodes as "Conversion between floating-point and integer" in
+// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Scalar-Floating-Point-and-Advanced-SIMD?lang=en
+func encodeCnvBetweenFloatInt(i *instruction) uint32 {
+	rd := regNumberInEncoding[i.rd.realReg()]
+	rn := regNumberInEncoding[i.rn.realReg()]
+
+	var opcode uint32
+	var rmode uint32
+	var ptype uint32
+	var sf uint32
+	switch i.kind {
+	case intToFpu: // Either UCVTF or SCVTF.
+		rmode = 0b00
+
+		signed := i.u1 == 1
+		src64bit := i.u2 == 1
+		dst64bit := i.u3 == 1
+		if signed {
+			opcode = 0b010
+		} else {
+			opcode = 0b011
+		}
+		if src64bit {
+			sf = 0b1
+		}
+		if dst64bit {
+			ptype = 0b01
+		} else {
+			ptype = 0b00
+		}
+	case fpuToInt:
+		panic("TODO")
+	}
+	return sf<<31 | 0b1111<<25 | ptype<<22 | 0b1<<21 | rmode<<19 | opcode<<16 | rn<<5 | rd
+}
+
 // encodeAdr encodes a PC-relative ADR instruction.
 // https://developer.arm.com/documentation/ddi0602/2022-06/Base-Instructions/ADR--Form-PC-relative-address-
 func encodeAdr(rd uint32, offset uint32) uint32 {
@@ -993,7 +1054,7 @@ func encodeVecLanes(op vecOp, rd uint32, rn uint32, arr vecArrangement) uint32 {
 
 // encodeVecMisc encodes as Data Processing (Advanced SIMD two-register miscellaneous) depending on vecOp in
 // https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Scalar-Floating-Point-and-Advanced-SIMD?lang=en#simd-dp
-func encodeVecMisc(op vecOp, rd, rn uint32, arr vecArrangement) uint32 {
+func encodeAdvancedSIMDTwoMisc(op vecOp, rd, rn uint32, arr vecArrangement) uint32 {
 	var q, u, size, opcode uint32
 	switch op {
 	case vecOpCnt:
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_instr.go b/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
index d8545327..b250d556 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
@@ -188,12 +188,35 @@ func (m *machine) LowerInstr(instr *ssa.Instruction) {
 		x := instr.UnaryData()
 		result := instr.Return()
 		m.lowerPopcnt(x, result)
+	case ssa.OpcodeFcvtFromSint:
+		x := instr.UnaryData()
+		result := instr.Return()
+		m.lowerIntToFpu(result, x, true, x.Type() == ssa.TypeI64, result.Type().Bits() == 64)
+	case ssa.OpcodeFcvtFromUint:
+		x := instr.UnaryData()
+		result := instr.Return()
+		m.lowerIntToFpu(result, x, false, x.Type() == ssa.TypeI64, result.Type().Bits() == 64)
+	case ssa.OpcodeFpromote:
+		v := instr.UnaryData()
+		rn := m.getOperand_NR(m.compiler.ValueDefinition(v), extModeNone)
+		rd := operandNR(m.compiler.VRegOf(instr.Return()))
+		cnt := m.allocateInstr()
+		cnt.asVecMisc(vecOpCvt32To64, rd, rn, vecArrangementNone)
+		m.insert(cnt)
 	default:
 		panic("TODO: lowering " + instr.Opcode().String())
 	}
 	m.FlushPendingInstructions()
 }
 
+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))
+	cvt := m.allocateInstr()
+	cvt.asIntToFpu(rd, rn, signed, src64bit, dst64bit)
+	m.insert(cvt)
+}
+
 func (m *machine) lowerFpuBinOp(si *ssa.Instruction) {
 	instr := m.allocateInstr()
 	var op fpuBinOp
@@ -377,20 +400,20 @@ func (m *machine) lowerPopcnt(x, result ssa.Value) {
 	//    mov x5, v0.d[0]     ;; finally we mov the result back to a GPR
 	//
 
-	rd := m.compiler.VRegOf(result)
+	rd := operandNR(m.compiler.VRegOf(result))
 	rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone)
 
-	rf1 := m.compiler.AllocateVReg(regalloc.RegTypeFloat)
+	rf1 := operandNR(m.compiler.AllocateVReg(regalloc.RegTypeFloat))
 	ins := m.allocateInstr()
-	ins.asMovToVec(rf1, rn.nr(), vecArrangementD, vecIndex(0))
+	ins.asMovToVec(rf1, rn, vecArrangementD, vecIndex(0))
 	m.insert(ins)
 
-	rf2 := m.compiler.AllocateVReg(regalloc.RegTypeFloat)
+	rf2 := operandNR(m.compiler.AllocateVReg(regalloc.RegTypeFloat))
 	cnt := m.allocateInstr()
 	cnt.asVecMisc(vecOpCnt, rf2, rf1, vecArrangement16B)
 	m.insert(cnt)
 
-	rf3 := m.compiler.AllocateVReg(regalloc.RegTypeFloat)
+	rf3 := operandNR(m.compiler.AllocateVReg(regalloc.RegTypeFloat))
 	uaddlv := m.allocateInstr()
 	uaddlv.asVecLanes(vecOpUaddlv, rf3, rf2, vecArrangement8B)
 	m.insert(uaddlv)
diff --git a/internal/engine/wazevo/frontend/lower.go b/internal/engine/wazevo/frontend/lower.go
index 39d2e7fd..5e2ddf54 100644
--- a/internal/engine/wazevo/frontend/lower.go
+++ b/internal/engine/wazevo/frontend/lower.go
@@ -415,6 +415,16 @@ func (c *Compiler) lowerOpcode(op wasm.Opcode) {
 			return
 		}
 		c.insertFcmp(ssa.FloatCmpCondGreaterThanOrEqual)
+	case wasm.OpcodeF32Neg, wasm.OpcodeF64Neg:
+		if state.unreachable {
+			return
+		}
+		x := state.pop()
+		neg := builder.AllocateInstruction()
+		neg.AsFneg(x)
+		builder.InsertInstruction(neg)
+		value := neg.Return()
+		state.push(value)
 	case wasm.OpcodeI32Shl, wasm.OpcodeI64Shl:
 		if state.unreachable {
 			return
@@ -937,6 +947,9 @@ func (c *Compiler) lowerOpcode(op wasm.Opcode) {
 
 	case wasm.OpcodeNop:
 	case wasm.OpcodeReturn:
+		if state.unreachable {
+			return
+		}
 		results := c.loweringState.nPeekDup(c.results())
 		instr := builder.AllocateInstruction()
 
@@ -945,6 +958,9 @@ func (c *Compiler) lowerOpcode(op wasm.Opcode) {
 		state.unreachable = true
 
 	case wasm.OpcodeUnreachable:
+		if state.unreachable {
+			return
+		}
 		exit := builder.AllocateInstruction()
 		exit.AsExitWithCode(c.execCtxPtrValue, wazevoapi.ExitCodeUnreachable)
 		builder.InsertInstruction(exit)
@@ -1029,6 +1045,36 @@ func (c *Compiler) lowerOpcode(op wasm.Opcode) {
 
 	case wasm.OpcodeDrop:
 		_ = state.pop()
+	case wasm.OpcodeF64ConvertI32S, wasm.OpcodeF64ConvertI64S, wasm.OpcodeF64ConvertI32U, wasm.OpcodeF64ConvertI64U:
+		if state.unreachable {
+			return
+		}
+		cvt := builder.AllocateInstruction()
+		cvt.AsFcvtFromInt(state.pop(),
+			op == wasm.OpcodeF64ConvertI32S || op == wasm.OpcodeF64ConvertI64S,
+			true,
+		)
+		builder.InsertInstruction(cvt)
+		state.push(cvt.Return())
+	case wasm.OpcodeF32ConvertI32S, wasm.OpcodeF32ConvertI64S, wasm.OpcodeF32ConvertI32U, wasm.OpcodeF32ConvertI64U:
+		if state.unreachable {
+			return
+		}
+		cvt := builder.AllocateInstruction()
+		cvt.AsFcvtFromInt(state.pop(),
+			op == wasm.OpcodeF32ConvertI32S || op == wasm.OpcodeF32ConvertI64S,
+			false,
+		)
+		builder.InsertInstruction(cvt)
+		state.push(cvt.Return())
+	case wasm.OpcodeF64PromoteF32:
+		if state.unreachable {
+			return
+		}
+		cvt := builder.AllocateInstruction()
+		cvt.AsFpromote(state.pop())
+		builder.InsertInstruction(cvt)
+		state.push(cvt.Return())
 	default:
 		panic("TODO: unsupported in wazevo yet: " + wasm.InstructionName(op))
 	}
diff --git a/internal/engine/wazevo/ssa/instructions.go b/internal/engine/wazevo/ssa/instructions.go
index 327d80e6..9b27e454 100644
--- a/internal/engine/wazevo/ssa/instructions.go
+++ b/internal/engine/wazevo/ssa/instructions.go
@@ -613,8 +613,7 @@ const (
 	// `v = fma x, y, z`.
 	OpcodeFma
 
-	// OpcodeFneg ...
-	// `v = fneg x`.
+	// OpcodeFneg negates the given floating point value: `v = Fneg x`.
 	OpcodeFneg
 
 	// OpcodeFabs ...
@@ -723,8 +722,7 @@ const (
 	// OpcodeSExtend sign-extends the given integer: `v = SExtend x, from->to`.
 	OpcodeSExtend
 
-	// OpcodeFpromote ...
-	// `v = fpromote x`.
+	// OpcodeFpromote promotes the given floating point value: `v = Fpromote x`.
 	OpcodeFpromote
 
 	// OpcodeFdemote ...
@@ -735,10 +733,6 @@ const (
 	// `v = fvdemote x`.
 	OpcodeFvdemote
 
-	// OpcodeFvpromoteLow ...
-	// `x = fvpromote_low a`.
-	OpcodeFvpromoteLow
-
 	// OpcodeFcvtToUint ...
 	// `v = fcvt_to_uint x`.
 	OpcodeFcvtToUint
@@ -755,18 +749,12 @@ const (
 	// `v = fcvt_to_sint_sat x`.
 	OpcodeFcvtToSintSat
 
-	// OpcodeFcvtFromUint ...
-	// `v = fcvt_from_uint x`.
+	// OpcodeFcvtFromUint converts an unsigned integer to a floating point value: `v = FcvtFromUint x`.
 	OpcodeFcvtFromUint
 
-	// OpcodeFcvtFromSint ...
-	// `v = fcvt_from_sint x`.
+	// OpcodeFcvtFromSint converts a signed integer to a floating point value: `v = FcvtFromSint x`.
 	OpcodeFcvtFromSint
 
-	// OpcodeFcvtLowFromSint ...
-	// `v = fcvt_low_from_sint x`.
-	OpcodeFcvtLowFromSint
-
 	// OpcodeIsplit ...
 	// `lo, hi = isplit x`.
 	OpcodeIsplit
@@ -870,6 +858,10 @@ var instructionSideEffects = [opcodeEnd]sideEffect{
 	OpcodeFmax:               sideEffectFalse,
 	OpcodeSelect:             sideEffectFalse,
 	OpcodeFmin:               sideEffectFalse,
+	OpcodeFneg:               sideEffectFalse,
+	OpcodeFcvtFromSint:       sideEffectFalse,
+	OpcodeFcvtFromUint:       sideEffectFalse,
+	OpcodeFpromote:           sideEffectFalse,
 }
 
 // HasSideEffects returns true if this instruction has side effects.
@@ -957,6 +949,10 @@ var instructionReturnTypes = [opcodeEnd]returnTypesFn{
 	OpcodeSload8:             returnTypesFnSingle,
 	OpcodeSload16:            returnTypesFnSingle,
 	OpcodeSload32:            returnTypesFnSingle,
+	OpcodeFcvtFromSint:       returnTypesFnSingle,
+	OpcodeFcvtFromUint:       returnTypesFnSingle,
+	OpcodeFneg:               returnTypesFnSingle,
+	OpcodeFpromote:           returnTypesFnF64,
 }
 
 // AsLoad initializes this instruction as a store instruction with OpcodeLoad.
@@ -1354,6 +1350,35 @@ func (i *Instruction) UnaryData() Value {
 	return i.v
 }
 
+// AsFneg initializes this instruction as an instruction with OpcodeFneg.
+func (i *Instruction) AsFneg(x Value) {
+	i.opcode = OpcodeFneg
+	i.v = x
+	i.typ = x.Type()
+}
+
+// AsFpromote initializes this instruction as an instruction with OpcodeFpromote.
+func (i *Instruction) AsFpromote(x Value) {
+	i.opcode = OpcodeFpromote
+	i.v = x
+	i.typ = TypeF64
+}
+
+// AsFcvtFromInt initializes this instruction as an instruction with either OpcodeFcvtFromUint or OpcodeFcvtFromSint
+func (i *Instruction) AsFcvtFromInt(x Value, signed bool, dst64bit bool) {
+	if signed {
+		i.opcode = OpcodeFcvtFromSint
+	} else {
+		i.opcode = OpcodeFcvtFromUint
+	}
+	i.v = x
+	if dst64bit {
+		i.typ = TypeF64
+	} else {
+		i.typ = TypeF32
+	}
+}
+
 // AsSExtend initializes this instruction as a sign extension instruction with OpcodeSExtend.
 func (i *Instruction) AsSExtend(v Value, from, to byte) {
 	i.opcode = OpcodeSExtend
@@ -1503,7 +1528,7 @@ func (i *Instruction) Format(b Builder) string {
 	case OpcodeIshl, OpcodeSshr, OpcodeUshr:
 		instSuffix = fmt.Sprintf(" %s, %s", i.v.Format(b), i.v2.Format(b))
 	case OpcodeUndefined:
-	case OpcodeClz, OpcodeCtz, OpcodePopcnt:
+	case OpcodeClz, OpcodeCtz, OpcodePopcnt, OpcodeFneg, OpcodeFcvtFromSint, OpcodeFcvtFromUint, OpcodeFpromote:
 		instSuffix = " " + i.v.Format(b)
 	default:
 		panic(fmt.Sprintf("TODO: format for %s", i.opcode))
@@ -1885,8 +1910,6 @@ func (o Opcode) String() (ret string) {
 		return "Fdemote"
 	case OpcodeFvdemote:
 		return "Fvdemote"
-	case OpcodeFvpromoteLow:
-		return "FvpromoteLow"
 	case OpcodeFcvtToUint:
 		return "FcvtToUint"
 	case OpcodeFcvtToSint:
@@ -1899,8 +1922,6 @@ func (o Opcode) String() (ret string) {
 		return "FcvtFromUint"
 	case OpcodeFcvtFromSint:
 		return "FcvtFromSint"
-	case OpcodeFcvtLowFromSint:
-		return "FcvtLowFromSint"
 	case OpcodeIsplit:
 		return "Isplit"
 	case OpcodeIconcat:


Test patch:
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 ab02021d..26a2ed68 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
@@ -27,14 +27,14 @@ func TestInstruction_encode(t *testing.T) {
 		{want: "41bc631e", setup: func(i *instruction) { i.asFpuCSel(operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), lt, true) }},
 		{want: "41cc231e", setup: func(i *instruction) { i.asFpuCSel(operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), gt, false) }},
 		{want: "41bc231e", setup: func(i *instruction) { i.asFpuCSel(operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), lt, false) }},
-		{want: "411c014e", setup: func(i *instruction) { i.asMovToVec(v1VReg, x2VReg, vecArrangementB, 0) }},
-		{want: "411c024e", setup: func(i *instruction) { i.asMovToVec(v1VReg, x2VReg, vecArrangementH, 0) }},
-		{want: "411c044e", setup: func(i *instruction) { i.asMovToVec(v1VReg, x2VReg, vecArrangementS, 0) }},
-		{want: "411c084e", setup: func(i *instruction) { i.asMovToVec(v1VReg, x2VReg, vecArrangementD, 0) }},
-		{want: "413c010e", setup: func(i *instruction) { i.asMovFromVec(x1VReg, v2VReg, vecArrangementB, 0) }},
-		{want: "413c020e", setup: func(i *instruction) { i.asMovFromVec(x1VReg, v2VReg, vecArrangementH, 0) }},
-		{want: "413c040e", setup: func(i *instruction) { i.asMovFromVec(x1VReg, v2VReg, vecArrangementS, 0) }},
-		{want: "413c084e", setup: func(i *instruction) { i.asMovFromVec(x1VReg, v2VReg, vecArrangementD, 0) }},
+		{want: "411c014e", setup: func(i *instruction) { i.asMovToVec(operandNR(v1VReg), operandNR(x2VReg), vecArrangementB, 0) }},
+		{want: "411c024e", setup: func(i *instruction) { i.asMovToVec(operandNR(v1VReg), operandNR(x2VReg), vecArrangementH, 0) }},
+		{want: "411c044e", setup: func(i *instruction) { i.asMovToVec(operandNR(v1VReg), operandNR(x2VReg), vecArrangementS, 0) }},
+		{want: "411c084e", setup: func(i *instruction) { i.asMovToVec(operandNR(v1VReg), operandNR(x2VReg), vecArrangementD, 0) }},
+		{want: "413c010e", setup: func(i *instruction) { i.asMovFromVec(operandNR(x1VReg), operandNR(v2VReg), vecArrangementB, 0) }},
+		{want: "413c020e", setup: func(i *instruction) { i.asMovFromVec(operandNR(x1VReg), operandNR(v2VReg), vecArrangementH, 0) }},
+		{want: "413c040e", setup: func(i *instruction) { i.asMovFromVec(operandNR(x1VReg), operandNR(v2VReg), vecArrangementS, 0) }},
+		{want: "413c084e", setup: func(i *instruction) { i.asMovFromVec(operandNR(x1VReg), operandNR(v2VReg), vecArrangementD, 0) }},
 		{want: "5b28030b", setup: func(i *instruction) {
 			i.asALU(aluOpAdd, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpLSL), false)
 		}},
@@ -449,27 +449,56 @@ func TestInstruction_encode(t *testing.T) {
 		{want: "4000c0da", setup: func(i *instruction) { i.asBitRR(bitOpRbit, x0VReg, x2VReg, true) }},
 		{want: "4010c05a", setup: func(i *instruction) { i.asBitRR(bitOpClz, x0VReg, x2VReg, false) }},
 		{want: "4010c0da", setup: func(i *instruction) { i.asBitRR(bitOpClz, x0VReg, x2VReg, true) }},
-		{want: "4138302e", setup: func(i *instruction) { i.asVecLanes(vecOpUaddlv, v1VReg, v2VReg, vecArrangement8B) }},
-		{want: "4138306e", setup: func(i *instruction) { i.asVecLanes(vecOpUaddlv, v1VReg, v2VReg, vecArrangement16B) }},
-		{want: "4138702e", setup: func(i *instruction) { i.asVecLanes(vecOpUaddlv, v1VReg, v2VReg, vecArrangement4H) }},
-		{want: "4138706e", setup: func(i *instruction) { i.asVecLanes(vecOpUaddlv, v1VReg, v2VReg, vecArrangement8H) }},
-		{want: "4138b06e", setup: func(i *instruction) { i.asVecLanes(vecOpUaddlv, v1VReg, v2VReg, vecArrangement4S) }},
-		{want: "4158200e", setup: func(i *instruction) { i.asVecMisc(vecOpCnt, v1VReg, v2VReg, vecArrangement8B) }},
-		{want: "4158204e", setup: func(i *instruction) { i.asVecMisc(vecOpCnt, v1VReg, v2VReg, vecArrangement16B) }},
+		{want: "4138302e", setup: func(i *instruction) {
+			i.asVecLanes(vecOpUaddlv, operandNR(v1VReg), operandNR(v2VReg), vecArrangement8B)
+		}},
+		{want: "4138306e", setup: func(i *instruction) {
+			i.asVecLanes(vecOpUaddlv, operandNR(v1VReg), operandNR(v2VReg), vecArrangement16B)
+		}},
+		{want: "4138702e", setup: func(i *instruction) {
+			i.asVecLanes(vecOpUaddlv, operandNR(v1VReg), operandNR(v2VReg), vecArrangement4H)
+		}},
+		{want: "4138706e", setup: func(i *instruction) {
+			i.asVecLanes(vecOpUaddlv, operandNR(v1VReg), operandNR(v2VReg), vecArrangement8H)
+		}},
+		{want: "4138b06e", setup: func(i *instruction) {
+			i.asVecLanes(vecOpUaddlv, operandNR(v1VReg), operandNR(v2VReg), vecArrangement4S)
+		}},
+		{want: "4158200e", setup: func(i *instruction) { i.asVecMisc(vecOpCnt, operandNR(v1VReg), operandNR(v2VReg), vecArrangement8B) }},
+		{want: "4158204e", setup: func(i *instruction) { i.asVecMisc(vecOpCnt, operandNR(v1VReg), operandNR(v2VReg), vecArrangement16B) }},
+		{want: "41c0221e", setup: func(i *instruction) {
+			i.asVecMisc(vecOpCvt32To64, operandNR(v1VReg), operandNR(v2VReg), vecArrangementNone)
+		}},
+		{want: "4201231e4201631e4201239e4201639e4201221e4201621e4201229e4201629e", setup: func(i *instruction) {
+			i.asNop0()
+			cur := i
+			trueFalse := []bool{false, true}
+			for _, rnSigned := range trueFalse {
+				for _, src64bit := range trueFalse {
+					for _, dst64bit := range trueFalse {
+						i := &instruction{prev: cur}
+						cur.next = i
+						i.asIntToFpu(operandNR(v2VReg), operandNR(x10VReg), rnSigned, src64bit, dst64bit)
+						cur = i
+					}
+				}
+			}
+		}},
 	} {
 		tc := tc
 		t.Run(tc.want, func(t *testing.T) {
 			i := &instruction{}
 			tc.setup(i)
 
-			m := &mockCompiler{}
-			i.encode(m)
+			mc := &mockCompiler{}
+			m := &machine{compiler: mc}
+			m.encode(i)
 			// Note: for quick iteration we can use golang.org/x/arch package to verify the encoding.
 			// 	but wazero doesn't add even a test dependency to it, so commented out.
 			// inst, err := arm64asm.Decode(m.buf)
 			// require.NoError(t, err, hex.EncodeToString(m.buf))
 			// fmt.Println(inst.String())
-			require.Equal(t, tc.want, hex.EncodeToString(m.buf))
+			require.Equal(t, tc.want, hex.EncodeToString(mc.buf))
 		})
 	}
 }
diff --git a/internal/engine/wazevo/e2e_test.go b/internal/engine/wazevo/e2e_test.go
index edc23609..77446e30 100644
--- a/internal/engine/wazevo/e2e_test.go
+++ b/internal/engine/wazevo/e2e_test.go
@@ -44,6 +44,7 @@ func TestSpectestV1(t *testing.T) {
 		{name: "custom.json"},
 		{name: "const.json"},
 		{name: "data.json"},
+		{name: "local_get.json"},
 		{name: "nop.json"},
 	} {
 		t.Run(tc.name, func(t *testing.T) { spectest.RunJson(t, v1.Testcases, tc.name, context.Background(), config) })
diff --git a/internal/engine/wazevo/frontend/frontend_test.go b/internal/engine/wazevo/frontend/frontend_test.go
index 62be89af..16f4d9ec 100644
--- a/internal/engine/wazevo/frontend/frontend_test.go
+++ b/internal/engine/wazevo/frontend/frontend_test.go
@@ -1501,7 +1501,6 @@ blk1: (v4:i32) <-- (blk12)
 	v20:i32 = Iconst_32 0x10
 	v21:i32 = Iadd v4, v20
 	Return v21
-	Exit exec_ctx, unreachable
 
 blk2: (v5:i32) <-- (blk11)
 	v18:i32 = Iconst_32 0xf
@@ -1559,7 +1558,6 @@ blk1: () <-- (blk12)
 	v20:i32 = Iconst_32 0x10
 	v21:i32 = Iadd v3, v20
 	Return v21
-	Exit exec_ctx, unreachable
 
 blk2: () <-- (blk11)
 	v18:i32 = Iconst_32 0xf