mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-09-02 18:55:13 +02:00
PrepareResume() used Core_RequestCPUStep(CPUStepType::Into, 1) to step past a delay slot instruction before deciding whether to add a breakpoint and call Core_Resume() - but Core_RequestCPUStep() only queues that step for Core_ProcessStepping() to perform later (on the next iteration of the normal stepping-mode loop). Every caller (Into's cross-thread branch, Over, Out, RunUntil, HLE) immediately inspected currentMIPS->pc/inDelaySlot right after PrepareResume() returned to decide what to do next - reading stale, pre-step state, since the queued step hadn't run yet. Worse: those callers then call Core_Resume(), which sets coreState back to CORE_RUNNING_CPU. Core_ProcessStepping() only processes g_cpuStepCommand when coreState is CORE_STEPPING_CPU/STEPPING_GE/RUNNING_GE, so once resumed, the queued step is never processed at all - not just late, silently dropped, leaving g_cpuStepCommand permanently set until the next Core_Break() resets it. Any cpu.step*/cpu.runUntil request a client issues in that window (CPU resumed running, breakpoint not yet hit again) hits Core_RequestCPUStep()'s "Can't submit two steps in one host frame" guard and is silently ignored, since none of these call sites check its return value - this is the "step-out sometimes just doesn't do anything" flakiness reported against this file. PrepareResume() is only ever called from within a Core_RunOnCPUThread() callback, so it's always already running on the CPU thread - safe to single-step synchronously (currentMIPS->SingleStep(), matching how Core_PerformCPUStep()'s own CPUStepType::Into case does it) instead of queuing an async request whose completion every caller then assumes without verifying. Verified via UnitTest.exe all (49/49). Attempted to force a live repro via wsdbg against a delay-slot jal in a demo ELF; wasn't able to reliably trigger the failure window externally (by the time a client's next command arrives, the CPU has typically already reached its next breakpoint and Core_Break() has cleaned up the stale state first) - the race window is real per the code trace above but appears to be narrow enough that it mainly shows up under real usage timing (a slow-to-reach next breakpoint, or a fast follow-up command from a script/UI), not simple synchronous scripting. The fix is unconditionally more correct regardless: it replaces a fire-and-forget async request every caller immediately assumed had already completed with a direct synchronous call that actually has by the time the next line runs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9