Files
ARMSX2/pcsx2/R3000AInterpreter.cpp
T
Brian Degenhardt 3e56da7f86 Merge upstream PCSX2 (2026-07-15 .. 2026-08-10)
71 commits from 474ad59818 to 2cf8dabe6b, triaged rather than taken wholesale.

Declined, resolved to ours:

- AGENTS.md: upstream's AI-agent instructions; we carry our own and do not
  want a second, conflicting policy file.
- CI deps bump (setup-node, labeler): both target workflows are absent here,
  and the labeler job is gated on the repository being PCSX2/pcsx2.
- KDDockWidgets 2.4.1: two of the six files do not exist here; we already
  build 2.4.0 against a 2.3.0 floor, so there is nothing to gain.
- The FullscreenUI Achievements-layout realignment: our section already
  carries the same settings, and ours is the branded copy.
- The GS draw/vertex-buffer cluster (7887919e74, b2fa00844e, 99cfbb49c1,
  5c611f85e1, 9945046a49, af48193ebb, d88510e3a6, 8c1bb5742e). Our vertex
  kick is an ARM64 rewrite of the same hot path -- register-resident cursor,
  fused min/max with a rewind watermark, and a scalar cull mirror that
  dual-issues against the NEON parse -- so upstream's generic pointer-logic
  optimisation is a variant of work already banked here, and their growth
  restructure replaces per-buffer capacity with a single global value, which
  the pooled draw-node model cannot express. Two of the four August commits
  in that cluster repair regressions the July rewrite introduced, and the
  third's genuine fix (staging arrays sized from an unrelated buffer) we had
  already made independently.

Taken with adjustment:

- EATAN coefficients (aae9438f98). Upstream relabelled mVU_Globals so the
  names match the powers; we had fixed the same defect by ordering the arm64
  call sites by power instead. Both fixes are correct alone and CANCEL when
  combined, so the arm64 call sites move to plain ascending order in the same
  commit. The values never moved, so this emits an identical instruction
  sequence. Their fix also repairs the x86 mVU we still carry.
- Shader cache version: upstream numbered their tfx.glsl change 109, which is
  below our 110. Taking their value would hand every user a stale blob, so
  this lands as 111.
- FullscreenUI: took the two readback-spin toggles, placed outside our
  non-Apple guard rather than inside upstream's unguarded run.
- Restored tools/generate_fullscreen_ui_translation_strings.py, dropped by
  431ca0c063, and regenerated both string areas. That also registers the Big
  Picture setup-wizard strings, which had never been extractable.

GameDB: the three serials upstream gave gsHWFixes (SLES-53869, PAPX-90020,
SCPS-15064) are absent from the mobile overlay, so no fix is silently erased
on handhelds.
2026-08-10 18:24:24 -07:00

338 lines
7.8 KiB
C++

// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "R3000A.h"
#include "Common.h"
#include "Config.h"
#include "VMManager.h"
#include "R5900OpcodeTables.h"
#include "DebugTools/Breakpoints.h"
#include "IopBios.h"
#include "IopHw.h"
using namespace R3000A;
// Used to flag delay slot instructions when throwig exceptions.
bool iopIsDelaySlot = false;
static bool branch2 = 0;
static u32 branchPC;
static void doBranch(s32 tar); // forward declared prototype
/*********************************************************
* Register branch logic *
* Format: OP rs, offset *
*********************************************************/
void psxBGEZ() // Branch if Rs >= 0
{
if (_i32(_rRs_) >= 0) doBranch(_BranchTarget_);
}
void psxBGEZAL() // Branch if Rs >= 0 and link
{
_SetLink(31);
if (_i32(_rRs_) >= 0)
{
doBranch(_BranchTarget_);
}
}
void psxBGTZ() // Branch if Rs > 0
{
if (_i32(_rRs_) > 0) doBranch(_BranchTarget_);
}
void psxBLEZ() // Branch if Rs <= 0
{
if (_i32(_rRs_) <= 0) doBranch(_BranchTarget_);
}
void psxBLTZ() // Branch if Rs < 0
{
if (_i32(_rRs_) < 0) doBranch(_BranchTarget_);
}
void psxBLTZAL() // Branch if Rs < 0 and link
{
_SetLink(31);
if (_i32(_rRs_) < 0)
{
doBranch(_BranchTarget_);
}
}
/*********************************************************
* Register branch logic *
* Format: OP rs, rt, offset *
*********************************************************/
void psxBEQ() // Branch if Rs == Rt
{
if (_i32(_rRs_) == _i32(_rRt_)) doBranch(_BranchTarget_);
}
void psxBNE() // Branch if Rs != Rt
{
if (_i32(_rRs_) != _i32(_rRt_)) doBranch(_BranchTarget_);
}
/*********************************************************
* Jump to target *
* Format: OP target *
*********************************************************/
void psxJ()
{
// check for iop module import table magic
u32 delayslot = iopMemRead32(psxRegs.pc);
if (delayslot >> 16 == 0x2400 && irxImportExec(irxImportTableAddr(psxRegs.pc), delayslot & 0xffff))
return;
doBranch(_JumpTarget_);
}
void psxJAL()
{
_SetLink(31);
doBranch(_JumpTarget_);
}
/*********************************************************
* Register jump *
* Format: OP rs, rd *
*********************************************************/
void psxJR()
{
doBranch(_u32(_rRs_));
}
void psxJALR()
{
const u32 target = _u32(_rRs_); // latch the target before linking
if (_Rd_)
{
_SetLink(_Rd_);
}
doBranch(target);
}
void psxBreakpoint(bool memcheck)
{
u32 pc = psxRegs.pc;
if (CBreakPoints::CheckSkipFirst(BREAKPOINT_IOP, pc) != 0)
{
CBreakPoints::ClearSkipFirst(BREAKPOINT_IOP);
return;
}
if (!memcheck)
{
auto cond = CBreakPoints::GetBreakPointCondition(BREAKPOINT_IOP, pc);
if (cond && !cond->Evaluate())
return;
}
CBreakPoints::SetBreakpointTriggered(true, BREAKPOINT_IOP);
VMManager::SetPaused(true);
Cpu->ExitExecution();
}
void psxMemcheck(u32 op, u32 bits, bool store)
{
// compute accessed address
u32 start = psxRegs.GPR.r[(op >> 21) & 0x1F];
if ((s16)op != 0)
start += (s16)op;
u32 end = start + bits / 8;
auto checks = CBreakPoints::GetMemChecks(BREAKPOINT_IOP);
for (size_t i = 0; i < checks.size(); i++)
{
auto& check = checks[i];
if (check.result == 0)
continue;
if ((check.memCond & MEMCHECK_WRITE) == 0 && store)
continue;
if ((check.memCond & MEMCHECK_READ) == 0 && !store)
continue;
if (check.hasCond)
{
if (!check.cond.Evaluate())
continue;
}
if (start < check.end && check.start < end)
psxBreakpoint(true);
}
}
void psxCheckMemcheck()
{
u32 pc = psxRegs.pc;
int needed = psxIsMemcheckNeeded(pc);
if (needed == 0)
return;
u32 op = iopMemRead32(needed == 2 ? pc + 4 : pc);
// Yeah, we use the R5900 opcode table for the R3000
const R5900::OPCODE& opcode = R5900::GetInstruction(op);
bool store = (opcode.flags & IS_STORE) != 0;
switch (opcode.flags & MEMTYPE_MASK)
{
case MEMTYPE_BYTE:
psxMemcheck(op, 8, store);
break;
case MEMTYPE_HALF:
psxMemcheck(op, 16, store);
break;
case MEMTYPE_WORD:
psxMemcheck(op, 32, store);
break;
case MEMTYPE_DWORD:
psxMemcheck(op, 64, store);
break;
}
}
///////////////////////////////////////////
// These macros are used to assemble the repassembler functions
static __fi void execI()
{
// This function is called for every instruction.
// Enabling the define below will probably, no, will cause the interpretor to be slower.
//#define EXTRA_DEBUG
#if defined(EXTRA_DEBUG) || defined(PCSX2_DEVBUILD)
if (psxIsBreakpointNeeded(psxRegs.pc))
psxBreakpoint(false);
psxCheckMemcheck();
CBreakPoints::CommitClearSkipFirst(BREAKPOINT_IOP);
#endif
// Inject IRX hack
if (psxRegs.pc == 0x1630 && EmuConfig.CurrentIRX.length() > 3) {
if (iopMemRead32(0x20018) == 0x1F) {
// FIXME do I need to increase the module count (0x1F -> 0x20)
iopMemWrite32(0x20094, 0xbffc0000);
}
}
psxRegs.code = iopMemRead32(psxRegs.pc);
PSXCPU_LOG("%s", disR3000AF(psxRegs.code, psxRegs.pc));
psxRegs.pc+= 4;
psxRegs.cycle++;
psxBSC[psxRegs.code >> 26]();
}
static void doBranch(s32 tar) {
if (tar == 0x0)
DevCon.Warning("[R3000 Interpreter] Warning: Branch to 0x0!");
// When upgrading the IOP, there are two resets, the second of which is a 'fake' reset
// This second 'reset' involves UDNL calling SYSMEM and LOADCORE directly, resetting LOADCORE's modules
// This detects when SYSMEM is called and clears the modules then
if(tar == 0x890)
{
DevCon.WriteLn(Color_Gray, "R3000 Debugger: Branch to 0x890 (SYSMEM). Clearing modules.");
R3000SymbolGuardian.ClearIrxModules();
}
// Override the memory size argument to IOPBOOT
if(static_cast<u32>(tar) == 0xbfc4a000) {
psxRegs.GPR.n.a0 = Ps2MemSize::ExposedIopRam >> 20;
}
branch2 = iopIsDelaySlot = true;
branchPC = tar;
execI();
PSXCPU_LOG( "\n" );
iopIsDelaySlot = false;
psxRegs.pc = branchPC;
iopEventTest();
}
// Interpret exactly one IOP instruction at psxRegs.pc, then return. This is the
// ARM64 IOP recompiler's per-instruction fallback for opcodes it cannot yet compile.
// It mirrors the interpreter's inner step: execI() reads the op, advances pc, charges
// one cycle and dispatches; for a branch opcode the interpreter's doBranch runs the
// delay slot, redirects pc and runs the IOP event test, exactly as in intExecuteBlock.
// It must NOT end the IOP timeslice (that is driven by the rec's recExecuteBlock loop
// via iopCycleEE).
void iopExecuteOneInst()
{
execI();
}
static void intReserve() {
}
static void intAlloc() {
}
static void intReset() {
intAlloc();
}
static s32 intExecuteBlock( s32 eeCycles )
{
psxRegs.iopBreak = 0;
psxRegs.iopCycleEE = eeCycles;
u64 lastIOPCycle = 0;
while (psxRegs.iopCycleEE > 0)
{
lastIOPCycle = psxRegs.cycle;
if ((psxHu32(HW_ICFG) & 8) && ((psxRegs.pc & 0x1fffffffU) == 0xa0 || (psxRegs.pc & 0x1fffffffU) == 0xb0 || (psxRegs.pc & 0x1fffffffU) == 0xc0))
psxBiosCall();
branch2 = 0;
while (!branch2)
execI();
if ((psxHu32(HW_ICFG) & (1 << 3)))
{
// F = gcd(PS2CLK, PSXCLK) = 230400
const u32 cnum = 1280; // PS2CLK / F
const u32 cdenom = 147; // PSXCLK / F
//One of the Iop to EE delta clocks to be set in PS1 mode.
const u32 t = ((cnum * (psxRegs.cycle - lastIOPCycle)) + psxRegs.iopCycleEECarry);
psxRegs.iopCycleEE -= t / cdenom;
psxRegs.iopCycleEECarry = t % cdenom;
}
else
{
//default ps2 mode value
psxRegs.iopCycleEE -= (psxRegs.cycle - lastIOPCycle) * 8;
}
}
return psxRegs.iopBreak + psxRegs.iopCycleEE;
}
static void intClear(u32 Addr, u32 Size) {
}
static void intShutdown() {
}
R3000Acpu psxInt = {
intReserve,
intReset,
intExecuteBlock,
intClear,
intShutdown
};