JitArm64: Use accurate single/double conversions

Our old conversion approach became a lot more inaccurate when
enabling flush-to-zero, to the point of obviously breaking games.
This commit is contained in:
JosJuice 2021-01-21 22:02:53 +01:00
parent 39eccf6603
commit 6e0a5876ef
1 changed files with 56 additions and 4 deletions

View File

@ -9,6 +9,7 @@
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
#include "Core/Core.h" #include "Core/Core.h"
#include "Core/CoreTiming.h" #include "Core/CoreTiming.h"
#include "Core/PowerPC/Interpreter/Interpreter_FPUtils.h"
#include "Core/PowerPC/JitArm64/Jit.h" #include "Core/PowerPC/JitArm64/Jit.h"
#include "Core/PowerPC/JitArm64/JitArm64_RegCache.h" #include "Core/PowerPC/JitArm64/JitArm64_RegCache.h"
#include "Core/PowerPC/PPCTables.h" #include "Core/PowerPC/PPCTables.h"
@ -385,22 +386,73 @@ void JitArm64::fctiwzx(UGeckoInstruction inst)
"Register allocation turned singles into doubles in the middle of fctiwzx"); "Register allocation turned singles into doubles in the middle of fctiwzx");
} }
// Since the following float conversion functions are used in non-arithmetic PPC float
// instructions, they must convert floats bitexact and never flush denormals to zero or turn SNaNs
// into QNaNs. This means we can't just use FCVT/FCVTL/FCVTN.
// When calling the conversion functions, we are cheating a little and not
// saving the FPRs since we know the functions happen to not use them.
void JitArm64::ConvertDoubleToSingleLower(ARM64Reg dest_reg, ARM64Reg src_reg) void JitArm64::ConvertDoubleToSingleLower(ARM64Reg dest_reg, ARM64Reg src_reg)
{ {
m_float_emit.FCVT(32, 64, EncodeRegToDouble(dest_reg), EncodeRegToDouble(src_reg)); FlushCarry();
const BitSet32 gpr_saved = gpr.GetCallerSavedUsed();
ABI_PushRegisters(gpr_saved);
m_float_emit.UMOV(64, ARM64Reg::X0, src_reg, 0);
QuickCallFunction(ARM64Reg::X1, &ConvertToSingle);
m_float_emit.INS(32, dest_reg, 0, ARM64Reg::W0);
ABI_PopRegisters(gpr_saved);
} }
void JitArm64::ConvertDoubleToSinglePair(ARM64Reg dest_reg, ARM64Reg src_reg) void JitArm64::ConvertDoubleToSinglePair(ARM64Reg dest_reg, ARM64Reg src_reg)
{ {
m_float_emit.FCVTN(32, EncodeRegToDouble(dest_reg), EncodeRegToDouble(src_reg)); FlushCarry();
const BitSet32 gpr_saved = gpr.GetCallerSavedUsed();
ABI_PushRegisters(gpr_saved);
m_float_emit.UMOV(64, ARM64Reg::X0, src_reg, 0);
QuickCallFunction(ARM64Reg::X1, &ConvertToSingle);
m_float_emit.INS(32, dest_reg, 0, ARM64Reg::W0);
m_float_emit.UMOV(64, ARM64Reg::X0, src_reg, 1);
QuickCallFunction(ARM64Reg::X1, &ConvertToSingle);
m_float_emit.INS(32, dest_reg, 1, ARM64Reg::W0);
ABI_PopRegisters(gpr_saved);
} }
void JitArm64::ConvertSingleToDoubleLower(ARM64Reg dest_reg, ARM64Reg src_reg) void JitArm64::ConvertSingleToDoubleLower(ARM64Reg dest_reg, ARM64Reg src_reg)
{ {
m_float_emit.FCVT(64, 32, EncodeRegToDouble(dest_reg), EncodeRegToDouble(src_reg)); FlushCarry();
const BitSet32 gpr_saved = gpr.GetCallerSavedUsed();
ABI_PushRegisters(gpr_saved);
m_float_emit.UMOV(32, ARM64Reg::W0, src_reg, 0);
QuickCallFunction(ARM64Reg::X1, &ConvertToDouble);
m_float_emit.INS(64, dest_reg, 0, ARM64Reg::X0);
ABI_PopRegisters(gpr_saved);
} }
void JitArm64::ConvertSingleToDoublePair(ARM64Reg dest_reg, ARM64Reg src_reg) void JitArm64::ConvertSingleToDoublePair(ARM64Reg dest_reg, ARM64Reg src_reg)
{ {
m_float_emit.FCVTL(64, EncodeRegToDouble(dest_reg), EncodeRegToDouble(src_reg)); FlushCarry();
const BitSet32 gpr_saved = gpr.GetCallerSavedUsed();
ABI_PushRegisters(gpr_saved);
m_float_emit.UMOV(32, ARM64Reg::W0, src_reg, 1);
QuickCallFunction(ARM64Reg::X1, &ConvertToDouble);
m_float_emit.INS(64, dest_reg, 1, ARM64Reg::X0);
m_float_emit.UMOV(32, ARM64Reg::W0, src_reg, 0);
QuickCallFunction(ARM64Reg::X1, &ConvertToDouble);
m_float_emit.INS(64, dest_reg, 0, ARM64Reg::X0);
ABI_PopRegisters(gpr_saved);
} }