Core: Threadsafety Synchronization Fixes (Frame Advance / FifoPlayer)

Fix Frame Advance and FifoPlayer pause/unpause/stop.

CPU::EnableStepping is not atomic but is called from multiple threads
which races and leaves the system in a random state; also instruction
stepping was unstable, m_StepEvent had an almost random value because
of the dual purpose it served which could cause races where CPU::Run
would SingleStep when it was supposed to be sleeping.

FifoPlayer never FinishStateMove()d which was causing it to deadlock.
Rather than partially reimplementing CPU::Run, just use CPUCoreBase
and then call CPU::Run(). More DRY and less likely to have weird bugs
specific to the player (i.e the previous freezing on pause/stop).

Refactor PowerPC::state into CPU since it manages the state of the
CPU Thread which is controlled by CPU, not PowerPC. This simplifies
the architecture somewhat and eliminates races that can be caused by
calling PowerPC state functions directly instead of using CPU's
(because they bypassed the EnableStepping lock).
This commit is contained in:
EmptyChaos 2016-05-12 09:17:17 +00:00
parent 0283ce2a7c
commit c1922783f8
27 changed files with 590 additions and 319 deletions

View File

@ -7,6 +7,7 @@
#include <cstdlib>
#include <jni.h>
#include <memory>
#include <thread>
#include <android/log.h>
#include <android/native_window_jni.h>
#include <EGL/egl.h>
@ -29,7 +30,6 @@
#include "Core/HW/Wiimote.h"
#include "Core/HW/WiimoteReal/WiimoteReal.h"
#include "Core/PowerPC/JitInterface.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/PowerPC/Profiler.h"
#include "DiscIO/Volume.h"
@ -66,8 +66,13 @@ void Host_NotifyMapLoaded() {}
void Host_RefreshDSPDebuggerWindow() {}
Common::Event updateMainFrameEvent;
static bool s_have_wm_user_stop = false;
void Host_Message(int Id)
{
if (Id == WM_USER_STOP)
{
s_have_wm_user_stop = true;
}
}
void* Host_GetRenderHandle()
@ -657,11 +662,22 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run(JNIEnv *
WiimoteReal::InitAdapterClass();
// No use running the loop when booting fails
s_have_wm_user_stop = false;
if ( BootManager::BootCore( g_filename.c_str() ) )
{
PowerPC::Start();
while (PowerPC::GetState() != PowerPC::CPU_POWERDOWN)
static constexpr int TIMEOUT = 10000;
static constexpr int WAIT_STEP = 25;
int time_waited = 0;
// A Core::CORE_ERROR state would be helpful here.
while (!Core::IsRunning() && time_waited < TIMEOUT && !s_have_wm_user_stop)
{
std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_STEP));
time_waited += WAIT_STEP;
}
while (Core::IsRunning())
{
updateMainFrameEvent.Wait();
}
}
Core::Shutdown();

View File

@ -12,9 +12,6 @@
#include "Common/Logging/Log.h"
#include "Core/ConfigManager.h"
// UGLINESS
#include "Core/PowerPC/PowerPC.h"
#if _M_SSE >= 0x301 && !(defined __GNUC__ && !defined __SSSE3__)
#include <tmmintrin.h>
#endif
@ -121,12 +118,6 @@ unsigned int CMixer::Mix(short* samples, unsigned int num_samples, bool consider
memset(samples, 0, num_samples * 2 * sizeof(short));
if (PowerPC::GetState() != PowerPC::CPU_RUNNING)
{
// Silence
return num_samples;
}
m_dma_mixer.Mix(samples, num_samples, consider_framelimit);
m_streaming_mixer.Mix(samples, num_samples, consider_framelimit);
m_wiimote_speaker_mixer.Mix(samples, num_samples, consider_framelimit);

View File

@ -42,8 +42,7 @@ public:
return;
std::unique_lock<std::mutex> lk(m_mutex);
m_condvar.wait(lk, [&]{ return m_flag.IsSet(); });
m_flag.Clear();
m_condvar.wait(lk, [&]{ return m_flag.TestAndClear(); });
}
template<class Rep, class Period>
@ -54,8 +53,7 @@ public:
std::unique_lock<std::mutex> lk(m_mutex);
bool signaled = m_condvar.wait_for(lk, rel_time,
[&]{ return m_flag.IsSet(); });
m_flag.Clear();
[&]{ return m_flag.TestAndClear(); });
return signaled;
}

View File

@ -266,10 +266,7 @@ void Stop() // - Hammertime!
// Stop the CPU
INFO_LOG(CONSOLE, "%s", StopMessage(true, "Stop CPU").c_str());
PowerPC::Stop();
// Kick it if it's waiting (code stepping wait loop)
CPU::StepOpcode();
CPU::Stop();
if (_CoreParameter.bCPUThread)
{
@ -376,6 +373,7 @@ static void CpuThread()
static void FifoPlayerThread()
{
DeclareAsCPUThread();
const SConfig& _CoreParameter = SConfig::GetInstance();
if (_CoreParameter.bCPUThread)
@ -388,18 +386,34 @@ static void FifoPlayerThread()
Common::SetCurrentThreadName("FIFO-GPU thread");
}
s_is_started = true;
DeclareAsCPUThread();
// Enter CPU run loop. When we leave it - we are done.
if (FifoPlayer::GetInstance().Open(_CoreParameter.m_strFilename))
{
FifoPlayer::GetInstance().Play();
if (auto cpu_core = FifoPlayer::GetInstance().GetCPUCore())
{
PowerPC::InjectExternalCPUCore(cpu_core.get());
s_is_started = true;
CPU::Run();
s_is_started = false;
PowerPC::InjectExternalCPUCore(nullptr);
}
FifoPlayer::GetInstance().Close();
}
UndeclareAsCPUThread();
s_is_started = false;
// If we did not enter the CPU Run Loop above then run a fake one instead.
// We need to be IsRunningAndStarted() for DolphinWX to stop us.
if (CPU::GetState() != CPU::CPU_POWERDOWN)
{
s_is_started = true;
Host_Message(WM_USER_STOP);
while (CPU::GetState() != CPU::CPU_POWERDOWN)
{
if (!_CoreParameter.bCPUThread)
g_video_backend->PeekMessages();
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
s_is_started = false;
}
if (!_CoreParameter.bCPUThread)
g_video_backend->Video_Cleanup();
@ -487,6 +501,9 @@ void EmuThread()
s_hardware_initialized = true;
// Boot to pause or not
// NOTE: This violates the Host Thread requirement for SetState but we should
// not race the Host because the UI should have the buttons disabled until
// Host_UpdateMainFrame enables them.
Core::SetState(core_parameter.bBootToPause ? Core::CORE_PAUSE : Core::CORE_RUN);
// Load GCM/DOL/ELF whatever ... we boot with the interpreter core
@ -552,7 +569,7 @@ void EmuThread()
// Spawn the CPU+GPU thread
s_cpu_thread = std::thread(cpuThreadFunc);
while (PowerPC::GetState() != PowerPC::CPU_POWERDOWN)
while (CPU::GetState() != CPU::CPU_POWERDOWN)
{
g_video_backend->PeekMessages();
Common::SleepCurrentThread(20);
@ -621,11 +638,13 @@ void EmuThread()
// Set or get the running state
void SetState(EState _State)
void SetState(EState state)
{
switch (_State)
switch (state)
{
case CORE_PAUSE:
// NOTE: GetState() will return CORE_PAUSE immediately, even before anything has
// stopped (including the CPU).
CPU::EnableStepping(true); // Break
Wiimote::Pause();
#if defined(__LIBUSB__) || defined(_WIN32)
@ -719,30 +738,50 @@ void RequestRefreshInfo()
s_request_refresh_info = true;
}
bool PauseAndLock(bool doLock, bool unpauseOnUnlock)
bool PauseAndLock(bool do_lock, bool unpause_on_unlock)
{
// WARNING: PauseAndLock is not fully threadsafe so is only valid on the Host Thread
if (!IsRunning())
return true;
// let's support recursive locking to simplify things on the caller's side,
// and let's do it at this outer level in case the individual systems don't support it.
if (doLock ? s_pause_and_lock_depth++ : --s_pause_and_lock_depth)
if (do_lock ? s_pause_and_lock_depth++ : --s_pause_and_lock_depth)
return true;
// first pause or unpause the CPU
bool wasUnpaused = CPU::PauseAndLock(doLock, unpauseOnUnlock);
ExpansionInterface::PauseAndLock(doLock, unpauseOnUnlock);
bool was_unpaused = true;
if (do_lock)
{
// first pause the CPU
// This acquires a wrapper mutex and converts the current thread into
// a temporary replacement CPU Thread.
was_unpaused = CPU::PauseAndLock(true);
}
ExpansionInterface::PauseAndLock(do_lock, false);
// audio has to come after CPU, because CPU thread can wait for audio thread (m_throttle).
DSP::GetDSPEmulator()->PauseAndLock(doLock, unpauseOnUnlock);
DSP::GetDSPEmulator()->PauseAndLock(do_lock, false);
// video has to come after CPU, because CPU thread can wait for video thread (s_efbAccessRequested).
Fifo::PauseAndLock(doLock, unpauseOnUnlock);
Fifo::PauseAndLock(do_lock, false);
#if defined(__LIBUSB__) || defined(_WIN32)
GCAdapter::ResetRumble();
#endif
return wasUnpaused;
// CPU is unlocked last because CPU::PauseAndLock contains the synchronization
// mechanism that prevents CPU::Break from racing.
if (!do_lock)
{
// The CPU is responsible for managing the Audio and FIFO state so we use its
// mechanism to unpause them. If we unpaused the systems above when releasing
// the locks then they could call CPU::Break which would require detecting it
// and re-pausing with CPU::EnableStepping.
was_unpaused = CPU::PauseAndLock(false, unpause_on_unlock, true);
}
return was_unpaused;
}
// Display FPS info
@ -803,7 +842,7 @@ void UpdateTitle()
float Speed = (float)(s_drawn_video.load() * (100 * 1000.0) / (VideoInterface::GetTargetRefreshRate() * ElapseTime));
// Settings are shown the same for both extended and summary info
std::string SSettings = StringFromFormat("%s %s | %s | %s", cpu_core_base->GetName(), _CoreParameter.bCPUThread ? "DC" : "SC",
std::string SSettings = StringFromFormat("%s %s | %s | %s", PowerPC::GetCPUName(), _CoreParameter.bCPUThread ? "DC" : "SC",
g_video_backend->GetDisplayName().c_str(), _CoreParameter.bDSPHLE ? "HLE" : "LLE");
std::string SFPS;

View File

@ -52,7 +52,8 @@ bool IsRunningInCurrentThread(); // this tells us whether we are running in the
bool IsCPUThread(); // this tells us whether we are the CPU thread.
bool IsGPUThread();
void SetState(EState _State);
// [NOT THREADSAFE] For use by Host only
void SetState(EState state);
EState GetState();
void SaveScreenShot();
@ -80,13 +81,14 @@ void UpdateTitle();
// or, if doLock is false, releases a lock on that state and optionally unpauses.
// calls must be balanced (once with doLock true, then once with doLock false) but may be recursive.
// the return value of the first call should be passed in as the second argument of the second call.
bool PauseAndLock(bool doLock, bool unpauseOnUnlock=true);
// [NOT THREADSAFE] Host only
bool PauseAndLock(bool doLock, bool unpauseOnUnlock = true);
// for calling back into UI code without introducing a dependency on it in core
typedef void(*StoppedCallbackFunc)(void);
void SetOnStoppedCallback(StoppedCallbackFunc callback);
// Run on the GUI thread when the factors change.
// Run on the Host thread when the factors change. [NOT THREADSAFE]
void UpdateWantDeterminism(bool initial = false);
} // namespace

View File

@ -10,7 +10,6 @@
#include "Core/Host.h"
#include "Core/Debugger/Debugger_SymbolMap.h"
#include "Core/Debugger/PPCDebugInterface.h"
#include "Core/HW/CPU.h"
#include "Core/HW/DSP.h"
#include "Core/HW/Memmap.h"
#include "Core/PowerPC/PowerPC.h"
@ -20,7 +19,7 @@
std::string PPCDebugInterface::Disassemble(unsigned int address)
{
// PowerPC::HostRead_U32 seemed to crash on shutdown
if (PowerPC::GetState() == PowerPC::CPU_POWERDOWN)
if (!IsAlive())
return "";
if (Core::GetState() == Core::CORE_PAUSE)
@ -51,7 +50,7 @@ std::string PPCDebugInterface::Disassemble(unsigned int address)
void PPCDebugInterface::GetRawMemoryString(int memory, unsigned int address, char *dest, int max_size)
{
if (Core::GetState() != Core::CORE_UNINITIALIZED)
if (IsAlive())
{
if (memory || PowerPC::HostIsRAMAddress(address))
{
@ -96,7 +95,7 @@ unsigned int PPCDebugInterface::ReadInstruction(unsigned int address)
bool PPCDebugInterface::IsAlive()
{
return Core::GetState() != Core::CORE_UNINITIALIZED;
return Core::IsRunning();
}
bool PPCDebugInterface::IsBreakpoint(unsigned int address)

View File

@ -7,12 +7,14 @@
#include "Common/Assert.h"
#include "Common/CommonTypes.h"
#include "Common/MsgHandler.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/CoreTiming.h"
#include "Core/Host.h"
#include "Core/FifoPlayer/FifoDataFile.h"
#include "Core/FifoPlayer/FifoPlayer.h"
#include "Core/HW/CPU.h"
#include "Core/HW/GPFifo.h"
#include "Core/HW/Memmap.h"
#include "Core/HW/ProcessorInterface.h"
@ -58,55 +60,103 @@ void FifoPlayer::Close()
m_FrameRangeEnd = 0;
}
bool FifoPlayer::Play()
class FifoPlayer::CPUCore final : public CPUCoreBase
{
if (!m_File)
return false;
if (m_File->GetFrameCount() == 0)
return false;
IsPlayingBackFifologWithBrokenEFBCopies = m_File->HasBrokenEFBCopies();
m_CurrentFrame = m_FrameRangeStart;
LoadMemory();
// This loop replaces the CPU loop that occurs when a game is run
while (PowerPC::GetState() != PowerPC::CPU_POWERDOWN)
public:
explicit CPUCore(FifoPlayer* parent)
: m_parent(parent)
{
if (PowerPC::GetState() == PowerPC::CPU_RUNNING)
}
CPUCore(const CPUCore&) = delete;
~CPUCore()
{
}
CPUCore& operator=(const CPUCore&) = delete;
void Init() override
{
IsPlayingBackFifologWithBrokenEFBCopies = m_parent->m_File->HasBrokenEFBCopies();
m_parent->m_CurrentFrame = m_parent->m_FrameRangeStart;
m_parent->LoadMemory();
}
void Shutdown() override
{
IsPlayingBackFifologWithBrokenEFBCopies = false;
}
void ClearCache() override
{
// Nothing to clear.
}
void SingleStep() override
{
// NOTE: AdvanceFrame() will get stuck forever in Dual Core because the FIFO
// is disabled by CPU::EnableStepping(true) so the frame never gets displayed.
PanicAlertT("Cannot SingleStep the FIFO. Use Frame Advance instead.");
}
const char* GetName() override
{
return "FifoPlayer";
}
void Run() override
{
while (CPU::GetState() == CPU::CPU_RUNNING)
{
if (m_CurrentFrame >= m_FrameRangeEnd)
switch (m_parent->AdvanceFrame())
{
if (m_Loop)
{
m_CurrentFrame = m_FrameRangeStart;
}
else
{
PowerPC::Stop();
Host_Message(WM_USER_STOP);
}
}
else
{
if (m_FrameWrittenCb)
m_FrameWrittenCb();
case CPU::CPU_POWERDOWN:
CPU::Break();
Host_Message(WM_USER_STOP);
break;
if (m_EarlyMemoryUpdates && m_CurrentFrame == m_FrameRangeStart)
WriteAllMemoryUpdates();
WriteFrame(m_File->GetFrame(m_CurrentFrame), m_FrameInfo[m_CurrentFrame]);
++m_CurrentFrame;
case CPU::CPU_STEPPING:
CPU::Break();
Host_UpdateMainFrame();
break;
}
}
}
IsPlayingBackFifologWithBrokenEFBCopies = false;
private:
FifoPlayer* m_parent;
};
return true;
int FifoPlayer::AdvanceFrame()
{
if (m_CurrentFrame >= m_FrameRangeEnd)
{
if (!m_Loop)
return CPU::CPU_POWERDOWN;
// If there are zero frames in the range then sleep instead of busy spinning
if (m_FrameRangeStart >= m_FrameRangeEnd)
return CPU::CPU_STEPPING;
m_CurrentFrame = m_FrameRangeStart;
}
if (m_FrameWrittenCb)
m_FrameWrittenCb();
if (m_EarlyMemoryUpdates && m_CurrentFrame == m_FrameRangeStart)
WriteAllMemoryUpdates();
WriteFrame(m_File->GetFrame(m_CurrentFrame), m_FrameInfo[m_CurrentFrame]);
++m_CurrentFrame;
return CPU::CPU_RUNNING;
}
std::unique_ptr<CPUCoreBase> FifoPlayer::GetCPUCore()
{
if (!m_File || m_File->GetFrameCount() == 0)
return nullptr;
return std::make_unique<CPUCore>(this);
}
u32 FifoPlayer::GetFrameObjectCount()

View File

@ -4,10 +4,12 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
#include "Core/FifoPlayer/FifoPlaybackAnalyzer.h"
#include "Core/PowerPC/CPUCoreBase.h"
class FifoDataFile;
struct MemoryUpdate;
@ -50,8 +52,12 @@ public:
bool Open(const std::string& filename);
void Close();
// Play is controlled by the state of PowerPC
bool Play();
// Returns a CPUCoreBase instance that can be injected into PowerPC as a
// pseudo-CPU. The instance is only valid while the FifoPlayer is Open().
// Returns nullptr if the FifoPlayer is not initialized correctly.
// Play/Pause/Stop of the FifoLog can be controlled normally via the
// PowerPC state.
std::unique_ptr<CPUCoreBase> GetCPUCore();
FifoDataFile *GetFile() { return m_File; }
@ -85,8 +91,12 @@ public:
static FifoPlayer &GetInstance();
private:
class CPUCore;
FifoPlayer();
int AdvanceFrame();
void WriteFrame(const FifoFrameInfo& frame, const AnalyzedFrameInfo &info);
void WriteFramePart(u32 dataStart, u32 dataEnd, u32 &nextMemUpdate, const FifoFrameInfo& frame, const AnalyzedFrameInfo& info);

View File

@ -9,6 +9,7 @@
#include "Core/ConfigManager.h"
#include "Core/Host.h"
#include "Core/HLE/HLE_Misc.h"
#include "Core/HW/CPU.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/PowerPC/PPCCache.h"
@ -35,7 +36,7 @@ void HLEPanicAlert()
void HBReload()
{
// There isn't much we can do. Just stop cleanly.
PowerPC::Pause();
CPU::Break();
Host_Message(WM_USER_STOP);
}

View File

@ -2,11 +2,13 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <condition_variable>
#include <mutex>
#include "AudioCommon/AudioCommon.h"
#include "Common/CommonTypes.h"
#include "Common/Event.h"
#include "Common/Logging/Log.h"
#include "Core/Core.h"
#include "Core/Host.h"
#include "Core/HW/CPU.h"
@ -14,82 +16,177 @@
#include "Core/PowerPC/PowerPC.h"
#include "VideoCommon/Fifo.h"
namespace
{
static Common::Event m_StepEvent;
static Common::Event *m_SyncEvent = nullptr;
static std::mutex m_csCpuOccupied;
}
namespace CPU
{
// CPU Thread execution state.
// Requires s_state_change_lock to modify the value.
// Read access is unsynchronized.
static State s_state = CPU_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;
void Init(int cpu_core)
{
PowerPC::Init(cpu_core);
m_SyncEvent = nullptr;
s_state = CPU_STEPPING;
}
void Shutdown()
{
Stop();
PowerPC::Shutdown();
m_SyncEvent = nullptr;
}
// Requires holding s_state_change_lock
static void FlushStepSyncEventLocked()
{
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;
}
void Run()
{
std::lock_guard<std::mutex> lk(m_csCpuOccupied);
Host_UpdateDisasmDialog();
while (true)
std::unique_lock<std::mutex> state_lock(s_state_change_lock);
while (s_state != CPU_POWERDOWN)
{
switch (PowerPC::GetState())
s_state_cpu_cvar.wait(state_lock, [] { return !s_state_paused_and_locked; });
switch (s_state)
{
case PowerPC::CPU_RUNNING:
//1: enter a fast runloop
case CPU_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::MODE_INTERPRETER)
{
#ifndef ENABLE_MEM_CHECK
if (PowerPC::breakpoints.IsAddressBreakPoint(PC))
#endif
{
PowerPC::CoreMode old_mode = PowerPC::GetMode();
PowerPC::SetMode(PowerPC::MODE_INTERPRETER);
PowerPC::SingleStep();
PowerPC::SetMode(old_mode);
}
}
// Enter a fast runloop
PowerPC::RunLoop();
state_lock.lock();
s_state_cpu_thread_active = false;
s_state_cpu_idle_cvar.notify_all();
break;
case PowerPC::CPU_STEPPING:
m_csCpuOccupied.unlock();
//1: wait for step command..
m_StepEvent.Wait();
m_csCpuOccupied.lock();
if (PowerPC::GetState() == PowerPC::CPU_POWERDOWN)
return;
if (PowerPC::GetState() != PowerPC::CPU_STEPPING)
case CPU_STEPPING:
// Wait for step command.
s_state_cpu_cvar.wait(state_lock, []
{
return s_state_cpu_step_instruction ||
s_state != CPU_STEPPING;
});
if (s_state != CPU_STEPPING)
{
// Signal event if the mode changes.
FlushStepSyncEventLocked();
continue;
}
if (s_state_paused_and_locked)
continue;
//3: do a step
// Do step
s_state_cpu_thread_active = true;
state_lock.unlock();
PowerPC::SingleStep();
//4: update disasm dialog
if (m_SyncEvent)
{
m_SyncEvent->Set();
m_SyncEvent = nullptr;
}
state_lock.lock();
s_state_cpu_thread_active = false;
s_state_cpu_idle_cvar.notify_all();
// Update disasm dialog
FlushStepSyncEventLocked();
Host_UpdateDisasmDialog();
break;
case PowerPC::CPU_POWERDOWN:
//1: Exit loop!!
return;
case CPU_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);
AudioCommon::ClearAudioBuffer(!running);
}
void Stop()
{
PowerPC::Stop();
m_StepEvent.Set();
// Change state and wait for it to be acknowledged.
// We don't need the stepping lock because CPU_POWERDOWN is a priority state which
// will stick permanently.
std::unique_lock<std::mutex> state_lock(s_state_change_lock);
s_state = CPU_POWERDOWN;
s_state_cpu_cvar.notify_one();
// FIXME: MsgHandler can cause this to deadlock the GUI Thread. Remove the timeout.
bool success = s_state_cpu_idle_cvar.wait_for(state_lock, std::chrono::seconds(5), []
{
return !s_state_cpu_thread_active;
});
if (!success)
ERROR_LOG(POWERPC, "CPU Thread failed to acknowledge CPU_POWERDOWN. It may be deadlocked.");
RunAdjacentSystems(false);
FlushStepSyncEventLocked();
}
bool IsStepping()
{
return PowerPC::GetState() == PowerPC::CPU_STEPPING;
return s_state == CPU_STEPPING;
}
State GetState()
{
return s_state;
}
const volatile State* GetStatePtr()
{
return &s_state;
}
void Reset()
@ -98,87 +195,142 @@ void Reset()
void StepOpcode(Common::Event* event)
{
m_StepEvent.Set();
if (PowerPC::GetState() == PowerPC::CPU_STEPPING)
std::lock_guard<std::mutex> state_lock(s_state_change_lock);
// If we're not stepping then this is pointless
if (!IsStepping())
{
m_SyncEvent = event;
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();
}
void EnableStepping(const bool stepping)
// Requires s_state_change_lock
static bool SetStateLocked(State s)
{
if (s_state == CPU_POWERDOWN)
return false;
s_state = s;
return true;
}
void EnableStepping(bool stepping)
{
std::lock_guard<std::mutex> stepping_lock(s_stepping_lock);
std::unique_lock<std::mutex> state_lock(s_state_change_lock);
if (stepping)
{
PowerPC::Pause();
m_StepEvent.Reset();
Fifo::EmulatorState(false);
AudioCommon::ClearAudioBuffer(true);
}
else
{
// 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.
bool could_be_bp;
#ifdef ENABLE_MEM_CHECK
could_be_bp = true;
#else
could_be_bp = PowerPC::breakpoints.IsAddressBreakPoint(PC);
#endif
if (could_be_bp && PowerPC::GetMode() != PowerPC::MODE_INTERPRETER)
SetStateLocked(CPU_STEPPING);
// Wait for the CPU Thread to leave the run loop
// FIXME: MsgHandler can cause this to deadlock the GUI Thread. Remove the timeout.
bool success = s_state_cpu_idle_cvar.wait_for(state_lock, std::chrono::seconds(5), []
{
PowerPC::CoreMode oldMode = PowerPC::GetMode();
PowerPC::SetMode(PowerPC::MODE_INTERPRETER);
PowerPC::SingleStep();
PowerPC::SetMode(oldMode);
}
PowerPC::Start();
m_StepEvent.Set();
Fifo::EmulatorState(true);
AudioCommon::ClearAudioBuffer(false);
return !s_state_cpu_thread_active;
});
if (!success)
ERROR_LOG(POWERPC, "Abandoned waiting for CPU Thread! The Core may be deadlocked.");
RunAdjacentSystems(false);
}
else if (SetStateLocked(CPU_RUNNING))
{
s_state_cpu_cvar.notify_one();
RunAdjacentSystems(true);
}
}
void Break()
{
EnableStepping(true);
std::lock_guard<std::mutex> 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(CPU_STEPPING);
RunAdjacentSystems(false);
}
bool PauseAndLock(bool do_lock, bool unpause_on_unlock)
bool PauseAndLock(bool do_lock, bool unpause_on_unlock, bool control_adjacent)
{
static bool s_have_fake_cpu_thread;
bool wasUnpaused = !IsStepping();
// NOTE: This is protected by s_stepping_lock.
static bool s_have_fake_cpu_thread = false;
bool was_unpaused = false;
if (do_lock)
{
// we can't use EnableStepping, that would causes deadlocks with both audio and video
PowerPC::Pause();
s_stepping_lock.lock();
std::unique_lock<std::mutex> state_lock(s_state_change_lock);
s_state_paused_and_locked = true;
was_unpaused = s_state == CPU_RUNNING;
SetStateLocked(CPU_STEPPING);
// FIXME: MsgHandler can cause this to deadlock the GUI Thread. Remove the timeout.
bool success = s_state_cpu_idle_cvar.wait_for(state_lock, std::chrono::seconds(10), []
{
return !s_state_cpu_thread_active;
});
if (!success)
NOTICE_LOG(POWERPC, "Abandoned CPU Thread synchronization in CPU::PauseAndLock! We'll probably crash now.");
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())
{
m_csCpuOccupied.lock();
s_have_fake_cpu_thread = true;
Core::DeclareAsCPUThread();
}
else
{
s_have_fake_cpu_thread = false;
}
}
else
{
if (unpause_on_unlock)
{
PowerPC::Start();
m_StepEvent.Set();
}
// Only need the stepping lock for this
if (s_have_fake_cpu_thread)
{
Core::UndeclareAsCPUThread();
m_csCpuOccupied.unlock();
s_have_fake_cpu_thread = false;
Core::UndeclareAsCPUThread();
}
{
std::lock_guard<std::mutex> state_lock(s_state_change_lock);
if (s_state_system_request_stepping)
{
s_state_system_request_stepping = false;
}
else if (unpause_on_unlock && SetStateLocked(CPU_RUNNING))
{
was_unpaused = true;
}
s_state_paused_and_locked = false;
s_state_cpu_cvar.notify_one();
if (control_adjacent)
RunAdjacentSystems(s_state == CPU_RUNNING);
}
s_stepping_lock.unlock();
}
return wasUnpaused;
return was_unpaused;
}
}

View File

@ -11,6 +11,13 @@ namespace Common {
namespace CPU
{
enum State
{
CPU_RUNNING = 0,
CPU_STEPPING = 2,
CPU_POWERDOWN = 3
};
// Init
void Init(int cpu_core);
@ -18,32 +25,49 @@ void Init(int cpu_core);
void Shutdown();
// Starts the CPU
// To be called by the CPU Thread.
void Run();
// Causes shutdown
// Once started, CPU_POWERDOWN cannot be stopped.
// Synchronizes with the CPU Thread (waits for CPU::Run to exit).
void Stop();
// Reset
// Reset [NOT IMPLEMENTED]
void Reset();
// StepOpcode (Steps one Opcode)
void StepOpcode(Common::Event* event = nullptr);
// Enable or Disable Stepping
// Enable or Disable Stepping. [Will deadlock if called from a system thread]
void EnableStepping(bool stepping);
// Break, same as EnableStepping(true).
// Breakpoint activation for system threads. Similar to EnableStepping(true).
// NOTE: Unlike EnableStepping, this does NOT synchronize with the CPU Thread
// which enables it to avoid deadlocks but also makes it less safe so it
// should not be used by the Host.
void Break();
// Is stepping ?
// Shorthand for GetState() == CPU_STEPPING.
// WARNING: CPU_POWERDOWN will return false, not just CPU_RUNNING.
bool IsStepping();
// Waits until is stepping and is ready for a command (paused and fully idle), and acquires a lock on that state.
// or, if doLock is false, releases a lock on that state and optionally re-disables stepping.
// calls must be balanced and non-recursive (once with doLock true, then once with doLock false).
// intended (but not required) to be called from another thread,
// e.g. when the GUI thread wants to make sure everything is paused so that it can create a savestate.
// the return value is whether the CPU was unpaused before the call.
bool PauseAndLock(bool do_lock, bool unpause_on_unlock = true);
// Get current CPU Thread State
State GetState();
// Direct State Access (Raw pointer for embedding into JIT Blocks)
// Strictly read-only. A lock is required to change the value.
const volatile State* GetStatePtr();
// Locks the CPU Thread (waiting for it to become idle).
// While this lock is held, the CPU Thread will not perform any action so it is safe to access
// PowerPC::ppcState, CoreTiming, etc. without racing the CPU Thread.
// Cannot be used recursively. Must be paired as PauseAndLock(true)/PauseAndLock(false).
// Return value for do_lock == true is whether the state was CPU_RUNNING or not.
// Return value for do_lock == false is whether the state was changed *to* CPU_RUNNING or not.
// Cannot be used by System threads as it will deadlock. It is threadsafe otherwise.
// "control_adjacent" causes PauseAndLock to behave like EnableStepping by modifying the
// state of the Audio and FIFO subsystems as well.
bool PauseAndLock(bool do_lock, bool unpause_on_unlock = true, bool control_adjacent = false);
}

View File

@ -21,6 +21,7 @@
#include "Core/NetPlayProto.h"
#include "Core/State.h"
#include "Core/DSP/DSPCore.h"
#include "Core/HW/CPU.h"
#include "Core/HW/DVDInterface.h"
#include "Core/HW/EXI_Device.h"
#include "Core/HW/ProcessorInterface.h"
@ -155,8 +156,8 @@ void FrameUpdate()
}
if (s_bFrameStep)
{
Core::SetState(Core::CORE_PAUSE);
s_bFrameStep = false;
CPU::Break();
}
if (s_framesToSkip)
@ -246,9 +247,9 @@ void DoFrameStep()
if (Core::GetState() == Core::CORE_PAUSE)
{
// if already paused, frame advance for 1 frame
Core::SetState(Core::CORE_RUN);
Core::RequestRefreshInfo();
s_bFrameStep = true;
Core::RequestRefreshInfo();
Core::SetState(Core::CORE_RUN);
}
else if (!s_bFrameStep)
{

View File

@ -7,6 +7,7 @@
#include "Core/ConfigManager.h"
#include "Core/CoreTiming.h"
#include "Core/HLE/HLE.h"
#include "Core/HW/CPU.h"
#include "Core/PowerPC/CachedInterpreter.h"
#include "Core/PowerPC/Gekko.h"
#include "Core/PowerPC/PowerPC.h"
@ -32,13 +33,10 @@ void CachedInterpreter::Shutdown()
void CachedInterpreter::Run()
{
while (!PowerPC::GetState())
while (!CPU::GetState())
{
SingleStep();
}
// Let the waiting thread know we are done leaving
PowerPC::FinishStateMove();
}
void CachedInterpreter::SingleStep()

View File

@ -200,7 +200,7 @@ int ShowSteps = 300;
// FastRun - inspired by GCemu (to imitate the JIT so that they can be compared).
void Interpreter::Run()
{
while (!PowerPC::GetState())
while (!CPU::GetState())
{
//we have to check exceptions at branches apparently (or maybe just rfi?)
if (SConfig::GetInstance().bEnableDebugging)
@ -279,9 +279,6 @@ void Interpreter::Run()
CoreTiming::Advance();
}
// Let the waiting thread know we are done leaving
PowerPC::FinishStateMove();
}
void Interpreter::unknown_instruction(UGeckoInstruction _inst)

View File

@ -19,6 +19,7 @@
#include "Core/CoreTiming.h"
#include "Core/PatchEngine.h"
#include "Core/HLE/HLE.h"
#include "Core/HW/CPU.h"
#include "Core/HW/GPFifo.h"
#include "Core/HW/ProcessorInterface.h"
#include "Core/PowerPC/JitInterface.h"
@ -561,7 +562,7 @@ void Jit64::Jit(u32 em_address)
// Comment out the following to disable breakpoints (speed-up)
if (!Profiler::g_ProfileBlocks)
{
if (GetState() == CPU_STEPPING)
if (CPU::GetState() == CPU::CPU_STEPPING)
{
blockSize = 1;
@ -800,7 +801,9 @@ const u8* Jit64::DoJit(u32 em_address, PPCAnalyst::CodeBuffer *code_buf, JitBloc
js.firstFPInstructionFound = true;
}
if (SConfig::GetInstance().bEnableDebugging && breakpoints.IsAddressBreakPoint(ops[i].address) && GetState() != CPU_STEPPING)
if (SConfig::GetInstance().bEnableDebugging &&
breakpoints.IsAddressBreakPoint(ops[i].address) &&
CPU::GetState() != CPU::CPU_STEPPING)
{
// Turn off block linking if there are breakpoints so that the Step Over command does not link this block.
jo.enableBlocklink = false;
@ -812,7 +815,7 @@ const u8* Jit64::DoJit(u32 em_address, PPCAnalyst::CodeBuffer *code_buf, JitBloc
ABI_PushRegistersAndAdjustStack({}, 0);
ABI_CallFunction(reinterpret_cast<void *>(&PowerPC::CheckBreakPoints));
ABI_PopRegistersAndAdjustStack({}, 0);
TEST(32, M(PowerPC::GetStatePtr()), Imm32(0xFFFFFFFF));
TEST(32, M(CPU::GetStatePtr()), Imm32(0xFFFFFFFF));
FixupBranch noBreakpoint = J_CC(CC_Z);
WriteExit(ops[i].address);

View File

@ -8,6 +8,7 @@
#include "Common/x64Emitter.h"
#include "Core/ConfigManager.h"
#include "Core/CoreTiming.h"
#include "Core/HW/CPU.h"
#include "Core/HW/Memmap.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/PowerPC/Jit64/Jit.h"
@ -75,12 +76,12 @@ void Jit64AsmRoutineManager::Generate()
if (SConfig::GetInstance().bEnableDebugging)
{
TEST(32, M(PowerPC::GetStatePtr()), Imm32(PowerPC::CPU_STEPPING));
TEST(32, M(CPU::GetStatePtr()), Imm32(CPU::CPU_STEPPING));
FixupBranch notStepping = J_CC(CC_Z);
ABI_PushRegistersAndAdjustStack({}, 0);
ABI_CallFunction(reinterpret_cast<void *>(&PowerPC::CheckBreakPoints));
ABI_PopRegistersAndAdjustStack({}, 0);
TEST(32, M(PowerPC::GetStatePtr()), Imm32(0xFFFFFFFF));
TEST(32, M(CPU::GetStatePtr()), Imm32(0xFFFFFFFF));
dbg_exit = J_CC(CC_NZ, true);
SetJumpTarget(notStepping);
}
@ -208,7 +209,7 @@ void Jit64AsmRoutineManager::Generate()
// Check the state pointer to see if we are exiting
// Gets checked on at the end of every slice
TEST(32, M(PowerPC::GetStatePtr()), Imm32(0xFFFFFFFF));
TEST(32, M(CPU::GetStatePtr()), Imm32(0xFFFFFFFF));
J_CC(CC_Z, outerLoop);
//Landing pad for drec space
@ -221,11 +222,6 @@ void Jit64AsmRoutineManager::Generate()
POP(RSP);
}
// Let the waiting thread know we are done leaving
ABI_PushRegistersAndAdjustStack({}, 0);
ABI_CallFunction(reinterpret_cast<void *>(&PowerPC::FinishStateMove));
ABI_PopRegistersAndAdjustStack({}, 0);
ABI_PopRegistersAndAdjustStack(ABI_ALL_CALLEE_SAVED, 8, 16);
RET();

View File

@ -12,6 +12,7 @@
#include "Common/x64Emitter.h"
#include "Core/ConfigManager.h"
#include "Core/CoreTiming.h"
#include "Core/HW/CPU.h"
#include "Core/HW/DSP.h"
#include "Core/HW/Memmap.h"
#include "Core/PowerPC/JitInterface.h"
@ -120,7 +121,7 @@ void Jit64::lXXx(UGeckoInstruction inst)
// TODO: We shouldn't use a debug read here. It should be possible to get
// the following instructions out of the JIT state.
if (SConfig::GetInstance().bSkipIdle &&
PowerPC::GetState() != PowerPC::CPU_STEPPING &&
CPU::GetState() != CPU::CPU_STEPPING &&
inst.OPCD == 32 &&
MergeAllowedNextInstructions(2) &&
(inst.hex & 0xFFFF0000) == 0x800D0000 &&

View File

@ -35,6 +35,7 @@ The register allocation is linear scan allocation.
#include "Common/x64ABI.h"
#include "Common/x64Emitter.h"
#include "Core/CoreTiming.h"
#include "Core/HW/CPU.h"
#include "Core/HW/ProcessorInterface.h"
#include "Core/PowerPC/Gekko.h"
#include "Core/PowerPC/PowerPC.h"
@ -2299,7 +2300,7 @@ static void DoWriteCode(IRBuilder* ibuild, JitIL* Jit, u32 exitAddress)
Jit->MOV(32, PPCSTATE(pc), Imm32(InstLoc));
Jit->ABI_CallFunction(reinterpret_cast<void *>(&PowerPC::CheckBreakPoints));
Jit->TEST(32, M(PowerPC::GetStatePtr()), Imm32(0xFFFFFFFF));
Jit->TEST(32, M(CPU::GetStatePtr()), Imm32(0xFFFFFFFF));
FixupBranch noBreakpoint = Jit->J_CC(CC_Z);
Jit->WriteExit(InstLoc);
Jit->SetJumpTarget(noBreakpoint);

View File

@ -17,6 +17,7 @@
#include "Common/Logging/Log.h"
#include "Core/PatchEngine.h"
#include "Core/HLE/HLE.h"
#include "Core/HW/CPU.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/PowerPC/Profiler.h"
#include "Core/PowerPC/Jit64IL/JitIL.h"
@ -478,7 +479,7 @@ void JitIL::Jit(u32 em_address)
// Comment out the following to disable breakpoints (speed-up)
if (!Profiler::g_ProfileBlocks)
{
if (GetState() == CPU_STEPPING)
if (CPU::GetState() == CPU::CPU_STEPPING)
{
blockSize = 1;
@ -624,7 +625,9 @@ const u8* JitIL::DoJit(u32 em_address, PPCAnalyst::CodeBuffer *code_buf, JitBloc
ibuild.EmitExtExceptionCheck(ibuild.EmitIntConst(ops[i].address));
}
if (SConfig::GetInstance().bEnableDebugging && breakpoints.IsAddressBreakPoint(ops[i].address) && GetState() != CPU_STEPPING)
if (SConfig::GetInstance().bEnableDebugging &&
breakpoints.IsAddressBreakPoint(ops[i].address) &&
CPU::GetState() != CPU::CPU_STEPPING)
{
// Turn off block linking if there are breakpoints so that the Step Over command does not link this block.
jo.enableBlocklink = false;

View File

@ -6,6 +6,7 @@
#include "Common/CommonTypes.h"
#include "Common/JitRegister.h"
#include "Core/CoreTiming.h"
#include "Core/HW/CPU.h"
#include "Core/HW/Memmap.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/PowerPC/JitArm64/Jit.h"
@ -85,7 +86,7 @@ void JitArm64AsmRoutineManager::Generate()
// Check the state pointer to see if we are exiting
// Gets checked on at the end of every slice
MOVI2R(X0, (u64)PowerPC::GetStatePtr());
MOVI2R(X0, (u64)CPU::GetStatePtr());
LDR(INDEX_UNSIGNED, W0, X0, 0);
CMP(W0, 0);
@ -96,10 +97,6 @@ void JitArm64AsmRoutineManager::Generate()
SetJumpTarget(Exit);
STR(INDEX_UNSIGNED, DISPATCHER_PC, PPC_REG, PPCSTATE_OFF(pc));
// Let the waiting thread know we are done leaving
MOVI2R(X0, (u64)&PowerPC::FinishStateMove);
BLR(X0);
ABI_PopRegisters(regs_to_save);
RET(X30);

View File

@ -12,6 +12,7 @@
#include "Common/StringUtil.h"
#include "Common/Logging/Log.h"
#include "Core/ConfigManager.h"
#include "Core/HW/CPU.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/PowerPC/PPCAnalyst.h"
#include "Core/PowerPC/JitCommon/JitBase.h"
@ -67,7 +68,7 @@ void LogGeneratedX86(int size, PPCAnalyst::CodeBuffer *code_buffer, const u8 *no
bool JitBase::MergeAllowedNextInstructions(int count)
{
if (PowerPC::GetState() == PowerPC::CPU_STEPPING || js.instructionsLeft < count)
if (CPU::GetState() == CPU::CPU_STEPPING || js.instructionsLeft < count)
return false;
// Be careful: a breakpoint kills flags in between instructions
for (int i = 1; i <= count; i++)

View File

@ -5,6 +5,7 @@
#include "Common/Assert.h"
#include "Common/CommonTypes.h"
#include "Core/ConfigManager.h"
#include "Core/HW/CPU.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/PowerPC/JitILCommon/JitILBase.h"
@ -57,7 +58,7 @@ void JitILBase::lXz(UGeckoInstruction inst)
// or higher in PPCAnalyst
// TODO: We shouldn't use debug reads here.
if (SConfig::GetInstance().bSkipIdle &&
PowerPC::GetState() != PowerPC::CPU_STEPPING &&
CPU::GetState() != CPU::CPU_STEPPING &&
inst.OPCD == 32 && // Lwx
(inst.hex & 0xFFFF0000) == 0x800D0000 &&
(PowerPC::HostRead_U32(js.compilerPC + 4) == 0x28000000 ||

View File

@ -5,13 +5,13 @@
#include "Common/Assert.h"
#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h"
#include "Common/Event.h"
#include "Common/FPURoundMode.h"
#include "Common/MathUtil.h"
#include "Common/Logging/Log.h"
#include "Core/ConfigManager.h"
#include "Core/Host.h"
#include "Core/HW/CPU.h"
#include "Core/HW/Memmap.h"
#include "Core/HW/SystemTimers.h"
#include "Core/PowerPC/CPUCoreBase.h"
@ -21,18 +21,16 @@
#include "Core/PowerPC/Interpreter/Interpreter.h"
CPUCoreBase *cpu_core_base;
namespace PowerPC
{
// STATE_TO_SAVE
PowerPCState ppcState;
static volatile CPUState state = CPU_POWERDOWN;
Interpreter * const interpreter = Interpreter::getInstance();
static CoreMode mode;
static Common::Event s_state_change;
static CPUCoreBase* s_cpu_core_base = nullptr;
static bool s_cpu_core_base_is_injected = false;
Interpreter* const s_interpreter = Interpreter::getInstance();
static CoreMode s_mode = MODE_INTERPRETER;
Watches watches;
BreakPoints breakpoints;
@ -114,6 +112,8 @@ static void ResetRegisters()
void Init(int cpu_core)
{
// NOTE: This function runs on EmuThread, not the CPU Thread.
// Changing the rounding mode has a limited effect.
FPURoundMode::SetPrecisionMode(FPURoundMode::PREC_53);
memset(ppcState.sr, 0, sizeof(ppcState.sr));
@ -139,33 +139,32 @@ void Init(int cpu_core)
// We initialize the interpreter because
// it is used on boot and code window independently.
interpreter->Init();
s_interpreter->Init();
switch (cpu_core)
{
case PowerPC::CORE_INTERPRETER:
cpu_core_base = interpreter;
s_cpu_core_base = s_interpreter;
break;
default:
cpu_core_base = JitInterface::InitJitCore(cpu_core);
if (!cpu_core_base) // Handle Situations where JIT core isn't available
s_cpu_core_base = JitInterface::InitJitCore(cpu_core);
if (!s_cpu_core_base) // Handle Situations where JIT core isn't available
{
WARN_LOG(POWERPC, "Jit core %d not available. Defaulting to interpreter.", cpu_core);
cpu_core_base = interpreter;
s_cpu_core_base = s_interpreter;
}
break;
}
if (cpu_core_base != interpreter)
if (s_cpu_core_base != s_interpreter)
{
mode = MODE_JIT;
s_mode = MODE_JIT;
}
else
{
mode = MODE_INTERPRETER;
s_mode = MODE_INTERPRETER;
}
state = CPU_STEPPING;
ppcState.iCache.Init();
@ -175,94 +174,87 @@ void Init(int cpu_core)
void Shutdown()
{
InjectExternalCPUCore(nullptr);
JitInterface::Shutdown();
interpreter->Shutdown();
cpu_core_base = nullptr;
state = CPU_POWERDOWN;
s_interpreter->Shutdown();
s_cpu_core_base = nullptr;
}
CoreMode GetMode()
{
return mode;
return !s_cpu_core_base_is_injected ? s_mode : MODE_INTERPRETER;
}
void SetMode(CoreMode new_mode)
static void ApplyMode()
{
if (new_mode == mode)
return; // We don't need to do anything.
mode = new_mode;
switch (mode)
switch (s_mode)
{
case MODE_INTERPRETER: // Switching from JIT to interpreter
cpu_core_base = interpreter;
s_cpu_core_base = s_interpreter;
break;
case MODE_JIT: // Switching from interpreter to JIT.
// Don't really need to do much. It'll work, the cache will refill itself.
cpu_core_base = JitInterface::GetCore();
if (!cpu_core_base) // Has a chance to not get a working JIT core if one isn't active on host
cpu_core_base = interpreter;
s_cpu_core_base = JitInterface::GetCore();
if (!s_cpu_core_base) // Has a chance to not get a working JIT core if one isn't active on host
s_cpu_core_base = s_interpreter;
break;
}
}
void SetMode(CoreMode new_mode)
{
if (new_mode == s_mode)
return; // We don't need to do anything.
s_mode = new_mode;
// If we're using an external CPU core implementation then don't do anything.
if (s_cpu_core_base_is_injected)
return;
ApplyMode();
}
const char* GetCPUName()
{
return s_cpu_core_base->GetName();
}
void InjectExternalCPUCore(CPUCoreBase* new_cpu)
{
// Previously injected.
if (s_cpu_core_base_is_injected)
s_cpu_core_base->Shutdown();
// nullptr means just remove
if (!new_cpu)
{
if (s_cpu_core_base_is_injected)
{
s_cpu_core_base_is_injected = false;
ApplyMode();
}
return;
}
new_cpu->Init();
s_cpu_core_base = new_cpu;
s_cpu_core_base_is_injected = true;
}
void SingleStep()
{
cpu_core_base->SingleStep();
s_cpu_core_base->SingleStep();
}
void RunLoop()
{
state = CPU_RUNNING;
cpu_core_base->Run();
Host_UpdateDisasmDialog();
}
CPUState GetState()
{
return state;
}
const volatile CPUState *GetStatePtr()
{
return &state;
}
void Start()
{
state = CPU_RUNNING;
s_cpu_core_base->Run();
Host_UpdateDisasmDialog();
}
void Pause()
{
volatile CPUState old_state = state;
state = CPU_STEPPING;
// Wait for the CPU core to leave
if (old_state == CPU_RUNNING)
s_state_change.WaitFor(std::chrono::seconds(1));
Host_UpdateDisasmDialog();
}
void Stop()
{
volatile CPUState old_state = state;
state = CPU_POWERDOWN;
// Wait for the CPU core to leave
if (old_state == CPU_RUNNING)
s_state_change.WaitFor(std::chrono::seconds(1));
Host_UpdateDisasmDialog();
}
void FinishStateMove()
{
s_state_change.Set();
}
void UpdatePerformanceMonitor(u32 cycles, u32 num_load_stores, u32 num_fp_inst)
{
switch (MMCR0.PMC1SELECT)
@ -521,7 +513,7 @@ void CheckBreakPoints()
{
if (PowerPC::breakpoints.IsAddressBreakPoint(PC))
{
PowerPC::Pause();
CPU::Break();
if (PowerPC::breakpoints.IsTempBreakPoint(PC))
PowerPC::breakpoints.Remove(PC);
}

View File

@ -11,14 +11,12 @@
#include "Common/CommonTypes.h"
#include "Core/Debugger/PPCDebugInterface.h"
#include "Core/PowerPC/CPUCoreBase.h"
#include "Core/PowerPC/Gekko.h"
#include "Core/PowerPC/PPCCache.h"
class CPUCoreBase;
class PointerWrap;
extern CPUCoreBase *cpu_core_base;
namespace PowerPC
{
@ -129,13 +127,6 @@ struct PowerPCState
static_assert(offsetof(PowerPC::PowerPCState, above_fits_in_first_0x100) <= 0x100, "top of PowerPCState too big");
#endif
enum CPUState
{
CPU_RUNNING = 0,
CPU_STEPPING = 2,
CPU_POWERDOWN = 3,
};
extern PowerPCState ppcState;
extern Watches watches;
@ -148,19 +139,26 @@ void Shutdown();
void DoState(PointerWrap &p);
CoreMode GetMode();
// [NOT THREADSAFE] CPU Thread or CPU::PauseAndLock or CORE_UNINITIALIZED
void SetMode(CoreMode _coreType);
const char* GetCPUName();
// Set the current CPU Core to the given implementation until removed.
// Remove the current injected CPU Core by passing nullptr.
// While an external CPUCoreBase is injected, GetMode() will return MODE_INTERPRETER.
// Init() will be called when added and Shutdown() when removed.
// [Threadsafety: Same as SetMode(), except it cannot be called from inside the CPU
// run loop on the CPU Thread - it doesn't make sense for a CPU to remove itself
// while it is CPU_RUNNING]
void InjectExternalCPUCore(CPUCoreBase* core);
// Stepping requires the CPU Execution lock (CPU::PauseAndLock or CPU Thread)
// It's not threadsafe otherwise.
void SingleStep();
void CheckExceptions();
void CheckExternalExceptions();
void CheckBreakPoints();
void RunLoop();
void Start();
void Pause();
void Stop();
void FinishStateMove();
CPUState GetState();
const volatile CPUState *GetStatePtr(); // this oddity is here instead of an extern declaration to easily be able to find all direct accesses throughout the code.
u32 CompactCR();
void ExpandCR(u32 cr);

View File

@ -317,6 +317,7 @@ void CCodeWindow::StepOut()
{
if (CPU::IsStepping())
{
CPU::PauseAndLock(true, false);
PowerPC::breakpoints.ClearAllTemporary();
// Keep stepping until the next blr or timeout after one second
@ -347,6 +348,7 @@ void CCodeWindow::StepOut()
PowerPC::SingleStep();
PowerPC::SetMode(oldMode);
CPU::PauseAndLock(false, false);
JumpToAddress(PC);
Update();

View File

@ -7,6 +7,7 @@
#include <wx/menu.h>
#include "Common/GekkoDisassembler.h"
#include "Core/Core.h"
#include "Core/HW/Memmap.h"
#include "Core/PowerPC/PowerPC.h"
#include "DolphinWX/Frame.h"
@ -87,7 +88,7 @@ static wxString GetValueByRowCol(int row, int col)
}
else if (row <= (int)PowerPC::watches.GetWatches().size())
{
if (PowerPC::GetState() != PowerPC::CPU_POWERDOWN)
if (Core::IsRunning())
{
switch (col)
{
@ -198,7 +199,7 @@ wxGridCellAttr* CWatchTable::GetAttr(int row, int col, wxGridCellAttr::wxAttrKin
attr->SetTextColour(red ? *wxRED : *wxBLACK);
if (row > (int)(PowerPC::watches.GetWatches().size() + 1) || (PowerPC::GetState() == PowerPC::CPU_POWERDOWN))
if (row > (int)(PowerPC::watches.GetWatches().size() + 1) || !Core::IsRunning())
{
attr->SetReadOnly(true);
attr->SetBackgroundColour(*wxLIGHT_GREY);
@ -224,7 +225,7 @@ CWatchView::CWatchView(wxWindow* parent, wxWindowID id)
void CWatchView::Update()
{
if (PowerPC::GetState() != PowerPC::CPU_POWERDOWN)
if (Core::IsRunning())
{
m_watch_table->UpdateWatch();
ForceRefresh();

View File

@ -22,7 +22,6 @@
#include "Core/HW/Wiimote.h"
#include "Core/IPC_HLE/WII_IPC_HLE_Device_usb.h"
#include "Core/IPC_HLE/WII_IPC_HLE_WiiMote.h"
#include "Core/PowerPC/PowerPC.h"
#include "UICommon/UICommon.h"
@ -359,8 +358,6 @@ int main(int argc, char* argv[])
platform->MainLoop();
Core::Stop();
while (PowerPC::GetState() != PowerPC::CPU_POWERDOWN)
updateMainFrameEvent.Wait();
Core::Shutdown();
platform->Shutdown();