Files
ARMSX2/pcsx2/x86/iFPU.cpp
T
Brian DegenhardtandClaude 679c230841 ee/fpu: make add/sub guard-bit emulation a toggleable option (default on)
Reintroduces the fpuGuardedAddSub Recompiler option removed in 37bc5e164,
but with the default flipped to ON so the PS2-accurate behavior is what
every game gets unless a title is explicitly opted out. The removal's
concern was per-game GameDB maintenance; a global default-on knob
sidesteps that entirely — no GameDB plumbing is restored, so games can
be flagged later once individually confirmed.

Both JITs gate the single-precision add/sub masking on CHECK_FPU_GUARDED
again (arm64 fpuEmitGuardedAddSub early-out; x86 FPU_ADD/FPU_SUB), which
also restores the eerunner --set fpuGuardedAddSub twindiff A/B knob. The
toggle only affects the fast path: Full clamp mode runs the DOUBLE path,
which guards unconditionally and ignores this bit. It composes
orthogonally with Extra clamp mode's operand clamping (that stays on the
fast path and is honored independently).

Exposed as a checkbox in both the Qt Advanced settings and the
FullscreenUI (handheld) CPU page, with tooltips noting the Full-mode
no-op. Test-side, EeRecTestHarness gains DisableFpuGuarded() and the
guard-bit suite pins the opt-out path
(EeRecFpuGuardBit.DisableEmitsPlainOpMatchingInterp). recompiler_tests:
1405/1405 green.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-20 16:05:59 -07:00

1921 lines
56 KiB
C++

// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "Common.h"
#include "R5900OpcodeTables.h"
#include "iR5900.h"
#include "iFPU.h"
using namespace x86Emitter;
alignas(16) const u32 g_minvals[4] = {0xff7fffff, 0xff7fffff, 0xff7fffff, 0xff7fffff};
alignas(16) const u32 g_maxvals[4] = {0x7f7fffff, 0x7f7fffff, 0x7f7fffff, 0x7f7fffff};
//------------------------------------------------------------------
namespace R5900 {
namespace Dynarec {
namespace OpcodeImpl {
namespace COP1 {
namespace DOUBLE
{
void recABS_S_xmm(int info);
void recADD_S_xmm(int info);
void recADDA_S_xmm(int info);
void recC_EQ_xmm(int info);
void recC_LE_xmm(int info);
void recC_LT_xmm(int info);
void recDIV_S_xmm(int info);
void recMADD_S_xmm(int info);
void recMADDA_S_xmm(int info);
void recMAX_S_xmm(int info);
void recMIN_S_xmm(int info);
void recMOV_S_xmm(int info);
void recMSUB_S_xmm(int info);
void recMSUBA_S_xmm(int info);
void recMUL_S_xmm(int info);
void recMULA_S_xmm(int info);
void recNEG_S_xmm(int info);
void recSUB_S_xmm(int info);
void recSUBA_S_xmm(int info);
void recSQRT_S_xmm(int info);
void recRSQRT_S_xmm(int info);
}; // namespace DOUBLE
//------------------------------------------------------------------
// Helper Macros
//------------------------------------------------------------------
#define _Ft_ _Rt_
#define _Fs_ _Rd_
#define _Fd_ _Sa_
// FCR31 Flags
#define FPUflagC 0x00800000
#define FPUflagI 0x00020000
#define FPUflagD 0x00010000
#define FPUflagO 0x00008000
#define FPUflagU 0x00004000
#define FPUflagSI 0x00000040
#define FPUflagSD 0x00000020
#define FPUflagSO 0x00000010
#define FPUflagSU 0x00000008
// Add/Sub guard-bit emulation (matching the PS2's missing mantissa guard bits)
// is gated at runtime on CHECK_FPU_GUARDED (the fpuGuardedAddSub Recompiler
// option, ON by default) so both JITs honor the same setting. The Full-mode
// DOUBLE path keeps its own unconditional guard (iFPUd.cpp).
alignas(16) static const u32 s_neg[4] = {0x80000000, 0xffffffff, 0xffffffff, 0xffffffff};
alignas(16) static const u32 s_pos[4] = {0x7fffffff, 0xffffffff, 0xffffffff, 0xffffffff};
#define REC_FPUBRANCH(f) \
void f(); \
void rec##f() \
{ \
iFlushCall(FLUSH_INTERPRETER); \
xFastCall((void*)(uptr)R5900::Interpreter::OpcodeImpl::COP1::f); \
g_branch = 2; \
}
#define REC_FPUFUNC(f) \
void f(); \
void rec##f() \
{ \
iFlushCall(FLUSH_INTERPRETER); \
xFastCall((void*)(uptr)R5900::Interpreter::OpcodeImpl::COP1::f); \
}
//------------------------------------------------------------------
//------------------------------------------------------------------
// *FPU Opcodes!*
//------------------------------------------------------------------
// Those opcode are marked as special ! But I don't understand why we can't run them in the interpreter
#ifndef FPU_RECOMPILE
REC_FPUFUNC(CFC1);
REC_FPUFUNC(CTC1);
REC_FPUFUNC(MFC1);
REC_FPUFUNC(MTC1);
#else
//------------------------------------------------------------------
// CFC1 / CTC1
//------------------------------------------------------------------
void recCFC1(void)
{
if (!_Rt_)
return;
EE::Profiler.EmitOp(eeOpcode::CFC1);
const int regt = _allocX86reg(X86TYPE_GPR, _Rt_, MODE_WRITE);
if (_Fs_ >= 16)
{
xMOV(xRegister32(regt), ptr32[&fpuRegs.fprc[31]]);
xAND(xRegister32(regt), 0x0083c078); //remove always-zero bits
xOR(xRegister32(regt), 0x01000001); //set always-one bits
xMOVSX(xRegister64(regt), xRegister32(regt));
}
else
{
xMOVSX(xRegister64(regt), ptr32[&fpuRegs.fprc[0]]);
}
}
void recCTC1()
{
if (_Fs_ != 31)
return;
EE::Profiler.EmitOp(eeOpcode::CTC1);
if (GPR_IS_CONST1(_Rt_))
{
xMOV(ptr32[&fpuRegs.fprc[_Fs_]], g_cpuConstRegs[_Rt_].UL[0]);
}
else
{
int mmreg = _checkXMMreg(XMMTYPE_GPRREG, _Rt_, MODE_READ);
if (mmreg >= 0)
{
xMOVSS(ptr[&fpuRegs.fprc[_Fs_]], xRegisterSSE(mmreg));
}
else if ((mmreg = _checkX86reg(X86TYPE_GPR, _Rt_, MODE_READ)) >= 0)
{
xMOV(ptr32[&fpuRegs.fprc[_Fs_]], xRegister32(mmreg));
}
else
{
_deleteGPRtoXMMreg(_Rt_, 1);
xMOV(eax, ptr[&cpuRegs.GPR.r[_Rt_].UL[0]]);
xMOV(ptr[&fpuRegs.fprc[_Fs_]], eax);
}
}
}
//------------------------------------------------------------------
//------------------------------------------------------------------
// MFC1
//------------------------------------------------------------------
void recMFC1()
{
if (!_Rt_)
return;
EE::Profiler.EmitOp(eeOpcode::MFC1);
const int xmmregt = _allocIfUsedGPRtoXMM(_Rt_, MODE_READ | MODE_WRITE);
const int regs = _allocIfUsedFPUtoXMM(_Fs_, MODE_READ);
if (regs >= 0 && xmmregt >= 0)
{
// if we're in xmm, we shouldn't be const
pxAssert(!GPR_IS_CONST1(_Rt_));
// both in xmm, sign extend and insert lower bits
const int temp = _allocTempXMMreg(XMMT_FPS);
xPSRA.D(xRegisterSSE(temp), xRegisterSSE(regs), 31);
xMOVSS(xRegisterSSE(xmmregt), xRegisterSSE(regs));
xINSERTPS(xRegisterSSE(xmmregt), xRegisterSSE(temp), _MM_MK_INSERTPS_NDX(0, 1, 0));
_freeXMMreg(temp);
return;
}
// storing to a gpr..
const int regt = _allocX86reg(X86TYPE_GPR, _Rt_, MODE_WRITE);
// shouldn't be const after we're writing.
pxAssert(!GPR_IS_CONST1(_Rt_));
if (regs >= 0)
{
// xmm -> gpr
xMOVD(xRegister32(regt), xRegisterSSE(regs));
xMOVSX(xRegister64(regt), xRegister32(regt));
}
else
{
// mem -> gpr
xMOVSX(xRegister64(regt), ptr32[&fpuRegs.fpr[_Fs_].UL]);
}
}
//------------------------------------------------------------------
//------------------------------------------------------------------
// MTC1
//------------------------------------------------------------------
void recMTC1()
{
EE::Profiler.EmitOp(eeOpcode::MTC1);
if (GPR_IS_CONST1(_Rt_))
{
const int xmmreg = _allocIfUsedFPUtoXMM(_Fs_, MODE_WRITE);
if (xmmreg >= 0)
{
// common case: mtc1 zero, fnn
if (g_cpuConstRegs[_Rt_].UL[0] == 0)
{
xPXOR(xRegisterSSE(xmmreg), xRegisterSSE(xmmreg));
}
else
{
// may as well flush the constant register, since we're needing it in a gpr anyway
const int x86reg = _allocX86reg(X86TYPE_GPR, _Rt_, MODE_READ);
xMOVDZX(xRegisterSSE(xmmreg), xRegister32(x86reg));
}
}
else
{
pxAssert(!_hasXMMreg(XMMTYPE_FPREG, _Fs_));
xMOV(ptr32[&fpuRegs.fpr[_Fs_].UL], g_cpuConstRegs[_Rt_].UL[0]);
}
}
else
{
const int xmmgpr = _checkXMMreg(XMMTYPE_GPRREG, _Rt_, MODE_READ);
if (xmmgpr >= 0)
{
if (g_pCurInstInfo->regs[_Rt_] & EEINST_LASTUSE)
{
// transfer the reg directly
_deleteFPtoXMMreg(_Fs_, DELETE_REG_FREE_NO_WRITEBACK);
_reallocateXMMreg(xmmgpr, XMMTYPE_FPREG, _Fs_, MODE_WRITE);
}
else
{
const int xmmreg2 = _allocIfUsedFPUtoXMM(_Fs_, MODE_WRITE);
if (xmmreg2 >= 0)
xMOVSS(xRegisterSSE(xmmreg2), xRegisterSSE(xmmgpr));
else
xMOVSS(ptr[&fpuRegs.fpr[_Fs_].UL], xRegisterSSE(xmmgpr));
}
}
else
{
// may as well cache it..
const int regt = _allocX86reg(X86TYPE_GPR, _Rt_, MODE_READ);
const int mmreg2 = _allocIfUsedFPUtoXMM(_Fs_, MODE_WRITE);
if (mmreg2 >= 0)
{
xMOVDZX(xRegisterSSE(mmreg2), xRegister32(regt));
}
else
{
xMOV(ptr32[&fpuRegs.fpr[_Fs_].UL], xRegister32(regt));
}
}
}
}
#endif
//------------------------------------------------------------------
#ifndef FPU_RECOMPILE // If FPU_RECOMPILE is not defined, then use the interpreter opcodes. (CFC1, CTC1, MFC1, and MTC1 are special because they work specifically with the EE rec so they're defined above)
REC_FPUFUNC(ABS_S);
REC_FPUFUNC(ADD_S);
REC_FPUFUNC(ADDA_S);
REC_FPUBRANCH(BC1F);
REC_FPUBRANCH(BC1T);
REC_FPUBRANCH(BC1FL);
REC_FPUBRANCH(BC1TL);
REC_FPUFUNC(C_EQ);
REC_FPUFUNC(C_F);
REC_FPUFUNC(C_LE);
REC_FPUFUNC(C_LT);
REC_FPUFUNC(CVT_S);
REC_FPUFUNC(CVT_W);
REC_FPUFUNC(DIV_S);
REC_FPUFUNC(MAX_S);
REC_FPUFUNC(MIN_S);
REC_FPUFUNC(MADD_S);
REC_FPUFUNC(MADDA_S);
REC_FPUFUNC(MOV_S);
REC_FPUFUNC(MSUB_S);
REC_FPUFUNC(MSUBA_S);
REC_FPUFUNC(MUL_S);
REC_FPUFUNC(MULA_S);
REC_FPUFUNC(NEG_S);
REC_FPUFUNC(SUB_S);
REC_FPUFUNC(SUBA_S);
REC_FPUFUNC(SQRT_S);
REC_FPUFUNC(RSQRT_S);
#else // FPU_RECOMPILE
//------------------------------------------------------------------
// Clamp Functions (Converts NaN's and Infinities to Normal Numbers)
//------------------------------------------------------------------
static int fpuCopyToTempForClamp(int fpureg, int xmmreg)
{
if (FPUINST_USEDTEST(fpureg))
{
const int tempreg = _allocTempXMMreg(XMMT_FPS);
xMOVSS(xRegisterSSE(tempreg), xRegisterSSE(xmmreg));
return tempreg;
}
// flush back the original value, before we mess with it below
if (FPUINST_LIVETEST(fpureg))
_flushXMMreg(xmmreg);
// turn it into a temp, so in case the liveness was incorrect, we don't reuse it after clamp
_reallocateXMMreg(xmmreg, XMMTYPE_TEMP, 0, 0, true);
return xmmreg;
}
static void fpuFreeIfTemp(int xmmreg)
{
if (xmmregs[xmmreg].inuse && xmmregs[xmmreg].type == XMMTYPE_TEMP)
_freeXMMreg(xmmreg);
}
__fi void fpuFloat3(int regd) // +NaN -> +fMax, -NaN -> -fMax, +Inf -> +fMax, -Inf -> -fMax
{
xPMIN.SD(xRegisterSSE(regd), ptr128[&g_maxvals[0]]);
xPMIN.UD(xRegisterSSE(regd), ptr128[&g_minvals[0]]);
}
__fi void fpuFloat(int regd) // +/-NaN -> +fMax, +Inf -> +fMax, -Inf -> -fMax
{
if (CHECK_FPU_OVERFLOW)
{
xMIN.SS(xRegisterSSE(regd), ptr[&g_maxvals[0]]); // MIN() must be before MAX()! So that NaN's become +Maximum
xMAX.SS(xRegisterSSE(regd), ptr[&g_minvals[0]]);
}
}
__fi void fpuFloat2(int regd) // +NaN -> +fMax, -NaN -> -fMax, +Inf -> +fMax, -Inf -> -fMax
{
if (CHECK_FPU_OVERFLOW)
{
fpuFloat3(regd);
}
}
void ClampValues(int regd)
{
fpuFloat(regd);
}
//------------------------------------------------------------------
//------------------------------------------------------------------
// ABS XMM
//------------------------------------------------------------------
void recABS_S_xmm(int info)
{
EE::Profiler.EmitOp(eeOpcode::ABS_F);
if (info & PROCESS_EE_S)
xMOVSS(xRegisterSSE(EEREC_D), xRegisterSSE(EEREC_S));
else
xMOVSSZX(xRegisterSSE(EEREC_D), ptr[&fpuRegs.fpr[_Fs_]]);
xAND.PS(xRegisterSSE(EEREC_D), ptr[&s_pos[0]]);
//xAND(ptr32[&fpuRegs.fprc[31]], ~(FPUflagO|FPUflagU)); // Clear O and U flags
if (CHECK_FPU_OVERFLOW) // Only need to do positive clamp, since EEREC_D is positive
xMIN.SS(xRegisterSSE(EEREC_D), ptr[&g_maxvals[0]]);
}
FPURECOMPILE_CONSTCODE(ABS_S, XMMINFO_WRITED | XMMINFO_READS);
//------------------------------------------------------------------
//------------------------------------------------------------------
// FPU_ADD_SUB (Used to mimic PS2's FPU add/sub behavior)
//------------------------------------------------------------------
// Compliant IEEE FPU uses, in computations, uses additional "guard" bits to the right of the mantissa
// but EE-FPU doesn't. Substraction (and addition of positive and negative) may shift the mantissa left,
// causing those bits to appear in the result; this function masks out the bits of the mantissa that will
// get shifted right to the guard bits to ensure that the guard bits are empty.
// The difference of the exponents = the amount that the smaller operand will be shifted right by.
// Modification - the PS2 uses a single guard bit? (Coded by Nneeve)
//------------------------------------------------------------------
void FPU_ADD_SUB(int regd, int regt, int issub)
{
const int xmmtemp = _allocTempXMMreg(XMMT_FPS); //temporary for anding with regd/regt
xMOVD(ecx, xRegisterSSE(regd)); // ecx receives regd
xMOVD(eax, xRegisterSSE(regt)); // eax receives regt
//mask the exponents
xSHR(ecx, 23);
xSHR(eax, 23);
xAND(ecx, 0xff);
xAND(eax, 0xff);
xSUB(ecx, eax); //tempecx = exponent difference
xCMP(ecx, 25);
j8Ptr[0] = JGE8(0);
xCMP(ecx, 0);
j8Ptr[1] = JG8(0);
j8Ptr[2] = JE8(0);
xCMP(ecx, -25);
j8Ptr[3] = JLE8(0);
//diff = -24 .. -1 , expd < expt
xNEG(ecx);
xDEC(ecx);
xMOV(eax, 0xffffffff);
xSHL(eax, cl); //temp2 = 0xffffffff << tempecx
xMOVDZX(xRegisterSSE(xmmtemp), eax);
xAND.PS(xRegisterSSE(regd), xRegisterSSE(xmmtemp));
if (issub)
xSUB.SS(xRegisterSSE(regd), xRegisterSSE(regt));
else
xADD.SS(xRegisterSSE(regd), xRegisterSSE(regt));
j8Ptr[4] = JMP8(0);
x86SetJ8(j8Ptr[0]);
//diff = 25 .. 255 , expt < expd
xAND.PS(xRegisterSSE(xmmtemp), xRegisterSSE(regt), ptr[s_neg]);
if (issub)
xSUB.SS(xRegisterSSE(regd), xRegisterSSE(xmmtemp));
else
xADD.SS(xRegisterSSE(regd), xRegisterSSE(xmmtemp));
j8Ptr[5] = JMP8(0);
x86SetJ8(j8Ptr[1]);
//diff = 1 .. 24, expt < expd
xDEC(ecx);
xMOV(eax, 0xffffffff);
xSHL(eax, cl); //temp2 = 0xffffffff << tempecx
xMOVDZX(xRegisterSSE(xmmtemp), eax);
xAND.PS(xRegisterSSE(xmmtemp), xRegisterSSE(regt));
if (issub)
xSUB.SS(xRegisterSSE(regd), xRegisterSSE(xmmtemp));
else
xADD.SS(xRegisterSSE(regd), xRegisterSSE(xmmtemp));
j8Ptr[6] = JMP8(0);
x86SetJ8(j8Ptr[3]);
//diff = -255 .. -25, expd < expt
xAND.PS(xRegisterSSE(regd), ptr[s_neg]);
if (issub)
xSUB.SS(xRegisterSSE(regd), xRegisterSSE(regt));
else
xADD.SS(xRegisterSSE(regd), xRegisterSSE(regt));
j8Ptr[7] = JMP8(0);
x86SetJ8(j8Ptr[2]);
//diff == 0
if (issub)
xSUB.SS(xRegisterSSE(regd), xRegisterSSE(regt));
else
xADD.SS(xRegisterSSE(regd), xRegisterSSE(regt));
x86SetJ8(j8Ptr[4]);
x86SetJ8(j8Ptr[5]);
x86SetJ8(j8Ptr[6]);
x86SetJ8(j8Ptr[7]);
_freeXMMreg(xmmtemp);
}
void FPU_ADD(int regd, int regt)
{
if (CHECK_FPU_GUARDED)
FPU_ADD_SUB(regd, regt, 0);
else
xADD.SS(xRegisterSSE(regd), xRegisterSSE(regt));
}
void FPU_SUB(int regd, int regt)
{
if (CHECK_FPU_GUARDED)
FPU_ADD_SUB(regd, regt, 1);
else
xSUB.SS(xRegisterSSE(regd), xRegisterSSE(regt));
}
//------------------------------------------------------------------
// Note: PS2's multiplication uses some variant of booth multiplication with wallace trees:
// It cuts off some bits, resulting in inaccurate and non-commutative results.
// The PS2's result mantissa is either equal to x86's rounding to zero result mantissa
// or SMALLER (by 0x1). (this means that x86's other rounding modes are only less similar to PS2's mul)
//------------------------------------------------------------------
void FPU_MUL(int regd, int regt, bool reverseOperands)
{
u8 *endMul = nullptr;
if (CHECK_FPUMULHACK)
{
// if ((s == 0x3e800000) && (t == 0x40490fdb))
// return 0x3f490fda; // needed for Tales of Destiny Remake (only in a very specific room late-game)
// else
// return 0;
alignas(16) static constexpr const u32 result[4] = { 0x3f490fda };
xMOVD(ecx, xRegisterSSE(reverseOperands ? regt : regd));
xMOVD(edx, xRegisterSSE(reverseOperands ? regd : regt));
// if (((s ^ 0x3e800000) | (t ^ 0x40490fdb)) != 0) { hack; }
xXOR(ecx, 0x3e800000);
xXOR(edx, 0x40490fdb);
xOR(edx, ecx);
u8* noHack = JNZ8(0);
xMOVAPS(xRegisterSSE(regd), ptr128[result]);
endMul = JMP8(0);
x86SetJ8(noHack);
}
xMUL.SS(xRegisterSSE(regd), xRegisterSSE(regt));
if (CHECK_FPUMULHACK)
x86SetJ8(endMul);
}
void FPU_MUL(int regd, int regt) { FPU_MUL(regd, regt, false); }
void FPU_MUL_REV(int regd, int regt) { FPU_MUL(regd, regt, true); } //reversed operands
//------------------------------------------------------------------
// CommutativeOp XMM (used for ADD, MUL, MAX, and MIN opcodes)
//------------------------------------------------------------------
static void (*recComOpXMM_to_XMM[])(x86SSERegType, x86SSERegType) = {
FPU_ADD, FPU_MUL, SSE_MAXSS_XMM_to_XMM, SSE_MINSS_XMM_to_XMM};
static void (*recComOpXMM_to_XMM_REV[])(x86SSERegType, x86SSERegType) = { //reversed operands
FPU_ADD, FPU_MUL_REV, SSE_MAXSS_XMM_to_XMM, SSE_MINSS_XMM_to_XMM};
//static void (*recComOpM32_to_XMM[] )(x86SSERegType, uptr) = {
// SSE_ADDSS_M32_to_XMM, SSE_MULSS_M32_to_XMM, SSE_MAXSS_M32_to_XMM, SSE_MINSS_M32_to_XMM };
int recCommutativeOp(int info, int regd, int op)
{
int t0reg = _allocTempXMMreg(XMMT_FPS);
switch (info & (PROCESS_EE_S | PROCESS_EE_T))
{
case PROCESS_EE_S:
if (regd == EEREC_S)
{
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Ft_]]);
if (CHECK_FPU_EXTRA_OVERFLOW /*&& !CHECK_FPUCLAMPHACK */ || (op >= 2))
{
fpuFloat2(regd);
fpuFloat2(t0reg);
}
recComOpXMM_to_XMM[op](regd, t0reg);
}
else
{
xMOVSSZX(xRegisterSSE(regd), ptr[&fpuRegs.fpr[_Ft_]]);
if (CHECK_FPU_EXTRA_OVERFLOW || (op >= 2))
{
fpuFloat2(regd);
fpuFloat2(EEREC_S);
}
recComOpXMM_to_XMM_REV[op](regd, EEREC_S);
}
break;
case PROCESS_EE_T:
if (regd == EEREC_T)
{
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Fs_]]);
if (CHECK_FPU_EXTRA_OVERFLOW || (op >= 2))
{
fpuFloat2(regd);
fpuFloat2(t0reg);
}
recComOpXMM_to_XMM_REV[op](regd, t0reg);
}
else
{
xMOVSSZX(xRegisterSSE(regd), ptr[&fpuRegs.fpr[_Fs_]]);
if (CHECK_FPU_EXTRA_OVERFLOW || (op >= 2))
{
fpuFloat2(regd);
fpuFloat2(EEREC_T);
}
recComOpXMM_to_XMM[op](regd, EEREC_T);
}
break;
case (PROCESS_EE_S | PROCESS_EE_T):
if (regd == EEREC_T)
{
if (CHECK_FPU_EXTRA_OVERFLOW || (op >= 2))
{
fpuFloat2(regd);
fpuFloat2(EEREC_S);
}
recComOpXMM_to_XMM_REV[op](regd, EEREC_S);
}
else
{
xMOVSS(xRegisterSSE(regd), xRegisterSSE(EEREC_S));
if (CHECK_FPU_EXTRA_OVERFLOW || (op >= 2))
{
fpuFloat2(regd);
fpuFloat2(EEREC_T);
}
recComOpXMM_to_XMM[op](regd, EEREC_T);
}
break;
default:
Console.WriteLn(Color_Magenta, "FPU: recCommutativeOp case 4");
xMOVSSZX(xRegisterSSE(regd), ptr[&fpuRegs.fpr[_Fs_]]);
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Ft_]]);
if (CHECK_FPU_EXTRA_OVERFLOW || (op >= 2))
{
fpuFloat2(regd);
fpuFloat2(t0reg);
}
recComOpXMM_to_XMM[op](regd, t0reg);
break;
}
_freeXMMreg(t0reg);
return regd;
}
//------------------------------------------------------------------
//------------------------------------------------------------------
// ADD XMM
//------------------------------------------------------------------
void recADD_S_xmm(int info)
{
EE::Profiler.EmitOp(eeOpcode::ADD_F);
//xAND(ptr32[&fpuRegs.fprc[31]], ~(FPUflagO|FPUflagU)); // Clear O and U flags
ClampValues(recCommutativeOp(info, EEREC_D, 0));
//REC_FPUOP(ADD_S);
}
FPURECOMPILE_CONSTCODE(ADD_S, XMMINFO_WRITED | XMMINFO_READS | XMMINFO_READT);
void recADDA_S_xmm(int info)
{
EE::Profiler.EmitOp(eeOpcode::ADDA_F);
//xAND(ptr32[&fpuRegs.fprc[31]], ~(FPUflagO|FPUflagU)); // Clear O and U flags
ClampValues(recCommutativeOp(info, EEREC_ACC, 0));
}
FPURECOMPILE_CONSTCODE(ADDA_S, XMMINFO_WRITEACC | XMMINFO_READS | XMMINFO_READT);
//------------------------------------------------------------------
//------------------------------------------------------------------
// BC1x XMM
//------------------------------------------------------------------
static void _setupBranchTest()
{
_eeFlushAllDirty();
// COP1 branch conditionals are based on the following equation:
// (fpuRegs.fprc[31] & 0x00800000)
// BC2F checks if the statement is false, BC2T checks if the statement is true.
xMOV(eax, ptr[&fpuRegs.fprc[31]]);
xTEST(eax, FPUflagC);
}
void recBC1F()
{
EE::Profiler.EmitOp(eeOpcode::BC1F);
const u32 branchTo = ((s32)_Imm_ * 4) + pc;
const bool swap = TrySwapDelaySlot(0, 0, 0, true);
_setupBranchTest();
recDoBranchImm(branchTo, JNZ32(0), false, swap);
}
void recBC1T()
{
EE::Profiler.EmitOp(eeOpcode::BC1T);
const u32 branchTo = ((s32)_Imm_ * 4) + pc;
const bool swap = TrySwapDelaySlot(0, 0, 0, true);
_setupBranchTest();
recDoBranchImm(branchTo, JZ32(0), false, swap);
}
void recBC1FL()
{
EE::Profiler.EmitOp(eeOpcode::BC1FL);
const u32 branchTo = ((s32)_Imm_ * 4) + pc;
_setupBranchTest();
recDoBranchImm(branchTo, JNZ32(0), true, false);
}
void recBC1TL()
{
EE::Profiler.EmitOp(eeOpcode::BC1TL);
const u32 branchTo = ((s32)_Imm_ * 4) + pc;
_setupBranchTest();
recDoBranchImm(branchTo, JZ32(0), true, false);
}
//------------------------------------------------------------------
//------------------------------------------------------------------
// C.x.S XMM
//------------------------------------------------------------------
void recC_EQ_xmm(int info)
{
EE::Profiler.EmitOp(eeOpcode::CEQ_F);
//Console.WriteLn("recC_EQ_xmm()");
switch (info & (PROCESS_EE_S | PROCESS_EE_T))
{
case PROCESS_EE_S:
{
const int regs = fpuCopyToTempForClamp(_Fs_, EEREC_S);
fpuFloat3(regs);
const int t0reg = _allocTempXMMreg(XMMT_FPS);
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Ft_]]);
fpuFloat3(t0reg);
xUCOMI.SS(xRegisterSSE(regs), xRegisterSSE(t0reg));
_freeXMMreg(t0reg);
fpuFreeIfTemp(regs);
}
break;
case PROCESS_EE_T:
{
const int regt = fpuCopyToTempForClamp(_Ft_, EEREC_T);
fpuFloat3(regt);
const int t0reg = _allocTempXMMreg(XMMT_FPS);
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Fs_]]);
fpuFloat3(t0reg);
xUCOMI.SS(xRegisterSSE(t0reg), xRegisterSSE(regt));
_freeXMMreg(t0reg);
fpuFreeIfTemp(regt);
}
break;
case (PROCESS_EE_S | PROCESS_EE_T):
{
const int regs = fpuCopyToTempForClamp(_Fs_, EEREC_S);
fpuFloat3(regs);
const int regt = fpuCopyToTempForClamp(_Ft_, EEREC_T);
fpuFloat3(regt);
xUCOMI.SS(xRegisterSSE(regs), xRegisterSSE(regt));
fpuFreeIfTemp(regs);
fpuFreeIfTemp(regt);
}
break;
default:
Console.WriteLn(Color_Magenta, "recC_EQ_xmm: Default");
xMOV(eax, ptr[&fpuRegs.fpr[_Fs_]]);
xCMP(eax, ptr[&fpuRegs.fpr[_Ft_]]);
j8Ptr[0] = JZ8(0);
xAND(ptr32[&fpuRegs.fprc[31]], ~FPUflagC);
j8Ptr[1] = JMP8(0);
x86SetJ8(j8Ptr[0]);
xOR(ptr32[&fpuRegs.fprc[31]], FPUflagC);
x86SetJ8(j8Ptr[1]);
return;
}
j8Ptr[0] = JZ8(0);
xAND(ptr32[&fpuRegs.fprc[31]], ~FPUflagC);
j8Ptr[1] = JMP8(0);
x86SetJ8(j8Ptr[0]);
xOR(ptr32[&fpuRegs.fprc[31]], FPUflagC);
x86SetJ8(j8Ptr[1]);
}
FPURECOMPILE_CONSTCODE(C_EQ, XMMINFO_READS | XMMINFO_READT);
//REC_FPUFUNC(C_EQ);
void recC_F()
{
EE::Profiler.EmitOp(eeOpcode::CF_F);
xAND(ptr32[&fpuRegs.fprc[31]], ~FPUflagC);
}
//REC_FPUFUNC(C_F);
void recC_LE_xmm(int info)
{
EE::Profiler.EmitOp(eeOpcode::CLE_F);
//Console.WriteLn("recC_LE_xmm()");
switch (info & (PROCESS_EE_S | PROCESS_EE_T))
{
case PROCESS_EE_S:
{
const int regs = fpuCopyToTempForClamp(_Fs_, EEREC_S);
fpuFloat3(regs);
const int t0reg = _allocTempXMMreg(XMMT_FPS);
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Ft_]]);
fpuFloat3(t0reg);
xUCOMI.SS(xRegisterSSE(regs), xRegisterSSE(t0reg));
_freeXMMreg(t0reg);
fpuFreeIfTemp(regs);
}
break;
case PROCESS_EE_T:
{
const int regt = fpuCopyToTempForClamp(_Ft_, EEREC_T);
fpuFloat3(regt);
const int t0reg = _allocTempXMMreg(XMMT_FPS);
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Fs_]]);
fpuFloat3(t0reg);
xUCOMI.SS(xRegisterSSE(t0reg), xRegisterSSE(regt));
_freeXMMreg(t0reg);
fpuFreeIfTemp(regt);
}
break;
case (PROCESS_EE_S | PROCESS_EE_T):
{
const int regs = fpuCopyToTempForClamp(_Fs_, EEREC_S);
fpuFloat3(regs);
const int regt = fpuCopyToTempForClamp(_Ft_, EEREC_T);
fpuFloat3(regt);
xUCOMI.SS(xRegisterSSE(regs), xRegisterSSE(regt));
fpuFreeIfTemp(regs);
fpuFreeIfTemp(regt);
}
break;
default: // Untested and incorrect, but this case is never reached AFAIK (cottonvibes)
Console.WriteLn(Color_Magenta, "recC_LE_xmm: Default");
xMOV(eax, ptr[&fpuRegs.fpr[_Fs_]]);
xCMP(eax, ptr[&fpuRegs.fpr[_Ft_]]);
j8Ptr[0] = JLE8(0);
xAND(ptr32[&fpuRegs.fprc[31]], ~FPUflagC);
j8Ptr[1] = JMP8(0);
x86SetJ8(j8Ptr[0]);
xOR(ptr32[&fpuRegs.fprc[31]], FPUflagC);
x86SetJ8(j8Ptr[1]);
return;
}
j8Ptr[0] = JBE8(0);
xAND(ptr32[&fpuRegs.fprc[31]], ~FPUflagC);
j8Ptr[1] = JMP8(0);
x86SetJ8(j8Ptr[0]);
xOR(ptr32[&fpuRegs.fprc[31]], FPUflagC);
x86SetJ8(j8Ptr[1]);
}
FPURECOMPILE_CONSTCODE(C_LE, XMMINFO_READS | XMMINFO_READT);
//REC_FPUFUNC(C_LE);
void recC_LT_xmm(int info)
{
EE::Profiler.EmitOp(eeOpcode::CLT_F);
//Console.WriteLn("recC_LT_xmm()");
switch (info & (PROCESS_EE_S | PROCESS_EE_T))
{
case PROCESS_EE_S:
{
const int regs = fpuCopyToTempForClamp(_Fs_, EEREC_S);
fpuFloat3(regs);
const int t0reg = _allocTempXMMreg(XMMT_FPS);
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Ft_]]);
fpuFloat3(t0reg);
xUCOMI.SS(xRegisterSSE(regs), xRegisterSSE(t0reg));
_freeXMMreg(t0reg);
fpuFreeIfTemp(regs);
}
break;
case PROCESS_EE_T:
{
const int regt = fpuCopyToTempForClamp(_Ft_, EEREC_T);
fpuFloat3(regt);
const int t0reg = _allocTempXMMreg(XMMT_FPS);
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Fs_]]);
fpuFloat3(t0reg);
xUCOMI.SS(xRegisterSSE(t0reg), xRegisterSSE(regt));
_freeXMMreg(t0reg);
fpuFreeIfTemp(regt);
}
break;
case (PROCESS_EE_S | PROCESS_EE_T):
{
const int regs = fpuCopyToTempForClamp(_Fs_, EEREC_S);
fpuFloat3(regs);
const int regt = fpuCopyToTempForClamp(_Ft_, EEREC_T);
fpuFloat3(regt);
xUCOMI.SS(xRegisterSSE(regs), xRegisterSSE(regt));
fpuFreeIfTemp(regs);
fpuFreeIfTemp(regt);
}
break;
default:
Console.WriteLn(Color_Magenta, "recC_LT_xmm: Default");
xMOV(eax, ptr[&fpuRegs.fpr[_Fs_]]);
xCMP(eax, ptr[&fpuRegs.fpr[_Ft_]]);
j8Ptr[0] = JL8(0);
xAND(ptr32[&fpuRegs.fprc[31]], ~FPUflagC);
j8Ptr[1] = JMP8(0);
x86SetJ8(j8Ptr[0]);
xOR(ptr32[&fpuRegs.fprc[31]], FPUflagC);
x86SetJ8(j8Ptr[1]);
return;
}
j8Ptr[0] = JB8(0);
xAND(ptr32[&fpuRegs.fprc[31]], ~FPUflagC);
j8Ptr[1] = JMP8(0);
x86SetJ8(j8Ptr[0]);
xOR(ptr32[&fpuRegs.fprc[31]], FPUflagC);
x86SetJ8(j8Ptr[1]);
}
FPURECOMPILE_CONSTCODE(C_LT, XMMINFO_READS | XMMINFO_READT);
//REC_FPUFUNC(C_LT);
//------------------------------------------------------------------
//------------------------------------------------------------------
// CVT.x XMM
//------------------------------------------------------------------
void recCVT_S_xmm(int info)
{
EE::Profiler.EmitOp(eeOpcode::CVTS_F);
if (info & PROCESS_EE_D)
{
if (info & PROCESS_EE_S)
xCVTDQ2PS(xRegisterSSE(EEREC_D), xRegisterSSE(EEREC_S));
else
xCVTSI2SS(xRegisterSSE(EEREC_D), ptr32[&fpuRegs.fpr[_Fs_]]);
}
else
{
const int temp = _allocTempXMMreg(XMMT_FPS);
xCVTSI2SS(xRegisterSSE(temp), ptr32[&fpuRegs.fpr[_Fs_]]);
xMOVSS(ptr32[&fpuRegs.fpr[_Fd_]], xRegisterSSE(temp));
_freeXMMreg(temp);
}
}
void recCVT_S()
{
// Float version is fully accurate, no double version
eeFPURecompileCode(recCVT_S_xmm, R5900::Interpreter::OpcodeImpl::COP1::CVT_S, XMMINFO_WRITED | XMMINFO_READS);
}
void recCVT_W()
{
// Float version is fully accurate, no double version
// If we have the following EmitOP() on the top then it'll get calculated twice when CHECK_FPU_FULL is true
// as we also have an EmitOP() at recCVT_W() on iFPUd.cpp. hence we have it below the possible return.
EE::Profiler.EmitOp(eeOpcode::CVTW);
int regs = _checkXMMreg(XMMTYPE_FPREG, _Fs_, MODE_READ);
if (regs >= 0)
{
xCVTTSS2SI(eax, xRegisterSSE(regs));
xMOVD(edx, xRegisterSSE(regs));
}
else
{
xCVTTSS2SI(eax, ptr32[&fpuRegs.fpr[_Fs_]]);
xMOV(edx, ptr[&fpuRegs.fpr[_Fs_]]);
}
//kill register allocation for dst because we write directly to fpuRegs.fpr[_Fd_]
_deleteFPtoXMMreg(_Fd_, DELETE_REG_FREE_NO_WRITEBACK);
// cvttss2si converts unrepresentable values to 0x80000000, so negative values are already handled.
// So we just need to handle positive values.
xCMP(edx, 0x4f000000); // If the input is greater than INT_MAX
xMOV(edx, 0x7fffffff);
xCMOVGE(eax, edx); // Saturate it
//Write the result
xMOV(ptr[&fpuRegs.fpr[_Fd_]], eax);
}
//------------------------------------------------------------------
//------------------------------------------------------------------
// DIV XMM
//------------------------------------------------------------------
void recDIVhelper1(int regd, int regt) // Sets flags
{
u8 *pjmp1, *pjmp2;
u32 *ajmp32, *bjmp32;
const int t1reg = _allocTempXMMreg(XMMT_FPS);
xAND(ptr32[&fpuRegs.fprc[31]], ~(FPUflagI | FPUflagD)); // Clear I and D flags
/*--- Check for divide by zero ---*/
xXOR.PS(xRegisterSSE(t1reg), xRegisterSSE(t1reg));
xCMPEQ.SS(xRegisterSSE(t1reg), xRegisterSSE(regt));
xMOVMSKPS(eax, xRegisterSSE(t1reg));
xAND(eax, 1); //Check sign (if regt == zero, sign will be set)
ajmp32 = JZ32(0); //Skip if not set
/*--- Check for 0/0 ---*/
xXOR.PS(xRegisterSSE(t1reg), xRegisterSSE(t1reg));
xCMPEQ.SS(xRegisterSSE(t1reg), xRegisterSSE(regd));
xMOVMSKPS(eax, xRegisterSSE(t1reg));
xAND(eax, 1); //Check sign (if regd == zero, sign will be set)
pjmp1 = JZ8(0); //Skip if not set
xOR(ptr32[&fpuRegs.fprc[31]], FPUflagI | FPUflagSI); // Set I and SI flags ( 0/0 )
pjmp2 = JMP8(0);
x86SetJ8(pjmp1); //x/0 but not 0/0
xOR(ptr32[&fpuRegs.fprc[31]], FPUflagD | FPUflagSD); // Set D and SD flags ( x/0 )
x86SetJ8(pjmp2);
/*--- Make regd +/- Maximum ---*/
xXOR.PS(xRegisterSSE(regd), xRegisterSSE(regt)); // Make regd Positive or Negative
xAND.PS(xRegisterSSE(regd), ptr[&s_neg[0]]); // Get the sign bit
xOR.PS(xRegisterSSE(regd), ptr[&g_maxvals[0]]); // regd = +/- Maximum
//xMOVSSZX(xRegisterSSE(regd), ptr[&g_maxvals[0]]);
bjmp32 = JMP32(0);
x86SetJ32(ajmp32);
/*--- Normal Divide ---*/
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(regd); fpuFloat2(regt); }
xDIV.SS(xRegisterSSE(regd), xRegisterSSE(regt));
ClampValues(regd);
x86SetJ32(bjmp32);
_freeXMMreg(t1reg);
}
void recDIVhelper2(int regd, int regt) // Doesn't sets flags
{
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(regd); fpuFloat2(regt); }
xDIV.SS(xRegisterSSE(regd), xRegisterSSE(regt));
ClampValues(regd);
}
alignas(16) static FPControlRegister roundmode_nearest;
void recDIV_S_xmm(int info)
{
EE::Profiler.EmitOp(eeOpcode::DIV_F);
int t0reg = _allocTempXMMreg(XMMT_FPS);
//Console.WriteLn("DIV");
if (EmuConfig.Cpu.FPUFPCR.bitmask != EmuConfig.Cpu.FPUDivFPCR.bitmask)
xLDMXCSR(ptr32[&EmuConfig.Cpu.FPUDivFPCR.bitmask]);
switch (info & (PROCESS_EE_S | PROCESS_EE_T))
{
case PROCESS_EE_S:
//Console.WriteLn("FPU: DIV case 1");
xMOVSS(xRegisterSSE(EEREC_D), xRegisterSSE(EEREC_S));
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Ft_]]);
if (CHECK_FPU_EXTRA_FLAGS)
recDIVhelper1(EEREC_D, t0reg);
else
recDIVhelper2(EEREC_D, t0reg);
break;
case PROCESS_EE_T:
//Console.WriteLn("FPU: DIV case 2");
if (EEREC_D == EEREC_T)
{
xMOVSS(xRegisterSSE(t0reg), xRegisterSSE(EEREC_T));
xMOVSSZX(xRegisterSSE(EEREC_D), ptr[&fpuRegs.fpr[_Fs_]]);
if (CHECK_FPU_EXTRA_FLAGS)
recDIVhelper1(EEREC_D, t0reg);
else
recDIVhelper2(EEREC_D, t0reg);
}
else
{
xMOVSSZX(xRegisterSSE(EEREC_D), ptr[&fpuRegs.fpr[_Fs_]]);
if (CHECK_FPU_EXTRA_FLAGS)
recDIVhelper1(EEREC_D, EEREC_T);
else
recDIVhelper2(EEREC_D, EEREC_T);
}
break;
case (PROCESS_EE_S | PROCESS_EE_T):
//Console.WriteLn("FPU: DIV case 3");
if (EEREC_D == EEREC_T)
{
xMOVSS(xRegisterSSE(t0reg), xRegisterSSE(EEREC_T));
xMOVSS(xRegisterSSE(EEREC_D), xRegisterSSE(EEREC_S));
if (CHECK_FPU_EXTRA_FLAGS)
recDIVhelper1(EEREC_D, t0reg);
else
recDIVhelper2(EEREC_D, t0reg);
}
else
{
xMOVSS(xRegisterSSE(EEREC_D), xRegisterSSE(EEREC_S));
if (CHECK_FPU_EXTRA_FLAGS)
recDIVhelper1(EEREC_D, EEREC_T);
else
recDIVhelper2(EEREC_D, EEREC_T);
}
break;
default:
//Console.WriteLn("FPU: DIV case 4");
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Ft_]]);
xMOVSSZX(xRegisterSSE(EEREC_D), ptr[&fpuRegs.fpr[_Fs_]]);
if (CHECK_FPU_EXTRA_FLAGS)
recDIVhelper1(EEREC_D, t0reg);
else
recDIVhelper2(EEREC_D, t0reg);
break;
}
if (EmuConfig.Cpu.FPUFPCR.bitmask != EmuConfig.Cpu.FPUDivFPCR.bitmask)
xLDMXCSR(ptr32[&EmuConfig.Cpu.FPUFPCR.bitmask]);
_freeXMMreg(t0reg);
}
FPURECOMPILE_CONSTCODE(DIV_S, XMMINFO_WRITED | XMMINFO_READS | XMMINFO_READT);
//------------------------------------------------------------------
//------------------------------------------------------------------
// MADD XMM
//------------------------------------------------------------------
void recMADDtemp(int info, int regd)
{
const int t0reg = _allocTempXMMreg(XMMT_FPS);
switch (info & (PROCESS_EE_S | PROCESS_EE_T))
{
case PROCESS_EE_S:
if (regd == EEREC_S)
{
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Ft_]]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(regd); fpuFloat2(t0reg); }
xMUL.SS(xRegisterSSE(regd), xRegisterSSE(t0reg));
if (info & PROCESS_EE_ACC)
{
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(EEREC_ACC); fpuFloat(regd); }
FPU_ADD(regd, EEREC_ACC);
}
else
{
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.ACC]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(EEREC_ACC); fpuFloat(t0reg); }
FPU_ADD(regd, t0reg);
}
}
else if ((info & PROCESS_EE_ACC) && regd == EEREC_ACC)
{
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Ft_]]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(EEREC_S); fpuFloat2(t0reg); }
xMUL.SS(xRegisterSSE(t0reg), xRegisterSSE(EEREC_S));
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(regd); fpuFloat(t0reg); }
FPU_ADD(regd, t0reg);
}
else
{
xMOVSSZX(xRegisterSSE(regd), ptr[&fpuRegs.fpr[_Ft_]]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(regd); fpuFloat2(EEREC_S); }
xMUL.SS(xRegisterSSE(regd), xRegisterSSE(EEREC_S));
if (info & PROCESS_EE_ACC)
{
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(EEREC_ACC); fpuFloat(regd); }
FPU_ADD(regd, EEREC_ACC);
}
else
{
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.ACC]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(EEREC_ACC); fpuFloat(t0reg); }
FPU_ADD(regd, t0reg);
}
}
break;
case PROCESS_EE_T:
if (regd == EEREC_T)
{
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Fs_]]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(regd); fpuFloat2(t0reg); }
xMUL.SS(xRegisterSSE(regd), xRegisterSSE(t0reg));
if (info & PROCESS_EE_ACC)
{
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(EEREC_ACC); fpuFloat(regd); }
FPU_ADD(regd, EEREC_ACC);
}
else
{
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.ACC]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(EEREC_ACC); fpuFloat(t0reg); }
FPU_ADD(regd, t0reg);
}
}
else if ((info & PROCESS_EE_ACC) && regd == EEREC_ACC)
{
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Fs_]]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(EEREC_T); fpuFloat2(t0reg); }
xMUL.SS(xRegisterSSE(t0reg), xRegisterSSE(EEREC_T));
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(regd); fpuFloat(t0reg); }
FPU_ADD(regd, t0reg);
}
else
{
xMOVSSZX(xRegisterSSE(regd), ptr[&fpuRegs.fpr[_Fs_]]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(regd); fpuFloat2(EEREC_T); }
xMUL.SS(xRegisterSSE(regd), xRegisterSSE(EEREC_T));
if (info & PROCESS_EE_ACC)
{
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(EEREC_ACC); fpuFloat(regd); }
FPU_ADD(regd, EEREC_ACC);
}
else
{
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.ACC]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(EEREC_ACC); fpuFloat(t0reg); }
FPU_ADD(regd, t0reg);
}
}
break;
case (PROCESS_EE_S | PROCESS_EE_T):
if (regd == EEREC_S)
{
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(regd); fpuFloat2(EEREC_T); }
xMUL.SS(xRegisterSSE(regd), xRegisterSSE(EEREC_T));
if (info & PROCESS_EE_ACC)
{
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(regd); fpuFloat(EEREC_ACC); }
FPU_ADD(regd, EEREC_ACC);
}
else
{
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.ACC]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(regd); fpuFloat(t0reg); }
FPU_ADD(regd, t0reg);
}
}
else if (regd == EEREC_T)
{
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(regd); fpuFloat2(EEREC_S); }
xMUL.SS(xRegisterSSE(regd), xRegisterSSE(EEREC_S));
if (info & PROCESS_EE_ACC)
{
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(regd); fpuFloat(EEREC_ACC); }
FPU_ADD(regd, EEREC_ACC);
}
else
{
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.ACC]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(regd); fpuFloat(t0reg); }
FPU_ADD(regd, t0reg);
}
}
else if ((info & PROCESS_EE_ACC) && regd == EEREC_ACC)
{
xMOVSS(xRegisterSSE(t0reg), xRegisterSSE(EEREC_S));
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(t0reg); fpuFloat2(EEREC_T); }
xMUL.SS(xRegisterSSE(t0reg), xRegisterSSE(EEREC_T));
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(regd); fpuFloat(t0reg); }
FPU_ADD(regd, t0reg);
}
else
{
xMOVSS(xRegisterSSE(regd), xRegisterSSE(EEREC_S));
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(regd); fpuFloat2(EEREC_T); }
xMUL.SS(xRegisterSSE(regd), xRegisterSSE(EEREC_T));
if (info & PROCESS_EE_ACC)
{
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(regd); fpuFloat(EEREC_ACC); }
FPU_ADD(regd, EEREC_ACC);
}
else
{
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.ACC]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(regd); fpuFloat(t0reg); }
FPU_ADD(regd, t0reg);
}
}
break;
default:
if ((info & PROCESS_EE_ACC) && regd == EEREC_ACC)
{
const int t1reg = _allocTempXMMreg(XMMT_FPS);
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Fs_]]);
xMOVSSZX(xRegisterSSE(t1reg), ptr[&fpuRegs.fpr[_Ft_]]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(t0reg); fpuFloat2(t1reg); }
xMUL.SS(xRegisterSSE(t0reg), xRegisterSSE(t1reg));
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(regd); fpuFloat(t0reg); }
FPU_ADD(regd, t0reg);
_freeXMMreg(t1reg);
}
else
{
xMOVSSZX(xRegisterSSE(regd), ptr[&fpuRegs.fpr[_Fs_]]);
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Ft_]]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(regd); fpuFloat2(t0reg); }
xMUL.SS(xRegisterSSE(regd), xRegisterSSE(t0reg));
if (info & PROCESS_EE_ACC)
{
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(regd); fpuFloat(EEREC_ACC); }
FPU_ADD(regd, EEREC_ACC);
}
else
{
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.ACC]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(regd); fpuFloat(t0reg); }
FPU_ADD(regd, t0reg);
}
}
break;
}
ClampValues(regd);
_freeXMMreg(t0reg);
}
void recMADD_S_xmm(int info)
{
EE::Profiler.EmitOp(eeOpcode::MADD_F);
//xAND(ptr32[&fpuRegs.fprc[31]], ~(FPUflagO|FPUflagU)); // Clear O and U flags
recMADDtemp(info, EEREC_D);
}
FPURECOMPILE_CONSTCODE(MADD_S, XMMINFO_WRITED | XMMINFO_READACC | XMMINFO_READS | XMMINFO_READT);
void recMADDA_S_xmm(int info)
{
EE::Profiler.EmitOp(eeOpcode::MADDA_F);
//xAND(ptr32[&fpuRegs.fprc[31]], ~(FPUflagO|FPUflagU)); // Clear O and U flags
recMADDtemp(info, EEREC_ACC);
}
FPURECOMPILE_CONSTCODE(MADDA_S, XMMINFO_WRITEACC | XMMINFO_READACC | XMMINFO_READS | XMMINFO_READT);
//------------------------------------------------------------------
//------------------------------------------------------------------
// MAX / MIN XMM
//------------------------------------------------------------------
void recMAX_S_xmm(int info)
{
EE::Profiler.EmitOp(eeOpcode::MAX_F);
//xAND(ptr32[&fpuRegs.fprc[31]], ~(FPUflagO|FPUflagU)); // Clear O and U flags
recCommutativeOp(info, EEREC_D, 2);
}
FPURECOMPILE_CONSTCODE(MAX_S, XMMINFO_WRITED | XMMINFO_READS | XMMINFO_READT);
void recMIN_S_xmm(int info)
{
EE::Profiler.EmitOp(eeOpcode::MIN_F);
//xAND(ptr32[&fpuRegs.fprc[31]], ~(FPUflagO|FPUflagU)); // Clear O and U flags
recCommutativeOp(info, EEREC_D, 3);
}
FPURECOMPILE_CONSTCODE(MIN_S, XMMINFO_WRITED | XMMINFO_READS | XMMINFO_READT);
//------------------------------------------------------------------
//------------------------------------------------------------------
// MOV XMM
//------------------------------------------------------------------
void recMOV_S_xmm(int info)
{
EE::Profiler.EmitOp(eeOpcode::MOV_F);
if (info & PROCESS_EE_S)
xMOVSS(xRegisterSSE(EEREC_D), xRegisterSSE(EEREC_S));
else
xMOVSSZX(xRegisterSSE(EEREC_D), ptr[&fpuRegs.fpr[_Fs_]]);
}
FPURECOMPILE_CONSTCODE(MOV_S, XMMINFO_WRITED | XMMINFO_READS);
//------------------------------------------------------------------
//------------------------------------------------------------------
// MSUB XMM
//------------------------------------------------------------------
void recMSUBtemp(int info, int regd)
{
int t0reg = _allocTempXMMreg(XMMT_FPS);
switch (info & (PROCESS_EE_S | PROCESS_EE_T))
{
case PROCESS_EE_S:
if (regd == EEREC_S)
{
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Ft_]]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(regd); fpuFloat2(t0reg); }
xMUL.SS(xRegisterSSE(regd), xRegisterSSE(t0reg));
if (info & PROCESS_EE_ACC)
xMOVSS(xRegisterSSE(t0reg), xRegisterSSE(EEREC_ACC));
else
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.ACC]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(regd); fpuFloat(t0reg); }
FPU_SUB(t0reg, regd);
xMOVSS(xRegisterSSE(regd), xRegisterSSE(t0reg));
}
else if ((info & PROCESS_EE_ACC) && regd == EEREC_ACC)
{
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Ft_]]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(EEREC_S); fpuFloat2(t0reg); }
xMUL.SS(xRegisterSSE(t0reg), xRegisterSSE(EEREC_S));
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(regd); fpuFloat(t0reg); }
FPU_SUB(regd, t0reg);
}
else
{
xMOVSSZX(xRegisterSSE(regd), ptr[&fpuRegs.fpr[_Ft_]]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(regd); fpuFloat2(EEREC_S); }
xMUL.SS(xRegisterSSE(regd), xRegisterSSE(EEREC_S));
if (info & PROCESS_EE_ACC)
xMOVSS(xRegisterSSE(t0reg), xRegisterSSE(EEREC_ACC));
else
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.ACC]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(regd); fpuFloat(t0reg); }
FPU_SUB(t0reg, regd);
xMOVSS(xRegisterSSE(regd), xRegisterSSE(t0reg));
}
break;
case PROCESS_EE_T:
if (regd == EEREC_T)
{
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Fs_]]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(regd); fpuFloat2(t0reg); }
xMUL.SS(xRegisterSSE(regd), xRegisterSSE(t0reg));
if (info & PROCESS_EE_ACC)
xMOVSS(xRegisterSSE(t0reg), xRegisterSSE(EEREC_ACC));
else
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.ACC]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(regd); fpuFloat(t0reg); }
FPU_SUB(t0reg, regd);
xMOVSS(xRegisterSSE(regd), xRegisterSSE(t0reg));
}
else if ((info & PROCESS_EE_ACC) && regd == EEREC_ACC)
{
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Fs_]]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(EEREC_T); fpuFloat2(t0reg); }
xMUL.SS(xRegisterSSE(t0reg), xRegisterSSE(EEREC_T));
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(regd); fpuFloat(t0reg); }
FPU_SUB(regd, t0reg);
}
else
{
xMOVSSZX(xRegisterSSE(regd), ptr[&fpuRegs.fpr[_Fs_]]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(regd); fpuFloat2(EEREC_T); }
xMUL.SS(xRegisterSSE(regd), xRegisterSSE(EEREC_T));
if (info & PROCESS_EE_ACC)
xMOVSS(xRegisterSSE(t0reg), xRegisterSSE(EEREC_ACC));
else
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.ACC]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(regd); fpuFloat(t0reg); }
FPU_SUB(t0reg, regd);
xMOVSS(xRegisterSSE(regd), xRegisterSSE(t0reg));
}
break;
case (PROCESS_EE_S | PROCESS_EE_T):
if (regd == EEREC_S)
{
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(regd); fpuFloat2(EEREC_T); }
xMUL.SS(xRegisterSSE(regd), xRegisterSSE(EEREC_T));
if (info & PROCESS_EE_ACC)
xMOVSS(xRegisterSSE(t0reg), xRegisterSSE(EEREC_ACC));
else
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.ACC]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(regd); fpuFloat(t0reg); }
FPU_SUB(t0reg, regd);
xMOVSS(xRegisterSSE(regd), xRegisterSSE(t0reg));
}
else if (regd == EEREC_T)
{
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(regd); fpuFloat2(EEREC_S); }
xMUL.SS(xRegisterSSE(regd), xRegisterSSE(EEREC_S));
if (info & PROCESS_EE_ACC)
xMOVSS(xRegisterSSE(t0reg), xRegisterSSE(EEREC_ACC));
else
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.ACC]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(regd); fpuFloat(t0reg); }
FPU_SUB(t0reg, regd);
xMOVSS(xRegisterSSE(regd), xRegisterSSE(t0reg));
}
else if ((info & PROCESS_EE_ACC) && regd == EEREC_ACC)
{
xMOVSS(xRegisterSSE(t0reg), xRegisterSSE(EEREC_S));
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(t0reg); fpuFloat2(EEREC_T); }
xMUL.SS(xRegisterSSE(t0reg), xRegisterSSE(EEREC_T));
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(regd); fpuFloat(t0reg); }
FPU_SUB(regd, t0reg);
}
else
{
xMOVSS(xRegisterSSE(regd), xRegisterSSE(EEREC_S));
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(regd); fpuFloat2(EEREC_T); }
xMUL.SS(xRegisterSSE(regd), xRegisterSSE(EEREC_T));
if (info & PROCESS_EE_ACC)
xMOVSS(xRegisterSSE(t0reg), xRegisterSSE(EEREC_ACC));
else
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.ACC]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(regd); fpuFloat(t0reg); }
FPU_SUB(t0reg, regd);
xMOVSS(xRegisterSSE(regd), xRegisterSSE(t0reg));
}
break;
default:
if ((info & PROCESS_EE_ACC) && regd == EEREC_ACC)
{
const int t1reg = _allocTempXMMreg(XMMT_FPS);
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Fs_]]);
xMOVSSZX(xRegisterSSE(t1reg), ptr[&fpuRegs.fpr[_Ft_]]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(t0reg); fpuFloat2(t1reg); }
xMUL.SS(xRegisterSSE(t0reg), xRegisterSSE(t1reg));
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(regd); fpuFloat(t0reg); }
FPU_SUB(regd, t0reg);
_freeXMMreg(t1reg);
}
else
{
xMOVSSZX(xRegisterSSE(regd), ptr[&fpuRegs.fpr[_Fs_]]);
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Ft_]]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat2(regd); fpuFloat2(t0reg); }
xMUL.SS(xRegisterSSE(regd), xRegisterSSE(t0reg));
if (info & PROCESS_EE_ACC)
xMOVSS(xRegisterSSE(t0reg), xRegisterSSE(EEREC_ACC));
else
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.ACC]);
if (CHECK_FPU_EXTRA_OVERFLOW) { fpuFloat(regd); fpuFloat(t0reg); }
FPU_SUB(t0reg, regd);
xMOVSS(xRegisterSSE(regd), xRegisterSSE(t0reg));
}
break;
}
ClampValues(regd);
_freeXMMreg(t0reg);
}
void recMSUB_S_xmm(int info)
{
EE::Profiler.EmitOp(eeOpcode::MSUB_F);
//xAND(ptr32[&fpuRegs.fprc[31]], ~(FPUflagO|FPUflagU)); // Clear O and U flags
recMSUBtemp(info, EEREC_D);
}
FPURECOMPILE_CONSTCODE(MSUB_S, XMMINFO_WRITED | XMMINFO_READACC | XMMINFO_READS | XMMINFO_READT);
void recMSUBA_S_xmm(int info)
{
EE::Profiler.EmitOp(eeOpcode::MSUBA_F);
//xAND(ptr32[&fpuRegs.fprc[31]], ~(FPUflagO|FPUflagU)); // Clear O and U flags
recMSUBtemp(info, EEREC_ACC);
}
FPURECOMPILE_CONSTCODE(MSUBA_S, XMMINFO_WRITEACC | XMMINFO_READACC | XMMINFO_READS | XMMINFO_READT);
//------------------------------------------------------------------
//------------------------------------------------------------------
// MUL XMM
//------------------------------------------------------------------
void recMUL_S_xmm(int info)
{
EE::Profiler.EmitOp(eeOpcode::MUL_F);
//xAND(ptr32[&fpuRegs.fprc[31]], ~(FPUflagO|FPUflagU)); // Clear O and U flags
ClampValues(recCommutativeOp(info, EEREC_D, 1));
}
FPURECOMPILE_CONSTCODE(MUL_S, XMMINFO_WRITED | XMMINFO_READS | XMMINFO_READT);
void recMULA_S_xmm(int info)
{
EE::Profiler.EmitOp(eeOpcode::MULA_F);
//xAND(ptr32[&fpuRegs.fprc[31]], ~(FPUflagO|FPUflagU)); // Clear O and U flags
ClampValues(recCommutativeOp(info, EEREC_ACC, 1));
}
FPURECOMPILE_CONSTCODE(MULA_S, XMMINFO_WRITEACC | XMMINFO_READS | XMMINFO_READT);
//------------------------------------------------------------------
//------------------------------------------------------------------
// NEG XMM
//------------------------------------------------------------------
void recNEG_S_xmm(int info)
{
EE::Profiler.EmitOp(eeOpcode::NEG_F);
if (info & PROCESS_EE_S)
xMOVSS(xRegisterSSE(EEREC_D), xRegisterSSE(EEREC_S));
else
xMOVSSZX(xRegisterSSE(EEREC_D), ptr[&fpuRegs.fpr[_Fs_]]);
//xAND(ptr32[&fpuRegs.fprc[31]], ~(FPUflagO|FPUflagU)); // Clear O and U flags
xXOR.PS(xRegisterSSE(EEREC_D), ptr[&s_neg[0]]);
// Always preserve sign. Using float clamping here would result in
// +inf to become +fMax instead of -fMax, which is definitely wrong.
fpuFloat3(EEREC_D);
}
FPURECOMPILE_CONSTCODE(NEG_S, XMMINFO_WRITED | XMMINFO_READS);
//------------------------------------------------------------------
//------------------------------------------------------------------
// SUB XMM
//------------------------------------------------------------------
void recSUBhelper(int regd, int regt)
{
if (CHECK_FPU_EXTRA_OVERFLOW /*&& !CHECK_FPUCLAMPHACK*/) { fpuFloat2(regd); fpuFloat2(regt); }
FPU_SUB(regd, regt);
}
void recSUBop(int info, int regd)
{
int t0reg = _allocTempXMMreg(XMMT_FPS);
//xAND(ptr32[&fpuRegs.fprc[31]], ~(FPUflagO|FPUflagU)); // Clear O and U flags
switch (info & (PROCESS_EE_S | PROCESS_EE_T))
{
case PROCESS_EE_S:
//Console.WriteLn("FPU: SUB case 1");
if (regd != EEREC_S)
xMOVSS(xRegisterSSE(regd), xRegisterSSE(EEREC_S));
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Ft_]]);
recSUBhelper(regd, t0reg);
break;
case PROCESS_EE_T:
//Console.WriteLn("FPU: SUB case 2");
if (regd == EEREC_T)
{
xMOVSS(xRegisterSSE(t0reg), xRegisterSSE(EEREC_T));
xMOVSSZX(xRegisterSSE(regd), ptr[&fpuRegs.fpr[_Fs_]]);
recSUBhelper(regd, t0reg);
}
else
{
xMOVSSZX(xRegisterSSE(regd), ptr[&fpuRegs.fpr[_Fs_]]);
recSUBhelper(regd, EEREC_T);
}
break;
case (PROCESS_EE_S | PROCESS_EE_T):
//Console.WriteLn("FPU: SUB case 3");
if (regd == EEREC_T)
{
xMOVSS(xRegisterSSE(t0reg), xRegisterSSE(EEREC_T));
xMOVSS(xRegisterSSE(regd), xRegisterSSE(EEREC_S));
recSUBhelper(regd, t0reg);
}
else
{
xMOVSS(xRegisterSSE(regd), xRegisterSSE(EEREC_S));
recSUBhelper(regd, EEREC_T);
}
break;
default:
Console.Warning("FPU: SUB case 4");
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Ft_]]);
xMOVSSZX(xRegisterSSE(regd), ptr[&fpuRegs.fpr[_Fs_]]);
recSUBhelper(regd, t0reg);
break;
}
ClampValues(regd);
_freeXMMreg(t0reg);
}
void recSUB_S_xmm(int info)
{
EE::Profiler.EmitOp(eeOpcode::SUB_F);
recSUBop(info, EEREC_D);
}
FPURECOMPILE_CONSTCODE(SUB_S, XMMINFO_WRITED | XMMINFO_READS | XMMINFO_READT);
void recSUBA_S_xmm(int info)
{
EE::Profiler.EmitOp(eeOpcode::SUBA_F);
recSUBop(info, EEREC_ACC);
}
FPURECOMPILE_CONSTCODE(SUBA_S, XMMINFO_WRITEACC | XMMINFO_READS | XMMINFO_READT);
//------------------------------------------------------------------
//------------------------------------------------------------------
// SQRT XMM
//------------------------------------------------------------------
void recSQRT_S_xmm(int info)
{
EE::Profiler.EmitOp(eeOpcode::SQRT_F);
bool roundmodeFlag = false;
//Console.WriteLn("FPU: SQRT");
if (EmuConfig.Cpu.FPUFPCR.GetRoundMode() != FPRoundMode::Nearest)
{
// Set roundmode to nearest if it isn't already
//Console.WriteLn("sqrt to nearest");
roundmode_nearest = EmuConfig.Cpu.FPUFPCR;
roundmode_nearest.SetRoundMode(FPRoundMode::Nearest);
xLDMXCSR(ptr32[&roundmode_nearest.bitmask]);
roundmodeFlag = true;
}
if (info & PROCESS_EE_T)
xMOVSS(xRegisterSSE(EEREC_D), xRegisterSSE(EEREC_T));
else
xMOVSSZX(xRegisterSSE(EEREC_D), ptr[&fpuRegs.fpr[_Ft_]]);
if (CHECK_FPU_EXTRA_FLAGS)
{
xAND(ptr32[&fpuRegs.fprc[31]], ~(FPUflagI | FPUflagD)); // Clear I and D flags
/*--- Check for negative SQRT ---*/
xMOVMSKPS(eax, xRegisterSSE(EEREC_D));
xAND(eax, 1); //Check sign
u8* pjmp = JZ8(0); //Skip if none are
xOR(ptr32[&fpuRegs.fprc[31]], FPUflagI | FPUflagSI); // Set I and SI flags
xAND.PS(xRegisterSSE(EEREC_D), ptr[&s_pos[0]]); // Make EEREC_D Positive
x86SetJ8(pjmp);
}
else
xAND.PS(xRegisterSSE(EEREC_D), ptr[&s_pos[0]]); // Make EEREC_D Positive
if (CHECK_FPU_OVERFLOW) // Only need to do positive clamp, since EEREC_D is positive
xMIN.SS(xRegisterSSE(EEREC_D), ptr[&g_maxvals[0]]);
xSQRT.SS(xRegisterSSE(EEREC_D), xRegisterSSE(EEREC_D));
if (CHECK_FPU_EXTRA_OVERFLOW) // Shouldn't need to clamp again since SQRT of a number will always be smaller than the original number, doing it just incase :/
ClampValues(EEREC_D);
if (roundmodeFlag)
xLDMXCSR(ptr32[&EmuConfig.Cpu.FPUFPCR.bitmask]);
}
FPURECOMPILE_CONSTCODE(SQRT_S, XMMINFO_WRITED | XMMINFO_READT);
//------------------------------------------------------------------
//------------------------------------------------------------------
// RSQRT XMM
//------------------------------------------------------------------
void recRSQRThelper1(int regd, int t0reg) // Preforms the RSQRT function when regd <- Fs and t0reg <- Ft (Sets correct flags)
{
u8 *pjmp1, *pjmp2;
u32 *pjmp32;
u8 *qjmp1, *qjmp2;
int t1reg = _allocTempXMMreg(XMMT_FPS);
xAND(ptr32[&fpuRegs.fprc[31]], ~(FPUflagI | FPUflagD)); // Clear I and D flags
/*--- (first) Check for negative SQRT ---*/
xMOVMSKPS(eax, xRegisterSSE(t0reg));
xAND(eax, 1); //Check sign
pjmp2 = JZ8(0); //Skip if not set
xOR(ptr32[&fpuRegs.fprc[31]], FPUflagI | FPUflagSI); // Set I and SI flags
xAND.PS(xRegisterSSE(t0reg), ptr[&s_pos[0]]); // Make t0reg Positive
x86SetJ8(pjmp2);
/*--- Check for zero ---*/
xXOR.PS(xRegisterSSE(t1reg), xRegisterSSE(t1reg));
xCMPEQ.SS(xRegisterSSE(t1reg), xRegisterSSE(t0reg));
xMOVMSKPS(eax, xRegisterSSE(t1reg));
xAND(eax, 1); //Check sign (if t0reg == zero, sign will be set)
pjmp1 = JZ8(0); //Skip if not set
/*--- Check for 0/0 ---*/
xXOR.PS(xRegisterSSE(t1reg), xRegisterSSE(t1reg));
xCMPEQ.SS(xRegisterSSE(t1reg), xRegisterSSE(regd));
xMOVMSKPS(eax, xRegisterSSE(t1reg));
xAND(eax, 1); //Check sign (if regd == zero, sign will be set)
qjmp1 = JZ8(0); //Skip if not set
xOR(ptr32[&fpuRegs.fprc[31]], FPUflagI | FPUflagSI); // Set I and SI flags ( 0/0 )
qjmp2 = JMP8(0);
x86SetJ8(qjmp1); //x/0 but not 0/0
xOR(ptr32[&fpuRegs.fprc[31]], FPUflagD | FPUflagSD); // Set D and SD flags ( x/0 )
x86SetJ8(qjmp2);
/*--- Make regd +/- Maximum ---*/
xAND.PS(xRegisterSSE(regd), ptr[&s_neg[0]]); // Get the sign bit
xOR.PS(xRegisterSSE(regd), ptr[&g_maxvals[0]]); // regd = +/- Maximum
pjmp32 = JMP32(0);
x86SetJ8(pjmp1);
if (CHECK_FPU_EXTRA_OVERFLOW)
{
xMIN.SS(xRegisterSSE(t0reg), ptr[&g_maxvals[0]]); // Only need to do positive clamp, since t0reg is positive
fpuFloat2(regd);
}
xSQRT.SS(xRegisterSSE(t0reg), xRegisterSSE(t0reg));
xDIV.SS(xRegisterSSE(regd), xRegisterSSE(t0reg));
ClampValues(regd);
x86SetJ32(pjmp32);
_freeXMMreg(t1reg);
}
void recRSQRThelper2(int regd, int t0reg) // Preforms the RSQRT function when regd <- Fs and t0reg <- Ft (Doesn't set flags)
{
xAND.PS(xRegisterSSE(t0reg), ptr[&s_pos[0]]); // Make t0reg Positive
if (CHECK_FPU_EXTRA_OVERFLOW)
{
xMIN.SS(xRegisterSSE(t0reg), ptr[&g_maxvals[0]]); // Only need to do positive clamp, since t0reg is positive
fpuFloat2(regd);
}
xSQRT.SS(xRegisterSSE(t0reg), xRegisterSSE(t0reg));
xDIV.SS(xRegisterSSE(regd), xRegisterSSE(t0reg));
ClampValues(regd);
}
void recRSQRT_S_xmm(int info)
{
EE::Profiler.EmitOp(eeOpcode::RSQRT_F);
// RSQRT doesn't change the round mode, because RSQRTSS ignores the rounding mode in MXCSR.
const int t0reg = _allocTempXMMreg(XMMT_FPS);
//Console.WriteLn("FPU: RSQRT");
switch (info & (PROCESS_EE_S | PROCESS_EE_T))
{
case PROCESS_EE_S:
//Console.WriteLn("FPU: RSQRT case 1");
xMOVSS(xRegisterSSE(EEREC_D), xRegisterSSE(EEREC_S));
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Ft_]]);
if (CHECK_FPU_EXTRA_FLAGS)
recRSQRThelper1(EEREC_D, t0reg);
else
recRSQRThelper2(EEREC_D, t0reg);
break;
case PROCESS_EE_T:
//Console.WriteLn("FPU: RSQRT case 2");
xMOVSS(xRegisterSSE(t0reg), xRegisterSSE(EEREC_T));
xMOVSSZX(xRegisterSSE(EEREC_D), ptr[&fpuRegs.fpr[_Fs_]]);
if (CHECK_FPU_EXTRA_FLAGS)
recRSQRThelper1(EEREC_D, t0reg);
else
recRSQRThelper2(EEREC_D, t0reg);
break;
case (PROCESS_EE_S | PROCESS_EE_T):
//Console.WriteLn("FPU: RSQRT case 3");
xMOVSS(xRegisterSSE(t0reg), xRegisterSSE(EEREC_T));
xMOVSS(xRegisterSSE(EEREC_D), xRegisterSSE(EEREC_S));
if (CHECK_FPU_EXTRA_FLAGS)
recRSQRThelper1(EEREC_D, t0reg);
else
recRSQRThelper2(EEREC_D, t0reg);
break;
default:
//Console.WriteLn("FPU: RSQRT case 4");
xMOVSSZX(xRegisterSSE(t0reg), ptr[&fpuRegs.fpr[_Ft_]]);
xMOVSSZX(xRegisterSSE(EEREC_D), ptr[&fpuRegs.fpr[_Fs_]]);
if (CHECK_FPU_EXTRA_FLAGS)
recRSQRThelper1(EEREC_D, t0reg);
else
recRSQRThelper2(EEREC_D, t0reg);
break;
}
_freeXMMreg(t0reg);
}
FPURECOMPILE_CONSTCODE(RSQRT_S, XMMINFO_WRITED | XMMINFO_READS | XMMINFO_READT);
#endif // FPU_RECOMPILE
} // namespace COP1
} // namespace OpcodeImpl
} // namespace Dynarec
} // namespace R5900