interrupts: never publish pc=0 from same-thread restore at top-level depth; exit-reg diagnostics

pc=0 from the same-thread-restore epilogue means "the interrupted native
frame is still on the host stack below - return and it continues". At
dispatch depth <= 0 no such frame can exist and the 0 reaches the main
loop as a bogus GUEST_EXIT, so prefer the compiled interrupt resume PC
there (the same remedy the scheduler top-level-resume case already uses).

Honest scope note: this closes a real hole by construction, but does NOT
fix the observed WipEout 3 static-bake boot exit (deterministic
"execution completed, PC=0", ra=0x800D7FC0, epc=0x8015FAEC) - that death
reproduces unchanged, so its escape route is elsewhere; investigation
continues. Also: the top-level exit path now prints the exit registers
(ra/sp/epc/sr/func/last_store_pc/v0/a0/t9), which is how the crash was
attributed on play builds without debug tools.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
tetrisgm
2026-08-19 10:03:48 -07:00
parent 58c1f40276
commit 2edce1b66f
2 changed files with 26 additions and 1 deletions
+12 -1
View File
@@ -1844,7 +1844,18 @@ irq_deliver_eval:
same_thread_resume = 1;
{
extern int psx_scheduler_top_level_resume_active(void);
if (psx_scheduler_top_level_resume_active()) {
/* pc=0 means "the interrupted native frame is still on the host
* stack below us — return and it continues". That contract also
* breaks whenever dispatch has already collapsed to TOP LEVEL
* (depth 0): statically-baked overlay functions cross into AOT
* text via CPS returns, so the interrupted chain may hold no
* host frames at all, and a published 0 reaches the main loop
* as a bogus GUEST_EXIT (WipEout 3 ntsc120full8 static bake:
* deterministic "execution completed, PC=0" ~10 s into boot,
* ra=0x800D7FC0 epc=0x8015FAEC). Same remedy as the scheduler
* case: prefer the compiled interrupt resume PC. */
if (psx_scheduler_top_level_resume_active() ||
g_psx_dispatch_depth <= 0) {
uint32_t resume = s_compiled_interrupt_resume_pc;
if (resume == 0u)
resume = s_last_interrupt_check_pc;
+14
View File
@@ -14380,6 +14380,20 @@ session_reboot:
}
std::fprintf(stdout, "psxrecomp runtime: execution completed, PC=0x%08X\n", cpu.pc);
/* The PC=0 top-level exit is an abnormal-boot class (see interrupts.c:
* approximate exception-resume PCs severing the live native chain). The
* registers at exit name the culprit without needing a debug-tools build:
* ra = who returned/jumped here, epc = the last exception context. */
{
extern uint32_t g_debug_current_func_addr, g_debug_last_store_pc;
std::fprintf(stdout,
"psxrecomp runtime: [exit regs] ra=0x%08X sp=0x%08X "
"epc=0x%08X sr=0x%08X func=0x%08X last_store_pc=0x%08X "
"v0=0x%08X a0=0x%08X t9=0x%08X\n",
cpu.gpr[31], cpu.gpr[29], cpu.cop0[14], cpu.cop0[12],
g_debug_current_func_addr, g_debug_last_store_pc,
cpu.gpr[2], cpu.gpr[4], cpu.gpr[25]);
}
{ extern uint64_t g_slice_fired, g_slice_irq_taken, g_dirty_ram_insns_run;
extern uint32_t g_slice_exit_pc, g_slice_exit_reason, g_slice_exit_iter;
extern uint32_t g_slice_exit_dispatchable, g_slice_exit_dirty, g_slice_exit_in_text, g_slice_exit_want;