From b2cfdac2363b2b700a2a2b351daff266a0d7c27a Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Mon, 22 Jun 2026 14:09:44 -0700 Subject: [PATCH] fntrace: capture dispatch-level bail source (runtime-only, no BIOS regen) The dominant Tomba 2 splash-spin bails are emitted by the generated dispatch trampoline (compiled functions C-returning wild), which the direct-call psx_call_contract ledger can't see. fntrace_record() runs at the top of every psx_dispatch_impl iteration; on a g_psx_bail_flattened delta it feeds (g_debug_current_func_addr = the just-bailed compiled fn, target = wild dest) into the traps.c bail ledger -> surfaced in the heartbeat as bail_top_site_ra / bail_top_wild_pc. Survives the bail-storm degradation (read from the file). Pinned the root cause: dominant bailing fn = 0xB0 (B0 syscall vector); a nested B0 syscall returns non-locally to its outer frame's return (0x8008AE50, the jal+8 of a `jal 0x80085900` in high RAM), the wild-return contract bails, the outer frame's $sp is not restored, and the guest degenerates to $ra=$sp=1 in an infinite bail loop -- the wild-call-contract family (Bug A/C/D) on the B0 path. Co-Authored-By: Claude Opus 4.8 --- runtime/src/fntrace.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/runtime/src/fntrace.c b/runtime/src/fntrace.c index a3ad1883..ce9d59f0 100644 --- a/runtime/src/fntrace.c +++ b/runtime/src/fntrace.c @@ -46,6 +46,28 @@ static inline int armed_match(uint32_t target) { } void fntrace_record(CPUState* cpu, uint32_t target) { + /* Dispatch-level bail-source capture (Tomba 2 wild-return spin, runtime-only, + * no BIOS regen). The generated psx_dispatch_impl calls us at the top of each + * dispatch iteration. When a previous iteration bail-FLATTENED a compiled + * function's wild return, g_psx_bail_flattened has incremented and + * g_debug_current_func_addr still holds that bailing function, while `target` + * is the wild destination it returned to. Feed (bailing_fn, wild_target) into + * the traps.c bail ledger (surfaced in the heartbeat as bail_top_site_ra / + * bail_top_wild_pc) so the dominant wild-returning function is readable even + * when the bail storm makes the window "Not Responding". */ + { + extern uint64_t g_psx_bail_flattened; + extern uint32_t g_debug_current_func_addr; + extern void psx_bail_record(uint32_t site_ra, uint32_t site_sp, + uint32_t wild_pc, uint32_t guest_sp); + static uint64_t s_last_flat = 0; + uint64_t f = g_psx_bail_flattened; + if (f != s_last_flat) { + s_last_flat = f; + psx_bail_record(g_debug_current_func_addr, cpu->gpr[29], target, cpu->gpr[31]); + } + } + if (!s_game_started && s_game_entry_phys != 0) { if ((target & 0x1FFFFFFFu) == s_game_entry_phys) { s_game_started = 1;