VFPU: vsgn of a denormal is zero

In the interpreter, the IR interpreter, every backend's FSign and the x86
JIT's own vsgn. Recorded in cpu/vfpu/specials.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Fable 5.1 committed 2026-09-22 14:10:22 -06:00
1 parent 2c801b8a31
commit b16cd9a734
7 files changed
+37 -18

No files matched your search

+8 -4
View File
@@ -20,6 +20,7 @@
#if PPSSPP_ARCH(ARM64) || (PPSSPP_PLATFORM(WINDOWS) && !defined(__LIBRETRO__))
#ifndef offsetof
#include <cfloat>
#include <cstddef>
#endif
@@ -99,8 +100,11 @@ void Arm64JitBackend::CompIR_FAssign(IRInst inst) {
case IROp::FSign:
regs_.Map(inst);
// We'll need this flag later. Vector could use a temp and FCMEQ.
fp_.FCMP(regs_.F(inst.src1));
// Zero and denormals both sign as zero on the hardware, so compare the magnitude against
// the smallest normal. We'll need this flag later.
fp_.FABS(SCRATCHF1, regs_.F(inst.src1));
fp_.MOVI2FDUP(EncodeRegToDouble(SCRATCHF2), FLT_MIN);
fp_.FCMP(SCRATCHF1, SCRATCHF2);
fp_.MOVI2FDUP(EncodeRegToDouble(SCRATCHF1), 1.0f);
// Invert 0x80000000 -> 0x7FFFFFFF as a mask for sign.
@@ -110,9 +114,9 @@ void Arm64JitBackend::CompIR_FAssign(IRInst inst) {
fp_.FMOV(regs_.FD(inst.dest), regs_.FD(inst.src1));
fp_.BIT(regs_.FD(inst.dest), EncodeRegToDouble(SCRATCHF1), EncodeRegToDouble(SCRATCHF2));
// It's later now, let's replace with zero if that FCmp was EQ to zero.
// It's later now, let's replace with zero if that FCmp said below (MI is false for a NaN.)
fp_.MOVI2FDUP(EncodeRegToDouble(SCRATCHF1), 0.0f);
fp_.FCSEL(regs_.F(inst.dest), SCRATCHF1, regs_.F(inst.dest), CC_EQ);
fp_.FCSEL(regs_.F(inst.dest), SCRATCHF1, regs_.F(inst.dest), CC_MI);
break;
default:
+2 -2
View File
@@ -894,10 +894,10 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) {
case IROp::FSign:
{
// Bitwise trickery
// Bitwise trickery. Denormals give zero, as on the hardware.
u32 val;
memcpy(&val, &mips->f[inst->src1], sizeof(u32));
if (val == 0 || val == 0x80000000)
if ((val & 0x7F800000) == 0)
mips->f[inst->dest] = 0.0f;
else if ((val >> 31) == 0)
mips->f[inst->dest] = 1.0f;
+3 -2
View File
@@ -800,10 +800,11 @@ namespace MIPSInt
for (int i = 0; i < n; i++) {
float diff = s[i] - t[i];
// To handle NaNs correctly, we do this with integer hackery
// To handle NaNs correctly, we do this with integer hackery. A denormal difference
// is zero, like everywhere else on the VFPU.
u32 val;
memcpy(&val, &diff, sizeof(u32));
if (val == 0 || val == 0x80000000)
if ((val & 0x7F800000) == 0)
d[i] = 0.0f;
else if ((val >> 31) == 0)
d[i] = 1.0f;
+2 -2
View File
@@ -158,10 +158,10 @@ void LoongArch64JitBackend::CompIR_FAssign(IRInst inst) {
case IROp::FSign:
{
regs_.Map(inst);
// Check if it's negative zero, either 0x20/0x200 is zero.
// Zero or a denormal signs as zero: 0x20/0x200 are the zeros, 0x10/0x100 the subnormals.
FCLASS_S(SCRATCHF1, regs_.F(inst.src1));
MOVFR2GR_S(SCRATCH1, SCRATCHF1);
ANDI(SCRATCH1, SCRATCH1, 0x220);
ANDI(SCRATCH1, SCRATCH1, 0x330);
SLTUI(SCRATCH1, SCRATCH1, 1);
// Okay, it's zero if zero, 1 otherwise. Convert 1 to a constant 1.0.
// Probably non-zero is the common case, so we make that the straight line.
+2 -2
View File
@@ -164,9 +164,9 @@ void RiscVJitBackend::CompIR_FAssign(IRInst inst) {
case IROp::FSign:
{
regs_.Map(inst);
// Check if it's negative zero, either 0x10/0x08 is zero.
// Zero or a denormal signs as zero: 0x10/0x08 are the zeros, 0x20/0x04 the subnormals.
FCLASS(32, SCRATCH1, regs_.F(inst.src1));
ANDI(SCRATCH1, SCRATCH1, 0x18);
ANDI(SCRATCH1, SCRATCH1, 0x3C);
SEQZ(SCRATCH1, SCRATCH1);
// Okay, it's zero if zero, 1 otherwise. Convert 1 to a constant 1.0.
// Probably non-zero is the common case, so we make that the straight line.
+10 -2
View File
@@ -2072,9 +2072,17 @@ void Jit::Comp_Vsgn(MIPSOpcode op) {
// Would be nice with more temp regs here so we could put signBitLower and oneOneOneOne into regs...
for (int i = 0; i < n; ++i) {
XORPS(XMM0, R(XMM0));
CMPEQSS(XMM0, fpr.V(sregs[i])); // XMM0 = s[i] == 0.0f
// A denormal signs as zero like on the hardware, so test the exponent field, not the value.
MOVSS(XMM1, fpr.V(sregs[i]));
MOVAPS(tempxregs[i], R(XMM1));
if (RipAccessible(fourinfnan)) {
ANDPS(tempxregs[i], M(&fourinfnan)); // rip accessible
} else {
MOV(PTRBITS, R(TEMPREG), ImmPtr(&fourinfnan));
ANDPS(tempxregs[i], MatR(TEMPREG));
}
XORPS(XMM0, R(XMM0));
CMPEQSS(XMM0, R(tempxregs[i])); // XMM0 = exponent of s[i] == 0
// Preserve sign bit, replace rest with ones
if (RipAccessible(signBitLower)) {
ANDPS(XMM1, M(&signBitLower)); // rip accessible
+10 -4
View File
@@ -241,13 +241,19 @@ void X64JitBackend::CompIR_FAssign(IRInst inst) {
}
ORPS(tempReg, M(constants.positiveOnes)); // rip accessible
// Set dest = 0xFFFFFFFF if +0.0 or -0.0.
// Set dest = 0xFFFFFFFF if the exponent is zero: +0.0, -0.0 or a denormal, which the
// hardware also signs as zero.
if (inst.dest != inst.src1) {
XORPS(regs_.FX(inst.dest), regs_.F(inst.dest));
CMPPS(regs_.FX(inst.dest), regs_.F(inst.src1), CMP_EQ);
if (cpu_info.bAVX) {
VANDPS(128, regs_.FX(inst.dest), regs_.FX(inst.src1), M(constants.positiveInfinity)); // rip accessible
} else {
MOVAPS(regs_.FX(inst.dest), regs_.F(inst.src1));
ANDPS(regs_.FX(inst.dest), M(constants.positiveInfinity)); // rip accessible
}
} else {
CMPPS(regs_.FX(inst.dest), M(constants.positiveZeroes), CMP_EQ); // rip accessible
ANDPS(regs_.FX(inst.dest), M(constants.positiveInfinity)); // rip accessible
}
CMPPS(regs_.FX(inst.dest), M(constants.positiveZeroes), CMP_EQ); // rip accessible
// Now not the mask to keep zero if it was zero.
ANDNPS(regs_.FX(inst.dest), R(tempReg));