diff --git a/ps2xRecomp/include/ps2recomp/code_generator.h b/ps2xRecomp/include/ps2recomp/code_generator.h index 04adfdea..d3221e62 100644 --- a/ps2xRecomp/include/ps2recomp/code_generator.h +++ b/ps2xRecomp/include/ps2recomp/code_generator.h @@ -38,6 +38,10 @@ namespace ps2recomp struct AnalysisResult { std::unordered_set entryPoints; std::unordered_map> jumpTableTargets; + // Targets from TOML-configured jump tables that are OUTSIDE the + // function boundary. Codegen emits lookupFunction calls for these + // instead of internal goto labels. + std::unordered_set externalJumpTableTargets; }; std::string generateFunction(const Function &function, const std::vector &instructions, const bool &useHeaders); diff --git a/ps2xRecomp/src/lib/code_generator.cpp b/ps2xRecomp/src/lib/code_generator.cpp index 666a59e7..6b03c61a 100644 --- a/ps2xRecomp/src/lib/code_generator.cpp +++ b/ps2xRecomp/src/lib/code_generator.cpp @@ -432,7 +432,22 @@ namespace ps2recomp ss << " switch (jumpTarget) {\n"; for (uint32_t t : sortedInternalTargets) { - ss << fmt::format(" case 0x{:X}u: goto label_{:x};\n", t, t); + if (analysisResult.externalJumpTableTargets.contains(t)) + { + // External target from TOML config: emit lookupFunction + // call. The target is in a different compiled function + // (e.g., a jump table case block in another CSV entry). + // After the call, chain if pc stays in the external range. + ss << fmt::format(" case 0x{:X}u: {{\n", t); + ss << " auto __extFn = runtime->lookupFunction(jumpTarget);\n"; + ss << " if (__extFn) __extFn(rdram, ctx, runtime);\n"; + ss << " return;\n"; + ss << " }\n"; + } + else + { + ss << fmt::format(" case 0x{:X}u: goto label_{:x};\n", t, t); + } } ss << " default: break;\n"; ss << " }\n"; @@ -781,28 +796,50 @@ namespace ps2recomp const auto configuredTableIt = m_configJumpTableTargetsByAddress.find(tableAddress); if (configuredTableIt != m_configJumpTableTargetsByAddress.end()) { - std::vector jrTargets; - jrTargets.reserve(configuredTableIt->second.size()); + std::vector internalTargets; + std::vector externalTargets; for (uint32_t target : configuredTableIt->second) { if (target >= function.start && target < function.end && instructionAddresses.contains(target)) { - jrTargets.push_back(target); + internalTargets.push_back(target); + } + else + { + externalTargets.push_back(target); } } - if (!jrTargets.empty()) + // Internal targets: handled as goto labels (existing path) + if (!internalTargets.empty()) { - std::sort(jrTargets.begin(), jrTargets.end()); - jrTargets.erase(std::unique(jrTargets.begin(), jrTargets.end()), jrTargets.end()); - result.jumpTableTargets[jrInst->address] = jrTargets; - for (uint32_t target : jrTargets) + std::sort(internalTargets.begin(), internalTargets.end()); + internalTargets.erase(std::unique(internalTargets.begin(), internalTargets.end()), internalTargets.end()); + result.jumpTableTargets[jrInst->address] = internalTargets; + for (uint32_t target : internalTargets) { result.entryPoints.insert(target); } foundTable = true; } + + // External targets: will be emitted as function calls + // in the jr dispatch switch. Store them alongside internal + // targets so the codegen can emit both gotos and calls. + if (!externalTargets.empty()) + { + auto &targets = result.jumpTableTargets[jrInst->address]; + for (uint32_t target : externalTargets) + { + targets.push_back(target); + } + std::sort(targets.begin(), targets.end()); + targets.erase(std::unique(targets.begin(), targets.end()), targets.end()); + // Mark external targets so codegen emits calls not gotos + result.externalJumpTableTargets.insert(externalTargets.begin(), externalTargets.end()); + foundTable = true; + } } uint32_t unshiftedIndexReg = 0;