diff --git a/.gitmodules b/.gitmodules index 1f0b80768..f498a60de 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,15 +1,18 @@ [submodule "inih"] - path = externals/inih/inih - url = https://github.com/svn2github/inih + path = externals/inih/inih + url = https://github.com/svn2github/inih [submodule "boost"] - path = externals/boost - url = https://github.com/citra-emu/ext-boost.git + path = externals/boost + url = https://github.com/citra-emu/ext-boost.git [submodule "nihstro"] - path = externals/nihstro - url = https://github.com/neobrain/nihstro.git + path = externals/nihstro + url = https://github.com/neobrain/nihstro.git [submodule "soundtouch"] - path = externals/soundtouch - url = https://github.com/citra-emu/ext-soundtouch.git + path = externals/soundtouch + url = https://github.com/citra-emu/ext-soundtouch.git [submodule "catch"] - path = externals/catch - url = https://github.com/philsquared/Catch.git + path = externals/catch + url = https://github.com/philsquared/Catch.git +[submodule "dynarmic"] + path = externals/dynarmic + url = https://github.com/MerryMage/dynarmic.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ac3df0e0..c7a24f04f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/externals/boost b/externals/boost index 2dcb9d979..f005c955f 160000 --- a/externals/boost +++ b/externals/boost @@ -1 +1 @@ -Subproject commit 2dcb9d979665b6aabb1635c617973e02914e60ec +Subproject commit f005c955f8147a29667aa0b65257abc3dd520b0c diff --git a/externals/dynarmic b/externals/dynarmic new file mode 160000 index 000000000..cc3e7e71a --- /dev/null +++ b/externals/dynarmic @@ -0,0 +1 @@ +Subproject commit cc3e7e71aa020cb243cb24d06f7577a9fd8170fe diff --git a/src/citra/config.cpp b/src/citra/config.cpp index ac35a52ed..503c4e484 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -65,6 +65,7 @@ void Config::ReadValues() { Settings::values.pad_circle_modifier_scale = (float)sdl2_config->GetReal("Controls", "pad_circle_modifier_scale", 0.4); // 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 diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index 251f10a72..4447380a1 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -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 = diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp index 62421db06..6b857b069 100644 --- a/src/citra_qt/config.cpp +++ b/src/citra_qt/config.cpp @@ -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(); @@ -135,6 +136,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(); diff --git a/src/citra_qt/configure_general.cpp b/src/citra_qt/configure_general.cpp index 95aab9f2e..e6832341a 100644 --- a/src/citra_qt/configure_general.cpp +++ b/src/citra_qt/configure_general.cpp @@ -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(); } diff --git a/src/citra_qt/configure_general.ui b/src/citra_qt/configure_general.ui index 343f804c0..81688113f 100644 --- a/src/citra_qt/configure_general.ui +++ b/src/citra_qt/configure_general.ui @@ -43,6 +43,26 @@ + + + + Performance + + + + + + + + Enable CPU JIT + + + + + + + + diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 1aae4c5aa..3b4a20986 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -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 @@ -142,6 +143,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 @@ -287,6 +289,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) diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h index de5e9c8fa..480c90e66 100644 --- a/src/core/arm/arm_interface.h +++ b/src/core/arm/arm_interface.h @@ -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 diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp new file mode 100644 index 000000000..0428c5681 --- /dev/null +++ b/src/core/arm/dynarmic/arm_dynarmic.cpp @@ -0,0 +1,167 @@ +// 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 + +#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(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(interpeter_state); + user_callbacks.CallSVC = (bool(*)(u32)) &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(initial_mode); + jit = std::make_unique(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 { + // TODO(bunnei): ImplementMe + UNIMPLEMENTED(); + return 0; +} + +void ARM_Dynarmic::SetVFPSystemReg(VFPSystemRegister reg, u32 value) { + // TODO(bunnei): ImplementMe + UNIMPLEMENTED(); +} + +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(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(); +} diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h new file mode 100644 index 000000000..d493cabd5 --- /dev/null +++ b/src/core/arm/dynarmic/arm_dynarmic.h @@ -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 + +#include + +#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 jit; + std::unique_ptr interpreter_state; +}; diff --git a/src/core/arm/dyncom/arm_dyncom.cpp b/src/core/arm/dyncom/arm_dyncom.cpp index ab77da965..d84917529 100644 --- a/src/core/arm/dyncom/arm_dyncom.cpp +++ b/src/core/arm/dyncom/arm_dyncom.cpp @@ -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() { diff --git a/src/core/arm/dyncom/arm_dyncom.h b/src/core/arm/dyncom/arm_dyncom.h index e763abc24..70f71a828 100644 --- a/src/core/arm/dyncom/arm_dyncom.h +++ b/src/core/arm/dyncom/arm_dyncom.h @@ -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; diff --git a/src/core/core.cpp b/src/core/core.cpp index cabab744a..a3834adae 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -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() { - g_sys_core = std::make_unique(USER32MODE); - g_app_core = std::make_unique(USER32MODE); + if (Settings::values.use_cpu_jit) { + g_sys_core = std::make_unique(USER32MODE); + g_app_core = std::make_unique(USER32MODE); + } else { + g_sys_core = std::make_unique(USER32MODE); + g_app_core = std::make_unique(USER32MODE); + } LOG_DEBUG(Core, "Initialized OK"); } diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 9dea995f4..f1e5cf3cb 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -441,6 +441,22 @@ std::tuple GetFreeThreadLocalSlot(std::vector>& 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> 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> 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; diff --git a/src/core/settings.h b/src/core/settings.h index 55bb7442e..f86389678 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -122,6 +122,7 @@ struct Values { float pad_circle_modifier_scale; // Core + bool use_cpu_jit; int frame_skip; // Data Storage