R5900: Make CPU exits consistent and safe

Previously, we would either throw an exception (ints), or longjmp out of
the recompiler when the execution state was checked. Unfortunately for
our stability, this happened at the end of the frame, just before it was
pushed to the GS, and in the middle of processing EE events (!).

Doing so not only meant that we executed a bunch of event
testing/exception code twice (once after we paused, again when we
resumed), but it also could potentially leave things in an inconsistent
state.

So instead, let's do it safely with a flag, replacing the old
iopBreakpoint flag, so there's no additional overhead on the hot path.
This commit is contained in:
Connor McLaughlin 2022-04-04 22:19:39 +10:00 committed by refractionpcsx2
parent 821e15f1ee
commit 5ac9419703
2 changed files with 36 additions and 15 deletions

View File

@ -38,6 +38,7 @@ extern int vu0branch, vu1branch;
static int branch2 = 0; static int branch2 = 0;
static u32 cpuBlockCycles = 0; // 3 bit fixed point version of cycle count static u32 cpuBlockCycles = 0; // 3 bit fixed point version of cycle count
static std::string disOut; static std::string disOut;
static bool intExitExecution = false;
static void intEventTest(); static void intEventTest();
@ -498,6 +499,12 @@ static void intEventTest()
{ {
// Perform counters, ints, and IOP updates: // Perform counters, ints, and IOP updates:
_cpuEventTest_Shared(); _cpuEventTest_Shared();
if (intExitExecution)
{
intExitExecution = false;
throw Exception::ExitCpuExecute();
}
} }
static void intExecute() static void intExecute()
@ -577,12 +584,20 @@ static void intExecute()
static void intCheckExecutionState() static void intCheckExecutionState()
{ {
#ifndef PCSX2_CORE #ifndef PCSX2_CORE
if( GetCoreThread().HasPendingStateChangeRequest() ) const bool interrupted = GetCoreThread().HasPendingStateChangeRequest();
throw Exception::ExitCpuExecute();
#else #else
if (VMManager::Internal::IsExecutionInterrupted()) const bool interrupted = VMManager::Internal::IsExecutionInterrupted();
throw Exception::ExitCpuExecute();
#endif #endif
if (interrupted)
{
// If we're currently processing events, we can't safely jump out of the interpreter here, because we'll
// leave things in an inconsistent state. So instead, we flag it for exiting once cpuEventTest() returns.
if (eeEventTestIsActive)
intExitExecution = true;
else
throw Exception::ExitCpuExecute();
}
} }
static void intStep() static void intStep()

View File

@ -48,6 +48,13 @@
using namespace x86Emitter; using namespace x86Emitter;
using namespace R5900; using namespace R5900;
static std::atomic<bool> eeRecIsReset(false);
static std::atomic<bool> eeRecNeedsReset(false);
static bool eeCpuExecuting = false;
static bool eeRecExitRequested = false;
static bool g_resetEeScalingStats = false;
static int g_patchesNeedRedo = 0;
#define PC_GETBLOCK(x) PC_GETBLOCK_(x, recLUT) #define PC_GETBLOCK(x) PC_GETBLOCK_(x, recLUT)
u32 maxrecmem = 0; u32 maxrecmem = 0;
@ -368,9 +375,9 @@ static void recEventTest()
{ {
_cpuEventTest_Shared(); _cpuEventTest_Shared();
if (iopBreakpoint) if (eeRecExitRequested)
{ {
iopBreakpoint = false; eeRecExitRequested = false;
recExitExecution(); recExitExecution();
} }
} }
@ -619,12 +626,6 @@ static void recAlloc()
alignas(16) static u16 manual_page[Ps2MemSize::MainRam >> 12]; alignas(16) static u16 manual_page[Ps2MemSize::MainRam >> 12];
alignas(16) static u8 manual_counter[Ps2MemSize::MainRam >> 12]; alignas(16) static u8 manual_counter[Ps2MemSize::MainRam >> 12];
static std::atomic<bool> eeRecIsReset(false);
static std::atomic<bool> eeRecNeedsReset(false);
static bool eeCpuExecuting = false;
static bool g_resetEeScalingStats = false;
static int g_patchesNeedRedo = 0;
//////////////////////////////////////////////////// ////////////////////////////////////////////////////
static void recResetRaw() static void recResetRaw()
{ {
@ -713,12 +714,17 @@ static void recExitExecution()
static void recCheckExecutionState() static void recCheckExecutionState()
{ {
#ifndef PCSX2_CORE #ifndef PCSX2_CORE
if (m_cpuException || m_Exception || eeRecNeedsReset || GetCoreThread().HasPendingStateChangeRequest()) if (m_cpuException || m_Exception || eeRecNeedsReset || iopBreakpoint || GetCoreThread().HasPendingStateChangeRequest())
#else #else
if (m_cpuException || m_Exception || eeRecNeedsReset || VMManager::Internal::IsExecutionInterrupted()) if (m_cpuException || m_Exception || eeRecNeedsReset || iopBreakpoint || VMManager::Internal::IsExecutionInterrupted())
#endif #endif
{ {
recExitExecution(); // If we're currently processing events, we can't safely jump out of the recompiler here, because we'll
// leave things in an inconsistent state. So instead, we flag it for exiting once cpuEventTest() returns.
if (eeEventTestIsActive)
eeRecExitRequested = true;
else
recExitExecution();
} }
} }