Files

745 lines
17 KiB
C++
Raw Permalink Normal View History

// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
2024-07-30 13:42:36 +02:00
// SPDX-License-Identifier: GPL-3.0+
2022-12-28 17:44:34 +10:00
#include "Common.h"
#include "R5900OpcodeTables.h"
#include "VMManager.h"
2010-04-22 00:48:06 +00:00
#include "Elfheader.h"
#include "Cache.h"
#include "ee_divtrace.h"
#include "no-jit-improvements.h"
2021-05-14 01:42:47 +02:00
#include "DebugTools/Breakpoints.h"
2014-08-21 13:01:07 +02:00
2022-12-28 17:44:34 +10:00
#include "common/FastJmp.h"
#include <float.h>
using namespace R5900; // for OPCODE and OpcodeImpl
extern int vu0branch, vu1branch;
static int branch2 = 0;
static u32 cpuBlockCycles = 0; // 3 bit fixed point version of cycle count
static std::string disOut;
2022-04-04 22:19:39 +10:00
static bool intExitExecution = false;
2022-12-28 17:44:34 +10:00
static fastjmp_buf intJmpBuf;
static u32 intLastBranchTo;
2025-11-22 07:39:17 +00:00
void intEventTest();
// Charge raw block cycles for a syscall handler the interpreter
// SKIPPED (FlushCache/iFlushCache under g_skip_flushcache_syscall), mirroring the
// JIT's recSYSCALL `s_nBlockCycles += 5650`. cpuBlockCycles is the same 3-bit
// fixed-point accumulator as the JIT's s_nBlockCycles with identical scaling
// (intUpdateCPUCycles == scaleblockcycles_calculation), so adding the same raw
// constant keeps the cycle-derived hardware (EE timers) in lockstep across the
// skip. Gated entirely by the caller; no effect in production.
void intChargeSkippedHandlerCycles(u32 raw_block_cycles)
{
cpuBlockCycles += raw_block_cycles;
}
2023-11-02 15:19:38 +00:00
void intUpdateCPUCycles()
{
2023-11-02 15:19:38 +00:00
const bool lowcycles = (cpuBlockCycles <= 40);
const s8 cyclerate = EmuConfig.Speedhacks.EECycleRate;
u32 scale_cycles = 0;
2023-11-02 15:19:38 +00:00
if (cyclerate == 0 || lowcycles || cyclerate < -99 || cyclerate > 3)
scale_cycles = cpuBlockCycles >> 3;
else if (cyclerate > 1)
scale_cycles = cpuBlockCycles >> (2 + cyclerate);
else if (cyclerate == 1)
scale_cycles = (cpuBlockCycles >> 3) / 1.3f; // Adds a mild 30% increase in clockspeed for value 1.
2024-02-11 15:24:16 +00:00
else if (cyclerate == -1) // the mildest value.
2023-11-02 15:19:38 +00:00
// These values were manually tuned to yield mild speedup with high compatibility
scale_cycles = (cpuBlockCycles <= 80 || cpuBlockCycles > 168 ? 5 : 7) * cpuBlockCycles / 32;
else
scale_cycles = ((5 + (-2 * (cyclerate + 1))) * cpuBlockCycles) >> 5;
// Ensure block cycle count is never less than 1.
cpuRegs.cycle += (scale_cycles < 1) ? 1 : scale_cycles;
if (cyclerate > 1)
{
cpuBlockCycles &= (0x1 << (cyclerate + 2)) - 1;
}
else
{
cpuBlockCycles &= 0x7;
}
}
// These macros are used to assemble the repassembler functions
2014-08-21 13:01:07 +02:00
void intBreakpoint(bool memcheck)
{
const u32 pc = cpuRegs.pc;
2021-01-08 18:34:08 -05:00
if (CBreakPoints::CheckSkipFirst(BREAKPOINT_EE, pc) != 0)
{
CBreakPoints::ClearSkipFirst(BREAKPOINT_EE);
2014-08-21 13:01:07 +02:00
return;
}
2014-08-21 13:01:07 +02:00
if (!memcheck)
{
2021-01-08 18:34:08 -05:00
auto cond = CBreakPoints::GetBreakPointCondition(BREAKPOINT_EE, pc);
2014-08-21 13:01:07 +02:00
if (cond && !cond->Evaluate())
return;
}
CBreakPoints::SetBreakpointTriggered(true, BREAKPOINT_EE);
2022-02-02 23:05:38 -05:00
VMManager::SetPaused(true);
2023-04-27 21:47:52 -04:00
Cpu->ExitExecution();
2014-08-21 13:01:07 +02:00
}
void intMemcheck(u32 op, u32 bits, bool store)
{
// compute accessed address
u32 start = cpuRegs.GPR.r[(op >> 21) & 0x1F].UL[0];
if (static_cast<s16>(op) != 0)
start += static_cast<s16>(op);
2014-08-21 13:01:07 +02:00
if (bits == 128)
start &= ~0x0F;
2022-01-20 23:59:48 -05:00
start = standardizeBreakpointAddress(start);
const u32 end = start + bits/8;
auto checks = CBreakPoints::GetMemChecks(BREAKPOINT_EE);
2014-08-21 13:01:07 +02:00
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)
2014-08-21 13:01:07 +02:00
continue;
if ((check.memCond & MEMCHECK_READ) == 0 && !store)
2014-08-21 13:01:07 +02:00
continue;
if (check.hasCond)
{
if (!check.cond.Evaluate())
continue;
}
2014-08-21 13:01:07 +02:00
if (start < check.end && check.start < end)
intBreakpoint(true);
}
}
void intCheckMemcheck()
{
const u32 pc = cpuRegs.pc;
const int needed = isMemcheckNeeded(pc);
2014-08-21 13:01:07 +02:00
if (needed == 0)
return;
const u32 op = memRead32(needed == 2 ? pc + 4 : pc);
2014-08-21 13:01:07 +02:00
const OPCODE& opcode = GetInstruction(op);
const bool store = (opcode.flags & IS_STORE) != 0;
2014-08-21 13:01:07 +02:00
switch (opcode.flags & MEMTYPE_MASK)
{
case MEMTYPE_BYTE:
intMemcheck(op, 8, store);
break;
case MEMTYPE_HALF:
intMemcheck(op, 16, store);
break;
case MEMTYPE_WORD:
intMemcheck(op, 32, store);
break;
case MEMTYPE_DWORD:
intMemcheck(op, 64, store);
break;
case MEMTYPE_QWORD:
intMemcheck(op, 128, store);
break;
2014-08-21 13:01:07 +02:00
}
}
static void execI()
{
2014-11-30 11:43:24 +01:00
// execI is called for every instruction so it must remains as light as possible.
// If you enable the next define, Interpreter will be much slower (around
// ~4fps on 3.9GHz Haswell vs ~8fps (even 10fps on dev build))
// Extra note: due to some cycle count issue PCSX2's internal debugger is
// not yet usable with the interpreter
//#define EXTRA_DEBUG
2022-01-20 23:59:48 -05:00
#if defined(EXTRA_DEBUG) || defined(PCSX2_DEVBUILD)
2014-08-21 13:01:07 +02:00
// check if any breakpoints or memchecks are triggered by this instruction
if (isBreakpointNeeded(cpuRegs.pc))
intBreakpoint(false);
intCheckMemcheck();
CBreakPoints::CommitClearSkipFirst(BREAKPOINT_EE);
2014-11-30 11:43:24 +01:00
#endif
2014-08-21 13:01:07 +02:00
const u32 pc = cpuRegs.pc;
// We need to increase the pc before executing the memRead32. An exception could appears
// and it expects the PC counter to be pre-incremented
cpuRegs.pc += 4;
2014-08-21 13:01:07 +02:00
// interprete instruction
cpuRegs.code = memRead32( pc );
const OPCODE& opcode = GetCurrentInstruction();
#if 0
static long int runs = 0;
//use this to find out what opcodes your game uses. very slow! (rama)
runs++;
//leave some time to startup the testgame
if (runs > 1599999999)
{
//find all opcodes beginning with "L"
if (opcode.Name[0] == 'L')
{
Console.WriteLn ("Load %s", opcode.Name);
}
}
#endif
#if 0
static long int print_me = 0;
// Based on cycle
2025-05-17 02:58:07 +01:00
// if ( cpuRegs.cycle > 0x4f24d714 )
// Or dump from a particular PC (useful to debug handler/syscall)
if (pc == 0x80000000)
{
print_me = 2000;
}
if (print_me)
{
print_me--;
disOut.clear();
disR5900Fasm(disOut, cpuRegs.code, pc);
CPU_LOG( disOut.c_str() );
}
#endif
cpuBlockCycles += opcode.cycles * (2 - ((cpuRegs.CP0.n.Config >> 18) & 0x1));
opcode.interpret();
#ifdef PCSX2_RECOMPILER_TESTS
// One sample per retired instruction (including branch delay slots, which also
// flow through execI). cpuRegs.pc now points at the next instruction to execute,
// so a sample with pc=X is "architectural state just before executing X" — the
// same point the JIT block hook captures for block entry X. Off unless
// ee_divtrace::g_enabled was set for this frame (single relaxed load otherwise).
// Test-hook only — release builds drop the per-instruction probe entirely.
if (ee_divtrace::g_enabled.load(std::memory_order_relaxed))
ee_divtrace::RecordSample(cpuRegs.pc);
#endif
}
static bool execCachedI(u32 pc, u32 code, const OPCODE& opcode)
{
// A previous instruction can redirect execution without using an opcode
// marked as a branch (for example, an exception). Stop at that boundary.
if (cpuRegs.pc != pc)
return false;
cpuRegs.pc = pc + 4;
cpuRegs.code = code;
cpuBlockCycles += opcode.cycles * (2 - ((cpuRegs.CP0.n.Config >> 18) & 0x1));
opcode.interpret();
#ifdef PCSX2_RECOMPILER_TESTS
if (ee_divtrace::g_enabled.load(std::memory_order_relaxed))
ee_divtrace::RecordSample(cpuRegs.pc);
#endif
return (opcode.flags & IS_BRANCH) == 0 && cpuRegs.pc == (pc + 4);
}
2010-08-09 04:10:38 +00:00
static __fi void _doBranch_shared(u32 tar)
{
branch2 = cpuRegs.branch = 1;
execI();
// branch being 0 means an exception was thrown, since only the exception
// handler should ever clear it.
if( cpuRegs.branch != 0 )
{
if (Cpu == &intCpu)
{
if (intLastBranchTo == tar && EmuConfig.Speedhacks.WaitLoop)
{
intUpdateCPUCycles();
bool can_skip = true;
if (tar != 0x81fc0)
{
if ((cpuRegs.pc - tar) < (4 * 10))
{
2023-11-07 14:14:51 +01:00
for (u32 i = tar; i < cpuRegs.pc; i += 4)
{
if (PSM(i) != 0)
{
can_skip = false;
break;
}
}
}
else
can_skip = false;
}
if (can_skip)
{
2026-03-06 21:55:35 +01:00
if (static_cast<s64>(cpuRegs.nextEventCycle - cpuRegs.cycle) > 0)
cpuRegs.cycle = cpuRegs.nextEventCycle;
else
cpuRegs.nextEventCycle = cpuRegs.cycle;
}
}
}
intLastBranchTo = tar;
cpuRegs.pc = tar;
cpuRegs.branch = 0;
}
}
static void doBranch( u32 target )
{
_doBranch_shared( target );
2023-11-02 15:19:38 +00:00
intUpdateCPUCycles();
intEventTest();
}
void intDoBranch(u32 target)
{
2009-10-04 08:27:27 +00:00
//Console.WriteLn("Interpreter Branch ");
_doBranch_shared( target );
if( Cpu == &intCpu )
{
2023-11-02 15:19:38 +00:00
intUpdateCPUCycles();
intEventTest();
}
}
void intSetBranch()
{
branch2 = /*cpuRegs.branch =*/ 1;
}
////////////////////////////////////////////////////////////////////
// R5900 Branching Instructions!
// These are the interpreter versions of the branch instructions. Unlike other
// types of interpreter instructions which can be called safely from the recompilers,
// these instructions are not "recSafe" because they may not invoke the
// necessary branch test logic that the recs need to maintain sync with the
// cpuRegs.pc and delaySlot instruction and such.
namespace R5900 {
namespace Interpreter {
namespace OpcodeImpl {
/*********************************************************
* Jump to target *
* Format: OP target *
*********************************************************/
// fixme: looking at the other branching code, shouldn't those _SetLinks in BGEZAL and such only be set
// if the condition is true? --arcum42
void J()
{
doBranch(_JumpTarget_);
}
void JAL()
{
2014-12-07 17:25:56 +01:00
// 0x3563b8 is the start address of the function that invalidate entry in TLB cache
if (EmuConfig.Gamefixes.GoemonTlbHack) {
if (_JumpTarget_ == 0x3563b8)
GoemonUnloadTlb(cpuRegs.GPR.n.a0.UL[0]);
}
_SetLink(31);
doBranch(_JumpTarget_);
}
/*********************************************************
* Register branch logic *
* Format: OP rs, rt, offset *
*********************************************************/
void BEQ() // Branch if Rs == Rt
{
if (cpuRegs.GPR.r[_Rs_].SD[0] == cpuRegs.GPR.r[_Rt_].SD[0])
doBranch(_BranchTarget_);
else
intEventTest();
}
void BNE() // Branch if Rs != Rt
{
if (cpuRegs.GPR.r[_Rs_].SD[0] != cpuRegs.GPR.r[_Rt_].SD[0])
doBranch(_BranchTarget_);
else
intEventTest();
}
/*********************************************************
* Register branch logic *
* Format: OP rs, offset *
*********************************************************/
void BGEZ() // Branch if Rs >= 0
{
if(cpuRegs.GPR.r[_Rs_].SD[0] >= 0)
{
doBranch(_BranchTarget_);
}
}
void BGEZAL() // Branch if Rs >= 0 and link
{
2016-09-16 03:58:37 -05:00
_SetLink(31);
if (cpuRegs.GPR.r[_Rs_].SD[0] >= 0)
{
doBranch(_BranchTarget_);
}
}
void BGTZ() // Branch if Rs > 0
{
if (cpuRegs.GPR.r[_Rs_].SD[0] > 0)
{
doBranch(_BranchTarget_);
}
}
void BLEZ() // Branch if Rs <= 0
{
if (cpuRegs.GPR.r[_Rs_].SD[0] <= 0)
{
doBranch(_BranchTarget_);
}
}
void BLTZ() // Branch if Rs < 0
{
if (cpuRegs.GPR.r[_Rs_].SD[0] < 0)
{
doBranch(_BranchTarget_);
}
}
void BLTZAL() // Branch if Rs < 0 and link
{
2016-09-16 03:58:37 -05:00
_SetLink(31);
if (cpuRegs.GPR.r[_Rs_].SD[0] < 0)
{
doBranch(_BranchTarget_);
}
}
/*********************************************************
* Register branch logic Likely *
* Format: OP rs, offset *
*********************************************************/
void BEQL() // Branch if Rs == Rt
{
if(cpuRegs.GPR.r[_Rs_].SD[0] == cpuRegs.GPR.r[_Rt_].SD[0])
{
doBranch(_BranchTarget_);
}
else
{
cpuRegs.pc +=4;
intEventTest();
}
}
void BNEL() // Branch if Rs != Rt
{
if(cpuRegs.GPR.r[_Rs_].SD[0] != cpuRegs.GPR.r[_Rt_].SD[0])
{
doBranch(_BranchTarget_);
}
else
{
cpuRegs.pc +=4;
intEventTest();
}
}
void BLEZL() // Branch if Rs <= 0
{
if(cpuRegs.GPR.r[_Rs_].SD[0] <= 0)
{
doBranch(_BranchTarget_);
}
else
{
cpuRegs.pc +=4;
intEventTest();
}
}
void BGTZL() // Branch if Rs > 0
{
if(cpuRegs.GPR.r[_Rs_].SD[0] > 0)
{
doBranch(_BranchTarget_);
}
else
{
cpuRegs.pc +=4;
intEventTest();
}
}
void BLTZL() // Branch if Rs < 0
{
if(cpuRegs.GPR.r[_Rs_].SD[0] < 0)
{
doBranch(_BranchTarget_);
}
else
{
cpuRegs.pc +=4;
intEventTest();
}
}
void BGEZL() // Branch if Rs >= 0
{
if(cpuRegs.GPR.r[_Rs_].SD[0] >= 0)
{
doBranch(_BranchTarget_);
}
else
{
cpuRegs.pc +=4;
intEventTest();
}
}
void BLTZALL() // Branch if Rs < 0 and link
{
2016-09-16 03:58:37 -05:00
_SetLink(31);
if(cpuRegs.GPR.r[_Rs_].SD[0] < 0)
{
doBranch(_BranchTarget_);
}
else
{
cpuRegs.pc +=4;
intEventTest();
}
}
void BGEZALL() // Branch if Rs >= 0 and link
{
2016-09-16 03:58:37 -05:00
_SetLink(31);
if(cpuRegs.GPR.r[_Rs_].SD[0] >= 0)
{
doBranch(_BranchTarget_);
}
else
{
cpuRegs.pc +=4;
intEventTest();
}
}
/*********************************************************
* Register jump *
* Format: OP rs, rd *
*********************************************************/
void JR()
{
// 0x33ad48 and 0x35060c are the return address of the function (0x356250) that populate the TLB cache
2014-12-07 17:25:56 +01:00
if (EmuConfig.Gamefixes.GoemonTlbHack) {
const u32 add = cpuRegs.GPR.r[_Rs_].UL[0];
if (add == 0x33ad48 || add == 0x35060c)
2014-12-07 17:25:56 +01:00
GoemonPreloadTlb();
}
doBranch(cpuRegs.GPR.r[_Rs_].UL[0]);
}
void JALR()
{
const u32 temp = cpuRegs.GPR.r[_Rs_].UL[0];
if (_Rd_) _SetLink(_Rd_);
doBranch(temp);
}
} } } // end namespace R5900::Interpreter::OpcodeImpl
2010-10-22 16:23:52 +00:00
// --------------------------------------------------------------------------------------
// R5900cpu/intCpu interface (implementations)
// --------------------------------------------------------------------------------------
static void intReserve()
{
// fixme : detect cpu for use the optimize asm code
}
static void intReset()
{
cpuRegs.branch = 0;
branch2 = 0;
NoJITImprovements::ResetEEBlockCache();
}
2025-11-22 07:39:17 +00:00
void intEventTest()
{
// Perform counters, ints, and IOP updates:
_cpuEventTest_Shared();
2022-04-04 22:19:39 +10:00
if (intExitExecution)
{
intExitExecution = false;
if (CHECK_EEREC)
writebackCache();
2022-12-28 17:44:34 +10:00
fastjmp_jmp(&intJmpBuf, 1);
2022-04-04 22:19:39 +10:00
}
}
static void intSafeExitExecution()
{
// If we're currently processing events, we can't safely jump out of the interpreter here, because we'll
// leave things in an inconsistent state. So instead, we flag it for exiting once cpuEventTest() returns.
if (eeEventTestIsActive)
intExitExecution = true;
else
{
if (CHECK_EEREC)
writebackCache();
2022-12-28 17:44:34 +10:00
fastjmp_jmp(&intJmpBuf, 1);
}
2022-12-28 17:44:34 +10:00
}
static void intCancelInstruction()
{
// See execute function.
fastjmp_jmp(&intJmpBuf, 0);
}
static void intExecute()
{
// This will come back as zero the first time it runs, or on instruction cancel.
// It will come back as nonzero when we exit execution.
if (fastjmp_set(&intJmpBuf) != 0)
return;
for (;;)
{
if (!VMManager::Internal::HasBootedELF())
{
// Avoid reloading every instruction.
u32 elf_entry_point = VMManager::Internal::GetCurrentELFEntryPoint();
u32 eeload_main = g_eeloadMain;
u32 eeload_exec = g_eeloadExec;
while (true)
2022-12-28 17:44:34 +10:00
{
execI();
2022-12-28 17:44:34 +10:00
if (cpuRegs.pc == EELOAD_START)
{
// The EELOAD _start function is the same across all BIOS versions afaik
const u32 mainjump = memRead32(EELOAD_START + 0x9c);
2022-12-28 17:44:34 +10:00
if (mainjump >> 26 == 3) // JAL
g_eeloadMain = ((EELOAD_START + 0xa0) & 0xf0000000U) | (mainjump << 2 & 0x0fffffffU);
eeload_main = g_eeloadMain;
2022-12-28 17:44:34 +10:00
}
else if (cpuRegs.pc == eeload_main)
2022-12-28 17:44:34 +10:00
{
eeloadHook();
if (VMManager::Internal::IsFastBootInProgress())
2022-12-28 17:44:34 +10:00
{
// See comments on this code in iR5900.cpp's recRecompile()
const u32 typeAexecjump = memRead32(EELOAD_START + 0x470);
const u32 typeBexecjump = memRead32(EELOAD_START + 0x5B0);
const u32 typeCexecjump = memRead32(EELOAD_START + 0x618);
const u32 typeDexecjump = memRead32(EELOAD_START + 0x600);
2022-12-28 17:44:34 +10:00
if ((typeBexecjump >> 26 == 3) || (typeCexecjump >> 26 == 3) || (typeDexecjump >> 26 == 3)) // JAL to 0x822B8
g_eeloadExec = EELOAD_START + 0x2B8;
else if (typeAexecjump >> 26 == 3) // JAL to 0x82170
g_eeloadExec = EELOAD_START + 0x170;
else
Console.WriteLn("intExecute: Could not enable launch arguments for fast boot mode; unidentified BIOS version! Please report this to the PCSX2 developers.");
eeload_exec = g_eeloadExec;
2022-12-28 17:44:34 +10:00
}
elf_entry_point = VMManager::Internal::GetCurrentELFEntryPoint();
2022-12-28 17:44:34 +10:00
}
else if (cpuRegs.pc == eeload_exec)
2022-12-28 17:44:34 +10:00
{
eeloadHook2();
}
else if (cpuRegs.pc == elf_entry_point)
2022-12-28 17:44:34 +10:00
{
VMManager::Internal::EntryPointCompilingOnCPUThread();
break;
2022-12-28 17:44:34 +10:00
}
}
}
else
{
while (true)
{
if (!NoJITImprovements::ExecuteEEBlock(cpuRegs.pc, execCachedI))
execI();
}
2022-12-28 17:44:34 +10:00
}
}
}
static void intStep()
{
// Arm the cancel target: intCancelInstruction (TLB miss / vtlb_Miss path)
// longjmps to intJmpBuf, which only intExecute used to arm — a cancel
// during a single Step (debugger stepping, recompiler_tests interp
// oracle) jumped through an unarmed buffer straight to PC=0. A cancelled
// instruction has already vectored via cpuException, so returning here
// with pc on the exception vector is exactly one completed "step".
if (fastjmp_set(&intJmpBuf) != 0)
return;
execI();
}
static void intClear(u32 Addr, u32 Size)
{
NoJITImprovements::InvalidateEEBlockCache(Addr, Size);
}
static void intShutdown() {
NoJITImprovements::ResetEEBlockCache();
}
R5900cpu intCpu =
{
2010-10-22 16:23:52 +00:00
intReserve,
intShutdown,
intReset,
intStep,
intExecute,
intSafeExitExecution,
2022-12-28 17:44:34 +10:00
intCancelInstruction,
2022-10-12 23:57:53 +10:00
intClear
};