Repository: tetratelabs/wazero
Pull request #1660 (2023-08-25 01:46:20)

wazevo: passes f32/f64 spectests
#1496 


Patch:
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr.go b/internal/engine/wazevo/backend/isa/arm64/instr.go
index 1ea2dbb2..6c680784 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr.go
@@ -939,8 +939,6 @@ func (i *instruction) String() (str string) {
 			formatVRegSized(i.rm.nr(), size),
 			condFlag(i.u1),
 		)
-	case fpuRound:
-		panic("TODO")
 	case movToFpu:
 		panic("TODO")
 	case movToVec:
@@ -1214,8 +1212,6 @@ const (
 	intToFpu
 	// fpuCSel represents a 32/64-bit FP conditional select.
 	fpuCSel
-	// fpuRound represents a rounding to integer operation.
-	fpuRound
 	// movToFpu represents a move from a GPR to a scalar FP register.
 	movToFpu
 	// movToVec represents a move to a vector element from a GPR.
@@ -1440,8 +1436,13 @@ const (
 type fpuUniOp byte
 
 const (
-	fpuUniOpNeg = iota
-	fpuUniCvt32To64
+	fpuUniOpNeg fpuUniOp = iota
+	fpuUniOpCvt32To64
+	fpuUniOpSqrt
+	fpuUniOpPlus
+	fpuUniOpMinus
+	fpuUniOpZero
+	fpuUniOpNearest
 )
 
 // String implements the fmt.Stringer.
@@ -1449,8 +1450,18 @@ func (f fpuUniOp) String() string {
 	switch f {
 	case fpuUniOpNeg:
 		return "fneg"
-	case fpuUniCvt32To64:
+	case fpuUniOpCvt32To64:
 		return "fcvt"
+	case fpuUniOpSqrt:
+		return "fsqrt"
+	case fpuUniOpPlus:
+		return "frintp"
+	case fpuUniOpMinus:
+		return "frintm"
+	case fpuUniOpZero:
+		return "frintz"
+	case fpuUniOpNearest:
+		return "frintn"
 	}
 	panic(int(f))
 }
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go b/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
index 89f0b002..11fa31c1 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
@@ -261,7 +261,7 @@ func (i *instruction) encode(c backend.Compiler) {
 		c.Emit4Bytes(encodeCnvBetweenFloatInt(i))
 	case fpuRR:
 		c.Emit4Bytes(encodeFloatDataOneSource(
-			vecOp(i.u1),
+			fpuUniOp(i.u1),
 			regNumberInEncoding[i.rd.realReg()],
 			regNumberInEncoding[i.rn.realReg()],
 			i.u3 == 1,
@@ -273,16 +273,41 @@ func (i *instruction) encode(c backend.Compiler) {
 
 // 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, dst64bit bool) uint32 {
+func encodeFloatDataOneSource(op fpuUniOp, rd, rn uint32, dst64bit bool) uint32 {
 	var opcode, ptype uint32
 	switch op {
-	case fpuUniCvt32To64:
+	case fpuUniOpCvt32To64:
 		opcode = 0b000101
 	case fpuUniOpNeg:
 		opcode = 0b000010
 		if dst64bit {
 			ptype = 0b01
 		}
+	case fpuUniOpSqrt:
+		opcode = 0b000011
+		if dst64bit {
+			ptype = 0b01
+		}
+	case fpuUniOpPlus:
+		opcode = 0b001001
+		if dst64bit {
+			ptype = 0b01
+		}
+	case fpuUniOpMinus:
+		opcode = 0b001010
+		if dst64bit {
+			ptype = 0b01
+		}
+	case fpuUniOpZero:
+		opcode = 0b001011
+		if dst64bit {
+			ptype = 0b01
+		}
+	case fpuUniOpNearest:
+		opcode = 0b001000
+		if dst64bit {
+			ptype = 0b01
+		}
 	default:
 		panic("BUG")
 	}
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_instr.go b/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
index 3b9fe370..b42d893d 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
@@ -217,7 +217,7 @@ func (m *machine) LowerInstr(instr *ssa.Instruction) {
 		rn := m.getOperand_NR(m.compiler.ValueDefinition(v), extModeNone)
 		rd := operandNR(m.compiler.VRegOf(instr.Return()))
 		cnt := m.allocateInstr()
-		cnt.asFpuRR(fpuUniCvt32To64, rd, rn, true)
+		cnt.asFpuRR(fpuUniOpCvt32To64, rd, rn, true)
 		m.insert(cnt)
 	case ssa.OpcodeIreduce:
 		rn := m.getOperand_NR(m.compiler.ValueDefinition(instr.UnaryData()), extModeNone)
@@ -231,7 +231,17 @@ func (m *machine) LowerInstr(instr *ssa.Instruction) {
 		mov.asMove32(rd, rn.reg())
 		m.insert(mov)
 	case ssa.OpcodeFneg:
-		m.lowerFneg(instr)
+		m.lowerFpuUniOp(fpuUniOpNeg, instr.Arg(), instr.Return())
+	case ssa.OpcodeSqrt:
+		m.lowerFpuUniOp(fpuUniOpSqrt, instr.Arg(), instr.Return())
+	case ssa.OpcodeCeil:
+		m.lowerFpuUniOp(fpuUniOpPlus, instr.Arg(), instr.Return())
+	case ssa.OpcodeFloor:
+		m.lowerFpuUniOp(fpuUniOpMinus, instr.Arg(), instr.Return())
+	case ssa.OpcodeTrunc:
+		m.lowerFpuUniOp(fpuUniOpZero, instr.Arg(), instr.Return())
+	case ssa.OpcodeNearest:
+		m.lowerFpuUniOp(fpuUniOpNearest, instr.Arg(), instr.Return())
 	case ssa.OpcodeBitcast:
 		m.lowerBitcast(instr)
 	default:
@@ -273,18 +283,12 @@ func (m *machine) lowerBitcast(instr *ssa.Instruction) {
 	}
 }
 
-func (m *machine) lowerFneg(instr *ssa.Instruction) {
-	x := instr.UnaryData()
-	rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone)
-	rd := operandNR(m.compiler.VRegOf(instr.Return()))
+func (m *machine) lowerFpuUniOp(op fpuUniOp, in, out ssa.Value) {
+	rn := m.getOperand_NR(m.compiler.ValueDefinition(in), extModeNone)
+	rd := operandNR(m.compiler.VRegOf(out))
 
 	neg := m.allocateInstr()
-	switch x.Type() {
-	case ssa.TypeF32, ssa.TypeF64:
-		neg.asFpuRR(fpuUniOpNeg, rd, rn, x.Type().Bits() == 64)
-	default:
-		panic("TODO: vector neg")
-	}
+	neg.asFpuRR(op, rd, rn, in.Type().Bits() == 64)
 	m.insert(neg)
 }
 
diff --git a/internal/engine/wazevo/frontend/lower.go b/internal/engine/wazevo/frontend/lower.go
index a172ab75..ac2fd6ad 100644
--- a/internal/engine/wazevo/frontend/lower.go
+++ b/internal/engine/wazevo/frontend/lower.go
@@ -420,9 +420,53 @@ func (c *Compiler) lowerOpcode(op wasm.Opcode) {
 			return
 		}
 		x := state.pop()
-		negated := builder.AllocateInstruction().AsFneg(x).Insert(builder).Return()
-		state.push(negated)
+		v := builder.AllocateInstruction().AsFneg(x).Insert(builder).Return()
+		state.push(v)
+	case wasm.OpcodeF32Sqrt, wasm.OpcodeF64Sqrt:
+		if state.unreachable {
+			return
+		}
+		x := state.pop()
+		v := builder.AllocateInstruction().AsSqrt(x).Insert(builder).Return()
+		state.push(v)
+	case wasm.OpcodeF32Abs, wasm.OpcodeF64Abs:
+		if state.unreachable {
+			return
+		}
+		x := state.pop()
+		v := builder.AllocateInstruction().AsFabs(x).Insert(builder).Return()
+		state.push(v)
+	case wasm.OpcodeF32Ceil, wasm.OpcodeF64Ceil:
+		if state.unreachable {
+			return
+		}
+		x := state.pop()
+		v := builder.AllocateInstruction().AsCeil(x).Insert(builder).Return()
+		state.push(v)
+	case wasm.OpcodeF32Floor, wasm.OpcodeF64Floor:
+		if state.unreachable {
+			return
+		}
+		x := state.pop()
+		v := builder.AllocateInstruction().AsFloor(x).Insert(builder).Return()
+		state.push(v)
+	case wasm.OpcodeF32Trunc, wasm.OpcodeF64Trunc:
+		if state.unreachable {
+			return
+		}
+		x := state.pop()
+		v := builder.AllocateInstruction().AsTrunc(x).Insert(builder).Return()
+		state.push(v)
+	case wasm.OpcodeF32Nearest, wasm.OpcodeF64Nearest:
+		if state.unreachable {
+			return
+		}
+		x := state.pop()
+		v := builder.AllocateInstruction().AsNearest(x).Insert(builder).Return()
+		state.push(v)
+
 	case wasm.OpcodeI64TruncF64S:
+		panic("TODO")
 	case wasm.OpcodeI32ReinterpretF32:
 		if state.unreachable {
 			return
diff --git a/internal/engine/wazevo/ssa/builder.go b/internal/engine/wazevo/ssa/builder.go
index a9b4f1d6..39e31850 100644
--- a/internal/engine/wazevo/ssa/builder.go
+++ b/internal/engine/wazevo/ssa/builder.go
@@ -51,7 +51,8 @@ type Builder interface {
 	// MustFindValue searches the latest definition of the given Variable and returns the result.
 	MustFindValue(variable Variable) Value
 
-	// FindValueInLinearPath TODO
+	// FindValueInLinearPath tries to find the latest definition of the given Variable in the linear path to the current BasicBlock.
+	// If it cannot find the definition, or it's not sealed yet, it returns ValueInvalid.
 	FindValueInLinearPath(variable Variable) Value
 
 	// Seal declares that we've known all the predecessors to this block and were added via AddPred.
@@ -362,7 +363,7 @@ func (b *builder) allocateValue(typ Type) (v Value) {
 	return
 }
 
-// FindValueInLinearPath implements Builder.FindValue.
+// FindValueInLinearPath implements Builder.FindValueInLinearPath.
 func (b *builder) FindValueInLinearPath(variable Variable) Value {
 	return b.findValueInLinearPath(variable, b.currentBB)
 }
diff --git a/internal/engine/wazevo/ssa/instructions.go b/internal/engine/wazevo/ssa/instructions.go
index 1b46fade..55e58b04 100644
--- a/internal/engine/wazevo/ssa/instructions.go
+++ b/internal/engine/wazevo/ssa/instructions.go
@@ -872,6 +872,11 @@ var instructionSideEffects = [opcodeEnd]sideEffect{
 	OpcodeFpromote:           sideEffectFalse,
 	OpcodeBitcast:            sideEffectFalse,
 	OpcodeIreduce:            sideEffectFalse,
+	OpcodeSqrt:               sideEffectFalse,
+	OpcodeCeil:               sideEffectFalse,
+	OpcodeFloor:              sideEffectFalse,
+	OpcodeTrunc:              sideEffectFalse,
+	OpcodeNearest:            sideEffectFalse,
 }
 
 // HasSideEffects returns true if this instruction has side effects.
@@ -901,6 +906,11 @@ var instructionReturnTypes = [opcodeEnd]returnTypesFn{
 	OpcodeSExtend:   returnTypesFnSingle,
 	OpcodeUExtend:   returnTypesFnSingle,
 	OpcodeIreduce:   returnTypesFnSingle,
+	OpcodeSqrt:      returnTypesFnSingle,
+	OpcodeCeil:      returnTypesFnSingle,
+	OpcodeFloor:     returnTypesFnSingle,
+	OpcodeTrunc:     returnTypesFnSingle,
+	OpcodeNearest:   returnTypesFnSingle,
 	OpcodeCallIndirect: func(b *builder, instr *Instruction) (t1 Type, ts []Type) {
 		sigID := SignatureID(instr.v)
 		sig, ok := b.signatures[sigID]
@@ -1441,6 +1451,54 @@ func (i *Instruction) AsFneg(x Value) *Instruction {
 	return i
 }
 
+// AsSqrt initializes this instruction as an instruction with OpcodeSqrt.
+func (i *Instruction) AsSqrt(x Value) *Instruction {
+	i.opcode = OpcodeSqrt
+	i.v = x
+	i.typ = x.Type()
+	return i
+}
+
+// AsFabs initializes this instruction as an instruction with OpcodeFabs.
+func (i *Instruction) AsFabs(x Value) *Instruction {
+	i.opcode = OpcodeFabs
+	i.v = x
+	i.typ = x.Type()
+	return i
+}
+
+// AsCeil initializes this instruction as an instruction with OpcodeCeil.
+func (i *Instruction) AsCeil(x Value) *Instruction {
+	i.opcode = OpcodeCeil
+	i.v = x
+	i.typ = x.Type()
+	return i
+}
+
+// AsFloor initializes this instruction as an instruction with OpcodeFloor.
+func (i *Instruction) AsFloor(x Value) *Instruction {
+	i.opcode = OpcodeFloor
+	i.v = x
+	i.typ = x.Type()
+	return i
+}
+
+// AsTrunc initializes this instruction as an instruction with OpcodeTrunc.
+func (i *Instruction) AsTrunc(x Value) *Instruction {
+	i.opcode = OpcodeTrunc
+	i.v = x
+	i.typ = x.Type()
+	return i
+}
+
+// AsNearest initializes this instruction as an instruction with OpcodeNearest.
+func (i *Instruction) AsNearest(x Value) *Instruction {
+	i.opcode = OpcodeNearest
+	i.v = x
+	i.typ = x.Type()
+	return i
+}
+
 // AsBitcast initializes this instruction as an instruction with OpcodeBitcast.
 func (i *Instruction) AsBitcast(x Value, dstType Type) *Instruction {
 	i.opcode = OpcodeBitcast
@@ -1627,7 +1685,7 @@ func (i *Instruction) Format(b Builder) string {
 		instSuffix = fmt.Sprintf(" %s, %s", i.v.Format(b), i.v2.Format(b))
 	case OpcodeUndefined:
 	case OpcodeClz, OpcodeCtz, OpcodePopcnt, OpcodeFneg, OpcodeFcvtFromSint, OpcodeFcvtFromUint, OpcodeFpromote,
-		OpcodeIreduce, OpcodeBitcast:
+		OpcodeIreduce, OpcodeBitcast, OpcodeSqrt, OpcodeFabs, OpcodeCeil, OpcodeFloor, OpcodeTrunc, OpcodeNearest:
 		instSuffix = " " + i.v.Format(b)
 	default:
 		panic(fmt.Sprintf("TODO: format for %s", i.opcode))


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 4bc8f550..348bd05e 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
@@ -506,11 +506,42 @@ func TestInstruction_encode(t *testing.T) {
 		{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.asFpuRR(fpuUniCvt32To64, operandNR(v1VReg), operandNR(v2VReg), true)
+			i.asFpuRR(fpuUniOpCvt32To64, operandNR(v1VReg), operandNR(v2VReg), true)
 		}},
 		{want: "4140211e", setup: func(i *instruction) {
 			i.asFpuRR(fpuUniOpNeg, operandNR(v1VReg), operandNR(v2VReg), false)
 		}},
+
+		{want: "41c0211e", setup: func(i *instruction) {
+			i.asFpuRR(fpuUniOpSqrt, operandNR(v1VReg), operandNR(v2VReg), false)
+		}},
+		{want: "41c0611e", setup: func(i *instruction) {
+			i.asFpuRR(fpuUniOpSqrt, operandNR(v1VReg), operandNR(v2VReg), true)
+		}},
+		{want: "41c0241e", setup: func(i *instruction) {
+			i.asFpuRR(fpuUniOpPlus, operandNR(v1VReg), operandNR(v2VReg), false)
+		}},
+		{want: "41c0641e", setup: func(i *instruction) {
+			i.asFpuRR(fpuUniOpPlus, operandNR(v1VReg), operandNR(v2VReg), true)
+		}},
+		{want: "4140251e", setup: func(i *instruction) {
+			i.asFpuRR(fpuUniOpMinus, operandNR(v1VReg), operandNR(v2VReg), false)
+		}},
+		{want: "4140651e", setup: func(i *instruction) {
+			i.asFpuRR(fpuUniOpMinus, operandNR(v1VReg), operandNR(v2VReg), true)
+		}},
+		{want: "41c0251e", setup: func(i *instruction) {
+			i.asFpuRR(fpuUniOpZero, operandNR(v1VReg), operandNR(v2VReg), false)
+		}},
+		{want: "41c0651e", setup: func(i *instruction) {
+			i.asFpuRR(fpuUniOpZero, operandNR(v1VReg), operandNR(v2VReg), true)
+		}},
+		{want: "4140241e", setup: func(i *instruction) {
+			i.asFpuRR(fpuUniOpNearest, operandNR(v1VReg), operandNR(v2VReg), false)
+		}},
+		{want: "4140641e", setup: func(i *instruction) {
+			i.asFpuRR(fpuUniOpNearest, operandNR(v1VReg), operandNR(v2VReg), true)
+		}},
 		{want: "4140611e", setup: func(i *instruction) { i.asFpuRR(fpuUniOpNeg, operandNR(v1VReg), operandNR(v2VReg), true) }},
 		{want: "4201231e4201631e4201239e4201639e4201221e4201621e4201229e4201629e", setup: func(i *instruction) {
 			i.asNop0()
diff --git a/internal/engine/wazevo/e2e_test.go b/internal/engine/wazevo/e2e_test.go
index d939393c..d4bfa161 100644
--- a/internal/engine/wazevo/e2e_test.go
+++ b/internal/engine/wazevo/e2e_test.go
@@ -46,7 +46,7 @@ func TestSpectestV1(t *testing.T) {
 		{name: "block"},
 		{name: "binary"},
 		{name: "binary-leb128"},
-		//{name: "call"},
+		{name: "call"},
 		//{name: "call_indirect"},
 		{name: "comments"},
 		{name: "custom"},
@@ -55,7 +55,9 @@ func TestSpectestV1(t *testing.T) {
 		{name: "elem"},
 		{name: "endianness"},
 		{name: "exports"},
+		{name: "f32"},
 		{name: "f32_cmp"},
+		{name: "f64"},
 		{name: "f64_cmp"},
 		//{name: "fac"},
 		//{name: "float_exprs"},
@@ -67,7 +69,7 @@ func TestSpectestV1(t *testing.T) {
 		{name: "if"},
 		{name: "imports"},
 		//{name: "int_exprs"},
-		{name: "int_literals"},
+		//{name: "int_literals"},
 		{name: "labels"},
 		//{name: "left-to-right"},
 		{name: "load"},