// Copyright 2008 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "Core/HW/CPU.h" #include #include #include #include "AudioCommon/AudioCommon.h" #include "Common/CommonTypes.h" #include "Common/Event.h" #include "Core/Core.h" #include "Core/Host.h" #include "Core/PowerPC/GDBStub.h" #include "Core/PowerPC/PowerPC.h" #include "VideoCommon/Fifo.h" namespace CPU { // CPU Thread execution state. // Requires s_state_change_lock to modify the value. // Read access is unsynchronized. static State s_state = State::PowerDown; // Synchronizes EnableStepping and PauseAndLock so only one instance can be // active at a time. Simplifies code by eliminating several edge cases where // the EnableStepping(true)/PauseAndLock(true) case must release the state lock // and wait for the CPU Thread which would otherwise require additional flags. // NOTE: When using the stepping lock, it must always be acquired first. If // the lock is acquired after the state lock then that is guaranteed to // deadlock because of the order inversion. (A -> X,Y; B -> Y,X; A waits for // B, B waits for A) static std::mutex s_stepping_lock; // Primary lock. Protects changing s_state, requesting instruction stepping and // pause-and-locking. static std::mutex s_state_change_lock; // When s_state_cpu_thread_active changes to false static std::condition_variable s_state_cpu_idle_cvar; // When s_state changes / s_state_paused_and_locked becomes false (for CPU Thread only) static std::condition_variable s_state_cpu_cvar; static bool s_state_cpu_thread_active = false; static bool s_state_paused_and_locked = false; static bool s_state_system_request_stepping = false; static bool s_state_cpu_step_instruction = false; static Common::Event* s_state_cpu_step_instruction_sync = nullptr; static std::queue> s_pending_jobs; void Init(PowerPC::CPUCore cpu_core) { PowerPC::Init(cpu_core); s_state = State::Stepping; } void Shutdown() { Stop(); PowerPC::Shutdown(); } // Requires holding s_state_change_lock static void FlushStepSyncEventLocked() { if (!s_state_cpu_step_instruction) return; if (s_state_cpu_step_instruction_sync) { s_state_cpu_step_instruction_sync->Set(); s_state_cpu_step_instruction_sync = nullptr; } s_state_cpu_step_instruction = false; } static void ExecutePendingJobs(std::unique_lock& state_lock) { while (!s_pending_jobs.empty()) { auto callback = s_pending_jobs.front(); s_pending_jobs.pop(); state_lock.unlock(); callback(); state_lock.lock(); } } void Run() { // Updating the host CPU's rounding mode must be done on the CPU thread. // We can't rely on PowerPC::Init doing it, since it's called from EmuThread. PowerPC::RoundingModeUpdated(); std::unique_lock state_lock(s_state_change_lock); while (s_state != State::PowerDown) { s_state_cpu_cvar.wait(state_lock, [] { return !s_state_paused_and_locked; }); ExecutePendingJobs(state_lock); Common::Event gdb_step_sync_event; switch (s_state) { case State::Running: s_state_cpu_thread_active = true; state_lock.unlock(); // Adjust PC for JIT when debugging // SingleStep so that the "continue", "step over" and "step out" debugger functions // work when the PC is at a breakpoint at the beginning of the block // If watchpoints are enabled, any instruction could be a breakpoint. if (PowerPC::GetMode() != PowerPC::CoreMode::Interpreter) { if (PowerPC::breakpoints.IsAddressBreakPoint(PC) || PowerPC::memchecks.HasAny()) { s_state = State::Stepping; PowerPC::CoreMode old_mode = PowerPC::GetMode(); PowerPC::SetMode(PowerPC::CoreMode::Interpreter); PowerPC::SingleStep(); PowerPC::SetMode(old_mode); s_state = State::Running; } } // Enter a fast runloop PowerPC::RunLoop(); state_lock.lock(); s_state_cpu_thread_active = false; s_state_cpu_idle_cvar.notify_all(); break; case State::Stepping: // Wait for step command. s_state_cpu_cvar.wait(state_lock, [&state_lock, &gdb_step_sync_event] { ExecutePendingJobs(state_lock); state_lock.unlock(); if (GDBStub::IsActive() && GDBStub::HasControl()) { if (!GDBStub::JustConnected()) GDBStub::SendSignal(GDBStub::Signal::Sigtrap); GDBStub::ProcessCommands(true); // If we are still going to step, emulate the fact we just sent a step command if (GDBStub::HasControl()) { // Make sure the previous step by gdb was serviced if (s_state_cpu_step_instruction_sync && s_state_cpu_step_instruction_sync != &gdb_step_sync_event) s_state_cpu_step_instruction_sync->Set(); s_state_cpu_step_instruction = true; s_state_cpu_step_instruction_sync = &gdb_step_sync_event; } } state_lock.lock(); return s_state_cpu_step_instruction || !IsStepping(); }); if (!IsStepping()) { // Signal event if the mode changes. FlushStepSyncEventLocked(); continue; } if (s_state_paused_and_locked) continue; // Do step s_state_cpu_thread_active = true; state_lock.unlock(); PowerPC::SingleStep(); state_lock.lock(); s_state_cpu_thread_active = false; s_state_cpu_idle_cvar.notify_all(); // Update disasm dialog FlushStepSyncEventLocked(); Host_UpdateDisasmDialog(); break; case State::PowerDown: break; } } state_lock.unlock(); Host_UpdateDisasmDialog(); } // Requires holding s_state_change_lock static void RunAdjacentSystems(bool running) { // NOTE: We're assuming these will not try to call Break or EnableStepping. Fifo::EmulatorState(running); // Core is responsible for shutting down the sound stream. if (s_state != State::PowerDown) AudioCommon::SetSoundStreamRunning(running); } void Stop() { // Change state and wait for it to be acknowledged. // We don't need the stepping lock because State::PowerDown is a priority state which // will stick permanently. std::unique_lock state_lock(s_state_change_lock); s_state = State::PowerDown; s_state_cpu_cvar.notify_one(); while (s_state_cpu_thread_active) { s_state_cpu_idle_cvar.wait(state_lock); } RunAdjacentSystems(false); FlushStepSyncEventLocked(); } bool IsStepping() { return s_state == State::Stepping; } State GetState() { return s_state; } const State* GetStatePtr() { return &s_state; } void Reset() { } void StepOpcode(Common::Event* event) { std::lock_guard state_lock(s_state_change_lock); // If we're not stepping then this is pointless if (!IsStepping()) { if (event) event->Set(); return; } // Potential race where the previous step has not been serviced yet. if (s_state_cpu_step_instruction_sync && s_state_cpu_step_instruction_sync != event) s_state_cpu_step_instruction_sync->Set(); s_state_cpu_step_instruction = true; s_state_cpu_step_instruction_sync = event; s_state_cpu_cvar.notify_one(); } // Requires s_state_change_lock static bool SetStateLocked(State s) { if (s_state == State::PowerDown) return false; s_state = s; return true; } void EnableStepping(bool stepping) { std::lock_guard stepping_lock(s_stepping_lock); std::unique_lock state_lock(s_state_change_lock); if (stepping) { SetStateLocked(State::Stepping); while (s_state_cpu_thread_active) { s_state_cpu_idle_cvar.wait(state_lock); } RunAdjacentSystems(false); } else if (SetStateLocked(State::Running)) { s_state_cpu_cvar.notify_one(); RunAdjacentSystems(true); } } void Break() { std::lock_guard state_lock(s_state_change_lock); // If another thread is trying to PauseAndLock then we need to remember this // for later to ignore the unpause_on_unlock. if (s_state_paused_and_locked) { s_state_system_request_stepping = true; return; } // We'll deadlock if we synchronize, the CPU may block waiting for our caller to // finish resulting in the CPU loop never terminating. SetStateLocked(State::Stepping); RunAdjacentSystems(false); } void Continue() { CPU::EnableStepping(false); Core::CallOnStateChangedCallbacks(Core::State::Running); } bool PauseAndLock(bool do_lock, bool unpause_on_unlock, bool control_adjacent) { // NOTE: This is protected by s_stepping_lock. static bool s_have_fake_cpu_thread = false; bool was_unpaused = false; if (do_lock) { s_stepping_lock.lock(); std::unique_lock state_lock(s_state_change_lock); s_state_paused_and_locked = true; was_unpaused = s_state == State::Running; SetStateLocked(State::Stepping); while (s_state_cpu_thread_active) { s_state_cpu_idle_cvar.wait(state_lock); } if (control_adjacent) RunAdjacentSystems(false); state_lock.unlock(); // NOTE: It would make more sense for Core::DeclareAsCPUThread() to keep a // depth counter instead of being a boolean. if (!Core::IsCPUThread()) { s_have_fake_cpu_thread = true; Core::DeclareAsCPUThread(); } } else { // Only need the stepping lock for this if (s_have_fake_cpu_thread) { s_have_fake_cpu_thread = false; Core::UndeclareAsCPUThread(); } { std::lock_guard state_lock(s_state_change_lock); if (s_state_system_request_stepping) { s_state_system_request_stepping = false; } else if (unpause_on_unlock && SetStateLocked(State::Running)) { was_unpaused = true; } s_state_paused_and_locked = false; s_state_cpu_cvar.notify_one(); if (control_adjacent) RunAdjacentSystems(s_state == State::Running); } s_stepping_lock.unlock(); } return was_unpaused; } void AddCPUThreadJob(std::function function) { std::unique_lock state_lock(s_state_change_lock); s_pending_jobs.push(std::move(function)); } } // namespace CPU