Repository: tetratelabs/wazero
Pull request #1698 (2023-09-11 20:16:28)

wazevo: passes simd_i16x8_arith, simd_i32x4_arith, simd_i64x2_arith spec tests
#1496

Signed-off-by: Edoardo Vacchi <evacchi@users.noreply.github.com>



evacchi: update: spec tests pass, encoding tests pass, now onto frontend/backend tests
evacchi: sorry about the rebase; I saw failures on amd64 and I thought they were due to some missing bits -- it was just a broken import 🤦 

Patch:
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr.go b/internal/engine/wazevo/backend/isa/arm64/instr.go
index 79972802..b39edf46 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr.go
@@ -1504,6 +1504,24 @@ func (b vecOp) String() string {
 		return "bit"
 	case vecOpEOR:
 		return "eor"
+	case vecOpAdd:
+		return "add"
+	case vecOpAddp:
+		return "addp"
+	case vecOpSub:
+		return "sub"
+	case vecOpMul:
+		return "mul"
+	case vecOpUmlal:
+		return "umlal"
+	case vecOpNeg:
+		return "neg"
+	case vecOpRev64:
+		return "rev64"
+	case vecOpXtn:
+		return "xtn"
+	case vecOpShll:
+		return "shll"
 	}
 	panic(int(b))
 }
@@ -1514,6 +1532,14 @@ const (
 	vecOpBit
 	vecOpEOR
 	vecOpAdd
+	vecOpAddp
+	vecOpSub
+	vecOpMul
+	vecOpUmlal
+	vecOpNeg
+	vecOpRev64
+	vecOpXtn
+	vecOpShll
 )
 
 // 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 2ebab5c2..1ee45c0c 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
@@ -352,8 +352,35 @@ func encodeVecRRR(op vecOp, rd, rn, rm uint32, arr vecArrangement) uint32 {
 		size, q := arrToSizeQEncoded(arr)
 		return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b00011, size, 0b1, q)
 	case vecOpAdd:
+		if arr == vecArrangement1D {
+			panic("unsupported arrangement: " + arr.String())
+		}
 		size, q := arrToSizeQEncoded(arr)
 		return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b10000, size, 0b0, q)
+	case vecOpAddp:
+		if arr == vecArrangement1D {
+			panic("unsupported arrangement: " + arr.String())
+		}
+		size, q := arrToSizeQEncoded(arr)
+		return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b10111, size, 0b0, q)
+	case vecOpSub:
+		if arr == vecArrangement1D {
+			panic("unsupported arrangement: " + arr.String())
+		}
+		size, q := arrToSizeQEncoded(arr)
+		return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b10000, size, 0b1, q)
+	case vecOpMul:
+		if arr > vecArrangement4S {
+			panic("unsupported arrangement: " + arr.String())
+		}
+		size, q := arrToSizeQEncoded(arr)
+		return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b10011, size, 0b0, q)
+	case vecOpUmlal:
+		if arr > vecArrangement4S {
+			panic("unsupported arrangement: " + arr.String())
+		}
+		size, q := arrToSizeQEncoded(arr)
+		return encodeAdvancedSIMDThreeDifferent(rd, rn, rm, 0b1000, size, 0b1, q)
 	default:
 		panic("TODO")
 	}
@@ -393,6 +420,12 @@ func encodeAdvancedSIMDThreeSame(rd, rn, rm, opcode, size, U, Q uint32) uint32 {
 	return Q<<30 | U<<29 | 0b111<<25 | size<<22 | 0b1<<21 | rm<<16 | opcode<<11 | 0b1<<10 | rn<<5 | rd
 }
 
+// encodeAdvancedSIMDThreeDifferent encodes as "Advanced SIMD three different" in
+// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Scalar-Floating-Point-and-Advanced-SIMD?lang=en
+func encodeAdvancedSIMDThreeDifferent(rd, rn, rm, opcode, size, U, Q uint32) uint32 {
+	return Q<<30 | U<<29 | 0b111<<25 | size<<22 | 0b1<<21 | rm<<16 | opcode<<12 | rn<<5 | rd
+}
+
 // 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 fpuUniOp, rd, rn uint32, dst64bit bool) uint32 {
@@ -1358,6 +1391,33 @@ func encodeAdvancedSIMDTwoMisc(op vecOp, rd, rn uint32, arr vecArrangement) uint
 		default:
 			panic("unsupported arrangement: " + arr.String())
 		}
+	case vecOpNeg:
+		if arr == vecArrangement1D {
+			panic("unsupported arrangement: " + arr.String())
+		}
+		opcode = 0b01011
+		u = 0b1
+		size, q = arrToSizeQEncoded(arr)
+	case vecOpRev64:
+		opcode = 0b00000
+		size, q = arrToSizeQEncoded(arr)
+	case vecOpXtn:
+		u = 0b0
+		opcode = 0b10010
+		size, q = arrToSizeQEncoded(arr)
+	case vecOpShll:
+		u = 0b1
+		opcode = 0b10011
+		switch arr {
+		case vecArrangement8B:
+			q, size = 0b0, 0b00
+		case vecArrangement4H:
+			q, size = 0b0, 0b01
+		case vecArrangement2S:
+			q, size = 0b0, 0b10
+		default:
+			panic("unsupported arrangement: " + arr.String())
+		}
 	default:
 		panic("unsupported or illegal vecOp: " + op.String())
 	}
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_instr.go b/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
index fc123ab3..ea666393 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
@@ -308,12 +308,77 @@ func (m *machine) LowerInstr(instr *ssa.Instruction) {
 		rd := operandNR(m.compiler.VRegOf(instr.Return()))
 		add.asVecRRR(vecOpAdd, rd, rn, rm, arr)
 		m.insert(add)
+	case ssa.OpcodeVIsub:
+		x, y, lane := instr.Arg2WithLane()
+		arr := ssaLeneToArrangement(lane)
+		sub := m.allocateInstr()
+		rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone)
+		rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone)
+		rd := operandNR(m.compiler.VRegOf(instr.Return()))
+		sub.asVecRRR(vecOpSub, rd, rn, rm, arr)
+		m.insert(sub)
+	case ssa.OpcodeVImul:
+		x, y, lane := instr.Arg2WithLane()
+		arr := ssaLeneToArrangement(lane)
+		rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone)
+		rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone)
+		rd := operandNR(m.compiler.VRegOf(instr.Return()))
+		m.lowerVIMul(rd, rn, rm, arr)
+	case ssa.OpcodeVIneg:
+		x, lane := instr.ArgWithLane()
+		arr := ssaLeneToArrangement(lane)
+		neg := m.allocateInstr()
+		rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone)
+		rd := operandNR(m.compiler.VRegOf(instr.Return()))
+		neg.asVecMisc(vecOpNeg, rd, rn, arr)
+		m.insert(neg)
 	default:
 		panic("TODO: lowering " + op.String())
 	}
 	m.FlushPendingInstructions()
 }
 
+func (m *machine) lowerVIMul(rd, rn, rm operand, arr vecArrangement) {
+	if arr != vecArrangement2D { // ssa.VecLaneI64x2
+		mul := m.allocateInstr()
+		mul.asVecRRR(vecOpMul, rd, rn, rm, arr)
+		m.insert(mul)
+	} else {
+		tmp1 := operandNR(m.compiler.AllocateVReg(regalloc.RegTypeFloat))
+		tmp2 := operandNR(m.compiler.AllocateVReg(regalloc.RegTypeFloat))
+		tmp3 := operandNR(m.compiler.AllocateVReg(regalloc.RegTypeFloat))
+
+		// Following the algorithm in https://chromium-review.googlesource.com/c/v8/v8/+/1781696
+		rev64 := m.allocateInstr()
+		rev64.asVecMisc(vecOpRev64, tmp2, rm, vecArrangement4S)
+		m.insert(rev64)
+
+		mul := m.allocateInstr()
+		mul.asVecRRR(vecOpMul, tmp2, tmp2, rn, vecArrangement4S)
+		m.insert(mul)
+
+		xtn1 := m.allocateInstr()
+		xtn1.asVecMisc(vecOpXtn, tmp1, rn, vecArrangement2S)
+		m.insert(xtn1)
+
+		addp := m.allocateInstr()
+		addp.asVecRRR(vecOpAddp, tmp2, tmp2, tmp2, vecArrangement4S)
+		m.insert(addp)
+
+		xtn2 := m.allocateInstr()
+		xtn2.asVecMisc(vecOpXtn, tmp3, rm, vecArrangement2S)
+		m.insert(xtn2)
+
+		shll := m.allocateInstr()
+		shll.asVecMisc(vecOpShll, rd, tmp2, vecArrangement2S)
+		m.insert(shll)
+
+		umlal := m.allocateInstr()
+		umlal.asVecRRR(vecOpUmlal, rd, tmp3, tmp1, vecArrangement2S)
+		m.insert(umlal)
+	}
+}
+
 func (m *machine) lowerIRem(execCtxVReg regalloc.VReg, rd, rn, rm operand, _64bit, signed bool) {
 	div := m.allocateInstr()
 
diff --git a/internal/engine/wazevo/frontend/lower.go b/internal/engine/wazevo/frontend/lower.go
index 10b0e76f..5b6405e4 100644
--- a/internal/engine/wazevo/frontend/lower.go
+++ b/internal/engine/wazevo/frontend/lower.go
@@ -1325,21 +1325,23 @@ func (c *Compiler) lowerCurrentOpcode() {
 	case wasm.OpcodeVecPrefix:
 		state.pc++
 		vecOp := c.wasmFunctionBody[state.pc]
-		state.pc++
 		switch vecOp {
 		case wasm.OpcodeVecV128Const:
+			state.pc++
 			lo := binary.LittleEndian.Uint64(c.wasmFunctionBody[state.pc:])
 			state.pc += 8
 			hi := binary.LittleEndian.Uint64(c.wasmFunctionBody[state.pc:])
 			state.pc += 7
 			ret := builder.AllocateInstruction().AsVconst(lo, hi).Insert(builder).Return()
 			state.push(ret)
-		case wasm.OpcodeVecI32x4Add, wasm.OpcodeVecI64x2Add:
+		case wasm.OpcodeVecI16x8Add, wasm.OpcodeVecI32x4Add, wasm.OpcodeVecI64x2Add:
 			if state.unreachable {
 				break
 			}
 			var lane ssa.VecLane
 			switch vecOp {
+			case wasm.OpcodeVecI16x8Add:
+				lane = ssa.VecLaneI16x8
 			case wasm.OpcodeVecI32x4Add:
 				lane = ssa.VecLaneI32x4
 			case wasm.OpcodeVecI64x2Add:
@@ -1349,6 +1351,56 @@ func (c *Compiler) lowerCurrentOpcode() {
 			v1 := state.pop()
 			ret := builder.AllocateInstruction().AsVIadd(v1, v2, lane).Insert(builder).Return()
 			state.push(ret)
+		case wasm.OpcodeVecI16x8Sub, wasm.OpcodeVecI32x4Sub, wasm.OpcodeVecI64x2Sub:
+			if state.unreachable {
+				break
+			}
+			var lane ssa.VecLane
+			switch vecOp {
+			case wasm.OpcodeVecI16x8Sub:
+				lane = ssa.VecLaneI16x8
+			case wasm.OpcodeVecI32x4Sub:
+				lane = ssa.VecLaneI32x4
+			case wasm.OpcodeVecI64x2Sub:
+				lane = ssa.VecLaneI64x2
+			}
+			v2 := state.pop()
+			v1 := state.pop()
+			ret := builder.AllocateInstruction().AsVIsub(v1, v2, lane).Insert(builder).Return()
+			state.push(ret)
+		case wasm.OpcodeVecI16x8Mul, wasm.OpcodeVecI32x4Mul, wasm.OpcodeVecI64x2Mul:
+			if state.unreachable {
+				break
+			}
+			var lane ssa.VecLane
+			switch vecOp {
+			case wasm.OpcodeVecI16x8Mul:
+				lane = ssa.VecLaneI16x8
+			case wasm.OpcodeVecI32x4Mul:
+				lane = ssa.VecLaneI32x4
+			case wasm.OpcodeVecI64x2Mul:
+				lane = ssa.VecLaneI64x2
+			}
+			v2 := state.pop()
+			v1 := state.pop()
+			ret := builder.AllocateInstruction().AsVImul(v1, v2, lane).Insert(builder).Return()
+			state.push(ret)
+		case wasm.OpcodeVecI16x8Neg, wasm.OpcodeVecI32x4Neg, wasm.OpcodeVecI64x2Neg:
+			if state.unreachable {
+				break
+			}
+			var lane ssa.VecLane
+			switch vecOp {
+			case wasm.OpcodeVecI16x8Neg:
+				lane = ssa.VecLaneI16x8
+			case wasm.OpcodeVecI32x4Neg:
+				lane = ssa.VecLaneI32x4
+			case wasm.OpcodeVecI64x2Neg:
+				lane = ssa.VecLaneI64x2
+			}
+			v1 := state.pop()
+			ret := builder.AllocateInstruction().AsVIneg(v1, lane).Insert(builder).Return()
+			state.push(ret)
 		default:
 			panic("TODO: unsupported vector instruction: " + wasm.VectorInstructionName(vecOp))
 		}
diff --git a/internal/engine/wazevo/ssa/instructions.go b/internal/engine/wazevo/ssa/instructions.go
index a9386641..eeac98ea 100644
--- a/internal/engine/wazevo/ssa/instructions.go
+++ b/internal/engine/wazevo/ssa/instructions.go
@@ -95,6 +95,11 @@ func (i *Instruction) Arg2() (Value, Value) {
 	return i.v, i.v2
 }
 
+// ArgWithLane returns the first argument to this instruction, and the lane type.
+func (i *Instruction) ArgWithLane() (Value, VecLane) {
+	return i.v, VecLane(i.u1)
+}
+
 // Arg2WithLane returns the first two arguments to this instruction, and the lane type.
 func (i *Instruction) Arg2WithLane() (Value, Value, VecLane) {
 	return i.v, i.v2, VecLane(i.u1)
@@ -335,6 +340,15 @@ const (
 	// OpcodeIsub performs an integer subtraction: `v = Isub x, y`.
 	OpcodeIsub
 
+	// OpcodeVIsub performs an integer subtraction: `v = VIsub.lane x, y` on vector.
+	OpcodeVIsub
+
+	// OpcodeVImul performs an integer multiplication: `v = VImul.lane x, y` on vector.
+	OpcodeVImul
+
+	// OpcodeVIneg negates the given vector value: `v = VIneg x`.
+	OpcodeVIneg
+
 	// OpcodeImul performs an integer multiplication: `v = Imul x, y`.
 	OpcodeImul
 
@@ -805,6 +819,9 @@ var instructionSideEffects = [opcodeEnd]sideEffect{
 	OpcodeFcopysign:          sideEffectNone,
 	OpcodeVconst:             sideEffectNone,
 	OpcodeVIadd:              sideEffectNone,
+	OpcodeVIsub:              sideEffectNone,
+	OpcodeVImul:              sideEffectNone,
+	OpcodeVIneg:              sideEffectNone,
 }
 
 // sideEffect returns true if this instruction has side effects.
@@ -819,6 +836,9 @@ func (i *Instruction) sideEffect() sideEffect {
 // instructionReturnTypes provides the function to determine the return types of an instruction.
 var instructionReturnTypes = [opcodeEnd]returnTypesFn{
 	OpcodeVIadd:     returnTypesFnV128,
+	OpcodeVIsub:     returnTypesFnV128,
+	OpcodeVImul:     returnTypesFnV128,
+	OpcodeVIneg:     returnTypesFnV128,
 	OpcodeBand:      returnTypesFnSingle,
 	OpcodeFcopysign: returnTypesFnSingle,
 	OpcodeBitcast:   returnTypesFnSingle,
@@ -944,6 +964,14 @@ func (i *Instruction) AsExtLoad(op Opcode, ptr Value, offset uint32, dst64bit bo
 	}
 }
 
+// AsSimdLoad initializes this instruction as a load instruction with OpcodeLoad 128 bit.
+func (i *Instruction) AsSimdLoad(op Opcode, ptr Value, offset uint32) {
+	i.opcode = op
+	i.v = ptr
+	i.u1 = uint64(offset)
+	i.typ = TypeV128
+}
+
 // LoadData returns the operands for a load instruction.
 func (i *Instruction) LoadData() (ptr Value, offset uint32, typ Type) {
 	return i.v, uint32(i.u1), i.typ
@@ -1012,6 +1040,35 @@ func (i *Instruction) AsVIadd(x, y Value, lane VecLane) *Instruction {
 	return i
 }
 
+// AsVIsub initializes this instruction as an integer subtraction instruction with OpcodeVIsub on a vector.
+func (i *Instruction) AsVIsub(x, y Value, lane VecLane) *Instruction {
+	i.opcode = OpcodeVIsub
+	i.v = x
+	i.v2 = y
+	i.u1 = uint64(lane)
+	i.typ = TypeV128
+	return i
+}
+
+// AsVImul initializes this instruction as an integer subtraction multiplication with OpcodeVImul on a vector.
+func (i *Instruction) AsVImul(x, y Value, lane VecLane) *Instruction {
+	i.opcode = OpcodeVImul
+	i.v = x
+	i.v2 = y
+	i.u1 = uint64(lane)
+	i.typ = TypeV128
+	return i
+}
+
+// AsVIneg initializes this instruction as a vector negation with OpcodeVIneg.
+func (i *Instruction) AsVIneg(x Value, lane VecLane) *Instruction {
+	i.opcode = OpcodeVIneg
+	i.v = x
+	i.u1 = uint64(lane)
+	i.typ = TypeV128
+	return i
+}
+
 // AsImul initializes this instruction as an integer addition instruction with OpcodeImul.
 func (i *Instruction) AsImul(x, y Value) {
 	i.opcode = OpcodeImul
@@ -1725,8 +1782,10 @@ func (i *Instruction) Format(b Builder) string {
 		OpcodeFcvtFromUint, OpcodeFcvtToSintSat, OpcodeFcvtToUintSat, OpcodeFdemote, OpcodeFpromote, OpcodeIreduce, OpcodeBitcast, OpcodeSqrt, OpcodeFabs,
 		OpcodeCeil, OpcodeFloor, OpcodeTrunc, OpcodeNearest:
 		instSuffix = " " + i.v.Format(b)
-	case OpcodeVIadd:
+	case OpcodeVIadd, OpcodeVIsub, OpcodeVImul:
 		instSuffix = fmt.Sprintf(".%s %s, %s", VecLane(i.u1), i.v.Format(b), i.v2.Format(b))
+	case OpcodeVIneg:
+		instSuffix = fmt.Sprintf(".%s %s", VecLane(i.u1), i.v.Format(b))
 	default:
 		panic(fmt.Sprintf("TODO: format for %s", i.opcode))
 	}
@@ -2099,6 +2158,12 @@ func (o Opcode) String() (ret string) {
 		return "ExtractVector"
 	case OpcodeVIadd:
 		return "VIadd"
+	case OpcodeVIsub:
+		return "VIsub"
+	case OpcodeVImul:
+		return "VImul"
+	case OpcodeVIneg:
+		return "VIneg"
 	}
 	panic(fmt.Sprintf("unknown opcode %d", o))
 }


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 8314a3ae..2acd4f41 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
@@ -48,6 +48,90 @@ func TestInstruction_encode(t *testing.T) {
 		{want: "4184e34e", setup: func(i *instruction) {
 			i.asVecRRR(vecOpAdd, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement2D)
 		}},
+		{want: "4184232e", setup: func(i *instruction) {
+			i.asVecRRR(vecOpSub, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement8B)
+		}},
+		{want: "4184236e", setup: func(i *instruction) {
+			i.asVecRRR(vecOpSub, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement16B)
+		}},
+		{want: "4184632e", setup: func(i *instruction) {
+			i.asVecRRR(vecOpSub, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement4H)
+		}},
+		{want: "4184636e", setup: func(i *instruction) {
+			i.asVecRRR(vecOpSub, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement8H)
+		}},
+		{want: "4184a32e", setup: func(i *instruction) {
+			i.asVecRRR(vecOpSub, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement2S)
+		}},
+		{want: "4184a36e", setup: func(i *instruction) {
+			i.asVecRRR(vecOpSub, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement4S)
+		}},
+		{want: "4184e36e", setup: func(i *instruction) {
+			i.asVecRRR(vecOpSub, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement2D)
+		}},
+		{want: "41bc230e", setup: func(i *instruction) {
+			i.asVecRRR(vecOpAddp, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement8B)
+		}},
+		{want: "41bc234e", setup: func(i *instruction) {
+			i.asVecRRR(vecOpAddp, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement16B)
+		}},
+		{want: "41bc630e", setup: func(i *instruction) {
+			i.asVecRRR(vecOpAddp, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement4H)
+		}},
+		{want: "41bc634e", setup: func(i *instruction) {
+			i.asVecRRR(vecOpAddp, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement8H)
+		}},
+		{want: "41bca30e", setup: func(i *instruction) {
+			i.asVecRRR(vecOpAddp, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement2S)
+		}},
+		{want: "41bca34e", setup: func(i *instruction) {
+			i.asVecRRR(vecOpAddp, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement4S)
+		}},
+		{want: "41bce34e", setup: func(i *instruction) {
+			i.asVecRRR(vecOpAddp, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement2D)
+		}},
+		{want: "419c230e", setup: func(i *instruction) {
+			i.asVecRRR(vecOpMul, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement8B)
+		}},
+		{want: "419c234e", setup: func(i *instruction) {
+			i.asVecRRR(vecOpMul, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement16B)
+		}},
+		{want: "419c630e", setup: func(i *instruction) {
+			i.asVecRRR(vecOpMul, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement4H)
+		}},
+		{want: "419c634e", setup: func(i *instruction) {
+			i.asVecRRR(vecOpMul, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement8H)
+		}},
+		{want: "419ca30e", setup: func(i *instruction) {
+			i.asVecRRR(vecOpMul, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement2S)
+		}},
+		{want: "419ca34e", setup: func(i *instruction) {
+			i.asVecRRR(vecOpMul, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement4S)
+		}},
+		{want: "41b8202e", setup: func(i *instruction) {
+			i.asVecMisc(vecOpNeg, operandNR(v1VReg), operandNR(v2VReg), vecArrangement8B)
+		}},
+		{want: "41b8206e", setup: func(i *instruction) {
+			i.asVecMisc(vecOpNeg, operandNR(v1VReg), operandNR(v2VReg), vecArrangement16B)
+		}},
+		{want: "41b8602e", setup: func(i *instruction) {
+			i.asVecMisc(vecOpNeg, operandNR(v1VReg), operandNR(v2VReg), vecArrangement4H)
+		}},
+		{want: "41b8606e", setup: func(i *instruction) {
+			i.asVecMisc(vecOpNeg, operandNR(v1VReg), operandNR(v2VReg), vecArrangement8H)
+		}},
+		{want: "41b8a02e", setup: func(i *instruction) {
+			i.asVecMisc(vecOpNeg, operandNR(v1VReg), operandNR(v2VReg), vecArrangement2S)
+		}},
+		{want: "41b8a06e", setup: func(i *instruction) {
+			i.asVecMisc(vecOpNeg, operandNR(v1VReg), operandNR(v2VReg), vecArrangement4S)
+		}},
+		{want: "41b8e06e", setup: func(i *instruction) {
+			i.asVecMisc(vecOpNeg, operandNR(v1VReg), operandNR(v2VReg), vecArrangement2D)
+		}},
+		{want: "4128a10e", setup: func(i *instruction) {
+			i.asVecMisc(vecOpXtn, operandNR(v1VReg), operandNR(v2VReg), vecArrangement2S)
+		}},
 		{want: "4100839a", setup: func(i *instruction) { i.asCSel(operandNR(x1VReg), operandNR(x2VReg), operandNR(x3VReg), eq, true) }},
 		{want: "4110839a", setup: func(i *instruction) { i.asCSel(operandNR(x1VReg), operandNR(x2VReg), operandNR(x3VReg), ne, true) }},
 		{want: "4100831a", setup: func(i *instruction) { i.asCSel(operandNR(x1VReg), operandNR(x2VReg), operandNR(x3VReg), eq, false) }},
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 2f94f8fc..1fb8b677 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"
@@ -451,3 +452,86 @@ fcvtzu w1, s2
 		})
 	}
 }
+
+func TestMachine_lowerVIMul(t *testing.T) {
+	for _, tc := range []struct {
+		name          string
+		expectedAsm   string
+		arrangement   vecArrangement
+		expectedBytes string
+	}{
+		{
+			name:        "2D",
+			arrangement: vecArrangement2D,
+			expectedAsm: `
+rev64 v2?.4s, x15.4s
+mul v2?.4s, v2?.4s, x2.4s
+xtn v1?.2s, x2.2s
+addp v2?.4s, v2?.4s, v2?.4s
+xtn v3?.2s, x15.2s
+shll x1.2s, v2?.2s
+umlal x1.2s, v3?.2s, v1?.2s
+`,
+			expectedBytes: "e009a04e009ca24e4028a10e00bca04ee029a10e0138a12e0180a02e",
+		},
+		{
+			name:        "8B",
+			arrangement: vecArrangement8B,
+			expectedAsm: `
+mul x1.8b, x2.8b, x15.8b
+`,
+			expectedBytes: "419c2f0e",
+		},
+		{
+			name:        "16B",
+			arrangement: vecArrangement16B,
+			expectedAsm: `
+mul x1.16b, x2.16b, x15.16b
+`,
+			expectedBytes: "419c2f4e",
+		},
+		{
+			name:        "4H",
+			arrangement: vecArrangement4H,
+			expectedAsm: `
+mul x1.4h, x2.4h, x15.4h
+`,
+			expectedBytes: "419c6f0e",
+		},
+		{
+			name:        "8H",
+			arrangement: vecArrangement8H,
+			expectedAsm: `
+mul x1.8h, x2.8h, x15.8h
+`,
+			expectedBytes: "419c6f4e",
+		},
+		{
+			name:        "2S",
+			arrangement: vecArrangement2S,
+			expectedAsm: `
+mul x1.2s, x2.2s, x15.2s
+`,
+			expectedBytes: "419caf0e",
+		},
+		{
+			name:        "4S",
+			arrangement: vecArrangement4S,
+			expectedAsm: `
+mul x1.4s, x2.4s, x15.4s
+`,
+			expectedBytes: "419caf4e",
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			_, _, m := newSetupWithMockContext()
+			m.lowerVIMul(operandNR(x1VReg), operandNR(x2VReg), operandNR(x15VReg), tc.arrangement)
+			require.Equal(t, tc.expectedAsm, "\n"+formatEmittedInstructionsInCurrentBlock(m)+"\n")
+
+			m.FlushPendingInstructions()
+			m.encode(m.perBlockHead)
+			buf := m.compiler.Buf()
+			require.Equal(t, tc.expectedBytes, hex.EncodeToString(buf))
+		})
+	}
+}
diff --git a/internal/engine/wazevo/e2e_test.go b/internal/engine/wazevo/e2e_test.go
index 8a88f165..a42b2325 100644
--- a/internal/engine/wazevo/e2e_test.go
+++ b/internal/engine/wazevo/e2e_test.go
@@ -137,6 +137,9 @@ func TestSpectestV2(t *testing.T) {
 		{"if"},
 		{"loop"},
 		{"simd_const"},
+		{"simd_i16x8_arith"},
+		{"simd_i32x4_arith"},
+		{"simd_i64x2_arith"},
 	} {
 		t.Run(tc.name, func(t *testing.T) {
 			t.Run("normal", func(t *testing.T) {