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

@ -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);
}