diff --git a/ps2xRecomp/src/lib/control_flow_analyzer.cpp b/ps2xRecomp/src/lib/control_flow_analyzer.cpp index a50cb12..ed7578d 100644 --- a/ps2xRecomp/src/lib/control_flow_analyzer.cpp +++ b/ps2xRecomp/src/lib/control_flow_analyzer.cpp @@ -135,6 +135,9 @@ 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); diff --git a/ps2xRuntime/include/ps2_runtime.h b/ps2xRuntime/include/ps2_runtime.h index cba28e5..36c299e 100644 --- a/ps2xRuntime/include/ps2_runtime.h +++ b/ps2xRuntime/include/ps2_runtime.h @@ -153,10 +153,10 @@ struct alignas(16) R5900Context // Reset COP0 registers cop0_random = 47; // Start at maximum value - // cop0_status = 0x400000; // BEV set, ERL clear, kernel mode - // 0x00400000 = BEV (Boot Exception Vectors). - // 0x00000000 = Normal mode (after BIOS handoff). - cop0_status = 0x00000000; + // Status as the EE kernel leaves it at handoff. IE (bit 0) and EIE + // (bit 16) are separate enables and guest code reads both; libkernel's + // StartThread refuses to run while IE is clear. + cop0_status = 0x00010001; // EIE | IE cop0_prid = 0x00002e20; // CPU ID for R5900 in_delay_slot = false; diff --git a/ps2xRuntime/src/lib/ps2_runtime.cpp b/ps2xRuntime/src/lib/ps2_runtime.cpp index 742110a..fe30540 100644 --- a/ps2xRuntime/src/lib/ps2_runtime.cpp +++ b/ps2xRuntime/src/lib/ps2_runtime.cpp @@ -491,7 +491,9 @@ PS2Runtime::PS2Runtime() } #endif - std::memset(&m_cpuContext, 0, sizeof(m_cpuContext)); + // Assign rather than memset: R5900Context's constructor zeroes itself and + // then applies the COP0 reset values, which a memset here would discard. + m_cpuContext = R5900Context{}; // R0 is always zero in MIPS m_cpuContext.r[0] = _mm_set1_epi32(0); diff --git a/ps2xTest/src/code_generator_tests.cpp b/ps2xTest/src/code_generator_tests.cpp index 23bef4a..463b6cf 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 @@ -644,6 +655,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";