Merge origin/main into feature/iop-emulator

Preserve the branch's GS CLUT and texture cache implementation while integrating upstream thread initialization and syscall resume coverage. Build passes; 448/456 tests pass with the same eight failures reproduced on pre-merge HEAD (447/455).
This commit is contained in:
Ran-j
2026-09-07 18:21:48 -03:00
4 changed files with 50 additions and 5 deletions
@@ -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);
+4 -4
View File
@@ -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;
+3 -1
View File
@@ -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);
+40
View File
@@ -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, <num> ; syscall ; jr $ra ; <delay slot>
Function func;
func.name = "syscall_wrapper";
func.start = 0x4000;
func.end = 0x4010;
func.isRecompiled = true;
func.isStub = false;
std::vector<Instruction> 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";