From 7cf34c5d1fda92646560390a1df5a5498a03cf6b Mon Sep 17 00:00:00 2001 From: James Sandri <7078671+jlsandri@users.noreply.github.com> Date: Fri, 27 Mar 2026 15:13:52 +1100 Subject: [PATCH 1/3] Remove dead else branch from codegen + add ps2_recompiled_functions.h only to register_functions --- ps2xRecomp/src/lib/code_generator.cpp | 34 ++++++++++----------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/ps2xRecomp/src/lib/code_generator.cpp b/ps2xRecomp/src/lib/code_generator.cpp index 666a59e7..a257e16e 100644 --- a/ps2xRecomp/src/lib/code_generator.cpp +++ b/ps2xRecomp/src/lib/code_generator.cpp @@ -315,7 +315,12 @@ namespace ps2recomp if (!funcName.empty()) { - ss << fmt::format(" if (runtime->hasFunction(0x{:X}u)) {{\n", target); + // Always use runtime dispatch (lookupFunction). The else branch + // that direct-called by symbol is dead code: all functions are + // registered before any execute. Removing it eliminates the + // #include "ps2_recompiled_functions.h" dependency from each + // .cpp file, dramatically improving ccache hit rates. + ss << " {\n"; ss << fmt::format(" auto targetFn = runtime->lookupFunction(0x{:X}u);\n", target); if (branchInst.opcode == OPCODE_J) { @@ -326,20 +331,7 @@ namespace ps2recomp ss << " const uint32_t __entryPc = ctx->pc;\n"; ss << " targetFn(rdram, ctx, runtime);\n"; ss << fmt::format(" if (ctx->pc == __entryPc) {{ ctx->pc = 0x{:X}u; }}\n", fallthroughPc); - ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ return; }}\n", fallthroughPc); - } - ss << " } else {\n"; - - if (branchInst.opcode == OPCODE_J) - { - ss << " " << funcName << "(rdram, ctx, runtime); return;\n"; - } - else - { - ss << " const uint32_t __entryPc = ctx->pc;\n"; - ss << " " << funcName << "(rdram, ctx, runtime);\n"; - ss << fmt::format(" if (ctx->pc == __entryPc) {{ ctx->pc = 0x{:X}u; }}\n", fallthroughPc); - ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ return; }}\n", fallthroughPc); + ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ fprintf(stderr, \"[PC_MISMATCH] at 0x{:X}: called 0x%x, expected ret 0x{:X}, got 0x%x\\n\", __entryPc, ctx->pc); return; }}\n", fallthroughPc, branchInst.address, fallthroughPc); } ss << " }\n"; } @@ -372,7 +364,7 @@ namespace ps2recomp } else { - ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ return; }}\n", fallthroughPc); + ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ fprintf(stderr, \"[PC_MISMATCH] at 0x{:X}: called reloc, expected ret 0x{:X}, got 0x%x\\n\", ctx->pc); return; }}\n", fallthroughPc, branchInst.address, fallthroughPc); } emittedRelocCall = true; } @@ -391,7 +383,7 @@ namespace ps2recomp else { ss << fmt::format(" if (ctx->pc == __entryPc) {{ ctx->pc = 0x{:X}u; }}\n", fallthroughPc); - ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ return; }}\n", fallthroughPc); + ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ fprintf(stderr, \"[PC_MISMATCH] at 0x{:X}: called 0x{:X}, expected ret 0x{:X}, got 0x%x\\n\", ctx->pc); return; }}\n", fallthroughPc, branchInst.address, target, fallthroughPc); } ss << " }\n"; } @@ -449,7 +441,7 @@ namespace ps2recomp ss << " const uint32_t __entryPc = ctx->pc;\n"; ss << " targetFn(rdram, ctx, runtime);\n"; ss << fmt::format(" if (ctx->pc == __entryPc) {{ ctx->pc = 0x{:X}u; }}\n", fallthroughPc); - ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ return; }}\n", fallthroughPc); + ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ fprintf(stderr, \"[PC_MISMATCH] at 0x{:X}: jalr 0x%x, expected ret 0x{:X}, got 0x%x\\n\", __entryPc, ctx->pc); return; }}\n", fallthroughPc, branchInst.address, fallthroughPc); ss << " }\n"; } @@ -899,7 +891,6 @@ namespace ps2recomp { ss << "#include \"ps2_runtime_macros.h\"\n"; ss << "#include \"ps2_runtime.h\"\n"; - ss << "#include \"ps2_recompiled_functions.h\"\n"; ss << "#include \"ps2_recompiled_stubs.h\"\n\n"; ss << "#include \"ps2_syscalls.h\"\n"; ss << "#include \"ps2_stubs.h\"\n\n"; @@ -3717,10 +3708,11 @@ namespace ps2recomp ss << "#include \"ps2_runtime.h\"\n"; ss << "#include \"ps2_recompiled_functions.h\"\n"; ss << "#include \"ps2_stubs.h\"\n"; - ss << "#include \"ps2_recompiled_stubs.h\"//this will give duplicated erros because runtime maybe has it define already, just delete the TODOS ones\n"; + ss << "#include \"ps2_recompiled_stubs.h\"\n"; ss << "#include \"ps2_syscalls.h\"\n\n"; - // Registration function + // Registration function — this file needs ps2_recompiled_functions.h + // for forward declarations of all function symbols ss << "void registerAllFunctions(PS2Runtime& runtime) {\n"; std::vector> normalFunctions; From ab0832d369e79084692ef86e5f56df4d4398fc4f Mon Sep 17 00:00:00 2001 From: James Sandri <7078671+jlsandri@users.noreply.github.com> Date: Fri, 27 Mar 2026 21:39:53 +1100 Subject: [PATCH 2/3] Handle continuation PCs without separate entry functions --- ps2xRecomp/include/ps2recomp/code_generator.h | 3 + ps2xRecomp/src/lib/code_generator.cpp | 97 ++++++++++++++++++- ps2xRecomp/src/lib/ps2_recompiler.cpp | 58 +++++++---- 3 files changed, 138 insertions(+), 20 deletions(-) diff --git a/ps2xRecomp/include/ps2recomp/code_generator.h b/ps2xRecomp/include/ps2recomp/code_generator.h index 04adfdea..c2a9a687 100644 --- a/ps2xRecomp/include/ps2recomp/code_generator.h +++ b/ps2xRecomp/include/ps2recomp/code_generator.h @@ -49,6 +49,7 @@ namespace ps2recomp void setBootstrapInfo(const BootstrapInfo &info); void setRelocationCallNames(const std::unordered_map &callNames); void setConfiguredJumpTables(const std::vector &jumpTables); + void setFunctionBounds(const std::vector &functions); AnalysisResult collectInternalBranchTargets(const Function &function, const std::vector &instructions); @@ -58,6 +59,8 @@ namespace ps2recomp std::unordered_map m_renamedFunctions; std::unordered_map m_relocationCallNames; std::unordered_map> m_configJumpTableTargetsByAddress; + std::unordered_map> m_functionResumePoints; + std::unordered_map m_entryOwnerStarts; const std::vector
& m_sections; BootstrapInfo m_bootstrapInfo; diff --git a/ps2xRecomp/src/lib/code_generator.cpp b/ps2xRecomp/src/lib/code_generator.cpp index a257e16e..08e5c84d 100644 --- a/ps2xRecomp/src/lib/code_generator.cpp +++ b/ps2xRecomp/src/lib/code_generator.cpp @@ -192,6 +192,63 @@ namespace ps2recomp return "ps2_" + sanitized; } + void CodeGenerator::setFunctionBounds(const std::vector &functions) + { + m_functionResumePoints.clear(); + m_entryOwnerStarts.clear(); + + std::vector owners; + owners.reserve(functions.size()); + for (const auto &function : functions) + { + if (!function.isRecompiled || function.isStub || function.isSkipped) + { + continue; + } + if (function.name.rfind("entry_", 0) == 0) + { + continue; + } + owners.push_back(&function); + } + + for (const auto &function : functions) + { + if (function.name.rfind("entry_", 0) != 0) + { + continue; + } + + const Function *bestOwner = nullptr; + for (const Function *candidate : owners) + { + if (function.start <= candidate->start || function.start >= candidate->end) + { + continue; + } + if (!bestOwner || candidate->start > bestOwner->start) + { + bestOwner = candidate; + } + } + + if (!bestOwner) + { + continue; + } + + m_entryOwnerStarts[function.start] = bestOwner->start; + m_functionResumePoints[bestOwner->start].push_back(function.start); + } + + for (auto &[ownerStart, resumePoints] : m_functionResumePoints) + { + (void)ownerStart; + std::sort(resumePoints.begin(), resumePoints.end()); + resumePoints.erase(std::unique(resumePoints.begin(), resumePoints.end()), resumePoints.end()); + } + } + std::string CodeGenerator::handleBranchDelaySlots( const Instruction &branchInst, const Instruction &delaySlot, @@ -900,6 +957,12 @@ namespace ps2recomp } AnalysisResult analysisResult = collectInternalBranchTargets(function, instructions); + std::unordered_set resumeTargets = analysisResult.entryPoints; + auto resumeIt = m_functionResumePoints.find(function.start); + if (resumeIt != m_functionResumePoints.end()) + { + resumeTargets.insert(resumeIt->second.begin(), resumeIt->second.end()); + } const std::unordered_set& internalTargets = analysisResult.entryPoints; ss << "// Function: " << function.name << "\n"; ss << "// Address: 0x" << std::hex << function.start << " - 0x" << function.end << std::dec << "\n"; @@ -917,6 +980,23 @@ namespace ps2recomp ss << " PS_LOG_ENTRY(\"" << sanitizedName << "\");\n"; ss << "#endif\n"; ss << "\n"; + if (!resumeTargets.empty()) + { + ss << " const uint32_t __resumePc = ctx->pc;\n"; + ss << fmt::format(" if (__resumePc != 0x{:X}u) {{\n", function.start); + ss << " switch (__resumePc) {\n"; + std::vector sortedResumeTargets(resumeTargets.begin(), resumeTargets.end()); + std::sort(sortedResumeTargets.begin(), sortedResumeTargets.end()); + for (uint32_t target : sortedResumeTargets) + { + ss << fmt::format(" case 0x{:X}u: ctx->pc = __resumePc; goto label_{:x};\n", + target, target); + } + ss << " default: break;\n"; + ss << " }\n"; + ss << " }\n"; + ss << "\n"; + } ss << " ctx->pc = 0x" << std::hex << function.start << "u;\n" << std::dec; ss << "\n"; @@ -925,7 +1005,7 @@ namespace ps2recomp { const Instruction &inst = instructions[i]; - if (internalTargets.contains(inst.address)) + if (resumeTargets.contains(inst.address)) { ss << "label_" << std::hex << inst.address << std::dec << ":\n"; } @@ -956,7 +1036,7 @@ namespace ps2recomp delaySlot = &syntheticDelaySlot; } - if (hasDecodedDelaySlot && internalTargets.contains(delaySlot->address)) + if (hasDecodedDelaySlot && resumeTargets.contains(delaySlot->address)) { ss << "label_" << std::hex << delaySlot->address << std::dec << ":\n"; } @@ -3729,6 +3809,19 @@ namespace ps2recomp continue; std::string generatedName = getFunctionName(function.start); + if (function.name.rfind("entry_", 0) == 0) + { + auto ownerIt = m_entryOwnerStarts.find(function.start); + if (ownerIt != m_entryOwnerStarts.end()) + { + generatedName = getFunctionName(ownerIt->second); + } + } + + if (generatedName.empty()) + { + continue; + } if (function.isSkipped) { diff --git a/ps2xRecomp/src/lib/ps2_recompiler.cpp b/ps2xRecomp/src/lib/ps2_recompiler.cpp index 2ea7d54c..210c3de1 100644 --- a/ps2xRecomp/src/lib/ps2_recompiler.cpp +++ b/ps2xRecomp/src/lib/ps2_recompiler.cpp @@ -74,6 +74,10 @@ namespace ps2recomp bool shouldGenerateCodeForFunction(const Function &function) { + if (function.isRecompiled && function.name.rfind("entry_", 0) == 0) + { + return false; + } return function.isRecompiled || function.isStub || function.isSkipped; } @@ -370,36 +374,22 @@ namespace ps2recomp const auto &instructions = decodedIt->second; - for (const auto &inst : instructions) + auto queuePendingEntry = [&](uint32_t target) { - auto targetOpt = getStaticEntryTarget(inst); - if (!targetOpt.has_value()) - { - continue; - } - - const StaticEntryTarget staticTarget = targetOpt.value(); - const uint32_t target = staticTarget.target; - if ((target & 0x3) != 0 || !isExecutableAddress(target)) { - continue; + return; } if (existingStarts.contains(target) || pendingStarts.contains(target)) { - continue; + return; } const bool targetInCurrentFunction = std::any_of( instructions.begin(), instructions.end(), [&](const Instruction &candidate) { return candidate.address == target; }); - if (targetInCurrentFunction && !staticTarget.isCall) - { - // jumps with the current decoded function remain labels/gotos. - continue; - } const Function *containingFunction = findContainingFunction(target); if (targetInCurrentFunction) @@ -410,7 +400,7 @@ namespace ps2recomp pending.containingEnd = function.end; pendingEntries.push_back(pending); pendingStarts.insert(target); - continue; + return; } PendingEntry pending{}; @@ -423,6 +413,37 @@ namespace ps2recomp pendingEntries.push_back(pending); pendingStarts.insert(target); + }; + + for (const auto &inst : instructions) + { + // Calls can yield and bubble their return PC up to the top-level + // dispatcher, so the post-call fallthrough must be resumable. + if (inst.isCall && inst.address <= (std::numeric_limits::max() - 8u)) + { + queuePendingEntry(inst.address + 8u); + } + + auto targetOpt = getStaticEntryTarget(inst); + if (!targetOpt.has_value()) + { + continue; + } + + const StaticEntryTarget staticTarget = targetOpt.value(); + const uint32_t target = staticTarget.target; + + const bool targetInCurrentFunction = std::any_of( + instructions.begin(), instructions.end(), + [&](const Instruction &candidate) + { return candidate.address == target; }); + if (targetInCurrentFunction && !staticTarget.isCall) + { + // jumps with the current decoded function remain labels/gotos. + continue; + } + + queuePendingEntry(target); } } @@ -996,6 +1017,7 @@ namespace ps2recomp if (m_codeGenerator) { m_codeGenerator->setRenamedFunctions(m_functionRenames); + m_codeGenerator->setFunctionBounds(m_functions); } if (m_bootstrapInfo.valid && m_codeGenerator) From ffeaddc019bc07545f06f1ff8768dd372622d9de Mon Sep 17 00:00:00 2001 From: James Sandri <7078671+jlsandri@users.noreply.github.com> Date: Sat, 28 Mar 2026 10:26:14 +1100 Subject: [PATCH 3/3] Codegen: add continuation re-entry loop for nested callee yields When a callee yields at an internal continuation PC, the caller now re-enters it in a while loop until it exits to the real fallthrough. Prevents nested continuations from leaking as false PC_MISMATCH. --- ps2xRecomp/src/lib/code_generator.cpp | 33 ++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/ps2xRecomp/src/lib/code_generator.cpp b/ps2xRecomp/src/lib/code_generator.cpp index 08e5c84d..697a0805 100644 --- a/ps2xRecomp/src/lib/code_generator.cpp +++ b/ps2xRecomp/src/lib/code_generator.cpp @@ -387,8 +387,17 @@ namespace ps2recomp { ss << " const uint32_t __entryPc = ctx->pc;\n"; ss << " targetFn(rdram, ctx, runtime);\n"; + ss << fmt::format(" while (runtime->hasFunction(ctx->pc) && runtime->lookupFunction(ctx->pc) == targetFn) {{\n"); + ss << " const uint32_t __resumePc = ctx->pc;\n"; + ss << fmt::format(" if (__resumePc == 0x{:X}u) {{ break; }}\n", fallthroughPc); + ss << " targetFn(rdram, ctx, runtime);\n"; + ss << fmt::format(" if (ctx->pc == __resumePc) {{ ctx->pc = 0x{:X}u; break; }}\n", fallthroughPc); + ss << " }\n"; ss << fmt::format(" if (ctx->pc == __entryPc) {{ ctx->pc = 0x{:X}u; }}\n", fallthroughPc); - ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ fprintf(stderr, \"[PC_MISMATCH] at 0x{:X}: called 0x%x, expected ret 0x{:X}, got 0x%x\\n\", __entryPc, ctx->pc); return; }}\n", fallthroughPc, branchInst.address, fallthroughPc); + ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{\n", fallthroughPc); + ss << fmt::format(" fprintf(stderr, \"[PC_MISMATCH] at 0x{:X}: called 0x%x, expected ret 0x{:X}, got 0x%x\\n\", __entryPc, ctx->pc);\n", branchInst.address, fallthroughPc); + ss << " return;\n"; + ss << " }\n"; } ss << " }\n"; } @@ -439,8 +448,17 @@ namespace ps2recomp } else { + ss << fmt::format(" while (runtime->hasFunction(ctx->pc) && runtime->lookupFunction(ctx->pc) == targetFn) {{\n"); + ss << " const uint32_t __resumePc = ctx->pc;\n"; + ss << fmt::format(" if (__resumePc == 0x{:X}u) {{ break; }}\n", fallthroughPc); + ss << " targetFn(rdram, ctx, runtime);\n"; + ss << fmt::format(" if (ctx->pc == __resumePc) {{ ctx->pc = 0x{:X}u; break; }}\n", fallthroughPc); + ss << " }\n"; ss << fmt::format(" if (ctx->pc == __entryPc) {{ ctx->pc = 0x{:X}u; }}\n", fallthroughPc); - ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ fprintf(stderr, \"[PC_MISMATCH] at 0x{:X}: called 0x{:X}, expected ret 0x{:X}, got 0x%x\\n\", ctx->pc); return; }}\n", fallthroughPc, branchInst.address, target, fallthroughPc); + ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{\n", fallthroughPc); + ss << fmt::format(" fprintf(stderr, \"[PC_MISMATCH] at 0x{:X}: called 0x{:X}, expected ret 0x{:X}, got 0x%x\\n\", ctx->pc);\n", branchInst.address, target, fallthroughPc); + ss << " return;\n"; + ss << " }\n"; } ss << " }\n"; } @@ -497,8 +515,17 @@ namespace ps2recomp ss << " auto targetFn = runtime->lookupFunction(jumpTarget);\n"; ss << " const uint32_t __entryPc = ctx->pc;\n"; ss << " targetFn(rdram, ctx, runtime);\n"; + ss << fmt::format(" while (runtime->hasFunction(ctx->pc) && runtime->lookupFunction(ctx->pc) == targetFn) {{\n"); + ss << " const uint32_t __resumePc = ctx->pc;\n"; + ss << fmt::format(" if (__resumePc == 0x{:X}u) {{ break; }}\n", fallthroughPc); + ss << " targetFn(rdram, ctx, runtime);\n"; + ss << fmt::format(" if (ctx->pc == __resumePc) {{ ctx->pc = 0x{:X}u; break; }}\n", fallthroughPc); + ss << " }\n"; ss << fmt::format(" if (ctx->pc == __entryPc) {{ ctx->pc = 0x{:X}u; }}\n", fallthroughPc); - ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ fprintf(stderr, \"[PC_MISMATCH] at 0x{:X}: jalr 0x%x, expected ret 0x{:X}, got 0x%x\\n\", __entryPc, ctx->pc); return; }}\n", fallthroughPc, branchInst.address, fallthroughPc); + ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{\n", fallthroughPc); + ss << fmt::format(" fprintf(stderr, \"[PC_MISMATCH] at 0x{:X}: jalr 0x%x, expected ret 0x{:X}, got 0x%x\\n\", __entryPc, ctx->pc);\n", branchInst.address, fallthroughPc); + ss << " return;\n"; + ss << " }\n"; ss << " }\n"; }