mirror of
https://github.com/libretro/parallel-ps2.git
synced 2026-11-10 03:11:38 -10:00
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:
1 parent
6516f15e04
commit
c199233c63
3 files changed
+124
-10
No files matched your search
+9
-10
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user