mirror of https://github.com/PCSX2/pcsx2.git
217 lines
5.4 KiB
C++
217 lines
5.4 KiB
C++
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
|
// SPDX-License-Identifier: GPL-3.0+
|
|
|
|
// This file abstracts the floating-point control registers, known as MXCSR on x86, and FPCR on AArch64.
|
|
|
|
#pragma once
|
|
|
|
#include "common/Pcsx2Defs.h"
|
|
#include "common/VectorIntrin.h"
|
|
|
|
enum class FPRoundMode : u8
|
|
{
|
|
Nearest,
|
|
NegativeInfinity,
|
|
PositiveInfinity,
|
|
ChopZero,
|
|
|
|
MaxCount
|
|
};
|
|
|
|
struct FPControlRegister
|
|
{
|
|
#ifdef _M_X86
|
|
u32 bitmask;
|
|
|
|
static constexpr u32 EXCEPTION_MASK = (0x3Fu << 7);
|
|
static constexpr u32 ROUNDING_CONTROL_SHIFT = 13;
|
|
static constexpr u32 ROUNDING_CONTROL_MASK = 3u;
|
|
static constexpr u32 ROUNDING_CONTROL_BITS = (ROUNDING_CONTROL_MASK << ROUNDING_CONTROL_SHIFT);
|
|
static constexpr u32 DENORMALS_ARE_ZERO_BIT = (1u << 6);
|
|
static constexpr u32 FLUSH_TO_ZERO_BIT = (1u << 15);
|
|
|
|
__fi static FPControlRegister GetCurrent()
|
|
{
|
|
return FPControlRegister{_mm_getcsr()};
|
|
}
|
|
|
|
__fi static void SetCurrent(FPControlRegister value)
|
|
{
|
|
_mm_setcsr(value.bitmask);
|
|
}
|
|
|
|
__fi static constexpr FPControlRegister GetDefault()
|
|
{
|
|
// 0x1f80 - all exceptions masked, nearest rounding
|
|
return FPControlRegister{0x1f80};
|
|
}
|
|
|
|
__fi constexpr FPControlRegister& EnableExceptions()
|
|
{
|
|
bitmask &= ~EXCEPTION_MASK;
|
|
return *this;
|
|
}
|
|
|
|
__fi constexpr FPControlRegister DisableExceptions()
|
|
{
|
|
bitmask |= EXCEPTION_MASK;
|
|
return *this;
|
|
}
|
|
|
|
__fi constexpr FPRoundMode GetRoundMode() const
|
|
{
|
|
return static_cast<FPRoundMode>((bitmask >> ROUNDING_CONTROL_SHIFT) & ROUNDING_CONTROL_MASK);
|
|
}
|
|
|
|
__fi constexpr FPControlRegister& SetRoundMode(FPRoundMode mode)
|
|
{
|
|
// These bits match on x86.
|
|
bitmask = (bitmask & ~ROUNDING_CONTROL_BITS) | ((static_cast<u32>(mode) & ROUNDING_CONTROL_MASK) << ROUNDING_CONTROL_SHIFT);
|
|
return *this;
|
|
}
|
|
|
|
__fi constexpr bool GetDenormalsAreZero() const
|
|
{
|
|
return ((bitmask & DENORMALS_ARE_ZERO_BIT) != 0);
|
|
}
|
|
|
|
__fi constexpr FPControlRegister SetDenormalsAreZero(bool daz)
|
|
{
|
|
if (daz)
|
|
bitmask |= DENORMALS_ARE_ZERO_BIT;
|
|
else
|
|
bitmask &= ~DENORMALS_ARE_ZERO_BIT;
|
|
return *this;
|
|
}
|
|
|
|
__fi constexpr bool GetFlushToZero() const
|
|
{
|
|
return ((bitmask & FLUSH_TO_ZERO_BIT) != 0);
|
|
}
|
|
|
|
__fi constexpr FPControlRegister SetFlushToZero(bool ftz)
|
|
{
|
|
if (ftz)
|
|
bitmask |= FLUSH_TO_ZERO_BIT;
|
|
else
|
|
bitmask &= ~FLUSH_TO_ZERO_BIT;
|
|
return *this;
|
|
}
|
|
|
|
__fi constexpr bool operator==(const FPControlRegister& rhs) const { return bitmask == rhs.bitmask; }
|
|
__fi constexpr bool operator!=(const FPControlRegister& rhs) const { return bitmask != rhs.bitmask; }
|
|
|
|
#elif defined(_M_ARM64)
|
|
u64 bitmask;
|
|
|
|
static constexpr u64 FZ_BIT = (0x1ULL << 24);
|
|
static constexpr u32 RMODE_SHIFT = 22;
|
|
static constexpr u64 RMODE_MASK = 0x3ULL;
|
|
static constexpr u64 RMODE_BITS = (RMODE_MASK << RMODE_SHIFT);
|
|
static constexpr u32 EXCEPTION_MASK = (0x3Fu << 5);
|
|
|
|
__fi static FPControlRegister GetCurrent()
|
|
{
|
|
u64 value;
|
|
asm volatile("\tmrs %0, FPCR\n"
|
|
: "=r"(value));
|
|
return FPControlRegister{value};
|
|
}
|
|
|
|
__fi static void SetCurrent(FPControlRegister value)
|
|
{
|
|
asm volatile("\tmsr FPCR, %0\n" ::"r"(value.bitmask));
|
|
}
|
|
|
|
__fi static constexpr FPControlRegister GetDefault()
|
|
{
|
|
// 0x0 - all exceptions masked, nearest rounding
|
|
return FPControlRegister{0x0};
|
|
}
|
|
|
|
__fi constexpr FPControlRegister& EnableExceptions()
|
|
{
|
|
bitmask |= EXCEPTION_MASK;
|
|
return *this;
|
|
}
|
|
|
|
__fi constexpr FPControlRegister& DisableExceptions()
|
|
{
|
|
bitmask &= ~EXCEPTION_MASK;
|
|
return *this;
|
|
}
|
|
|
|
__fi constexpr FPRoundMode GetRoundMode() const
|
|
{
|
|
// Negative/Positive infinity rounding is flipped on A64.
|
|
const u64 RMode = (bitmask >> RMODE_SHIFT) & RMODE_MASK;
|
|
return static_cast<FPRoundMode>((RMode == 0b00 || RMode == 0b11) ? RMode : (RMode ^ 0b11));
|
|
}
|
|
|
|
__fi constexpr FPControlRegister& SetRoundMode(FPRoundMode mode)
|
|
{
|
|
const u64 RMode = ((mode == FPRoundMode::Nearest || mode == FPRoundMode::ChopZero) ? static_cast<u64>(mode) : (static_cast<u64>(mode) ^ 0b11));
|
|
bitmask = (bitmask & ~RMODE_BITS) | ((RMode & RMODE_MASK) << RMODE_SHIFT);
|
|
return *this;
|
|
}
|
|
|
|
__fi constexpr bool GetDenormalsAreZero() const
|
|
{
|
|
// Without FEAT_AFP, most ARM chips don't have separate DaZ/FtZ. This includes Apple Silicon, which
|
|
// implements x86-like behavior with a vendor-specific extension that we cannot access from usermode.
|
|
// The FZ bit causes both inputs and outputs to be flushed to zero.
|
|
return ((bitmask & FZ_BIT) != 0);
|
|
}
|
|
|
|
__fi constexpr FPControlRegister SetDenormalsAreZero(bool daz)
|
|
{
|
|
if (daz)
|
|
bitmask |= FZ_BIT;
|
|
else
|
|
bitmask &= ~FZ_BIT;
|
|
return *this;
|
|
}
|
|
|
|
__fi constexpr bool GetFlushToZero() const
|
|
{
|
|
// See note in GetDenormalsAreZero().
|
|
return ((bitmask & FZ_BIT) != 0);
|
|
}
|
|
|
|
__fi constexpr FPControlRegister SetFlushToZero(bool ftz)
|
|
{
|
|
if (ftz)
|
|
bitmask |= FZ_BIT;
|
|
else
|
|
bitmask &= ~FZ_BIT;
|
|
return *this;
|
|
}
|
|
|
|
__fi constexpr bool operator==(const FPControlRegister& rhs) const { return bitmask == rhs.bitmask; }
|
|
__fi constexpr bool operator!=(const FPControlRegister& rhs) const { return bitmask != rhs.bitmask; }
|
|
#else
|
|
#error Unknown architecture.
|
|
#endif
|
|
};
|
|
|
|
/// Helper to back up/restore FPCR.
|
|
class FPControlRegisterBackup
|
|
{
|
|
public:
|
|
__fi FPControlRegisterBackup(FPControlRegister new_value)
|
|
: m_prev_val(FPControlRegister::GetCurrent())
|
|
{
|
|
FPControlRegister::SetCurrent(new_value);
|
|
}
|
|
__fi ~FPControlRegisterBackup()
|
|
{
|
|
FPControlRegister::SetCurrent(m_prev_val);
|
|
}
|
|
|
|
FPControlRegisterBackup(const FPControlRegisterBackup&) = delete;
|
|
FPControlRegisterBackup& operator=(const FPControlRegisterBackup&) = delete;
|
|
|
|
private:
|
|
FPControlRegister m_prev_val;
|
|
};
|