diff --git a/Core/MIPS/ARM64/Arm64IRCompFPU.cpp b/Core/MIPS/ARM64/Arm64IRCompFPU.cpp index 38bbda5194..01fdeed9e7 100644 --- a/Core/MIPS/ARM64/Arm64IRCompFPU.cpp +++ b/Core/MIPS/ARM64/Arm64IRCompFPU.cpp @@ -20,6 +20,7 @@ #if PPSSPP_ARCH(ARM64) || (PPSSPP_PLATFORM(WINDOWS) && !defined(__LIBRETRO__)) #ifndef offsetof +#include #include #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: diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index c11405ed6a..e9bde15ce5 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -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; diff --git a/Core/MIPS/InterpreterVFPU.cpp b/Core/MIPS/InterpreterVFPU.cpp index 3012731a2b..d944c7c256 100644 --- a/Core/MIPS/InterpreterVFPU.cpp +++ b/Core/MIPS/InterpreterVFPU.cpp @@ -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; diff --git a/Core/MIPS/LoongArch64/LoongArch64CompFPU.cpp b/Core/MIPS/LoongArch64/LoongArch64CompFPU.cpp index 7fae145e9e..7839072816 100644 --- a/Core/MIPS/LoongArch64/LoongArch64CompFPU.cpp +++ b/Core/MIPS/LoongArch64/LoongArch64CompFPU.cpp @@ -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. diff --git a/Core/MIPS/RiscV/RiscVCompFPU.cpp b/Core/MIPS/RiscV/RiscVCompFPU.cpp index 01f58e1374..f4a967df5d 100644 --- a/Core/MIPS/RiscV/RiscVCompFPU.cpp +++ b/Core/MIPS/RiscV/RiscVCompFPU.cpp @@ -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. diff --git a/Core/MIPS/x86/CompVFPU.cpp b/Core/MIPS/x86/CompVFPU.cpp index 4a54eeea65..b20d6e3d5d 100644 --- a/Core/MIPS/x86/CompVFPU.cpp +++ b/Core/MIPS/x86/CompVFPU.cpp @@ -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 diff --git a/Core/MIPS/x86/X64IRCompFPU.cpp b/Core/MIPS/x86/X64IRCompFPU.cpp index 65076f6d7d..1da0535e3f 100644 --- a/Core/MIPS/x86/X64IRCompFPU.cpp +++ b/Core/MIPS/x86/X64IRCompFPU.cpp @@ -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));