From 0080f71ca430fe2b3e92b3bef4fc85206e63cfd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 19 Jun 2024 18:09:38 +0200 Subject: [PATCH 1/3] Implement FPU rounding mode support in the IR interpreter for x86/x64 --- Core/HLE/sceKernel.cpp | 2 +- Core/MIPS/IR/IRCompFPU.cpp | 1 + Core/MIPS/IR/IRFrontend.cpp | 2 -- Core/MIPS/IR/IRInterpreter.cpp | 39 ++++++++++++++++++++++++++++++++-- Core/MIPS/IR/IRInterpreter.h | 3 +++ 5 files changed, 42 insertions(+), 5 deletions(-) diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp index d4da98bf42..e0427b9ec8 100644 --- a/Core/HLE/sceKernel.cpp +++ b/Core/HLE/sceKernel.cpp @@ -322,7 +322,7 @@ void sceKernelExitGame() __KernelSwitchOffThread("game exited"); Core_Stop(); - g_OSD.Show(OSDType::MESSAGE_INFO, "sceKernelExitGame()"); + g_OSD.Show(OSDType::MESSAGE_INFO, "sceKernelExitGame()", 0.0f, "kernelexit"); } void sceKernelExitGameWithStatus() diff --git a/Core/MIPS/IR/IRCompFPU.cpp b/Core/MIPS/IR/IRCompFPU.cpp index e83f3a709f..ca603199ea 100644 --- a/Core/MIPS/IR/IRCompFPU.cpp +++ b/Core/MIPS/IR/IRCompFPU.cpp @@ -223,6 +223,7 @@ void IRFrontend::Comp_mxc1(MIPSOpcode op) { // Set rounding mode RestoreRoundingMode(); ir.Write(IROp::FpCtrlFromReg, 0, rt); + // TODO: Do the UpdateRoundingMode check at runtime? UpdateRoundingMode(); ApplyRoundingMode(); } else { diff --git a/Core/MIPS/IR/IRFrontend.cpp b/Core/MIPS/IR/IRFrontend.cpp index 4af6d9a753..f299ba9cae 100644 --- a/Core/MIPS/IR/IRFrontend.cpp +++ b/Core/MIPS/IR/IRFrontend.cpp @@ -133,11 +133,9 @@ bool IRFrontend::CheckRounding(u32 blockAddress) { js.startDefaultPrefix = false; cleanSlate = true; } - return cleanSlate; } - void IRFrontend::Comp_ReplacementFunc(MIPSOpcode op) { int index = op.encoding & MIPS_EMUHACK_VALUE_MASK; diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index 4d46983c97..bbabadefa1 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -89,6 +89,37 @@ u32 IRRunMemCheck(u32 pc, u32 addr) { return coreState != CORE_RUNNING ? 1 : 0; } +void IRApplyRounding(MIPSState *mips) { + u32 fcr1Bits = mips->fcr31 & 0x01000003; + // If these are 0, we just leave things as they are. + if (fcr1Bits) { +#if PPSSPP_ARCH(SSE2) && 0 + u32 csr = _mm_getcsr() & ~0x6000; + // Translate the rounding mode bits to X86, the same way as in Asm.cpp. + int rnd = fcr1Bits & 3; + if (rnd & 1) { + rnd ^= 2; + } + csr |= rnd << 13; + + if (fcr1Bits & 0x01000000) { + // Flush to zero + csr |= 0x8000; + } + _mm_setcsr(csr); +#endif + } +} + +void IRRestoreRounding() { +#if PPSSPP_ARCH(SSE2) + // We should avoid this if we didn't apply rounding in the first place. + u32 csr = _mm_getcsr(); + csr &= ~(7 << 13); + _mm_setcsr(csr); +#endif +} + // We cannot use NEON on ARM32 here until we make it a hard dependency. We can, however, on ARM64. u32 IRInterpret(MIPSState *mips, const IRInst *inst) { while (true) { @@ -565,9 +596,11 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { } break; - // Not quickly implementable on all platforms, unfortunately. case IROp::Vec4Dot: { + // Not quickly implementable on all platforms, unfortunately. + // Though, this is still pretty fast compared to one split into multiple IR instructions. + // This might be good though: https://stackoverflow.com/a/17004629 float dot = mips->f[inst->src1] * mips->f[inst->src2]; for (int i = 1; i < 4; i++) dot += mips->f[inst->src1 + i] * mips->f[inst->src2 + i]; @@ -826,9 +859,9 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { mips->f[inst->dest] = vfpu_clamp(mips->f[inst->src1], -1.0f, 1.0f); break; - // Bitwise trickery case IROp::FSign: { + // Bitwise trickery u32 val; memcpy(&val, &mips->f[inst->src1], sizeof(u32)); if (val == 0 || val == 0x80000000) @@ -1097,9 +1130,11 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { break; case IROp::ApplyRoundingMode: + IRApplyRounding(mips); // TODO: Implement break; case IROp::RestoreRoundingMode: + IRRestoreRounding(); // TODO: Implement break; case IROp::UpdateRoundingMode: diff --git a/Core/MIPS/IR/IRInterpreter.h b/Core/MIPS/IR/IRInterpreter.h index 6f02ec9482..0939e982d0 100644 --- a/Core/MIPS/IR/IRInterpreter.h +++ b/Core/MIPS/IR/IRInterpreter.h @@ -11,6 +11,9 @@ u32 IRRunBreakpoint(u32 pc); u32 IRRunMemCheck(u32 pc, u32 addr); u32 IRInterpret(MIPSState *ms, const IRInst *inst); +void IRApplyRounding(); +void IRRestoreRounding(); + template u32 RunValidateAddress(u32 pc, u32 addr, u32 isWrite) { const auto toss = [&](MemoryExceptionType t) { From e64d768113569fff616cdd8396874ebfd8cacc16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 19 Jun 2024 20:00:36 +0200 Subject: [PATCH 2/3] Implement the same for ARM64 --- Core/MIPS/IR/IRInterpreter.cpp | 44 +++++++++++++++++++++++++++------- android/jni/TestRunner.cpp | 4 ++++ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index bbabadefa1..c1e751379f 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -93,30 +93,57 @@ void IRApplyRounding(MIPSState *mips) { u32 fcr1Bits = mips->fcr31 & 0x01000003; // If these are 0, we just leave things as they are. if (fcr1Bits) { -#if PPSSPP_ARCH(SSE2) && 0 + int rmode = fcr1Bits & 3; + bool ftz = (fcr1Bits & 0x01000000) != 0; +#if PPSSPP_ARCH(SSE2) u32 csr = _mm_getcsr() & ~0x6000; // Translate the rounding mode bits to X86, the same way as in Asm.cpp. - int rnd = fcr1Bits & 3; - if (rnd & 1) { - rnd ^= 2; + if (rmode & 1) { + rmode ^= 2; } - csr |= rnd << 13; + csr |= rmode << 13; - if (fcr1Bits & 0x01000000) { + if (ftz) { // Flush to zero csr |= 0x8000; } _mm_setcsr(csr); +#elif PPSSPP_ARCH(ARM64) + // On ARM64 we need to use inline assembly for a portable solution. + // Note that in the JIT, for fcvts, we use specific conversions. However, + // fcvts also has a variant that reads the fpcr, and I think that's what our + // C++ code (the IR interpreter) ends up using. + u64 fpcr; // not really 64-bit, just to match the regsiter size. + asm volatile ("mrs %0, fpcr" : "=r" (fpcr)); + + // Translate MIPS to ARM rounding mode + static const u8 lookup[4] = {0, 3, 1, 2}; + + fpcr &= ~(3 << 22); // Clear bits [23:22] + fpcr |= (lookup[rmode] << 22); + + if (ftz) { + fpcr |= 1 << 24; + } + // Write back the modified FPCR + asm volatile ("msr fpcr, %0" : : "r" (fpcr)); #endif } } void IRRestoreRounding() { #if PPSSPP_ARCH(SSE2) - // We should avoid this if we didn't apply rounding in the first place. + // TODO: We should avoid this if we didn't apply rounding in the first place. + // In the meantime, clear out FTZ and rounding mode bits. u32 csr = _mm_getcsr(); csr &= ~(7 << 13); _mm_setcsr(csr); +#elif PPSSPP_ARCH(ARM64) + u64 fpcr; // not really 64-bit, just to match the regsiter size. + asm volatile ("mrs %0, fpcr" : "=r" (fpcr)); + fpcr &= ~(7 << 22); // Clear bits [23:22] for rounding, 24 for FTZ + // Write back the modified FPCR + asm volatile ("msr fpcr, %0" : : "r" (fpcr)); #endif } @@ -989,6 +1016,7 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { mips->fs[inst->dest] = my_isinf(src) && src < 0.0f ? -2147483648LL : 2147483647LL; break; } + // TODO: Inline assembly to use here would be better. switch (IRRoundMode(mips->fcr31 & 3)) { case IRRoundMode::RINT_0: mips->fs[inst->dest] = (int)round_ieee_754(src); break; case IRRoundMode::CAST_1: mips->fs[inst->dest] = (int)src; break; @@ -1131,11 +1159,9 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { case IROp::ApplyRoundingMode: IRApplyRounding(mips); - // TODO: Implement break; case IROp::RestoreRoundingMode: IRRestoreRounding(); - // TODO: Implement break; case IROp::UpdateRoundingMode: // TODO: Implement diff --git a/android/jni/TestRunner.cpp b/android/jni/TestRunner.cpp index 691cf2e3cd..3fc0059df7 100644 --- a/android/jni/TestRunner.cpp +++ b/android/jni/TestRunner.cpp @@ -66,6 +66,8 @@ bool TestsAvailable() { // Hack to easily run the tests on Windows from the submodule if (File::IsDirectory(Path("../pspautotests"))) { testDirectory = Path(".."); + } else if (File::IsDirectory(Path("pspautotests"))) { + testDirectory = Path("."); } return File::Exists(testDirectory / "pspautotests" / "tests"); } @@ -80,6 +82,8 @@ bool RunTests() { // Hack to easily run the tests on Windows from the submodule if (File::IsDirectory(Path("../pspautotests"))) { baseDirectory = Path(".."); + } else if (File::IsDirectory(Path("pspautotests"))) { + baseDirectory = Path("."); } #endif From 06e636bfdc572f4da2fdd122b9ac1b259b0a246d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 19 Jun 2024 20:24:59 +0200 Subject: [PATCH 3/3] Build and comment fixes --- Core/MIPS/IR/IRInterpreter.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index c1e751379f..08d13a0eee 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -108,12 +108,12 @@ void IRApplyRounding(MIPSState *mips) { csr |= 0x8000; } _mm_setcsr(csr); -#elif PPSSPP_ARCH(ARM64) +#elif PPSSPP_ARCH(ARM64) && !PPSSPP_PLATFORM(WINDOWS) // On ARM64 we need to use inline assembly for a portable solution. - // Note that in the JIT, for fcvts, we use specific conversions. However, - // fcvts also has a variant that reads the fpcr, and I think that's what our - // C++ code (the IR interpreter) ends up using. - u64 fpcr; // not really 64-bit, just to match the regsiter size. + // Unfortunately we don't have this possibility on Windows with MSVC, so ifdeffed out above. + // Note that in the JIT, for fcvts, we use specific conversions. We could use the FCVTS variants + // directly through inline assembly. + u64 fpcr; // not really 64-bit, just to match the register size. asm volatile ("mrs %0, fpcr" : "=r" (fpcr)); // Translate MIPS to ARM rounding mode @@ -138,7 +138,7 @@ void IRRestoreRounding() { u32 csr = _mm_getcsr(); csr &= ~(7 << 13); _mm_setcsr(csr); -#elif PPSSPP_ARCH(ARM64) +#elif PPSSPP_ARCH(ARM64) && !PPSSPP_PLATFORM(WINDOWS) u64 fpcr; // not really 64-bit, just to match the regsiter size. asm volatile ("mrs %0, fpcr" : "=r" (fpcr)); fpcr &= ~(7 << 22); // Clear bits [23:22] for rounding, 24 for FTZ