diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index 27465a1616..7a6a764951 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -3,10 +3,6 @@ #include "ppsspp_config.h" -#if PPSSPP_PLATFORM(WINDOWS) && PPSSPP_ARCH(ARM64) -#include -#endif - #include "Common/BitSet.h" #include "Common/BitScan.h" #include "Common/Common.h" @@ -28,32 +24,6 @@ #include "Core/System.h" #include "Core/MIPS/MIPSTracer.h" -#if PPSSPP_ARCH(ARM64) - -// TODO: This should be put in some common header. -static inline u64 ARM64ReadFPCR() { -#if PPSSPP_PLATFORM(WINDOWS) - return _ReadStatusReg(ARM64_FPCR); -#else - // TODO: Try __builtin_arm_get_fpcr() - u64 fpcr; // not really 64-bit, just to match the register size. - asm volatile ("mrs %0, fpcr" : "=r" (fpcr)); - return fpcr; -#endif -} - -static inline void ARM64WriteFPCR(u64 fpcr) { -#if PPSSPP_PLATFORM(WINDOWS) - _WriteStatusReg(ARM64_FPCR, fpcr); -#else - // TODO: Try __builtin_arm_set_fpcr() - // Write back the modified FPCR - asm volatile ("msr fpcr, %0" : : "r" (fpcr)); -#endif -} - -#endif - #ifdef mips // Why do MIPS compilers define something so generic? Try to keep defined, at least... #undef mips @@ -110,57 +80,6 @@ u32 IRRunMemCheck(u32 pc, u32 addr) { return coreState != CORE_RUNNING_CPU ? 1 : 0; } -void IRApplyRounding(MIPSState *mips) { - u32 fcr1Bits = mips->fcr31 & 0x01000003; - // If these are 0, we just leave things as they are. - if (fcr1Bits) { - 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. - if (rmode & 1) { - rmode ^= 2; - } - csr |= rmode << 13; - - if (ftz) { - // Flush to zero - csr |= 0x8000; - } - _mm_setcsr(csr); -#elif PPSSPP_ARCH(ARM64) - u64 fpcr = ARM64ReadFPCR(); - // Translate MIPS to ARM rounding mode - static const u8 lookup[4] = {0, 3, 1, 2}; - - fpcr &= ~(3 << 22); // Clear bits [23:22] - fpcr |= ((u64)lookup[rmode] << 22); - - if (ftz) { - fpcr |= 1 << 24; - } - - ARM64WriteFPCR(fpcr); -#endif - } -} - -void IRRestoreRounding() { -#if PPSSPP_ARCH(SSE2) - // 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 = ARM64ReadFPCR(); // not really 64-bit, just to match the regsiter size. - fpcr &= ~(7 << 22); // Clear bits [23:22] for rounding, 24 for FTZ - // Write back the modified FPCR - ARM64WriteFPCR(fpcr); -#endif -} - u32 IRInterpret(MIPSState *mips, const IRInst *inst) { while (true) { switch (inst->op) { @@ -1257,10 +1176,10 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { break; case IROp::ApplyRoundingMode: - IRApplyRounding(mips); + ApplyHostRoundingMode(mips); break; case IROp::RestoreRoundingMode: - IRRestoreRounding(); + RestoreHostRoundingMode(); break; case IROp::UpdateRoundingMode: // TODO: Implement diff --git a/Core/MIPS/IR/IRInterpreter.h b/Core/MIPS/IR/IRInterpreter.h index 85e3795069..f555552f49 100644 --- a/Core/MIPS/IR/IRInterpreter.h +++ b/Core/MIPS/IR/IRInterpreter.h @@ -11,9 +11,6 @@ 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) { static_assert(alignment <= 16); diff --git a/Core/MIPS/Interpreter.cpp b/Core/MIPS/Interpreter.cpp index 8c8af8c931..7fd2be1d70 100644 --- a/Core/MIPS/Interpreter.cpp +++ b/Core/MIPS/Interpreter.cpp @@ -82,6 +82,8 @@ int MIPS_InterpretSingleStep(MIPSState *mips) { return 0; } MIPSOpcode op = Memory::Read_Opcode_JIT(mips->pc); // now unchecked + // Same reason as the run loop in MIPSInterpret_RunUntil - see ApplyHostRoundingMode. + ApplyHostRoundingMode(mips); if (mips->inDelaySlot) { MIPSInterpret(mips, op); if (mips->inDelaySlot) { @@ -91,6 +93,7 @@ int MIPS_InterpretSingleStep(MIPSState *mips) { } else { MIPSInterpret(mips, op); } + RestoreHostRoundingMode(); return 1; } @@ -235,7 +238,10 @@ namespace MIPSInt { mips->pc += 4; } mips->inDelaySlot = false; + // HLE code is host code - it must not run under the guest's rounding mode. + RestoreHostRoundingMode(); CallSyscallWithPC(op, syscallPC); + ApplyHostRoundingMode(mips); } void Int_Sync(MIPSState *mips, MIPSOpcode op) { @@ -718,6 +724,13 @@ namespace MIPSInt { if (MIPSComp::jit) { // In case of DISABLE, we need to tell jit we updated FCR31. MIPSComp::jit->UpdateFCR31(); + } else { + // The interpreter emulates the rounding mode by putting the host FPU in it, + // so it has to switch right here rather than at the next block boundary. + // Restore first: the new value may be back to the default, which Apply + // deliberately doesn't write. + RestoreHostRoundingMode(); + ApplyHostRoundingMode(mips); } } else { WARN_LOG_REPORT(Log::CPU, "WriteFCR: Unexpected reg %d (value %08x)", fs, value); @@ -1100,7 +1113,9 @@ namespace MIPSInt { } switch (op & 0x3f) { - case 12: FsI(fd) = (int)floorf(F(fs)+0.5f); break; //round.w.s + // round.w.s is round-half-to-even, not half-away-from-zero - and its mode is fixed, + // so unlike cvt.w.s below it must not follow fcr31. round_ieee_754 is both. + case 12: FsI(fd) = (int)round_ieee_754(F(fs)); break; //round.w.s case 13: //trunc.w.s if (F(fs) >= 0.0f) { FsI(fd) = (int)floorf(F(fs)); @@ -1247,7 +1262,10 @@ namespace MIPSInt { int index = op.encoding & 0xFFFFFF; const ReplacementTableEntry *entry = GetReplacementFunc(index); if (entry && entry->replaceFunc && (entry->flags & REPFLAG_DISABLED) == 0) { + // Like a syscall, a replacement function is host code - see Int_Syscall. + RestoreHostRoundingMode(); int cycles = entry->replaceFunc(); + ApplyHostRoundingMode(mips); if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) { // Interpret the original instruction under the hook. diff --git a/Core/MIPS/MIPS.cpp b/Core/MIPS/MIPS.cpp index c733131cad..8ba3733d0d 100644 --- a/Core/MIPS/MIPS.cpp +++ b/Core/MIPS/MIPS.cpp @@ -20,8 +20,14 @@ #include #include +#include "ppsspp_config.h" + +#if PPSSPP_PLATFORM(WINDOWS) && PPSSPP_ARCH(ARM64) +#include +#endif #include "Common/CommonTypes.h" +#include "Common/Math/SIMDHeaders.h" #include "Common/Serialize/Serializer.h" #include "Common/Serialize/SerializeFuncs.h" #include "Core/ConfigValues.h" @@ -42,6 +48,82 @@ MIPSState *currentMIPS = &mipsr4k; MIPSDebugInterface debugr4k(&mipsr4k); MIPSDebugInterface *currentDebugMIPS = &debugr4k; +#if PPSSPP_ARCH(ARM64) + +static inline u64 ARM64ReadFPCR() { +#if PPSSPP_PLATFORM(WINDOWS) + return _ReadStatusReg(ARM64_FPCR); +#else + // TODO: Try __builtin_arm_get_fpcr() + u64 fpcr; // not really 64-bit, just to match the register size. + asm volatile ("mrs %0, fpcr" : "=r" (fpcr)); + return fpcr; +#endif +} + +static inline void ARM64WriteFPCR(u64 fpcr) { +#if PPSSPP_PLATFORM(WINDOWS) + _WriteStatusReg(ARM64_FPCR, fpcr); +#else + // TODO: Try __builtin_arm_set_fpcr() + // Write back the modified FPCR + asm volatile ("msr fpcr, %0" : : "r" (fpcr)); +#endif +} + +#endif + +void ApplyHostRoundingMode(const MIPSState *mips) { + u32 fcr1Bits = mips->fcr31 & 0x01000003; + // If these are 0, we just leave things as they are. + if (fcr1Bits) { + 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. + if (rmode & 1) { + rmode ^= 2; + } + csr |= rmode << 13; + + if (ftz) { + // Flush to zero + csr |= 0x8000; + } + _mm_setcsr(csr); +#elif PPSSPP_ARCH(ARM64) + u64 fpcr = ARM64ReadFPCR(); + // Translate MIPS to ARM rounding mode + static const u8 lookup[4] = {0, 3, 1, 2}; + + fpcr &= ~(3 << 22); // Clear bits [23:22] + fpcr |= ((u64)lookup[rmode] << 22); + + if (ftz) { + fpcr |= 1 << 24; + } + + ARM64WriteFPCR(fpcr); +#endif + } +} + +void RestoreHostRoundingMode() { + // 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. +#if PPSSPP_ARCH(SSE2) + u32 csr = _mm_getcsr(); + csr &= ~(7 << 13); + _mm_setcsr(csr); +#elif PPSSPP_ARCH(ARM64) + u64 fpcr = ARM64ReadFPCR(); // not really 64-bit, just to match the register size. + fpcr &= ~(7 << 22); // Clear bits [23:22] for rounding, 24 for FTZ + // Write back the modified FPCR + ARM64WriteFPCR(fpcr); +#endif +} + u8 voffset[128]; u8 fromvoffset[128]; diff --git a/Core/MIPS/MIPS.h b/Core/MIPS/MIPS.h index 9ae7dd87ca..2c5cc2847e 100644 --- a/Core/MIPS/MIPS.h +++ b/Core/MIPS/MIPS.h @@ -293,3 +293,12 @@ extern MIPSDebugInterface *currentDebugMIPS; extern MIPSState mipsr4k; extern const float cst_constants[32]; + +// The guest's rounding mode and flush-to-zero flag (fcr31 bits 0-1 and 24) are emulated by putting +// the *host* FPU into the matching mode, since we do the arithmetic with plain host float ops. +// That mode must not be left on while running anything that isn't emulating a guest instruction - +// HLE syscalls, replacement functions, the GPU - so an emulation loop applies it on entry and +// restores it before calling out, the way the JITs do (see Jit::ApplyRoundingMode). +// Apply is cheap when the guest is in the default mode (by far the common case): it does nothing. +void ApplyHostRoundingMode(const MIPSState *mips); +void RestoreHostRoundingMode(); diff --git a/Core/MIPS/MIPSTables.cpp b/Core/MIPS/MIPSTables.cpp index f84b4b3fe1..0ad08e5556 100644 --- a/Core/MIPS/MIPSTables.cpp +++ b/Core/MIPS/MIPSTables.cpp @@ -1272,6 +1272,12 @@ int MIPSInterpret_RunUntil(MIPSState *mips, u64 globalTicks) { while (coreState == CORE_RUNNING_CPU) { CoreTiming::Advance(mips); + // The emulated instructions below do their float math with plain host float ops, so the + // host FPU has to be in the guest's rounding mode while they run - and back in the normal + // one whenever we're not running them, which is what the JITs do too. Calls out from + // inside the run loops (syscalls, replacement functions) restore it themselves. + ApplyHostRoundingMode(mips); + uint64_t ticksLeft = globalTicks - CoreTiming::GetTicks(mips); if (g_breakpoints.HasBreakPoints() || g_breakpoints.HasMemChecks() || g_breakpoints.HasRegBreakpoints() || ticksLeft <= mips->downcount) { RunUntilDowncountZeroWithChecks(mips, globalTicks); @@ -1279,6 +1285,8 @@ int MIPSInterpret_RunUntil(MIPSState *mips, u64 globalTicks) { RunUntilDowncountZeroFast(mips); } + RestoreHostRoundingMode(); + if (CoreTiming::GetTicks(mips) > globalTicks) { // DEBUG_LOG(Log::CPU, "Hit the max ticks, bailing 1 : %llu, %llu", globalTicks, CoreTiming::GetTicks(mips)); return 1;