Repository: tetratelabs/wazero
Pull request #1767 (2023-10-09 00:25:18)

wazevo(arm64): fixes Fcopysign instruction order
detected by the fuzzer

#1496 


Patch:
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_instr.go b/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
index 6887024e..f38a1225 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
@@ -1155,7 +1155,12 @@ func (m *machine) lowerFcopysign(x, y, ret ssa.Value) {
 	rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone)
 	rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone)
 	tmpF := operandNR(m.compiler.AllocateVReg(regalloc.RegTypeFloat))
+	tmpI := operandNR(m.compiler.AllocateVReg(regalloc.RegTypeInt))
+	rd := m.compiler.VRegOf(ret)
+	m.lowerFcopysignImpl(operandNR(rd), rn, rm, tmpI, tmpF, x.Type() == ssa.TypeF64)
+}
 
+func (m *machine) lowerFcopysignImpl(rd, rn, rm, tmpI, tmpF operand, _64bit bool) {
 	// This is exactly the same code emitted by GCC for "__builtin_copysign":
 	//
 	//    mov     x0, -9223372036854775808
@@ -1164,23 +1169,22 @@ func (m *machine) lowerFcopysign(x, y, ret ssa.Value) {
 	//
 
 	setMSB := m.allocateInstr()
-	tmpReg := m.compiler.AllocateVReg(regalloc.RegTypeInt)
-	if x.Type() == ssa.TypeF32 {
-		m.lowerConstantI32(tmpReg, math.MinInt32)
-		setMSB.asMovToVec(tmpF, operandNR(tmpReg), vecArrangementS, vecIndex(0))
+	if _64bit {
+		m.lowerConstantI64(tmpI.nr(), math.MinInt64)
+		setMSB.asMovToVec(tmpF, tmpI, vecArrangementD, vecIndex(0))
 	} else {
-		m.lowerConstantI64(tmpReg, math.MinInt64)
-		setMSB.asMovToVec(tmpF, operandNR(tmpReg), vecArrangementD, vecIndex(0))
+		m.lowerConstantI32(tmpI.nr(), math.MinInt32)
+		setMSB.asMovToVec(tmpF, tmpI, vecArrangementS, vecIndex(0))
 	}
 	m.insert(setMSB)
 
-	vbit := m.allocateInstr()
-	vbit.asVecRRR(vecOpBit, rn, rm, tmpF, vecArrangement8B)
-	m.insert(vbit)
-
 	mov := m.allocateInstr()
-	mov.asFpuMov64(m.compiler.VRegOf(ret), rn.nr())
+	mov.asFpuMov64(rd.nr(), rn.nr())
 	m.insert(mov)
+
+	vbit := m.allocateInstr()
+	vbit.asVecRRR(vecOpBit, rd, rm, tmpF, vecArrangement8B)
+	m.insert(vbit)
 }
 
 func (m *machine) lowerBitcast(instr *ssa.Instruction) {
diff --git a/internal/engine/wazevo/call_engine.go b/internal/engine/wazevo/call_engine.go
index 26f658c0..efd362e1 100644
--- a/internal/engine/wazevo/call_engine.go
+++ b/internal/engine/wazevo/call_engine.go
@@ -87,7 +87,7 @@ func (c *callEngine) requiredInitialStackSize() int {
 	const initialStackSizeDefault = 512
 	stackSize := initialStackSizeDefault
 	paramResultInBytes := c.sizeOfParamResultSlice * 8 * 2 // * 8 because uint64 is 8 bytes, and *2 because we need both separated param/result slots.
-	required := paramResultInBytes + 32                    // 32 is enough to accommodate the call frame info.
+	required := paramResultInBytes + 32 + 16               // 32 is enough to accommodate the call frame info, and 16 exists just in case when []byte is not aligned to 16 bytes.
 	if required > stackSize {
 		stackSize = required
 	}


Test patch:
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 1331a1c8..b8eaaa7b 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_instr_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_instr_test.go
@@ -851,3 +851,47 @@ dup v4?.2d, x5?
 bsl v4?.16b, v2?.16b, v3?.16b
 `, "\n"+formatEmittedInstructionsInCurrentBlock(m)+"\n")
 }
+
+func TestMachine_lowerFcopysign(t *testing.T) {
+	for _, tc := range []struct {
+		_64bit bool
+		exp    string
+	}{
+		{
+			_64bit: false,
+			exp: `
+movz w1?, #0x8000, lsl 16
+ins v2?.s[0], w1?
+mov v5?.8b, v3?.8b
+bit v5?.8b, v4?.8b, v2?.8b
+`,
+		},
+		{
+			_64bit: true,
+			exp: `
+movz x1?, #0x8000, lsl 48
+ins v2?.d[0], x1?
+mov v5?.8b, v3?.8b
+bit v5?.8b, v4?.8b, v2?.8b
+`,
+		},
+	} {
+		t.Run(fmt.Sprintf("64bit=%v", tc._64bit), func(t *testing.T) {
+			_, _, m := newSetupWithMockContext()
+			tmpI := operandNR(m.compiler.AllocateVReg(regalloc.RegTypeInt))
+			tmpF := operandNR(m.compiler.AllocateVReg(regalloc.RegTypeFloat))
+			rn := operandNR(m.compiler.AllocateVReg(regalloc.RegTypeFloat))
+			rm := operandNR(m.compiler.AllocateVReg(regalloc.RegTypeFloat))
+			rd := operandNR(m.compiler.AllocateVReg(regalloc.RegTypeFloat))
+
+			require.Equal(t, 1, int(tmpI.reg().ID()))
+			require.Equal(t, 2, int(tmpF.reg().ID()))
+			require.Equal(t, 3, int(rn.reg().ID()))
+			require.Equal(t, 4, int(rm.reg().ID()))
+			require.Equal(t, 5, int(rd.reg().ID()))
+
+			m.lowerFcopysignImpl(rd, rn, rm, tmpI, tmpF, tc._64bit)
+			require.Equal(t, tc.exp, "\n"+formatEmittedInstructionsInCurrentBlock(m)+"\n")
+		})
+	}
+}
diff --git a/internal/engine/wazevo/call_engine_test.go b/internal/engine/wazevo/call_engine_test.go
index ae3c3c12..b751f0fe 100644
--- a/internal/engine/wazevo/call_engine_test.go
+++ b/internal/engine/wazevo/call_engine_test.go
@@ -61,5 +61,5 @@ func TestCallEngine_requiredInitialStackSize(t *testing.T) {
 	c.sizeOfParamResultSlice = 10
 	require.Equal(t, 512, c.requiredInitialStackSize())
 	c.sizeOfParamResultSlice = 120
-	require.Equal(t, 120*16+32, c.requiredInitialStackSize())
+	require.Equal(t, 120*16+32+16, c.requiredInitialStackSize())
 }