mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-22 22:00:39 -06:00
HW/CPU: Refactor to class, move to System.
This commit is contained in:
@ -19,74 +19,48 @@
|
||||
|
||||
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;
|
||||
CPUManager::CPUManager() = default;
|
||||
CPUManager::~CPUManager() = default;
|
||||
|
||||
// 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<std::function<void()>> s_pending_jobs;
|
||||
|
||||
void Init(PowerPC::CPUCore cpu_core)
|
||||
void CPUManager::Init(PowerPC::CPUCore cpu_core)
|
||||
{
|
||||
PowerPC::Init(cpu_core);
|
||||
s_state = State::Stepping;
|
||||
m_state = State::Stepping;
|
||||
}
|
||||
|
||||
void Shutdown()
|
||||
void CPUManager::Shutdown()
|
||||
{
|
||||
Stop();
|
||||
PowerPC::Shutdown();
|
||||
}
|
||||
|
||||
// Requires holding s_state_change_lock
|
||||
static void FlushStepSyncEventLocked()
|
||||
// Requires holding m_state_change_lock
|
||||
void CPUManager::FlushStepSyncEventLocked()
|
||||
{
|
||||
if (!s_state_cpu_step_instruction)
|
||||
if (!m_state_cpu_step_instruction)
|
||||
return;
|
||||
|
||||
if (s_state_cpu_step_instruction_sync)
|
||||
if (m_state_cpu_step_instruction_sync)
|
||||
{
|
||||
s_state_cpu_step_instruction_sync->Set();
|
||||
s_state_cpu_step_instruction_sync = nullptr;
|
||||
m_state_cpu_step_instruction_sync->Set();
|
||||
m_state_cpu_step_instruction_sync = nullptr;
|
||||
}
|
||||
s_state_cpu_step_instruction = false;
|
||||
m_state_cpu_step_instruction = false;
|
||||
}
|
||||
|
||||
static void ExecutePendingJobs(std::unique_lock<std::mutex>& state_lock)
|
||||
void CPUManager::ExecutePendingJobs(std::unique_lock<std::mutex>& state_lock)
|
||||
{
|
||||
while (!s_pending_jobs.empty())
|
||||
while (!m_pending_jobs.empty())
|
||||
{
|
||||
auto callback = s_pending_jobs.front();
|
||||
s_pending_jobs.pop();
|
||||
auto callback = m_pending_jobs.front();
|
||||
m_pending_jobs.pop();
|
||||
state_lock.unlock();
|
||||
callback();
|
||||
state_lock.lock();
|
||||
}
|
||||
}
|
||||
|
||||
void Run()
|
||||
void CPUManager::Run()
|
||||
{
|
||||
auto& system = Core::System::GetInstance();
|
||||
|
||||
@ -94,17 +68,17 @@ void Run()
|
||||
// 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)
|
||||
std::unique_lock state_lock(m_state_change_lock);
|
||||
while (m_state != State::PowerDown)
|
||||
{
|
||||
s_state_cpu_cvar.wait(state_lock, [] { return !s_state_paused_and_locked; });
|
||||
m_state_cpu_cvar.wait(state_lock, [this] { return !m_state_paused_and_locked; });
|
||||
ExecutePendingJobs(state_lock);
|
||||
|
||||
Common::Event gdb_step_sync_event;
|
||||
switch (s_state)
|
||||
switch (m_state)
|
||||
{
|
||||
case State::Running:
|
||||
s_state_cpu_thread_active = true;
|
||||
m_state_cpu_thread_active = true;
|
||||
state_lock.unlock();
|
||||
|
||||
// Adjust PC for JIT when debugging
|
||||
@ -116,12 +90,12 @@ void Run()
|
||||
if (PowerPC::breakpoints.IsAddressBreakPoint(system.GetPPCState().pc) ||
|
||||
PowerPC::memchecks.HasAny())
|
||||
{
|
||||
s_state = State::Stepping;
|
||||
m_state = State::Stepping;
|
||||
PowerPC::CoreMode old_mode = PowerPC::GetMode();
|
||||
PowerPC::SetMode(PowerPC::CoreMode::Interpreter);
|
||||
PowerPC::SingleStep();
|
||||
PowerPC::SetMode(old_mode);
|
||||
s_state = State::Running;
|
||||
m_state = State::Running;
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,13 +103,13 @@ void Run()
|
||||
PowerPC::RunLoop();
|
||||
|
||||
state_lock.lock();
|
||||
s_state_cpu_thread_active = false;
|
||||
s_state_cpu_idle_cvar.notify_all();
|
||||
m_state_cpu_thread_active = false;
|
||||
m_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] {
|
||||
m_state_cpu_cvar.wait(state_lock, [this, &state_lock, &gdb_step_sync_event] {
|
||||
ExecutePendingJobs(state_lock);
|
||||
state_lock.unlock();
|
||||
if (GDBStub::IsActive() && GDBStub::HasControl())
|
||||
@ -147,16 +121,18 @@ void Run()
|
||||
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();
|
||||
if (m_state_cpu_step_instruction_sync &&
|
||||
m_state_cpu_step_instruction_sync != &gdb_step_sync_event)
|
||||
{
|
||||
m_state_cpu_step_instruction_sync->Set();
|
||||
}
|
||||
|
||||
s_state_cpu_step_instruction = true;
|
||||
s_state_cpu_step_instruction_sync = &gdb_step_sync_event;
|
||||
m_state_cpu_step_instruction = true;
|
||||
m_state_cpu_step_instruction_sync = &gdb_step_sync_event;
|
||||
}
|
||||
}
|
||||
state_lock.lock();
|
||||
return s_state_cpu_step_instruction || !IsStepping();
|
||||
return m_state_cpu_step_instruction || !IsStepping();
|
||||
});
|
||||
if (!IsStepping())
|
||||
{
|
||||
@ -164,18 +140,18 @@ void Run()
|
||||
FlushStepSyncEventLocked();
|
||||
continue;
|
||||
}
|
||||
if (s_state_paused_and_locked)
|
||||
if (m_state_paused_and_locked)
|
||||
continue;
|
||||
|
||||
// Do step
|
||||
s_state_cpu_thread_active = true;
|
||||
m_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();
|
||||
m_state_cpu_thread_active = false;
|
||||
m_state_cpu_idle_cvar.notify_all();
|
||||
|
||||
// Update disasm dialog
|
||||
FlushStepSyncEventLocked();
|
||||
@ -190,57 +166,57 @@ void Run()
|
||||
Host_UpdateDisasmDialog();
|
||||
}
|
||||
|
||||
// Requires holding s_state_change_lock
|
||||
static void RunAdjacentSystems(bool running)
|
||||
// Requires holding m_state_change_lock
|
||||
void CPUManager::RunAdjacentSystems(bool running)
|
||||
{
|
||||
// NOTE: We're assuming these will not try to call Break or EnableStepping.
|
||||
auto& system = Core::System::GetInstance();
|
||||
system.GetFifo().EmulatorState(running);
|
||||
// Core is responsible for shutting down the sound stream.
|
||||
if (s_state != State::PowerDown)
|
||||
if (m_state != State::PowerDown)
|
||||
AudioCommon::SetSoundStreamRunning(Core::System::GetInstance(), running);
|
||||
}
|
||||
|
||||
void Stop()
|
||||
void CPUManager::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();
|
||||
std::unique_lock state_lock(m_state_change_lock);
|
||||
m_state = State::PowerDown;
|
||||
m_state_cpu_cvar.notify_one();
|
||||
|
||||
while (s_state_cpu_thread_active)
|
||||
while (m_state_cpu_thread_active)
|
||||
{
|
||||
s_state_cpu_idle_cvar.wait(state_lock);
|
||||
m_state_cpu_idle_cvar.wait(state_lock);
|
||||
}
|
||||
|
||||
RunAdjacentSystems(false);
|
||||
FlushStepSyncEventLocked();
|
||||
}
|
||||
|
||||
bool IsStepping()
|
||||
bool CPUManager::IsStepping() const
|
||||
{
|
||||
return s_state == State::Stepping;
|
||||
return m_state == State::Stepping;
|
||||
}
|
||||
|
||||
State GetState()
|
||||
State CPUManager::GetState() const
|
||||
{
|
||||
return s_state;
|
||||
return m_state;
|
||||
}
|
||||
|
||||
const State* GetStatePtr()
|
||||
const State* CPUManager::GetStatePtr() const
|
||||
{
|
||||
return &s_state;
|
||||
return &m_state;
|
||||
}
|
||||
|
||||
void Reset()
|
||||
void CPUManager::Reset()
|
||||
{
|
||||
}
|
||||
|
||||
void StepOpcode(Common::Event* event)
|
||||
void CPUManager::StepOpcode(Common::Event* event)
|
||||
{
|
||||
std::lock_guard state_lock(s_state_change_lock);
|
||||
std::lock_guard state_lock(m_state_change_lock);
|
||||
// If we're not stepping then this is pointless
|
||||
if (!IsStepping())
|
||||
{
|
||||
@ -250,55 +226,55 @@ void StepOpcode(Common::Event* event)
|
||||
}
|
||||
|
||||
// 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();
|
||||
if (m_state_cpu_step_instruction_sync && m_state_cpu_step_instruction_sync != event)
|
||||
m_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();
|
||||
m_state_cpu_step_instruction = true;
|
||||
m_state_cpu_step_instruction_sync = event;
|
||||
m_state_cpu_cvar.notify_one();
|
||||
}
|
||||
|
||||
// Requires s_state_change_lock
|
||||
static bool SetStateLocked(State s)
|
||||
// Requires m_state_change_lock
|
||||
bool CPUManager::SetStateLocked(State s)
|
||||
{
|
||||
if (s_state == State::PowerDown)
|
||||
if (m_state == State::PowerDown)
|
||||
return false;
|
||||
s_state = s;
|
||||
m_state = s;
|
||||
return true;
|
||||
}
|
||||
|
||||
void EnableStepping(bool stepping)
|
||||
void CPUManager::EnableStepping(bool stepping)
|
||||
{
|
||||
std::lock_guard stepping_lock(s_stepping_lock);
|
||||
std::unique_lock state_lock(s_state_change_lock);
|
||||
std::lock_guard stepping_lock(m_stepping_lock);
|
||||
std::unique_lock state_lock(m_state_change_lock);
|
||||
|
||||
if (stepping)
|
||||
{
|
||||
SetStateLocked(State::Stepping);
|
||||
|
||||
while (s_state_cpu_thread_active)
|
||||
while (m_state_cpu_thread_active)
|
||||
{
|
||||
s_state_cpu_idle_cvar.wait(state_lock);
|
||||
m_state_cpu_idle_cvar.wait(state_lock);
|
||||
}
|
||||
|
||||
RunAdjacentSystems(false);
|
||||
}
|
||||
else if (SetStateLocked(State::Running))
|
||||
{
|
||||
s_state_cpu_cvar.notify_one();
|
||||
m_state_cpu_cvar.notify_one();
|
||||
RunAdjacentSystems(true);
|
||||
}
|
||||
}
|
||||
|
||||
void Break()
|
||||
void CPUManager::Break()
|
||||
{
|
||||
std::lock_guard state_lock(s_state_change_lock);
|
||||
std::lock_guard state_lock(m_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)
|
||||
if (m_state_paused_and_locked)
|
||||
{
|
||||
s_state_system_request_stepping = true;
|
||||
m_state_system_request_stepping = true;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -308,31 +284,31 @@ void Break()
|
||||
RunAdjacentSystems(false);
|
||||
}
|
||||
|
||||
void Continue()
|
||||
void CPUManager::Continue()
|
||||
{
|
||||
CPU::EnableStepping(false);
|
||||
EnableStepping(false);
|
||||
Core::CallOnStateChangedCallbacks(Core::State::Running);
|
||||
}
|
||||
|
||||
bool PauseAndLock(bool do_lock, bool unpause_on_unlock, bool control_adjacent)
|
||||
bool CPUManager::PauseAndLock(bool do_lock, bool unpause_on_unlock, bool control_adjacent)
|
||||
{
|
||||
// NOTE: This is protected by s_stepping_lock.
|
||||
// NOTE: This is protected by m_stepping_lock.
|
||||
static bool s_have_fake_cpu_thread = false;
|
||||
bool was_unpaused = false;
|
||||
|
||||
if (do_lock)
|
||||
{
|
||||
s_stepping_lock.lock();
|
||||
m_stepping_lock.lock();
|
||||
|
||||
std::unique_lock state_lock(s_state_change_lock);
|
||||
s_state_paused_and_locked = true;
|
||||
std::unique_lock state_lock(m_state_change_lock);
|
||||
m_state_paused_and_locked = true;
|
||||
|
||||
was_unpaused = s_state == State::Running;
|
||||
was_unpaused = m_state == State::Running;
|
||||
SetStateLocked(State::Stepping);
|
||||
|
||||
while (s_state_cpu_thread_active)
|
||||
while (m_state_cpu_thread_active)
|
||||
{
|
||||
s_state_cpu_idle_cvar.wait(state_lock);
|
||||
m_state_cpu_idle_cvar.wait(state_lock);
|
||||
}
|
||||
|
||||
if (control_adjacent)
|
||||
@ -357,30 +333,30 @@ bool PauseAndLock(bool do_lock, bool unpause_on_unlock, bool control_adjacent)
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard state_lock(s_state_change_lock);
|
||||
if (s_state_system_request_stepping)
|
||||
std::lock_guard state_lock(m_state_change_lock);
|
||||
if (m_state_system_request_stepping)
|
||||
{
|
||||
s_state_system_request_stepping = false;
|
||||
m_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();
|
||||
m_state_paused_and_locked = false;
|
||||
m_state_cpu_cvar.notify_one();
|
||||
|
||||
if (control_adjacent)
|
||||
RunAdjacentSystems(s_state == State::Running);
|
||||
RunAdjacentSystems(m_state == State::Running);
|
||||
}
|
||||
s_stepping_lock.unlock();
|
||||
m_stepping_lock.unlock();
|
||||
}
|
||||
return was_unpaused;
|
||||
}
|
||||
|
||||
void AddCPUThreadJob(std::function<void()> function)
|
||||
void CPUManager::AddCPUThreadJob(std::function<void()> function)
|
||||
{
|
||||
std::unique_lock state_lock(s_state_change_lock);
|
||||
s_pending_jobs.push(std::move(function));
|
||||
std::unique_lock state_lock(m_state_change_lock);
|
||||
m_pending_jobs.push(std::move(function));
|
||||
}
|
||||
|
||||
} // namespace CPU
|
||||
|
Reference in New Issue
Block a user