mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-28 10:59:16 -04:00
dispatch call contract: continuations only run when the guest really returns
Bug D root cause (Tomba chest freeze, also QA issue #1): the game's own dispatcher func_80056E08 has a dead jumptable case (type 0x21 -> jal 0x80120B3C) whose target is mid-function in EVERY overlay variant on the disc (only the boot EXE references it; the real handler entry is 0x80120AC8). Entering mid-function skips the prologue, runs an epilogue for a frame that was never pushed, and returns through garbage. Our C model then resumed the suspended generated continuations on the moved guest stack -> double-executed tails -> infinite zombie loop (~3K wild calls/frame) -> MMIO fatal. Family root of Bugs A/C/D. The contract: a generated C continuation may only run if the guest actually arrived at it ($ra == call site return address, $sp == caller sp at the call). Violations begin a "bail" unwind: cpu->pc := the wild jr's true target, every generated frame returns immediately, and the unwind resolves at the first enclosing site whose contract matches (or flattens at the outermost dispatch loop, re-dispatching the wild target on a clean host stack -- matching real-HW one-stack semantics). - cpu_state.h: bail state + psx_call_contract() helper (+ overlay-DLL pointer indirection under PSX_OVERLAY_DLL_BUILD) - emitters: (ra, sp) checks at every BIOS and game jal/jalr site; psx_dispatch_impl validates pc==0 and stop-hits against (stop, sp), handles bail resolve/propagate/flatten - dirty_ram_interp: surfaced calls validate the contract on return; bail aborts the interp run; tail transfers into compiled code now run with pc cleared (closes a latent double-run on plain C returns) - overlay cache ABI v2: DLLs export overlay_abi(); loader rejects and deletes stale DLLs (autocompile regenerates with contract checks) - telemetry: bail_first/resolved/flattened/anomaly in heartbeat + dump headers; anomaly guard at exception entry; TCP_COMMANDS.md docs Verified: BIOS+Tomba regen, boot to intro at locked 60.0 fps, all bail counters 0 through boot/title/attract (no false positives). Chest-area gameplay verification pending (requires user play). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1199,13 +1199,20 @@ std::string CodeGenerator::translate_basic_block(
|
||||
ss << config_.indent << "return;\n";
|
||||
}
|
||||
} else if (block.exit_instr.type == ControlFlowType::JumpLink) {
|
||||
// Function call (jal)
|
||||
// Function call (jal). The call contract (Bug D family)
|
||||
// guards the continuation: it may only run if the guest
|
||||
// actually returned here with the caller's $sp.
|
||||
ss << config_.indent << "{ uint32_t _csp = cpu->gpr[29];\n";
|
||||
ss << config_.indent << "cpu->gpr[31] = " << fmt::format("0x{:08X}; /* return address */\n", addr + 8);
|
||||
uint32_t target = block.exit_instr.target;
|
||||
if (known_functions_.count(target) > 0) {
|
||||
ss << config_.indent << fmt::format("func_{:08X}(cpu); /* jal */\n", target);
|
||||
ss << config_.indent << fmt::format("if (psx_call_contract(cpu, 0x{:08X}u, _csp)) return; }}\n", addr + 8);
|
||||
} else {
|
||||
ss << config_.indent << fmt::format("call_by_address(cpu, 0x{:08X}u); /* external jal */\n", target);
|
||||
/* psx_dispatch_call validated the (ra, sp) contract;
|
||||
* only propagate an active bail unwind here. */
|
||||
ss << config_.indent << "if (g_psx_call_bail) return; (void)_csp; }\n";
|
||||
}
|
||||
if (!block.successors.empty()) {
|
||||
ss << config_.indent
|
||||
@@ -1234,6 +1241,9 @@ std::string CodeGenerator::translate_basic_block(
|
||||
// Dispatch indirect call — target is a runtime register value
|
||||
ss << config_.indent << fmt::format("call_by_address(cpu, {}); /* jalr {} */\n",
|
||||
reg_name(rs), reg_name(rs));
|
||||
/* psx_dispatch_call validated the (ra, sp) contract;
|
||||
* only propagate an active bail unwind here. */
|
||||
ss << config_.indent << "if (g_psx_call_bail) return;\n";
|
||||
|
||||
// Continue to successor block (instruction at return address)
|
||||
if (!block.successors.empty()) {
|
||||
|
||||
Reference in New Issue
Block a user