Repository: tetratelabs/wazero
Pull request #1690 (2023-09-07 02:04:09)

wazevo: adds initial support for SIMD vectors
This makes wazevo pass the simd_const spectest.

#1496 


Patch:
diff --git a/internal/engine/wazevo/backend/compiler.go b/internal/engine/wazevo/backend/compiler.go
index 14f0f4bb..fea7ec7f 100644
--- a/internal/engine/wazevo/backend/compiler.go
+++ b/internal/engine/wazevo/backend/compiler.go
@@ -135,6 +135,7 @@ type compiler struct {
 	alreadyLowered map[*ssa.Instruction]bool
 	regAlloc       regalloc.Allocator
 	varEdges       [][2]regalloc.VReg
+	varEdgeTypes   []ssa.Type
 	constEdges     []struct {
 		cInst *ssa.Instruction
 		dst   regalloc.VReg
diff --git a/internal/engine/wazevo/backend/compiler_lower.go b/internal/engine/wazevo/backend/compiler_lower.go
index c3a2582c..fba3a12f 100644
--- a/internal/engine/wazevo/backend/compiler_lower.go
+++ b/internal/engine/wazevo/backend/compiler_lower.go
@@ -138,6 +138,7 @@ func (c *compiler) lowerBlockArguments(args []ssa.Value, succ ssa.BasicBlock) {
 	}
 
 	c.varEdges = c.varEdges[:0]
+	c.varEdgeTypes = c.varEdgeTypes[:0]
 	c.constEdges = c.constEdges[:0]
 	for i := 0; i < len(args); i++ {
 		dst := succ.Param(i)
@@ -154,6 +155,7 @@ func (c *compiler) lowerBlockArguments(args []ssa.Value, succ ssa.BasicBlock) {
 			srcReg := c.VRegOf(src)
 			// Even when the src=dst, insert the move so that we can keep such registers keep-alive.
 			c.varEdges = append(c.varEdges, [2]regalloc.VReg{srcReg, dstReg})
+			c.varEdgeTypes = append(c.varEdgeTypes, src.Type())
 		}
 	}
 
@@ -174,26 +176,26 @@ func (c *compiler) lowerBlockArguments(args []ssa.Value, succ ssa.BasicBlock) {
 
 	if separated {
 		// If there's no overlap, we can simply move the source to destination.
-		for _, edge := range c.varEdges {
+		for i, edge := range c.varEdges {
 			src, dst := edge[0], edge[1]
-			c.mach.InsertMove(dst, src)
+			c.mach.InsertMove(dst, src, c.varEdgeTypes[i])
 		}
 	} else {
 		// Otherwise, we allocate a temporary registers and move the source to the temporary register,
 		//
 		// First move all of them to temporary registers.
 		c.tempRegs = c.tempRegs[:0]
-		for _, edge := range c.varEdges {
+		for i, edge := range c.varEdges {
 			src := edge[0]
 			temp := c.AllocateVReg(src.RegType())
 			c.tempRegs = append(c.tempRegs, temp)
-			c.mach.InsertMove(temp, src)
+			c.mach.InsertMove(temp, src, c.varEdgeTypes[i])
 		}
 		// Then move the temporary registers to the destination.
 		for i, edge := range c.varEdges {
 			temp := c.tempRegs[i]
 			dst := edge[1]
-			c.mach.InsertMove(dst, temp)
+			c.mach.InsertMove(dst, temp, c.varEdgeTypes[i])
 		}
 	}
 
diff --git a/internal/engine/wazevo/backend/isa/arm64/abi.go b/internal/engine/wazevo/backend/isa/arm64/abi.go
index 5d12e034..001c1f35 100644
--- a/internal/engine/wazevo/backend/isa/arm64/abi.go
+++ b/internal/engine/wazevo/backend/isa/arm64/abi.go
@@ -153,7 +153,7 @@ func (a *abiImpl) CalleeGenFunctionArgsToVRegs(args []ssa.Value) {
 		reg := a.m.compiler.VRegOf(ssaArg)
 		arg := &a.args[i]
 		if arg.Kind == backend.ABIArgKindReg {
-			a.m.InsertMove(reg, arg.Reg)
+			a.m.InsertMove(reg, arg.Reg, arg.Type)
 		} else {
 			// TODO: we could use pair load if there's consecutive loads for the same type.
 			//
@@ -214,7 +214,7 @@ func (a *abiImpl) CalleeGenVRegsToFunctionReturns(rets []ssa.Value) {
 			}
 		}
 		if r.Kind == backend.ABIArgKindReg {
-			a.m.InsertMove(r.Reg, reg)
+			a.m.InsertMove(r.Reg, reg, ret.Type())
 		} else {
 			// TODO: we could use pair store if there's consecutive stores for the same type.
 			//
@@ -267,7 +267,7 @@ func (a *abiImpl) callerGenVRegToFunctionArg(argIndex int, reg regalloc.VReg, de
 		}
 	}
 	if arg.Kind == backend.ABIArgKindReg {
-		a.m.InsertMove(arg.Reg, reg)
+		a.m.InsertMove(arg.Reg, reg, arg.Type)
 	} else {
 		// TODO: we could use pair store if there's consecutive stores for the same type.
 		//
@@ -283,7 +283,7 @@ func (a *abiImpl) callerGenVRegToFunctionArg(argIndex int, reg regalloc.VReg, de
 func (a *abiImpl) callerGenFunctionReturnVReg(retIndex int, reg regalloc.VReg) {
 	r := &a.rets[retIndex]
 	if r.Kind == backend.ABIArgKindReg {
-		a.m.InsertMove(reg, r.Reg)
+		a.m.InsertMove(reg, r.Reg, r.Type)
 	} else {
 		// TODO: we could use pair load if there's consecutive loads for the same type.
 		amode := a.m.resolveAddressModeForOffset(r.Offset, r.Type.Bits(), spVReg)
diff --git a/internal/engine/wazevo/backend/isa/arm64/abi_go_entry.go b/internal/engine/wazevo/backend/isa/arm64/abi_go_entry.go
index 5bef5e27..355dde62 100644
--- a/internal/engine/wazevo/backend/isa/arm64/abi_go_entry.go
+++ b/internal/engine/wazevo/backend/isa/arm64/abi_go_entry.go
@@ -65,12 +65,12 @@ func (a *abiImpl) constructGoEntryPreamble() (root *instruction) {
 	// 		mov sp, x28
 	cur = a.move64(spVReg, goAllocatedStackPtr, cur)
 
+	var offset int64
 	for i := range a.args {
 		if i < 2 {
 			// module context ptr and execution context ptr are passed in x0 and x1 by the Go assembly function.
 			continue
 		}
-		offset := int64((i - 2) * 8) // Each param is passed in as uint64, so *8.
 		arg := &a.args[i]
 		switch arg.Kind {
 		case backend.ABIArgKindReg:
@@ -91,11 +91,18 @@ func (a *abiImpl) constructGoEntryPreamble() (root *instruction) {
 				instr.asFpuLoad(rd, mode, 32)
 			case ssa.TypeF64:
 				instr.asFpuLoad(rd, mode, 64)
+			case ssa.TypeV128:
+				instr.asFpuLoad(rd, mode, 128)
 			}
 			cur = linkInstr(cur, instr)
 		case backend.ABIArgKindStack:
 			panic("TODO")
 		}
+		if arg.Type == ssa.TypeV128 {
+			offset += 16
+		} else {
+			offset += 8
+		}
 	}
 
 	// Call the real function coming after epilogue:
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr.go b/internal/engine/wazevo/backend/isa/arm64/instr.go
index 76a55e24..2350b480 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr.go
@@ -89,6 +89,7 @@ var defKinds = [numInstructionKinds]defKind{
 	fpuLoad128:      defKindRD,
 	loadFpuConst32:  defKindRD,
 	loadFpuConst64:  defKindRD,
+	loadFpuConst128: defKindRD,
 	fpuStore32:      defKindNone,
 	fpuStore64:      defKindNone,
 	fpuStore128:     defKindNone,
@@ -198,6 +199,7 @@ var useKinds = [numInstructionKinds]useKind{
 	fpuStore128:     useKindRNAMode,
 	loadFpuConst32:  useKindNone,
 	loadFpuConst64:  useKindNone,
+	loadFpuConst128: useKindNone,
 	cSel:            useKindRNRM,
 	fpuCSel:         useKindRNRM,
 	movToVec:        useKindRN,
@@ -606,6 +608,13 @@ func (i *instruction) asLoadFpuConst64(rd regalloc.VReg, raw uint64) {
 	i.rd = operandNR(rd)
 }
 
+func (i *instruction) asLoadFpuConst128(rd regalloc.VReg, lo, hi uint64) {
+	i.kind = loadFpuConst128
+	i.u1 = lo
+	i.u2 = hi
+	i.rd = operandNR(rd)
+}
+
 func (i *instruction) asFpuCmp(rn, rm operand, is64bit bool) {
 	i.kind = fpuCmp
 	i.rn, i.rm = rn, rm
@@ -988,7 +997,8 @@ func (i *instruction) String() (str string) {
 	case loadFpuConst64:
 		str = fmt.Sprintf("ldr %s, #8; b 16; data.f64 %f", formatVRegSized(i.rd.nr(), 64), math.Float64frombits(i.u1))
 	case loadFpuConst128:
-		panic("TODO")
+		str = fmt.Sprintf("ldr %s, #8; b 32; data.v128  %016x %016x",
+			formatVRegSized(i.rd.nr(), 128), i.u1, i.u2)
 	case fpuToInt:
 		var op, src, dst string
 		if signed := i.u1 == 1; signed {
@@ -1504,6 +1514,7 @@ const (
 	vecOpUaddlv
 	vecOpBit
 	vecOpEOR
+	vecOpAdd
 )
 
 // bitOp determines the type of bitwise operation. Instructions whose kind is one of
@@ -1775,7 +1786,10 @@ func (i *instruction) size() int64 {
 		}
 		return 4 + 4 + 8
 	case loadFpuConst128:
-		panic("TODO")
+		if i.u1 == 0 && i.u2 == 0 {
+			return 4 // zero loading can be encoded as a single instruction.
+		}
+		return 4 + 4 + 16
 	case brTableSequence:
 		return 4*4 + int64(len(i.targets))*4
 	default:
@@ -1865,3 +1879,22 @@ type vecIndex byte
 
 // vecIndexNone indicates no vector index specified.
 const vecIndexNone = ^vecIndex(0)
+
+func ssaLeneToArrangement(lane ssa.VecLane) vecArrangement {
+	switch lane {
+	case ssa.VecLaneI8x16:
+		return vecArrangement16B
+	case ssa.VecLaneI16x8:
+		return vecArrangement8H
+	case ssa.VecLaneI32x4:
+		return vecArrangement4S
+	case ssa.VecLaneI64x2:
+		return vecArrangement2D
+	case ssa.VecLaneF32x4:
+		return vecArrangement4S
+	case ssa.VecLaneF64x2:
+		return vecArrangement2D
+	default:
+		panic(lane)
+	}
+}
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go b/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
index 2efaade3..af784a0f 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
@@ -109,6 +109,14 @@ func (i *instruction) encode(c backend.Compiler) {
 		} else {
 			encodeLoadFpuConst64(c, regNumberInEncoding[i.rd.realReg()], i.u1)
 		}
+	case loadFpuConst128:
+		rd := regNumberInEncoding[i.rd.realReg()]
+		lo, hi := i.u1, i.u2
+		if lo == 0 && hi == 0 {
+			c.Emit4Bytes(encodeVecRRR(vecOpEOR, rd, rd, rd, vecArrangement16B))
+		} else {
+			encodeLoadFpuConst128(c, rd, lo, hi)
+		}
 	case aluRRRR:
 		c.Emit4Bytes(encodeAluRRRR(
 			aluOp(i.u1),
@@ -328,32 +336,47 @@ func encodeSystemRegisterMove(rt uint32, fromSystem bool) uint32 {
 func encodeVecRRR(op vecOp, rd, rn, rm uint32, arr vecArrangement) uint32 {
 	switch op {
 	case vecOpBit:
-		var q uint32
-		switch arr {
-		case vecArrangement8B:
-			q = 0b0
-		case vecArrangement16B:
-			q = 0b1
-		default:
-			panic("BUG")
-		}
-		return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b00011, 0b10, 0b1, q)
+		_, q := arrToSizeQEncoded(arr)
+		return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b00011, 0b10 /* always has size 0b10 */, 0b1, q)
 	case vecOpEOR:
-		var q uint32
-		switch arr {
-		case vecArrangement8B:
-			q = 0b0
-		case vecArrangement16B:
-			q = 0b1
-		default:
-			panic("BUG")
-		}
-		return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b00011, 0b00, 0b1, q)
+		size, q := arrToSizeQEncoded(arr)
+		return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b00011, size, 0b1, q)
+	case vecOpAdd:
+		size, q := arrToSizeQEncoded(arr)
+		return encodeAdvancedSIMDThreeSame(rd, rn, rm, 0b10000, size, 0b0, q)
 	default:
 		panic("TODO")
 	}
 }
 
+func arrToSizeQEncoded(arr vecArrangement) (size, q uint32) {
+	switch arr {
+	case vecArrangement16B:
+		q = 0b1
+		fallthrough
+	case vecArrangement8B:
+		size = 0b00
+	case vecArrangement8H:
+		q = 0b1
+		fallthrough
+	case vecArrangement4H:
+		size = 0b01
+	case vecArrangement4S:
+		q = 0b1
+		fallthrough
+	case vecArrangement2S:
+		size = 0b10
+	case vecArrangement2D:
+		q = 0b1
+		fallthrough
+	case vecArrangement1D:
+		size = 0b11
+	default:
+		panic("BUG")
+	}
+	return
+}
+
 // encodeAdvancedSIMDThreeSame encodes as "Advanced SIMD three same" in
 // https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Scalar-Floating-Point-and-Advanced-SIMD?lang=en
 func encodeAdvancedSIMDThreeSame(rd, rn, rm, opcode, size, U, Q uint32) uint32 {
@@ -627,6 +650,32 @@ func encodeLoadFpuConst64(c backend.Compiler, rd uint32, rawF64 uint64) {
 	}
 }
 
+// encodeLoadFpuConst128 encodes the following three instructions:
+//
+//	ldr v8, #8  ;; literal load of data.f64
+//	b 20           ;; skip the data
+//	data.v128 xxxxxxx
+func encodeLoadFpuConst128(c backend.Compiler, rd uint32, lo, hi uint64) {
+	c.Emit4Bytes(
+		// https://developer.arm.com/documentation/ddi0596/2020-12/SIMD-FP-Instructions/LDR--literal--SIMD-FP---Load-SIMD-FP-Register--PC-relative-literal--?lang=en
+		0b1<<31 | 0b111<<26 | (0x8/4)<<5 | rd,
+	)
+	c.Emit4Bytes(encodeUnconditionalBranch(false, 20)) // b 20
+	if wazevoapi.PrintMachineCodeHexPerFunctionDisassemblable {
+		// Inlined data.v128 cannot be disassembled, so we add dummy instructions here.
+		c.Emit4Bytes(dummyInstruction)
+		c.Emit4Bytes(dummyInstruction)
+		c.Emit4Bytes(dummyInstruction)
+		c.Emit4Bytes(dummyInstruction)
+	} else {
+		// data.v128 xxxxxxx
+		c.Emit4Bytes(uint32(lo))
+		c.Emit4Bytes(uint32(lo >> 32))
+		c.Emit4Bytes(uint32(hi))
+		c.Emit4Bytes(uint32(hi >> 32))
+	}
+}
+
 // encodeAluRRRR encodes as Data-processing (3 source) in
 // https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Register?lang=en
 func encodeAluRRRR(op aluOp, rd, rn, rm, ra, _64bit uint32) uint32 {
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_constant.go b/internal/engine/wazevo/backend/isa/arm64/lower_constant.go
index f88affe4..ff3fc9fe 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_constant.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_constant.go
@@ -36,13 +36,13 @@ func (m *machine) InsertLoadConstant(instr *ssa.Instruction, vr regalloc.VReg) {
 		m.insert(loadF)
 	case ssa.TypeI32:
 		if v == 0 {
-			m.InsertMove(vr, xzrVReg)
+			m.InsertMove(vr, xzrVReg, ssa.TypeI32)
 		} else {
 			m.lowerConstantI32(vr, int32(v))
 		}
 	case ssa.TypeI64:
 		if v == 0 {
-			m.InsertMove(vr, xzrVReg)
+			m.InsertMove(vr, xzrVReg, ssa.TypeI64)
 		} else {
 			m.lowerConstantI64(vr, int64(v))
 		}
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_instr.go b/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
index d440b4b2..4f23e72e 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
@@ -291,6 +291,21 @@ func (m *machine) LowerInstr(instr *ssa.Instruction) {
 		rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone)
 		rd := operandNR(m.compiler.VRegOf(instr.Return()))
 		m.lowerIRem(ctxVReg, rd, rn, rm, x.Type() == ssa.TypeI64, op == ssa.OpcodeSrem)
+	case ssa.OpcodeVconst:
+		result := m.compiler.VRegOf(instr.Return())
+		lo, hi := instr.VconstData()
+		v := m.allocateInstr()
+		v.asLoadFpuConst128(result, lo, hi)
+		m.insert(v)
+	case ssa.OpcodeVIadd:
+		x, y, lane := instr.Arg2WithLane()
+		arr := ssaLeneToArrangement(lane)
+		add := 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()))
+		add.asVecRRR(vecOpAdd, rd, rn, rm, arr)
+		m.insert(add)
 	default:
 		panic("TODO: lowering " + op.String())
 	}
@@ -534,13 +549,15 @@ func (m *machine) lowerSubOrAdd(si *ssa.Instruction, add bool) {
 }
 
 // InsertMove implements backend.Machine.
-func (m *machine) InsertMove(dst, src regalloc.VReg) {
+func (m *machine) InsertMove(dst, src regalloc.VReg, typ ssa.Type) {
 	instr := m.allocateInstr()
-	switch src.RegType() {
-	case regalloc.RegTypeInt:
+	switch typ {
+	case ssa.TypeI32, ssa.TypeI64:
 		instr.asMove64(dst, src)
-	case regalloc.RegTypeFloat:
+	case ssa.TypeF32, ssa.TypeF64:
 		instr.asFpuMov64(dst, src)
+	case ssa.TypeV128:
+		instr.asFpuMov128(dst, src)
 	default:
 		panic("TODO")
 	}
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_mem.go b/internal/engine/wazevo/backend/isa/arm64/lower_mem.go
index 00c1b2c3..507ccf95 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_mem.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_mem.go
@@ -219,6 +219,8 @@ func (m *machine) lowerLoad(si *ssa.Instruction) {
 		load.asULoad(operandNR(dst), amode, typ.Bits())
 	case ssa.TypeF32, ssa.TypeF64:
 		load.asFpuLoad(operandNR(dst), amode, typ.Bits())
+	case ssa.TypeV128:
+		load.asFpuLoad(operandNR(dst), amode, 128)
 	default:
 		panic("TODO")
 	}
diff --git a/internal/engine/wazevo/backend/isa/arm64/machine_regalloc.go b/internal/engine/wazevo/backend/isa/arm64/machine_regalloc.go
index e96b2dc7..53179deb 100644
--- a/internal/engine/wazevo/backend/isa/arm64/machine_regalloc.go
+++ b/internal/engine/wazevo/backend/isa/arm64/machine_regalloc.go
@@ -322,6 +322,8 @@ func (m *machine) reloadRegister(v regalloc.VReg, instr *instruction, after bool
 		load.asULoad(operandNR(v), admode, typ.Bits())
 	case ssa.TypeF32, ssa.TypeF64:
 		load.asFpuLoad(operandNR(v), admode, typ.Bits())
+	case ssa.TypeV128:
+		load.asFpuLoad(operandNR(v), admode, 128)
 	default:
 		panic("TODO")
 	}
diff --git a/internal/engine/wazevo/backend/isa/arm64/reg.go b/internal/engine/wazevo/backend/isa/arm64/reg.go
index c2bc68f8..9f0ee1cb 100644
--- a/internal/engine/wazevo/backend/isa/arm64/reg.go
+++ b/internal/engine/wazevo/backend/isa/arm64/reg.go
@@ -286,8 +286,7 @@ func formatVRegSized(r regalloc.VReg, size byte) (ret string) {
 				panic("BUG: invalid register size")
 			}
 		default:
-			fmt.Println(r)
-			panic("BUG: invalid register type")
+			panic(fmt.Sprintf("BUG: invalid register type: %d fro %s", r.RegType(), r))
 		}
 	}
 	return
diff --git a/internal/engine/wazevo/backend/machine.go b/internal/engine/wazevo/backend/machine.go
index f051221c..130a2392 100644
--- a/internal/engine/wazevo/backend/machine.go
+++ b/internal/engine/wazevo/backend/machine.go
@@ -66,8 +66,8 @@ type (
 		// This will be called after the lowering of each SSA Instruction.
 		FlushPendingInstructions()
 
-		// InsertMove inserts a move instruction from src to dst.
-		InsertMove(dst, src regalloc.VReg)
+		// InsertMove inserts a move instruction from src to dst whose type is typ.
+		InsertMove(dst, src regalloc.VReg, typ ssa.Type)
 
 		// InsertReturn inserts the return instruction to return from the current function.
 		InsertReturn()
diff --git a/internal/engine/wazevo/backend/regalloc/reg.go b/internal/engine/wazevo/backend/regalloc/reg.go
index 31e39732..995ab188 100644
--- a/internal/engine/wazevo/backend/regalloc/reg.go
+++ b/internal/engine/wazevo/backend/regalloc/reg.go
@@ -117,7 +117,7 @@ func RegTypeOf(p ssa.Type) RegType {
 	switch p {
 	case ssa.TypeI32, ssa.TypeI64:
 		return RegTypeInt
-	case ssa.TypeF32, ssa.TypeF64:
+	case ssa.TypeF32, ssa.TypeF64, ssa.TypeV128:
 		return RegTypeFloat
 	default:
 		panic("invalid type")
diff --git a/internal/engine/wazevo/frontend/frontend.go b/internal/engine/wazevo/frontend/frontend.go
index 752073fd..cc33a20e 100644
--- a/internal/engine/wazevo/frontend/frontend.go
+++ b/internal/engine/wazevo/frontend/frontend.go
@@ -170,6 +170,8 @@ func (c *Compiler) declareWasmLocals(entry ssa.BasicBlock) {
 			zeroInst.AsF32const(0)
 		case ssa.TypeF64:
 			zeroInst.AsF64const(0)
+		case ssa.TypeV128:
+			zeroInst.AsVconst(0, 0)
 		default:
 			panic("TODO: " + wasm.ValueTypeName(typ))
 		}
@@ -215,6 +217,8 @@ func (c *Compiler) declareWasmGlobal(typ wasm.ValueType, mutable bool) {
 		st = ssa.TypeF32
 	case wasm.ValueTypeF64:
 		st = ssa.TypeF64
+	case wasm.ValueTypeV128:
+		st = ssa.TypeV128
 	default:
 		panic("TODO: " + wasm.ValueTypeName(typ))
 	}
@@ -238,6 +242,8 @@ func WasmTypeToSSAType(vt wasm.ValueType) ssa.Type {
 		return ssa.TypeF32
 	case wasm.ValueTypeF64:
 		return ssa.TypeF64
+	case wasm.ValueTypeV128:
+		return ssa.TypeV128
 	default:
 		panic("TODO: " + wasm.ValueTypeName(vt))
 	}
diff --git a/internal/engine/wazevo/frontend/lower.go b/internal/engine/wazevo/frontend/lower.go
index 748bf2ba..e249c09d 100644
--- a/internal/engine/wazevo/frontend/lower.go
+++ b/internal/engine/wazevo/frontend/lower.go
@@ -481,14 +481,14 @@ func (c *Compiler) lowerCurrentOpcode() {
 		).Insert(builder).Return()
 		state.push(ret)
 	case wasm.OpcodeMiscPrefix:
-		c.loweringState.pc++
+		state.pc++
 		// A misc opcode is encoded as an unsigned variable 32-bit integer.
-		miscOpUint, num, err := leb128.LoadUint32(c.wasmFunctionBody[c.loweringState.pc:])
+		miscOpUint, num, err := leb128.LoadUint32(c.wasmFunctionBody[state.pc:])
 		if err != nil {
 			// In normal conditions this should never happen because the function has passed validation.
 			panic(fmt.Sprintf("failed to read misc opcode: %v", err))
 		}
-		c.loweringState.pc += int(num - 1)
+		state.pc += int(num - 1)
 		miscOp := wasm.OpcodeMisc(miscOpUint)
 		switch miscOp {
 		case wasm.OpcodeMiscI64TruncSatF64S, wasm.OpcodeMiscI64TruncSatF32S,
@@ -1298,6 +1298,37 @@ func (c *Compiler) lowerCurrentOpcode() {
 		cvt.AsFpromote(state.pop())
 		builder.InsertInstruction(cvt)
 		state.push(cvt.Return())
+
+	case wasm.OpcodeVecPrefix:
+		state.pc++
+		vecOp := c.wasmFunctionBody[state.pc]
+		state.pc++
+		switch vecOp {
+		case wasm.OpcodeVecV128Const:
+			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:
+			if state.unreachable {
+				break
+			}
+			var lane ssa.VecLane
+			switch vecOp {
+			case wasm.OpcodeVecI32x4Add:
+				lane = ssa.VecLaneI32x4
+			case wasm.OpcodeVecI64x2Add:
+				lane = ssa.VecLaneI64x2
+			}
+			v2 := state.pop()
+			v1 := state.pop()
+			ret := builder.AllocateInstruction().AsVIadd(v1, v2, lane).Insert(builder).Return()
+			state.push(ret)
+		default:
+			panic("TODO: unsupported vector instruction: " + wasm.VectorInstructionName(vecOp))
+		}
 	default:
 		panic("TODO: unsupported in wazevo yet: " + wasm.InstructionName(op))
 	}
diff --git a/internal/engine/wazevo/module_engine.go b/internal/engine/wazevo/module_engine.go
index 249d20cb..1f1d9211 100644
--- a/internal/engine/wazevo/module_engine.go
+++ b/internal/engine/wazevo/module_engine.go
@@ -121,8 +121,8 @@ func (m *moduleEngine) NewFunction(index wasm.Index) api.Function {
 
 	src := m.module.Source
 	typ := src.TypeSection[src.FunctionSection[localIndex]]
-	sizeOfParamResultSlice := len(typ.Results)
-	if ps := len(typ.Params); ps > sizeOfParamResultSlice {
+	sizeOfParamResultSlice := typ.ResultNumInUint64
+	if ps := typ.ParamNumInUint64; ps > sizeOfParamResultSlice {
 		sizeOfParamResultSlice = ps
 	}
 	p := m.parent
diff --git a/internal/engine/wazevo/ssa/instructions.go b/internal/engine/wazevo/ssa/instructions.go
index b526f1dd..a9386641 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
 }
 
+// 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)
+}
+
 // Arg3 returns the first three arguments to this instruction.
 func (i *Instruction) Arg3() (Value, Value, Value) {
 	return i.v, i.v2, i.v3
@@ -271,16 +276,13 @@ const (
 	// OpcodeIconst represents the integer const.
 	OpcodeIconst
 
-	// OpcodeF32const ...
-	// `v = f32const N`. (UnaryIeee32)
+	// OpcodeF32const represents the single-precision const.
 	OpcodeF32const
 
-	// OpcodeF64const ...
-	// `v = f64const N`. (UnaryIeee64)
+	// OpcodeF64const represents the double-precision const.
 	OpcodeF64const
 
-	// OpcodeVconst ...
-	// `v = vconst N`.
+	// OpcodeVconst represents the 128bit vector const.
 	OpcodeVconst
 
 	// OpcodeShuffle ...
@@ -327,6 +329,9 @@ const (
 	// OpcodeIadd performs an integer addition: `v = Iadd x, y`.
 	OpcodeIadd
 
+	// OpcodeVIadd performs an integer addition: `v = IVadd.lane x, y` on vector.
+	OpcodeVIadd
+
 	// OpcodeIsub performs an integer subtraction: `v = Isub x, y`.
 	OpcodeIsub
 
@@ -707,6 +712,7 @@ var (
 	returnTypesFnI32                     = func(b *builder, instr *Instruction) (t1 Type, ts []Type) { return TypeI32, nil }
 	returnTypesFnF32                     = func(b *builder, instr *Instruction) (t1 Type, ts []Type) { return TypeF32, nil }
 	returnTypesFnF64                     = func(b *builder, instr *Instruction) (t1 Type, ts []Type) { return TypeF64, nil }
+	returnTypesFnV128                    = func(b *builder, instr *Instruction) (t1 Type, ts []Type) { return TypeV128, nil }
 )
 
 // sideEffect provides the info to determine if an instruction has side effects which
@@ -797,6 +803,8 @@ var instructionSideEffects = [opcodeEnd]sideEffect{
 	OpcodeUrem:               sideEffectTraps,
 	OpcodeFabs:               sideEffectNone,
 	OpcodeFcopysign:          sideEffectNone,
+	OpcodeVconst:             sideEffectNone,
+	OpcodeVIadd:              sideEffectNone,
 }
 
 // sideEffect returns true if this instruction has side effects.
@@ -810,6 +818,7 @@ func (i *Instruction) sideEffect() sideEffect {
 
 // instructionReturnTypes provides the function to determine the return types of an instruction.
 var instructionReturnTypes = [opcodeEnd]returnTypesFn{
+	OpcodeVIadd:     returnTypesFnV128,
 	OpcodeBand:      returnTypesFnSingle,
 	OpcodeFcopysign: returnTypesFnSingle,
 	OpcodeBitcast:   returnTypesFnSingle,
@@ -911,6 +920,7 @@ var instructionReturnTypes = [opcodeEnd]returnTypesFn{
 	OpcodeFneg:               returnTypesFnSingle,
 	OpcodeFdemote:            returnTypesFnF32,
 	OpcodeFpromote:           returnTypesFnF64,
+	OpcodeVconst:             returnTypesFnV128,
 }
 
 // AsLoad initializes this instruction as a store instruction with OpcodeLoad.
@@ -992,6 +1002,16 @@ func (i *Instruction) AsIadd(x, y Value) *Instruction {
 	return i
 }
 
+// AsVIadd initializes this instruction as an integer addition instruction with OpcodeVIadd on a vector.
+func (i *Instruction) AsVIadd(x, y Value, lane VecLane) *Instruction {
+	i.opcode = OpcodeVIadd
+	i.v = x
+	i.v2 = y
+	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
@@ -1213,6 +1233,20 @@ func (i *Instruction) AsF64const(f float64) *Instruction {
 	return i
 }
 
+// AsVconst initializes this instruction as a vector constant instruction with OpcodeVconst.
+func (i *Instruction) AsVconst(lo, hi uint64) *Instruction {
+	i.opcode = OpcodeVconst
+	i.typ = TypeV128
+	i.u1 = lo
+	i.u2 = hi
+	return i
+}
+
+// VconstData returns the operands of this vector constant instruction.
+func (i *Instruction) VconstData() (lo, hi uint64) {
+	return i.u1, i.u2
+}
+
 // AsReturn initializes this instruction as a return instruction with OpcodeReturn.
 func (i *Instruction) AsReturn(vs []Value) *Instruction {
 	i.opcode = OpcodeReturn
@@ -1635,6 +1669,8 @@ func (i *Instruction) Format(b Builder) string {
 		case TypeI64:
 			instSuffix = fmt.Sprintf("_64 %#x", i.u1)
 		}
+	case OpcodeVconst:
+		instSuffix = fmt.Sprintf(" %016x %016x", i.u1, i.u2)
 	case OpcodeF32const:
 		instSuffix = fmt.Sprintf(" %f", math.Float32frombits(uint32(i.u1)))
 	case OpcodeF64const:
@@ -1689,6 +1725,8 @@ 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:
+		instSuffix = fmt.Sprintf(".%s %s, %s", VecLane(i.u1), i.v.Format(b), i.v2.Format(b))
 	default:
 		panic(fmt.Sprintf("TODO: format for %s", i.opcode))
 	}
@@ -2059,6 +2097,8 @@ func (o Opcode) String() (ret string) {
 		return "Fence"
 	case OpcodeExtractVector:
 		return "ExtractVector"
+	case OpcodeVIadd:
+		return "VIadd"
 	}
 	panic(fmt.Sprintf("unknown opcode %d", o))
 }
diff --git a/internal/engine/wazevo/ssa/type.go b/internal/engine/wazevo/ssa/type.go
index 1657e3cb..83c61b24 100644
--- a/internal/engine/wazevo/ssa/type.go
+++ b/internal/engine/wazevo/ssa/type.go
@@ -19,7 +19,8 @@ const (
 	// TypeF64 represents 64-bit floats in the IEEE 754.
 	TypeF64
 
-	// TODO: SIMD, ref types!
+	// TypeV128 represents 128-bit SIMD vectors.
+	TypeV128
 )
 
 // String implements fmt.Stringer.
@@ -35,6 +36,8 @@ func (t Type) String() (ret string) {
 		return "f32"
 	case TypeF64:
 		return "f64"
+	case TypeV128:
+		return "v128"
 	default:
 		panic(int(t))
 	}
@@ -52,6 +55,8 @@ func (t Type) Bits() byte {
 		return 32
 	case TypeI64, TypeF64:
 		return 64
+	case TypeV128:
+		return 128
 	default:
 		panic(int(t))
 	}
@@ -65,3 +70,38 @@ func (t Type) Size() byte {
 func (t Type) invalid() bool {
 	return t == typeInvalid
 }
+
+// VecLane represents a lane in a SIMD vector.
+type VecLane byte
+
+const (
+	VecLaneInvalid VecLane = 1 + iota
+	VecLaneI8x16
+	VecLaneI16x8
+	VecLaneI32x4
+	VecLaneI64x2
+	VecLaneF32x4
+	VecLaneF64x2
+)
+
+// String implements fmt.Stringer.
+func (vl VecLane) String() (ret string) {
+	switch vl {
+	case VecLaneInvalid:
+		return "invalid"
+	case VecLaneI8x16:
+		return "i8x16"
+	case VecLaneI16x8:
+		return "i16x8"
+	case VecLaneI32x4:
+		return "i32x4"
+	case VecLaneI64x2:
+		return "i64x2"
+	case VecLaneF32x4:
+		return "f32x4"
+	case VecLaneF64x2:
+		return "f64x2"
+	default:
+		panic(int(vl))
+	}
+}


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 65fe22fb..a5f6201f 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
@@ -40,6 +40,15 @@ func TestInstruction_encode(t *testing.T) {
 		{want: "411c232e", setup: func(i *instruction) {
 			i.asVecRRR(vecOpEOR, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement8B)
 		}},
+		{want: "4184234e", setup: func(i *instruction) {
+			i.asVecRRR(vecOpAdd, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement16B)
+		}},
+		{want: "4184a34e", setup: func(i *instruction) {
+			i.asVecRRR(vecOpAdd, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement4S)
+		}},
+		{want: "4184e34e", setup: func(i *instruction) {
+			i.asVecRRR(vecOpAdd, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement2D)
+		}},
 		{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) }},
@@ -186,6 +195,8 @@ func TestInstruction_encode(t *testing.T) {
 		{want: "5000005c03000014000000000000f03f", setup: func(i *instruction) {
 			i.asLoadFpuConst64(v16VReg, math.Float64bits(1.0))
 		}},
+		{want: "101e306e", setup: func(i *instruction) { i.asLoadFpuConst128(v16VReg, 0, 0) }},
+		{want: "5000009c05000014ffffffffffffffffaaaaaaaaaaaaaaaa", setup: func(i *instruction) { i.asLoadFpuConst128(v16VReg, 0xffffffff_ffffffff, 0xaaaaaaaa_aaaaaaaa) }},
 		{want: "8220061b", setup: func(i *instruction) {
 			i.asALURRRR(aluOpMAdd, operandNR(x2VReg), operandNR(x4VReg), operandNR(x6VReg), operandNR(x8VReg), false)
 		}},
@@ -626,6 +637,12 @@ func TestInstruction_encode(t *testing.T) {
 			// require.NoError(t, err, hex.EncodeToString(m.buf))
 			// fmt.Println(inst.String())
 			require.Equal(t, tc.want, hex.EncodeToString(mc.buf))
+
+			var actualSize int
+			for cur := i; cur != nil; cur = cur.next {
+				actualSize += int(cur.size())
+			}
+			require.Equal(t, len(tc.want)/2, actualSize)
 		})
 	}
 }
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 34e32b88..b3415ab9 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_instr_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_instr_test.go
@@ -256,6 +256,7 @@ func TestMachine_InsertMove(t *testing.T) {
 	for _, tc := range []struct {
 		name        string
 		src, dst    regalloc.VReg
+		typ         ssa.Type
 		instruction string
 	}{
 		{
@@ -263,17 +264,26 @@ func TestMachine_InsertMove(t *testing.T) {
 			src:         regalloc.VReg(1).SetRegType(regalloc.RegTypeInt),
 			dst:         regalloc.VReg(2).SetRegType(regalloc.RegTypeInt),
 			instruction: "mov x1?, x2?",
+			typ:         ssa.TypeI64,
 		},
 		{
 			name:        "float",
 			src:         regalloc.VReg(1).SetRegType(regalloc.RegTypeFloat),
 			dst:         regalloc.VReg(2).SetRegType(regalloc.RegTypeFloat),
 			instruction: "mov v1?.8b, v2?.8b",
+			typ:         ssa.TypeF64,
+		},
+		{
+			name:        "vector",
+			src:         regalloc.VReg(1).SetRegType(regalloc.RegTypeFloat),
+			dst:         regalloc.VReg(2).SetRegType(regalloc.RegTypeFloat),
+			instruction: "mov v1?.16b, v2?.16b",
+			typ:         ssa.TypeV128,
 		},
 	} {
 		t.Run(tc.name, func(t *testing.T) {
 			_, _, m := newSetupWithMockContext()
-			m.InsertMove(tc.src, tc.dst)
+			m.InsertMove(tc.src, tc.dst, tc.typ)
 			require.Equal(t, tc.instruction, formatEmittedInstructionsInCurrentBlock(m))
 		})
 	}
diff --git a/internal/engine/wazevo/backend/machine_test.go b/internal/engine/wazevo/backend/machine_test.go
index 5e44a698..d0cd665b 100644
--- a/internal/engine/wazevo/backend/machine_test.go
+++ b/internal/engine/wazevo/backend/machine_test.go
@@ -118,7 +118,7 @@ func (m mockMachine) Reset() {
 func (m mockMachine) FlushPendingInstructions() {}
 
 // InsertMove implements Machine.InsertMove.
-func (m mockMachine) InsertMove(dst, src regalloc.VReg) {
+func (m mockMachine) InsertMove(dst, src regalloc.VReg, typ ssa.Type) {
 	m.insertMove(dst, src)
 }
 
diff --git a/internal/engine/wazevo/e2e_test.go b/internal/engine/wazevo/e2e_test.go
index 5e6bb7f2..116808b3 100644
--- a/internal/engine/wazevo/e2e_test.go
+++ b/internal/engine/wazevo/e2e_test.go
@@ -129,6 +129,7 @@ func TestSpectestV2(t *testing.T) {
 		name string
 	}{
 		{"conversions"}, // includes non-trapping conversions.
+		{"simd_const"},
 	} {
 		t.Run(tc.name, func(t *testing.T) {
 			spectest.RunCase(t, v2.Testcases, tc.name, context.Background(), config,