Don't block the render thread while the CPU is paused. This is a prereq for imgui debuggers.

This commit is contained in:
Henrik Rydgård 2024-11-03 20:49:20 +01:00
parent 758faac445
commit 3a5968ba33
8 changed files with 88 additions and 89 deletions

View File

@ -35,6 +35,7 @@
#include "Core/Core.h" #include "Core/Core.h"
#include "Core/Config.h" #include "Core/Config.h"
#include "Core/MemMap.h" #include "Core/MemMap.h"
#include "Core/MIPS/MIPSDebugInterface.h"
#include "Core/SaveState.h" #include "Core/SaveState.h"
#include "Core/System.h" #include "Core/System.h"
#include "Core/MemFault.h" #include "Core/MemFault.h"
@ -51,16 +52,33 @@
#include "Windows/InputDevice.h" #include "Windows/InputDevice.h"
#endif #endif
static std::condition_variable m_StepCond; // Step command to execute next
static std::mutex m_hStepMutex; static std::mutex g_stepMutex;
struct StepCommand {
CPUStepType type;
int param;
const char *reason;
u32 relatedAddr;
bool empty() const {
return type == CPUStepType::None;
}
void clear() {
type = CPUStepType::None;
param = 0;
reason = "";
relatedAddr = 0;
}
};
static StepCommand g_stepCommand;
// This is so that external threads can wait for the CPU to become inactive.
static std::condition_variable m_InactiveCond; static std::condition_variable m_InactiveCond;
static std::mutex m_hInactiveMutex; static std::mutex m_hInactiveMutex;
static int g_singleStepsPending = 0;
static int steppingCounter = 0; static int steppingCounter = 0;
static const char *steppingReason = "";
static uint32_t steppingAddress = 0;
static std::set<CoreLifecycleFunc> lifecycleFuncs; static std::set<CoreLifecycleFunc> lifecycleFuncs;
static std::set<CoreStopRequestFunc> stopFuncs; static std::set<CoreStopRequestFunc> stopFuncs;
static bool windowHidden = false; static bool windowHidden = false;
static bool powerSaving = false; static bool powerSaving = false;
@ -126,14 +144,6 @@ bool Core_IsInactive() {
return coreState != CORE_RUNNING && coreState != CORE_NEXTFRAME && !coreStatePending; return coreState != CORE_RUNNING && coreState != CORE_NEXTFRAME && !coreStatePending;
} }
static inline void Core_StateProcessed() {
if (coreStatePending) {
std::lock_guard<std::mutex> guard(m_hInactiveMutex);
coreStatePending = false;
m_InactiveCond.notify_all();
}
}
void Core_WaitInactive() { void Core_WaitInactive() {
while (Core_IsActive() && !GPUStepping::IsStepping()) { while (Core_IsActive() && !GPUStepping::IsStepping()) {
std::unique_lock<std::mutex> guard(m_hInactiveMutex); std::unique_lock<std::mutex> guard(m_hInactiveMutex);
@ -227,19 +237,23 @@ void Core_RunLoop(GraphicsContext *ctx) {
NativeFrame(ctx); NativeFrame(ctx);
} }
void Core_DoSingleStep() { bool Core_RequestSingleStep(CPUStepType type, int stepSize) {
std::lock_guard<std::mutex> guard(m_hStepMutex); std::lock_guard<std::mutex> guard(g_stepMutex);
g_singleStepsPending++; if (g_stepCommand.type != CPUStepType::None) {
m_StepCond.notify_all(); ERROR_LOG(Log::CPU, "Can't submit two steps in one frame");
} return false;
}
void Core_UpdateSingleStep() { g_stepCommand = { type, stepSize };
std::lock_guard<std::mutex> guard(m_hStepMutex); return true;
m_StepCond.notify_all();
} }
// See comment in header. // See comment in header.
void Core_PerformStep(DebugInterface *cpu, CPUStepType stepType, int stepSize) { // Handles more advanced step types (used by the debugger).
// stepSize is to support stepping through compound instructions like fused lui+ladd (li).
// Yes, our disassembler does support those.
// Doesn't return the new address, as that's just mips->getPC().
// Internal use.
static void Core_PerformStep(MIPSDebugInterface *cpu, CPUStepType stepType, int stepSize) {
switch (stepType) { switch (stepType) {
case CPUStepType::Into: case CPUStepType::Into:
{ {
@ -247,8 +261,8 @@ void Core_PerformStep(DebugInterface *cpu, CPUStepType stepType, int stepSize) {
u32 newAddress = currentPc + stepSize; u32 newAddress = currentPc + stepSize;
// If the current PC is on a breakpoint, the user still wants the step to happen. // If the current PC is on a breakpoint, the user still wants the step to happen.
CBreakPoints::SetSkipFirst(currentPc); CBreakPoints::SetSkipFirst(currentPc);
for (int i = 0; i < (newAddress - currentPc) / 4; i++) { for (int i = 0; i < (int)(newAddress - currentPc) / 4; i++) {
Core_DoSingleStep(); currentMIPS->SingleStep();
} }
return; return;
} }
@ -316,22 +330,8 @@ void Core_PerformStep(DebugInterface *cpu, CPUStepType stepType, int stepSize) {
} }
} }
static inline int Core_WaitStepping() { void Core_ProcessStepping(MIPSDebugInterface *cpu) {
std::unique_lock<std::mutex> guard(m_hStepMutex); coreStatePending = false;
// We only wait 16ms so that we can still draw UI or react to events.
double sleepStart = time_now_d();
if (!g_singleStepsPending && coreState == CORE_STEPPING)
m_StepCond.wait_for(guard, std::chrono::milliseconds(16));
double sleepEnd = time_now_d();
DisplayNotifySleep(sleepEnd - sleepStart);
int result = g_singleStepsPending;
g_singleStepsPending = 0;
return result;
}
void Core_ProcessStepping() {
Core_StateProcessed();
// Check if there's any pending save state actions. // Check if there's any pending save state actions.
SaveState::Process(); SaveState::Process();
@ -352,21 +352,26 @@ void Core_ProcessStepping() {
} }
// Need to check inside the lock to avoid races. // Need to check inside the lock to avoid races.
int doSteps = Core_WaitStepping(); std::lock_guard<std::mutex> guard(g_stepMutex);
// We may still be stepping without singleStepPending to process a save state. if (coreState != CORE_STEPPING || g_stepCommand.empty()) {
if (doSteps && coreState == CORE_STEPPING) { return;
Core_ResetException();
for (int i = 0; i < doSteps; i++) {
currentMIPS->SingleStep();
steppingCounter++;
}
// Update disasm dialog.
System_Notify(SystemNotification::DISASSEMBLY_AFTERSTEP);
System_Notify(SystemNotification::MEM_VIEW);
} }
Core_ResetException();
if (!g_stepCommand.empty()) {
Core_PerformStep(cpu, g_stepCommand.type, g_stepCommand.param);
if (g_stepCommand.type == CPUStepType::Into) {
// We're already done. The other step types will resume the CPU.
System_Notify(SystemNotification::DISASSEMBLY_AFTERSTEP);
}
g_stepCommand.clear();
steppingCounter++;
}
// Update disasm dialog.
System_Notify(SystemNotification::MEM_VIEW);
} }
// Many platforms, like Android, do not call this function but handle things on their own. // Many platforms, like Android, do not call this function but handle things on their own.
@ -375,7 +380,6 @@ bool Core_Run(GraphicsContext *ctx) {
System_Notify(SystemNotification::DISASSEMBLY); System_Notify(SystemNotification::DISASSEMBLY);
while (true) { while (true) {
if (GetUIState() != UISTATE_INGAME) { if (GetUIState() != UISTATE_INGAME) {
Core_StateProcessed();
if (GetUIState() == UISTATE_EXIT) { if (GetUIState() == UISTATE_EXIT) {
// Not sure why we do a final frame here? // Not sure why we do a final frame here?
NativeFrame(ctx); NativeFrame(ctx);
@ -388,11 +392,9 @@ bool Core_Run(GraphicsContext *ctx) {
switch (coreState) { switch (coreState) {
case CORE_RUNNING: case CORE_RUNNING:
case CORE_STEPPING: case CORE_STEPPING:
Core_StateProcessed();
// enter a fast runloop // enter a fast runloop
Core_RunLoop(ctx); Core_RunLoop(ctx);
if (coreState == CORE_POWERDOWN) { if (coreState == CORE_POWERDOWN) {
Core_StateProcessed();
return true; return true;
} }
break; break;
@ -402,7 +404,6 @@ bool Core_Run(GraphicsContext *ctx) {
case CORE_BOOT_ERROR: case CORE_BOOT_ERROR:
case CORE_RUNTIME_ERROR: case CORE_RUNTIME_ERROR:
// Exit loop!! // Exit loop!!
Core_StateProcessed();
return true; return true;
case CORE_NEXTFRAME: case CORE_NEXTFRAME:
@ -411,27 +412,30 @@ bool Core_Run(GraphicsContext *ctx) {
} }
} }
// Free-threaded (hm, possibly except tracing).
void Core_Break(const char *reason, u32 relatedAddress) { void Core_Break(const char *reason, u32 relatedAddress) {
// Stop the tracer // Stop the tracer
mipsTracer.stop_tracing(); mipsTracer.stop_tracing();
Core_UpdateState(CORE_STEPPING); {
steppingCounter++; std::lock_guard<std::mutex> lock(g_stepMutex);
_assert_msg_(reason != nullptr, "No reason specified for break"); steppingCounter++;
steppingReason = reason; _assert_msg_(reason != nullptr, "No reason specified for break");
steppingAddress = relatedAddress;
Core_UpdateState(CORE_STEPPING);
}
System_Notify(SystemNotification::DEBUG_MODE_CHANGE); System_Notify(SystemNotification::DEBUG_MODE_CHANGE);
} }
// Free-threaded (or at least should be)
void Core_Resume() { void Core_Resume() {
// Clear the exception if we resume. // Clear the exception if we resume.
Core_ResetException(); Core_ResetException();
coreState = CORE_RUNNING; coreState = CORE_RUNNING;
coreStatePending = false;
m_StepCond.notify_all();
System_Notify(SystemNotification::DEBUG_MODE_CHANGE); System_Notify(SystemNotification::DEBUG_MODE_CHANGE);
} }
// Should be called from the EmuThread.
bool Core_NextFrame() { bool Core_NextFrame() {
if (coreState == CORE_RUNNING) { if (coreState == CORE_RUNNING) {
coreState = CORE_NEXTFRAME; coreState = CORE_NEXTFRAME;
@ -447,8 +451,11 @@ int Core_GetSteppingCounter() {
SteppingReason Core_GetSteppingReason() { SteppingReason Core_GetSteppingReason() {
SteppingReason r; SteppingReason r;
r.reason = steppingReason; std::lock_guard<std::mutex> lock(g_stepMutex);
r.relatedAddress = steppingAddress; if (!g_stepCommand.empty()) {
r.reason = g_stepCommand.reason;
r.relatedAddress = g_stepCommand.relatedAddr;
}
return r; return r;
} }

View File

@ -24,7 +24,6 @@
#include "Core/CoreParameter.h" #include "Core/CoreParameter.h"
class GraphicsContext; class GraphicsContext;
class DebugInterface;
// called from emu thread // called from emu thread
void UpdateRunLoop(GraphicsContext *ctx); void UpdateRunLoop(GraphicsContext *ctx);
@ -53,16 +52,9 @@ void Core_Break(const char *reason, u32 relatedAddress = 0);
// void Core_Step(CPUStepType type); // CPUStepType::None not allowed // void Core_Step(CPUStepType type); // CPUStepType::None not allowed
void Core_Resume(); void Core_Resume();
// Handles more advanced step types (used by the debugger). // This should be called externally.
// stepSize is to support stepping through compound instructions like fused lui+ladd (li). // Can fail if another step type was requested this frame.
// Yes, our disassembler does support those. bool Core_RequestSingleStep(CPUStepType stepType, int stepSize);
// Doesn't return the new address, as that's just mips->getPC().
void Core_PerformStep(DebugInterface *cpu, CPUStepType stepType, int stepSize);
// Refactor.
void Core_DoSingleStep();
void Core_UpdateSingleStep();
void Core_ProcessStepping();
bool Core_ShouldRunBehind(); bool Core_ShouldRunBehind();
bool Core_MustRunBehind(); bool Core_MustRunBehind();

View File

@ -90,7 +90,7 @@ void WebSocketCPUResume(DebuggerRequest &req) {
CBreakPoints::SetSkipFirst(currentMIPS->pc); CBreakPoints::SetSkipFirst(currentMIPS->pc);
if (currentMIPS->inDelaySlot) { if (currentMIPS->inDelaySlot) {
Core_DoSingleStep(); Core_RequestSingleStep(CPUStepType::Into, 1);
} }
Core_Resume(); Core_Resume();
} }

View File

@ -107,9 +107,7 @@ void WebSocketSteppingState::Into(DebuggerRequest &req) {
CBreakPoints::SetSkipFirst(currentMIPS->pc); CBreakPoints::SetSkipFirst(currentMIPS->pc);
int c = GetNextInstructionCount(cpuDebug); int c = GetNextInstructionCount(cpuDebug);
for (int i = 0; i < c; ++i) { Core_RequestSingleStep(CPUStepType::Into, c);
Core_DoSingleStep();
}
} else { } else {
uint32_t breakpointAddress = cpuDebug->GetPC(); uint32_t breakpointAddress = cpuDebug->GetPC();
PrepareResume(); PrepareResume();
@ -278,7 +276,8 @@ int WebSocketSteppingState::GetNextInstructionCount(DebugInterface *cpuDebug) {
void WebSocketSteppingState::PrepareResume() { void WebSocketSteppingState::PrepareResume() {
if (currentMIPS->inDelaySlot) { if (currentMIPS->inDelaySlot) {
Core_DoSingleStep(); // Delay slot instructions are never joined, so we pass 1.
Core_RequestSingleStep(CPUStepType::Into, 1);
} else { } else {
// If the current PC is on a breakpoint, the user doesn't want to do nothing. // If the current PC is on a breakpoint, the user doesn't want to do nothing.
CBreakPoints::SetSkipFirst(currentMIPS->pc); CBreakPoints::SetSkipFirst(currentMIPS->pc);

View File

@ -419,7 +419,6 @@ namespace SaveState
// Don't actually run it until next frame. // Don't actually run it until next frame.
// It's possible there might be a duplicate but it won't hurt us. // It's possible there might be a duplicate but it won't hurt us.
needsProcess = true; needsProcess = true;
Core_UpdateSingleStep();
} }
void Load(const Path &filename, int slot, Callback callback, void *cbUserData) void Load(const Path &filename, int slot, Callback callback, void *cbUserData)

View File

@ -118,6 +118,9 @@ static volatile bool pspIsIniting = false;
static volatile bool pspIsQuitting = false; static volatile bool pspIsQuitting = false;
static volatile bool pspIsRebooting = false; static volatile bool pspIsRebooting = false;
// This is called on EmuThread before RunLoop.
void Core_ProcessStepping(MIPSDebugInterface *cpu);
void ResetUIState() { void ResetUIState() {
globalUIState = UISTATE_MENU; globalUIState = UISTATE_MENU;
} }
@ -386,7 +389,6 @@ void Core_UpdateState(CoreState newState) {
if ((coreState == CORE_RUNNING || coreState == CORE_NEXTFRAME) && newState != CORE_RUNNING) if ((coreState == CORE_RUNNING || coreState == CORE_NEXTFRAME) && newState != CORE_RUNNING)
coreStatePending = true; coreStatePending = true;
coreState = newState; coreState = newState;
Core_UpdateSingleStep();
} }
void Core_UpdateDebugStats(bool collectStats) { void Core_UpdateDebugStats(bool collectStats) {
@ -629,7 +631,7 @@ void PSP_RunLoopUntil(u64 globalticks) {
if (coreState == CORE_POWERDOWN || coreState == CORE_BOOT_ERROR || coreState == CORE_RUNTIME_ERROR) { if (coreState == CORE_POWERDOWN || coreState == CORE_BOOT_ERROR || coreState == CORE_RUNTIME_ERROR) {
return; return;
} else if (coreState == CORE_STEPPING) { } else if (coreState == CORE_STEPPING) {
Core_ProcessStepping(); Core_ProcessStepping(currentDebugMIPS);
return; return;
} }

View File

@ -74,8 +74,8 @@ static void SetPauseAction(PauseAction act, bool waitComplete = true) {
pauseAction = act; pauseAction = act;
pauseLock.unlock(); pauseLock.unlock();
if (coreState == CORE_STEPPING && act != PAUSE_CONTINUE) // if (coreState == CORE_STEPPING && act != PAUSE_CONTINUE)
Core_UpdateSingleStep(); // Core_UpdateSingleStep();
actionComplete = false; actionComplete = false;
pauseWait.notify_all(); pauseWait.notify_all();

View File

@ -208,7 +208,7 @@ void CDisasm::step(CPUStepType stepType) {
lastTicks_ = CoreTiming::GetTicks(); lastTicks_ = CoreTiming::GetTicks();
u32 stepSize = ptr->getInstructionSizeAt(cpu->GetPC()); u32 stepSize = ptr->getInstructionSizeAt(cpu->GetPC());
Core_PerformStep(cpu, stepType, stepSize); Core_RequestSingleStep(stepType, stepSize);
} }
void CDisasm::runToLine() { void CDisasm::runToLine() {