mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-21 02:09:05 +02:00
The COP0 register file was a static array in the interpreter, shared by anything that ran kernel code and lost on a savestate load. It's now part of MIPSState and saved with it (MIPSState section version 5). eret returns to EPC, or to ErrorEPC with Status.ERL set, as MIPS32 defines it; that isn't checked against a PSP yet. It and halt carry a new ENDS_BLOCK flag: the IR frontend ends the block after them and exits to whatever PC the interpreter left, and the interpreter's IR-matching block timing stops there too. TestCPUEret runs it on the interpreter, IR interpreter and JIT. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
570 lines
15 KiB
C++
570 lines
15 KiB
C++
// Copyright (c) 2012- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
#include <cfenv>
|
|
#include <cmath>
|
|
#include <limits>
|
|
#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"
|
|
#include "Core/MIPS/MIPS.h"
|
|
#include "Core/MIPS/Interpreter.h"
|
|
#include "Core/MIPS/MIPSTables.h"
|
|
#include "Core/MIPS/MIPSDebugInterface.h"
|
|
#include "Core/MIPS/MIPSVFPUUtils.h"
|
|
#include "Core/MIPS/IR/IRJit.h"
|
|
#include "Core/Reporting.h"
|
|
#include "Core/Core.h"
|
|
#include "Core/System.h"
|
|
#include "Core/MIPS/JitCommon/JitCommon.h"
|
|
#include "Core/CoreTiming.h"
|
|
#include "Core/HLE/ReplaceTables.h"
|
|
|
|
MIPSState mipsr4k;
|
|
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
|
|
}
|
|
|
|
#elif PPSSPP_ARCH(ARM) && !defined(_MSC_VER)
|
|
|
|
// FPSCR has the rounding mode and flush-to-zero at the same bits as ARM64's FPCR.
|
|
#define ARM32_FPSCR 1
|
|
|
|
static inline u32 ARM32ReadFPSCR() {
|
|
u32 fpscr;
|
|
asm volatile ("vmrs %0, fpscr" : "=r" (fpscr));
|
|
return fpscr;
|
|
}
|
|
|
|
static inline void ARM32WriteFPSCR(u32 fpscr) {
|
|
asm volatile ("vmsr fpscr, %0" : : "r" (fpscr));
|
|
}
|
|
|
|
#endif
|
|
|
|
// The host's own mode, saved by the first apply and written back by restore, so restore doesn't have
|
|
// to read the register. Our bits are cleared in it, in case a JIT's asm stub had already set them.
|
|
// Only the CPU thread calls these.
|
|
static bool g_hostModeSaved = false;
|
|
#if PPSSPP_ARCH(SSE2)
|
|
static u32 g_savedHostMode;
|
|
#elif PPSSPP_ARCH(ARM64)
|
|
static u64 g_savedHostMode;
|
|
#elif defined(ARM32_FPSCR)
|
|
static u32 g_savedHostMode;
|
|
#endif
|
|
|
|
void ApplyHostRoundingMode(const MIPSState *mips) {
|
|
u32 fcr1Bits = mips->fcr31 & 0x01000003;
|
|
if (!fcr1Bits) {
|
|
// The default: the host's own mode is the right one.
|
|
RestoreHostRoundingMode();
|
|
return;
|
|
}
|
|
int rmode = fcr1Bits & 3;
|
|
bool ftz = (fcr1Bits & 0x01000000) != 0;
|
|
#if PPSSPP_ARCH(SSE2)
|
|
if (!g_hostModeSaved) {
|
|
g_savedHostMode = _mm_getcsr() & ~0xE040;
|
|
g_hostModeSaved = true;
|
|
}
|
|
u32 csr = g_savedHostMode;
|
|
// Translate the rounding mode bits to X86, the same way as in Asm.cpp.
|
|
if (rmode & 1) {
|
|
rmode ^= 2;
|
|
}
|
|
csr |= rmode << 13;
|
|
if (ftz) {
|
|
// FTZ and DAZ together, which flushes like ARM64's FZ: inputs as well as results.
|
|
csr |= 0x8040;
|
|
}
|
|
_mm_setcsr(csr);
|
|
#elif PPSSPP_ARCH(ARM64) || defined(ARM32_FPSCR)
|
|
if (!g_hostModeSaved) {
|
|
#if PPSSPP_ARCH(ARM64)
|
|
g_savedHostMode = ARM64ReadFPCR() & ~(7 << 22);
|
|
#else
|
|
g_savedHostMode = ARM32ReadFPSCR() & ~(7 << 22);
|
|
#endif
|
|
g_hostModeSaved = true;
|
|
}
|
|
// Translate MIPS to ARM rounding mode. FPSCR has them at the same bits as FPCR.
|
|
static const u8 lookup[4] = {0, 3, 1, 2};
|
|
auto mode = g_savedHostMode | ((decltype(g_savedHostMode))lookup[rmode] << 22);
|
|
if (ftz) {
|
|
mode |= 1 << 24;
|
|
}
|
|
#if PPSSPP_ARCH(ARM64)
|
|
ARM64WriteFPCR(mode);
|
|
#else
|
|
ARM32WriteFPSCR(mode);
|
|
#endif
|
|
#else
|
|
// No control register access written for this architecture, so go through the standard
|
|
// call. Rounding is all it can do: there is no portable flush-to-zero, and neither
|
|
// riscv64 nor loongarch64 has one in the base ISA either, so denormals stay as they are.
|
|
static const int roundLookup[4] = { FE_TONEAREST, FE_TOWARDZERO, FE_UPWARD, FE_DOWNWARD };
|
|
fesetround(roundLookup[rmode]);
|
|
g_hostModeSaved = true;
|
|
(void)ftz;
|
|
#endif
|
|
}
|
|
|
|
void RestoreHostRoundingMode(bool force) {
|
|
if (!g_hostModeSaved) {
|
|
if (force) {
|
|
#if PPSSPP_ARCH(SSE2)
|
|
_mm_setcsr(_mm_getcsr() & ~0xE040);
|
|
#elif PPSSPP_ARCH(ARM64)
|
|
ARM64WriteFPCR(ARM64ReadFPCR() & ~(7 << 22));
|
|
#elif defined(ARM32_FPSCR)
|
|
ARM32WriteFPSCR(ARM32ReadFPSCR() & ~(7 << 22));
|
|
#else
|
|
fesetround(FE_TONEAREST);
|
|
#endif
|
|
}
|
|
return;
|
|
}
|
|
g_hostModeSaved = false;
|
|
#if PPSSPP_ARCH(SSE2)
|
|
_mm_setcsr(g_savedHostMode);
|
|
#elif PPSSPP_ARCH(ARM64)
|
|
ARM64WriteFPCR(g_savedHostMode);
|
|
#elif defined(ARM32_FPSCR)
|
|
ARM32WriteFPSCR(g_savedHostMode);
|
|
#else
|
|
fesetround(FE_TONEAREST);
|
|
#endif
|
|
}
|
|
|
|
u8 voffset[128];
|
|
u8 fromvoffset[128];
|
|
|
|
#ifndef M_LOG2E
|
|
#define M_E 2.71828182845904523536f
|
|
#define M_LOG2E 1.44269504088896340736f
|
|
#define M_LOG10E 0.434294481903251827651f
|
|
#define M_LN2 0.693147180559945309417f
|
|
#define M_LN10 2.30258509299404568402f
|
|
#undef M_PI
|
|
#define M_PI 3.14159265358979323846f
|
|
|
|
#ifndef M_PI_2
|
|
#define M_PI_2 1.57079632679489661923f
|
|
#endif
|
|
#define M_PI_4 0.785398163397448309616f
|
|
#define M_1_PI 0.318309886183790671538f
|
|
#define M_2_PI 0.636619772367581343076f
|
|
#define M_2_SQRTPI 1.12837916709551257390f
|
|
#define M_SQRT2 1.41421356237309504880f
|
|
#define M_SQRT1_2 0.707106781186547524401f
|
|
#endif
|
|
|
|
const float cst_constants[32] = {
|
|
0,
|
|
std::numeric_limits<float>::max(), // all these are verified on real PSP
|
|
sqrtf(2.0f),
|
|
sqrtf(0.5f),
|
|
2.0f/sqrtf((float)M_PI),
|
|
2.0f/(float)M_PI,
|
|
1.0f/(float)M_PI,
|
|
(float)M_PI/4,
|
|
(float)M_PI/2,
|
|
(float)M_PI,
|
|
(float)M_E,
|
|
(float)M_LOG2E,
|
|
(float)M_LOG10E,
|
|
(float)M_LN2,
|
|
(float)M_LN10,
|
|
2*(float)M_PI,
|
|
(float)M_PI/6,
|
|
log10f(2.0f),
|
|
logf(10.0f)/logf(2.0f),
|
|
sqrtf(3.0f)/2.0f,
|
|
};
|
|
|
|
MIPSState::MIPSState() {
|
|
// Initialize vorder
|
|
|
|
// This reordering of the VFPU registers in RAM means that instead of being like this:
|
|
|
|
// 0x00 0x20 0x40 0x60 -> "columns", the most common direction
|
|
// 0x01 0x21 0x41 0x61
|
|
// 0x02 0x22 0x42 0x62
|
|
// 0x03 0x23 0x43 0x63
|
|
|
|
// 0x04 0x24 0x44 0x64
|
|
// 0x06 0x26 0x45 0x65
|
|
// ....
|
|
|
|
// the VPU registers are effectively organized like this:
|
|
// 0x00 0x01 0x02 0x03
|
|
// 0x04 0x05 0x06 0x07
|
|
// 0x08 0x09 0x0a 0x0b
|
|
// ....
|
|
|
|
// This is because the original indices look like this:
|
|
// 0XXMMMYY where M is the matrix number.
|
|
|
|
// We will now map 0YYMMMXX to 0MMMXXYY.
|
|
|
|
// Advantages:
|
|
// * Columns can be flushed and reloaded faster "at once"
|
|
// * 4x4 Matrices are contiguous in RAM, making them, too, fast-loadable in NEON
|
|
|
|
// Disadvantages:
|
|
// * Extra indirection, can be confusing and slower (interpreter only, however we can often skip the table by rerranging formulas)
|
|
// * Flushing and reloading row registers is now slower
|
|
|
|
int i = 0;
|
|
for (int m = 0; m < 8; m++) {
|
|
for (int y = 0; y < 4; y++) {
|
|
for (int x = 0; x < 4; x++) {
|
|
voffset[m * 4 + x * 32 + y] = i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// And the inverse.
|
|
for (int i = 0; i < 128; i++) {
|
|
fromvoffset[voffset[i]] = i;
|
|
}
|
|
|
|
// Sanity check that things that should be ordered are ordered.
|
|
static const u8 firstThirtyTwo[] = {
|
|
0x0, 0x20, 0x40, 0x60,
|
|
0x1, 0x21, 0x41, 0x61,
|
|
0x2, 0x22, 0x42, 0x62,
|
|
0x3, 0x23, 0x43, 0x63,
|
|
|
|
0x4, 0x24, 0x44, 0x64,
|
|
0x5, 0x25, 0x45, 0x65,
|
|
0x6, 0x26, 0x46, 0x66,
|
|
0x7, 0x27, 0x47, 0x67,
|
|
};
|
|
|
|
for (int i = 0; i < (int)ARRAY_SIZE(firstThirtyTwo); i++) {
|
|
if (voffset[firstThirtyTwo[i]] != i) {
|
|
ERROR_LOG(Log::CPU, "Wrong voffset order! %i: %i should have been %i", firstThirtyTwo[i], voffset[firstThirtyTwo[i]], i);
|
|
}
|
|
}
|
|
}
|
|
|
|
MIPSState::~MIPSState() {
|
|
}
|
|
|
|
// The block shadow's replacement hook entries have to match the JIT's dispatcher.
|
|
static void UpdateBlockShadowHook(const MIPSComp::JitInterface *jit) {
|
|
Replacement_SetBlockShadowHook(jit ? jit->GetBlockShadowHookValue() : Memory::BLOCK_SHADOW_HOOK_NO_JIT);
|
|
}
|
|
|
|
void MIPSState::Shutdown() {
|
|
MIPSComp::JitInterface *oldjit = jit;
|
|
if (oldjit) {
|
|
oldjit->FlushProfile();
|
|
jit = nullptr;
|
|
delete oldjit;
|
|
}
|
|
UpdateBlockShadowHook(jit);
|
|
pendingInvalidates_.clear();
|
|
invalidateAll_ = false;
|
|
}
|
|
|
|
void MIPSState::Reset() {
|
|
Shutdown();
|
|
Init();
|
|
}
|
|
|
|
void MIPSState::Init() {
|
|
InitRegisters();
|
|
if (PSP_CoreParameter().cpuCore == CPUCore::JIT || PSP_CoreParameter().cpuCore == CPUCore::JIT_IR) {
|
|
jit = MIPSComp::CreateNativeJit(this);
|
|
} else if (PSP_CoreParameter().cpuCore == CPUCore::IR_INTERPRETER) {
|
|
jit = new MIPSComp::IRInterpreterJit(this);
|
|
} else {
|
|
jit = nullptr;
|
|
}
|
|
UpdateBlockShadowHook(jit);
|
|
}
|
|
|
|
void MIPSState::InitRegisters() {
|
|
memset(r, 0, sizeof(r));
|
|
memset(f, 0, sizeof(f));
|
|
memset(v, 0, sizeof(v));
|
|
memset(vfpuCtrl, 0, sizeof(vfpuCtrl));
|
|
memset(cop0, 0, sizeof(cop0));
|
|
|
|
vfpuCtrl[VFPU_CTRL_SPREFIX] = 0xe4; //passthru
|
|
vfpuCtrl[VFPU_CTRL_TPREFIX] = 0xe4; //passthru
|
|
vfpuCtrl[VFPU_CTRL_DPREFIX] = 0;
|
|
vfpuCtrl[VFPU_CTRL_CC] = 0x3f;
|
|
vfpuCtrl[VFPU_CTRL_INF4] = 0;
|
|
vfpuCtrl[VFPU_CTRL_REV] = 0x7772ceab;
|
|
vfpuCtrl[VFPU_CTRL_RCX0] = 0x3f800001;
|
|
vfpuCtrl[VFPU_CTRL_RCX1] = 0x3f800002;
|
|
vfpuCtrl[VFPU_CTRL_RCX2] = 0x3f800004;
|
|
vfpuCtrl[VFPU_CTRL_RCX3] = 0x3f800008;
|
|
vfpuCtrl[VFPU_CTRL_RCX4] = 0x3f800000;
|
|
vfpuCtrl[VFPU_CTRL_RCX5] = 0x3f800000;
|
|
vfpuCtrl[VFPU_CTRL_RCX6] = 0x3f800000;
|
|
vfpuCtrl[VFPU_CTRL_RCX7] = 0x3f800000;
|
|
|
|
pc = 0;
|
|
hi = 0;
|
|
lo = 0;
|
|
fpcond = 0;
|
|
fcr31 = 0;
|
|
debugCount = 0;
|
|
inDelaySlot = false;
|
|
llBit = 0;
|
|
nextPC = 0;
|
|
downcount = 0;
|
|
|
|
memset(vcmpResult, 0, sizeof(vcmpResult));
|
|
}
|
|
|
|
const float vfpuPrefixConstants[8] = { 0.f, 1.f, 2.f, 0.5f, 3.f, 1.f / 3.f, 0.25f, 1.f / 6.f };
|
|
|
|
bool MIPSState::HasDefaultPrefix() const {
|
|
return vfpuCtrl[VFPU_CTRL_SPREFIX] == 0xe4 && vfpuCtrl[VFPU_CTRL_TPREFIX] == 0xe4 && vfpuCtrl[VFPU_CTRL_DPREFIX] == 0;
|
|
}
|
|
|
|
void MIPSState::UpdateCore(CPUCore desired) {
|
|
if (PSP_CoreParameter().cpuCore == desired) {
|
|
return;
|
|
}
|
|
|
|
IncrementDebugCounter(DebugCounter::CPUCORE_SWITCHES);
|
|
|
|
// Get rid of the old JIT first, before switching.
|
|
if (jit) {
|
|
delete jit;
|
|
jit = nullptr;
|
|
}
|
|
|
|
PSP_CoreParameter().cpuCore = desired;
|
|
|
|
MIPSComp::JitInterface *newjit = nullptr;
|
|
switch (PSP_CoreParameter().cpuCore) {
|
|
case CPUCore::JIT:
|
|
case CPUCore::JIT_IR:
|
|
INFO_LOG(Log::CPU, "Switching to JIT%s", PSP_CoreParameter().cpuCore == CPUCore::JIT_IR ? " IR" : "");
|
|
newjit = MIPSComp::CreateNativeJit(this);
|
|
break;
|
|
|
|
case CPUCore::IR_INTERPRETER:
|
|
INFO_LOG(Log::CPU, "Switching to IR interpreter");
|
|
newjit = new MIPSComp::IRInterpreterJit(this);
|
|
break;
|
|
|
|
case CPUCore::INTERPRETER:
|
|
INFO_LOG(Log::CPU, "Switching to interpreter");
|
|
// Leaving newjit as null.
|
|
break;
|
|
|
|
default:
|
|
WARN_LOG(Log::CPU, "Invalid value for cpuCore, falling back to interpreter");
|
|
break;
|
|
}
|
|
|
|
jit = newjit;
|
|
if (newjit) {
|
|
// The game may already have set a rounding mode on the previous core.
|
|
newjit->UpdateFCR31();
|
|
}
|
|
UpdateBlockShadowHook(jit);
|
|
}
|
|
|
|
void MIPSState::DoState(PointerWrap &p) {
|
|
auto s = p.Section("MIPSState", 1, 5);
|
|
if (!s)
|
|
return;
|
|
|
|
if (p.mode == p.MODE_READ) {
|
|
if (jit && !keepJitBlocks_.empty()) {
|
|
// Blocks in code the load didn't change can stay. Queued invalidations stay too.
|
|
InitRegisters();
|
|
jit->InvalidateCacheExcept(keepJitBlocks_);
|
|
} else {
|
|
// Start the JIT over.
|
|
Reset();
|
|
}
|
|
keepJitBlocks_.clear();
|
|
}
|
|
// Assume we're not saving state during a CPU core reset, so no lock.
|
|
if (jit)
|
|
jit->DoState(p);
|
|
else
|
|
MIPSComp::DoDummyJitState(p, this);
|
|
|
|
DoArray(p, r, sizeof(r) / sizeof(r[0]));
|
|
DoArray(p, f, sizeof(f) / sizeof(f[0]));
|
|
if (s <= 2) {
|
|
float vtemp[128];
|
|
DoArray(p, vtemp, sizeof(v) / sizeof(v[0]));
|
|
for (int i = 0; i < 128; i++) {
|
|
v[voffset[i]] = vtemp[i];
|
|
}
|
|
} else {
|
|
DoArray(p, v, sizeof(v) / sizeof(v[0]));
|
|
}
|
|
DoArray(p, vfpuCtrl, sizeof(vfpuCtrl) / sizeof(vfpuCtrl[0]));
|
|
Do(p, pc);
|
|
Do(p, nextPC);
|
|
Do(p, downcount);
|
|
// Reversed, but we can just leave it that way.
|
|
Do(p, hi);
|
|
Do(p, lo);
|
|
Do(p, fpcond);
|
|
if (s <= 1) {
|
|
u32 fcr0_unused = 0;
|
|
Do(p, fcr0_unused);
|
|
}
|
|
Do(p, fcr31);
|
|
if (s <= 3) {
|
|
uint32_t dummy;
|
|
Do(p, dummy); // rng.m_w
|
|
Do(p, dummy); // rng.m_z
|
|
}
|
|
|
|
Do(p, inDelaySlot);
|
|
Do(p, llBit);
|
|
Do(p, debugCount);
|
|
if (s >= 5) {
|
|
DoArray(p, cop0, sizeof(cop0) / sizeof(cop0[0]));
|
|
} else {
|
|
memset(cop0, 0, sizeof(cop0));
|
|
}
|
|
|
|
if (p.mode == p.MODE_READ && jit) {
|
|
// Now that we've loaded fcr31, update any jit state associated.
|
|
jit->UpdateFCR31();
|
|
}
|
|
}
|
|
|
|
void MIPSState::SingleStep() {
|
|
ProcessPendingInvalidates();
|
|
int cycles = MIPS_InterpretSingleStep(this);
|
|
downcount -= cycles;
|
|
}
|
|
|
|
// returns 1 if reached ticks limit
|
|
int MIPSState::RunLoopUntil(u64 globalTicks) {
|
|
switch (PSP_CoreParameter().cpuCore) {
|
|
case CPUCore::JIT:
|
|
case CPUCore::JIT_IR:
|
|
case CPUCore::IR_INTERPRETER:
|
|
while (inDelaySlot) {
|
|
// We must get out of the delay slot before going into jit.
|
|
// This normally should never take more than one step...
|
|
int cycles = MIPS_InterpretSingleStep(this);
|
|
downcount -= cycles;
|
|
}
|
|
ProcessPendingInvalidates();
|
|
jit->RunLoopUntil(globalTicks);
|
|
break;
|
|
|
|
case CPUCore::INTERPRETER:
|
|
ProcessPendingInvalidates();
|
|
return MIPSInterpret_RunUntil(this, globalTicks);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void MIPSState::InvalidateICacheRangeImmediate(u32 address, u32 length) {
|
|
Replacement_CheckRange(address, length);
|
|
if (!jit) {
|
|
// Nothing to do.
|
|
return;
|
|
}
|
|
if (length != 0) {
|
|
jit->InvalidateCacheAt(address, length);
|
|
}
|
|
}
|
|
|
|
// The replacement hook checks happen here rather than when queued, since callers may queue the
|
|
// invalidation before writing. Dropping a hook queues its address, hence the loop.
|
|
void MIPSState::ProcessPendingInvalidates() {
|
|
while (invalidateAll_ || !pendingInvalidates_.empty()) {
|
|
const bool all = invalidateAll_;
|
|
invalidateAll_ = false;
|
|
std::vector<PendingCacheOperation> pending;
|
|
pending.swap(pendingInvalidates_);
|
|
for (const auto &p : pending) {
|
|
Replacement_CheckRange(p.addr, p.size);
|
|
if (jit && !all) {
|
|
jit->InvalidateCacheAt(p.addr, p.size);
|
|
}
|
|
}
|
|
if (jit && all) {
|
|
jit->ClearCache();
|
|
}
|
|
}
|
|
}
|
|
|
|
void MIPSState::InvalidateICacheRangeDeferred(u32 address, u32 length) {
|
|
// TODO: We can try to merge the invalidate with the previous one.
|
|
pendingInvalidates_.push_back(PendingCacheOperation{address, length});
|
|
if (jit) {
|
|
Core_ReenterDispatcher();
|
|
}
|
|
}
|
|
|
|
// Queued ranges still get their replacement hook checks.
|
|
void MIPSState::ClearJitCacheDeferred() {
|
|
if (!jit) {
|
|
return;
|
|
}
|
|
|
|
invalidateAll_ = true;
|
|
Core_ReenterDispatcher();
|
|
}
|