EE/FPU: narrow SQRT's root with a plain Fcvt

ToPS2FPU_Full's saturating and flushing arms are for results that leave
the PS2's range, and a root does not, so both sat dead behind their
compares. The body goes from 54 host instructions to 23, under the 26
the single-precision path spends.
This commit is contained in:
pstef
2026-08-16 00:11:46 +02:00
parent deda4c2bb2
commit a0eaa92c0a
2 changed files with 38 additions and 3 deletions
+9 -3
View File
@@ -1034,8 +1034,8 @@ void recDIV_S_xmm(int info)
void recSQRT_S_xmm(int info)
{
// Round-to-nearest for the double Fsqrt + the ToPS2FPU narrowing, like
// x86's roundmode_nearest swap (FPUDivFPCR is the nearest-mode FPCR).
// Round-to-nearest for the double Fsqrt and the narrowing Fcvt, like x86's
// roundmode_nearest swap (FPUDivFPCR is the nearest-mode FPCR).
const bool swapFpcr = EmuConfig.Cpu.FPUFPCR.bitmask != EmuConfig.Cpu.FPUDivFPCR.bitmask;
if (swapFpcr)
emitLoadFPCRImm(EmuConfig.Cpu.FPUDivFPCR.bitmask);
@@ -1056,7 +1056,13 @@ void recSQRT_S_xmm(int info)
armAsm->Bind(&tPositive);
armAsm->Fsqrt(armDRegister(treg), armDRegister(treg));
ToPS2FPU_Full(treg, false, treg, false, false);
// A root cannot leave the in-range band, so the narrowing is the plain
// Fcvt with none of ToPS2FPU_Full's arms around it. The largest operand is
// a shade under 2^129 and roots to under 2^65; the smallest one FZ does
// not flush is 2^-126 and roots to 2^-63. Both sit inside [2^-126, 2^128),
// and the only result outside it is the zero the underflow arm would have
// flushed to the same zero, |t| having already made its sign positive.
armAsm->Fcvt(armSRegister(treg), armDRegister(treg));
SingleToSlot(EEREC_D, treg);
_freeNEONreg(treg);
@@ -588,6 +588,35 @@ TEST(EeRecFpuFull, SqrtNegativeSetsIFlagAndUsesAbs)
EXPECT_EQ(h.GetGpr64Jit(reg::v0) & 0x00020040u, 0x00020040u) << "I|SI not set";
}
// recSQRT_S_xmm narrows with a plain Fcvt because a root cannot leave the band
// ToPS2FPU_Full's saturating and flushing arms exist for. These are the four
// operands nearest the ends of that band.
TEST(EeRecFpuFull, SqrtStaysInsideTheNarrowingBand)
{
struct Case
{
u32 ft, want;
const char* what;
};
static constexpr Case kCases[] = {
{0x7FFFFFFFu, 0x5FB504F3u, "EEMAX: the largest root there is, 2^64.5"},
{0x00800000u, 0x20000000u, "2^-126: the smallest operand FZ keeps, root 2^-63"},
{0x007FFFFFu, 0x00000000u, "the largest denormal, flushed ahead of the root"},
{0x80000000u, 0x00000000u, "-0.0"},
};
for (const Case& c : kCases)
{
SCOPED_TRACE(c.what);
EeRecTestHarness h;
h.EnableCop1();
h.EnableFpuFullMode();
h.SetFprBits(1, c.ft);
h.LoadProgram({SQRT_S(2, 1)});
h.RunJitNoDiff();
EXPECT_EQ(h.GetFprBitsJit(2), c.want) << std::hex << "ft=" << c.ft;
}
}
TEST(EeRecFpuFull, RsqrtPseudoInfExact)
{
// 1.0 / sqrt(2^128) = 2^-64 = 0x1f800000 exactly. The current interp