summaryrefslogtreecommitdiff
path: root/Source/Core
diff options
context:
space:
mode:
Diffstat (limited to 'Source/Core')
-rw-r--r--Source/Core/AudioCommon/Mixer.cpp9
-rw-r--r--Source/Core/Common/Event.h6
-rw-r--r--Source/Core/Core/Core.cpp83
-rw-r--r--Source/Core/Core/Core.h8
-rw-r--r--Source/Core/Core/Debugger/PPCDebugInterface.cpp7
-rw-r--r--Source/Core/Core/FifoPlayer/FifoPlayer.cpp116
-rw-r--r--Source/Core/Core/FifoPlayer/FifoPlayer.h14
-rw-r--r--Source/Core/Core/HLE/HLE_Misc.cpp3
-rw-r--r--Source/Core/Core/HW/CPU.cpp326
-rw-r--r--Source/Core/Core/HW/CPU.h46
-rw-r--r--Source/Core/Core/Movie.cpp7
-rw-r--r--Source/Core/Core/PowerPC/CachedInterpreter.cpp6
-rw-r--r--Source/Core/Core/PowerPC/Interpreter/Interpreter.cpp5
-rw-r--r--Source/Core/Core/PowerPC/Jit64/Jit.cpp9
-rw-r--r--Source/Core/Core/PowerPC/Jit64/JitAsm.cpp12
-rw-r--r--Source/Core/Core/PowerPC/Jit64/Jit_LoadStore.cpp3
-rw-r--r--Source/Core/Core/PowerPC/Jit64IL/IR_X86.cpp3
-rw-r--r--Source/Core/Core/PowerPC/Jit64IL/JitIL.cpp7
-rw-r--r--Source/Core/Core/PowerPC/JitArm64/JitAsm.cpp7
-rw-r--r--Source/Core/Core/PowerPC/JitCommon/JitBase.cpp3
-rw-r--r--Source/Core/Core/PowerPC/JitILCommon/JitILBase_LoadStore.cpp3
-rw-r--r--Source/Core/Core/PowerPC/PowerPC.cpp128
-rw-r--r--Source/Core/Core/PowerPC/PowerPC.h32
-rw-r--r--Source/Core/DolphinWX/Debugger/CodeWindow.cpp2
-rw-r--r--Source/Core/DolphinWX/Debugger/WatchView.cpp7
-rw-r--r--Source/Core/DolphinWX/MainNoGUI.cpp3
26 files changed, 555 insertions, 300 deletions
diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp
index f35a671094..5e3aef733a 100644
--- a/Source/Core/AudioCommon/Mixer.cpp
+++ b/Source/Core/AudioCommon/Mixer.cpp
@@ -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);
diff --git a/Source/Core/Common/Event.h b/Source/Core/Common/Event.h
index e05bafac1f..49cf543ead 100644
--- a/Source/Core/Common/Event.h
+++ b/Source/Core/Common/Event.h
@@ -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;
}
diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp
index 5c0258c37f..b5fbf14663 100644
--- a/Source/Core/Core/Core.cpp
+++ b/Source/Core/Core/Core.cpp
@@ -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;
diff --git a/Source/Core/Core/Core.h b/Source/Core/Core/Core.h
index e9f179579d..b5de60fd5e 100644
--- a/Source/Core/Core/Core.h
+++ b/Source/Core/Core/Core.h
@@ -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
diff --git a/Source/Core/Core/Debugger/PPCDebugInterface.cpp b/Source/Core/Core/Debugger/PPCDebugInterface.cpp
index 0f61bff49f..9d6eacef8e 100644
--- a/Source/Core/Core/Debugger/PPCDebugInterface.cpp
+++ b/Source/Core/Core/Debugger/PPCDebugInterface.cpp
@@ -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)
diff --git a/Source/Core/Core/FifoPlayer/FifoPlayer.cpp b/Source/Core/Core/FifoPlayer/FifoPlayer.cpp
index a61d4a5887..a59861cde6 100644
--- a/Source/Core/Core/FifoPlayer/FifoPlayer.cpp
+++ b/Source/Core/Core/FifoPlayer/FifoPlayer.cpp
@@ -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;
+public:
+ explicit CPUCore(FifoPlayer* parent)
+ : m_parent(parent)
+ {
+ }
+ CPUCore(const CPUCore&) = delete;
+ ~CPUCore()
+ {
+ }
+ CPUCore& operator=(const CPUCore&) = delete;
- if (m_File->GetFrameCount() == 0)
- return false;
+ void Init() override
+ {
+ IsPlayingBackFifologWithBrokenEFBCopies = m_parent->m_File->HasBrokenEFBCopies();
+
+ m_parent->m_CurrentFrame = m_parent->m_FrameRangeStart;
+ m_parent->LoadMemory();
+ }
- IsPlayingBackFifologWithBrokenEFBCopies = m_File->HasBrokenEFBCopies();
+ void Shutdown() override
+ {
+ IsPlayingBackFifologWithBrokenEFBCopies = false;
+ }
- m_CurrentFrame = m_FrameRangeStart;
+ void ClearCache() override
+ {
+ // Nothing to clear.
+ }
- LoadMemory();
+ 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.");
+ }
- // This loop replaces the CPU loop that occurs when a game is run
- while (PowerPC::GetState() != PowerPC::CPU_POWERDOWN)
+ const char* GetName() override
{
- if (PowerPC::GetState() == PowerPC::CPU_RUNNING)
+ 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);
- }
+ case CPU::CPU_POWERDOWN:
+ CPU::Break();
+ Host_Message(WM_USER_STOP);
+ break;
+
+ case CPU::CPU_STEPPING:
+ CPU::Break();
+ Host_UpdateMainFrame();
+ break;
}
- else
- {
- if (m_FrameWrittenCb)
- m_FrameWrittenCb();
+ }
+ }
- if (m_EarlyMemoryUpdates && m_CurrentFrame == m_FrameRangeStart)
- WriteAllMemoryUpdates();
+private:
+ FifoPlayer* m_parent;
+};
- WriteFrame(m_File->GetFrame(m_CurrentFrame), m_FrameInfo[m_CurrentFrame]);
+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_CurrentFrame = m_FrameRangeStart;
}
- IsPlayingBackFifologWithBrokenEFBCopies = false;
+ 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 true;
+ return std::make_unique<CPUCore>(this);
}
u32 FifoPlayer::GetFrameObjectCount()
diff --git a/Source/Core/Core/FifoPlayer/FifoPlayer.h b/Source/Core/Core/FifoPlayer/FifoPlayer.h
index a61ab7cc86..78f40ac777 100644
--- a/Source/Core/Core/FifoPlayer/FifoPlayer.h
+++ b/Source/Core/Core/FifoPlayer/FifoPlayer.h
@@ -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);
diff --git a/Source/Core/Core/HLE/HLE_Misc.cpp b/Source/Core/Core/HLE/HLE_Misc.cpp
index cb77e0f215..26ce8c884f 100644
--- a/Source/Core/Core/HLE/HLE_Misc.cpp
+++ b/Source/Core/Core/HLE/HLE_Misc.cpp
@@ -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);
}
diff --git a/Source/Core/Core/HW/CPU.cpp b/Source/Core/Core/HW/CPU.cpp
index a853fdbd73..e436b90d1b 100644
--- a/Source/Core/Core/HW/CPU.cpp
+++ b/Source/Core/Core/HW/CPU.cpp
@@ -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;
}
-void Run()
+// Requires holding s_state_change_lock
+static void FlushStepSyncEventLocked()
{
- std::lock_guard<std::mutex> lk(m_csCpuOccupied);
- Host_UpdateDisasmDialog();
+ 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;
+}
- while (true)
+void Run()
+{
+ 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
- PowerPC::RunLoop();
- break;
+ case CPU_RUNNING:
+ s_state_cpu_thread_active = true;
+ state_lock.unlock();
- case PowerPC::CPU_STEPPING:
- m_csCpuOccupied.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);
+ }
+ }
- //1: wait for step command..
- m_StepEvent.Wait();
+ // Enter a fast runloop
+ PowerPC::RunLoop();
- m_csCpuOccupied.lock();
- if (PowerPC::GetState() == PowerPC::CPU_POWERDOWN)
- return;
- if (PowerPC::GetState() != PowerPC::CPU_STEPPING)
+ state_lock.lock();
+ s_state_cpu_thread_active = false;
+ s_state_cpu_idle_cvar.notify_all();
+ break;
+
+ 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();
+}
+
+// Requires s_state_change_lock
+static bool SetStateLocked(State s)
+{
+ if (s_state == CPU_POWERDOWN)
+ return false;
+ s_state = s;
+ return true;
}
-void EnableStepping(const bool stepping)
+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);
+ 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), []
+ {
+ return !s_state_cpu_thread_active;
+ });
+ if (!success)
+ ERROR_LOG(POWERPC, "Abandoned waiting for CPU Thread! The Core may be deadlocked.");
+
+ RunAdjacentSystems(false);
}
- else
+ else if (SetStateLocked(CPU_RUNNING))
{
- // 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)
- {
- 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);
+ 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)
+ // Only need the stepping lock for this
+ if (s_have_fake_cpu_thread)
{
- PowerPC::Start();
- m_StepEvent.Set();
+ s_have_fake_cpu_thread = false;
+ Core::UndeclareAsCPUThread();
}
- if (s_have_fake_cpu_thread)
{
- Core::UndeclareAsCPUThread();
- m_csCpuOccupied.unlock();
- s_have_fake_cpu_thread = false;
+ 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;
}
}
diff --git a/Source/Core/Core/HW/CPU.h b/Source/Core/Core/HW/CPU.h
index 41b2630f2e..f9fc6aaca9 100644
--- a/Source/Core/Core/HW/CPU.h
+++ b/Source/Core/Core/HW/CPU.h
@@ -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);
}
diff --git a/Source/Core/Core/Movie.cpp b/Source/Core/Core/Movie.cpp
index 23eaf160fe..368ad04eb9 100644
--- a/Source/Core/Core/Movie.cpp
+++ b/Source/Core/Core/Movie.cpp
@@ -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)
{
diff --git a/Source/Core/Core/PowerPC/CachedInterpreter.cpp b/Source/Core/Core/PowerPC/CachedInterpreter.cpp
index 3b52caaa62..6adc822349 100644
--- a/Source/Core/Core/PowerPC/CachedInterpreter.cpp
+++ b/Source/Core/Core/PowerPC/CachedInterpreter.cpp
@@ -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()
diff --git a/Source/Core/Core/PowerPC/Interpreter/Interpreter.cpp b/Source/Core/Core/PowerPC/Interpreter/Interpreter.cpp
index 280bb8d80a..ecf3bbb151 100644
--- a/Source/Core/Core/PowerPC/Interpreter/Interpreter.cpp
+++ b/Source/Core/Core/PowerPC/Interpreter/Interpreter.cpp
@@ -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)
diff --git a/Source/Core/Core/PowerPC/Jit64/Jit.cpp b/Source/Core/Core/PowerPC/Jit64/Jit.cpp
index 111dcb46ab..e4f211a090 100644
--- a/Source/Core/Core/PowerPC/Jit64/Jit.cpp
+++ b/Source/Core/Core/PowerPC/Jit64/Jit.cpp
@@ -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);
diff --git a/Source/Core/Core/PowerPC/Jit64/JitAsm.cpp b/Source/Core/Core/PowerPC/Jit64/JitAsm.cpp
index 0b1638bbf3..28deee1053 100644
--- a/Source/Core/Core/PowerPC/Jit64/JitAsm.cpp
+++ b/Source/Core/Core/PowerPC/Jit64/JitAsm.cpp
@@ -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();
diff --git a/Source/Core/Core/PowerPC/Jit64/Jit_LoadStore.cpp b/Source/Core/Core/PowerPC/Jit64/Jit_LoadStore.cpp
index 70eef7373d..37fb86e17b 100644
--- a/Source/Core/Core/PowerPC/Jit64/Jit_LoadStore.cpp
+++ b/Source/Core/Core/PowerPC/Jit64/Jit_LoadStore.cpp
@@ -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 &&
diff --git a/Source/Core/Core/PowerPC/Jit64IL/IR_X86.cpp b/Source/Core/Core/PowerPC/Jit64IL/IR_X86.cpp
index c05b3052bd..06102dc8f7 100644
--- a/Source/Core/Core/PowerPC/Jit64IL/IR_X86.cpp
+++ b/Source/Core/Core/PowerPC/Jit64IL/IR_X86.cpp
@@ -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);
diff --git a/Source/Core/Core/PowerPC/Jit64IL/JitIL.cpp b/Source/Core/Core/PowerPC/Jit64IL/JitIL.cpp
index 68fb1aa686..c8628b76e5 100644
--- a/Source/Core/Core/PowerPC/Jit64IL/JitIL.cpp
+++ b/Source/Core/Core/PowerPC/Jit64IL/JitIL.cpp
@@ -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;
diff --git a/Source/Core/Core/PowerPC/JitArm64/JitAsm.cpp b/Source/Core/Core/PowerPC/JitArm64/JitAsm.cpp
index e2d2b2cbcc..f5279a61d9 100644
--- a/Source/Core/Core/PowerPC/JitArm64/JitAsm.cpp
+++ b/Source/Core/Core/PowerPC/JitArm64/JitAsm.cpp
@@ -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);
diff --git a/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp b/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp
index 89da8cef31..9beb4c8faa 100644
--- a/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp
+++ b/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp
@@ -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++)
diff --git a/Source/Core/Core/PowerPC/JitILCommon/JitILBase_LoadStore.cpp b/Source/Core/Core/PowerPC/JitILCommon/JitILBase_LoadStore.cpp
index f47756d861..42aa779724 100644
--- a/Source/Core/Core/PowerPC/JitILCommon/JitILBase_LoadStore.cpp
+++ b/Source/Core/Core/PowerPC/JitILCommon/JitILBase_LoadStore.cpp
@@ -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 ||
diff --git a/Source/Core/Core/PowerPC/PowerPC.cpp b/Source/Core/Core/PowerPC/PowerPC.cpp
index d709708456..433b94a11b 100644
--- a/Source/Core/Core/PowerPC/PowerPC.cpp
+++ b/Source/Core/Core/PowerPC/PowerPC.cpp
@@ -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,92 +174,85 @@ 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 SingleStep()
+void SetMode(CoreMode new_mode)
{
- cpu_core_base->SingleStep();
-}
+ if (new_mode == s_mode)
+ return; // We don't need to do anything.
-void RunLoop()
-{
- state = CPU_RUNNING;
- cpu_core_base->Run();
- Host_UpdateDisasmDialog();
-}
+ s_mode = new_mode;
-CPUState GetState()
-{
- return state;
-}
+ // If we're using an external CPU core implementation then don't do anything.
+ if (s_cpu_core_base_is_injected)
+ return;
-const volatile CPUState *GetStatePtr()
-{
- return &state;
+ ApplyMode();
}
-void Start()
+const char* GetCPUName()
{
- state = CPU_RUNNING;
- Host_UpdateDisasmDialog();
+ return s_cpu_core_base->GetName();
}
-void Pause()
+void InjectExternalCPUCore(CPUCoreBase* new_cpu)
{
- volatile CPUState old_state = state;
- state = CPU_STEPPING;
+ // Previously injected.
+ if (s_cpu_core_base_is_injected)
+ s_cpu_core_base->Shutdown();
- // Wait for the CPU core to leave
- if (old_state == CPU_RUNNING)
- s_state_change.WaitFor(std::chrono::seconds(1));
- Host_UpdateDisasmDialog();
+ // 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 Stop()
+void SingleStep()
{
- 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();
+ s_cpu_core_base->SingleStep();
}
-void FinishStateMove()
+void RunLoop()
{
- s_state_change.Set();
+ Host_UpdateDisasmDialog();
+ s_cpu_core_base->Run();
+ Host_UpdateDisasmDialog();
}
void UpdatePerformanceMonitor(u32 cycles, u32 num_load_stores, u32 num_fp_inst)
@@ -521,7 +513,7 @@ void CheckBreakPoints()
{
if (PowerPC::breakpoints.IsAddressBreakPoint(PC))
{
- PowerPC::Pause();
+ CPU::Break();
if (PowerPC::breakpoints.IsTempBreakPoint(PC))
PowerPC::breakpoints.Remove(PC);
}
diff --git a/Source/Core/Core/PowerPC/PowerPC.h b/Source/Core/Core/PowerPC/PowerPC.h
index 71faf219b5..5a9fe5842d 100644
--- a/Source/Core/Core/PowerPC/PowerPC.h
+++ b/Source/Core/Core/PowerPC/PowerPC.h
@@ -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);
diff --git a/Source/Core/DolphinWX/Debugger/CodeWindow.cpp b/Source/Core/DolphinWX/Debugger/CodeWindow.cpp
index c0108dc026..4e5581ca8e 100644
--- a/Source/Core/DolphinWX/Debugger/CodeWindow.cpp
+++ b/Source/Core/DolphinWX/Debugger/CodeWindow.cpp
@@ -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();
diff --git a/Source/Core/DolphinWX/Debugger/WatchView.cpp b/Source/Core/DolphinWX/Debugger/WatchView.cpp
index ea79573f12..4f46c8aab2 100644
--- a/Source/Core/DolphinWX/Debugger/WatchView.cpp
+++ b/Source/Core/DolphinWX/Debugger/WatchView.cpp
@@ -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();
diff --git a/Source/Core/DolphinWX/MainNoGUI.cpp b/Source/Core/DolphinWX/MainNoGUI.cpp
index 2ccd9f633d..bf2089a5c8 100644
--- a/Source/Core/DolphinWX/MainNoGUI.cpp
+++ b/Source/Core/DolphinWX/MainNoGUI.cpp
@@ -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();