Merge pull request #2042 from bunnei/dynarmic

Interface ARM CPU JIT (Dynarmic)
This commit is contained in:
Yuri Kunde Schlesner 2016-09-15 20:14:18 -07:00 committed by GitHub
commit f196924ddd
19 changed files with 319 additions and 48 deletions

3
.gitmodules vendored
View File

@ -13,3 +13,6 @@
[submodule "catch"]
path = externals/catch
url = https://github.com/philsquared/Catch.git
[submodule "dynarmic"]
path = externals/dynarmic
url = https://github.com/MerryMage/dynarmic.git

View File

@ -118,12 +118,11 @@ else()
endif()
find_package(Boost 1.57.0 QUIET)
if (Boost_FOUND)
include_directories(${Boost_INCLUDE_DIRS})
else()
if (NOT Boost_FOUND)
message(STATUS "Boost 1.57.0 or newer not found, falling back to externals")
include_directories(externals/boost)
set(Boost_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/externals/boost")
endif()
include_directories(${Boost_INCLUDE_DIR})
# Include bundled CMake modules
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/externals/cmake-modules")
@ -227,6 +226,9 @@ set(INI_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/externals/inih")
include_directories(${INI_PREFIX})
add_subdirectory(${INI_PREFIX})
option(DYNARMIC_TESTS OFF)
add_subdirectory(externals/dynarmic)
add_subdirectory(externals/glad)
include_directories(externals/microprofile)
include_directories(externals/nihstro/include)

2
externals/boost vendored

@ -1 +1 @@
Subproject commit 2dcb9d979665b6aabb1635c617973e02914e60ec
Subproject commit f005c955f8147a29667aa0b65257abc3dd520b0c

1
externals/dynarmic vendored Submodule

@ -0,0 +1 @@
Subproject commit 943487eceed82fbae016c333dc9fe33981e69aa8

View File

@ -65,6 +65,7 @@ void Config::ReadValues() {
Settings::values.pad_circle_modifier_scale = (float)sdl2_config->GetReal("Controls", "pad_circle_modifier_scale", 0.5);
// Core
Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
Settings::values.frame_skip = sdl2_config->GetInteger("Core", "frame_skip", 0);
// Renderer

View File

@ -38,6 +38,10 @@ pad_circle_modifier =
pad_circle_modifier_scale =
[Core]
# Whether to use the Just-In-Time (JIT) compiler for CPU emulation
# 0: Interpreter (slow), 1 (default): JIT (fast)
use_cpu_jit =
# The applied frameskip amount. Must be a power of two.
# 0 (default): No frameskip, 1: x2 frameskip, 2: x4 frameskip, 3: x8 frameskip, etc.
frame_skip =

View File

@ -41,6 +41,7 @@ void Config::ReadValues() {
qt_config->endGroup();
qt_config->beginGroup("Core");
Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool();
Settings::values.frame_skip = qt_config->value("frame_skip", 0).toInt();
qt_config->endGroup();
@ -134,6 +135,7 @@ void Config::SaveValues() {
qt_config->endGroup();
qt_config->beginGroup("Core");
qt_config->setValue("use_cpu_jit", Settings::values.use_cpu_jit);
qt_config->setValue("frame_skip", Settings::values.frame_skip);
qt_config->endGroup();

View File

@ -7,6 +7,7 @@
#include "ui_configure_general.h"
#include "core/settings.h"
#include "core/system.h"
ConfigureGeneral::ConfigureGeneral(QWidget *parent) :
QWidget(parent),
@ -14,6 +15,8 @@ ConfigureGeneral::ConfigureGeneral(QWidget *parent) :
{
ui->setupUi(this);
this->setConfiguration();
ui->toggle_cpu_jit->setEnabled(!System::IsPoweredOn());
}
ConfigureGeneral::~ConfigureGeneral() {
@ -22,6 +25,7 @@ ConfigureGeneral::~ConfigureGeneral() {
void ConfigureGeneral::setConfiguration() {
ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan);
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
ui->toggle_cpu_jit->setChecked(Settings::values.use_cpu_jit);
ui->region_combobox->setCurrentIndex(Settings::values.region_value);
}
@ -29,5 +33,6 @@ void ConfigureGeneral::applyConfiguration() {
UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked();
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
Settings::values.region_value = ui->region_combobox->currentIndex();
Settings::values.use_cpu_jit = ui->toggle_cpu_jit->isChecked();
Settings::Apply();
}

View File

@ -43,6 +43,26 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Performance</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QCheckBox" name="toggle_cpu_jit">
<property name="text">
<string>Enable CPU JIT</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">

View File

@ -13,7 +13,7 @@
#define MICROPROFILE_WEBSERVER 0
#define MICROPROFILE_GPU_TIMERS 0 // TODO: Implement timer queries when we upgrade to OpenGL 3.3
#define MICROPROFILE_CONTEXT_SWITCH_TRACE 0
#define MICROPROFILE_PER_THREAD_BUFFER_SIZE (2048<<12) // 8 MB
#define MICROPROFILE_PER_THREAD_BUFFER_SIZE (2048<<13) // 16 MB
#ifdef _WIN32
// This isn't defined by the standard library in MSVC2015

View File

@ -1,6 +1,7 @@
set(SRCS
arm/disassembler/arm_disasm.cpp
arm/disassembler/load_symbol_map.cpp
arm/dynarmic/arm_dynarmic.cpp
arm/dyncom/arm_dyncom.cpp
arm/dyncom/arm_dyncom_dec.cpp
arm/dyncom/arm_dyncom_interpreter.cpp
@ -141,6 +142,7 @@ set(HEADERS
arm/arm_interface.h
arm/disassembler/arm_disasm.h
arm/disassembler/load_symbol_map.h
arm/dynarmic/arm_dynarmic.h
arm/dyncom/arm_dyncom.h
arm/dyncom/arm_dyncom_dec.h
arm/dyncom/arm_dyncom_interpreter.h
@ -285,6 +287,10 @@ set(HEADERS
system.h
)
include_directories(../../externals/dynarmic/include)
create_directory_groups(${SRCS} ${HEADERS})
add_library(core STATIC ${SRCS} ${HEADERS})
target_link_libraries(core dynarmic)

View File

@ -121,15 +121,6 @@ public:
*/
virtual void AddTicks(u64 ticks) = 0;
/**
* Initializes a CPU context for use on this CPU
* @param context Thread context to reset
* @param stack_top Pointer to the top of the stack
* @param entry_point Entry point for execution
* @param arg User argument for thread
*/
virtual void ResetContext(Core::ThreadContext& context, u32 stack_top, u32 entry_point, u32 arg) = 0;
/**
* Saves the current CPU context
* @param ctx Thread context to save

View File

@ -0,0 +1,174 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/assert.h"
#include "common/microprofile.h"
#include <dynarmic/dynarmic.h>
#include "core/arm/dynarmic/arm_dynarmic.h"
#include "core/arm/dyncom/arm_dyncom_interpreter.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/svc.h"
#include "core/memory.h"
static void InterpreterFallback(u32 pc, Dynarmic::Jit* jit, void* user_arg) {
ARMul_State* state = static_cast<ARMul_State*>(user_arg);
state->Reg = jit->Regs();
state->Cpsr = jit->Cpsr();
state->Reg[15] = pc;
state->ExtReg = jit->ExtRegs();
state->VFP[VFP_FPSCR] = jit->Fpscr();
state->NumInstrsToExecute = 1;
InterpreterMainLoop(state);
bool is_thumb = (state->Cpsr & (1 << 5)) != 0;
state->Reg[15] &= (is_thumb ? 0xFFFFFFFE : 0xFFFFFFFC);
jit->Regs() = state->Reg;
jit->Cpsr() = state->Cpsr;
jit->ExtRegs() = state->ExtReg;
jit->SetFpscr(state->VFP[VFP_FPSCR]);
}
static bool IsReadOnlyMemory(u32 vaddr) {
// TODO(bunnei): ImplementMe
return false;
}
static Dynarmic::UserCallbacks GetUserCallbacks(ARMul_State* interpeter_state) {
Dynarmic::UserCallbacks user_callbacks{};
user_callbacks.InterpreterFallback = &InterpreterFallback;
user_callbacks.user_arg = static_cast<void*>(interpeter_state);
user_callbacks.CallSVC = &SVC::CallSVC;
user_callbacks.IsReadOnlyMemory = &IsReadOnlyMemory;
user_callbacks.MemoryRead8 = &Memory::Read8;
user_callbacks.MemoryRead16 = &Memory::Read16;
user_callbacks.MemoryRead32 = &Memory::Read32;
user_callbacks.MemoryRead64 = &Memory::Read64;
user_callbacks.MemoryWrite8 = &Memory::Write8;
user_callbacks.MemoryWrite16 = &Memory::Write16;
user_callbacks.MemoryWrite32 = &Memory::Write32;
user_callbacks.MemoryWrite64 = &Memory::Write64;
return user_callbacks;
}
ARM_Dynarmic::ARM_Dynarmic(PrivilegeMode initial_mode) {
interpreter_state = std::make_unique<ARMul_State>(initial_mode);
jit = std::make_unique<Dynarmic::Jit>(GetUserCallbacks(interpreter_state.get()));
}
void ARM_Dynarmic::SetPC(u32 pc) {
jit->Regs()[15] = pc;
}
u32 ARM_Dynarmic::GetPC() const {
return jit->Regs()[15];
}
u32 ARM_Dynarmic::GetReg(int index) const {
return jit->Regs()[index];
}
void ARM_Dynarmic::SetReg(int index, u32 value) {
jit->Regs()[index] = value;
}
u32 ARM_Dynarmic::GetVFPReg(int index) const {
return jit->ExtRegs()[index];
}
void ARM_Dynarmic::SetVFPReg(int index, u32 value) {
jit->ExtRegs()[index] = value;
}
u32 ARM_Dynarmic::GetVFPSystemReg(VFPSystemRegister reg) const {
if (reg == VFP_FPSCR) {
return jit->Fpscr();
}
// Dynarmic does not implement and/or expose other VFP registers, fallback to interpreter state
return interpreter_state->VFP[reg];
}
void ARM_Dynarmic::SetVFPSystemReg(VFPSystemRegister reg, u32 value) {
if (reg == VFP_FPSCR) {
jit->SetFpscr(value);
}
// Dynarmic does not implement and/or expose other VFP registers, fallback to interpreter state
interpreter_state->VFP[reg] = value;
}
u32 ARM_Dynarmic::GetCPSR() const {
return jit->Cpsr();
}
void ARM_Dynarmic::SetCPSR(u32 cpsr) {
jit->Cpsr() = cpsr;
}
u32 ARM_Dynarmic::GetCP15Register(CP15Register reg) {
return interpreter_state->CP15[reg];
}
void ARM_Dynarmic::SetCP15Register(CP15Register reg, u32 value) {
interpreter_state->CP15[reg] = value;
}
void ARM_Dynarmic::AddTicks(u64 ticks) {
down_count -= ticks;
if (down_count < 0) {
CoreTiming::Advance();
}
}
MICROPROFILE_DEFINE(ARM_Jit, "ARM JIT", "ARM JIT", MP_RGB(255, 64, 64));
void ARM_Dynarmic::ExecuteInstructions(int num_instructions) {
MICROPROFILE_SCOPE(ARM_Jit);
jit->Run(static_cast<unsigned>(num_instructions));
AddTicks(num_instructions);
}
void ARM_Dynarmic::SaveContext(Core::ThreadContext& ctx) {
memcpy(ctx.cpu_registers, jit->Regs().data(), sizeof(ctx.cpu_registers));
memcpy(ctx.fpu_registers, jit->ExtRegs().data(), sizeof(ctx.fpu_registers));
ctx.sp = jit->Regs()[13];
ctx.lr = jit->Regs()[14];
ctx.pc = jit->Regs()[15];
ctx.cpsr = jit->Cpsr();
ctx.fpscr = jit->Fpscr();
ctx.fpexc = interpreter_state->VFP[VFP_FPEXC];
}
void ARM_Dynarmic::LoadContext(const Core::ThreadContext& ctx) {
memcpy(jit->Regs().data(), ctx.cpu_registers, sizeof(ctx.cpu_registers));
memcpy(jit->ExtRegs().data(), ctx.fpu_registers, sizeof(ctx.fpu_registers));
jit->Regs()[13] = ctx.sp;
jit->Regs()[14] = ctx.lr;
jit->Regs()[15] = ctx.pc;
jit->Cpsr() = ctx.cpsr;
jit->SetFpscr(ctx.fpscr);
interpreter_state->VFP[VFP_FPEXC] = ctx.fpexc;
}
void ARM_Dynarmic::PrepareReschedule() {
if (jit->IsExecuting()) {
jit->HaltExecution();
}
}
void ARM_Dynarmic::ClearInstructionCache() {
jit->ClearCache();
}

View File

@ -0,0 +1,50 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <dynarmic/dynarmic.h>
#include "common/common_types.h"
#include "core/arm/arm_interface.h"
#include "core/arm/skyeye_common/armstate.h"
namespace Core {
struct ThreadContext;
}
class ARM_Dynarmic final : public ARM_Interface {
public:
ARM_Dynarmic(PrivilegeMode initial_mode);
void SetPC(u32 pc) override;
u32 GetPC() const override;
u32 GetReg(int index) const override;
void SetReg(int index, u32 value) override;
u32 GetVFPReg(int index) const override;
void SetVFPReg(int index, u32 value) override;
u32 GetVFPSystemReg(VFPSystemRegister reg) const override;
void SetVFPSystemReg(VFPSystemRegister reg, u32 value) override;
u32 GetCPSR() const override;
void SetCPSR(u32 cpsr) override;
u32 GetCP15Register(CP15Register reg) override;
void SetCP15Register(CP15Register reg, u32 value) override;
void AddTicks(u64 ticks) override;
void SaveContext(Core::ThreadContext& ctx) override;
void LoadContext(const Core::ThreadContext& ctx) override;
void PrepareReschedule() override;
void ExecuteInstructions(int num_instructions) override;
void ClearInstructionCache() override;
private:
std::unique_ptr<Dynarmic::Jit> jit;
std::unique_ptr<ARMul_State> interpreter_state;
};

View File

@ -93,15 +93,6 @@ void ARM_DynCom::ExecuteInstructions(int num_instructions) {
AddTicks(ticks_executed);
}
void ARM_DynCom::ResetContext(Core::ThreadContext& context, u32 stack_top, u32 entry_point, u32 arg) {
memset(&context, 0, sizeof(Core::ThreadContext));
context.cpu_registers[0] = arg;
context.pc = entry_point;
context.sp = stack_top;
context.cpsr = USER32MODE | ((entry_point & 1) << 5); // Usermode and THUMB mode
}
void ARM_DynCom::SaveContext(Core::ThreadContext& ctx) {
memcpy(ctx.cpu_registers, state->Reg.data(), sizeof(ctx.cpu_registers));
memcpy(ctx.fpu_registers, state->ExtReg.data(), sizeof(ctx.fpu_registers));
@ -111,8 +102,8 @@ void ARM_DynCom::SaveContext(Core::ThreadContext& ctx) {
ctx.pc = state->Reg[15];
ctx.cpsr = state->Cpsr;
ctx.fpscr = state->VFP[1];
ctx.fpexc = state->VFP[2];
ctx.fpscr = state->VFP[VFP_FPSCR];
ctx.fpexc = state->VFP[VFP_FPEXC];
}
void ARM_DynCom::LoadContext(const Core::ThreadContext& ctx) {
@ -124,8 +115,8 @@ void ARM_DynCom::LoadContext(const Core::ThreadContext& ctx) {
state->Reg[15] = ctx.pc;
state->Cpsr = ctx.cpsr;
state->VFP[1] = ctx.fpscr;
state->VFP[2] = ctx.fpexc;
state->VFP[VFP_FPSCR] = ctx.fpscr;
state->VFP[VFP_FPEXC] = ctx.fpexc;
}
void ARM_DynCom::PrepareReschedule() {

View File

@ -16,7 +16,7 @@ namespace Core {
struct ThreadContext;
}
class ARM_DynCom final : virtual public ARM_Interface {
class ARM_DynCom final : public ARM_Interface {
public:
ARM_DynCom(PrivilegeMode initial_mode);
~ARM_DynCom();
@ -38,7 +38,6 @@ public:
void AddTicks(u64 ticks) override;
void ResetContext(Core::ThreadContext& context, u32 stack_top, u32 entry_point, u32 arg) override;
void SaveContext(Core::ThreadContext& ctx) override;
void LoadContext(const Core::ThreadContext& ctx) override;

View File

@ -6,16 +6,16 @@
#include "common/logging/log.h"
#include "core/arm/arm_interface.h"
#include "core/arm/dynarmic/arm_dynarmic.h"
#include "core/arm/dyncom/arm_dyncom.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/arm/arm_interface.h"
#include "core/arm/dyncom/arm_dyncom.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/hle.h"
#include "core/hle/kernel/thread.h"
#include "core/hw/hw.h"
#include "core/gdbstub/gdbstub.h"
#include "core/settings.h"
namespace Core {
@ -73,8 +73,13 @@ void Stop() {
/// Initialize the core
void Init() {
if (Settings::values.use_cpu_jit) {
g_sys_core = std::make_unique<ARM_Dynarmic>(USER32MODE);
g_app_core = std::make_unique<ARM_Dynarmic>(USER32MODE);
} else {
g_sys_core = std::make_unique<ARM_DynCom>(USER32MODE);
g_app_core = std::make_unique<ARM_DynCom>(USER32MODE);
}
LOG_DEBUG(Core, "Initialized OK");
}

View File

@ -441,6 +441,22 @@ std::tuple<u32, u32, bool> GetFreeThreadLocalSlot(std::vector<std::bitset<8>>& t
return std::make_tuple(0, 0, true);
}
/**
* Resets a thread context, making it ready to be scheduled and run by the CPU
* @param context Thread context to reset
* @param stack_top Address of the top of the stack
* @param entry_point Address of entry point for execution
* @param arg User argument for thread
*/
static void ResetThreadContext(Core::ThreadContext& context, u32 stack_top, u32 entry_point, u32 arg) {
memset(&context, 0, sizeof(Core::ThreadContext));
context.cpu_registers[0] = arg;
context.pc = entry_point;
context.sp = stack_top;
context.cpsr = USER32MODE | ((entry_point & 1) << 5); // Usermode and THUMB mode
}
ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, s32 priority,
u32 arg, s32 processor_id, VAddr stack_top) {
if (priority < THREADPRIO_HIGHEST || priority > THREADPRIO_LOWEST) {
@ -525,7 +541,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
// TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used
// to initialize the context
Core::g_app_core->ResetContext(thread->context, stack_top, entry_point, arg);
ResetThreadContext(thread->context, stack_top, entry_point, arg);
ready_queue.push_back(thread->current_priority, thread.get());
thread->status = THREADSTATUS_READY;

View File

@ -60,6 +60,7 @@ struct Values {
float pad_circle_modifier_scale;
// Core
bool use_cpu_jit;
int frame_skip;
// Data Storage