CPU: Provide debugger/breakpoint/step functionality
This commit is contained in:
parent
a8af0f7ecb
commit
3b23542ec9
|
@ -174,7 +174,9 @@ static void ExecuteImpl()
|
||||||
{
|
{
|
||||||
CodeBlockKey next_block_key;
|
CodeBlockKey next_block_key;
|
||||||
|
|
||||||
|
g_using_interpreter = false;
|
||||||
g_state.frame_done = false;
|
g_state.frame_done = false;
|
||||||
|
|
||||||
while (!g_state.frame_done)
|
while (!g_state.frame_done)
|
||||||
{
|
{
|
||||||
if (HasPendingInterrupt())
|
if (HasPendingInterrupt())
|
||||||
|
@ -301,7 +303,9 @@ CodeBlock::HostCodePointer* GetFastMapPointer()
|
||||||
|
|
||||||
void ExecuteRecompiler()
|
void ExecuteRecompiler()
|
||||||
{
|
{
|
||||||
|
g_using_interpreter = false;
|
||||||
g_state.frame_done = false;
|
g_state.frame_done = false;
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
while (!g_state.frame_done)
|
while (!g_state.frame_done)
|
||||||
{
|
{
|
||||||
|
@ -568,7 +572,7 @@ bool CompileBlock(CodeBlock* block)
|
||||||
Log_DebugPrintf("Block at 0x%08X", block->GetPC());
|
Log_DebugPrintf("Block at 0x%08X", block->GetPC());
|
||||||
for (const CodeBlockInstruction& cbi : block->instructions)
|
for (const CodeBlockInstruction& cbi : block->instructions)
|
||||||
{
|
{
|
||||||
CPU::DisassembleInstruction(&disasm, cbi.pc, cbi.instruction.bits, nullptr);
|
CPU::DisassembleInstruction(&disasm, cbi.pc, cbi.instruction.bits);
|
||||||
Log_DebugPrintf("[%s %s 0x%08X] %08X %s", cbi.is_branch_delay_slot ? "BD" : " ",
|
Log_DebugPrintf("[%s %s 0x%08X] %08X %s", cbi.is_branch_delay_slot ? "BD" : " ",
|
||||||
cbi.is_load_delay_slot ? "LD" : " ", cbi.pc, cbi.instruction.bits, disasm.GetCharArray());
|
cbi.is_load_delay_slot ? "LD" : " ", cbi.pc, cbi.instruction.bits, disasm.GetCharArray());
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,10 @@
|
||||||
#include "cpu_disasm.h"
|
#include "cpu_disasm.h"
|
||||||
#include "cpu_recompiler_thunks.h"
|
#include "cpu_recompiler_thunks.h"
|
||||||
#include "gte.h"
|
#include "gte.h"
|
||||||
|
#include "host_interface.h"
|
||||||
#include "pgxp.h"
|
#include "pgxp.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
#include "system.h"
|
||||||
#include "timing_event.h"
|
#include "timing_event.h"
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
Log_SetChannel(CPU::Core);
|
Log_SetChannel(CPU::Core);
|
||||||
|
@ -22,9 +24,16 @@ static void Branch(u32 target);
|
||||||
static void FlushPipeline();
|
static void FlushPipeline();
|
||||||
|
|
||||||
State g_state;
|
State g_state;
|
||||||
|
bool g_using_interpreter = false;
|
||||||
bool TRACE_EXECUTION = false;
|
bool TRACE_EXECUTION = false;
|
||||||
bool LOG_EXECUTION = false;
|
bool LOG_EXECUTION = false;
|
||||||
|
|
||||||
|
static constexpr u32 INVALID_BREAKPOINT_PC = UINT32_C(0xFFFFFFFF);
|
||||||
|
static std::vector<Breakpoint> s_breakpoints;
|
||||||
|
static u32 s_breakpoint_counter = 1;
|
||||||
|
static u32 s_last_breakpoint_check_pc = INVALID_BREAKPOINT_PC;
|
||||||
|
static bool s_single_step = false;
|
||||||
|
|
||||||
void WriteToExecutionLog(const char* format, ...)
|
void WriteToExecutionLog(const char* format, ...)
|
||||||
{
|
{
|
||||||
static std::FILE* log_file = nullptr;
|
static std::FILE* log_file = nullptr;
|
||||||
|
@ -53,6 +62,12 @@ void Initialize()
|
||||||
// From nocash spec.
|
// From nocash spec.
|
||||||
g_state.cop0_regs.PRID = UINT32_C(0x00000002);
|
g_state.cop0_regs.PRID = UINT32_C(0x00000002);
|
||||||
|
|
||||||
|
g_state.use_debug_dispatcher = false;
|
||||||
|
s_breakpoints.clear();
|
||||||
|
s_breakpoint_counter = 1;
|
||||||
|
s_last_breakpoint_check_pc = INVALID_BREAKPOINT_PC;
|
||||||
|
s_single_step = false;
|
||||||
|
|
||||||
GTE::Initialize();
|
GTE::Initialize();
|
||||||
|
|
||||||
if (g_settings.gpu_pgxp_enable)
|
if (g_settings.gpu_pgxp_enable)
|
||||||
|
@ -63,6 +78,7 @@ void Shutdown()
|
||||||
{
|
{
|
||||||
// GTE::Shutdown();
|
// GTE::Shutdown();
|
||||||
PGXP::Shutdown();
|
PGXP::Shutdown();
|
||||||
|
ClearBreakpoints();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Reset()
|
void Reset()
|
||||||
|
@ -427,7 +443,16 @@ ALWAYS_INLINE_RELEASE static void WriteCop0Reg(Cop0Reg reg, u32 value)
|
||||||
static void PrintInstruction(u32 bits, u32 pc, Registers* regs)
|
static void PrintInstruction(u32 bits, u32 pc, Registers* regs)
|
||||||
{
|
{
|
||||||
TinyString instr;
|
TinyString instr;
|
||||||
DisassembleInstruction(&instr, pc, bits, regs);
|
TinyString comment;
|
||||||
|
DisassembleInstruction(&instr, pc, bits);
|
||||||
|
DisassembleInstructionComment(&comment, pc, bits, regs);
|
||||||
|
if (!comment.IsEmpty())
|
||||||
|
{
|
||||||
|
for (u32 i = instr.GetLength(); i < 30; i++)
|
||||||
|
instr.AppendCharacter(' ');
|
||||||
|
instr.AppendString("; ");
|
||||||
|
instr.AppendString(comment);
|
||||||
|
}
|
||||||
|
|
||||||
std::printf("%08x: %08x %s\n", pc, bits, instr.GetCharArray());
|
std::printf("%08x: %08x %s\n", pc, bits, instr.GetCharArray());
|
||||||
}
|
}
|
||||||
|
@ -435,7 +460,16 @@ static void PrintInstruction(u32 bits, u32 pc, Registers* regs)
|
||||||
static void LogInstruction(u32 bits, u32 pc, Registers* regs)
|
static void LogInstruction(u32 bits, u32 pc, Registers* regs)
|
||||||
{
|
{
|
||||||
TinyString instr;
|
TinyString instr;
|
||||||
DisassembleInstruction(&instr, pc, bits, regs);
|
TinyString comment;
|
||||||
|
DisassembleInstruction(&instr, pc, bits);
|
||||||
|
DisassembleInstructionComment(&comment, pc, bits, regs);
|
||||||
|
if (!comment.IsEmpty())
|
||||||
|
{
|
||||||
|
for (u32 i = instr.GetLength(); i < 30; i++)
|
||||||
|
instr.AppendCharacter(' ');
|
||||||
|
instr.AppendString("; ");
|
||||||
|
instr.AppendString(comment);
|
||||||
|
}
|
||||||
|
|
||||||
WriteToExecutionLog("%08x: %08x %s\n", pc, bits, instr.GetCharArray());
|
WriteToExecutionLog("%08x: %08x %s\n", pc, bits, instr.GetCharArray());
|
||||||
}
|
}
|
||||||
|
@ -1429,9 +1463,265 @@ void DispatchInterrupt()
|
||||||
g_state.regs.pc);
|
g_state.regs.pc);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<PGXPMode pgxp_mode>
|
static void UpdateDebugDispatcherFlag()
|
||||||
|
{
|
||||||
|
const bool has_any_breakpoints = !s_breakpoints.empty();
|
||||||
|
|
||||||
|
// TODO: cop0 breakpoints
|
||||||
|
|
||||||
|
const bool use_debug_dispatcher = has_any_breakpoints;
|
||||||
|
if (use_debug_dispatcher == g_state.use_debug_dispatcher)
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_state.use_debug_dispatcher = use_debug_dispatcher;
|
||||||
|
ForceDispatcherExit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ForceDispatcherExit()
|
||||||
|
{
|
||||||
|
// zero the downcount so we break out and switch
|
||||||
|
g_state.downcount = 0;
|
||||||
|
g_state.frame_done = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasAnyBreakpoints()
|
||||||
|
{
|
||||||
|
return !s_breakpoints.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasBreakpointAtAddress(VirtualMemoryAddress address)
|
||||||
|
{
|
||||||
|
for (const Breakpoint& bp : s_breakpoints)
|
||||||
|
{
|
||||||
|
if (bp.address == address)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BreakpointList GetBreakpointList(bool include_auto_clear, bool include_callbacks)
|
||||||
|
{
|
||||||
|
BreakpointList bps;
|
||||||
|
bps.reserve(s_breakpoints.size());
|
||||||
|
|
||||||
|
for (const Breakpoint& bp : s_breakpoints)
|
||||||
|
{
|
||||||
|
if (bp.callback && !include_callbacks)
|
||||||
|
continue;
|
||||||
|
if (bp.auto_clear && !include_auto_clear)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bps.push_back(bp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bps;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AddBreakpoint(VirtualMemoryAddress address, bool auto_clear, bool enabled)
|
||||||
|
{
|
||||||
|
if (HasBreakpointAtAddress(address))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Log_InfoPrintf("Adding breakpoint at %08X, auto clear = %u", address, static_cast<unsigned>(auto_clear));
|
||||||
|
|
||||||
|
Breakpoint bp{address, nullptr, auto_clear ? 0 : s_breakpoint_counter++, 0, auto_clear, enabled};
|
||||||
|
s_breakpoints.push_back(std::move(bp));
|
||||||
|
UpdateDebugDispatcherFlag();
|
||||||
|
|
||||||
|
if (!auto_clear)
|
||||||
|
{
|
||||||
|
g_host_interface->ReportFormattedDebuggerMessage(
|
||||||
|
g_host_interface->TranslateString("DebuggerMessage", "Added breakpoint at 0x%08X."), address);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AddBreakpointWithCallback(VirtualMemoryAddress address, BreakpointCallback callback)
|
||||||
|
{
|
||||||
|
if (HasBreakpointAtAddress(address))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Log_InfoPrintf("Adding breakpoint with callback at %08X", address);
|
||||||
|
|
||||||
|
Breakpoint bp{address, callback, 0, 0, false, true};
|
||||||
|
s_breakpoints.push_back(std::move(bp));
|
||||||
|
UpdateDebugDispatcherFlag();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RemoveBreakpoint(VirtualMemoryAddress address)
|
||||||
|
{
|
||||||
|
auto it = std::find_if(s_breakpoints.begin(), s_breakpoints.end(),
|
||||||
|
[address](const Breakpoint& bp) { return bp.address == address; });
|
||||||
|
if (it == s_breakpoints.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
g_host_interface->ReportFormattedDebuggerMessage(
|
||||||
|
g_host_interface->TranslateString("DebuggerMessage", "Removed breakpoint at 0x%08X."), address);
|
||||||
|
|
||||||
|
s_breakpoints.erase(it);
|
||||||
|
UpdateDebugDispatcherFlag();
|
||||||
|
|
||||||
|
if (address == s_last_breakpoint_check_pc)
|
||||||
|
s_last_breakpoint_check_pc = INVALID_BREAKPOINT_PC;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClearBreakpoints()
|
||||||
|
{
|
||||||
|
s_breakpoints.clear();
|
||||||
|
s_breakpoint_counter = 0;
|
||||||
|
s_last_breakpoint_check_pc = INVALID_BREAKPOINT_PC;
|
||||||
|
UpdateDebugDispatcherFlag();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AddStepOverBreakpoint()
|
||||||
|
{
|
||||||
|
u32 bp_pc = g_state.regs.pc;
|
||||||
|
|
||||||
|
Instruction inst;
|
||||||
|
if (!SafeReadInstruction(bp_pc, &inst.bits))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bp_pc += sizeof(Instruction);
|
||||||
|
|
||||||
|
if (!IsCallInstruction(inst))
|
||||||
|
{
|
||||||
|
g_host_interface->ReportFormattedDebuggerMessage(
|
||||||
|
g_host_interface->TranslateString("DebuggerMessage", "0x%08X is not a call instruction."), g_state.regs.pc);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SafeReadInstruction(bp_pc, &inst.bits))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (IsBranchInstruction(inst))
|
||||||
|
{
|
||||||
|
g_host_interface->ReportFormattedDebuggerMessage(
|
||||||
|
g_host_interface->TranslateString("DebuggerMessage", "Can't step over double branch at 0x%08X"), g_state.regs.pc);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip the delay slot
|
||||||
|
bp_pc += sizeof(Instruction);
|
||||||
|
|
||||||
|
g_host_interface->ReportFormattedDebuggerMessage(
|
||||||
|
g_host_interface->TranslateString("DebuggerMessage", "Stepping over to 0x%08X."), bp_pc);
|
||||||
|
|
||||||
|
return AddBreakpoint(bp_pc, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AddStepOutBreakpoint(u32 max_instructions_to_search)
|
||||||
|
{
|
||||||
|
// find the branch-to-ra instruction.
|
||||||
|
u32 ret_pc = g_state.regs.pc;
|
||||||
|
for (u32 i = 0; i < max_instructions_to_search; i++)
|
||||||
|
{
|
||||||
|
ret_pc += sizeof(Instruction);
|
||||||
|
|
||||||
|
Instruction inst;
|
||||||
|
if (!SafeReadInstruction(ret_pc, &inst.bits))
|
||||||
|
{
|
||||||
|
g_host_interface->ReportFormattedDebuggerMessage(
|
||||||
|
g_host_interface->TranslateString("DebuggerMessage",
|
||||||
|
"Instruction read failed at %08X while searching for function end."),
|
||||||
|
ret_pc);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsReturnInstruction(inst))
|
||||||
|
{
|
||||||
|
g_host_interface->ReportFormattedDebuggerMessage(
|
||||||
|
g_host_interface->TranslateString("DebuggerMessage", "Stepping out to 0x%08X."), ret_pc);
|
||||||
|
|
||||||
|
return AddBreakpoint(ret_pc, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_host_interface->ReportFormattedDebuggerMessage(
|
||||||
|
g_host_interface->TranslateString("DebuggerMessage",
|
||||||
|
"No return instruction found after %u instructions for step-out at %08X."),
|
||||||
|
max_instructions_to_search, g_state.regs.pc);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ALWAYS_INLINE_RELEASE bool BreakpointCheck()
|
||||||
|
{
|
||||||
|
const u32 pc = g_state.regs.pc;
|
||||||
|
|
||||||
|
// single step - we want to break out after this instruction, so set a pending exit
|
||||||
|
// the bp check happens just before execution, so this is fine
|
||||||
|
if (s_single_step)
|
||||||
|
{
|
||||||
|
ForceDispatcherExit();
|
||||||
|
s_single_step = false;
|
||||||
|
s_last_breakpoint_check_pc = pc;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pc == s_last_breakpoint_check_pc)
|
||||||
|
{
|
||||||
|
// we don't want to trigger the same breakpoint which just paused us repeatedly.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 count = static_cast<u32>(s_breakpoints.size());
|
||||||
|
for (u32 i = 0; i < count;)
|
||||||
|
{
|
||||||
|
Breakpoint& bp = s_breakpoints[i];
|
||||||
|
if (!bp.enabled || bp.address != pc)
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bp.hit_count++;
|
||||||
|
|
||||||
|
if (bp.callback)
|
||||||
|
{
|
||||||
|
// if callback returns false, the bp is no longer recorded
|
||||||
|
if (!bp.callback(pc))
|
||||||
|
{
|
||||||
|
s_breakpoints.erase(s_breakpoints.begin() + i);
|
||||||
|
count--;
|
||||||
|
UpdateDebugDispatcherFlag();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_host_interface->PauseSystem(true);
|
||||||
|
|
||||||
|
if (bp.auto_clear)
|
||||||
|
{
|
||||||
|
g_host_interface->ReportFormattedDebuggerMessage("Stopped execution at 0x%08X.", pc);
|
||||||
|
s_breakpoints.erase(s_breakpoints.begin() + i);
|
||||||
|
count--;
|
||||||
|
UpdateDebugDispatcherFlag();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_host_interface->ReportFormattedDebuggerMessage("Hit breakpoint %u at 0x%08X.", bp.number, pc);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s_last_breakpoint_check_pc = pc;
|
||||||
|
return System::IsPaused();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<PGXPMode pgxp_mode, bool debug>
|
||||||
static void ExecuteImpl()
|
static void ExecuteImpl()
|
||||||
{
|
{
|
||||||
|
g_using_interpreter = true;
|
||||||
g_state.frame_done = false;
|
g_state.frame_done = false;
|
||||||
while (!g_state.frame_done)
|
while (!g_state.frame_done)
|
||||||
{
|
{
|
||||||
|
@ -1442,6 +1732,15 @@ static void ExecuteImpl()
|
||||||
if (HasPendingInterrupt() && !g_state.interrupt_delay)
|
if (HasPendingInterrupt() && !g_state.interrupt_delay)
|
||||||
DispatchInterrupt();
|
DispatchInterrupt();
|
||||||
|
|
||||||
|
if constexpr (debug)
|
||||||
|
{
|
||||||
|
if (BreakpointCheck())
|
||||||
|
{
|
||||||
|
// continue is measurably faster than break on msvc for some reason
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
g_state.interrupt_delay = false;
|
g_state.interrupt_delay = false;
|
||||||
g_state.pending_ticks++;
|
g_state.pending_ticks++;
|
||||||
|
|
||||||
|
@ -1454,7 +1753,7 @@ static void ExecuteImpl()
|
||||||
g_state.branch_was_taken = false;
|
g_state.branch_was_taken = false;
|
||||||
g_state.exception_raised = false;
|
g_state.exception_raised = false;
|
||||||
|
|
||||||
// fetch the next instruction
|
// fetch the next instruction - even if this fails, it'll still refetch on the flush so we can continue
|
||||||
if (!FetchInstruction())
|
if (!FetchInstruction())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@ -1482,16 +1781,38 @@ void Execute()
|
||||||
if (g_settings.gpu_pgxp_enable)
|
if (g_settings.gpu_pgxp_enable)
|
||||||
{
|
{
|
||||||
if (g_settings.gpu_pgxp_cpu)
|
if (g_settings.gpu_pgxp_cpu)
|
||||||
ExecuteImpl<PGXPMode::CPU>();
|
ExecuteImpl<PGXPMode::CPU, false>();
|
||||||
else
|
else
|
||||||
ExecuteImpl<PGXPMode::Memory>();
|
ExecuteImpl<PGXPMode::Memory, false>();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ExecuteImpl<PGXPMode::Disabled>();
|
ExecuteImpl<PGXPMode::Disabled, false>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ExecuteDebug()
|
||||||
|
{
|
||||||
|
if (g_settings.gpu_pgxp_enable)
|
||||||
|
{
|
||||||
|
if (g_settings.gpu_pgxp_cpu)
|
||||||
|
ExecuteImpl<PGXPMode::CPU, true>();
|
||||||
|
else
|
||||||
|
ExecuteImpl<PGXPMode::Memory, true>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ExecuteImpl<PGXPMode::Disabled, true>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SingleStep()
|
||||||
|
{
|
||||||
|
s_single_step = true;
|
||||||
|
ExecuteDebug();
|
||||||
|
g_host_interface->ReportFormattedDebuggerMessage("Stepped to 0x%08X.", g_state.regs.pc);
|
||||||
|
}
|
||||||
|
|
||||||
namespace CodeCache {
|
namespace CodeCache {
|
||||||
|
|
||||||
template<PGXPMode pgxp_mode>
|
template<PGXPMode pgxp_mode>
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class StateWrapper;
|
class StateWrapper;
|
||||||
|
|
||||||
|
@ -74,6 +75,9 @@ struct State
|
||||||
// GTE registers are stored here so we can access them on ARM with a single instruction
|
// GTE registers are stored here so we can access them on ARM with a single instruction
|
||||||
GTE::Regs gte_regs = {};
|
GTE::Regs gte_regs = {};
|
||||||
|
|
||||||
|
// 4 bytes of padding here on x64
|
||||||
|
bool use_debug_dispatcher = false;
|
||||||
|
|
||||||
u8* fastmem_base = nullptr;
|
u8* fastmem_base = nullptr;
|
||||||
|
|
||||||
// data cache (used as scratchpad)
|
// data cache (used as scratchpad)
|
||||||
|
@ -83,6 +87,7 @@ struct State
|
||||||
};
|
};
|
||||||
|
|
||||||
extern State g_state;
|
extern State g_state;
|
||||||
|
extern bool g_using_interpreter;
|
||||||
|
|
||||||
void Initialize();
|
void Initialize();
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
@ -92,6 +97,11 @@ void ClearICache();
|
||||||
|
|
||||||
/// Executes interpreter loop.
|
/// Executes interpreter loop.
|
||||||
void Execute();
|
void Execute();
|
||||||
|
void ExecuteDebug();
|
||||||
|
void SingleStep();
|
||||||
|
|
||||||
|
// Forces an early exit from the CPU dispatcher.
|
||||||
|
void ForceDispatcherExit();
|
||||||
|
|
||||||
ALWAYS_INLINE Registers& GetRegs() { return g_state.regs; }
|
ALWAYS_INLINE Registers& GetRegs() { return g_state.regs; }
|
||||||
|
|
||||||
|
@ -122,6 +132,32 @@ void DisassembleAndPrint(u32 addr, u32 instructions_before, u32 instructions_aft
|
||||||
// Write to CPU execution log file.
|
// Write to CPU execution log file.
|
||||||
void WriteToExecutionLog(const char* format, ...);
|
void WriteToExecutionLog(const char* format, ...);
|
||||||
|
|
||||||
|
// Breakpoint callback - if the callback returns false, the breakpoint will be removed.
|
||||||
|
using BreakpointCallback = bool (*)(VirtualMemoryAddress address);
|
||||||
|
|
||||||
|
struct Breakpoint
|
||||||
|
{
|
||||||
|
VirtualMemoryAddress address;
|
||||||
|
BreakpointCallback callback;
|
||||||
|
u32 number;
|
||||||
|
u32 hit_count;
|
||||||
|
bool auto_clear;
|
||||||
|
bool enabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
using BreakpointList = std::vector<Breakpoint>;
|
||||||
|
|
||||||
|
// Breakpoints
|
||||||
|
bool HasAnyBreakpoints();
|
||||||
|
bool HasBreakpointAtAddress(VirtualMemoryAddress address);
|
||||||
|
BreakpointList GetBreakpointList(bool include_auto_clear = false, bool include_callbacks = false);
|
||||||
|
bool AddBreakpoint(VirtualMemoryAddress address, bool auto_clear = false, bool enabled = true);
|
||||||
|
bool AddBreakpointWithCallback(VirtualMemoryAddress address, BreakpointCallback callback);
|
||||||
|
bool RemoveBreakpoint(VirtualMemoryAddress address);
|
||||||
|
void ClearBreakpoints();
|
||||||
|
bool AddStepOverBreakpoint();
|
||||||
|
bool AddStepOutBreakpoint(u32 max_instructions_to_search = 1000);
|
||||||
|
|
||||||
extern bool TRACE_EXECUTION;
|
extern bool TRACE_EXECUTION;
|
||||||
extern bool LOG_EXECUTION;
|
extern bool LOG_EXECUTION;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#include "cpu_disasm.h"
|
#include "cpu_disasm.h"
|
||||||
#include "cpu_core.h"
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
#include "cpu_core.h"
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
namespace CPU {
|
namespace CPU {
|
||||||
|
@ -167,12 +167,10 @@ static const std::array<std::pair<CopCommonInstruction, const char*>, 5> s_cop_c
|
||||||
|
|
||||||
static const std::array<std::pair<Cop0Instruction, const char*>, 1> s_cop0_table = {{{Cop0Instruction::rfe, "rfe"}}};
|
static const std::array<std::pair<Cop0Instruction, const char*>, 1> s_cop0_table = {{{Cop0Instruction::rfe, "rfe"}}};
|
||||||
|
|
||||||
static void FormatInstruction(String* dest, const Instruction inst, u32 pc, Registers* regs, const char* format)
|
static void FormatInstruction(String* dest, const Instruction inst, u32 pc, const char* format)
|
||||||
{
|
{
|
||||||
dest->Clear();
|
dest->Clear();
|
||||||
|
|
||||||
TinyString comment;
|
|
||||||
|
|
||||||
const char* str = format;
|
const char* str = format;
|
||||||
while (*str != '\0')
|
while (*str != '\0')
|
||||||
{
|
{
|
||||||
|
@ -186,34 +184,16 @@ static void FormatInstruction(String* dest, const Instruction inst, u32 pc, Regi
|
||||||
if (std::strncmp(str, "rs", 2) == 0)
|
if (std::strncmp(str, "rs", 2) == 0)
|
||||||
{
|
{
|
||||||
dest->AppendString(GetRegName(inst.r.rs));
|
dest->AppendString(GetRegName(inst.r.rs));
|
||||||
if (regs)
|
|
||||||
{
|
|
||||||
comment.AppendFormattedString("%s%s=0x%08X", comment.IsEmpty() ? "" : ", ", GetRegName(inst.r.rs),
|
|
||||||
regs->r[static_cast<u8>(inst.r.rs.GetValue())]);
|
|
||||||
}
|
|
||||||
|
|
||||||
str += 2;
|
str += 2;
|
||||||
}
|
}
|
||||||
else if (std::strncmp(str, "rt", 2) == 0)
|
else if (std::strncmp(str, "rt", 2) == 0)
|
||||||
{
|
{
|
||||||
dest->AppendString(GetRegName(inst.r.rt));
|
dest->AppendString(GetRegName(inst.r.rt));
|
||||||
if (regs)
|
|
||||||
{
|
|
||||||
comment.AppendFormattedString("%s%s=0x%08X", comment.IsEmpty() ? "" : ", ", GetRegName(inst.r.rt),
|
|
||||||
regs->r[static_cast<u8>(inst.r.rt.GetValue())]);
|
|
||||||
}
|
|
||||||
|
|
||||||
str += 2;
|
str += 2;
|
||||||
}
|
}
|
||||||
else if (std::strncmp(str, "rd", 2) == 0)
|
else if (std::strncmp(str, "rd", 2) == 0)
|
||||||
{
|
{
|
||||||
dest->AppendString(GetRegName(inst.r.rd));
|
dest->AppendString(GetRegName(inst.r.rd));
|
||||||
if (regs)
|
|
||||||
{
|
|
||||||
comment.AppendFormattedString("%s%s=0x%08X", comment.IsEmpty() ? "" : ", ", GetRegName(inst.r.rd),
|
|
||||||
regs->r[static_cast<u8>(inst.r.rd.GetValue())]);
|
|
||||||
}
|
|
||||||
|
|
||||||
str += 2;
|
str += 2;
|
||||||
}
|
}
|
||||||
else if (std::strncmp(str, "shamt", 5) == 0)
|
else if (std::strncmp(str, "shamt", 5) == 0)
|
||||||
|
@ -242,12 +222,6 @@ static void FormatInstruction(String* dest, const Instruction inst, u32 pc, Regi
|
||||||
{
|
{
|
||||||
const s32 offset = static_cast<s32>(inst.i.imm_sext32());
|
const s32 offset = static_cast<s32>(inst.i.imm_sext32());
|
||||||
dest->AppendFormattedString("%d(%s)", offset, GetRegName(inst.i.rs));
|
dest->AppendFormattedString("%d(%s)", offset, GetRegName(inst.i.rs));
|
||||||
if (regs)
|
|
||||||
{
|
|
||||||
comment.AppendFormattedString("%saddr=0x%08X", comment.IsEmpty() ? "" : ", ",
|
|
||||||
regs->r[static_cast<u8>(inst.i.rs.GetValue())] + offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
str += 8;
|
str += 8;
|
||||||
}
|
}
|
||||||
else if (std::strncmp(str, "jt", 2) == 0)
|
else if (std::strncmp(str, "jt", 2) == 0)
|
||||||
|
@ -281,25 +255,95 @@ static void FormatInstruction(String* dest, const Instruction inst, u32 pc, Regi
|
||||||
Panic("Unknown operand");
|
Panic("Unknown operand");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!comment.IsEmpty())
|
static void FormatComment(String* dest, const Instruction inst, u32 pc, Registers* regs, const char* format)
|
||||||
|
{
|
||||||
|
const char* str = format;
|
||||||
|
while (*str != '\0')
|
||||||
{
|
{
|
||||||
for (u32 i = dest->GetLength(); i < 30; i++)
|
const char ch = *(str++);
|
||||||
dest->AppendCharacter(' ');
|
if (ch != '$')
|
||||||
dest->AppendString("; ");
|
continue;
|
||||||
dest->AppendString(comment);
|
|
||||||
|
if (std::strncmp(str, "rs", 2) == 0)
|
||||||
|
{
|
||||||
|
dest->AppendFormattedString("%s%s=0x%08X", dest->IsEmpty() ? "" : ", ", GetRegName(inst.r.rs),
|
||||||
|
regs->r[static_cast<u8>(inst.r.rs.GetValue())]);
|
||||||
|
|
||||||
|
str += 2;
|
||||||
|
}
|
||||||
|
else if (std::strncmp(str, "rt", 2) == 0)
|
||||||
|
{
|
||||||
|
dest->AppendFormattedString("%s%s=0x%08X", dest->IsEmpty() ? "" : ", ", GetRegName(inst.r.rt),
|
||||||
|
regs->r[static_cast<u8>(inst.r.rt.GetValue())]);
|
||||||
|
str += 2;
|
||||||
|
}
|
||||||
|
else if (std::strncmp(str, "rd", 2) == 0)
|
||||||
|
{
|
||||||
|
dest->AppendFormattedString("%s%s=0x%08X", dest->IsEmpty() ? "" : ", ", GetRegName(inst.r.rd),
|
||||||
|
regs->r[static_cast<u8>(inst.r.rd.GetValue())]);
|
||||||
|
str += 2;
|
||||||
|
}
|
||||||
|
else if (std::strncmp(str, "shamt", 5) == 0)
|
||||||
|
{
|
||||||
|
str += 5;
|
||||||
|
}
|
||||||
|
else if (std::strncmp(str, "immu", 4) == 0)
|
||||||
|
{
|
||||||
|
str += 4;
|
||||||
|
}
|
||||||
|
else if (std::strncmp(str, "imm", 3) == 0)
|
||||||
|
{
|
||||||
|
str += 3;
|
||||||
|
}
|
||||||
|
else if (std::strncmp(str, "rel", 3) == 0)
|
||||||
|
{
|
||||||
|
str += 3;
|
||||||
|
}
|
||||||
|
else if (std::strncmp(str, "offsetrs", 8) == 0)
|
||||||
|
{
|
||||||
|
const s32 offset = static_cast<s32>(inst.i.imm_sext32());
|
||||||
|
dest->AppendFormattedString("%saddr=0x%08X", dest->IsEmpty() ? "" : ", ",
|
||||||
|
regs->r[static_cast<u8>(inst.i.rs.GetValue())] + offset);
|
||||||
|
str += 8;
|
||||||
|
}
|
||||||
|
else if (std::strncmp(str, "jt", 2) == 0)
|
||||||
|
{
|
||||||
|
str += 2;
|
||||||
|
}
|
||||||
|
else if (std::strncmp(str, "copcc", 5) == 0)
|
||||||
|
{
|
||||||
|
str += 5;
|
||||||
|
}
|
||||||
|
else if (std::strncmp(str, "coprd", 5) == 0)
|
||||||
|
{
|
||||||
|
str += 5;
|
||||||
|
}
|
||||||
|
else if (std::strncmp(str, "coprt", 5) == 0)
|
||||||
|
{
|
||||||
|
str += 5;
|
||||||
|
}
|
||||||
|
else if (std::strncmp(str, "cop", 3) == 0)
|
||||||
|
{
|
||||||
|
str += 3;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Panic("Unknown operand");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void FormatCopInstruction(String* dest, u32 pc, Registers* regs, const Instruction inst,
|
void FormatCopInstruction(String* dest, u32 pc, const Instruction inst, const std::pair<T, const char*>* table,
|
||||||
const std::pair<T, const char*>* table, size_t table_size, T table_key)
|
size_t table_size, T table_key)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < table_size; i++)
|
for (size_t i = 0; i < table_size; i++)
|
||||||
{
|
{
|
||||||
if (table[i].first == table_key)
|
if (table[i].first == table_key)
|
||||||
{
|
{
|
||||||
FormatInstruction(dest, inst, pc, regs, table[i].second);
|
FormatInstruction(dest, inst, pc, table[i].second);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -307,13 +351,27 @@ void FormatCopInstruction(String* dest, u32 pc, Registers* regs, const Instructi
|
||||||
dest->Format("<cop%u 0x%08X>", ZeroExtend32(inst.cop.cop_n.GetValue()), inst.cop.imm25.GetValue());
|
dest->Format("<cop%u 0x%08X>", ZeroExtend32(inst.cop.cop_n.GetValue()), inst.cop.imm25.GetValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisassembleInstruction(String* dest, u32 pc, u32 bits, Registers* regs)
|
template<typename T>
|
||||||
|
void FormatCopComment(String* dest, u32 pc, Registers* regs, const Instruction inst,
|
||||||
|
const std::pair<T, const char*>* table, size_t table_size, T table_key)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < table_size; i++)
|
||||||
|
{
|
||||||
|
if (table[i].first == table_key)
|
||||||
|
{
|
||||||
|
FormatComment(dest, inst, pc, regs, table[i].second);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisassembleInstruction(String* dest, u32 pc, u32 bits)
|
||||||
{
|
{
|
||||||
const Instruction inst{bits};
|
const Instruction inst{bits};
|
||||||
switch (inst.op)
|
switch (inst.op)
|
||||||
{
|
{
|
||||||
case InstructionOp::funct:
|
case InstructionOp::funct:
|
||||||
FormatInstruction(dest, inst, pc, regs, s_special_table[static_cast<u8>(inst.r.funct.GetValue())]);
|
FormatInstruction(dest, inst, pc, s_special_table[static_cast<u8>(inst.r.funct.GetValue())]);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case InstructionOp::cop0:
|
case InstructionOp::cop0:
|
||||||
|
@ -323,8 +381,7 @@ void DisassembleInstruction(String* dest, u32 pc, u32 bits, Registers* regs)
|
||||||
{
|
{
|
||||||
if (inst.cop.IsCommonInstruction())
|
if (inst.cop.IsCommonInstruction())
|
||||||
{
|
{
|
||||||
FormatCopInstruction(dest, pc, regs, inst, s_cop_common_table.data(), s_cop_common_table.size(),
|
FormatCopInstruction(dest, pc, inst, s_cop_common_table.data(), s_cop_common_table.size(), inst.cop.CommonOp());
|
||||||
inst.cop.CommonOp());
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -332,7 +389,7 @@ void DisassembleInstruction(String* dest, u32 pc, u32 bits, Registers* regs)
|
||||||
{
|
{
|
||||||
case InstructionOp::cop0:
|
case InstructionOp::cop0:
|
||||||
{
|
{
|
||||||
FormatCopInstruction(dest, pc, regs, inst, s_cop0_table.data(), s_cop0_table.size(), inst.cop.Cop0Op());
|
FormatCopInstruction(dest, pc, inst, s_cop0_table.data(), s_cop0_table.size(), inst.cop.Cop0Op());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -356,14 +413,75 @@ void DisassembleInstruction(String* dest, u32 pc, u32 bits, Registers* regs)
|
||||||
const bool bgez = ConvertToBoolUnchecked(rt & u8(1));
|
const bool bgez = ConvertToBoolUnchecked(rt & u8(1));
|
||||||
const bool link = ConvertToBoolUnchecked((rt >> 4) & u8(1));
|
const bool link = ConvertToBoolUnchecked((rt >> 4) & u8(1));
|
||||||
if (link)
|
if (link)
|
||||||
FormatInstruction(dest, inst, pc, regs, bgez ? "bgezal $rs, $rel" : "bltzal $rs, $rel");
|
FormatInstruction(dest, inst, pc, bgez ? "bgezal $rs, $rel" : "bltzal $rs, $rel");
|
||||||
else
|
else
|
||||||
FormatInstruction(dest, inst, pc, regs, bgez ? "bgez $rs, $rel" : "bltz $rs, $rel");
|
FormatInstruction(dest, inst, pc, bgez ? "bgez $rs, $rel" : "bltz $rs, $rel");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
FormatInstruction(dest, inst, pc, regs, s_base_table[static_cast<u8>(inst.op.GetValue())]);
|
FormatInstruction(dest, inst, pc, s_base_table[static_cast<u8>(inst.op.GetValue())]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisassembleInstructionComment(String* dest, u32 pc, u32 bits, Registers* regs)
|
||||||
|
{
|
||||||
|
const Instruction inst{bits};
|
||||||
|
switch (inst.op)
|
||||||
|
{
|
||||||
|
case InstructionOp::funct:
|
||||||
|
FormatComment(dest, inst, pc, regs, s_special_table[static_cast<u8>(inst.r.funct.GetValue())]);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case InstructionOp::cop0:
|
||||||
|
case InstructionOp::cop1:
|
||||||
|
case InstructionOp::cop2:
|
||||||
|
case InstructionOp::cop3:
|
||||||
|
{
|
||||||
|
if (inst.cop.IsCommonInstruction())
|
||||||
|
{
|
||||||
|
FormatCopComment(dest, pc, regs, inst, s_cop_common_table.data(), s_cop_common_table.size(),
|
||||||
|
inst.cop.CommonOp());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (inst.op)
|
||||||
|
{
|
||||||
|
case InstructionOp::cop0:
|
||||||
|
{
|
||||||
|
FormatCopComment(dest, pc, regs, inst, s_cop0_table.data(), s_cop0_table.size(), inst.cop.Cop0Op());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case InstructionOp::cop1:
|
||||||
|
case InstructionOp::cop2:
|
||||||
|
case InstructionOp::cop3:
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
dest->Format("<cop%u 0x%08X>", ZeroExtend32(inst.cop.cop_n.GetValue()), inst.cop.imm25.GetValue());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// special case for bltz/bgez{al}
|
||||||
|
case InstructionOp::b:
|
||||||
|
{
|
||||||
|
const u8 rt = static_cast<u8>(inst.i.rt.GetValue());
|
||||||
|
const bool bgez = ConvertToBoolUnchecked(rt & u8(1));
|
||||||
|
const bool link = ConvertToBoolUnchecked((rt >> 4) & u8(1));
|
||||||
|
if (link)
|
||||||
|
FormatComment(dest, inst, pc, regs, bgez ? "bgezal $rs, $rel" : "bltzal $rs, $rel");
|
||||||
|
else
|
||||||
|
FormatComment(dest, inst, pc, regs, bgez ? "bgez $rs, $rel" : "bltz $rs, $rel");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
FormatComment(dest, inst, pc, regs, s_base_table[static_cast<u8>(inst.op.GetValue())]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,5 +3,6 @@
|
||||||
#include "cpu_types.h"
|
#include "cpu_types.h"
|
||||||
|
|
||||||
namespace CPU {
|
namespace CPU {
|
||||||
void DisassembleInstruction(String* dest, u32 pc, u32 bits, Registers* regs = nullptr);
|
void DisassembleInstruction(String* dest, u32 pc, u32 bits);
|
||||||
|
void DisassembleInstructionComment(String* dest, u32 pc, u32 bits, Registers* regs);
|
||||||
} // namespace CPU
|
} // namespace CPU
|
||||||
|
|
|
@ -35,7 +35,7 @@ bool CodeGenerator::CompileBlock(CodeBlock* block, CodeBlock::HostCodePointer* o
|
||||||
{
|
{
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
SmallString disasm;
|
SmallString disasm;
|
||||||
DisassembleInstruction(&disasm, cbi->pc, cbi->instruction.bits, nullptr);
|
DisassembleInstruction(&disasm, cbi->pc, cbi->instruction.bits);
|
||||||
Log_DebugPrintf("Compiling instruction '%s'", disasm.GetCharArray());
|
Log_DebugPrintf("Compiling instruction '%s'", disasm.GetCharArray());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -111,9 +111,15 @@ State GetState()
|
||||||
|
|
||||||
void SetState(State new_state)
|
void SetState(State new_state)
|
||||||
{
|
{
|
||||||
|
if (s_state == new_state)
|
||||||
|
return;
|
||||||
|
|
||||||
Assert(s_state == State::Paused || s_state == State::Running);
|
Assert(s_state == State::Paused || s_state == State::Running);
|
||||||
Assert(new_state == State::Paused || new_state == State::Running);
|
Assert(new_state == State::Paused || new_state == State::Running);
|
||||||
s_state = new_state;
|
s_state = new_state;
|
||||||
|
|
||||||
|
if (new_state == State::Paused)
|
||||||
|
CPU::ForceDispatcherExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsRunning()
|
bool IsRunning()
|
||||||
|
@ -1163,30 +1169,55 @@ bool SaveState(ByteStream* state, u32 screenshot_size /* = 128 */)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SingleStepCPU()
|
||||||
|
{
|
||||||
|
const u32 old_frame_number = s_frame_number;
|
||||||
|
|
||||||
|
s_frame_timer.Reset();
|
||||||
|
|
||||||
|
g_gpu->RestoreGraphicsAPIState();
|
||||||
|
|
||||||
|
CPU::SingleStep();
|
||||||
|
|
||||||
|
g_spu.GeneratePendingSamples();
|
||||||
|
|
||||||
|
if (s_frame_number != old_frame_number && s_cheat_list)
|
||||||
|
s_cheat_list->Apply();
|
||||||
|
|
||||||
|
g_gpu->ResetGraphicsAPIState();
|
||||||
|
}
|
||||||
|
|
||||||
void RunFrame()
|
void RunFrame()
|
||||||
{
|
{
|
||||||
s_frame_timer.Reset();
|
s_frame_timer.Reset();
|
||||||
|
|
||||||
g_gpu->RestoreGraphicsAPIState();
|
g_gpu->RestoreGraphicsAPIState();
|
||||||
|
|
||||||
switch (g_settings.cpu_execution_mode)
|
if (CPU::g_state.use_debug_dispatcher)
|
||||||
{
|
{
|
||||||
case CPUExecutionMode::Recompiler:
|
CPU::ExecuteDebug();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (g_settings.cpu_execution_mode)
|
||||||
|
{
|
||||||
|
case CPUExecutionMode::Recompiler:
|
||||||
#ifdef WITH_RECOMPILER
|
#ifdef WITH_RECOMPILER
|
||||||
CPU::CodeCache::ExecuteRecompiler();
|
CPU::CodeCache::ExecuteRecompiler();
|
||||||
#else
|
#else
|
||||||
CPU::CodeCache::Execute();
|
CPU::CodeCache::Execute();
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CPUExecutionMode::CachedInterpreter:
|
case CPUExecutionMode::CachedInterpreter:
|
||||||
CPU::CodeCache::Execute();
|
CPU::CodeCache::Execute();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CPUExecutionMode::Interpreter:
|
case CPUExecutionMode::Interpreter:
|
||||||
default:
|
default:
|
||||||
CPU::Execute();
|
CPU::Execute();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate any pending samples from the SPU before sleeping, this way we reduce the chances of underruns.
|
// Generate any pending samples from the SPU before sleeping, this way we reduce the chances of underruns.
|
||||||
|
|
|
@ -144,6 +144,7 @@ bool SaveState(ByteStream* state, u32 screenshot_size = 128);
|
||||||
/// Recreates the GPU component, saving/loading the state so it is preserved. Call when the GPU renderer changes.
|
/// Recreates the GPU component, saving/loading the state so it is preserved. Call when the GPU renderer changes.
|
||||||
bool RecreateGPU(GPURenderer renderer, bool update_display = true);
|
bool RecreateGPU(GPURenderer renderer, bool update_display = true);
|
||||||
|
|
||||||
|
void SingleStepCPU();
|
||||||
void RunFrame();
|
void RunFrame();
|
||||||
|
|
||||||
/// Sets target emulation speed.
|
/// Sets target emulation speed.
|
||||||
|
|
|
@ -48,8 +48,7 @@ std::unique_ptr<TimingEvent> CreateTimingEvent(std::string name, TickCount perio
|
||||||
|
|
||||||
void UpdateCPUDowncount()
|
void UpdateCPUDowncount()
|
||||||
{
|
{
|
||||||
if (!CPU::g_state.frame_done &&
|
if (!CPU::g_state.frame_done && (!CPU::HasPendingInterrupt() || CPU::g_using_interpreter))
|
||||||
(!CPU::HasPendingInterrupt() || g_settings.cpu_execution_mode == CPUExecutionMode::Interpreter))
|
|
||||||
{
|
{
|
||||||
CPU::g_state.downcount = s_active_events_head->GetDowncount();
|
CPU::g_state.downcount = s_active_events_head->GetDowncount();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue