diff --git a/ps2xRecomp/src/lib/control_flow_analyzer.cpp b/ps2xRecomp/src/lib/control_flow_analyzer.cpp index a099f5cf9..2b27ffc9f 100644 --- a/ps2xRecomp/src/lib/control_flow_analyzer.cpp +++ b/ps2xRecomp/src/lib/control_flow_analyzer.cpp @@ -385,7 +385,14 @@ namespace ps2recomp } } } - if (!foundTable) + // Only an unresolved computed *jump* can land on an arbitrary + // instruction of this function and therefore force every address to + // become an entry point. JALR is a call: it transfers control to + // another function and comes back to the instruction after the delay + // slot, which is already queued as a resume target above. Treating a + // call like a jump here promotes the whole function for what is + // usually just a function pointer or virtual dispatch. + if (!foundTable && jrInst->function != SPECIAL_JALR) { needsIndirectFallback = true; } diff --git a/ps2xTest/src/code_generator_tests.cpp b/ps2xTest/src/code_generator_tests.cpp index 2dd9c531f..d9128c644 100644 --- a/ps2xTest/src/code_generator_tests.cpp +++ b/ps2xTest/src/code_generator_tests.cpp @@ -529,7 +529,7 @@ void register_code_generator_tests() "unresolved JR should not pretend it has a resolved local jump table"); }); - tc.Run("unresolved JALR marks internal labels as indirect fallback resume entries", [](TestCase &t) { + tc.Run("unresolved JALR resumes after the call without promoting the function", [](TestCase &t) { Function func; func.name = "unresolved_jalr_fallback"; func.start = 0x3200; @@ -548,10 +548,21 @@ void register_code_generator_tests() CodeGenerator gen({}, {}); CodeGenerator::AnalysisResult analysis = gen.collectInternalBranchTargets(func, instructions); - t.IsTrue(analysis.indirectFallbackEntryPoints.contains(0x320Cu), - "unresolved JALR should register internal labels as resumable entries for the owning function"); + // JALR is a call: it returns past the delay slot, so 0x320C is the only + // address in this function that has to be reachable from outside. + t.IsTrue(analysis.resumeEntryPoints.contains(0x320Cu), + "unresolved JALR should mark its return pc as resumable"); t.IsTrue(analysis.entryPoints.contains(0x320Cu), - "unresolved JALR fallback targets should still emit labels in the owner"); + "unresolved JALR resume pc should still emit a label in the owner"); + + // Both sets feed the same owner resume-target list, so the return pc is + // registered either way; what must not happen is the whole-function + // promotion reserved for jumps that could land anywhere. + t.IsFalse(analysis.indirectFallbackEntryPoints.contains(0x3210u), + "an indirect call must not promote unrelated instructions to entry points"); + t.IsFalse(analysis.indirectFallbackEntryPoints.contains(0x3200u), + "an indirect call must not promote the function start to a fallback entry"); + t.IsFalse(analysis.jumpTableTargets.contains(0x3204u), "unresolved JALR should not pretend it has a resolved local jump table"); });