From 88d6befc02d58a370e54a879613d594c92cd9610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 23 Oct 2026 14:23:39 +0200 Subject: [PATCH] CPU: jr and jalr ignore the target's low two bits on the main CPU too On a PSP, jr and jalr to an address with its low two bits set raise no address error: they run from the aligned address, the PC keeps no low bits, and jalr's link is unaffected (cpu/jump/unaligned, recorded on firmware 6.61). This was done only for the Media Engine. The firmware's PS1 emulator returns through such an address. The interpreters stopped there with an invalid jump, and the JITs found the block at the aligned address only if it was already compiled, and otherwise compiled one at the unaligned address. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/MIPS/IR/IRCompBranch.cpp | 20 +++++++++++--------- Core/MIPS/Interpreter.cpp | 7 +++---- pspautotests | 2 +- test.py | 1 + 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/Core/MIPS/IR/IRCompBranch.cpp b/Core/MIPS/IR/IRCompBranch.cpp index a2beeba266..8077107d19 100644 --- a/Core/MIPS/IR/IRCompBranch.cpp +++ b/Core/MIPS/IR/IRCompBranch.cpp @@ -411,9 +411,13 @@ void IRFrontend::Comp_JumpReg(MIPSOpcode op) { if (andLink && rs == rd) delaySlotIsNice = false; + // The CPUs ignore the target's low two bits (tests/cpu/jump/unaligned). Sony's ME firmware keeps + // flags there in its command table, and calls the entries as they are, and the PS1 emulator returns + // through such an address. int destReg; if (IsSyscall(delaySlotOp)) { - ir.Write(IROp::SetPC, 0, rs); + ir.Write(IROp::AndConst, IRTEMP_LHS, rs, 0, ~3U); + ir.Write(IROp::SetPC, 0, IRTEMP_LHS); if (andLink) ir.WriteSetConstant(rd, GetCompilerPC() + 8); CompileDelaySlot(); @@ -422,19 +426,17 @@ void IRFrontend::Comp_JumpReg(MIPSOpcode op) { // Account for the delay slot itself in total bytes. js.compilerPC += 4; return; // Syscall (delay slot) wrote exit code. - } else if (delaySlotIsNice && !IsMediaEngine()) { + } else if (delaySlotIsNice) { if (andLink) ir.WriteSetConstant(rd, GetCompilerPC() + 8); CompileDelaySlot(); - destReg = rs; // Safe because FlushAll doesn't change any regs + // Safe because the delay slot doesn't change rs. + ir.Write(IROp::AndConst, IRTEMP_LHS, rs, 0, ~3U); + destReg = IRTEMP_LHS; FlushAll(); } else { - // Bad delay slot. The Media Engine ignores the target's low two bits: Sony's ME firmware keeps - // flags there in its command table, and calls the entries as they are. - if (IsMediaEngine()) - ir.Write(IROp::AndConst, IRTEMP_LHS, rs, 0, ~3U); - else - ir.Write(IROp::Mov, IRTEMP_LHS, rs); + // Bad delay slot. + ir.Write(IROp::AndConst, IRTEMP_LHS, rs, 0, ~3U); destReg = IRTEMP_LHS; if (andLink) ir.WriteSetConstant(rd, GetCompilerPC() + 8); diff --git a/Core/MIPS/Interpreter.cpp b/Core/MIPS/Interpreter.cpp index 9b8fe9b630..3dae884035 100644 --- a/Core/MIPS/Interpreter.cpp +++ b/Core/MIPS/Interpreter.cpp @@ -334,10 +334,9 @@ namespace MIPSInt { int rs = _RS; int rd = _RD; - u32 addr = R(rs); - // The Media Engine ignores the target's low two bits: Sony's ME firmware keeps flags there. - if (mips->IsMediaEngine()) - addr &= ~3; + // The CPUs ignore the target's low two bits (tests/cpu/jump/unaligned). Sony's ME firmware keeps + // flags there, and the PS1 emulator returns through such an address. + u32 addr = R(rs) & ~3; switch (op & 0x3f) { case 8: //jr if (!mips->inDelaySlot) diff --git a/pspautotests b/pspautotests index 51fc2c4d87..713b8ccc4f 160000 --- a/pspautotests +++ b/pspautotests @@ -1 +1 @@ -Subproject commit 51fc2c4d87fc09df9e87a62e1755ae4816cda13a +Subproject commit 713b8ccc4f2830c54ce3c20bbafc085f3506e810 diff --git a/test.py b/test.py index fe430cad47..628ee7abc5 100755 --- a/test.py +++ b/test.py @@ -93,6 +93,7 @@ tests_good = [ "cpu/cpu_alu/cpu_div", "cpu/cpu_alu/shift_encodings", "cpu/cpu_alu/allegrex_encodings", + "cpu/jump/unaligned", "cpu/vfpu/callout", "cpu/vfpu/colors", "cpu/vfpu/convert",