mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
71 commits from474ad59818to2cf8dabe6b, 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 by431ca0c063, 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.
338 lines
7.8 KiB
C++
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
|
|
};
|