core: Create a thread for each CPU core, keep in lock-step with a barrier.
This commit is contained in:
parent
5590245930
commit
9776ff9179
4 changed files with 94 additions and 18 deletions
|
@ -27,6 +27,13 @@ namespace Core {
|
||||||
|
|
||||||
System::~System() = default;
|
System::~System() = default;
|
||||||
|
|
||||||
|
/// Runs a CPU core while the system is powered on
|
||||||
|
static void RunCpuCore(std::shared_ptr<Cpu> cpu_state) {
|
||||||
|
while (Core::System().GetInstance().IsPoweredOn()) {
|
||||||
|
cpu_state->RunLoop(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
System::ResultStatus System::RunLoop(bool tight_loop) {
|
System::ResultStatus System::RunLoop(bool tight_loop) {
|
||||||
status = ResultStatus::Success;
|
status = ResultStatus::Success;
|
||||||
|
|
||||||
|
@ -109,7 +116,7 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::PrepareReschedule() {
|
void System::PrepareReschedule() {
|
||||||
cpu_cores[0]->PrepareReschedule();
|
CurrentCpuCore().PrepareReschedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
PerfStats::Results System::GetAndResetPerfStats() {
|
PerfStats::Results System::GetAndResetPerfStats() {
|
||||||
|
@ -123,14 +130,13 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
|
||||||
|
|
||||||
current_process = Kernel::Process::Create("main");
|
current_process = Kernel::Process::Create("main");
|
||||||
|
|
||||||
for (auto& cpu_core : cpu_cores) {
|
cpu_barrier = std::make_shared<CpuBarrier>();
|
||||||
cpu_core = std::make_unique<Cpu>();
|
for (size_t index = 0; index < cpu_cores.size(); ++index) {
|
||||||
|
cpu_cores[index] = std::make_shared<Cpu>(cpu_barrier, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
gpu_core = std::make_unique<Tegra::GPU>();
|
gpu_core = std::make_unique<Tegra::GPU>();
|
||||||
|
|
||||||
telemetry_session = std::make_unique<Core::TelemetrySession>();
|
telemetry_session = std::make_unique<Core::TelemetrySession>();
|
||||||
|
|
||||||
service_manager = std::make_shared<Service::SM::ServiceManager>();
|
service_manager = std::make_shared<Service::SM::ServiceManager>();
|
||||||
|
|
||||||
HW::Init();
|
HW::Init();
|
||||||
|
@ -142,6 +148,14 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
|
||||||
return ResultStatus::ErrorVideoCore;
|
return ResultStatus::ErrorVideoCore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create threads for CPU cores 1-3, and build thread_to_cpu map
|
||||||
|
// CPU core 0 is run on the main thread
|
||||||
|
thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0];
|
||||||
|
for (size_t index = 0; index < cpu_core_threads.size(); ++index) {
|
||||||
|
cpu_core_threads[index] = std::make_unique<std::thread>(RunCpuCore, cpu_cores[index + 1]);
|
||||||
|
thread_to_cpu[cpu_core_threads[index]->get_id()] = cpu_cores[index + 1];
|
||||||
|
}
|
||||||
|
|
||||||
NGLOG_DEBUG(Core, "Initialized OK");
|
NGLOG_DEBUG(Core, "Initialized OK");
|
||||||
|
|
||||||
// Reset counters and set time origin to current frame
|
// Reset counters and set time origin to current frame
|
||||||
|
@ -171,9 +185,15 @@ void System::Shutdown() {
|
||||||
telemetry_session.reset();
|
telemetry_session.reset();
|
||||||
gpu_core.reset();
|
gpu_core.reset();
|
||||||
|
|
||||||
|
// Close all CPU/threading state
|
||||||
|
thread_to_cpu.clear();
|
||||||
for (auto& cpu_core : cpu_cores) {
|
for (auto& cpu_core : cpu_cores) {
|
||||||
cpu_core.reset();
|
cpu_core.reset();
|
||||||
}
|
}
|
||||||
|
for (auto& thread : cpu_core_threads) {
|
||||||
|
thread->join();
|
||||||
|
thread.reset();
|
||||||
|
}
|
||||||
|
|
||||||
CoreTiming::Shutdown();
|
CoreTiming::Shutdown();
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "core/core_cpu.h"
|
#include "core/core_cpu.h"
|
||||||
#include "core/hle/kernel/kernel.h"
|
#include "core/hle/kernel/kernel.h"
|
||||||
|
@ -112,7 +113,7 @@ public:
|
||||||
* @returns A reference to the emulated CPU.
|
* @returns A reference to the emulated CPU.
|
||||||
*/
|
*/
|
||||||
ARM_Interface& CPU() {
|
ARM_Interface& CPU() {
|
||||||
return cpu_cores[0]->CPU();
|
return CurrentCpuCore().CPU();
|
||||||
}
|
}
|
||||||
|
|
||||||
Tegra::GPU& GPU() {
|
Tegra::GPU& GPU() {
|
||||||
|
@ -120,7 +121,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
Kernel::Scheduler& Scheduler() {
|
Kernel::Scheduler& Scheduler() {
|
||||||
return cpu_cores[0]->Scheduler();
|
return CurrentCpuCore().Scheduler();
|
||||||
}
|
}
|
||||||
|
|
||||||
Kernel::SharedPtr<Kernel::Process>& CurrentProcess() {
|
Kernel::SharedPtr<Kernel::Process>& CurrentProcess() {
|
||||||
|
@ -157,6 +158,14 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// Returns the current CPU core based on the calling host thread
|
||||||
|
Cpu& CurrentCpuCore() {
|
||||||
|
const auto& search = thread_to_cpu.find(std::this_thread::get_id());
|
||||||
|
ASSERT(search != thread_to_cpu.end());
|
||||||
|
ASSERT(search->second);
|
||||||
|
return *search->second;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the emulated system.
|
* Initialize the emulated system.
|
||||||
* @param emu_window Pointer to the host-system window used for video output and keyboard input.
|
* @param emu_window Pointer to the host-system window used for video output and keyboard input.
|
||||||
|
@ -167,14 +176,12 @@ private:
|
||||||
|
|
||||||
/// AppLoader used to load the current executing application
|
/// AppLoader used to load the current executing application
|
||||||
std::unique_ptr<Loader::AppLoader> app_loader;
|
std::unique_ptr<Loader::AppLoader> app_loader;
|
||||||
|
|
||||||
std::array<std::unique_ptr<Cpu>, 4> cpu_cores;
|
|
||||||
std::unique_ptr<Tegra::GPU> gpu_core;
|
std::unique_ptr<Tegra::GPU> gpu_core;
|
||||||
std::shared_ptr<Tegra::DebugContext> debug_context;
|
std::shared_ptr<Tegra::DebugContext> debug_context;
|
||||||
Kernel::SharedPtr<Kernel::Process> current_process;
|
Kernel::SharedPtr<Kernel::Process> current_process;
|
||||||
|
std::shared_ptr<CpuBarrier> cpu_barrier;
|
||||||
/// When true, signals that a reschedule should happen
|
std::array<std::shared_ptr<Cpu>, NUM_CPU_CORES> cpu_cores;
|
||||||
bool reschedule_pending{};
|
std::array<std::unique_ptr<std::thread>, NUM_CPU_CORES - 1> cpu_core_threads;
|
||||||
|
|
||||||
/// Service manager
|
/// Service manager
|
||||||
std::shared_ptr<Service::SM::ServiceManager> service_manager;
|
std::shared_ptr<Service::SM::ServiceManager> service_manager;
|
||||||
|
@ -186,6 +193,9 @@ private:
|
||||||
|
|
||||||
ResultStatus status = ResultStatus::Success;
|
ResultStatus status = ResultStatus::Success;
|
||||||
std::string status_details = "";
|
std::string status_details = "";
|
||||||
|
|
||||||
|
/// Map of guest threads to CPU cores
|
||||||
|
std::map<std::thread::id, std::shared_ptr<Cpu>> thread_to_cpu;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline ARM_Interface& CPU() {
|
inline ARM_Interface& CPU() {
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#ifdef ARCHITECTURE_x86_64
|
#ifdef ARCHITECTURE_x86_64
|
||||||
#include "core/arm/dynarmic/arm_dynarmic.h"
|
#include "core/arm/dynarmic/arm_dynarmic.h"
|
||||||
|
@ -16,7 +19,9 @@
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
Cpu::Cpu() {
|
Cpu::Cpu(std::shared_ptr<CpuBarrier> cpu_barrier, size_t core_index)
|
||||||
|
: cpu_barrier{std::move(cpu_barrier)}, core_index{core_index} {
|
||||||
|
|
||||||
if (Settings::values.use_cpu_jit) {
|
if (Settings::values.use_cpu_jit) {
|
||||||
#ifdef ARCHITECTURE_x86_64
|
#ifdef ARCHITECTURE_x86_64
|
||||||
arm_interface = std::make_shared<ARM_Dynarmic>();
|
arm_interface = std::make_shared<ARM_Dynarmic>();
|
||||||
|
@ -32,15 +37,25 @@ Cpu::Cpu() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Cpu::RunLoop(bool tight_loop) {
|
void Cpu::RunLoop(bool tight_loop) {
|
||||||
|
// Wait for all other CPU cores to complete the previous slice, such that they run in lock-step
|
||||||
|
cpu_barrier->Rendezvous();
|
||||||
|
|
||||||
// If we don't have a currently active thread then don't execute instructions,
|
// If we don't have a currently active thread then don't execute instructions,
|
||||||
// instead advance to the next event and try to yield to the next thread
|
// instead advance to the next event and try to yield to the next thread
|
||||||
if (Kernel::GetCurrentThread() == nullptr) {
|
if (Kernel::GetCurrentThread() == nullptr) {
|
||||||
NGLOG_TRACE(Core, "Idling");
|
NGLOG_TRACE(Core, "Core-{} idling", core_index);
|
||||||
CoreTiming::Idle();
|
|
||||||
CoreTiming::Advance();
|
if (IsMainCore()) {
|
||||||
|
CoreTiming::Idle();
|
||||||
|
CoreTiming::Advance();
|
||||||
|
}
|
||||||
|
|
||||||
PrepareReschedule();
|
PrepareReschedule();
|
||||||
} else {
|
} else {
|
||||||
CoreTiming::Advance();
|
if (IsMainCore()) {
|
||||||
|
CoreTiming::Advance();
|
||||||
|
}
|
||||||
|
|
||||||
if (tight_loop) {
|
if (tight_loop) {
|
||||||
arm_interface->Run();
|
arm_interface->Run();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -4,7 +4,9 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <condition_variable>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
@ -16,9 +18,32 @@ class Scheduler;
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
|
constexpr unsigned NUM_CPU_CORES{4};
|
||||||
|
|
||||||
|
class CpuBarrier {
|
||||||
|
public:
|
||||||
|
void Rendezvous() {
|
||||||
|
std::unique_lock<std::mutex> lock(mutex);
|
||||||
|
|
||||||
|
--cores_waiting;
|
||||||
|
if (!cores_waiting) {
|
||||||
|
cores_waiting = NUM_CPU_CORES;
|
||||||
|
condition.notify_all();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
condition.wait(lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
unsigned cores_waiting{NUM_CPU_CORES};
|
||||||
|
std::mutex mutex;
|
||||||
|
std::condition_variable condition;
|
||||||
|
};
|
||||||
|
|
||||||
class Cpu {
|
class Cpu {
|
||||||
public:
|
public:
|
||||||
Cpu();
|
Cpu(std::shared_ptr<CpuBarrier> cpu_barrier, size_t core_index);
|
||||||
|
|
||||||
void RunLoop(bool tight_loop = true);
|
void RunLoop(bool tight_loop = true);
|
||||||
|
|
||||||
|
@ -34,13 +59,19 @@ public:
|
||||||
return *scheduler;
|
return *scheduler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsMainCore() const {
|
||||||
|
return core_index == 0;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Reschedule();
|
void Reschedule();
|
||||||
|
|
||||||
std::shared_ptr<ARM_Interface> arm_interface;
|
std::shared_ptr<ARM_Interface> arm_interface;
|
||||||
|
std::shared_ptr<CpuBarrier> cpu_barrier;
|
||||||
std::unique_ptr<Kernel::Scheduler> scheduler;
|
std::unique_ptr<Kernel::Scheduler> scheduler;
|
||||||
|
|
||||||
bool reschedule_pending{};
|
bool reschedule_pending{};
|
||||||
|
size_t core_index;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
Loading…
Reference in a new issue