Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion ps2xRecomp/src/lib/control_flow_analyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,12 @@ namespace ps2recomp
bool needsIndirectFallback = false;
for (const Instruction *jrInst : indirectJumps)
{
if (jrInst->function == SPECIAL_JALR)
if (jrInst->function == SPECIAL_JALR && jrInst->rd != 0u)
{
// A link-producing JALR is emitted as an indirect call through the runtime dispatcher. Only its
// return PC can resume in the caller; JALR $zero remains an unresolved indirect jump below.
queueResumeEntryTarget(jrInst->address + 8u);
continue;
}

bool foundTable = false;
Expand Down
43 changes: 39 additions & 4 deletions ps2xTest/src/code_generator_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,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 only marks its return pc as resumable", [](TestCase &t) {
Function func;
func.name = "unresolved_jalr_fallback";
func.start = 0x3200;
Expand All @@ -647,14 +647,49 @@ 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");
t.IsTrue(analysis.resumeEntryPoints.contains(0x320Cu),
"unresolved JALR should register its return pc as resumable");
t.IsTrue(analysis.entryPoints.contains(0x320Cu),
"unresolved JALR fallback targets should still emit labels in the owner");
"unresolved JALR return pc should emit a label in the caller");
t.IsFalse(analysis.indirectFallbackEntryPoints.contains(0x3200u),
"unresolved JALR should not promote the caller start as an indirect jump target");
t.IsFalse(analysis.indirectFallbackEntryPoints.contains(0x3210u),
"unresolved JALR should not promote unrelated caller instructions");
t.IsFalse(analysis.entryPoints.contains(0x3210u),
"unresolved JALR should not emit labels for unrelated caller instructions");
t.IsFalse(analysis.jumpTableTargets.contains(0x3204u),
"unresolved JALR should not pretend it has a resolved local jump table");
});

tc.Run("unresolved JALR without a link register retains indirect fallback", [](TestCase &t) {
Function func;
func.name = "unresolved_jalr_tail_jump";
func.start = 0x3300;
func.end = 0x3320;
func.isRecompiled = true;
func.isStub = false;

std::vector<Instruction> instructions{
makeNop(0x3300),
makeJalr(0x3304, 25, 0),
makeNop(0x3308),
makeNop(0x330C),
makeNop(0x3310),
};

CodeGenerator gen({}, {});
CodeGenerator::AnalysisResult analysis = gen.collectInternalBranchTargets(func, instructions);

t.IsFalse(analysis.resumeEntryPoints.contains(0x330Cu),
"JALR without a link register should not add a return resume entry");
t.IsTrue(analysis.indirectFallbackEntryPoints.contains(0x330Cu),
"JALR without a link register should retain unresolved indirect-jump fallback");
t.IsTrue(analysis.entryPoints.contains(0x3310u),
"JALR without a link register should retain fallback labels in the owner");
t.IsFalse(analysis.jumpTableTargets.contains(0x3304u),
"unresolved JALR without a link register should not fabricate a jump table");
});

tc.Run("syscall marks the following instruction as a resume entry", [](TestCase &t) {
// Shape of a real SDK syscall wrapper:
// addiu $v1, $zero, <num> ; syscall ; jr $ra ; <delay slot>
Expand Down