diff --git a/runtime/src/interrupts.c b/runtime/src/interrupts.c index 9825b456..4a4e03c8 100644 --- a/runtime/src/interrupts.c +++ b/runtime/src/interrupts.c @@ -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; diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index 64b517bf..436295e4 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -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;