Skip to content

Commit

Permalink
JitArm64: Use accurate single/double conversions
Browse files Browse the repository at this point in the history
Our old conversion approach became a lot more inaccurate when
enabling flush-to-zero, to the point of obviously breaking games.
  • Loading branch information
JosJuice committed Jan 23, 2021
1 parent 19d549c commit 843c0fd
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 7 deletions.
2 changes: 1 addition & 1 deletion Source/Core/Core/PowerPC/JitArm64/Jit.h
Expand Up @@ -245,7 +245,7 @@ class JitArm64 : public JitBase, public Arm64Gen::ARM64CodeBlock, public CommonA
std::map<const u8*, FastmemArea> m_fault_to_handler;
std::map<SlowmemHandler, const u8*> m_handler_to_loc;
Arm64GPRCache gpr;
Arm64FPRCache fpr;
Arm64FPRCache fpr{gpr};

JitArm64BlockCache blocks{*this};

Expand Down
58 changes: 53 additions & 5 deletions Source/Core/Core/PowerPC/JitArm64/JitArm64_RegCache.cpp
Expand Up @@ -13,6 +13,7 @@
#include "Common/BitSet.h"
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
#include "Core/PowerPC/Interpreter/Interpreter_FPUtils.h"
#include "Core/PowerPC/JitArm64/Jit.h"

using namespace Arm64Gen;
Expand Down Expand Up @@ -381,7 +382,7 @@ void Arm64GPRCache::FlushByHost(ARM64Reg host_reg)
// FPR Cache
constexpr size_t GUEST_FPR_COUNT = 32;

Arm64FPRCache::Arm64FPRCache() : Arm64RegCache(GUEST_FPR_COUNT)
Arm64FPRCache::Arm64FPRCache(const Arm64GPRCache& gpr) : Arm64RegCache(GUEST_FPR_COUNT), m_gpr(gpr)
{
}

Expand Down Expand Up @@ -756,22 +757,69 @@ void Arm64FPRCache::FixSinglePrecision(size_t preg)
}
}

// 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 use FCVT/FCVTL/FCVTN.

// Another problem is that officially, converting doubles to single format results in undefined
// behavior. Relying on undefined behavior is a bug so no software should ever do this.
// Super Mario 64 (on Wii VC) accidentally relies on this behavior. See issue #11173

// 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 Arm64FPRCache::ConvertDoubleToSingleLower(ARM64Reg dest_reg, ARM64Reg src_reg)
{
m_float_emit->FCVT(32, 64, EncodeRegToDouble(dest_reg), EncodeRegToDouble(src_reg));
const BitSet32 gpr_saved = m_gpr.GetCallerSavedUsed();
m_emit->ABI_PushRegisters(gpr_saved);

m_float_emit->UMOV(64, X0, src_reg, 0);
m_emit->QuickCallFunction(X1, &ConvertToSingle);
m_float_emit->INS(32, dest_reg, 0, W0);

m_emit->ABI_PopRegisters(gpr_saved);
}

void Arm64FPRCache::ConvertDoubleToSinglePair(ARM64Reg dest_reg, ARM64Reg src_reg)
{
m_float_emit->FCVTN(32, EncodeRegToDouble(dest_reg), EncodeRegToDouble(src_reg));
const BitSet32 gpr_saved = m_gpr.GetCallerSavedUsed();
m_emit->ABI_PushRegisters(gpr_saved);

m_float_emit->UMOV(64, X0, src_reg, 0);
m_emit->QuickCallFunction(X1, &ConvertToSingle);
m_float_emit->INS(32, dest_reg, 0, W0);

m_float_emit->UMOV(64, X0, src_reg, 1);
m_emit->QuickCallFunction(X1, &ConvertToSingle);
m_float_emit->INS(32, dest_reg, 1, W0);

m_emit->ABI_PopRegisters(gpr_saved);
}

void Arm64FPRCache::ConvertSingleToDoubleLower(ARM64Reg dest_reg, ARM64Reg src_reg)
{
m_float_emit->FCVT(64, 32, EncodeRegToDouble(dest_reg), EncodeRegToDouble(src_reg));
const BitSet32 gpr_saved = m_gpr.GetCallerSavedUsed();
m_emit->ABI_PushRegisters(gpr_saved);

m_float_emit->UMOV(32, W0, src_reg, 0);
m_emit->QuickCallFunction(X1, &ConvertToDouble);
m_float_emit->INS(64, dest_reg, 0, X0);

m_emit->ABI_PopRegisters(gpr_saved);
}

void Arm64FPRCache::ConvertSingleToDoublePair(ARM64Reg dest_reg, ARM64Reg src_reg)
{
m_float_emit->FCVTL(64, EncodeRegToDouble(dest_reg), EncodeRegToDouble(src_reg));
const BitSet32 gpr_saved = m_gpr.GetCallerSavedUsed();
m_emit->ABI_PushRegisters(gpr_saved);

m_float_emit->UMOV(32, W0, src_reg, 1);
m_emit->QuickCallFunction(X1, &ConvertToDouble);
m_float_emit->INS(64, dest_reg, 1, X0);

m_float_emit->UMOV(32, W0, src_reg, 0);
m_emit->QuickCallFunction(X1, &ConvertToDouble);
m_float_emit->INS(64, dest_reg, 0, X0);

m_emit->ABI_PopRegisters(gpr_saved);
}
4 changes: 3 additions & 1 deletion Source/Core/Core/PowerPC/JitArm64/JitArm64_RegCache.h
Expand Up @@ -274,7 +274,7 @@ class Arm64GPRCache : public Arm64RegCache
class Arm64FPRCache : public Arm64RegCache
{
public:
Arm64FPRCache();
Arm64FPRCache(const Arm64GPRCache& gpr);

// Flushes the register cache in different ways depending on the mode
void Flush(FlushMode mode, PPCAnalyst::CodeOp* op = nullptr) override;
Expand Down Expand Up @@ -311,4 +311,6 @@ class Arm64FPRCache : public Arm64RegCache
bool IsCalleeSaved(Arm64Gen::ARM64Reg reg) const;

void FlushRegisters(BitSet32 regs, bool maintain_state);

const Arm64GPRCache& m_gpr;
};

0 comments on commit 843c0fd

Please sign in to comment.