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..163398324d 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; } @@ -113,7 +116,7 @@ static u8 ReadMMIO_U8(MIPSState *mips, u32 addr) { static u16 ReadMMIO_U16(MIPSState *mips, u32 addr) { if (!Memory::IsKernelCodeAddress(mips->pc)) { Core_MemoryException(addr, 2, mips->pc, MemoryExceptionType::READ_WORD, "Kernel mode only"); - return (u8)UNKNOWN_MMIO_POISON; + return (u16)UNKNOWN_MMIO_POISON; } WARN_LOG(Log::CPU, "Unhandled MMIO Read16 at %08x", addr); return (u16)UNKNOWN_MMIO_POISON; @@ -122,7 +125,7 @@ static u16 ReadMMIO_U16(MIPSState *mips, u32 addr) { static u32 ReadMMIO_U32(MIPSState *mips, u32 addr) { if (!Memory::IsKernelCodeAddress(mips->pc)) { Core_MemoryException(addr, 4, mips->pc, MemoryExceptionType::READ_WORD, "Kernel mode only"); - return (u8)UNKNOWN_MMIO_POISON; + return UNKNOWN_MMIO_POISON; } if (GpioMMIO::IsGpioAddress(addr)) { return GpioMMIO::Read32(addr); @@ -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) { @@ -414,9 +420,10 @@ namespace MIPSInt { if (rt != 0) { if (!Memory::IsValid4AlignedAddress(addr)) { Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "ll"); - return; + R(rt) = 0; + } else { + R(rt) = Memory::ReadUnchecked_U32(addr); } - R(rt) = Memory::ReadUnchecked_U32(addr); } mips->llBit = 1; break; @@ -424,9 +431,11 @@ namespace MIPSInt { if (mips->llBit) { if (!Memory::IsValid4AlignedAddress(addr)) { Core_MemoryException(addr, 4, PC, MemoryExceptionType::WRITE_WORD, "sc"); - return; + } else { + Memory::WriteUnchecked_U32(R(rt), addr); } - Memory::WriteUnchecked_U32(R(rt), addr); + // Report success even if the store got dropped - reporting failure just makes + // the usual retry loop spin on the same bad address forever. if (rt != 0) { R(rt) = 1; } @@ -474,6 +483,11 @@ namespace MIPSInt { PC += 4; } + // On a bad access that's set to be ignored (the default), we mirror what + // Memory::ReadOrException_*/WriteOrException_* do, which is also what the JIT's slow + // path calls: loads produce zero, stores are dropped, and PC advances either way. + // Returning without advancing PC instead would just re-execute the same instruction forever. + // When the access is set to break, Core_MemoryException has already stopped the core. void Int_ITypeMem(MIPSState *mips, MIPSOpcode op) { int imm = (signed short)(op&0xFFFF); int rt = _RT; @@ -495,7 +509,8 @@ namespace MIPSInt { break; } Core_MemoryException(addr, 1, PC, MemoryExceptionType::READ_WORD, "lb"); - return; + R(rt) = 0; + break; } R(rt) = SignExtend8ToU32(Memory::ReadUnchecked_U8(addr)); break; //lb @@ -507,7 +522,8 @@ namespace MIPSInt { break; } Core_MemoryException(addr, 2, PC, MemoryExceptionType::READ_WORD, "lh"); - return; + R(rt) = 0; + break; } R(rt) = SignExtend16ToU32(Memory::ReadUnchecked_U16(addr)); break; //lh @@ -519,7 +535,8 @@ namespace MIPSInt { break; } Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "lw"); - return; + R(rt) = 0; + break; } R(rt) = Memory::ReadUnchecked_U32(addr); break; //lw @@ -531,7 +548,8 @@ namespace MIPSInt { break; } Core_MemoryException(addr, 1, PC, MemoryExceptionType::READ_WORD, "lbu"); - return; + R(rt) = 0; + break; } R(rt) = Memory::ReadUnchecked_U8(addr); break; //lbu @@ -543,7 +561,8 @@ namespace MIPSInt { break; } Core_MemoryException(addr, 2, PC, MemoryExceptionType::READ_WORD, "lhu"); - return; + R(rt) = 0; + break; } R(rt) = Memory::ReadUnchecked_U16(addr); break; //lhu @@ -555,7 +574,7 @@ namespace MIPSInt { break; } Core_MemoryException(addr, 1, PC, MemoryExceptionType::WRITE_WORD, "sb"); - return; + break; } Memory::WriteUnchecked_U8(R(rt), addr); break; //sb @@ -567,7 +586,7 @@ namespace MIPSInt { break; } Core_MemoryException(addr, 2, PC, MemoryExceptionType::WRITE_WORD, "sh"); - return; + break; } Memory::WriteUnchecked_U16(R(rt), addr); break; //sh @@ -579,7 +598,7 @@ namespace MIPSInt { break; } Core_MemoryException(addr, 4, PC, MemoryExceptionType::WRITE_WORD, "sw"); - return; + break; } Memory::WriteUnchecked_U32(R(rt), addr); break; //sw @@ -589,12 +608,13 @@ namespace MIPSInt { case 34: //lwl { // Not checking for alignment here - the actual read will be aligned. - if (!Memory::IsValidAddress(addr)) { + u32 mem = 0; + if (Memory::IsValidAddress(addr)) { + mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc); + } else { Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "lwl"); - return; } u32 shift = (addr & 3) * 8; - u32 mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc); u32 result = ( u32(R(rt)) & (0x00ffffff >> shift) ) | ( mem << (24 - shift) ); R(rt) = result; } @@ -603,12 +623,13 @@ namespace MIPSInt { case 38: //lwr { // Not checking for alignment here - the actual read will be aligned. - if (!Memory::IsValidAddress(addr)) { + u32 mem = 0; + if (Memory::IsValidAddress(addr)) { + mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc); + } else { Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "lwr"); - return; } u32 shift = (addr & 3) * 8; - u32 mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc); u32 regval = R(rt); u32 result = ( regval & (0xffffff00 << (24 - shift)) ) | ( mem >> shift ); R(rt) = result; @@ -620,7 +641,7 @@ namespace MIPSInt { // Not checking for alignment here - the actual read/write will be aligned. if (!Memory::IsValidAddress(addr)) { Core_MemoryException(addr, 4, PC, MemoryExceptionType::WRITE_WORD, "swl"); - return; + break; } u32 shift = (addr & 3) * 8; u32 mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc); @@ -634,7 +655,7 @@ namespace MIPSInt { // Not checking for alignment here - the actual read/write will be aligned. if (!Memory::IsValidAddress(addr)) { Core_MemoryException(addr, 4, PC, MemoryExceptionType::WRITE_WORD, "swr"); - return; + break; } u32 shift = (addr & 3) << 3; u32 mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc); @@ -660,14 +681,15 @@ namespace MIPSInt { case 49: if (!Memory::IsValid4AlignedAddress(addr)) { Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "lwc1"); - return; + FI(ft) = 0; + break; } FI(ft) = Memory::ReadUnchecked_U32(addr); break; //lwc1 case 57: if (!Memory::IsValid4AlignedAddress(addr)) { Core_MemoryException(addr, 4, PC, MemoryExceptionType::WRITE_WORD, "swc1"); - return; + break; } Memory::WriteUnchecked_U32(FI(ft), addr); break; //swc1 @@ -718,6 +740,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); @@ -1068,10 +1097,16 @@ namespace MIPSInt { break; case 0x4: //ins { - int size = (_SIZE + 1) - pos; - u32 sourcemask = 0xFFFFFFFFUL >> (32 - size); - u32 destmask = sourcemask << pos; - R(rt) = (R(rt) & ~destmask) | ((R(rs)&sourcemask) << pos); + // The size field actually holds msb (= pos + size - 1), so build the mask from + // that and shift it down, the way the JITs do. Computing the width as + // (_SIZE + 1) - pos instead would shift by 32 or more when msb < pos - undefined + // behavior, and on x86 it yields an all-ones mask that writes bits the JITs leave + // alone. Hardware calls that encoding unpredictable, so all we need is to be + // consistent and not invoke UB. + const u32 mask = 0xFFFFFFFFUL >> (31 - _SIZE); + const u32 sourcemask = mask >> pos; + const u32 destmask = sourcemask << pos; + R(rt) = (R(rt) & ~destmask) | ((R(rs) & sourcemask) << pos); } break; } @@ -1100,7 +1135,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 +1284,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/InterpreterVFPU.cpp b/Core/MIPS/InterpreterVFPU.cpp index d378158cf8..5947ba6dc5 100644 --- a/Core/MIPS/InterpreterVFPU.cpp +++ b/Core/MIPS/InterpreterVFPU.cpp @@ -216,23 +216,22 @@ namespace MIPSInt float d[4]; ReadVector(mips, d, V_Quad, vt); int offset = (addr >> 2) & 3; + const bool valid = Memory::IsValid4AlignedAddress(addr); if ((op & 2) == 0) { - if (!Memory::IsValid4AlignedAddress(addr)) { + if (!valid) { Core_MemoryException(addr, 16, PC, MemoryExceptionType::READ_WORD, "lvl.q"); - return; } // It's an LVL for (int i = 0; i < offset + 1; i++) { - d[3 - i] = Memory::ReadUnchecked_Float(addr - 4 * i); + d[3 - i] = valid ? Memory::ReadUnchecked_Float(addr - 4 * i) : 0.0f; } } else { - if (!Memory::IsValid4AlignedAddress(addr)) { + if (!valid) { Core_MemoryException(addr, 16, PC, MemoryExceptionType::READ_WORD, "lvr.q"); - return; } // It's an LVR for (int i = 0; i < (3 - offset) + 1; i++) { - d[i] = Memory::ReadUnchecked_Float(addr + 4 * i); + d[i] = valid ? Memory::ReadUnchecked_Float(addr + 4 * i) : 0.0f; } } WriteVector(mips, d, V_Quad, vt); @@ -240,14 +239,18 @@ namespace MIPSInt break; case 54: //lv.q + // A quadword access has to be 16-byte aligned, so a misaligned one is simply + // rejected - we don't try to carry it out anyway, same as every other path here. if ((addr & 0xF) || !Memory::IsValid4AlignedAddress(addr)) { Core_MemoryException(addr, 16, PC, MemoryExceptionType::READ_WORD, "lv.q"); + const float zero[4]{}; + WriteVector(mips, zero, V_Quad, vt); + break; } #ifndef COMMON_BIG_ENDIAN cf = reinterpret_cast(Memory::GetPointerUnchecked(addr)); - if (cf) - WriteVector(mips, cf, V_Quad, vt); + WriteVector(mips, cf, V_Quad, vt); #else float lvqd[4]; @@ -268,7 +271,7 @@ namespace MIPSInt if ((op & 2) == 0) { if (!Memory::IsValid4AlignedAddress(addr)) { Core_MemoryException(addr, 16, PC, MemoryExceptionType::WRITE_WORD, "svl.q"); - return; + break; } // It's an SVL for (int i = 0; i < offset + 1; i++) @@ -278,7 +281,7 @@ namespace MIPSInt } else { if (!Memory::IsValid4AlignedAddress(addr)) { Core_MemoryException(addr, 16, PC, MemoryExceptionType::WRITE_WORD, "svr.q"); - return; + break; } // It's an SVR for (int i = 0; i < (3 - offset) + 1; i++) { @@ -289,8 +292,10 @@ namespace MIPSInt } case 62: //sv.q + // See lv.q above. if ((addr & 0xF) || !Memory::IsValid4AlignedAddress(addr)) { Core_MemoryException(addr, 16, PC, MemoryExceptionType::WRITE_WORD, "sv.q"); + break; } #ifndef COMMON_BIG_ENDIAN f = reinterpret_cast(Memory::GetPointerWriteUnchecked(addr)); @@ -1642,7 +1647,8 @@ namespace MIPSInt } // D prefix works, just not for the cosine lane. - uint32_t dprefixRemove = (3 << cosineLane) | (1 << (8 + cosineLane)); + // The saturation field is two bits per element (see ApplyPrefixD), the mask field one. + uint32_t dprefixRemove = (3 << (cosineLane * 2)) | (1 << (8 + cosineLane)); mips->vfpuCtrl[VFPU_CTRL_DPREFIX] &= 0xFFFFF ^ dprefixRemove; ApplyPrefixD(mips, d, sz); WriteVector(mips, d, sz, vd); @@ -1757,14 +1763,15 @@ namespace MIPSInt case 50: //lv.s if (!Memory::IsValid4AlignedAddress(addr)) { Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "lv.s"); - return; + VI(vt) = 0; + break; } VI(vt) = Memory::ReadUnchecked_U32(addr); break; case 58: //sv.s if (!Memory::IsValid4AlignedAddress(addr)) { Core_MemoryException(addr, 4, PC, MemoryExceptionType::WRITE_WORD, "sv.s"); - return; + break; } Memory::WriteUnchecked_U32(VI(vt), addr); break; 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;