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 u32 cpuBlockCycles = 0; // 3 bit fixed point version of cycle count
static std::string disOut;
static bool intExitExecution = false;
static void intEventTest();
@ -498,6 +499,12 @@ static void intEventTest()
{
// Perform counters, ints, and IOP updates:
_cpuEventTest_Shared();
if (intExitExecution)
{
intExitExecution = false;
throw Exception::ExitCpuExecute();
}
}
static void intExecute()
@ -577,12 +584,20 @@ static void intExecute()
static void intCheckExecutionState()
{
#ifndef PCSX2_CORE
if( GetCoreThread().HasPendingStateChangeRequest() )
throw Exception::ExitCpuExecute();
const bool interrupted = GetCoreThread().HasPendingStateChangeRequest();
#else
if (VMManager::Internal::IsExecutionInterrupted())
throw Exception::ExitCpuExecute();
const bool interrupted = VMManager::Internal::IsExecutionInterrupted();
#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()

View File

@ -48,6 +48,13 @@
using namespace x86Emitter;
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)
u32 maxrecmem = 0;
@ -368,9 +375,9 @@ static void recEventTest()
{
_cpuEventTest_Shared();
if (iopBreakpoint)
if (eeRecExitRequested)
{
iopBreakpoint = false;
eeRecExitRequested = false;
recExitExecution();
}
}
@ -619,12 +626,6 @@ static void recAlloc()
alignas(16) static u16 manual_page[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()
{
@ -713,12 +714,17 @@ static void recExitExecution()
static void recCheckExecutionState()
{
#ifndef PCSX2_CORE
if (m_cpuException || m_Exception || eeRecNeedsReset || GetCoreThread().HasPendingStateChangeRequest())
if (m_cpuException || m_Exception || eeRecNeedsReset || iopBreakpoint || GetCoreThread().HasPendingStateChangeRequest())
#else
if (m_cpuException || m_Exception || eeRecNeedsReset || VMManager::Internal::IsExecutionInterrupted())
if (m_cpuException || m_Exception || eeRecNeedsReset || iopBreakpoint || VMManager::Internal::IsExecutionInterrupted())
#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();
}
}