From 905b4edfa88d79092835bb4235e53e778a5b24f8 Mon Sep 17 00:00:00 2001 From: TH3BACKLOG Date: Mon, 13 Jul 2026 08:50:36 -0400 Subject: [PATCH] fix(recomp): advance ctx->pc on fallthrough functions with no terminating branch (#168) * fix(recomp): advance ctx->pc on fallthrough functions with no terminating branch FunctionEmitter::emit only ever advances ctx->pc via the per-instruction `ctx->pc = 0xu;` assignment (overwritten by the next instruction in the same function) or via handleBranchDelaySlots when the last instruction is a branch/jump. A function whose last instruction is neither (e.g. a lone padduw/NOP-style instruction with no terminator) leaves ctx->pc pointing at its own last instruction forever after returning, since nothing ever advances it to the next function. dispatchLoop then reads ctx->pc, looks up the same function, and calls it again -- forever. No exception, no crash, just an infinite loop that silently never makes forward progress. Reproduced on SDBZ's SLUS_214.42 ELF entry point: 0x100008 is emitted as a standalone 1-instruction function (padduw $at, $zero, $zero) with no branch, causing dispatchLoop to spin on pc=0x100008 indefinitely. Fix: track whether the last processed instruction had a delay slot (i.e. was a branch/jump); if the function ends without one, emit an unconditional ctx->pc = function.end before closing the function so dispatchLoop resumes at the next function instead of spinning. * review: trim overly verbose comment per ran-j feedback --- ps2xRecomp/src/lib/function_emitter.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ps2xRecomp/src/lib/function_emitter.cpp b/ps2xRecomp/src/lib/function_emitter.cpp index af98cda..53a6005 100644 --- a/ps2xRecomp/src/lib/function_emitter.cpp +++ b/ps2xRecomp/src/lib/function_emitter.cpp @@ -103,9 +103,12 @@ namespace ps2recomp << std::dec; ss << "\n"; + bool lastInstructionWasControlFlow = false; + for (size_t i = 0; i < instructions.size(); ++i) { const Instruction &inst = instructions[i]; + lastInstructionWasControlFlow = inst.hasDelaySlot; if (internalTargets.contains(inst.address)) { @@ -223,6 +226,13 @@ namespace ps2recomp } } + // Fallthrough with no terminating branch: advance ctx->pc past the function so dispatchLoop doesn't re-call it forever. + if (!instructions.empty() && !lastInstructionWasControlFlow) + { + ss << " ctx->pc = 0x" << std::hex << function.end << "u;\n" + << std::dec; + } + ss << "}\n"; return ss.str(); }