mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-29 09:05:41 +00:00
CPU: Fix loading recompiler-saved states with interpreter
This commit is contained in:
parent
b7bfbc0cf6
commit
0407f939fc
|
@ -27,6 +27,7 @@
|
||||||
Log_SetChannel(CPU::Core);
|
Log_SetChannel(CPU::Core);
|
||||||
|
|
||||||
namespace CPU {
|
namespace CPU {
|
||||||
|
static bool ShouldUseInterpreter();
|
||||||
static void SetPC(u32 new_pc);
|
static void SetPC(u32 new_pc);
|
||||||
static void UpdateLoadDelay();
|
static void UpdateLoadDelay();
|
||||||
static void Branch(u32 target);
|
static void Branch(u32 target);
|
||||||
|
@ -66,7 +67,7 @@ static void LogInstruction(u32 bits, u32 pc, bool regs);
|
||||||
static void HandleWriteSyscall();
|
static void HandleWriteSyscall();
|
||||||
static void HandlePutcSyscall();
|
static void HandlePutcSyscall();
|
||||||
static void HandlePutsSyscall();
|
static void HandlePutsSyscall();
|
||||||
static void ExecuteDebug();
|
[[noreturn]] static void ExecuteInterpreter();
|
||||||
|
|
||||||
template<PGXPMode pgxp_mode, bool debug>
|
template<PGXPMode pgxp_mode, bool debug>
|
||||||
static void ExecuteInstruction();
|
static void ExecuteInstruction();
|
||||||
|
@ -161,7 +162,8 @@ void CPU::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;
|
g_state.using_debug_dispatcher = false;
|
||||||
|
g_state.using_interpreter = ShouldUseInterpreter();
|
||||||
for (BreakpointList& bps : s_breakpoints)
|
for (BreakpointList& bps : s_breakpoints)
|
||||||
bps.clear();
|
bps.clear();
|
||||||
s_breakpoint_counter = 1;
|
s_breakpoint_counter = 1;
|
||||||
|
@ -200,6 +202,7 @@ void CPU::Reset()
|
||||||
|
|
||||||
ClearICache();
|
ClearICache();
|
||||||
UpdateMemoryPointers();
|
UpdateMemoryPointers();
|
||||||
|
UpdateDebugDispatcherFlag();
|
||||||
|
|
||||||
GTE::Reset();
|
GTE::Reset();
|
||||||
|
|
||||||
|
@ -276,8 +279,20 @@ bool CPU::DoState(StateWrapper& sw)
|
||||||
sw.Do(&g_state.icache_data);
|
sw.Do(&g_state.icache_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool using_interpreter = g_state.using_interpreter;
|
||||||
|
sw.DoEx(&using_interpreter, 67, g_state.using_interpreter);
|
||||||
|
|
||||||
if (sw.IsReading())
|
if (sw.IsReading())
|
||||||
{
|
{
|
||||||
|
// Since the recompilers do not use npc/next_instruction, and the icache emulation doesn't actually fill the data,
|
||||||
|
// only the tags, if we save state with the recompiler, then load state with the interpreter, we're most likely
|
||||||
|
// going to crash. Clear both in the case that we are switching.
|
||||||
|
if (using_interpreter != g_state.using_interpreter)
|
||||||
|
{
|
||||||
|
WARNING_LOG("Current execution mode does not match save state. Resetting icache state.");
|
||||||
|
ExecutionModeChanged();
|
||||||
|
}
|
||||||
|
|
||||||
UpdateMemoryPointers();
|
UpdateMemoryPointers();
|
||||||
g_state.gte_completion_tick = 0;
|
g_state.gte_completion_tick = 0;
|
||||||
}
|
}
|
||||||
|
@ -285,6 +300,12 @@ bool CPU::DoState(StateWrapper& sw)
|
||||||
return !sw.HasError();
|
return !sw.HasError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE_RELEASE bool CPU::ShouldUseInterpreter()
|
||||||
|
{
|
||||||
|
// Currently, any breakpoints require the interpreter.
|
||||||
|
return (g_settings.cpu_execution_mode == CPUExecutionMode::Interpreter || g_state.using_debug_dispatcher);
|
||||||
|
}
|
||||||
|
|
||||||
ALWAYS_INLINE_RELEASE void CPU::SetPC(u32 new_pc)
|
ALWAYS_INLINE_RELEASE void CPU::SetPC(u32 new_pc)
|
||||||
{
|
{
|
||||||
DebugAssert(Common::IsAlignedPow2(new_pc, 4));
|
DebugAssert(Common::IsAlignedPow2(new_pc, 4));
|
||||||
|
@ -1983,11 +2004,16 @@ bool CPU::UpdateDebugDispatcherFlag()
|
||||||
const bool use_debug_dispatcher =
|
const bool use_debug_dispatcher =
|
||||||
has_any_breakpoints || has_cop0_breakpoints || s_trace_to_log ||
|
has_any_breakpoints || has_cop0_breakpoints || s_trace_to_log ||
|
||||||
(g_settings.cpu_execution_mode == CPUExecutionMode::Interpreter && g_settings.bios_tty_logging);
|
(g_settings.cpu_execution_mode == CPUExecutionMode::Interpreter && g_settings.bios_tty_logging);
|
||||||
if (use_debug_dispatcher == g_state.use_debug_dispatcher)
|
if (use_debug_dispatcher == g_state.using_debug_dispatcher)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
DEV_LOG("{} debug dispatcher", use_debug_dispatcher ? "Now using" : "No longer using");
|
DEV_LOG("{} debug dispatcher", use_debug_dispatcher ? "Now using" : "No longer using");
|
||||||
g_state.use_debug_dispatcher = use_debug_dispatcher;
|
g_state.using_debug_dispatcher = use_debug_dispatcher;
|
||||||
|
|
||||||
|
// Switching to interpreter?
|
||||||
|
if (g_state.using_interpreter != ShouldUseInterpreter())
|
||||||
|
ExecutionModeChanged();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2350,74 +2376,47 @@ template<PGXPMode pgxp_mode, bool debug>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::ExecuteDebug()
|
void CPU::ExecuteInterpreter()
|
||||||
{
|
{
|
||||||
if (g_settings.gpu_pgxp_enable)
|
if (g_state.using_debug_dispatcher)
|
||||||
{
|
{
|
||||||
if (g_settings.gpu_pgxp_cpu)
|
if (g_settings.gpu_pgxp_enable)
|
||||||
ExecuteImpl<PGXPMode::CPU, true>();
|
{
|
||||||
|
if (g_settings.gpu_pgxp_cpu)
|
||||||
|
ExecuteImpl<PGXPMode::CPU, true>();
|
||||||
|
else
|
||||||
|
ExecuteImpl<PGXPMode::Memory, true>();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
ExecuteImpl<PGXPMode::Memory, true>();
|
{
|
||||||
|
ExecuteImpl<PGXPMode::Disabled, true>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ExecuteImpl<PGXPMode::Disabled, true>();
|
if (g_settings.gpu_pgxp_enable)
|
||||||
|
{
|
||||||
|
if (g_settings.gpu_pgxp_cpu)
|
||||||
|
ExecuteImpl<PGXPMode::CPU, false>();
|
||||||
|
else
|
||||||
|
ExecuteImpl<PGXPMode::Memory, false>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ExecuteImpl<PGXPMode::Disabled, false>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::Execute()
|
void CPU::Execute()
|
||||||
{
|
{
|
||||||
const CPUExecutionMode exec_mode = g_settings.cpu_execution_mode;
|
|
||||||
const bool use_debug_dispatcher = g_state.use_debug_dispatcher;
|
|
||||||
if (fastjmp_set(&s_jmp_buf) != 0)
|
if (fastjmp_set(&s_jmp_buf) != 0)
|
||||||
{
|
|
||||||
// Before we return, set npc to pc so that we can switch from recs to int.
|
|
||||||
// We'll also need to fetch the next instruction to execute.
|
|
||||||
if (exec_mode != CPUExecutionMode::Interpreter && !use_debug_dispatcher)
|
|
||||||
{
|
|
||||||
if (!SafeReadInstruction(g_state.pc, &g_state.next_instruction.bits)) [[unlikely]]
|
|
||||||
{
|
|
||||||
g_state.next_instruction.bits = 0;
|
|
||||||
ERROR_LOG("Failed to read current instruction from 0x{:08X}", g_state.pc);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_state.npc = g_state.pc + sizeof(Instruction);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (use_debug_dispatcher)
|
if (g_state.using_interpreter)
|
||||||
{
|
ExecuteInterpreter();
|
||||||
ExecuteDebug();
|
else
|
||||||
return;
|
CodeCache::Execute();
|
||||||
}
|
|
||||||
|
|
||||||
switch (exec_mode)
|
|
||||||
{
|
|
||||||
case CPUExecutionMode::Recompiler:
|
|
||||||
case CPUExecutionMode::CachedInterpreter:
|
|
||||||
case CPUExecutionMode::NewRec:
|
|
||||||
CodeCache::Execute();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CPUExecutionMode::Interpreter:
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
if (g_settings.gpu_pgxp_enable)
|
|
||||||
{
|
|
||||||
if (g_settings.gpu_pgxp_cpu)
|
|
||||||
ExecuteImpl<PGXPMode::CPU, false>();
|
|
||||||
else
|
|
||||||
ExecuteImpl<PGXPMode::Memory, false>();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ExecuteImpl<PGXPMode::Disabled, false>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::SetSingleStepFlag()
|
void CPU::SetSingleStepFlag()
|
||||||
|
@ -2572,9 +2571,37 @@ void CPU::UpdateMemoryPointers()
|
||||||
|
|
||||||
void CPU::ExecutionModeChanged()
|
void CPU::ExecutionModeChanged()
|
||||||
{
|
{
|
||||||
|
const bool prev_interpreter = g_state.using_interpreter;
|
||||||
|
|
||||||
|
UpdateDebugDispatcherFlag();
|
||||||
|
|
||||||
|
// Clear out bus errors in case only memory exceptions are toggled on.
|
||||||
g_state.bus_error = false;
|
g_state.bus_error = false;
|
||||||
if (UpdateDebugDispatcherFlag())
|
|
||||||
System::InterruptExecution();
|
// Have to clear out the icache too, only the tags are valid in the recs.
|
||||||
|
ClearICache();
|
||||||
|
|
||||||
|
// Switching to interpreter?
|
||||||
|
g_state.using_interpreter = ShouldUseInterpreter();
|
||||||
|
if (g_state.using_interpreter != prev_interpreter && !prev_interpreter)
|
||||||
|
{
|
||||||
|
// Before we return, set npc to pc so that we can switch from recs to int.
|
||||||
|
// We'll also need to fetch the next instruction to execute.
|
||||||
|
if (!SafeReadInstruction(g_state.pc, &g_state.next_instruction.bits)) [[unlikely]]
|
||||||
|
{
|
||||||
|
g_state.next_instruction.bits = 0;
|
||||||
|
ERROR_LOG("Failed to read current instruction from 0x{:08X}", g_state.pc);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_state.npc = g_state.pc + sizeof(Instruction);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wipe out code cache when switching back to recompiler.
|
||||||
|
if (!g_state.using_interpreter && prev_interpreter)
|
||||||
|
CPU::CodeCache::Reset();
|
||||||
|
|
||||||
|
UpdateDebugDispatcherFlag();
|
||||||
|
System::InterruptExecution();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<bool add_ticks, bool icache_read, u32 word_count, bool raise_exceptions>
|
template<bool add_ticks, bool icache_read, u32 word_count, bool raise_exceptions>
|
||||||
|
|
|
@ -110,8 +110,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
|
// 2 bytes of padding here on x64
|
||||||
bool use_debug_dispatcher = false;
|
bool using_interpreter = false;
|
||||||
|
bool using_debug_dispatcher = false;
|
||||||
|
|
||||||
void* fastmem_base = nullptr;
|
void* fastmem_base = nullptr;
|
||||||
void** memory_handlers = nullptr;
|
void** memory_handlers = nullptr;
|
||||||
|
|
|
@ -2843,7 +2843,7 @@ bool CodeGenerator::Compile_cop0(Instruction instruction, const CodeCache::Instr
|
||||||
|
|
||||||
// update dispatcher flag, if enabled, exit block
|
// update dispatcher flag, if enabled, exit block
|
||||||
EmitFunctionCall(nullptr, &UpdateDebugDispatcherFlag);
|
EmitFunctionCall(nullptr, &UpdateDebugDispatcherFlag);
|
||||||
EmitLoadCPUStructField(dcic_value.GetHostRegister(), RegSize_8, OFFSETOF(State, use_debug_dispatcher));
|
EmitLoadCPUStructField(dcic_value.GetHostRegister(), RegSize_8, OFFSETOF(State, using_debug_dispatcher));
|
||||||
EmitBranchIfBitClear(dcic_value.GetHostRegister(), RegSize_8, 0, ¬_enabled);
|
EmitBranchIfBitClear(dcic_value.GetHostRegister(), RegSize_8, 0, ¬_enabled);
|
||||||
|
|
||||||
m_register_cache.UninhibitAllocation();
|
m_register_cache.UninhibitAllocation();
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "imgui_overlays.h"
|
#include "imgui_overlays.h"
|
||||||
#include "cdrom.h"
|
#include "cdrom.h"
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
|
#include "cpu_core_private.h"
|
||||||
#include "dma.h"
|
#include "dma.h"
|
||||||
#include "fullscreen_ui.h"
|
#include "fullscreen_ui.h"
|
||||||
#include "gpu.h"
|
#include "gpu.h"
|
||||||
|
@ -320,9 +321,9 @@ void ImGuiManager::DrawPerformanceOverlay()
|
||||||
System::GetMaximumFrameTime());
|
System::GetMaximumFrameTime());
|
||||||
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
|
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
|
||||||
|
|
||||||
if (g_settings.cpu_overclock_active ||
|
if (g_settings.cpu_overclock_active || CPU::g_state.using_interpreter ||
|
||||||
(g_settings.cpu_execution_mode != CPUExecutionMode::Recompiler || g_settings.cpu_recompiler_icache ||
|
g_settings.cpu_execution_mode != CPUExecutionMode::Recompiler || g_settings.cpu_recompiler_icache ||
|
||||||
g_settings.cpu_recompiler_memory_exceptions))
|
g_settings.cpu_recompiler_memory_exceptions)
|
||||||
{
|
{
|
||||||
first = true;
|
first = true;
|
||||||
text.assign("CPU[");
|
text.assign("CPU[");
|
||||||
|
@ -331,7 +332,7 @@ void ImGuiManager::DrawPerformanceOverlay()
|
||||||
text.append_format("{}", g_settings.GetCPUOverclockPercent());
|
text.append_format("{}", g_settings.GetCPUOverclockPercent());
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
if (g_settings.cpu_execution_mode == CPUExecutionMode::Interpreter)
|
if (CPU::g_state.using_interpreter)
|
||||||
{
|
{
|
||||||
text.append_format("{}{}", first ? "" : "/", "I");
|
text.append_format("{}{}", first ? "" : "/", "I");
|
||||||
first = false;
|
first = false;
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
static constexpr u32 SAVE_STATE_MAGIC = 0x43435544;
|
static constexpr u32 SAVE_STATE_MAGIC = 0x43435544;
|
||||||
static constexpr u32 SAVE_STATE_VERSION = 66;
|
static constexpr u32 SAVE_STATE_VERSION = 67;
|
||||||
static constexpr u32 SAVE_STATE_MINIMUM_VERSION = 42;
|
static constexpr u32 SAVE_STATE_MINIMUM_VERSION = 42;
|
||||||
|
|
||||||
static_assert(SAVE_STATE_VERSION >= SAVE_STATE_MINIMUM_VERSION);
|
static_assert(SAVE_STATE_VERSION >= SAVE_STATE_MINIMUM_VERSION);
|
||||||
|
|
|
@ -4012,7 +4012,6 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
|
||||||
CPU::CodeCache::Shutdown();
|
CPU::CodeCache::Shutdown();
|
||||||
if (g_settings.cpu_execution_mode != CPUExecutionMode::Interpreter)
|
if (g_settings.cpu_execution_mode != CPUExecutionMode::Interpreter)
|
||||||
CPU::CodeCache::Initialize();
|
CPU::CodeCache::Initialize();
|
||||||
CPU::ClearICache();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CPU::CodeCache::IsUsingAnyRecompiler() &&
|
if (CPU::CodeCache::IsUsingAnyRecompiler() &&
|
||||||
|
@ -4025,10 +4024,6 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
|
||||||
TRANSLATE_STR("OSDMessage", "Recompiler options changed, flushing all blocks."),
|
TRANSLATE_STR("OSDMessage", "Recompiler options changed, flushing all blocks."),
|
||||||
Host::OSD_INFO_DURATION);
|
Host::OSD_INFO_DURATION);
|
||||||
CPU::ExecutionModeChanged();
|
CPU::ExecutionModeChanged();
|
||||||
CPU::CodeCache::Reset();
|
|
||||||
|
|
||||||
if (g_settings.cpu_recompiler_icache != old_settings.cpu_recompiler_icache)
|
|
||||||
CPU::ClearICache();
|
|
||||||
}
|
}
|
||||||
else if (g_settings.cpu_execution_mode == CPUExecutionMode::Interpreter &&
|
else if (g_settings.cpu_execution_mode == CPUExecutionMode::Interpreter &&
|
||||||
g_settings.bios_tty_logging != old_settings.bios_tty_logging)
|
g_settings.bios_tty_logging != old_settings.bios_tty_logging)
|
||||||
|
|
Loading…
Reference in a new issue