From 70fd457cc4a14bf214f2020ecdfdbe2303ba89b7 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 6 Sep 2024 20:00:30 +1000 Subject: [PATCH] CPU: Refactor execution mode switching Fixes single step breaking in branch delay slots with recompiler. Simplifies initialization. Removes multiple sources of truth for fastmem. --- src/core/bus.cpp | 156 ++++++++++++++++-------------- src/core/bus.h | 5 +- src/core/cpu_code_cache.cpp | 43 +-------- src/core/cpu_code_cache.h | 6 -- src/core/cpu_core.cpp | 187 ++++++++++++++++++++---------------- src/core/cpu_core.h | 2 +- src/core/system.cpp | 42 ++++---- src/util/gpu_device.h | 1 + 8 files changed, 220 insertions(+), 222 deletions(-) diff --git a/src/core/bus.cpp b/src/core/bus.cpp index fa147f58f..025de0fa6 100644 --- a/src/core/bus.cpp +++ b/src/core/bus.cpp @@ -143,8 +143,6 @@ static RAM_SIZE_REG s_RAM_SIZE = {}; static std::string s_tty_line_buffer; -static CPUFastmemMode s_fastmem_mode = CPUFastmemMode::Disabled; - #ifdef ENABLE_MMAP_FASTMEM static SharedMemoryMappingArea s_fastmem_arena; static std::vector> s_fastmem_ram_views; @@ -161,6 +159,8 @@ static void SetRAMSize(bool enable_8mb_ram); static std::tuple CalculateMemoryTiming(MEMDELAY mem_delay, COMDELAY common_delay); static void RecalculateMemoryTimings(); +static void MapFastmemViews(); +static void UnmapFastmemViews(); static u8* GetLUTFastmemPointer(u32 address, u8* ram_ptr); static void SetRAMPageWritable(u32 page_index, bool writable); @@ -252,6 +252,7 @@ bool Bus::AllocateMemoryMap(bool export_shared_memory, Error* error) VERBOSE_LOG("LUTs are mapped at {}.", static_cast(g_memory_handlers)); g_memory_handlers_isc = g_memory_handlers + MEMORY_LUT_SLOTS; + g_ram_mapped_size = RAM_8MB_SIZE; SetHandlers(); #ifndef __ANDROID__ @@ -348,7 +349,7 @@ bool Bus::ReallocateMemoryMap(bool export_shared_memory, Error* error) if (System::IsValid()) { CPU::CodeCache::InvalidateAllRAMBlocks(); - UpdateFastmemViews(CPUFastmemMode::Disabled); + UnmapFastmemViews(); ram_backup.resize(RAM_8MB_SIZE); std::memcpy(ram_backup.data(), g_unprotected_ram, RAM_8MB_SIZE); @@ -365,7 +366,7 @@ bool Bus::ReallocateMemoryMap(bool export_shared_memory, Error* error) UpdateMappedRAMSize(); std::memcpy(g_unprotected_ram, ram_backup.data(), RAM_8MB_SIZE); std::memcpy(g_bios, bios_backup.data(), BIOS_SIZE); - UpdateFastmemViews(g_settings.cpu_fastmem_mode); + MapFastmemViews(); } return true; @@ -380,11 +381,10 @@ void Bus::CleanupMemoryMap() #endif } -bool Bus::Initialize() +void Bus::Initialize() { SetRAMSize(g_settings.enable_8mb_ram); - Reset(); - return true; + MapFastmemViews(); } void Bus::SetRAMSize(bool enable_8mb_ram) @@ -400,7 +400,7 @@ void Bus::SetRAMSize(bool enable_8mb_ram) void Bus::Shutdown() { - UpdateFastmemViews(CPUFastmemMode::Disabled); + UnmapFastmemViews(); CPU::g_state.fastmem_base = nullptr; g_ram_mask = 0; @@ -439,8 +439,7 @@ bool Bus::DoState(StateWrapper& sw) { const bool using_8mb_ram = (ram_size == RAM_8MB_SIZE); SetRAMSize(using_8mb_ram); - UpdateFastmemViews(s_fastmem_mode); - CPU::UpdateMemoryPointers(); + RemapFastmemViews(); } sw.Do(&g_exp1_access_time); @@ -526,18 +525,13 @@ void Bus::RecalculateMemoryTimings() g_spu_access_time[2] + 1); } -CPUFastmemMode Bus::GetFastmemMode() -{ - return s_fastmem_mode; -} - void* Bus::GetFastmemBase(bool isc) { #ifdef ENABLE_MMAP_FASTMEM - if (s_fastmem_mode == CPUFastmemMode::MMap) + if (g_settings.cpu_fastmem_mode == CPUFastmemMode::MMap) return isc ? nullptr : s_fastmem_arena.BasePointer(); #endif - if (s_fastmem_mode == CPUFastmemMode::LUT) + if (g_settings.cpu_fastmem_mode == CPUFastmemMode::LUT) return reinterpret_cast(s_fastmem_lut + (isc ? (FASTMEM_LUT_SIZE * sizeof(void*)) : 0)); return nullptr; @@ -548,24 +542,16 @@ u8* Bus::GetLUTFastmemPointer(u32 address, u8* ram_ptr) return ram_ptr - address; } -void Bus::UpdateFastmemViews(CPUFastmemMode mode) +void Bus::MapFastmemViews() { -#ifndef ENABLE_MMAP_FASTMEM - Assert(mode != CPUFastmemMode::MMap); -#else - for (const auto& it : s_fastmem_ram_views) - s_fastmem_arena.Unmap(it.first, it.second); - s_fastmem_ram_views.clear(); -#endif + Assert(s_fastmem_ram_views.empty()); - s_fastmem_mode = mode; - if (mode == CPUFastmemMode::Disabled) - return; - -#ifdef ENABLE_MMAP_FASTMEM + const CPUFastmemMode mode = g_settings.cpu_fastmem_mode; if (mode == CPUFastmemMode::MMap) { +#ifdef ENABLE_MMAP_FASTMEM auto MapRAM = [](u32 base_address) { + // No need to check mapped RAM range here, we only ever fastmem map the first 2MB. u8* map_address = s_fastmem_arena.BasePointer() + base_address; if (!s_fastmem_arena.Map(s_shmem_handle, 0, map_address, g_ram_size, PageProtect::ReadWrite)) [[unlikely]] { @@ -600,57 +586,80 @@ void Bus::UpdateFastmemViews(CPUFastmemMode mode) // KSEG1 - uncached MapRAM(0xA0000000); - - return; - } +#else + Panic("MMap fastmem should not be selected on this platform."); #endif - - if (!s_fastmem_lut) + } + else if (mode == CPUFastmemMode::LUT) { - s_fastmem_lut = static_cast(std::malloc(sizeof(u8*) * FASTMEM_LUT_SLOTS)); - Assert(s_fastmem_lut); + if (!s_fastmem_lut) + { + s_fastmem_lut = static_cast(std::malloc(sizeof(u8*) * FASTMEM_LUT_SLOTS)); + Assert(s_fastmem_lut); - INFO_LOG("Fastmem base (software): {}", static_cast(s_fastmem_lut)); - } + INFO_LOG("Fastmem base (software): {}", static_cast(s_fastmem_lut)); + } - // This assumes the top 4KB of address space is not mapped. It shouldn't be on any sane OSes. - for (u32 i = 0; i < FASTMEM_LUT_SLOTS; i++) - s_fastmem_lut[i] = GetLUTFastmemPointer(i << FASTMEM_LUT_PAGE_SHIFT, nullptr); + // This assumes the top 4KB of address space is not mapped. It shouldn't be on any sane OSes. + for (u32 i = 0; i < FASTMEM_LUT_SLOTS; i++) + s_fastmem_lut[i] = GetLUTFastmemPointer(i << FASTMEM_LUT_PAGE_SHIFT, nullptr); - auto MapRAM = [](u32 base_address) { - u8* ram_ptr = g_ram + (base_address & g_ram_mask); - for (u32 address = 0; address < g_ram_size; address += FASTMEM_LUT_PAGE_SIZE) - { - const u32 lut_index = (base_address + address) >> FASTMEM_LUT_PAGE_SHIFT; - s_fastmem_lut[lut_index] = GetLUTFastmemPointer(base_address + address, ram_ptr); - ram_ptr += FASTMEM_LUT_PAGE_SIZE; - } - }; + auto MapRAM = [](u32 base_address) { + // Don't map RAM that isn't accessible. + if ((base_address & CPU::PHYSICAL_MEMORY_ADDRESS_MASK) >= g_ram_mapped_size) + return; + + u8* ram_ptr = g_ram + (base_address & g_ram_mask); + for (u32 address = 0; address < g_ram_size; address += FASTMEM_LUT_PAGE_SIZE) + { + const u32 lut_index = (base_address + address) >> FASTMEM_LUT_PAGE_SHIFT; + s_fastmem_lut[lut_index] = GetLUTFastmemPointer(base_address + address, ram_ptr); + ram_ptr += FASTMEM_LUT_PAGE_SIZE; + } + }; + + // KUSEG - cached + MapRAM(0x00000000); + MapRAM(0x00200000); + MapRAM(0x00400000); + MapRAM(0x00600000); + + // KSEG0 - cached + MapRAM(0x80000000); + MapRAM(0x80200000); + MapRAM(0x80400000); + MapRAM(0x80600000); + + // KSEG1 - uncached + MapRAM(0xA0000000); + MapRAM(0xA0200000); + MapRAM(0xA0400000); + MapRAM(0xA0600000); + } - // KUSEG - cached - MapRAM(0x00000000); - MapRAM(0x00200000); - MapRAM(0x00400000); - MapRAM(0x00600000); + CPU::UpdateMemoryPointers(); +} - // KSEG0 - cached - MapRAM(0x80000000); - MapRAM(0x80200000); - MapRAM(0x80400000); - MapRAM(0x80600000); +void Bus::UnmapFastmemViews() +{ +#ifdef ENABLE_MMAP_FASTMEM + for (const auto& it : s_fastmem_ram_views) + s_fastmem_arena.Unmap(it.first, it.second); + s_fastmem_ram_views.clear(); +#endif +} - // KSEG1 - uncached - MapRAM(0xA0000000); - MapRAM(0xA0200000); - MapRAM(0xA0400000); - MapRAM(0xA0600000); +void Bus::RemapFastmemViews() +{ + UnmapFastmemViews(); + MapFastmemViews(); } bool Bus::CanUseFastmemForAddress(VirtualMemoryAddress address) { const PhysicalMemoryAddress paddr = address & CPU::PHYSICAL_MEMORY_ADDRESS_MASK; - switch (s_fastmem_mode) + switch (g_settings.cpu_fastmem_mode) { #ifdef ENABLE_MMAP_FASTMEM case CPUFastmemMode::MMap: @@ -706,7 +715,7 @@ void Bus::SetRAMPageWritable(u32 page_index, bool writable) } #ifdef ENABLE_MMAP_FASTMEM - if (s_fastmem_mode == CPUFastmemMode::MMap) + if (g_settings.cpu_fastmem_mode == CPUFastmemMode::MMap) { const PageProtect protect = writable ? PageProtect::ReadWrite : PageProtect::ReadOnly; @@ -734,7 +743,7 @@ void Bus::ClearRAMCodePageFlags() ERROR_LOG("Failed to restore RAM protection to read-write."); #ifdef ENABLE_MMAP_FASTMEM - if (s_fastmem_mode == CPUFastmemMode::MMap) + if (g_settings.cpu_fastmem_mode == CPUFastmemMode::MMap) { // unprotect fastmem pages for (const auto& it : s_fastmem_ram_views) @@ -1041,7 +1050,8 @@ bool Bus::SideloadEXE(const std::string& path, Error* error) } if (okay) { - const std::optional> exe_data = FileSystem::ReadBinaryFile(System::GetExeOverride().c_str(), error); + const std::optional> exe_data = + FileSystem::ReadBinaryFile(System::GetExeOverride().c_str(), error); okay = (exe_data.has_value() && InjectExecutable(exe_data->cspan(), true, error)); if (!okay) Error::AddPrefixFmt(error, "Failed to load {}: ", Path::GetFileName(path)); @@ -1961,6 +1971,8 @@ void Bus::SetHandlers() void Bus::UpdateMappedRAMSize() { + const u32 prev_mapped_size = g_ram_mapped_size; + switch (s_RAM_SIZE.memory_window) { case 4: // 2MB memory + 6MB unmapped @@ -2003,6 +2015,10 @@ void Bus::UpdateMappedRAMSize() } break; } + + // Fastmem needs to be remapped. + if (prev_mapped_size != g_ram_mapped_size) + RemapFastmemViews(); } #undef SET diff --git a/src/core/bus.h b/src/core/bus.h index 44b34eab7..b158580c6 100644 --- a/src/core/bus.h +++ b/src/core/bus.h @@ -127,7 +127,7 @@ bool ReallocateMemoryMap(bool export_shared_memory, Error* error); /// Should be called when the process crashes, to avoid leaking. void CleanupMemoryMap(); -bool Initialize(); +void Initialize(); void Shutdown(); void Reset(); bool DoState(StateWrapper& sw); @@ -144,9 +144,8 @@ ALWAYS_INLINE_RELEASE static FP* OffsetHandlerArray(void** handlers, MemoryAcces (((static_cast(size) * 2) + static_cast(type)) * MEMORY_LUT_SIZE)); } -CPUFastmemMode GetFastmemMode(); void* GetFastmemBase(bool isc); -void UpdateFastmemViews(CPUFastmemMode mode); +void RemapFastmemViews(); bool CanUseFastmemForAddress(VirtualMemoryAddress address); void SetExpansionROM(std::vector data); diff --git a/src/core/cpu_code_cache.cpp b/src/core/cpu_code_cache.cpp index 3a3aa8ee4..fe086fb02 100644 --- a/src/core/cpu_code_cache.cpp +++ b/src/core/cpu_code_cache.cpp @@ -102,7 +102,6 @@ static void BacklinkBlocks(u32 pc, const void* dst); static void UnlinkBlockExits(Block* block); static void ResetCodeBuffer(); -static void ClearASMFunctions(); static void CompileASMFunctions(); static bool CompileBlock(Block* block); static PageFaultHandler::HandlerResult HandleFastmemException(void* exception_pc, void* fault_address, bool is_write); @@ -174,7 +173,7 @@ bool CPU::CodeCache::IsUsingAnyRecompiler() bool CPU::CodeCache::IsUsingFastmem() { - return IsUsingAnyRecompiler() && g_settings.cpu_fastmem_mode != CPUFastmemMode::Disabled; + return g_settings.cpu_fastmem_mode != CPUFastmemMode::Disabled; } bool CPU::CodeCache::ProcessStartup(Error* error) @@ -214,37 +213,12 @@ void CPU::CodeCache::ProcessShutdown() #endif } -void CPU::CodeCache::Initialize() -{ - Assert(s_blocks.empty()); - - if (IsUsingAnyRecompiler()) - { - ResetCodeBuffer(); - CompileASMFunctions(); - ResetCodeLUT(); - } - - Bus::UpdateFastmemViews(IsUsingAnyRecompiler() ? g_settings.cpu_fastmem_mode : CPUFastmemMode::Disabled); - CPU::UpdateMemoryPointers(); -} - -void CPU::CodeCache::Shutdown() -{ - ClearBlocks(); - ClearASMFunctions(); - - Bus::UpdateFastmemViews(CPUFastmemMode::Disabled); - CPU::UpdateMemoryPointers(); -} - void CPU::CodeCache::Reset() { ClearBlocks(); if (IsUsingAnyRecompiler()) { - ClearASMFunctions(); ResetCodeBuffer(); CompileASMFunctions(); ResetCodeLUT(); @@ -1560,25 +1534,14 @@ const void* CPU::CodeCache::GetInterpretUncachedBlockFunction() } } -void CPU::CodeCache::ClearASMFunctions() +void CPU::CodeCache::CompileASMFunctions() { - g_enter_recompiler = nullptr; - g_compile_or_revalidate_block = nullptr; - g_check_events_and_dispatch = nullptr; - g_run_events_and_dispatch = nullptr; - g_dispatcher = nullptr; - g_interpret_block = nullptr; - g_discard_and_recompile_block = nullptr; + MemMap::BeginCodeWrite(); #ifdef _DEBUG s_total_instructions_compiled = 0; s_total_host_instructions_emitted = 0; #endif -} - -void CPU::CodeCache::CompileASMFunctions() -{ - MemMap::BeginCodeWrite(); const u32 asm_size = EmitASMFunctions(GetFreeCodePointer(), GetFreeCodeSpace()); diff --git a/src/core/cpu_code_cache.h b/src/core/cpu_code_cache.h index a04568dcd..581b94a9b 100644 --- a/src/core/cpu_code_cache.h +++ b/src/core/cpu_code_cache.h @@ -22,12 +22,6 @@ bool ProcessStartup(Error* error); /// Frees resources, call once at shutdown. void ProcessShutdown(); -/// Initializes resources for the system. -void Initialize(); - -/// Frees resources used by the system. -void Shutdown(); - /// Runs the system. [[noreturn]] void Execute(); diff --git a/src/core/cpu_core.cpp b/src/core/cpu_core.cpp index 8c87eab54..c106d93cd 100644 --- a/src/core/cpu_core.cpp +++ b/src/core/cpu_core.cpp @@ -29,7 +29,14 @@ Log_SetChannel(CPU::Core); namespace CPU { -static bool ShouldUseInterpreter(); +enum class ExecutionBreakType +{ + None, + ExecuteOneInstruction, + SingleStep, + Breakpoint, +}; + static void UpdateLoadDelay(); static void Branch(u32 target); static void FlushLoadDelay(); @@ -68,6 +75,8 @@ static void LogInstruction(u32 bits, u32 pc, bool regs); static void HandleWriteSyscall(); static void HandlePutcSyscall(); static void HandlePutsSyscall(); + +static void CheckForExecutionModeChange(); [[noreturn]] static void ExecuteInterpreter(); template @@ -104,8 +113,8 @@ static constexpr u32 INVALID_BREAKPOINT_PC = UINT32_C(0xFFFFFFFF); static std::array, static_cast(BreakpointType::Count)> s_breakpoints; static u32 s_breakpoint_counter = 1; static u32 s_last_breakpoint_check_pc = INVALID_BREAKPOINT_PC; -static bool s_single_step = false; -static bool s_break_after_instruction = false; +static CPUExecutionMode s_current_execution_mode = CPUExecutionMode::Interpreter; +static ExecutionBreakType s_break_type = ExecutionBreakType::None; } // namespace CPU bool CPU::IsTraceEnabled() @@ -163,14 +172,14 @@ void CPU::Initialize() // From nocash spec. g_state.cop0_regs.PRID = UINT32_C(0x00000002); + s_current_execution_mode = g_settings.cpu_execution_mode; g_state.using_debug_dispatcher = false; - g_state.using_interpreter = ShouldUseInterpreter(); + g_state.using_interpreter = (s_current_execution_mode == CPUExecutionMode::Interpreter); for (BreakpointList& bps : s_breakpoints) bps.clear(); s_breakpoint_counter = 1; s_last_breakpoint_check_pc = INVALID_BREAKPOINT_PC; - s_single_step = false; - s_break_after_instruction = false; + s_break_type = ExecutionBreakType::None; UpdateMemoryPointers(); UpdateDebugDispatcherFlag(); @@ -280,33 +289,23 @@ bool CPU::DoState(StateWrapper& sw) sw.Do(&g_state.icache_data); } - bool using_interpreter = g_state.using_interpreter; - sw.DoEx(&using_interpreter, 67, g_state.using_interpreter); + sw.DoEx(&g_state.using_interpreter, 67, g_state.using_interpreter); 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(); + // Trigger an execution mode change if the state was/wasn't using the interpreter. + s_current_execution_mode = + g_state.using_interpreter ? + CPUExecutionMode::Interpreter : + ((g_settings.cpu_execution_mode == CPUExecutionMode::Interpreter) ? CPUExecutionMode::CachedInterpreter : + g_settings.cpu_execution_mode); g_state.gte_completion_tick = 0; + UpdateMemoryPointers(); } 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); -} - void CPU::SetPC(u32 new_pc) { DebugAssert(Common::IsAlignedPow2(new_pc, 4)); @@ -1994,9 +1993,14 @@ void CPU::DispatchInterrupt() TimingEvents::UpdateCPUDowncount(); } +CPUExecutionMode CPU::GetCurrentExecutionMode() +{ + return s_current_execution_mode; +} + bool CPU::UpdateDebugDispatcherFlag() { - const bool has_any_breakpoints = HasAnyBreakpoints() || s_single_step; + const bool has_any_breakpoints = (HasAnyBreakpoints() || s_break_type == ExecutionBreakType::SingleStep); const auto& dcic = g_state.cop0_regs.dcic; const bool has_cop0_breakpoints = dcic.super_master_enable_1 && dcic.super_master_enable_2 && @@ -2010,12 +2014,73 @@ bool CPU::UpdateDebugDispatcherFlag() DEV_LOG("{} debug dispatcher", use_debug_dispatcher ? "Now using" : "No longer using"); g_state.using_debug_dispatcher = use_debug_dispatcher; + return true; +} - // Switching to interpreter? - if (g_state.using_interpreter != ShouldUseInterpreter()) - ExecutionModeChanged(); +void CPU::CheckForExecutionModeChange() +{ + // Currently, any breakpoints require the interpreter. + const CPUExecutionMode new_execution_mode = + (g_state.using_debug_dispatcher ? CPUExecutionMode::Interpreter : g_settings.cpu_execution_mode); + if (s_current_execution_mode == new_execution_mode) [[likely]] + { + DebugAssert(g_state.using_interpreter == (s_current_execution_mode == CPUExecutionMode::Interpreter)); + return; + } - return true; + WARNING_LOG("Execution mode changed from {} to {}", Settings::GetCPUExecutionModeName(s_current_execution_mode), + Settings::GetCPUExecutionModeName(new_execution_mode)); + + const bool new_interpreter = (new_execution_mode == CPUExecutionMode::Interpreter); + if (g_state.using_interpreter != new_interpreter) + { + // Have to clear out the icache too, only the tags are valid in the recs. + ClearICache(); + g_state.bus_error = false; + + if (new_interpreter) + { + // Switching to interpreter. Set up the pipeline. + // 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); + } + else + { + // Switching to recompiler. We can't start a rec block in a branch delay slot, so we need to execute the + // instruction if we're currently in one. + if (g_state.next_instruction_is_branch_delay_slot) [[unlikely]] + { + while (g_state.next_instruction_is_branch_delay_slot) + { + WARNING_LOG("EXECMODE: Executing instruction at 0x{:08X} because it is in a branch delay slot.", g_state.pc); + if (fastjmp_set(&s_jmp_buf) == 0) + { + s_break_type = ExecutionBreakType::ExecuteOneInstruction; + g_state.using_debug_dispatcher = true; + ExecuteInterpreter(); + } + } + + // Need to restart the whole process again, because the branch slot could change the debug flag. + UpdateDebugDispatcherFlag(); + CheckForExecutionModeChange(); + return; + } + } + } + + s_current_execution_mode = new_execution_mode; + g_state.using_interpreter = new_interpreter; + + // Wipe out code cache when switching modes. + if (!new_interpreter) + CPU::CodeCache::Reset(); } [[noreturn]] void CPU::ExitExecution() @@ -2269,20 +2334,11 @@ ALWAYS_INLINE_RELEASE bool CPU::CheckBreakpointList(BreakpointType type, Virtual ALWAYS_INLINE_RELEASE void CPU::ExecutionBreakpointCheck() { - if (s_single_step) [[unlikely]] - { - // single step ignores breakpoints, since it stops anyway - s_single_step = false; - s_break_after_instruction = true; - Host::ReportDebuggerMessage(fmt::format("Stepped to 0x{:08X}.", g_state.npc)); - return; - } - if (s_breakpoints[static_cast(BreakpointType::Execute)].empty()) [[likely]] return; const u32 pc = g_state.pc; - if (pc == s_last_breakpoint_check_pc) [[unlikely]] + if (pc == s_last_breakpoint_check_pc || s_break_type == ExecutionBreakType::ExecuteOneInstruction) [[unlikely]] { // we don't want to trigger the same breakpoint which just paused us repeatedly. return; @@ -2292,7 +2348,7 @@ ALWAYS_INLINE_RELEASE void CPU::ExecutionBreakpointCheck() if (CheckBreakpointList(BreakpointType::Execute, pc)) [[unlikely]] { - s_single_step = false; + s_break_type = ExecutionBreakType::None; ExitExecution(); } } @@ -2301,8 +2357,8 @@ template ALWAYS_INLINE_RELEASE void CPU::MemoryBreakpointCheck(VirtualMemoryAddress address) { const BreakpointType bptype = (type == MemoryAccessType::Read) ? BreakpointType::Read : BreakpointType::Write; - if (CheckBreakpointList(bptype, address)) - s_break_after_instruction = true; + if (CheckBreakpointList(bptype, address)) [[unlikely]] + s_break_type = ExecutionBreakType::Breakpoint; } template @@ -2364,10 +2420,12 @@ template if constexpr (debug) { - if (s_break_after_instruction) + if (s_break_type != ExecutionBreakType::None) [[unlikely]] { - s_break_after_instruction = false; - System::PauseSystem(true); + const ExecutionBreakType break_type = std::exchange(s_break_type, ExecutionBreakType::None); + if (break_type >= ExecutionBreakType::SingleStep) + System::PauseSystem(true); + UpdateDebugDispatcherFlag(); ExitExecution(); } @@ -2412,6 +2470,8 @@ void CPU::ExecuteInterpreter() void CPU::Execute() { + CheckForExecutionModeChange(); + if (fastjmp_set(&s_jmp_buf) != 0) return; @@ -2423,7 +2483,7 @@ void CPU::Execute() void CPU::SetSingleStepFlag() { - s_single_step = true; + s_break_type = ExecutionBreakType::SingleStep; if (UpdateDebugDispatcherFlag()) System::InterruptExecution(); } @@ -2571,41 +2631,6 @@ void CPU::UpdateMemoryPointers() g_state.fastmem_base = Bus::GetFastmemBase(g_state.cop0_regs.sr.Isc); } -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; - - // 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 ALWAYS_INLINE_RELEASE bool CPU::DoInstructionRead(PhysicalMemoryAddress address, void* data) { diff --git a/src/core/cpu_core.h b/src/core/cpu_core.h index ab1c367ba..c5ec1c700 100644 --- a/src/core/cpu_core.h +++ b/src/core/cpu_core.h @@ -132,9 +132,9 @@ void Shutdown(); void Reset(); bool DoState(StateWrapper& sw); void ClearICache(); +CPUExecutionMode GetCurrentExecutionMode(); bool UpdateDebugDispatcherFlag(); void UpdateMemoryPointers(); -void ExecutionModeChanged(); /// Executes interpreter loop. void Execute(); diff --git a/src/core/system.cpp b/src/core/system.cpp index e5374c8e6..49aba3fa8 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -1832,20 +1832,13 @@ bool System::Initialize(bool force_software_renderer, Error* error) TimingEvents::Initialize(); + Bus::Initialize(); CPU::Initialize(); - if (!Bus::Initialize()) - { - CPU::Shutdown(); - return false; - } - - CPU::CodeCache::Initialize(); - if (!CreateGPU(force_software_renderer ? GPURenderer::Software : g_settings.gpu_renderer, false, error)) { - Bus::Shutdown(); CPU::Shutdown(); + Bus::Shutdown(); return false; } @@ -1933,9 +1926,8 @@ void System::DestroySystem() g_gpu.reset(); DMA::Shutdown(); CPU::PGXP::Shutdown(); - CPU::CodeCache::Shutdown(); - Bus::Shutdown(); CPU::Shutdown(); + Bus::Shutdown(); TimingEvents::Shutdown(); ClearRunningGame(); @@ -3383,6 +3375,9 @@ void System::UpdateDisplayVSync() // Avoid flipping vsync on and off by manually throttling when vsync is on. const GPUVSyncMode vsync_mode = GetEffectiveVSyncMode(); const bool allow_present_throttle = ShouldAllowPresentThrottle(); + if (g_gpu_device->GetVSyncMode() == vsync_mode && g_gpu_device->IsPresentThrottleAllowed() == allow_present_throttle) + return; + VERBOSE_LOG("VSync: {}{}{}", vsync_modes[static_cast(vsync_mode)], s_syncing_to_host_with_vsync ? " (for throttling)" : "", allow_present_throttle ? " (present throttle allowed)" : ""); @@ -4246,22 +4241,18 @@ void System::CheckForSettingsChanges(const Settings& old_settings) if (g_settings.emulation_speed != old_settings.emulation_speed) UpdateThrottlePeriod(); - if (g_settings.cpu_execution_mode != old_settings.cpu_execution_mode || - g_settings.cpu_fastmem_mode != old_settings.cpu_fastmem_mode) + if (g_settings.cpu_execution_mode != old_settings.cpu_execution_mode) { Host::AddIconOSDMessage("CPUExecutionModeSwitch", ICON_FA_MICROCHIP, fmt::format(TRANSLATE_FS("OSDMessage", "Switching to {} CPU execution mode."), TRANSLATE_SV("CPUExecutionMode", Settings::GetCPUExecutionModeDisplayName( g_settings.cpu_execution_mode))), Host::OSD_INFO_DURATION); - CPU::ExecutionModeChanged(); - if (old_settings.cpu_execution_mode != CPUExecutionMode::Interpreter) - CPU::CodeCache::Shutdown(); - if (g_settings.cpu_execution_mode != CPUExecutionMode::Interpreter) - CPU::CodeCache::Initialize(); + CPU::UpdateDebugDispatcherFlag(); + InterruptExecution(); } - if (CPU::CodeCache::IsUsingAnyRecompiler() && + if (CPU::GetCurrentExecutionMode() != CPUExecutionMode::Interpreter && (g_settings.cpu_recompiler_memory_exceptions != old_settings.cpu_recompiler_memory_exceptions || g_settings.cpu_recompiler_block_linking != old_settings.cpu_recompiler_block_linking || g_settings.cpu_recompiler_icache != old_settings.cpu_recompiler_icache || @@ -4270,12 +4261,21 @@ void System::CheckForSettingsChanges(const Settings& old_settings) Host::AddIconOSDMessage("CPUFlushAllBlocks", ICON_FA_MICROCHIP, TRANSLATE_STR("OSDMessage", "Recompiler options changed, flushing all blocks."), Host::OSD_INFO_DURATION); - CPU::ExecutionModeChanged(); + CPU::CodeCache::Reset(); + CPU::g_state.bus_error = false; } else if (g_settings.cpu_execution_mode == CPUExecutionMode::Interpreter && g_settings.bios_tty_logging != old_settings.bios_tty_logging) { - CPU::UpdateDebugDispatcherFlag(); + // TTY interception requires debug dispatcher. + if (CPU::UpdateDebugDispatcherFlag()) + InterruptExecution(); + } + + if (g_settings.cpu_fastmem_mode != old_settings.cpu_fastmem_mode) + { + // Reallocate fastmem area, even if it's not being used. + Bus::RemapFastmemViews(); } if (g_settings.enable_cheats != old_settings.enable_cheats) diff --git a/src/util/gpu_device.h b/src/util/gpu_device.h index f9800b79d..4ab1a6974 100644 --- a/src/util/gpu_device.h +++ b/src/util/gpu_device.h @@ -711,6 +711,7 @@ public: ALWAYS_INLINE GPUVSyncMode GetVSyncMode() const { return m_vsync_mode; } ALWAYS_INLINE bool IsVSyncModeBlocking() const { return (m_vsync_mode == GPUVSyncMode::FIFO); } + ALWAYS_INLINE bool IsPresentThrottleAllowed() const { return m_allow_present_throttle; } virtual void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) = 0; ALWAYS_INLINE bool IsDebugDevice() const { return m_debug_device; }