FPU: DIV.S and RSQRT.S leave O/U alone, and RSQRT.S of a negative raises I

The soft-float COP1 interpreter set and cleared O/U and SO/SU after
DIV.S and RSQRT.S, where both recompilers and the interpreter before
the soft model clamp the result without touching them. It now does the
same. ps2f_rsqrt drops Ft's sign before its square root, so RSQRT.S of
a negative non-zero Ft never raised I/SI; the interpreter now raises it
from Ft, as both recompilers do. Results are unchanged: ps2autotests
ee_fpu still scores 36/36 on DIV and 39/39 on RSQRT through FPU.cpp.

tests/fpu fpuflags: DIV.S and RSQRT.S overflow and underflow with the
O/U bits clear and set, and RSQRT.S of -4.0, through the real FPU.cpp.
Self-contained, so CI runs it without a ps2autotests checkout.
This commit is contained in:
libretroadmin committed 2026-11-08 01:48:14 +01:00
1 parent 6516f15e04
commit c199233c63
3 files changed
+124 -10

No files matched your search

+9 -10
View File
@@ -329,12 +329,8 @@ void CVT_W() {
void DIV_S() {
if (checkDivideByZero( _FdValUl_, _FtValUl_, _FsValUl_, FPUflagD | FPUflagSD, FPUflagI | FPUflagSI)) return;
{
u64 res = ps2f_div(_FsValUl_, _FtValUl_);
_FdValUl_ = ps2f_raw(res);
if (checkOverflowUnderflowSoft(res, FPUflagO | FPUflagSO, 1)) return;
checkOverflowUnderflowSoft(res, FPUflagU | FPUflagSU, 0);
}
/* DIV.S clamps but leaves O/U and SO/SU alone, as both recompilers do. */
_FdValUl_ = ps2f_raw(ps2f_div(_FsValUl_, _FtValUl_));
}
/* The Instruction Set manual has an overly complicated way of
@@ -414,11 +410,14 @@ void NEG_S() {
void RSQRT_S() {
clearFPUFlags(FPUflagD | FPUflagI);
u64 res = ps2f_rsqrt(_FsValUl_, _FtValUl_);
u32 ft = _FtValUl_;
u64 res = ps2f_rsqrt(_FsValUl_, ft);
_FdValUl_ = ps2f_raw(res);
if (checkDivideByZeroInvalidSoft(res, FPUflagD | FPUflagSD, FPUflagI | FPUflagSI)) return;
if (checkOverflowUnderflowSoft(res, FPUflagO | FPUflagSO, 1)) return;
checkOverflowUnderflowSoft(res, FPUflagU | FPUflagSU, 0);
/* ps2f_rsqrt drops Ft's sign before the sqrt, so a negative non-zero
* Ft raises I here. O/U stay untouched, as in both recompilers. */
if ((ft & 0x80000000) && (ft & 0x7F800000))
_ContVal_ |= FPUflagI | FPUflagSI;
checkDivideByZeroInvalidSoft(res, FPUflagD | FPUflagSD, FPUflagI | FPUflagSI);
}
void SQRT_S() {
+9
View File
@@ -13,6 +13,15 @@ CC=${CC:-cc}
SANFLAGS=""
[ -n "$SANITIZER" ] && SANFLAGS="-fsanitize=$SANITIZER"
echo "== EE FPU DIV/RSQRT flags through FPU.cpp =="
${CXX:-c++} -std=c++17 -O1 -w -frounding-math $SANFLAGS \
-I "$ROOT" -I "$ROOT/pcsx2" -I "$ROOT/common" \
-I "$ROOT/common/include" -I "$ROOT/3rdparty/include" \
-I "$ROOT/libretro/libretro-common/include" \
-o "$DIR/fpu_fpuflags" "$DIR/fpuflags.cpp" \
"$ROOT/pcsx2/FPU.cpp" "$ROOT/pcsx2/ps2float.c"
"$DIR/fpu_fpuflags"
echo "== EE FPU vs console =="
EXPECTED="${PS2AUTOTESTS:-}/tests/cpu/ee_fpu"
if [ -d "$EXPECTED" ]; then
+106
View File
@@ -0,0 +1,106 @@
/* EE FPU flag behaviour of DIV.S and RSQRT.S, through the real FPU.cpp.
*
* The console-capture suites score results, not FCR31, so these flag
* rules had nothing guarding them. The reference is what both
* recompilers emit and what the interpreter did before the soft model:
*
* - DIV.S and RSQRT.S clamp but never set or clear O/U or SO/SU.
* - RSQRT.S with a negative, non-zero Ft sets I and SI and computes
* from |Ft|.
*
* Self-contained: needs no ps2autotests checkout.
*
* Usage: tests/fpu/fpuflags
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <fenv.h>
#include "Common.h"
#include "R5900.h"
alignas(16) cpuRegisters cpuRegs;
alignas(16) fpuRegisters fpuRegs;
u32 vtlb_memRead32(u32) { return 0; }
void vtlb_memWrite32(u32, u32) {}
void intDoBranch(u32) {}
namespace R5900 { namespace Interpreter { namespace OpcodeImpl { namespace COP1 {
void DIV_S(); void RSQRT_S();
} } } }
using namespace R5900::Interpreter::OpcodeImpl::COP1;
#define FS 1
#define FT 2
#define FD 3
#define FLAG_I 0x00020000u
#define FLAG_O 0x00008000u
#define FLAG_U 0x00004000u
#define FLAG_SI 0x00000040u
#define FLAG_SO 0x00000010u
#define FLAG_SU 0x00000008u
#define FLAGS_OU (FLAG_O | FLAG_U | FLAG_SO | FLAG_SU)
static int failures;
static void run(const char* name, void (*fn)(), u32 fs, u32 ft, u32 fcr_in,
u32 mask, u32 want_bits, int check_res, u32 want_res)
{
u32 got;
cpuRegs.code = (FT << 16) | (FS << 11) | (FD << 6);
fpuRegs.fpr[FS].UL = fs;
fpuRegs.fpr[FT].UL = ft;
fpuRegs.fpr[FD].UL = 0;
fpuRegs.fprc[31] = fcr_in;
fn();
got = fpuRegs.fprc[31] & mask;
if (got != want_bits)
{
printf(" FAIL %-34s fcr31&%08x = %08x, want %08x\n", name,
(unsigned)mask, (unsigned)got, (unsigned)want_bits);
failures++;
}
if (check_res && fpuRegs.fpr[FD].UL != want_res)
{
printf(" FAIL %-34s fd = %08x, want %08x\n", name,
(unsigned)fpuRegs.fpr[FD].UL, (unsigned)want_res);
failures++;
}
}
int main(void)
{
const u32 huge = 0x7F000000u; /* 2^127 */
const u32 tiny = 0x00800000u; /* 2^-126 */
fesetround(FE_TOWARDZERO);
/* DIV.S: an overflowing and an underflowing quotient touch no O/U bit,
* whether those bits start clear or set. */
run("div overflow, flags clear", DIV_S, huge, tiny, 0, FLAGS_OU, 0, 0, 0);
run("div underflow, flags clear", DIV_S, tiny, huge, 0, FLAGS_OU, 0, 0, 0);
run("div overflow, flags set", DIV_S, huge, tiny, FLAGS_OU, FLAGS_OU, FLAGS_OU, 0, 0);
run("div ordinary, flags set", DIV_S, 0x40000000u, 0x3F800000u, FLAGS_OU,
FLAGS_OU, FLAGS_OU, 1, 0x40000000u);
/* RSQRT.S: negative Ft raises I/SI and uses |Ft|: 1 / sqrt(4) = 0.5. */
run("rsqrt negative ft raises I", RSQRT_S, 0x3F800000u, 0xC0800000u, 0,
FLAG_I | FLAG_SI, FLAG_I | FLAG_SI, 1, 0x3F000000u);
run("rsqrt positive ft, no I", RSQRT_S, 0x3F800000u, 0x40800000u, 0,
FLAG_I | FLAG_SI, 0, 1, 0x3F000000u);
run("rsqrt overflow, flags clear", RSQRT_S, huge, tiny, 0, FLAGS_OU, 0, 0, 0);
run("rsqrt overflow, flags set", RSQRT_S, huge, tiny, FLAGS_OU, FLAGS_OU, FLAGS_OU, 0, 0);
run("rsqrt underflow, flags clear", RSQRT_S, tiny, 0x7E800000u, 0, FLAGS_OU, 0, 0, 0);
if (failures)
{
printf("fpuflags: %d failure(s)\n", failures);
return 1;
}
printf("fpuflags: all cases pass\n");
return 0;
}