// Copyright 2021 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. #include #include #include "Common/Arm64Emitter.h" #include "Common/BitUtils.h" #include "Common/CommonTypes.h" #include "Common/FPURoundMode.h" #include "Core/PowerPC/Interpreter/Interpreter_FPUtils.h" #include "Core/PowerPC/JitArm64/Jit.h" #include #include namespace { using namespace Arm64Gen; // The ABI situation for returning an std::tuple seems annoying. Let's use this struct instead template struct Pair { T value1; T value2; }; class TestConversion : private JitArm64 { public: TestConversion() { AllocCodeSpace(4096); AddChildCodeSpace(&farcode, 2048); gpr.Init(this); fpr.Init(this); js.fpr_is_store_safe = BitSet32(0); GetAsmRoutines()->cdts = GetCodePtr(); GenerateConvertDoubleToSingle(); GetAsmRoutines()->cstd = GetCodePtr(); GenerateConvertSingleToDouble(); gpr.Lock(ARM64Reg::W30); fpr.Lock(ARM64Reg::Q0, ARM64Reg::Q1); convert_single_to_double_lower = Common::BitCast(GetCodePtr()); m_float_emit.INS(32, ARM64Reg::S0, 0, ARM64Reg::W0); ConvertSingleToDoubleLower(0, ARM64Reg::D0, ARM64Reg::S0, ARM64Reg::Q1); m_float_emit.UMOV(64, ARM64Reg::X0, ARM64Reg::D0, 0); RET(); convert_single_to_double_pair = Common::BitCast (*)(u32, u32)>(GetCodePtr()); m_float_emit.INS(32, ARM64Reg::D0, 0, ARM64Reg::W0); m_float_emit.INS(32, ARM64Reg::D0, 1, ARM64Reg::W1); ConvertSingleToDoublePair(0, ARM64Reg::Q0, ARM64Reg::D0, ARM64Reg::Q1); m_float_emit.UMOV(64, ARM64Reg::X0, ARM64Reg::Q0, 0); m_float_emit.UMOV(64, ARM64Reg::X1, ARM64Reg::Q0, 1); RET(); convert_double_to_single_lower = Common::BitCast(GetCodePtr()); m_float_emit.INS(64, ARM64Reg::D0, 0, ARM64Reg::X0); ConvertDoubleToSingleLower(0, ARM64Reg::S0, ARM64Reg::D0); m_float_emit.UMOV(32, ARM64Reg::W0, ARM64Reg::S0, 0); RET(); convert_double_to_single_pair = Common::BitCast (*)(u64, u64)>(GetCodePtr()); m_float_emit.INS(64, ARM64Reg::Q0, 0, ARM64Reg::X0); m_float_emit.INS(64, ARM64Reg::Q0, 1, ARM64Reg::X1); ConvertDoubleToSinglePair(0, ARM64Reg::D0, ARM64Reg::Q0); m_float_emit.UMOV(64, ARM64Reg::X0, ARM64Reg::D0, 0); RET(); gpr.Unlock(ARM64Reg::W30); fpr.Unlock(ARM64Reg::Q0, ARM64Reg::Q1); FlushIcache(); // Set the rounding mode to something that's as annoying as possible to handle // (flush-to-zero enabled, and rounding not symmetric about the origin) FPURoundMode::SetSIMDMode(FPURoundMode::RoundMode::ROUND_UP, true); } ~TestConversion() override { FPURoundMode::LoadDefaultSIMDState(); FreeCodeSpace(); } u64 ConvertSingleToDouble(u32 value) { return convert_single_to_double_lower(value); } Pair ConvertSingleToDouble(u32 value1, u32 value2) { return convert_single_to_double_pair(value1, value2); } u32 ConvertDoubleToSingle(u64 value) { return convert_double_to_single_lower(value); } Pair ConvertDoubleToSingle(u64 value1, u64 value2) { return convert_double_to_single_pair(value1, value2); } private: std::function convert_single_to_double_lower; std::function(u32, u32)> convert_single_to_double_pair; std::function convert_double_to_single_lower; std::function(u64, u64)> convert_double_to_single_pair; }; } // namespace TEST(JitArm64, ConvertDoubleToSingle) { TestConversion test; const std::vector input_values{ // Special values 0x0000'0000'0000'0000, // positive zero 0x0000'0000'0000'0001, // smallest positive denormal 0x0000'0000'0100'0000, 0x000F'FFFF'FFFF'FFFF, // largest positive denormal 0x0010'0000'0000'0000, // smallest positive normal 0x0010'0000'0000'0002, 0x3FF0'0000'0000'0000, // 1.0 0x7FEF'FFFF'FFFF'FFFF, // largest positive normal 0x7FF0'0000'0000'0000, // positive infinity 0x7FF0'0000'0000'0001, // first positive SNaN 0x7FF7'FFFF'FFFF'FFFF, // last positive SNaN 0x7FF8'0000'0000'0000, // first positive QNaN 0x7FFF'FFFF'FFFF'FFFF, // last positive QNaN 0x8000'0000'0000'0000, // negative zero 0x8000'0000'0000'0001, // smallest negative denormal 0x8000'0000'0100'0000, 0x800F'FFFF'FFFF'FFFF, // largest negative denormal 0x8010'0000'0000'0000, // smallest negative normal 0x8010'0000'0000'0002, 0xBFF0'0000'0000'0000, // -1.0 0xFFEF'FFFF'FFFF'FFFF, // largest negative normal 0xFFF0'0000'0000'0000, // negative infinity 0xFFF0'0000'0000'0001, // first negative SNaN 0xFFF7'FFFF'FFFF'FFFF, // last negative SNaN 0xFFF8'0000'0000'0000, // first negative QNaN 0xFFFF'FFFF'FFFF'FFFF, // last negative QNaN // (exp > 896) Boundary Case 0x3800'0000'0000'0000, // 2^(-127) = Denormal in single-prec 0x3810'0000'0000'0000, // 2^(-126) = Smallest single-prec normal 0xB800'0000'0000'0000, // -2^(-127) = Denormal in single-prec 0xB810'0000'0000'0000, // -2^(-126) = Smallest single-prec normal 0x3800'1234'5678'9ABC, 0x3810'1234'5678'9ABC, 0xB800'1234'5678'9ABC, 0xB810'1234'5678'9ABC, // (exp >= 874) Boundary Case 0x3680'0000'0000'0000, // 2^(-150) = Unrepresentable in single-prec 0x36A0'0000'0000'0000, // 2^(-149) = Smallest single-prec denormal 0x36B0'0000'0000'0000, // 2^(-148) = Single-prec denormal 0xB680'0000'0000'0000, // -2^(-150) = Unrepresentable in single-prec 0xB6A0'0000'0000'0000, // -2^(-149) = Smallest single-prec denormal 0xB6B0'0000'0000'0000, // -2^(-148) = Single-prec denormal 0x3680'1234'5678'9ABC, 0x36A0'1234'5678'9ABC, 0x36B0'1234'5678'9ABC, 0xB680'1234'5678'9ABC, 0xB6A0'1234'5678'9ABC, 0xB6B0'1234'5678'9ABC, // Some typical numbers 0x3FF8'0000'0000'0000, // 1.5 0x408F'4000'0000'0000, // 1000 0xC008'0000'0000'0000, // -3 }; for (const u64 input : input_values) { const u32 expected = ConvertToSingle(input); const u32 actual = test.ConvertDoubleToSingle(input); if (expected != actual) fmt::print("{:016x} -> {:08x} == {:08x}\n", input, actual, expected); EXPECT_EQ(expected, actual); } for (const u64 input1 : input_values) { for (const u64 input2 : input_values) { const u32 expected1 = ConvertToSingle(input1); const u32 expected2 = ConvertToSingle(input2); const auto [actual1, actual2] = test.ConvertDoubleToSingle(input1, input2); if (expected1 != actual1 || expected2 != actual2) { fmt::print("{:016x} -> {:08x} == {:08x},\n", input1, actual1, expected1); fmt::print("{:016x} -> {:08x} == {:08x}\n", input2, actual2, expected2); } EXPECT_EQ(expected1, actual1); EXPECT_EQ(expected2, actual2); } } } TEST(JitArm64, ConvertSingleToDouble) { TestConversion test; const std::vector input_values{ // Special values 0x0000'0000, // positive zero 0x0000'0001, // smallest positive denormal 0x0000'1000, 0x007F'FFFF, // largest positive denormal 0x0080'0000, // smallest positive normal 0x0080'0002, 0x3F80'0000, // 1.0 0x7F7F'FFFF, // largest positive normal 0x7F80'0000, // positive infinity 0x7F80'0001, // first positive SNaN 0x7FBF'FFFF, // last positive SNaN 0x7FC0'0000, // first positive QNaN 0x7FFF'FFFF, // last positive QNaN 0x8000'0000, // negative zero 0x8000'0001, // smallest negative denormal 0x8000'1000, 0x807F'FFFF, // largest negative denormal 0x8080'0000, // smallest negative normal 0x8080'0002, 0xBFF0'0000, // -1.0 0xFF7F'FFFF, // largest negative normal 0xFF80'0000, // negative infinity 0xFF80'0001, // first negative SNaN 0xFFBF'FFFF, // last negative SNaN 0xFFC0'0000, // first negative QNaN 0xFFFF'FFFF, // last negative QNaN // Some typical numbers 0x3FC0'0000, // 1.5 0x447A'0000, // 1000 0xC040'0000, // -3 }; for (const u32 input : input_values) { const u64 expected = ConvertToDouble(input); const u64 actual = test.ConvertSingleToDouble(input); if (expected != actual) fmt::print("{:08x} -> {:016x} == {:016x}\n", input, actual, expected); EXPECT_EQ(expected, actual); } for (const u32 input1 : input_values) { for (const u32 input2 : input_values) { const u64 expected1 = ConvertToDouble(input1); const u64 expected2 = ConvertToDouble(input2); const auto [actual1, actual2] = test.ConvertSingleToDouble(input1, input2); if (expected1 != actual1 || expected2 != actual2) { fmt::print("{:08x} -> {:016x} == {:016x},\n", input1, actual1, expected1); fmt::print("{:08x} -> {:016x} == {:016x}\n", input2, actual2, expected2); } EXPECT_EQ(expected1, actual1); EXPECT_EQ(expected2, actual2); } } }