mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
Merge pull request #22150 from hrydgard/interpreter-review
Claude review of the interpreter
This commit is contained in:
7 files changed
+190
-128
No files matched your search
@@ -3,10 +3,6 @@
|
||||
|
||||
#include "ppsspp_config.h"
|
||||
|
||||
#if PPSSPP_PLATFORM(WINDOWS) && PPSSPP_ARCH(ARM64)
|
||||
#include <arm64intr.h>
|
||||
#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
|
||||
|
||||
@@ -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 <uint32_t alignment>
|
||||
u32 RunValidateAddress(u32 pc, u32 addr, u32 isWrite) {
|
||||
static_assert(alignment <= 16);
|
||||
|
||||
+69
-29
@@ -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.
|
||||
|
||||
@@ -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<const float *>(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<float *>(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;
|
||||
|
||||
@@ -20,8 +20,14 @@
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
#include "ppsspp_config.h"
|
||||
|
||||
#if PPSSPP_PLATFORM(WINDOWS) && PPSSPP_ARCH(ARM64)
|
||||
#include <arm64intr.h>
|
||||
#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];
|
||||
|
||||
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user