mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-01-24 15:01:16 +01:00
JitArm64: Add unit tests for single/double conversion
This commit is contained in:
parent
2a9d88739c
commit
9d6263f306
@ -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;
|
||||
|
@ -21,6 +21,7 @@ if(_M_X86)
|
||||
)
|
||||
elseif(_M_ARM_64)
|
||||
add_dolphin_test(PowerPCTest
|
||||
PowerPC/JitArm64/ConvertSingleDouble.cpp
|
||||
PowerPC/JitArm64/MovI2R.cpp
|
||||
)
|
||||
endif()
|
||||
|
273
Source/UnitTests/Core/PowerPC/JitArm64/ConvertSingleDouble.cpp
Normal file
273
Source/UnitTests/Core/PowerPC/JitArm64/ConvertSingleDouble.cpp
Normal file
@ -0,0 +1,273 @@
|
||||
// Copyright 2021 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#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 <fmt/format.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace Arm64Gen;
|
||||
|
||||
// The ABI situation for returning an std::tuple seems annoying. Let's use this struct instead
|
||||
template <typename T>
|
||||
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<u64 (*)(u32)>(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<Pair<u64> (*)(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<u32 (*)(u64)>(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<Pair<u32> (*)(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<u64> 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<u32> ConvertDoubleToSingle(u64 value1, u64 value2)
|
||||
{
|
||||
return convert_double_to_single_pair(value1, value2);
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<u64(u32)> convert_single_to_double_lower;
|
||||
std::function<Pair<u64>(u32, u32)> convert_single_to_double_pair;
|
||||
std::function<u32(u64)> convert_double_to_single_lower;
|
||||
std::function<Pair<u32>(u64, u64)> convert_double_to_single_pair;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(JitArm64, ConvertDoubleToSingle)
|
||||
{
|
||||
TestConversion test;
|
||||
|
||||
const std::vector<u64> 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<u32> 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -81,6 +81,7 @@
|
||||
<ClCompile Include="Core\PowerPC\Jit64Common\Frsqrte.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(Platform)'=='ARM64'">
|
||||
<ClCompile Include="Core\PowerPC\JitArm64\ConvertSingleDouble.cpp" />
|
||||
<ClCompile Include="Core\PowerPC\JitArm64\MovI2R.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
Loading…
x
Reference in New Issue
Block a user