R3000A/R5900: Refactor interpreter/recompiler exits

Now, IOP breakpoints work nice and reliably in both interpreter and
recompiler, exiting as soon as possible, without leaving the event state
indeterminate.
This commit is contained in:
Connor McLaughlin 2022-04-05 21:53:49 +10:00 committed by refractionpcsx2
parent 25e15a16b1
commit 3801825793
9 changed files with 72 additions and 96 deletions

View File

@ -488,10 +488,13 @@ static __fi void frameLimitUpdateCore()
#ifndef PCSX2_CORE
GetCoreThread().VsyncInThread();
if (GetCoreThread().HasPendingStateChangeRequest())
Cpu->ExitExecution();
#else
VMManager::Internal::VSyncOnCPUThread();
if (VMManager::Internal::IsExecutionInterrupted())
Cpu->ExitExecution();
#endif
Cpu->CheckExecutionState();
}
// Framelimiter - Measures the delta time between calls and stalls until a

View File

@ -581,23 +581,14 @@ static void intExecute()
} while (instruction_was_cancelled);
}
static void intCheckExecutionState()
static void intSafeExitExecution()
{
#ifndef PCSX2_CORE
const bool interrupted = GetCoreThread().HasPendingStateChangeRequest();
#else
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();
}
// 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()
@ -642,7 +633,7 @@ R5900cpu intCpu =
intStep,
intExecute,
intCheckExecutionState,
intSafeExitExecution,
intThrowException,
intThrowException,
intClear,

View File

@ -43,8 +43,6 @@ s32 iopBreak = 0;
// control is returned to the EE.
s32 iopCycleEE = -1;
bool iopBreakpoint = 0;
// Used to signal to the EE when important actions that need IOP-attention have
// happened (hsyncs, vsyncs, IOP exceptions, etc). IOP runs code whenever this
// is true, even if it's already running ahead a bit.

View File

@ -120,7 +120,6 @@ alignas(16) extern psxRegisters psxRegs;
extern u32 g_iopNextEventCycle;
extern s32 iopBreak; // used when the IOP execution is broken and control returned to the EE
extern s32 iopCycleEE; // tracks IOP's current sych status with the EE
extern bool iopBreakpoint;
#ifndef _PC_
@ -186,7 +185,6 @@ extern bool iopIsDelaySlot;
struct R3000Acpu {
void (*Reserve)();
void (*Reset)();
void (*Execute)();
s32 (*ExecuteBlock)( s32 eeCycles ); // executes the given number of EE cycles.
void (*Clear)(u32 Addr, u32 Size);
void (*Shutdown)();

View File

@ -270,24 +270,29 @@ static void intReset() {
intAlloc();
}
static void intExecute() {
for (;;) execI();
}
static s32 intExecuteBlock( s32 eeCycles )
{
iopBreak = 0;
iopCycleEE = eeCycles;
while (iopCycleEE > 0){
if ((psxHu32(HW_ICFG) & 8) && ((psxRegs.pc & 0x1fffffffU) == 0xa0 || (psxRegs.pc & 0x1fffffffU) == 0xb0 || (psxRegs.pc & 0x1fffffffU) == 0xc0))
psxBiosCall();
try
{
while (iopCycleEE > 0) {
if ((psxHu32(HW_ICFG) & 8) && ((psxRegs.pc & 0x1fffffffU) == 0xa0 || (psxRegs.pc & 0x1fffffffU) == 0xb0 || (psxRegs.pc & 0x1fffffffU) == 0xc0))
psxBiosCall();
branch2 = 0;
while (!branch2) {
execI();
}
branch2 = 0;
while (!branch2) {
execI();
}
}
}
catch (Exception::ExitCpuExecute&)
{
// Get out of the EE too, regardless of whether it's int or rec.
Cpu->ExitExecution();
}
return iopBreak + iopCycleEE;
}
@ -309,7 +314,6 @@ static uint intGetCacheReserve()
R3000Acpu psxInt = {
intReserve,
intReset,
intExecute,
intExecuteBlock,
intClear,
intShutdown,

View File

@ -554,16 +554,18 @@ void __fastcall eeGameStarting()
//Console.WriteLn( Color_Green, "(R5900) ELF Entry point! [addr=0x%08X]", ElfEntry );
g_GameStarted = true;
g_GameLoading = false;
#ifndef PCSX2_CORE
GetCoreThread().GameStartingInThread();
#else
VMManager::Internal::GameStartingOnCPUThread();
#endif
// GameStartingInThread may issue a reset of the cpu and/or recompilers. Check for and
// handle such things here:
Cpu->CheckExecutionState();
#ifndef PCSX2_CORE
GetCoreThread().GameStartingInThread();
if (GetCoreThread().HasPendingStateChangeRequest())
Cpu->ExitExecution();
#else
VMManager::Internal::GameStartingOnCPUThread();
if (VMManager::Internal::IsExecutionInterrupted())
Cpu->ExitExecution();
#endif
}
else
{

View File

@ -347,22 +347,10 @@ struct R5900cpu
//
void (*Execute)();
// Checks for execution suspension or cancellation. In pthreads terms this provides
// a "cancellation point." Execution state checks are typically performed at Vsyncs
// by the generic VM event handlers in R5900.cpp/Counters.cpp (applies to both recs
// and ints).
//
// Implementation note: Because of the nuances of recompiled code execution, setjmp
// may be used in place of thread cancellation or C++ exceptions (non-SEH exceptions
// cannot unwind through the recompiled code stackframes, thus longjmp must be used).
//
// Thread Affinity:
// Must be called on the same thread as Execute.
//
// Exception Throws:
// May throw Execution/Pthreads cancellations if the compiler supports SEH.
//
void (*CheckExecutionState)();
// Immediately exits execution of recompiled code if we are in a state to do so, or
// queues an exit as soon as it is safe. Safe in this case refers to whether we are
// currently executing events or not.
void (*ExitExecution)();
// Safely throws host exceptions from executing code (either recompiled or interpreted).
// If this function is called outside the context of the CPU's code execution, then the

View File

@ -816,12 +816,6 @@ static void iopClearRecLUT(BASEBLOCK* base, int count)
base[i].SetFnptr((uptr)iopJITCompile);
}
static void recExecute()
{
// note: this function is currently never used.
//for (;;) R3000AExecute();
}
static __noinline s32 recExecuteBlock(s32 eeCycles)
{
iopBreak = 0;
@ -1094,11 +1088,11 @@ void rpsxBREAK()
//if (!psxbranch) psxbranch = 2;
}
void psxDynarecCheckBreakpoint()
static bool psxDynarecCheckBreakpoint()
{
u32 pc = psxRegs.pc;
if (CBreakPoints::CheckSkipFirst(BREAKPOINT_IOP, pc) == pc)
return;
return false;
int bpFlags = psxIsBreakpointNeeded(pc);
bool hit = false;
@ -1120,29 +1114,35 @@ void psxDynarecCheckBreakpoint()
}
if (!hit)
return;
return false;
CBreakPoints::SetBreakpointTriggered(true);
#ifndef PCSX2_CORE
GetCoreThread().PauseSelfDebug();
#endif
iopBreakpoint = true;
// Exit the EE too.
Cpu->ExitExecution();
return true;
}
void psxDynarecMemcheck()
static bool psxDynarecMemcheck()
{
u32 pc = psxRegs.pc;
if (CBreakPoints::CheckSkipFirst(BREAKPOINT_IOP, pc) == pc)
return;
return false;
CBreakPoints::SetBreakpointTriggered(true);
#ifndef PCSX2_CORE
GetCoreThread().PauseSelfDebug();
#endif
iopBreakpoint = true;
// Exit the EE too.
Cpu->ExitExecution();
return true;
}
void __fastcall psxDynarecMemLogcheck(u32 start, bool store)
static void psxDynarecMemLogcheck(u32 start, bool store)
{
if (store)
DevCon.WriteLn("Hit store breakpoint @0x%x", start);
@ -1150,7 +1150,7 @@ void __fastcall psxDynarecMemLogcheck(u32 start, bool store)
DevCon.WriteLn("Hit load breakpoint @0x%x", start);
}
void psxRecMemcheck(u32 op, u32 bits, bool store)
static void psxRecMemcheck(u32 op, u32 bits, bool store)
{
_psxFlushCall(FLUSH_EVERYTHING | FLUSH_PC);
@ -1196,29 +1196,27 @@ void psxRecMemcheck(u32 op, u32 bits, bool store)
if (checks[i].result & MEMCHECK_BREAK)
{
xFastCall((void*)psxDynarecMemcheck);
xTEST(al, al);
xJNZ(iopExitRecompiledCode);
}
next1.SetTarget();
next2.SetTarget();
}
// get out of here
xCMP(ptr8[&iopBreakpoint], 0);
xJNE(iopExitRecompiledCode);
}
void psxEncodeBreakpoint()
static void psxEncodeBreakpoint()
{
if (psxIsBreakpointNeeded(psxpc) != 0)
{
_psxFlushCall(FLUSH_EVERYTHING | FLUSH_PC);
xFastCall((void*)psxDynarecCheckBreakpoint);
// get out of here
xCMP(ptr8[&iopBreakpoint], 0);
xJNE(iopExitRecompiledCode);
xTEST(al, al);
xJNZ(iopExitRecompiledCode);
}
}
void psxEncodeMemcheck()
static void psxEncodeMemcheck()
{
int needed = psxIsMemcheckNeeded(psxpc);
if (needed == 0)
@ -1559,7 +1557,6 @@ static uint recGetCacheReserve()
R3000Acpu psxRec = {
recReserve,
recResetIOP,
recExecute,
recExecuteBlock,
recClearIOP,
recShutdown,

View File

@ -686,7 +686,9 @@ static void recResetEE()
{
if (eeCpuExecuting)
{
// get outta here as soon as we can
eeRecNeedsReset = true;
eeRecExitRequested = true;
return;
}
@ -711,21 +713,14 @@ static void recExitExecution()
fastjmp_jmp(&m_SetJmp_StateCheck, 1);
}
static void recCheckExecutionState()
static void recSafeExitExecution()
{
#ifndef PCSX2_CORE
if (m_cpuException || m_Exception || eeRecNeedsReset || iopBreakpoint || GetCoreThread().HasPendingStateChangeRequest())
#else
if (m_cpuException || m_Exception || eeRecNeedsReset || iopBreakpoint || VMManager::Internal::IsExecutionInterrupted())
#endif
{
// 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();
}
// 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();
}
static void recExecute()
@ -2399,7 +2394,7 @@ R5900cpu recCpu =
recStep,
recExecute,
recCheckExecutionState,
recSafeExitExecution,
recThrowException,
recThrowException,
recClear,