From 9d6263f306848ba960c13627e6a54e3c1eebe7b8 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 13 Feb 2021 12:38:20 +0100 Subject: [PATCH] JitArm64: Add unit tests for single/double conversion --- Source/Core/Core/PowerPC/JitArm64/Jit.h | 2 +- Source/UnitTests/Core/CMakeLists.txt | 1 + .../PowerPC/JitArm64/ConvertSingleDouble.cpp | 273 ++++++++++++++++++ Source/UnitTests/UnitTests.vcxproj | 1 + 4 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 Source/UnitTests/Core/PowerPC/JitArm64/ConvertSingleDouble.cpp diff --git a/Source/Core/Core/PowerPC/JitArm64/Jit.h b/Source/Core/Core/PowerPC/JitArm64/Jit.h index f8b4b5f146..815e808847 100644 --- a/Source/Core/Core/PowerPC/JitArm64/Jit.h +++ b/Source/Core/Core/PowerPC/JitArm64/Jit.h @@ -163,7 +163,7 @@ public: Arm64Gen::ARM64Reg src_reg, Arm64Gen::ARM64Reg scratch_reg = Arm64Gen::ARM64Reg::INVALID_REG); -private: +protected: struct SlowmemHandler { Arm64Gen::ARM64Reg dest_reg; diff --git a/Source/UnitTests/Core/CMakeLists.txt b/Source/UnitTests/Core/CMakeLists.txt index 01bfd99fbe..1ea9bee6fb 100644 --- a/Source/UnitTests/Core/CMakeLists.txt +++ b/Source/UnitTests/Core/CMakeLists.txt @@ -21,6 +21,7 @@ if(_M_X86) ) elseif(_M_ARM_64) add_dolphin_test(PowerPCTest + PowerPC/JitArm64/ConvertSingleDouble.cpp PowerPC/JitArm64/MovI2R.cpp ) endif() diff --git a/Source/UnitTests/Core/PowerPC/JitArm64/ConvertSingleDouble.cpp b/Source/UnitTests/Core/PowerPC/JitArm64/ConvertSingleDouble.cpp new file mode 100644 index 0000000000..0c81ab1e8b --- /dev/null +++ b/Source/UnitTests/Core/PowerPC/JitArm64/ConvertSingleDouble.cpp @@ -0,0 +1,273 @@ +// 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); + } + } +} diff --git a/Source/UnitTests/UnitTests.vcxproj b/Source/UnitTests/UnitTests.vcxproj index a178911b22..697625be2a 100644 --- a/Source/UnitTests/UnitTests.vcxproj +++ b/Source/UnitTests/UnitTests.vcxproj @@ -81,6 +81,7 @@ +