JitX64: Implement register allocator
This commit is contained in:
		| @@ -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}) | ||||
|   | ||||
							
								
								
									
										39
									
								
								src/core/arm/jit_x64/common.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/core/arm/jit_x64/common.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||
| }; | ||||
|  | ||||
| } | ||||
							
								
								
									
										335
									
								
								src/core/arm/jit_x64/reg_alloc.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										335
									
								
								src/core/arm/jit_x64/reg_alloc.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,335 @@ | ||||
| // Copyright 2016 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include <cstddef> | ||||
| #include <map> | ||||
|  | ||||
| #include "common/assert.h" | ||||
|  | ||||
| #include "core/arm/jit_x64/reg_alloc.h" | ||||
|  | ||||
| namespace JitX64 { | ||||
|  | ||||
| static const std::map<Gen::X64Reg, size_t> 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<decltype(JitState::cpu_state), ARMul_State>::value, "JitState::cpu_state must be ARMul_State"); | ||||
|     static_assert(std::is_same<decltype(ARMul_State::Reg), std::array<u32, 16>>::value, "ARMul_State::Reg must be std::array<u32, 16>"); | ||||
|  | ||||
|     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(); | ||||
| } | ||||
|  | ||||
| } | ||||
							
								
								
									
										102
									
								
								src/core/arm/jit_x64/reg_alloc.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								src/core/arm/jit_x64/reg_alloc.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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 <array> | ||||
|  | ||||
| #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<ArmState, 15> arm_gpr; | ||||
|     std::array<X64State, 16> 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); | ||||
| }; | ||||
|  | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 MerryMage
					MerryMage