diff --git a/ps2xRecomp/src/lib/control_flow_analyzer.cpp b/ps2xRecomp/src/lib/control_flow_analyzer.cpp index a099f5cf9..ed7578dd1 100644 --- a/ps2xRecomp/src/lib/control_flow_analyzer.cpp +++ b/ps2xRecomp/src/lib/control_flow_analyzer.cpp @@ -135,6 +135,14 @@ namespace ps2recomp for (const auto &inst : instructions) { + // A guest-installed syscall handler runs as a separate invocation, + // so the scheduler resumes this thread at syscall+4 and needs an + // entry point there. +4, not +8: syscall has no delay slot. + if (inst.opcode == OPCODE_SPECIAL && inst.function == SPECIAL_SYSCALL) + { + queueResumeEntryTarget(inst.address + 4u); + } + bool isStaticJump = (inst.opcode == OPCODE_J || inst.opcode == OPCODE_JAL); if (inst.isBranch && inst.opcode != OPCODE_J && inst.opcode != OPCODE_JAL) { diff --git a/ps2xTest/src/code_generator_tests.cpp b/ps2xTest/src/code_generator_tests.cpp index 2dd9c531f..1a1f37025 100644 --- a/ps2xTest/src/code_generator_tests.cpp +++ b/ps2xTest/src/code_generator_tests.cpp @@ -144,6 +144,17 @@ static Instruction makeJr(uint32_t address, uint8_t rs) return inst; } +static Instruction makeSyscall(uint32_t address) +{ + Instruction inst{}; + inst.address = address; + inst.opcode = OPCODE_SPECIAL; + inst.function = SPECIAL_SYSCALL; + inst.hasDelaySlot = false; + inst.raw = (OPCODE_SPECIAL << 26) | SPECIAL_SYSCALL; + return inst; +} + static void printGeneratedCode(const std::string& name, const std::string& code) { #ifdef PRINT_GENERATED_CODE @@ -556,6 +567,35 @@ void register_code_generator_tests() "unresolved JALR should not pretend it has a resolved local 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, ; syscall ; jr $ra ; + Function func; + func.name = "syscall_wrapper"; + func.start = 0x4000; + func.end = 0x4010; + func.isRecompiled = true; + func.isStub = false; + + std::vector instructions{ + makeAddiu(0x4000, 3, 0, 0x83), + makeSyscall(0x4004), + makeJr(0x4008, 31), + makeNop(0x400C), + }; + + CodeGenerator gen({}, {}); + CodeGenerator::AnalysisResult analysis = gen.collectInternalBranchTargets(func, instructions); + + // +4, not +8: syscall has no delay slot. + t.IsTrue(analysis.resumeEntryPoints.contains(0x4008u), + "syscall should mark the next instruction as resumable"); + t.IsTrue(analysis.entryPoints.contains(0x4008u), + "syscall resume pc should emit a label in the owner"); + t.IsFalse(analysis.resumeEntryPoints.contains(0x400Cu), + "syscall must not claim a delay slot it does not have"); + }); + tc.Run("resume entry targets emit a top-level pc switch in the owner wrapper", [](TestCase &t) { Function func; func.name = "resume_owner";