From e34c66d62b553ee5475e3a046ce6a196abbcf556 Mon Sep 17 00:00:00 2001 From: MerryMage Date: Sun, 20 Mar 2016 02:50:08 +0000 Subject: [PATCH] JitX64: Implement register allocator --- src/core/CMakeLists.txt | 12 ++ src/core/arm/jit_x64/common.h | 39 ++++ src/core/arm/jit_x64/reg_alloc.cpp | 335 +++++++++++++++++++++++++++++ src/core/arm/jit_x64/reg_alloc.h | 102 +++++++++ 4 files changed, 488 insertions(+) create mode 100644 src/core/arm/jit_x64/common.h create mode 100644 src/core/arm/jit_x64/reg_alloc.cpp create mode 100644 src/core/arm/jit_x64/reg_alloc.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 323575215..938524add 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -264,6 +264,18 @@ set(HEADERS system.h ) +if(ARCHITECTURE_x86_64) + set(SRCS ${SRCS} + arm/jit_x64/reg_alloc.cpp + ) + + set(HEADERS ${HEADERS} + arm/jit_x64/common.h + arm/jit_x64/reg_alloc.h + ) +endif() + + create_directory_groups(${SRCS} ${HEADERS}) add_library(core STATIC ${SRCS} ${HEADERS}) diff --git a/src/core/arm/jit_x64/common.h b/src/core/arm/jit_x64/common.h new file mode 100644 index 000000000..ed9a4250d --- /dev/null +++ b/src/core/arm/jit_x64/common.h @@ -0,0 +1,39 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +#include "core/arm/decoder/decoder.h" +#include "core/arm/skyeye_common/armstate.h" + +namespace JitX64 { + +using ArmReg = ArmDecoder::Register; +using ArmImm5 = ArmDecoder::Imm5; +using ArmImm8 = ArmDecoder::Imm8; +using ArmImm11 = ArmDecoder::Imm11; +using ArmImm24 = ArmDecoder::Imm24; +using Cond = ArmDecoder::Cond; +using ShiftType = ArmDecoder::ShiftType; + +struct JitState { + JitState() : cpu_state(PrivilegeMode::USER32MODE) {} + void Reset() { + cpu_state.Reset(); + } + + ARMul_State cpu_state; + + void* bb; + + u64 save_host_RSP; + u64 return_RIP; + + void* page_table; + s32 cycles_remaining; +}; + +} diff --git a/src/core/arm/jit_x64/reg_alloc.cpp b/src/core/arm/jit_x64/reg_alloc.cpp new file mode 100644 index 000000000..1e7600afc --- /dev/null +++ b/src/core/arm/jit_x64/reg_alloc.cpp @@ -0,0 +1,335 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include + +#include "common/assert.h" + +#include "core/arm/jit_x64/reg_alloc.h" + +namespace JitX64 { + +static const std::map x64_reg_to_index = { + { Gen::RAX, 0 }, + { Gen::RBX, 1 }, + { Gen::RCX, 2 }, + { Gen::RDX, 3 }, + { Gen::RBP, 4 }, + { Gen::RSI, 5 }, + { Gen::RDI, 6 }, + { Gen::RSP, 7 }, + { Gen::R8, 8 }, + { Gen::R9, 9 }, + { Gen::R10, 10 }, + { Gen::R11, 11 }, + { Gen::R12, 12 }, + { Gen::R13, 13 }, + { Gen::R14, 14 }, +}; + +constexpr Gen::X64Reg jit_state_reg = Gen::R15; + +Gen::X64Reg RegAlloc::JitStateReg() { + return jit_state_reg; +} + +static Gen::OpArg MJitStateCpuReg(ArmReg arm_reg) { + // The below pointer arithmetic assumes the following: + static_assert(std::is_same::value, "JitState::cpu_state must be ARMul_State"); + static_assert(std::is_same>::value, "ARMul_State::Reg must be std::array"); + + ASSERT(arm_reg >= 0 && arm_reg <= 14); + + return Gen::MDisp(jit_state_reg, offsetof(JitState, cpu_state) + offsetof(ARMul_State, Reg) + (arm_reg) * sizeof(u32)); +} + +static Gen::OpArg MJitStateMemoryMap() { + return Gen::MDisp(jit_state_reg, offsetof(JitState, page_table)); +} + +void RegAlloc::Init(Gen::XEmitter* emitter) { + code = emitter; + + for (size_t i = 0; i < arm_gpr.size(); i++) { + arm_gpr[i].locked = false; + arm_gpr[i].location = MJitStateCpuReg(i); + } + + for (size_t i = 0; i < x64_gpr.size(); i++) { + x64_gpr[i].locked = false; + x64_gpr[i].state = X64State::State::Free; + } +} + +void RegAlloc::FlushX64(Gen::X64Reg x64_reg) { + const size_t x64_index = x64_reg_to_index.at(x64_reg); + X64State& state = x64_gpr[x64_index]; + + ASSERT(!state.locked); + + switch (state.state) { + case X64State::State::Free: + case X64State::State::CleanArmReg: + case X64State::State::MemoryMap: + case X64State::State::Temp: + state.state = X64State::State::Free; + break; + case X64State::State::DirtyArmReg: + FlushArm(state.arm_reg); + break; + default: + UNREACHABLE(); + break; + } + + ASSERT(state.state == X64State::State::Free); + ASSERT(!state.locked); +} + +void RegAlloc::LockX64(Gen::X64Reg x64_reg) { + X64State& x64_state = x64_gpr[x64_reg_to_index.at(x64_reg)]; + + ASSERT(!x64_state.locked); + x64_state.locked = true; +} + +void RegAlloc::UnlockX64(Gen::X64Reg x64_reg) { + X64State& x64_state = x64_gpr[x64_reg_to_index.at(x64_reg)]; + + ASSERT(x64_state.locked); + x64_state.locked = false; +} + +void RegAlloc::FlushArm(ArmReg arm_reg) { + ArmState& arm_state = arm_gpr[arm_reg]; + Gen::X64Reg x64_reg = GetX64For(arm_reg); + X64State& x64_state = x64_gpr[x64_reg_to_index.at(x64_reg)]; + + ASSERT(!arm_state.locked); + ASSERT(!x64_state.locked); + ASSERT(x64_state.state == X64State::State::CleanArmReg || x64_state.state == X64State::State::DirtyArmReg); + ASSERT(x64_state.arm_reg == arm_reg); + + if (x64_state.state == X64State::State::DirtyArmReg) { + code->MOV(32, MJitStateCpuReg(arm_reg), R(x64_reg)); + } + x64_state.state = X64State::State::Free; + arm_state.location = MJitStateCpuReg(arm_reg); +} + +void RegAlloc::LockArm(ArmReg arm_reg) { + ArmState& arm_state = arm_gpr[arm_reg]; + + ASSERT(!arm_state.locked); + arm_state.locked = true; + + if (arm_state.location.IsSimpleReg()) { + Gen::X64Reg x64_reg = arm_state.location.GetSimpleReg(); + X64State& x64_state = x64_gpr[x64_reg_to_index.at(x64_reg)]; + ASSERT(!x64_state.locked); + ASSERT(x64_state.state == X64State::State::CleanArmReg || x64_state.state == X64State::State::DirtyArmReg); + ASSERT(x64_state.arm_reg == arm_reg); + + x64_state.locked = true; + } +} + +Gen::X64Reg RegAlloc::BindMaybeLoadAndLockArm(ArmReg arm_reg, bool load) { + ArmState& arm_state = arm_gpr[arm_reg]; + + ASSERT(!arm_state.locked); + arm_state.locked = true; + + if (arm_state.location.IsSimpleReg()) { + const Gen::X64Reg x64_reg = arm_state.location.GetSimpleReg(); + X64State& x64_state = x64_gpr[x64_reg_to_index.at(x64_reg)]; + ASSERT(!x64_state.locked); + ASSERT(x64_state.state == X64State::State::CleanArmReg || x64_state.state == X64State::State::DirtyArmReg); + ASSERT(x64_state.arm_reg == arm_reg); + + x64_state.locked = true; + return x64_reg; + } + + const Gen::X64Reg x64_reg = AllocReg(); + X64State& x64_state = x64_gpr[x64_reg_to_index.at(x64_reg)]; + x64_state.locked = true; + x64_state.arm_reg = arm_reg; + x64_state.state = X64State::State::CleanArmReg; + + if (load) + code->MOV(32, R(x64_reg), MJitStateCpuReg(arm_reg)); + + arm_state.location = R(x64_reg); + + return x64_reg; +} + +Gen::X64Reg RegAlloc::BindAndLockArm(ArmReg arm_reg) { + return BindMaybeLoadAndLockArm(arm_reg, true); +} + +Gen::X64Reg RegAlloc::BindNoLoadAndLockArm(ArmReg arm_reg) { + return BindMaybeLoadAndLockArm(arm_reg, false); +} + +void RegAlloc::UnlockArm(ArmReg arm_reg) { + ArmState& arm_state = arm_gpr[arm_reg]; + + ASSERT(arm_state.locked); + arm_state.locked = false; + + if (arm_state.location.IsSimpleReg()) { + const Gen::X64Reg x64_reg = arm_state.location.GetSimpleReg(); + X64State& x64_state = x64_gpr[x64_reg_to_index.at(x64_reg)]; + ASSERT(x64_state.locked); + ASSERT(x64_state.state == X64State::State::CleanArmReg || x64_state.state == X64State::State::DirtyArmReg); + ASSERT(x64_state.arm_reg == arm_reg); + + x64_state.locked = false; + } +} + +void RegAlloc::FlushAllArm() { + for (ArmReg arm_reg = 0; arm_reg < arm_gpr.size(); arm_reg++) { + ArmState& arm_state = arm_gpr[arm_reg]; + ASSERT(!arm_state.locked); + if (arm_state.location.IsSimpleReg()) { + FlushArm(arm_reg); + } + } +} + +void RegAlloc::MarkDirty(ArmReg arm_reg) { + const ArmState& arm_state = arm_gpr[arm_reg]; + + ASSERT(arm_state.locked); + ASSERT(arm_state.location.IsSimpleReg()); + + const Gen::X64Reg x64_reg = arm_state.location.GetSimpleReg(); + X64State& x64_state = x64_gpr[x64_reg_to_index.at(x64_reg)]; + + ASSERT(x64_state.locked); + ASSERT(x64_state.state == X64State::State::CleanArmReg || x64_state.state == X64State::State::DirtyArmReg); + ASSERT(x64_state.arm_reg == arm_reg); + + x64_state.state = X64State::State::DirtyArmReg; +} + +void RegAlloc::FlushEverything() { + for (auto i : x64_reg_to_index) { + X64State& x64_state = x64_gpr[i.second]; + ASSERT(!x64_state.locked); + FlushX64(i.first); + ASSERT(x64_state.state == X64State::State::Free); + } +} + +Gen::X64Reg RegAlloc::GetX64For(ArmReg arm_reg) { + const ArmState& arm_state = arm_gpr[arm_reg]; + + ASSERT(arm_state.location.IsSimpleReg()); + + const Gen::X64Reg x64_reg = arm_state.location.GetSimpleReg(); + const X64State& x64_state = x64_gpr[x64_reg_to_index.at(x64_reg)]; + + ASSERT(x64_state.state == X64State::State::CleanArmReg || x64_state.state == X64State::State::DirtyArmReg); + ASSERT(x64_state.arm_reg == arm_reg); + + return x64_reg; +} + +Gen::OpArg RegAlloc::ArmR(ArmReg arm_reg) { + const ArmState& arm_state = arm_gpr[arm_reg]; + + ASSERT(arm_state.locked); + + return arm_state.location; +} + +Gen::X64Reg RegAlloc::AllocAndLockTemp() { + const Gen::X64Reg x64_reg = AllocReg(); + X64State& x64_state = x64_gpr[x64_reg_to_index.at(x64_reg)]; + x64_state.locked = true; + x64_state.state = X64State::State::Temp; + + return x64_reg; +} + +void RegAlloc::UnlockTemp(Gen::X64Reg x64_reg) { + X64State& x64_state = x64_gpr[x64_reg_to_index.at(x64_reg)]; + + ASSERT(x64_state.locked); + ASSERT(x64_state.state == X64State::State::Temp); + + x64_state.locked = false; + x64_state.state = X64State::State::Free; +} + +Gen::X64Reg RegAlloc::BindAndLockMemoryMap() { + // First check to see if it exists. + for (auto i : x64_reg_to_index) { + X64State& x64_state = x64_gpr[i.second]; + + if (x64_state.state == X64State::State::MemoryMap) { + ASSERT(!x64_state.locked); + x64_state.locked = true; + return i.first; + } + } + + // Otherwise allocate it. + const Gen::X64Reg x64_reg = AllocReg(); + X64State& x64_state = x64_gpr[x64_reg_to_index.at(x64_reg)]; + x64_state.locked = true; + x64_state.state = X64State::State::MemoryMap; + + code->MOV(64, R(x64_reg), MJitStateMemoryMap()); + + return x64_reg; +} + +void RegAlloc::UnlockMemoryMap(Gen::X64Reg x64_reg) { + X64State& x64_state = x64_gpr[x64_reg_to_index.at(x64_reg)]; + + ASSERT(x64_state.locked); + ASSERT(x64_state.state == X64State::State::MemoryMap); + + x64_state.locked = false; +} + +Gen::X64Reg RegAlloc::AllocReg() { + // TODO: This is terrible. + + // First check to see if there anything free. + for (auto i : x64_reg_to_index) { + X64State& x64_state = x64_gpr[i.second]; + + if (x64_state.locked) + continue; + + ASSERT(x64_state.state != X64State::State::Temp); // This can never happen. + + if (x64_state.state == X64State::State::Free) { + return i.first; + } + } + + // Otherwise flush something. + for (auto i : x64_reg_to_index) { + X64State& x64_state = x64_gpr[i.second]; + + if (x64_state.locked) + continue; + + FlushX64(i.first); + return i.first; + } + + ASSERT_MSG(false, "Ran out of x64 registers"); + UNREACHABLE(); +} + +} diff --git a/src/core/arm/jit_x64/reg_alloc.h b/src/core/arm/jit_x64/reg_alloc.h new file mode 100644 index 000000000..c6805890d --- /dev/null +++ b/src/core/arm/jit_x64/reg_alloc.h @@ -0,0 +1,102 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "common/common_types.h" +#include "common/common_funcs.h" +#include "common/x64/emitter.h" + +#include "core/arm/decoder/decoder.h" +#include "core/arm/jit_x64/common.h" + +namespace JitX64 { + +// This is the register allocator +// TODO: Better algorithm required, ideally something that does reigster usage lookahead. +// (Designed so it should be simple to implement later.) + +class RegAlloc final { +private: + struct ArmState { + Gen::OpArg location; + bool locked; + }; + + struct X64State { + enum class State { + Free, + Temp, + DirtyArmReg, + CleanArmReg, + MemoryMap + }; + + bool locked; + State state; + ArmReg arm_reg; + }; + + std::array arm_gpr; + std::array x64_gpr; + + Gen::XEmitter* code = nullptr; + +public: + /// Initialise register allocator (call compiling a basic block as it resets internal state) + void Init(Gen::XEmitter* emitter); + + /// Ensures that the state of that register is State::Free. + void FlushX64(Gen::X64Reg x64_reg); + /// Locks a register: Marks it as in-use so it isn't allocated. + void LockX64(Gen::X64Reg x64_reg); + /// Unlocks a register: Allows it to be used for allocation again. + void UnlockX64(Gen::X64Reg x64_reg); + + /// Ensures that this ARM register is not in an x64 register. + void FlushArm(ArmReg arm_reg); + /// Locks an ARM register so it doesn't move. + void LockArm(ArmReg arm_reg); + /// Allocates a x64 register for an ARM register and loads its value into it if necessary. + Gen::X64Reg BindAndLockArm(ArmReg arm_reg); + /// Allocates a x64 register for an ARM register but does not load a value. + Gen::X64Reg BindNoLoadAndLockArm(ArmReg arm_reg); + /// Unlock ARM register so the register is free to move and the underlying x64 register is available (if any). + void UnlockArm(ArmReg arm_reg); + /// Flush all ARM registers. + void FlushAllArm(); + /// Marks an ARM register as dirty. + void MarkDirty(ArmReg arm_reg); + + /// Flush absolutely everything. + void FlushEverything(); + + /// Gets the x64 register which corresponds to that ARM register. (ASSERTS IF NOT IN A x64 REG OR NOT LOCKED!) + Gen::X64Reg GetX64For(ArmReg arm_reg); + /// Gets the current location of this ARM register. (ASSERTS IF NOT LOCKED!) + Gen::OpArg ArmR(ArmReg arm_reg); + + /// Allocates a temporary register + Gen::X64Reg AllocAndLockTemp(); + /// Releases a temporary register + void UnlockTemp(Gen::X64Reg x64_reg); + + /// Gets the x64 register with the address of the memory map in it. Allocates one if one doesn't already exist. + Gen::X64Reg BindAndLockMemoryMap(); + /// Releases the memory map register. + void UnlockMemoryMap(Gen::X64Reg x64_reg); + + /// Returns the register in which the JitState pointer is stored. + Gen::X64Reg JitStateReg(); + +private: + /// INTERNAL: Allocates a register that is free. Flushes registers that are not locked if necessary. + Gen::X64Reg AllocReg(); + /// INTERNAL: Implementation of BindNoLoadAndLockArm and BindAndLockArm + Gen::X64Reg BindMaybeLoadAndLockArm(ArmReg arm_reg, bool load); +}; + +}