diff --git a/boost.natvis b/boost.natvis new file mode 100644 index 00000000..2781a585 --- /dev/null +++ b/boost.natvis @@ -0,0 +1,26 @@ + + + + + + m_holder.m_size + + m_holder.m_size + m_holder.m_start + + + + + + {{ size={m_holder.m_size} }} + + m_holder.m_size + static_capacity + + m_holder.m_size + ($T1*)m_holder.storage.data + + + + + diff --git a/dependencies/vcpkg b/dependencies/vcpkg index a4275b7e..533a5fda 160000 --- a/dependencies/vcpkg +++ b/dependencies/vcpkg @@ -1 +1 @@ -Subproject commit a4275b7eee79fb24ec2e135481ef5fce8b41c339 +Subproject commit 533a5fda5c0646d1771345fb572e759283444d5f diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index c8e563b2..f777b5ac 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -83,6 +83,7 @@ add_library(CemuCafe HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp HW/Espresso/Recompiler/PPCRecompilerIml.h HW/Espresso/Recompiler/PPCRecompilerIntermediate.cpp + HW/Espresso/Recompiler/RecompilerTests.cpp HW/Latte/Common/RegisterSerializer.cpp HW/Latte/Common/RegisterSerializer.h HW/Latte/Common/ShaderSerializer.cpp diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterFPU.cpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterFPU.cpp index aed571d7..2c99b84c 100644 --- a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterFPU.cpp +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterFPU.cpp @@ -32,7 +32,7 @@ espresso_frsqrte_entry_t frsqrteLookupTable[32] = {0x20c1000, 0x35e},{0x1f12000, 0x332},{0x1d79000, 0x30a},{0x1bf4000, 0x2e6}, }; -double frsqrte_espresso(double input) +ATTR_MS_ABI double frsqrte_espresso(double input) { unsigned long long x = *(unsigned long long*)&input; @@ -111,7 +111,7 @@ espresso_fres_entry_t fresLookupTable[32] = {0x88400, 0x11a}, {0x65000, 0x11a}, {0x41c00, 0x108}, {0x20c00, 0x106} }; -double fres_espresso(double input) +ATTR_MS_ABI double fres_espresso(double input) { // based on testing we know that fres uses only the first 15 bits of the mantissa // seee eeee eeee mmmm mmmm mmmm mmmx xxxx .... (s = sign, e = exponent, m = mantissa, x = not used) diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h index bac253c4..896fd21c 100644 --- a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h @@ -191,8 +191,8 @@ inline double roundTo25BitAccuracy(double d) return *(double*)&v; } -double fres_espresso(double input); -double frsqrte_espresso(double input); +ATTR_MS_ABI double fres_espresso(double input); +ATTR_MS_ABI double frsqrte_espresso(double input); void fcmpu_espresso(PPCInterpreter_t* hCPU, int crfD, double a, double b); diff --git a/src/Cafe/HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.cpp b/src/Cafe/HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.cpp index d6a7c79e..d946f629 100644 --- a/src/Cafe/HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.cpp @@ -8,9 +8,8 @@ #include -#include "../PPCRecompiler.h" #include "asm/x64util.h" -#include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include "../PPCRecompiler.h" #include "Common/precompiled.h" #include "Common/cpu_features.h" #include "HW/Espresso/Interpreter/PPCInterpreterInternal.h" @@ -24,39 +23,42 @@ constexpr uint32_t TEMP_GPR_2_ID = 26; constexpr uint32_t PPC_RECOMPILER_INSTANCE_DATA_REG_ID = 27; constexpr uint32_t MEMORY_BASE_REG_ID = 28; constexpr uint32_t HCPU_REG_ID = 29; -constexpr uint32_t TEMP_FPR_1_ID = 28; -constexpr uint32_t TEMP_FPR_2_ID = 29; -constexpr uint32_t TEMP_FPR_3_ID = 30; + +constexpr uint32_t TEMP_FPR_1_ID = 29; +constexpr uint32_t TEMP_FPR_2_ID = 30; +constexpr uint32_t TEMP_FPR_3_ID = 31; constexpr uint32_t ASM_ROUTINE_FPR_ID = 31; + struct FPReg { explicit FPReg(size_t index) - : VReg(index), DReg(index), SReg(index), HReg(index), QReg(index), BReg(index) + : index(index), VReg(index), DReg(index), SReg(index), HReg(index), QReg(index), BReg(index) { } - VReg VReg; - QReg QReg; - DReg DReg; - SReg SReg; - HReg HReg; - BReg BReg; + const size_t index; + const VReg VReg; + const QReg QReg; + const DReg DReg; + const SReg SReg; + const HReg HReg; + const BReg BReg; }; struct GPReg { explicit GPReg(size_t index) - : XReg(index), WReg(index) + : index(index), XReg(index), WReg(index) { } - XReg XReg; - WReg WReg; + const size_t index; + const XReg XReg; + const WReg WReg; }; constexpr uint64_t DOUBLE_1_0 = std::bit_cast(1.0); static const XReg HCPU_REG{HCPU_REG_ID}, PPC_REC_INSTANCE_REG{PPC_RECOMPILER_INSTANCE_DATA_REG_ID}, MEM_BASE_REG{MEMORY_BASE_REG_ID}; static const GPReg TEMP_GPR1{TEMP_GPR_1_ID}; static const GPReg TEMP_GPR2{TEMP_GPR_2_ID}; -static const WReg LR_WREG{TEMP_GPR_2_ID}; -static const XReg LR_XREG{TEMP_GPR_2_ID}; +static const GPReg LR{TEMP_GPR_2_ID}; static const FPReg TEMP_FPR1{TEMP_FPR_1_ID}; static const FPReg TEMP_FPR2{TEMP_FPR_2_ID}; @@ -65,6 +67,43 @@ static const FPReg ASM_ROUTINE_FPR{ASM_ROUTINE_FPR_ID}; static const util::Cpu s_cpu; +class AArch64Allocator : public Allocator +{ + private: +#ifdef XBYAK_USE_MMAP_ALLOCATOR + inline static MmapAllocator s_allocator; +#else + inline static Allocator s_allocator; +#endif + Allocator* m_allocatorImpl; + bool m_freeDisabled = false; + + public: + AArch64Allocator() + : m_allocatorImpl(reinterpret_cast(&s_allocator)) {} + + uint32_t* alloc(size_t size) override + { + return m_allocatorImpl->alloc(size); + } + + void setFreeDisabled(bool disabled) + { + m_freeDisabled = disabled; + } + + void free(uint32_t* p) override + { + if (!m_freeDisabled) + m_allocatorImpl->free(p); + } + + [[nodiscard]] bool useProtect() const override + { + return !m_freeDisabled && m_allocatorImpl->useProtect(); + } +}; + struct UnconditionalJumpInfo { IMLSegment* target; @@ -88,10 +127,9 @@ using JumpInfo = std::variant< ConditionalRegJumpInfo, NegativeRegValueJumpInfo>; -struct AArch64GenContext_t : CodeGenerator, CodeContext +struct AArch64GenContext_t : CodeGenerator { - AArch64GenContext_t(); - + explicit AArch64GenContext_t(Allocator* allocator = nullptr); void enterRecompilerCode(); void leaveRecompilerCode(); @@ -109,12 +147,13 @@ struct AArch64GenContext_t : CodeGenerator, CodeContext bool store(IMLInstruction* imlInstruction, bool indexed); void atomic_cmp_store(IMLInstruction* imlInstruction); bool macro(IMLInstruction* imlInstruction); + void call_imm(IMLInstruction* imlInstruction); bool fpr_load(IMLInstruction* imlInstruction, bool indexed); void psq_load(uint8 mode, VReg& dataVReg, WReg& memReg, WReg& indexReg, sint32 memImmS32, bool indexed, const IMLReg& registerGQR = IMLREG_INVALID); void psq_load_generic(uint8 mode, VReg& dataReg, WReg& memReg, WReg& indexReg, sint32 memImmS32, bool indexed, const IMLReg& registerGQR); bool fpr_store(IMLInstruction* imlInstruction, bool indexed); - void psq_store(uint8 mode, IMLRegID dataRegId, WReg& memReg, WReg& indexReg, sint32 memOffset, bool indexed, const IMLReg& registerGQR = IMLREG_INVALID); - void psq_store_generic(uint8 mode, IMLRegID dataRegId, WReg& memReg, WReg& indexReg, sint32 memOffset, bool indexed, const IMLReg& registerGQR); + void psq_store(uint8 mode, const IMLReg& dataReg, WReg& memReg, WReg& indexReg, sint32 memOffset, bool indexed, const IMLReg& registerGQR = IMLREG_INVALID); + void psq_store_generic(uint8 mode, const IMLReg& dataReg, WReg& memReg, WReg& indexReg, sint32 memOffset, bool indexed, const IMLReg& registerGQR); void fpr_r_r(IMLInstruction* imlInstruction); void fpr_r_r_r(IMLInstruction* imlInstruction); void fpr_r_r_r_r(IMLInstruction* imlInstruction); @@ -140,34 +179,41 @@ struct AArch64GenContext_t : CodeGenerator, CodeContext segmentStarts[imlSegment] = getSize(); } - void processAllJumps() + bool processAllJumps() { for (auto&& [jumpStart, jumpInfo] : jumps) { - std::visit( + bool success = std::visit( [&, this](const auto& jump) { setSize(jumpStart); sint64 targetAddress = segmentStarts.at(jump.target); sint64 addressOffset = targetAddress - jumpStart; - handleJump(addressOffset, jump); + return handleJump(addressOffset, jump); }, jumpInfo); + if (!success) + { + return false; + } } + return true; } - void handleJump(sint64 addressOffset, const UnconditionalJumpInfo& jump) + bool handleJump(sint64 addressOffset, const UnconditionalJumpInfo& jump) { // in +/-128MB if (-0x8000000 <= addressOffset && addressOffset <= 0x7ffffff) { b(addressOffset); - return; + return true; } cemu_assert_suspicious(); + + return false; } - void handleJump(sint64 addressOffset, const ConditionalRegJumpInfo& jump) + bool handleJump(sint64 addressOffset, const ConditionalRegJumpInfo& jump) { bool mustBeTrue = jump.mustBeTrue; @@ -178,7 +224,7 @@ struct AArch64GenContext_t : CodeGenerator, CodeContext tbnz(jump.regBool, 0, addressOffset); else tbz(jump.regBool, 0, addressOffset); - return; + return true; } // in +/-1MB @@ -188,7 +234,7 @@ struct AArch64GenContext_t : CodeGenerator, CodeContext cbnz(jump.regBool, addressOffset); else cbz(jump.regBool, addressOffset); - return; + return true; } Label skipJump; @@ -203,19 +249,21 @@ struct AArch64GenContext_t : CodeGenerator, CodeContext { b(addressOffset); L(skipJump); - return; + return true; } cemu_assert_suspicious(); + + return false; } - void handleJump(sint64 addressOffset, const NegativeRegValueJumpInfo& jump) + bool handleJump(sint64 addressOffset, const NegativeRegValueJumpInfo& jump) { // in +/-32KB if (-0x8000 <= addressOffset && addressOffset <= 0x7fff) { tbnz(jump.regValue, 31, addressOffset); - return; + return true; } // in +/-1MB @@ -224,7 +272,7 @@ struct AArch64GenContext_t : CodeGenerator, CodeContext tst(jump.regValue, 0x80000000); addressOffset -= 4; bne(addressOffset); - return; + return true; } Label skipJump; @@ -236,44 +284,39 @@ struct AArch64GenContext_t : CodeGenerator, CodeContext { b(addressOffset); L(skipJump); - return; + return true; } cemu_assert_suspicious(); - } - bool conditional_r_s32([[maybe_unused]] IMLInstruction* imlInstruction) - { - cemu_assert_unimplemented(); return false; } }; template -T fpReg(uint32 index) +T fpReg(const IMLReg& imlReg) { - return T(index - IMLArchAArch64::PHYSREG_FPR_BASE); + return T(imlReg.GetRegID() - IMLArchAArch64::PHYSREG_FPR_BASE); } template -T gpReg(uint32 index) +T gpReg(const IMLReg& imlReg) { - return T(index - IMLArchAArch64::PHYSREG_GPR_BASE); + return T(imlReg.GetRegID() - IMLArchAArch64::PHYSREG_GPR_BASE); } -AArch64GenContext_t::AArch64GenContext_t() - : CodeGenerator(DEFAULT_MAX_CODE_SIZE, AutoGrow) +AArch64GenContext_t::AArch64GenContext_t(Allocator* allocator) + : CodeGenerator(DEFAULT_MAX_CODE_SIZE, AutoGrow, allocator) { } void AArch64GenContext_t::r_name(IMLInstruction* imlInstruction) { uint32 name = imlInstruction->op_r_name.name; - auto regId = imlInstruction->op_r_name.regR.GetRegID(); if (imlInstruction->op_r_name.regR.GetBaseFormat() == IMLRegFormat::I64) { - WReg regR = gpReg(regId); + WReg regR = gpReg(imlInstruction->op_r_name.regR); if (name >= PPCREC_NAME_R0 && name < PPCREC_NAME_R0 + 32) { ldr(regR, AdrImm(HCPU_REG, offsetof(PPCInterpreter_t, gpr) + sizeof(uint32) * (name - PPCREC_NAME_R0))); @@ -323,7 +366,7 @@ void AArch64GenContext_t::r_name(IMLInstruction* imlInstruction) } else if (imlInstruction->op_r_name.regR.GetBaseFormat() == IMLRegFormat::F64) { - QReg regR = fpReg(imlInstruction->op_r_name.regR.GetRegID()); + QReg regR = fpReg(imlInstruction->op_r_name.regR); if (name >= PPCREC_NAME_FPR0 && name < (PPCREC_NAME_FPR0 + 32)) { mov(TEMP_GPR1.XReg, offsetof(PPCInterpreter_t, fpr) + sizeof(FPR_t) * (name - PPCREC_NAME_FPR0)); @@ -348,11 +391,10 @@ void AArch64GenContext_t::r_name(IMLInstruction* imlInstruction) void AArch64GenContext_t::name_r(IMLInstruction* imlInstruction) { uint32 name = imlInstruction->op_r_name.name; - IMLRegID regId = imlInstruction->op_r_name.regR.GetRegID(); if (imlInstruction->op_r_name.regR.GetBaseFormat() == IMLRegFormat::I64) { - auto regR = gpReg(regId); + auto regR = gpReg(imlInstruction->op_r_name.regR); if (name >= PPCREC_NAME_R0 && name < PPCREC_NAME_R0 + 32) { str(regR, AdrImm(HCPU_REG, offsetof(PPCInterpreter_t, gpr) + sizeof(uint32) * (name - PPCREC_NAME_R0))); @@ -402,7 +444,7 @@ void AArch64GenContext_t::name_r(IMLInstruction* imlInstruction) } else if (imlInstruction->op_r_name.regR.GetBaseFormat() == IMLRegFormat::F64) { - QReg regR = fpReg(imlInstruction->op_r_name.regR.GetRegID()); + QReg regR = fpReg(imlInstruction->op_r_name.regR); if (name >= PPCREC_NAME_FPR0 && name < (PPCREC_NAME_FPR0 + 32)) { mov(TEMP_GPR1.XReg, offsetof(PPCInterpreter_t, fpr) + sizeof(FPR_t) * (name - PPCREC_NAME_FPR0)); @@ -426,15 +468,12 @@ void AArch64GenContext_t::name_r(IMLInstruction* imlInstruction) bool AArch64GenContext_t::r_r(IMLInstruction* imlInstruction) { - IMLRegID regRId = imlInstruction->op_r_r.regR.GetRegID(); - IMLRegID regAId = imlInstruction->op_r_r.regA.GetRegID(); - WReg regR = gpReg(regRId); - WReg regA = gpReg(regAId); + WReg regR = gpReg(imlInstruction->op_r_r.regR); + WReg regA = gpReg(imlInstruction->op_r_r.regA); if (imlInstruction->operation == PPCREC_IML_OP_ASSIGN) { - if (regRId != regAId) - mov(regR, regA); + mov(regR, regA); } else if (imlInstruction->operation == PPCREC_IML_OP_ENDIAN_SWAP) { @@ -460,22 +499,6 @@ bool AArch64GenContext_t::r_r(IMLInstruction* imlInstruction) { clz(regR, regA); } - else if (imlInstruction->operation == PPCREC_IML_OP_DCBZ) - { - movi(TEMP_FPR1.VReg.d2, 0); - if (regRId != regAId) - { - add(TEMP_GPR1.WReg, regA, regR); - and_(TEMP_GPR1.WReg, TEMP_GPR1.WReg, ~0x1f); - } - else - { - and_(TEMP_GPR1.WReg, regA, ~0x1f); - } - add(TEMP_GPR1.XReg, MEM_BASE_REG, TEMP_GPR1.XReg); - stp(TEMP_FPR1.QReg, TEMP_FPR1.QReg, AdrNoOfs(TEMP_GPR1.XReg)); - return true; - } else { cemuLog_log(LogType::Recompiler, "PPCRecompilerAArch64Gen_imlInstruction_r_r(): Unsupported operation {:x}", imlInstruction->operation); @@ -487,7 +510,7 @@ bool AArch64GenContext_t::r_r(IMLInstruction* imlInstruction) bool AArch64GenContext_t::r_s32(IMLInstruction* imlInstruction) { sint32 imm32 = imlInstruction->op_r_immS32.immS32; - WReg reg = gpReg(imlInstruction->op_r_immS32.regR.GetRegID()); + WReg reg = gpReg(imlInstruction->op_r_immS32.regR); if (imlInstruction->operation == PPCREC_IML_OP_ASSIGN) { @@ -507,8 +530,8 @@ bool AArch64GenContext_t::r_s32(IMLInstruction* imlInstruction) bool AArch64GenContext_t::r_r_s32(IMLInstruction* imlInstruction) { - WReg regR = gpReg(imlInstruction->op_r_r_s32.regR.GetRegID()); - WReg regA = gpReg(imlInstruction->op_r_r_s32.regA.GetRegID()); + WReg regR = gpReg(imlInstruction->op_r_r_s32.regR); + WReg regA = gpReg(imlInstruction->op_r_r_s32.regA); sint32 immS32 = imlInstruction->op_r_r_s32.immS32; if (imlInstruction->operation == PPCREC_IML_OP_ADD) @@ -534,25 +557,6 @@ bool AArch64GenContext_t::r_r_s32(IMLInstruction* imlInstruction) mov(TEMP_GPR1.WReg, immS32); eor(regR, regA, TEMP_GPR1.WReg); } - else if (imlInstruction->operation == PPCREC_IML_OP_RLWIMI) - { - uint32 vImm = (uint32)immS32; - uint32 mb = (vImm >> 0) & 0xFF; - uint32 me = (vImm >> 8) & 0xFF; - uint32 sh = (vImm >> 16) & 0xFF; - uint32 mask = ppc_mask(mb, me); - if (sh) - { - ror(TEMP_GPR1.WReg, regA, 32 - (sh & 0x1F)); - and_(TEMP_GPR1.WReg, TEMP_GPR1.WReg, mask); - } - else - { - and_(TEMP_GPR1.WReg, regA, mask); - } - and_(regR, regR, ~mask); - orr(regR, regR, TEMP_GPR1.WReg); - } else if (imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_SIGNED) { mov(TEMP_GPR1.WReg, immS32); @@ -581,9 +585,9 @@ bool AArch64GenContext_t::r_r_s32(IMLInstruction* imlInstruction) bool AArch64GenContext_t::r_r_s32_carry(IMLInstruction* imlInstruction) { - WReg regR = gpReg(imlInstruction->op_r_r_s32_carry.regR.GetRegID()); - WReg regA = gpReg(imlInstruction->op_r_r_s32_carry.regA.GetRegID()); - WReg regCarry = gpReg(imlInstruction->op_r_r_s32_carry.regCarry.GetRegID()); + WReg regR = gpReg(imlInstruction->op_r_r_s32_carry.regR); + WReg regA = gpReg(imlInstruction->op_r_r_s32_carry.regA); + WReg regCarry = gpReg(imlInstruction->op_r_r_s32_carry.regCarry); sint32 immS32 = imlInstruction->op_r_r_s32_carry.immS32; if (imlInstruction->operation == PPCREC_IML_OP_ADD) @@ -609,10 +613,10 @@ bool AArch64GenContext_t::r_r_s32_carry(IMLInstruction* imlInstruction) bool AArch64GenContext_t::r_r_r(IMLInstruction* imlInstruction) { - WReg regResult = gpReg(imlInstruction->op_r_r_r.regR.GetRegID()); - XReg reg64Result = gpReg(imlInstruction->op_r_r_r.regR.GetRegID()); - WReg regOperand1 = gpReg(imlInstruction->op_r_r_r.regA.GetRegID()); - WReg regOperand2 = gpReg(imlInstruction->op_r_r_r.regB.GetRegID()); + WReg regResult = gpReg(imlInstruction->op_r_r_r.regR); + XReg reg64Result = gpReg(imlInstruction->op_r_r_r.regR); + WReg regOperand1 = gpReg(imlInstruction->op_r_r_r.regA); + WReg regOperand2 = gpReg(imlInstruction->op_r_r_r.regB); if (imlInstruction->operation == PPCREC_IML_OP_ADD) { @@ -695,10 +699,10 @@ bool AArch64GenContext_t::r_r_r(IMLInstruction* imlInstruction) bool AArch64GenContext_t::r_r_r_carry(IMLInstruction* imlInstruction) { - WReg regR = gpReg(imlInstruction->op_r_r_r_carry.regR.GetRegID()); - WReg regA = gpReg(imlInstruction->op_r_r_r_carry.regA.GetRegID()); - WReg regB = gpReg(imlInstruction->op_r_r_r_carry.regB.GetRegID()); - WReg regCarry = gpReg(imlInstruction->op_r_r_r_carry.regCarry.GetRegID()); + WReg regR = gpReg(imlInstruction->op_r_r_r_carry.regR); + WReg regA = gpReg(imlInstruction->op_r_r_r_carry.regA); + WReg regB = gpReg(imlInstruction->op_r_r_r_carry.regB); + WReg regCarry = gpReg(imlInstruction->op_r_r_r_carry.regCarry); if (imlInstruction->operation == PPCREC_IML_OP_ADD) { @@ -746,9 +750,9 @@ Cond ImlCondToArm64Cond(IMLCondition condition) void AArch64GenContext_t::compare(IMLInstruction* imlInstruction) { - WReg regR = gpReg(imlInstruction->op_compare.regR.GetRegID()); - WReg regA = gpReg(imlInstruction->op_compare.regA.GetRegID()); - WReg regB = gpReg(imlInstruction->op_compare.regB.GetRegID()); + WReg regR = gpReg(imlInstruction->op_compare.regR); + WReg regA = gpReg(imlInstruction->op_compare.regA); + WReg regB = gpReg(imlInstruction->op_compare.regB); Cond cond = ImlCondToArm64Cond(imlInstruction->op_compare.cond); cmp(regA, regB); cset(regR, cond); @@ -756,8 +760,8 @@ void AArch64GenContext_t::compare(IMLInstruction* imlInstruction) void AArch64GenContext_t::compare_s32(IMLInstruction* imlInstruction) { - WReg regR = gpReg(imlInstruction->op_compare.regR.GetRegID()); - WReg regA = gpReg(imlInstruction->op_compare.regA.GetRegID()); + WReg regR = gpReg(imlInstruction->op_compare.regR); + WReg regA = gpReg(imlInstruction->op_compare.regA); sint32 imm = imlInstruction->op_compare_s32.immS32; auto cond = ImlCondToArm64Cond(imlInstruction->op_compare.cond); cmp_imm(regA, imm, TEMP_GPR1.WReg); @@ -766,7 +770,7 @@ void AArch64GenContext_t::compare_s32(IMLInstruction* imlInstruction) void AArch64GenContext_t::cjump(IMLInstruction* imlInstruction, IMLSegment* imlSegment) { - auto regBool = gpReg(imlInstruction->op_conditional_jump.registerBool.GetRegID()); + auto regBool = gpReg(imlInstruction->op_conditional_jump.registerBool); prepareJump(ConditionalRegJumpInfo{ .target = imlSegment->nextSegmentBranchTaken, .regBool = regBool, @@ -787,18 +791,6 @@ void AArch64GenContext_t::conditionalJumpCycleCheck(IMLSegment* imlSegment) }); } -void ATTR_MS_ABI PPCRecompiler_getTBL(PPCInterpreter_t* ppcInterpreter, uint32 gprIndex) -{ - uint64 coreTime = coreinit::OSGetSystemTime(); - ppcInterpreter->gpr[gprIndex] = (uint32)(coreTime & 0xFFFFFFFF); -} - -void ATTR_MS_ABI PPCRecompiler_getTBU(PPCInterpreter_t* ppcInterpreter, uint32 gprIndex) -{ - uint64 coreTime = coreinit::OSGetSystemTime(); - ppcInterpreter->gpr[gprIndex] = (uint32)((coreTime >> 32) & 0xFFFFFFFF); -} - void* ATTR_MS_ABI PPCRecompiler_virtualHLE(PPCInterpreter_t* ppcInterpreter, uint32 hleFuncId) { void* prevRSPTemp = ppcInterpreter->rspTemp; @@ -823,12 +815,12 @@ bool AArch64GenContext_t::macro(IMLInstruction* imlInstruction) { if (imlInstruction->operation == PPCREC_IML_MACRO_B_TO_REG) { - XReg branchDstReg = gpReg(imlInstruction->op_macro.paramReg.GetRegID()); + XReg branchDstReg = gpReg(imlInstruction->op_macro.paramReg); mov(TEMP_GPR1.XReg, offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); add(TEMP_GPR1.XReg, TEMP_GPR1.XReg, branchDstReg, ShMod::LSL, 1); ldr(TEMP_GPR1.XReg, AdrReg(PPC_REC_INSTANCE_REG, TEMP_GPR1.XReg)); - mov(LR_XREG, branchDstReg); + mov(LR.XReg, branchDstReg); br(TEMP_GPR1.XReg); return true; } @@ -843,7 +835,7 @@ bool AArch64GenContext_t::macro(IMLInstruction* imlInstruction) uint64 lookupOffset = (uint64)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable) + (uint64)newIP * 2ULL; mov(TEMP_GPR1.XReg, lookupOffset); ldr(TEMP_GPR1.XReg, AdrReg(PPC_REC_INSTANCE_REG, TEMP_GPR1.XReg)); - mov(LR_WREG, newIP); + mov(LR.WReg, newIP); br(TEMP_GPR1.XReg); return true; } @@ -853,7 +845,7 @@ bool AArch64GenContext_t::macro(IMLInstruction* imlInstruction) uint64 lookupOffset = (uint64)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable) + (uint64)newIP * 2ULL; mov(TEMP_GPR1.XReg, lookupOffset); ldr(TEMP_GPR1.XReg, AdrReg(PPC_REC_INSTANCE_REG, TEMP_GPR1.XReg)); - mov(LR_WREG, newIP); + mov(LR.WReg, newIP); br(TEMP_GPR1.XReg); return true; } @@ -862,12 +854,13 @@ bool AArch64GenContext_t::macro(IMLInstruction* imlInstruction) uint32 currentInstructionAddress = imlInstruction->op_macro.param; mov(TEMP_GPR1.XReg, (uint64)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); // newIP = 0 special value for recompiler exit ldr(TEMP_GPR1.XReg, AdrReg(PPC_REC_INSTANCE_REG, TEMP_GPR1.XReg)); - mov(LR_WREG, currentInstructionAddress); + mov(LR.WReg, currentInstructionAddress); br(TEMP_GPR1.XReg); return true; } else if (imlInstruction->operation == PPCREC_IML_MACRO_DEBUGBREAK) { + brk(0xf000); return true; } else if (imlInstruction->operation == PPCREC_IML_MACRO_COUNT_CYCLES) @@ -908,50 +901,21 @@ bool AArch64GenContext_t::macro(IMLInstruction* imlInstruction) mov(TEMP_GPR1.XReg, offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); ldr(TEMP_GPR1.XReg, AdrReg(PPC_REC_INSTANCE_REG, TEMP_GPR1.XReg)); - ldr(LR_WREG, AdrImm(HCPU_REG, offsetof(PPCInterpreter_t, instructionPointer))); - // JMP [recompilerCallTable+EAX/4*8] + ldr(LR.WReg, AdrImm(HCPU_REG, offsetof(PPCInterpreter_t, instructionPointer))); + // branch to recompiler exit br(TEMP_GPR1.XReg); L(cyclesLeftLabel); // check if instruction pointer was changed - // assign new instruction pointer to EAX - ldr(LR_WREG, AdrImm(HCPU_REG, offsetof(PPCInterpreter_t, instructionPointer))); + // assign new instruction pointer to LR.WReg + ldr(LR.WReg, AdrImm(HCPU_REG, offsetof(PPCInterpreter_t, instructionPointer))); mov(TEMP_GPR1.XReg, offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); - // remember instruction pointer in REG_EDX - // EAX *= 2 - add(TEMP_GPR1.XReg, TEMP_GPR1.XReg, LR_XREG, ShMod::LSL, 1); - // ADD RAX, R15 (R15 -> Pointer to ppcRecompilerInstanceData + add(TEMP_GPR1.XReg, TEMP_GPR1.XReg, LR.XReg, ShMod::LSL, 1); ldr(TEMP_GPR1.XReg, AdrReg(PPC_REC_INSTANCE_REG, TEMP_GPR1.XReg)); - // JMP [ppcRecompilerDirectJumpTable+RAX/4*8] + // branch to [ppcRecompilerDirectJumpTable + PPCInterpreter_t::instructionPointer * 2] br(TEMP_GPR1.XReg); return true; } - else if (imlInstruction->operation == PPCREC_IML_MACRO_MFTB) - { - uint32 ppcAddress = imlInstruction->op_macro.param; - uint32 sprId = imlInstruction->op_macro.param2 & 0xFFFF; - uint32 gprIndex = (imlInstruction->op_macro.param2 >> 16) & 0x1F; - - // update instruction pointer - mov(TEMP_GPR1.WReg, ppcAddress); - str(TEMP_GPR1.WReg, AdrImm(HCPU_REG, offsetof(PPCInterpreter_t, instructionPointer))); - // set parameters - - mov(x0, HCPU_REG); - mov(x1, gprIndex); - // call function - if (sprId == SPR_TBL) - mov(TEMP_GPR1.XReg, (uint64)PPCRecompiler_getTBL); - else if (sprId == SPR_TBU) - mov(TEMP_GPR1.XReg, (uint64)PPCRecompiler_getTBU); - else - cemu_assert_suspicious(); - - str(x30, AdrPreImm(sp, -16)); - blr(TEMP_GPR1.XReg); - ldr(x30, AdrPostImm(sp, 16)); - return true; - } else { cemuLog_log(LogType::Recompiler, "Unknown recompiler macro operation %d\n", imlInstruction->operation); @@ -970,12 +934,12 @@ bool AArch64GenContext_t::load(IMLInstruction* imlInstruction, bool indexed) sint32 memOffset = imlInstruction->op_storeLoad.immS32; bool signExtend = imlInstruction->op_storeLoad.flags2.signExtend; bool switchEndian = imlInstruction->op_storeLoad.flags2.swapEndian; - WReg memReg = gpReg(imlInstruction->op_storeLoad.registerMem.GetRegID()); - WReg dataReg = gpReg(imlInstruction->op_storeLoad.registerData.GetRegID()); + WReg memReg = gpReg(imlInstruction->op_storeLoad.registerMem); + WReg dataReg = gpReg(imlInstruction->op_storeLoad.registerData); add_imm(TEMP_GPR1.WReg, memReg, memOffset, TEMP_GPR1.WReg); if (indexed) - add(TEMP_GPR1.WReg, TEMP_GPR1.WReg, gpReg(imlInstruction->op_storeLoad.registerMem2.GetRegID())); + add(TEMP_GPR1.WReg, TEMP_GPR1.WReg, gpReg(imlInstruction->op_storeLoad.registerMem2)); auto adr = AdrExt(MEM_BASE_REG, TEMP_GPR1.WReg, ExtMod::UXTW); if (imlInstruction->op_storeLoad.copyWidth == 32) @@ -1024,14 +988,14 @@ bool AArch64GenContext_t::store(IMLInstruction* imlInstruction, bool indexed) if (indexed) cemu_assert_debug(imlInstruction->op_storeLoad.registerMem2.GetRegFormat() == IMLRegFormat::I32); - WReg dataReg = gpReg(imlInstruction->op_storeLoad.registerData.GetRegID()); - WReg memReg = gpReg(imlInstruction->op_storeLoad.registerMem.GetRegID()); + WReg dataReg = gpReg(imlInstruction->op_storeLoad.registerData); + WReg memReg = gpReg(imlInstruction->op_storeLoad.registerMem); sint32 memOffset = imlInstruction->op_storeLoad.immS32; bool swapEndian = imlInstruction->op_storeLoad.flags2.swapEndian; add_imm(TEMP_GPR1.WReg, memReg, memOffset, TEMP_GPR1.WReg); if (indexed) - add(TEMP_GPR1.WReg, TEMP_GPR1.WReg, gpReg(imlInstruction->op_storeLoad.registerMem2.GetRegID())); + add(TEMP_GPR1.WReg, TEMP_GPR1.WReg, gpReg(imlInstruction->op_storeLoad.registerMem2)); AdrExt adr = AdrExt(MEM_BASE_REG, TEMP_GPR1.WReg, ExtMod::UXTW); if (imlInstruction->op_storeLoad.copyWidth == 32) { @@ -1071,10 +1035,10 @@ bool AArch64GenContext_t::store(IMLInstruction* imlInstruction, bool indexed) void AArch64GenContext_t::atomic_cmp_store(IMLInstruction* imlInstruction) { - WReg outReg = gpReg(imlInstruction->op_atomic_compare_store.regBoolOut.GetRegID()); - WReg eaReg = gpReg(imlInstruction->op_atomic_compare_store.regEA.GetRegID()); - WReg valReg = gpReg(imlInstruction->op_atomic_compare_store.regWriteValue.GetRegID()); - WReg cmpValReg = gpReg(imlInstruction->op_atomic_compare_store.regCompareValue.GetRegID()); + WReg outReg = gpReg(imlInstruction->op_atomic_compare_store.regBoolOut); + WReg eaReg = gpReg(imlInstruction->op_atomic_compare_store.regEA); + WReg valReg = gpReg(imlInstruction->op_atomic_compare_store.regWriteValue); + WReg cmpValReg = gpReg(imlInstruction->op_atomic_compare_store.regCompareValue); if (s_cpu.isAtomicSupported()) { @@ -1086,7 +1050,6 @@ void AArch64GenContext_t::atomic_cmp_store(IMLInstruction* imlInstruction) } else { - Label endCmpStore; Label notEqual; Label storeFailed; @@ -1097,18 +1060,15 @@ void AArch64GenContext_t::atomic_cmp_store(IMLInstruction* imlInstruction) bne(notEqual); stlxr(TEMP_GPR2.WReg, valReg, AdrNoOfs(TEMP_GPR1.XReg)); cbnz(TEMP_GPR2.WReg, storeFailed); - mov(outReg, 1); - b(endCmpStore); L(notEqual); - mov(outReg, 0); - L(endCmpStore); + cset(outReg, Cond::EQ); } } void AArch64GenContext_t::gqr_generateScaleCode(const VReg& resReg, const VReg& dataReg, bool isLoad, bool scalePS1, const IMLReg& registerGQR) { - auto gqrReg = gpReg(registerGQR.GetRegID()); + auto gqrReg = gpReg(registerGQR); // load GQR & extract scale field and multiply by 16 to get array offset lsr(TEMP_GPR1.WReg, gqrReg, (isLoad ? 16 : 0) + 8 - 4); and_(TEMP_GPR1.WReg, TEMP_GPR1.WReg, (0x3F << 4)); @@ -1128,6 +1088,7 @@ void AArch64GenContext_t::gqr_generateScaleCode(const VReg& resReg, const VReg& mov(TEMP_GPR2.XReg, offsetof(PPCRecompilerInstanceData_t, _psq_st_scale_ps0_1)); } add(TEMP_GPR1.XReg, TEMP_GPR1.XReg, TEMP_GPR2.XReg); + cemu_assert_debug(dataReg.getIdx() != TEMP_FPR1.index); ldr(TEMP_FPR1.QReg, AdrReg(PPC_REC_INSTANCE_REG, TEMP_GPR1.XReg)); fmul(resReg.d2, dataReg.d2, TEMP_FPR1.VReg.d2); } @@ -1261,7 +1222,7 @@ void AArch64GenContext_t::psq_load_generic(uint8 mode, VReg& dataReg, WReg& memR Label u8FormatLabel, u16FormatLabel, s8FormatLabel, s16FormatLabel, casesEndLabel; // load GQR & extract load type field - lsr(TEMP_GPR1.WReg, gpReg(registerGQR.GetRegID()), 16); + lsr(TEMP_GPR1.WReg, gpReg(registerGQR), 16); and_(TEMP_GPR1.WReg, TEMP_GPR1.WReg, 7); // jump cases @@ -1303,12 +1264,12 @@ void AArch64GenContext_t::psq_load_generic(uint8 mode, VReg& dataReg, WReg& memR bool AArch64GenContext_t::fpr_load(IMLInstruction* imlInstruction, bool indexed) { - IMLRegID dataRegId = imlInstruction->op_storeLoad.registerData.GetRegID(); - VReg dataVReg = fpReg(dataRegId); - SReg dataSReg = fpReg(dataRegId); - DReg dataDReg = fpReg(dataRegId); - WReg realRegisterMem = gpReg(imlInstruction->op_storeLoad.registerMem.GetRegID()); - WReg realRegisterMem2 = indexed ? gpReg(imlInstruction->op_storeLoad.registerMem2.GetRegID()) : wzr; + const IMLReg& dataReg = imlInstruction->op_storeLoad.registerData; + VReg dataVReg = fpReg(dataReg); + SReg dataSReg = fpReg(dataReg); + DReg dataDReg = fpReg(dataReg); + WReg realRegisterMem = gpReg(imlInstruction->op_storeLoad.registerMem); + WReg realRegisterMem2 = indexed ? gpReg(imlInstruction->op_storeLoad.registerMem2) : wzr; sint32 adrOffset = imlInstruction->op_storeLoad.immS32; uint8 mode = imlInstruction->op_storeLoad.mode; @@ -1365,10 +1326,10 @@ bool AArch64GenContext_t::fpr_load(IMLInstruction* imlInstruction, bool indexed) return true; } -void AArch64GenContext_t::psq_store(uint8 mode, IMLRegID dataRegId, WReg& memReg, WReg& indexReg, sint32 memOffset, bool indexed, const IMLReg& registerGQR) +void AArch64GenContext_t::psq_store(uint8 mode, const IMLReg& dataReg, WReg& memReg, WReg& indexReg, sint32 memOffset, bool indexed, const IMLReg& registerGQR) { - auto dataVReg = fpReg(dataRegId); - auto dataDReg = fpReg(dataRegId); + auto dataVReg = fpReg(dataReg); + auto dataDReg = fpReg(dataReg); bool storePS1 = (mode == PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1 || mode == PPCREC_FPR_ST_MODE_PSQ_S8_PS0_PS1 || @@ -1543,12 +1504,12 @@ void AArch64GenContext_t::psq_store(uint8 mode, IMLRegID dataRegId, WReg& memReg } } -void AArch64GenContext_t::psq_store_generic(uint8 mode, IMLRegID dataRegId, WReg& memReg, WReg& indexReg, sint32 memOffset, bool indexed, const IMLReg& registerGQR) +void AArch64GenContext_t::psq_store_generic(uint8 mode, const IMLReg& dataReg, WReg& memReg, WReg& indexReg, sint32 memOffset, bool indexed, const IMLReg& registerGQR) { bool storePS1 = (mode == PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1); Label u8FormatLabel, u16FormatLabel, s8FormatLabel, s16FormatLabel, casesEndLabel; // load GQR & extract store type field - and_(TEMP_GPR1.WReg, gpReg(registerGQR.GetRegID()), 7); + and_(TEMP_GPR1.WReg, gpReg(registerGQR), 7); // jump cases cmp(TEMP_GPR1.WReg, 4); // type 4 -> u8 @@ -1566,23 +1527,23 @@ void AArch64GenContext_t::psq_store_generic(uint8 mode, IMLRegID dataRegId, WReg // default case -> float // generate cases - psq_store(storePS1 ? PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0, dataRegId, memReg, indexReg, memOffset, indexed, registerGQR); + psq_store(storePS1 ? PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0, dataReg, memReg, indexReg, memOffset, indexed, registerGQR); b(casesEndLabel); L(u16FormatLabel); - psq_store(storePS1 ? PPCREC_FPR_ST_MODE_PSQ_U16_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_U16_PS0, dataRegId, memReg, indexReg, memOffset, indexed, registerGQR); + psq_store(storePS1 ? PPCREC_FPR_ST_MODE_PSQ_U16_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_U16_PS0, dataReg, memReg, indexReg, memOffset, indexed, registerGQR); b(casesEndLabel); L(s16FormatLabel); - psq_store(storePS1 ? PPCREC_FPR_ST_MODE_PSQ_S16_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_S16_PS0, dataRegId, memReg, indexReg, memOffset, indexed, registerGQR); + psq_store(storePS1 ? PPCREC_FPR_ST_MODE_PSQ_S16_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_S16_PS0, dataReg, memReg, indexReg, memOffset, indexed, registerGQR); b(casesEndLabel); L(u8FormatLabel); - psq_store(storePS1 ? PPCREC_FPR_ST_MODE_PSQ_U8_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_U8_PS0, dataRegId, memReg, indexReg, memOffset, indexed, registerGQR); + psq_store(storePS1 ? PPCREC_FPR_ST_MODE_PSQ_U8_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_U8_PS0, dataReg, memReg, indexReg, memOffset, indexed, registerGQR); b(casesEndLabel); L(s8FormatLabel); - psq_store(storePS1 ? PPCREC_FPR_ST_MODE_PSQ_S8_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_S8_PS0, dataRegId, memReg, indexReg, memOffset, indexed, registerGQR); + psq_store(storePS1 ? PPCREC_FPR_ST_MODE_PSQ_S8_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_S8_PS0, dataReg, memReg, indexReg, memOffset, indexed, registerGQR); L(casesEndLabel); } @@ -1590,11 +1551,11 @@ void AArch64GenContext_t::psq_store_generic(uint8 mode, IMLRegID dataRegId, WReg // store to memory bool AArch64GenContext_t::fpr_store(IMLInstruction* imlInstruction, bool indexed) { - IMLRegID dataRegId = imlInstruction->op_storeLoad.registerData.GetRegID(); - VReg dataReg = fpReg(dataRegId); - DReg dataDReg = fpReg(dataRegId); - WReg memReg = gpReg(imlInstruction->op_storeLoad.registerMem.GetRegID()); - WReg indexReg = indexed ? gpReg(imlInstruction->op_storeLoad.registerMem2.GetRegID()) : wzr; + const IMLReg& dataImlReg = imlInstruction->op_storeLoad.registerData; + VReg dataVReg = fpReg(dataImlReg); + DReg dataDReg = fpReg(dataImlReg); + WReg memReg = gpReg(imlInstruction->op_storeLoad.registerMem); + WReg indexReg = indexed ? gpReg(imlInstruction->op_storeLoad.registerMem2) : wzr; sint32 memOffset = imlInstruction->op_storeLoad.immS32; uint8 mode = imlInstruction->op_storeLoad.mode; @@ -1607,7 +1568,7 @@ bool AArch64GenContext_t::fpr_store(IMLInstruction* imlInstruction, bool indexed if (imlInstruction->op_storeLoad.flags2.notExpanded) { // value is already in single format - mov(TEMP_GPR2.WReg, dataReg.s[0]); + mov(TEMP_GPR2.WReg, dataVReg.s[0]); } else { @@ -1622,7 +1583,7 @@ bool AArch64GenContext_t::fpr_store(IMLInstruction* imlInstruction, bool indexed add_imm(TEMP_GPR1.WReg, memReg, memOffset, TEMP_GPR1.WReg); if (indexed) add(TEMP_GPR1.WReg, TEMP_GPR1.WReg, indexReg); - mov(TEMP_GPR2.XReg, dataReg.d[0]); + mov(TEMP_GPR2.XReg, dataVReg.d[0]); rev(TEMP_GPR2.XReg, TEMP_GPR2.XReg); str(TEMP_GPR2.XReg, AdrExt(MEM_BASE_REG, TEMP_GPR1.WReg, ExtMod::UXTW)); } @@ -1631,7 +1592,7 @@ bool AArch64GenContext_t::fpr_store(IMLInstruction* imlInstruction, bool indexed add_imm(TEMP_GPR1.WReg, memReg, memOffset, TEMP_GPR1.WReg); if (indexed) add(TEMP_GPR1.WReg, TEMP_GPR1.WReg, indexReg); - mov(TEMP_GPR2.WReg, dataReg.s[0]); + mov(TEMP_GPR2.WReg, dataVReg.s[0]); rev(TEMP_GPR2.WReg, TEMP_GPR2.WReg); str(TEMP_GPR2.WReg, AdrExt(MEM_BASE_REG, TEMP_GPR1.WReg, ExtMod::UXTW)); } @@ -1647,12 +1608,12 @@ bool AArch64GenContext_t::fpr_store(IMLInstruction* imlInstruction, bool indexed mode == PPCREC_FPR_ST_MODE_PSQ_U16_PS0_PS1) { cemu_assert_debug(imlInstruction->op_storeLoad.flags2.notExpanded == false); - psq_store(mode, dataRegId, memReg, indexReg, imlInstruction->op_storeLoad.immS32, indexed); + psq_store(mode, dataImlReg, memReg, indexReg, imlInstruction->op_storeLoad.immS32, indexed); } else if (mode == PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1 || mode == PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0) { - psq_store_generic(mode, dataRegId, memReg, indexReg, imlInstruction->op_storeLoad.immS32, indexed, imlInstruction->op_storeLoad.registerGQR); + psq_store_generic(mode, dataImlReg, memReg, indexReg, imlInstruction->op_storeLoad.immS32, indexed, imlInstruction->op_storeLoad.registerGQR); } else { @@ -1666,11 +1627,9 @@ bool AArch64GenContext_t::fpr_store(IMLInstruction* imlInstruction, bool indexed // FPR op FPR void AArch64GenContext_t::fpr_r_r(IMLInstruction* imlInstruction) { - IMLRegID regAId = imlInstruction->op_fpr_r_r.regA.GetRegID(); - IMLRegID regRId = imlInstruction->op_fpr_r_r.regR.GetRegID(); - VReg regRVReg = fpReg(regRId); - VReg regAVReg = fpReg(regAId); - DReg regADReg = fpReg(regAId); + VReg regRVReg = fpReg(imlInstruction->op_fpr_r_r.regR); + VReg regAVReg = fpReg(imlInstruction->op_fpr_r_r.regA); + DReg regADReg = fpReg(imlInstruction->op_fpr_r_r.regA); if (imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP) { @@ -1682,8 +1641,7 @@ void AArch64GenContext_t::fpr_r_r(IMLInstruction* imlInstruction) } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM) { - if (regRId != regAId) - mov(regRVReg.d[0], regAVReg.d[0]); + mov(regRVReg.d[0], regAVReg.d[0]); } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_TOP) { @@ -1695,8 +1653,7 @@ void AArch64GenContext_t::fpr_r_r(IMLInstruction* imlInstruction) } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_TOP_TO_TOP) { - if (regRId != regAId) - mov(regRVReg.d[1], regAVReg.d[1]); + mov(regRVReg.d[1], regAVReg.d[1]); } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM) { @@ -1744,32 +1701,13 @@ void AArch64GenContext_t::fpr_r_r(IMLInstruction* imlInstruction) } else if (imlInstruction->operation == PPCREC_IML_OP_ASSIGN) { - if (regRId != regAId) - mov(regRVReg.b16, regAVReg.b16); + mov(regRVReg.b16, regAVReg.b16); } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_BOTTOM_FCTIWZ) { fcvtzs(TEMP_GPR1.WReg, regADReg); mov(regRVReg.d[0], TEMP_GPR1.XReg); } - else if (imlInstruction->operation == PPCREC_IML_OP_FPR_BOTTOM_FRES_TO_BOTTOM_AND_TOP) - { - mov(TEMP_GPR2.XReg, x30); - mov(TEMP_GPR1.XReg, (uint64)recompiler_fres); - mov(ASM_ROUTINE_FPR.VReg.d[0], regAVReg.d[0]); - blr(TEMP_GPR1.XReg); - dup(regRVReg.d2, ASM_ROUTINE_FPR.VReg.d[0]); - mov(x30, TEMP_GPR2.XReg); - } - else if (imlInstruction->operation == PPCREC_IML_OP_FPR_BOTTOM_RECIPROCAL_SQRT) - { - mov(TEMP_GPR2.XReg, x30); - mov(TEMP_GPR1.XReg, (uint64)recompiler_frsqrte); - mov(ASM_ROUTINE_FPR.VReg.d[0], regAVReg.d[0]); - blr(TEMP_GPR1.XReg); - mov(regRVReg.d[0], ASM_ROUTINE_FPR.VReg.d[0]); - mov(x30, TEMP_GPR2.XReg); - } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATE_PAIR) { fneg(regRVReg.d2, regAVReg.d2); @@ -1778,22 +1716,11 @@ void AArch64GenContext_t::fpr_r_r(IMLInstruction* imlInstruction) { fabs(regRVReg.d2, regAVReg.d2); } - else if (imlInstruction->operation == PPCREC_IML_OP_FPR_FRES_PAIR) + else if (imlInstruction->operation == PPCREC_IML_OP_FPR_FRES_PAIR || imlInstruction->operation == PPCREC_IML_OP_FPR_FRSQRTE_PAIR) { + uintptr_t routine = imlInstruction->operation == PPCREC_IML_OP_FPR_FRES_PAIR ? (uintptr_t)recompiler_fres : (uintptr_t)recompiler_frsqrte; mov(TEMP_GPR2.XReg, x30); - mov(TEMP_GPR1.XReg, (uint64)recompiler_fres); - mov(ASM_ROUTINE_FPR.VReg.d[0], regAVReg.d[0]); - blr(TEMP_GPR1.XReg); - mov(regRVReg.d[0], ASM_ROUTINE_FPR.VReg.d[0]); - mov(ASM_ROUTINE_FPR.VReg.d[0], regAVReg.d[1]); - blr(TEMP_GPR1.XReg); - mov(regRVReg.d[1], ASM_ROUTINE_FPR.VReg.d[0]); - mov(x30, TEMP_GPR2.XReg); - } - else if (imlInstruction->operation == PPCREC_IML_OP_FPR_FRSQRTE_PAIR) - { - mov(TEMP_GPR2.XReg, x30); - mov(TEMP_GPR1.XReg, (uint64)recompiler_frsqrte); + mov(TEMP_GPR1.XReg, routine); mov(ASM_ROUTINE_FPR.VReg.d[0], regAVReg.d[0]); blr(TEMP_GPR1.XReg); mov(regRVReg.d[0], ASM_ROUTINE_FPR.VReg.d[0]); @@ -1810,9 +1737,9 @@ void AArch64GenContext_t::fpr_r_r(IMLInstruction* imlInstruction) void AArch64GenContext_t::fpr_r_r_r(IMLInstruction* imlInstruction) { - auto regR = fpReg(imlInstruction->op_fpr_r_r_r.regR.GetRegID()); - auto regA = fpReg(imlInstruction->op_fpr_r_r_r.regA.GetRegID()); - auto regB = fpReg(imlInstruction->op_fpr_r_r_r.regB.GetRegID()); + auto regR = fpReg(imlInstruction->op_fpr_r_r_r.regR); + auto regA = fpReg(imlInstruction->op_fpr_r_r_r.regA); + auto regB = fpReg(imlInstruction->op_fpr_r_r_r.regB); if (imlInstruction->operation == PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM) { @@ -1821,9 +1748,16 @@ void AArch64GenContext_t::fpr_r_r_r(IMLInstruction* imlInstruction) } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_ADD_BOTTOM) { - fadd(TEMP_FPR1.VReg.d2, regA.d2, regB.d2); - mov(regR.d[0], TEMP_FPR1.VReg.d[0]); - mov(regR.d[1], regA.d[1]); + fadd(TEMP_FPR1.DReg, fpReg(imlInstruction->op_fpr_r_r_r.regA), fpReg(imlInstruction->op_fpr_r_r_r.regB)); + if (regR.getIdx() == regA.getIdx()) + { + mov(regR.d[0], TEMP_FPR1.VReg.d[0]); + } + else + { + mov(TEMP_FPR1.VReg.d[1], regA.d[1]); + mov(regR.b16, TEMP_FPR1.VReg.b16); + } } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_SUB_PAIR) { @@ -1845,39 +1779,45 @@ void AArch64GenContext_t::fpr_r_r_r(IMLInstruction* imlInstruction) */ void AArch64GenContext_t::fpr_r_r_r_r(IMLInstruction* imlInstruction) { - auto regR = fpReg(imlInstruction->op_fpr_r_r_r_r.regR.GetRegID()); - auto regA = fpReg(imlInstruction->op_fpr_r_r_r_r.regA.GetRegID()); - auto regB = fpReg(imlInstruction->op_fpr_r_r_r_r.regB.GetRegID()); - auto regC = fpReg(imlInstruction->op_fpr_r_r_r_r.regC.GetRegID()); + auto regR = fpReg(imlInstruction->op_fpr_r_r_r_r.regR); + auto regA = fpReg(imlInstruction->op_fpr_r_r_r_r.regA); + auto regB = fpReg(imlInstruction->op_fpr_r_r_r_r.regB); + auto regC = fpReg(imlInstruction->op_fpr_r_r_r_r.regC); if (imlInstruction->operation == PPCREC_IML_OP_FPR_SUM0) { - mov(TEMP_FPR1.VReg.d[0], regB.d[1]); - fadd(TEMP_FPR1.VReg.d2, TEMP_FPR1.VReg.d2, regA.d2); - mov(TEMP_FPR1.VReg.d[1], regC.d[1]); - mov(regR.b16, TEMP_FPR1.VReg.b16); + dup(TEMP_FPR1.VReg.d2, regB.d[1]); + fadd(regR.d2, regA.d2, TEMP_FPR1.VReg.d2); + mov(regR.d[1], regC.d[1]); } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_SUM1) { - mov(TEMP_FPR1.VReg.d[1], regA.d[0]); - fadd(TEMP_FPR1.VReg.d2, TEMP_FPR1.VReg.d2, regB.d2); - mov(TEMP_FPR1.VReg.d[0], regC.d[0]); - mov(regR.b16, TEMP_FPR1.VReg.b16); + dup(TEMP_FPR1.VReg.d2, regB.d[1]); + fadd(TEMP_FPR1.VReg.d2, TEMP_FPR1.VReg.d2, regA.d2); + zip1(regR.d2, regC.d2, TEMP_FPR1.VReg.d2); } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_SELECT_BOTTOM) { - auto regADReg = fpReg(imlInstruction->op_fpr_r_r_r_r.regA.GetRegID()); - auto regBDReg = fpReg(imlInstruction->op_fpr_r_r_r_r.regB.GetRegID()); - auto regCDReg = fpReg(imlInstruction->op_fpr_r_r_r_r.regC.GetRegID()); + auto regADReg = fpReg(imlInstruction->op_fpr_r_r_r_r.regA); + auto regBDReg = fpReg(imlInstruction->op_fpr_r_r_r_r.regB); + auto regCDReg = fpReg(imlInstruction->op_fpr_r_r_r_r.regC); fcmp(regADReg, 0.0); fcsel(TEMP_FPR1.DReg, regCDReg, regBDReg, Cond::GE); mov(regR.d[0], TEMP_FPR1.VReg.d[0]); } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_SELECT_PAIR) { - fcmge(TEMP_FPR1.VReg.d2, regA.d2, 0.0); - bsl(TEMP_FPR1.VReg.b16, regC.b16, regB.b16); - mov(regR.b16, TEMP_FPR1.VReg.b16); + if (regR.getIdx() != regB.getIdx() && regR.getIdx() != regC.getIdx()) + { + fcmge(regR.d2, regA.d2, 0.0); + bsl(regR.b16, regC.b16, regB.b16); + } + else + { + fcmge(TEMP_FPR1.VReg.d2, regA.d2, 0.0); + bsl(TEMP_FPR1.VReg.b16, regC.b16, regB.b16); + mov(regR.b16, TEMP_FPR1.VReg.b16); + } } else { @@ -1887,9 +1827,9 @@ void AArch64GenContext_t::fpr_r_r_r_r(IMLInstruction* imlInstruction) void AArch64GenContext_t::fpr_r(IMLInstruction* imlInstruction) { - auto regRVReg = fpReg(imlInstruction->op_fpr_r.regR.GetRegID()); - auto regRDReg = fpReg(imlInstruction->op_fpr_r.regR.GetRegID()); - auto regRSReg = fpReg(imlInstruction->op_fpr_r.regR.GetRegID()); + auto regRVReg = fpReg(imlInstruction->op_fpr_r.regR); + auto regRDReg = fpReg(imlInstruction->op_fpr_r.regR); + auto regRSReg = fpReg(imlInstruction->op_fpr_r.regR); if (imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATE_BOTTOM) { @@ -1927,7 +1867,7 @@ void AArch64GenContext_t::fpr_r(IMLInstruction* imlInstruction) // convert bottom to 64bit double fcvt(regRDReg, regRSReg); // copy to top half - mov(regRVReg.d[1], regRVReg.d[0]); + dup(regRVReg.d2, regRVReg.d[0]); } else { @@ -1957,17 +1897,26 @@ Cond ImlFPCondToArm64Cond(IMLCondition cond) void AArch64GenContext_t::fpr_compare(IMLInstruction* imlInstruction) { - auto regR = gpReg(imlInstruction->op_fpr_compare.regR.GetRegID()); - auto regA = fpReg(imlInstruction->op_fpr_compare.regA.GetRegID()); - auto regB = fpReg(imlInstruction->op_fpr_compare.regB.GetRegID()); + auto regR = gpReg(imlInstruction->op_fpr_compare.regR); + auto regA = fpReg(imlInstruction->op_fpr_compare.regA); + auto regB = fpReg(imlInstruction->op_fpr_compare.regB); auto cond = ImlFPCondToArm64Cond(imlInstruction->op_fpr_compare.cond); fcmp(regA, regB); cset(regR, cond); } -std::unique_ptr PPCRecompiler_generateAArch64Code(struct PPCRecFunction_t* PPCRecFunction, struct ppcImlGenContext_t* ppcImlGenContext) +void AArch64GenContext_t::call_imm(IMLInstruction* imlInstruction) { - auto aarch64GenContext = std::make_unique(); + str(x30, AdrPreImm(sp, -16)); + mov(TEMP_GPR1.XReg, imlInstruction->op_call_imm.callAddress); + blr(TEMP_GPR1.XReg); + ldr(x30, AdrPostImm(sp, 16)); +} + +bool PPCRecompiler_generateAArch64Code(struct PPCRecFunction_t* PPCRecFunction, struct ppcImlGenContext_t* ppcImlGenContext) +{ + AArch64Allocator allocator; + AArch64GenContext_t aarch64GenContext{&allocator}; // generate iml instruction code bool codeGenerationFailed = false; @@ -1975,149 +1924,148 @@ std::unique_ptr PPCRecompiler_generateAArch64Code(struct PPCRecFunc { if (codeGenerationFailed) break; - segIt->x64Offset = aarch64GenContext->getSize(); + segIt->x64Offset = aarch64GenContext.getSize(); - aarch64GenContext->storeSegmentStart(segIt); + aarch64GenContext.storeSegmentStart(segIt); for (size_t i = 0; i < segIt->imlList.size(); i++) { IMLInstruction* imlInstruction = segIt->imlList.data() + i; if (imlInstruction->type == PPCREC_IML_TYPE_R_NAME) { - aarch64GenContext->r_name(imlInstruction); + aarch64GenContext.r_name(imlInstruction); } else if (imlInstruction->type == PPCREC_IML_TYPE_NAME_R) { - aarch64GenContext->name_r(imlInstruction); + aarch64GenContext.name_r(imlInstruction); } else if (imlInstruction->type == PPCREC_IML_TYPE_R_R) { - if (!aarch64GenContext->r_r(imlInstruction)) + if (!aarch64GenContext.r_r(imlInstruction)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_R_S32) { - if (!aarch64GenContext->r_s32(imlInstruction)) - codeGenerationFailed = true; - } - else if (imlInstruction->type == PPCREC_IML_TYPE_CONDITIONAL_R_S32) - { - if (!aarch64GenContext->conditional_r_s32(imlInstruction)) + if (!aarch64GenContext.r_s32(imlInstruction)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_S32) { - if (!aarch64GenContext->r_r_s32(imlInstruction)) + if (!aarch64GenContext.r_r_s32(imlInstruction)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_S32_CARRY) { - if (!aarch64GenContext->r_r_s32_carry(imlInstruction)) + if (!aarch64GenContext.r_r_s32_carry(imlInstruction)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_R) { - if (!aarch64GenContext->r_r_r(imlInstruction)) + if (!aarch64GenContext.r_r_r(imlInstruction)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_R_CARRY) { - if (!aarch64GenContext->r_r_r_carry(imlInstruction)) + if (!aarch64GenContext.r_r_r_carry(imlInstruction)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_COMPARE) { - aarch64GenContext->compare(imlInstruction); + aarch64GenContext.compare(imlInstruction); } else if (imlInstruction->type == PPCREC_IML_TYPE_COMPARE_S32) { - aarch64GenContext->compare_s32(imlInstruction); + aarch64GenContext.compare_s32(imlInstruction); } else if (imlInstruction->type == PPCREC_IML_TYPE_CONDITIONAL_JUMP) { if (segIt->nextSegmentBranchTaken == segIt) cemu_assert_suspicious(); - aarch64GenContext->cjump(imlInstruction, segIt); + aarch64GenContext.cjump(imlInstruction, segIt); } else if (imlInstruction->type == PPCREC_IML_TYPE_JUMP) { - aarch64GenContext->jump(segIt); + aarch64GenContext.jump(segIt); } else if (imlInstruction->type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK) { - aarch64GenContext->conditionalJumpCycleCheck(segIt); + aarch64GenContext.conditionalJumpCycleCheck(segIt); } else if (imlInstruction->type == PPCREC_IML_TYPE_MACRO) { - if (!aarch64GenContext->macro(imlInstruction)) + if (!aarch64GenContext.macro(imlInstruction)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_LOAD) { - if (!aarch64GenContext->load(imlInstruction, false)) + if (!aarch64GenContext.load(imlInstruction, false)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_LOAD_INDEXED) { - if (!aarch64GenContext->load(imlInstruction, true)) + if (!aarch64GenContext.load(imlInstruction, true)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_STORE) { - if (!aarch64GenContext->store(imlInstruction, false)) + if (!aarch64GenContext.store(imlInstruction, false)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_STORE_INDEXED) { - if (!aarch64GenContext->store(imlInstruction, true)) + if (!aarch64GenContext.store(imlInstruction, true)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_ATOMIC_CMP_STORE) { - aarch64GenContext->atomic_cmp_store(imlInstruction); + aarch64GenContext.atomic_cmp_store(imlInstruction); } else if (imlInstruction->type == PPCREC_IML_TYPE_NO_OP) { } + else if (imlInstruction->type == PPCREC_IML_TYPE_CALL_IMM) + { + aarch64GenContext.call_imm(imlInstruction); + } else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD) { - if (!aarch64GenContext->fpr_load(imlInstruction, false)) + if (!aarch64GenContext.fpr_load(imlInstruction, false)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED) { - if (!aarch64GenContext->fpr_load(imlInstruction, true)) + if (!aarch64GenContext.fpr_load(imlInstruction, true)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE) { - if (!aarch64GenContext->fpr_store(imlInstruction, false)) + if (!aarch64GenContext.fpr_store(imlInstruction, false)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE_INDEXED) { - if (!aarch64GenContext->fpr_store(imlInstruction, true)) + if (!aarch64GenContext.fpr_store(imlInstruction, true)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R) { - aarch64GenContext->fpr_r_r(imlInstruction); + aarch64GenContext.fpr_r_r(imlInstruction); } else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R) { - aarch64GenContext->fpr_r_r_r(imlInstruction); + aarch64GenContext.fpr_r_r_r(imlInstruction); } else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R_R) { - aarch64GenContext->fpr_r_r_r_r(imlInstruction); + aarch64GenContext.fpr_r_r_r_r(imlInstruction); } else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R) { - aarch64GenContext->fpr_r(imlInstruction); + aarch64GenContext.fpr_r(imlInstruction); } else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_COMPARE) { - aarch64GenContext->fpr_compare(imlInstruction); + aarch64GenContext.fpr_compare(imlInstruction); } else { @@ -2131,24 +2079,37 @@ std::unique_ptr PPCRecompiler_generateAArch64Code(struct PPCRecFunc // handle failed code generation if (codeGenerationFailed) { - return nullptr; + return false; } - aarch64GenContext->processAllJumps(); + if (!aarch64GenContext.processAllJumps()) + { + return false; + } - aarch64GenContext->readyRE(); + aarch64GenContext.readyRE(); // set code - PPCRecFunction->x86Code = aarch64GenContext->getCode(); - PPCRecFunction->x86Size = aarch64GenContext->getSize(); - return aarch64GenContext; + PPCRecFunction->x86Code = aarch64GenContext.getCode(); + PPCRecFunction->x86Size = aarch64GenContext.getMaxSize(); + // set free disabled to skip freeing the code from the CodeGenerator destructor + allocator.setFreeDisabled(true); + return true; +} + +void PPCRecompiler_cleanupAArch64Code(void* code, size_t size) +{ + AArch64Allocator allocator; + if (allocator.useProtect()) + CodeArray::protect(code, size, CodeArray::PROTECT_RW); + allocator.free(static_cast(code)); } void AArch64GenContext_t::enterRecompilerCode() { - constexpr size_t stackSize = 8 * (30 - 18) /* x18 - x30 */ + 8 * (15 - 8) /*v8.d[0] - v15.d[0]*/ + 8; - static_assert(stackSize % 16 == 0); - sub(sp, sp, stackSize); + constexpr size_t STACK_SIZE = 160 /* x19 .. x30 + v8.d[0] .. v15.d[0] */; + static_assert(STACK_SIZE % 16 == 0); + sub(sp, sp, STACK_SIZE); mov(x9, sp); stp(x19, x20, AdrPostImm(x9, 16)); @@ -2176,13 +2137,14 @@ void AArch64GenContext_t::enterRecompilerCode() ld4((v8.d - v11.d)[0], AdrPostImm(x9, 32)); ld4((v12.d - v15.d)[0], AdrPostImm(x9, 32)); - add(sp, sp, stackSize); + add(sp, sp, STACK_SIZE); + ret(); } void AArch64GenContext_t::leaveRecompilerCode() { - str(LR_WREG, AdrImm(HCPU_REG, offsetof(PPCInterpreter_t, instructionPointer))); + str(LR.WReg, AdrImm(HCPU_REG, offsetof(PPCInterpreter_t, instructionPointer))); ret(); } diff --git a/src/Cafe/HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.h b/src/Cafe/HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.h index be4ee530..af0b0176 100644 --- a/src/Cafe/HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.h +++ b/src/Cafe/HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.h @@ -2,12 +2,9 @@ #include "HW/Espresso/Recompiler/IML/IMLInstruction.h" #include "../PPCRecompiler.h" -struct CodeContext -{ - virtual ~CodeContext() = default; -}; -std::unique_ptr PPCRecompiler_generateAArch64Code(struct PPCRecFunction_t* PPCRecFunction, struct ppcImlGenContext_t* ppcImlGenContext); +bool PPCRecompiler_generateAArch64Code(struct PPCRecFunction_t* PPCRecFunction, struct ppcImlGenContext_t* ppcImlGenContext); +void PPCRecompiler_cleanupAArch64Code(void* code, size_t size); void PPCRecompilerAArch64Gen_generateRecompilerInterfaceFunctions(); @@ -17,5 +14,5 @@ namespace IMLArchAArch64 static constexpr int PHYSREG_GPR_BASE = 0; static constexpr int PHYSREG_GPR_COUNT = 25; static constexpr int PHYSREG_FPR_BASE = PHYSREG_GPR_COUNT; - static constexpr int PHYSREG_FPR_COUNT = 28; + static constexpr int PHYSREG_FPR_COUNT = 29; }; // namespace IMLArchAArch64 \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.cpp b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.cpp index fb056899..6a8aac2b 100644 --- a/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.cpp @@ -7,6 +7,7 @@ #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "util/MemMapper/MemMapper.h" #include "Common/cpu_features.h" +#include static x86Assembler64::GPR32 _reg32(IMLReg physReg) { @@ -82,6 +83,36 @@ X86Cond _x86Cond(IMLCondition imlCond) return X86_CONDITION_Z; } +X86Cond _x86CondInverted(IMLCondition imlCond) +{ + switch (imlCond) + { + case IMLCondition::EQ: + return X86_CONDITION_NZ; + case IMLCondition::NEQ: + return X86_CONDITION_Z; + case IMLCondition::UNSIGNED_GT: + return X86_CONDITION_BE; + case IMLCondition::UNSIGNED_LT: + return X86_CONDITION_NB; + case IMLCondition::SIGNED_GT: + return X86_CONDITION_LE; + case IMLCondition::SIGNED_LT: + return X86_CONDITION_NL; + default: + break; + } + cemu_assert_suspicious(); + return X86_CONDITION_Z; +} + +X86Cond _x86Cond(IMLCondition imlCond, bool condIsInverted) +{ + if (condIsInverted) + return _x86CondInverted(imlCond); + return _x86Cond(imlCond); +} + /* * Remember current instruction output offset for reloc * The instruction generated after this method has been called will be adjusted @@ -130,7 +161,7 @@ void* ATTR_MS_ABI PPCRecompiler_virtualHLE(PPCInterpreter_t* hCPU, uint32 hleFun hCPU->remainingCycles -= 500; // let subtract about 500 cycles for each HLE call hCPU->gpr[3] = 0; PPCInterpreter_nextInstruction(hCPU); - return PPCInterpreter_getCurrentInstance(); + return hCPU; } else { @@ -142,22 +173,11 @@ void* ATTR_MS_ABI PPCRecompiler_virtualHLE(PPCInterpreter_t* hCPU, uint32 hleFun return PPCInterpreter_getCurrentInstance(); } -void ATTR_MS_ABI PPCRecompiler_getTBL(PPCInterpreter_t* hCPU, uint32 gprIndex) -{ - uint64 coreTime = coreinit::OSGetSystemTime(); - hCPU->gpr[gprIndex] = (uint32)(coreTime&0xFFFFFFFF); -} - -void ATTR_MS_ABI PPCRecompiler_getTBU(PPCInterpreter_t* hCPU, uint32 gprIndex) -{ - uint64 coreTime = coreinit::OSGetSystemTime(); - hCPU->gpr[gprIndex] = (uint32)((coreTime>>32)&0xFFFFFFFF); -} - bool PPCRecompilerX64Gen_imlInstruction_macro(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { if (imlInstruction->operation == PPCREC_IML_MACRO_B_TO_REG) { + //x64Gen_int3(x64GenContext); uint32 branchDstReg = _reg32(imlInstruction->op_macro.paramReg); if(X86_REG_RDX != branchDstReg) x64Gen_mov_reg64_reg64(x64GenContext, X86_REG_RDX, branchDstReg); @@ -174,7 +194,7 @@ bool PPCRecompilerX64Gen_imlInstruction_macro(PPCRecFunction_t* PPCRecFunction, { // MOV DWORD [SPR_LinkRegister], newLR uint32 newLR = imlInstruction->op_macro.param + 4; - x64Gen_mov_mem32Reg64_imm32(x64GenContext, X86_REG_RSP, offsetof(PPCInterpreter_t, spr.LR), newLR); + x64Gen_mov_mem32Reg64_imm32(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, spr.LR), newLR); // remember new instruction pointer in RDX uint32 newIP = imlInstruction->op_macro.param2; x64Gen_mov_reg64Low32_imm32(x64GenContext, X86_REG_RDX, newIP); @@ -247,26 +267,20 @@ bool PPCRecompilerX64Gen_imlInstruction_macro(PPCRecFunction_t* PPCRecFunction, else if( imlInstruction->operation == PPCREC_IML_MACRO_COUNT_CYCLES ) { uint32 cycleCount = imlInstruction->op_macro.param; - x64Gen_sub_mem32reg64_imm32(x64GenContext, X86_REG_RSP, offsetof(PPCInterpreter_t, remainingCycles), cycleCount); + x64Gen_sub_mem32reg64_imm32(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, remainingCycles), cycleCount); return true; } else if( imlInstruction->operation == PPCREC_IML_MACRO_HLE ) { uint32 ppcAddress = imlInstruction->op_macro.param; uint32 funcId = imlInstruction->op_macro.param2; - //x64Gen_int3(x64GenContext); // update instruction pointer - x64Gen_mov_mem32Reg64_imm32(x64GenContext, X86_REG_RSP, offsetof(PPCInterpreter_t, instructionPointer), ppcAddress); - //// save hCPU (RSP) - //x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_TEMP, (uint64)&ppcRecompilerX64_hCPUTemp); - //x64Emit_mov_mem64_reg64(x64GenContext, REG_RESV_TEMP, 0, REG_RSP); + x64Gen_mov_mem32Reg64_imm32(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, instructionPointer), ppcAddress); // set parameters - x64Gen_mov_reg64_reg64(x64GenContext, X86_REG_RCX, X86_REG_RSP); + x64Gen_mov_reg64_reg64(x64GenContext, X86_REG_RCX, REG_RESV_HCPU); x64Gen_mov_reg64_imm64(x64GenContext, X86_REG_RDX, funcId); - // restore stackpointer from executionContext/hCPU->rspTemp + // restore stackpointer from hCPU->rspTemp x64Emit_mov_reg64_mem64(x64GenContext, X86_REG_RSP, REG_RESV_HCPU, offsetof(PPCInterpreter_t, rspTemp)); - //x64Emit_mov_reg64_mem64(x64GenContext, REG_RSP, REG_R14, 0); - //x64Gen_int3(x64GenContext); // reserve space on stack for call parameters x64Gen_sub_reg64_imm32(x64GenContext, X86_REG_RSP, 8*11); // must be uneven number in order to retain stack 0x10 alignment x64Gen_mov_reg64_imm64(x64GenContext, X86_REG_RBP, 0); @@ -274,79 +288,37 @@ bool PPCRecompilerX64Gen_imlInstruction_macro(PPCRecFunction_t* PPCRecFunction, x64Gen_mov_reg64_imm64(x64GenContext, X86_REG_RAX, (uint64)PPCRecompiler_virtualHLE); x64Gen_call_reg64(x64GenContext, X86_REG_RAX); // restore RSP to hCPU (from RAX, result of PPCRecompiler_virtualHLE) - //x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_TEMP, (uint64)&ppcRecompilerX64_hCPUTemp); - //x64Emit_mov_reg64_mem64Reg64(x64GenContext, REG_RSP, REG_RESV_TEMP, 0); - x64Gen_mov_reg64_reg64(x64GenContext, X86_REG_RSP, X86_REG_RAX); + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_HCPU, X86_REG_RAX); // MOV R15, ppcRecompilerInstanceData - x64Gen_mov_reg64_imm64(x64GenContext, X86_REG_R15, (uint64)ppcRecompilerInstanceData); + x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_RECDATA, (uint64)ppcRecompilerInstanceData); // MOV R13, memory_base - x64Gen_mov_reg64_imm64(x64GenContext, X86_REG_R13, (uint64)memory_base); + x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_MEMBASE, (uint64)memory_base); // check if cycles where decreased beyond zero, if yes -> leave recompiler - x64Gen_bt_mem8(x64GenContext, X86_REG_RSP, offsetof(PPCInterpreter_t, remainingCycles), 31); // check if negative + x64Gen_bt_mem8(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, remainingCycles), 31); // check if negative sint32 jumpInstructionOffset1 = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmpc_near(x64GenContext, X86_CONDITION_NOT_CARRY, 0); - //x64Gen_int3(x64GenContext); - //x64Gen_mov_reg64Low32_imm32(x64GenContext, REG_RDX, ppcAddress); - x64Emit_mov_reg64_mem32(x64GenContext, X86_REG_RDX, X86_REG_RSP, offsetof(PPCInterpreter_t, instructionPointer)); + x64Emit_mov_reg64_mem32(x64GenContext, X86_REG_RDX, REG_RESV_HCPU, offsetof(PPCInterpreter_t, instructionPointer)); // set EAX to 0 (we assume that ppcRecompilerDirectJumpTable[0] will be a recompiler escape function) x64Gen_xor_reg32_reg32(x64GenContext, X86_REG_RAX, X86_REG_RAX); - // ADD RAX, R15 (R15 -> Pointer to ppcRecompilerInstanceData - x64Gen_add_reg64_reg64(x64GenContext, X86_REG_RAX, X86_REG_R15); - //// JMP [recompilerCallTable+EAX/4*8] - //x64Gen_int3(x64GenContext); + // ADD RAX, REG_RESV_RECDATA + x64Gen_add_reg64_reg64(x64GenContext, X86_REG_RAX, REG_RESV_RECDATA); + // JMP [recompilerCallTable+EAX/4*8] x64Gen_jmp_memReg64(x64GenContext, X86_REG_RAX, (uint32)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1, x64GenContext->emitter->GetWriteIndex()); // check if instruction pointer was changed // assign new instruction pointer to EAX - x64Emit_mov_reg64_mem32(x64GenContext, X86_REG_RAX, X86_REG_RSP, offsetof(PPCInterpreter_t, instructionPointer)); + x64Emit_mov_reg64_mem32(x64GenContext, X86_REG_RAX, REG_RESV_HCPU, offsetof(PPCInterpreter_t, instructionPointer)); // remember instruction pointer in REG_EDX x64Gen_mov_reg64_reg64(x64GenContext, X86_REG_RDX, X86_REG_RAX); // EAX *= 2 x64Gen_add_reg64_reg64(x64GenContext, X86_REG_RAX, X86_REG_RAX); - // ADD RAX, R15 (R15 -> Pointer to ppcRecompilerInstanceData - x64Gen_add_reg64_reg64(x64GenContext, X86_REG_RAX, X86_REG_R15); + // ADD RAX, REG_RESV_RECDATA + x64Gen_add_reg64_reg64(x64GenContext, X86_REG_RAX, REG_RESV_RECDATA); // JMP [ppcRecompilerDirectJumpTable+RAX/4*8] x64Gen_jmp_memReg64(x64GenContext, X86_REG_RAX, (uint32)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); return true; } - else if( imlInstruction->operation == PPCREC_IML_MACRO_MFTB ) - { - // according to MS ABI the caller needs to save: - // RAX, RCX, RDX, R8, R9, R10, R11 - - uint32 ppcAddress = imlInstruction->op_macro.param; - uint32 sprId = imlInstruction->op_macro.param2&0xFFFF; - uint32 gprIndex = (imlInstruction->op_macro.param2>>16)&0x1F; - // update instruction pointer - x64Gen_mov_mem32Reg64_imm32(x64GenContext, X86_REG_RSP, offsetof(PPCInterpreter_t, instructionPointer), ppcAddress); - // set parameters - x64Gen_mov_reg64_reg64(x64GenContext, X86_REG_RCX, X86_REG_RSP); - x64Gen_mov_reg64_imm64(x64GenContext, X86_REG_RDX, gprIndex); - // restore stackpointer to original RSP - x64Emit_mov_reg64_mem64(x64GenContext, X86_REG_RSP, REG_RESV_HCPU, offsetof(PPCInterpreter_t, rspTemp)); - // push hCPU on stack - x64Gen_push_reg64(x64GenContext, X86_REG_RCX); - // reserve space on stack for call parameters - x64Gen_sub_reg64_imm32(x64GenContext, X86_REG_RSP, 8*11 + 8); - x64Gen_mov_reg64_imm64(x64GenContext, X86_REG_RBP, 0); - // call function - if( sprId == SPR_TBL ) - x64Gen_mov_reg64_imm64(x64GenContext, X86_REG_RAX, (uint64)PPCRecompiler_getTBL); - else if( sprId == SPR_TBU ) - x64Gen_mov_reg64_imm64(x64GenContext, X86_REG_RAX, (uint64)PPCRecompiler_getTBU); - else - assert_dbg(); - x64Gen_call_reg64(x64GenContext, X86_REG_RAX); - // restore hCPU from stack - x64Gen_add_reg64_imm32(x64GenContext, X86_REG_RSP, 8 * 11 + 8); - x64Gen_pop_reg64(x64GenContext, X86_REG_RSP); - // MOV R15, ppcRecompilerInstanceData - x64Gen_mov_reg64_imm64(x64GenContext, X86_REG_R15, (uint64)ppcRecompilerInstanceData); - // MOV R13, memory_base - x64Gen_mov_reg64_imm64(x64GenContext, X86_REG_R13, (uint64)memory_base); - return true; - } else { debug_printf("Unknown recompiler macro operation %d\n", imlInstruction->operation); @@ -377,18 +349,14 @@ bool PPCRecompilerX64Gen_imlInstruction_load(PPCRecFunction_t* PPCRecFunction, p if( indexed && realRegisterData == realRegisterMem2 ) { // for indexed memory access realRegisterData must not be the same register as the second memory register, - // this can easily be fixed by swapping the logic of realRegisterMem and realRegisterMem2 - sint32 temp = realRegisterMem; - realRegisterMem = realRegisterMem2; - realRegisterMem2 = temp; + // this can easily be worked around by swapping realRegisterMem and realRegisterMem2 + std::swap(realRegisterMem, realRegisterMem2); } bool signExtend = imlInstruction->op_storeLoad.flags2.signExtend; bool switchEndian = imlInstruction->op_storeLoad.flags2.swapEndian; if( imlInstruction->op_storeLoad.copyWidth == 32 ) { - //if( indexed ) - // PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); if (indexed) { x64Gen_lea_reg64Low32_reg64Low32PlusReg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem, realRegisterMem2); @@ -397,28 +365,24 @@ bool PPCRecompilerX64Gen_imlInstruction_load(PPCRecFunction_t* PPCRecFunction, p { if (indexed) { - x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, realRegisterData, X86_REG_R13, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32); - //if (indexed && realRegisterMem != realRegisterData) - // x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, realRegisterData, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32); } else { - x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, realRegisterData, X86_REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32); + x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, realRegisterData, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32); } } else { if (indexed) { - x64Emit_mov_reg32_mem32(x64GenContext, realRegisterData, X86_REG_R13, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32); - //if (realRegisterMem != realRegisterData) - // x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + x64Emit_mov_reg32_mem32(x64GenContext, realRegisterData, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32); if (switchEndian) x64Gen_bswap_reg64Lower32bit(x64GenContext, realRegisterData); } else { - x64Emit_mov_reg32_mem32(x64GenContext, realRegisterData, X86_REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32); + x64Emit_mov_reg32_mem32(x64GenContext, realRegisterData, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32); if (switchEndian) x64Gen_bswap_reg64Lower32bit(x64GenContext, realRegisterData); } @@ -432,13 +396,13 @@ bool PPCRecompilerX64Gen_imlInstruction_load(PPCRecFunction_t* PPCRecFunction, p } if(g_CPUFeatures.x86.movbe && switchEndian ) { - x64Gen_movBEZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext, realRegisterData, X86_REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32); + x64Gen_movBEZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext, realRegisterData, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32); if( indexed && realRegisterMem != realRegisterData ) x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); } else { - x64Gen_movZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext, realRegisterData, X86_REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32); + x64Gen_movZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext, realRegisterData, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32); if( indexed && realRegisterMem != realRegisterData ) x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); if( switchEndian ) @@ -454,9 +418,9 @@ bool PPCRecompilerX64Gen_imlInstruction_load(PPCRecFunction_t* PPCRecFunction, p if( indexed ) x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); if( signExtend ) - x64Gen_movSignExtend_reg64Low32_mem8Reg64PlusReg64(x64GenContext, realRegisterData, X86_REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32); + x64Gen_movSignExtend_reg64Low32_mem8Reg64PlusReg64(x64GenContext, realRegisterData, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32); else - x64Emit_movZX_reg32_mem8(x64GenContext, realRegisterData, X86_REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32); + x64Emit_movZX_reg32_mem8(x64GenContext, realRegisterData, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32); if( indexed && realRegisterMem != realRegisterData ) x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); } @@ -488,10 +452,8 @@ bool PPCRecompilerX64Gen_imlInstruction_store(PPCRecFunction_t* PPCRecFunction, if (indexed && realRegisterData == realRegisterMem2) { // for indexed memory access realRegisterData must not be the same register as the second memory register, - // this can easily be fixed by swapping the logic of realRegisterMem and realRegisterMem2 - sint32 temp = realRegisterMem; - realRegisterMem = realRegisterMem2; - realRegisterMem2 = temp; + // this can easily be worked around by swapping realRegisterMem and realRegisterMem2 + std::swap(realRegisterMem, realRegisterMem2); } bool signExtend = imlInstruction->op_storeLoad.flags2.signExtend; @@ -513,9 +475,9 @@ bool PPCRecompilerX64Gen_imlInstruction_store(PPCRecFunction_t* PPCRecFunction, if (indexed) x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); if (g_CPUFeatures.x86.movbe && swapEndian) - x64Gen_movBETruncate_mem32Reg64PlusReg64_reg64(x64GenContext, X86_REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32, valueRegister); + x64Gen_movBETruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, valueRegister); else - x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, X86_REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32, valueRegister); + x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, valueRegister); if (indexed) x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); } @@ -526,7 +488,7 @@ bool PPCRecompilerX64Gen_imlInstruction_store(PPCRecFunction_t* PPCRecFunction, x64Gen_rol_reg64Low16_imm8(x64GenContext, REG_RESV_TEMP, 8); if (indexed) x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - x64Gen_movTruncate_mem16Reg64PlusReg64_reg64(x64GenContext, X86_REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); + x64Gen_movTruncate_mem16Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); if (indexed) x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); // todo: Optimize this, e.g. by using MOVBE @@ -549,31 +511,34 @@ bool PPCRecompilerX64Gen_imlInstruction_store(PPCRecFunction_t* PPCRecFunction, return true; } -bool PPCRecompilerX64Gen_imlInstruction_atomic_cmp_store(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) +void PPCRecompilerX64Gen_imlInstruction_atomic_cmp_store(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { auto regBoolOut = _reg32_from_reg8(_reg8(imlInstruction->op_atomic_compare_store.regBoolOut)); auto regEA = _reg32(imlInstruction->op_atomic_compare_store.regEA); auto regVal = _reg32(imlInstruction->op_atomic_compare_store.regWriteValue); auto regCmp = _reg32(imlInstruction->op_atomic_compare_store.regCompareValue); - // make sure non of the regs are in EAX - if (regEA == X86_REG_EAX || - regBoolOut == X86_REG_EAX || - regVal == X86_REG_EAX || - regCmp == X86_REG_EAX) - { - printf("x86: atomic_cmp_store cannot emit due to EAX already being in use\n"); - return false; - } + cemu_assert_debug(regBoolOut == X86_REG_EAX); + cemu_assert_debug(regEA != X86_REG_EAX); + cemu_assert_debug(regVal != X86_REG_EAX); + cemu_assert_debug(regCmp != X86_REG_EAX); - x64GenContext->emitter->XCHG_qq(REG_RESV_TEMP, X86_REG_RAX); x64GenContext->emitter->MOV_dd(X86_REG_EAX, regCmp); - x64GenContext->emitter->XOR_dd(_reg32_from_reg8(regBoolOut), _reg32_from_reg8(regBoolOut)); // zero bytes unaffected by SETcc x64GenContext->emitter->LockPrefix(); x64GenContext->emitter->CMPXCHG_dd_l(REG_RESV_MEMBASE, 0, _reg64_from_reg32(regEA), 1, regVal); x64GenContext->emitter->SETcc_b(X86Cond::X86_CONDITION_Z, regBoolOut); - x64GenContext->emitter->XCHG_qq(REG_RESV_TEMP, X86_REG_RAX); - return true; + x64GenContext->emitter->AND_di32(regBoolOut, 1); // SETcc doesn't clear the upper bits so we do it manually here +} + +void PPCRecompilerX64Gen_imlInstruction_call_imm(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) +{ + // the register allocator takes care of spilling volatile registers and moving parameters to the right registers, so we don't need to do any special handling here + x64GenContext->emitter->SUB_qi8(X86_REG_RSP, 0x20); // reserve enough space for any parameters while keeping stack alignment of 16 intact + x64GenContext->emitter->MOV_qi64(X86_REG_RAX, imlInstruction->op_call_imm.callAddress); + x64GenContext->emitter->CALL_q(X86_REG_RAX); + x64GenContext->emitter->ADD_qi8(X86_REG_RSP, 0x20); + // a note about the stack pointer: + // currently the code generated by generateEnterRecompilerCode makes sure the stack is 16 byte aligned, so we don't need to fix it up here } bool PPCRecompilerX64Gen_imlInstruction_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) @@ -638,26 +603,9 @@ bool PPCRecompilerX64Gen_imlInstruction_r_r(PPCRecFunction_t* PPCRecFunction, pp PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2, x64GenContext->emitter->GetWriteIndex()); } } - else if( imlInstruction->operation == PPCREC_IML_OP_DCBZ ) + else if( imlInstruction->operation == PPCREC_IML_OP_X86_CMP) { - if( regR != regA ) - { - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, regA); - x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, regR); - x64Gen_and_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, ~0x1F); - x64Gen_add_reg64_reg64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE); - for(sint32 f=0; f<0x20; f+=8) - x64Gen_mov_mem64Reg64_imm32(x64GenContext, REG_RESV_TEMP, f, 0); - } - else - { - // calculate effective address - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, regA); - x64Gen_and_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, ~0x1F); - x64Gen_add_reg64_reg64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE); - for(sint32 f=0; f<0x20; f+=8) - x64Gen_mov_mem64Reg64_imm32(x64GenContext, REG_RESV_TEMP, f, 0); - } + x64GenContext->emitter->CMP_dd(regR, regA); } else { @@ -680,6 +628,11 @@ bool PPCRecompilerX64Gen_imlInstruction_r_s32(PPCRecFunction_t* PPCRecFunction, cemu_assert_debug((imlInstruction->op_r_immS32.immS32 & 0x80) == 0); x64Gen_rol_reg64Low32_imm8(x64GenContext, regR, (uint8)imlInstruction->op_r_immS32.immS32); } + else if( imlInstruction->operation == PPCREC_IML_OP_X86_CMP) + { + sint32 imm = imlInstruction->op_r_immS32.immS32; + x64GenContext->emitter->CMP_di32(regR, imm); + } else { debug_printf("PPCRecompilerX64Gen_imlInstruction_r_s32(): Unsupported operation 0x%x\n", imlInstruction->operation); @@ -688,29 +641,6 @@ bool PPCRecompilerX64Gen_imlInstruction_r_s32(PPCRecFunction_t* PPCRecFunction, return true; } -bool PPCRecompilerX64Gen_imlInstruction_conditional_r_s32(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) -{ - cemu_assert_unimplemented(); - //if (imlInstruction->operation == PPCREC_IML_OP_ASSIGN) - //{ - // // registerResult = immS32 (conditional) - // if (imlInstruction->crRegister != PPC_REC_INVALID_REGISTER) - // { - // assert_dbg(); - // } - - // x64Gen_mov_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, (uint32)imlInstruction->op_conditional_r_s32.immS32); - // uint8 crBitIndex = imlInstruction->op_conditional_r_s32.crRegisterIndex * 4 + imlInstruction->op_conditional_r_s32.crBitIndex; - // x64Gen_bt_mem8(x64GenContext, X86_REG_RSP, offsetof(PPCInterpreter_t, cr) + crBitIndex * sizeof(uint8), 0); - // if (imlInstruction->op_conditional_r_s32.bitMustBeSet) - // x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext, X86_CONDITION_CARRY, imlInstruction->op_conditional_r_s32.registerIndex, REG_RESV_TEMP); - // else - // x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext, X86_CONDITION_NOT_CARRY, imlInstruction->op_conditional_r_s32.registerIndex, REG_RESV_TEMP); - // return true; - //} - return false; -} - bool PPCRecompilerX64Gen_imlInstruction_r_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { auto rRegResult = _reg32(imlInstruction->op_r_r_r.regR); @@ -869,83 +799,41 @@ bool PPCRecompilerX64Gen_imlInstruction_r_r_r(PPCRecFunction_t* PPCRecFunction, imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_U || imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT) { - // x86's shift and rotate instruction have the shift amount hardwired to the CL register - // since our register allocator doesn't support instruction based fixed phys registers yet - // we'll instead have to temporarily shuffle registers around - - // we use BMI2's shift instructions until the RA can assign fixed registers - if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_S) + if(g_CPUFeatures.x86.bmi2) { - x64Gen_sarx_reg32_reg32_reg32(x64GenContext, rRegResult, rRegOperand1, rRegOperand2); + if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_S) + x64Gen_sarx_reg32_reg32_reg32(x64GenContext, rRegResult, rRegOperand1, rRegOperand2); + else if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_U) + x64Gen_shrx_reg32_reg32_reg32(x64GenContext, rRegResult, rRegOperand1, rRegOperand2); + else if (imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT) + x64Gen_shlx_reg32_reg32_reg32(x64GenContext, rRegResult, rRegOperand1, rRegOperand2); } - else if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_U) + else { - x64Gen_shrx_reg32_reg32_reg32(x64GenContext, rRegResult, rRegOperand1, rRegOperand2); + cemu_assert_debug(rRegOperand2 == X86_REG_ECX); + bool useTempReg = rRegResult == X86_REG_ECX && rRegOperand1 != X86_REG_ECX; + auto origRegResult = rRegResult; + if(useTempReg) + { + x64GenContext->emitter->MOV_dd(REG_RESV_TEMP, rRegOperand1); + rRegResult = REG_RESV_TEMP; + } + if(rRegOperand1 != rRegResult) + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand1); + if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_S) + x64GenContext->emitter->SAR_d_CL(rRegResult); + else if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_U) + x64GenContext->emitter->SHR_d_CL(rRegResult); + else if (imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT) + x64GenContext->emitter->SHL_d_CL(rRegResult); + if(useTempReg) + x64GenContext->emitter->MOV_dd(origRegResult, REG_RESV_TEMP); } - else if (imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT) - { - x64Gen_shlx_reg32_reg32_reg32(x64GenContext, rRegResult, rRegOperand1, rRegOperand2); - } - - //auto rResult = _reg32(rRegResult); - //auto rOp2 = _reg8_from_reg32(_reg32(rRegOperand2)); - - //if (rRegResult == rRegOperand2) - //{ - // if (rRegResult != rRegOperand1) - // DEBUG_BREAK; // cannot handle yet (we use rRegResult as a temporary reg, but its not possible if it is shared with op2) - //} - - //if(rRegOperand1 != rRegResult) - // x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand1); - - //cemu_assert_debug(rRegOperand1 != X86_REG_ECX); - - //if (rRegOperand2 == X86_REG_ECX) - //{ - // if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_S) - // x64GenContext->emitter->SAR_d_CL(rResult); - // else if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_U) - // x64GenContext->emitter->SHR_d_CL(rResult); - // else if (imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT) - // x64GenContext->emitter->SHL_d_CL(rResult); - // else - // cemu_assert_unimplemented(); - //} - //else - //{ - // auto rRegResultOrg = rRegResult; - // if (rRegResult == X86_REG_ECX) - // { - // x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegResult); - // rRegResult = REG_RESV_TEMP; - // rResult = _reg32(rRegResult); - // } - // - // x64Gen_xchg_reg64_reg64(x64GenContext, X86_REG_RCX, rRegOperand2); - // - // if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_S) - // x64GenContext->emitter->SAR_d_CL(rResult); - // else if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_U) - // x64GenContext->emitter->SHR_d_CL(rResult); - // else if (imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT) - // x64GenContext->emitter->SHL_d_CL(rResult); - // else - // cemu_assert_unimplemented(); - - // x64Gen_xchg_reg64_reg64(x64GenContext, X86_REG_RCX, rRegOperand2); - - // // move result back if it was in ECX - // if (rRegResultOrg == X86_REG_ECX) - // { - // x64Gen_mov_reg64_reg64(x64GenContext, rRegResultOrg, REG_RESV_TEMP); - // } - //} } else if( imlInstruction->operation == PPCREC_IML_OP_DIVIDE_SIGNED || imlInstruction->operation == PPCREC_IML_OP_DIVIDE_UNSIGNED ) { - x64Emit_mov_mem32_reg32(x64GenContext, X86_REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0]), X86_REG_EAX); - x64Emit_mov_mem32_reg32(x64GenContext, X86_REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[1]), X86_REG_EDX); + x64Emit_mov_mem32_reg32(x64GenContext, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0]), X86_REG_EAX); + x64Emit_mov_mem32_reg32(x64GenContext, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[1]), X86_REG_EDX); // mov operand 2 to temp register x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand2); // mov operand1 to EAX @@ -968,14 +856,14 @@ bool PPCRecompilerX64Gen_imlInstruction_r_r_r(PPCRecFunction_t* PPCRecFunction, x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, X86_REG_EAX); // restore EAX / EDX if( rRegResult != X86_REG_RAX ) - x64Emit_mov_reg64_mem32(x64GenContext, X86_REG_EAX, X86_REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0])); + x64Emit_mov_reg64_mem32(x64GenContext, X86_REG_EAX, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0])); if( rRegResult != X86_REG_RDX ) - x64Emit_mov_reg64_mem32(x64GenContext, X86_REG_EDX, X86_REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[1])); + x64Emit_mov_reg64_mem32(x64GenContext, X86_REG_EDX, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[1])); } else if( imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED || imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_HIGH_UNSIGNED ) { - x64Emit_mov_mem32_reg32(x64GenContext, X86_REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0]), X86_REG_EAX); - x64Emit_mov_mem32_reg32(x64GenContext, X86_REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[1]), X86_REG_EDX); + x64Emit_mov_mem32_reg32(x64GenContext, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0]), X86_REG_EAX); + x64Emit_mov_mem32_reg32(x64GenContext, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[1]), X86_REG_EDX); // mov operand 2 to temp register x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand2); // mov operand1 to EAX @@ -1000,9 +888,9 @@ bool PPCRecompilerX64Gen_imlInstruction_r_r_r(PPCRecFunction_t* PPCRecFunction, x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, X86_REG_EDX); // restore EAX / EDX if( rRegResult != X86_REG_RAX ) - x64Emit_mov_reg64_mem32(x64GenContext, X86_REG_EAX, X86_REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0])); + x64Emit_mov_reg64_mem32(x64GenContext, X86_REG_EAX, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0])); if( rRegResult != X86_REG_RDX ) - x64Emit_mov_reg64_mem32(x64GenContext, X86_REG_EDX, X86_REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[1])); + x64Emit_mov_reg64_mem32(x64GenContext, X86_REG_EDX, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[1])); } else { @@ -1018,7 +906,8 @@ bool PPCRecompilerX64Gen_imlInstruction_r_r_r_carry(PPCRecFunction_t* PPCRecFunc auto regA = _reg32(imlInstruction->op_r_r_r_carry.regA); auto regB = _reg32(imlInstruction->op_r_r_r_carry.regB); auto regCarry = _reg32(imlInstruction->op_r_r_r_carry.regCarry); - cemu_assert_debug(regCarry != regR && regCarry != regA); + bool carryRegIsShared = regCarry == regA || regCarry == regB; + cemu_assert_debug(regCarry != regR); // two outputs sharing the same register is undefined behavior switch (imlInstruction->operation) { @@ -1027,9 +916,12 @@ bool PPCRecompilerX64Gen_imlInstruction_r_r_r_carry(PPCRecFunction_t* PPCRecFunc std::swap(regB, regA); if (regR != regA) x64GenContext->emitter->MOV_dd(regR, regA); - x64GenContext->emitter->XOR_dd(regCarry, regCarry); + if(!carryRegIsShared) + x64GenContext->emitter->XOR_dd(regCarry, regCarry); x64GenContext->emitter->ADD_dd(regR, regB); x64GenContext->emitter->SETcc_b(X86_CONDITION_B, _reg8_from_reg32(regCarry)); // below condition checks carry flag + if(carryRegIsShared) + x64GenContext->emitter->AND_di8(regCarry, 1); // clear upper bits break; case PPCREC_IML_OP_ADD_WITH_CARRY: // assumes that carry is already correctly initialized as 0 or 1 @@ -1048,27 +940,72 @@ bool PPCRecompilerX64Gen_imlInstruction_r_r_r_carry(PPCRecFunction_t* PPCRecFunc return true; } -bool PPCRecompilerX64Gen_imlInstruction_compare(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) +bool PPCRecompilerX64Gen_IsSameCompare(IMLInstruction* imlInstructionA, IMLInstruction* imlInstructionB) { - auto regR = _reg8(imlInstruction->op_compare.regR); - auto regA = _reg32(imlInstruction->op_compare.regA); - auto regB = _reg32(imlInstruction->op_compare.regB); - X86Cond cond = _x86Cond(imlInstruction->op_compare.cond); - x64GenContext->emitter->XOR_dd(_reg32_from_reg8(regR), _reg32_from_reg8(regR)); // zero bytes unaffected by SETcc - x64GenContext->emitter->CMP_dd(regA, regB); - x64GenContext->emitter->SETcc_b(cond, regR); - return true; + if(imlInstructionA->type != imlInstructionB->type) + return false; + if(imlInstructionA->type == PPCREC_IML_TYPE_COMPARE) + return imlInstructionA->op_compare.regA == imlInstructionB->op_compare.regA && imlInstructionA->op_compare.regB == imlInstructionB->op_compare.regB; + else if(imlInstructionA->type == PPCREC_IML_TYPE_COMPARE_S32) + return imlInstructionA->op_compare_s32.regA == imlInstructionB->op_compare_s32.regA && imlInstructionA->op_compare_s32.immS32 == imlInstructionB->op_compare_s32.immS32; + return false; } -bool PPCRecompilerX64Gen_imlInstruction_compare_s32(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) +bool PPCRecompilerX64Gen_imlInstruction_compare_x(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, sint32& extraInstructionsProcessed) { - auto regR = _reg8(imlInstruction->op_compare_s32.regR); - auto regA = _reg32(imlInstruction->op_compare_s32.regA); - sint32 imm = imlInstruction->op_compare_s32.immS32; - X86Cond cond = _x86Cond(imlInstruction->op_compare_s32.cond); - x64GenContext->emitter->XOR_dd(_reg32_from_reg8(regR), _reg32_from_reg8(regR)); // zero bytes unaffected by SETcc - x64GenContext->emitter->CMP_di32(regA, imm); - x64GenContext->emitter->SETcc_b(cond, regR); + extraInstructionsProcessed = 0; + boost::container::static_vector compareInstructions; + compareInstructions.push_back(imlInstruction); + for(sint32 i=1; i<4; i++) + { + IMLInstruction* nextIns = x64GenContext->GetNextInstruction(i); + if(!nextIns || !PPCRecompilerX64Gen_IsSameCompare(imlInstruction, nextIns)) + break; + compareInstructions.push_back(nextIns); + } + auto OperandOverlapsWithR = [&](IMLInstruction* ins) -> bool + { + cemu_assert_debug(ins->type == PPCREC_IML_TYPE_COMPARE || ins->type == PPCREC_IML_TYPE_COMPARE_S32); + if(ins->type == PPCREC_IML_TYPE_COMPARE) + return _reg32_from_reg8(_reg8(ins->op_compare.regR)) == _reg32(ins->op_compare.regA) || _reg32_from_reg8(_reg8(ins->op_compare.regR)) == _reg32(ins->op_compare.regB); + else /* PPCREC_IML_TYPE_COMPARE_S32 */ + return _reg32_from_reg8(_reg8(ins->op_compare_s32.regR)) == _reg32(ins->op_compare_s32.regA); + }; + auto GetRegR = [](IMLInstruction* insn) + { + return insn->type == PPCREC_IML_TYPE_COMPARE ? _reg32_from_reg8(_reg8(insn->op_compare.regR)) : _reg32_from_reg8(_reg8(insn->op_compare_s32.regR)); + }; + // prefer XOR method for zeroing out registers if possible + for(auto& it : compareInstructions) + { + if(OperandOverlapsWithR(it)) + continue; + auto regR = GetRegR(it); + x64GenContext->emitter->XOR_dd(regR, regR); // zero bytes unaffected by SETcc + } + // emit the compare instruction + if(imlInstruction->type == PPCREC_IML_TYPE_COMPARE) + { + auto regA = _reg32(imlInstruction->op_compare.regA); + auto regB = _reg32(imlInstruction->op_compare.regB); + x64GenContext->emitter->CMP_dd(regA, regB); + } + else if(imlInstruction->type == PPCREC_IML_TYPE_COMPARE_S32) + { + auto regA = _reg32(imlInstruction->op_compare_s32.regA); + sint32 imm = imlInstruction->op_compare_s32.immS32; + x64GenContext->emitter->CMP_di32(regA, imm); + } + // emit the SETcc instructions + for(auto& it : compareInstructions) + { + auto regR = _reg8(it->op_compare.regR); + X86Cond cond = _x86Cond(it->op_compare.cond); + if(OperandOverlapsWithR(it)) + x64GenContext->emitter->MOV_di32(_reg32_from_reg8(regR), 0); + x64GenContext->emitter->SETcc_b(cond, regR); + } + extraInstructionsProcessed = (sint32)compareInstructions.size() - 1; return true; } @@ -1082,6 +1019,13 @@ bool PPCRecompilerX64Gen_imlInstruction_cjump2(PPCRecFunction_t* PPCRecFunction, return true; } +void PPCRecompilerX64Gen_imlInstruction_x86_eflags_jcc(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, IMLSegment* imlSegment) +{ + X86Cond cond = _x86Cond(imlInstruction->op_x86_eflags_jcc.cond, imlInstruction->op_x86_eflags_jcc.invertedCondition); + PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, imlSegment->nextSegmentBranchTaken); + x64GenContext->emitter->Jcc_j32(cond, 0); +} + bool PPCRecompilerX64Gen_imlInstruction_jump2(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, IMLSegment* imlSegment) { PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, imlSegment->nextSegmentBranchTaken); @@ -1099,13 +1043,13 @@ bool PPCRecompilerX64Gen_imlInstruction_r_r_s32(PPCRecFunction_t* PPCRecFunction { uint32 immU32 = (uint32)imlInstruction->op_r_r_s32.immS32; if(regR != regA) - x64Gen_mov_reg64_reg64(x64GenContext, regR, regA); + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, regR, regA); x64Gen_add_reg64Low32_imm32(x64GenContext, regR, (uint32)immU32); } else if (imlInstruction->operation == PPCREC_IML_OP_SUB) { if (regR != regA) - x64Gen_mov_reg64_reg64(x64GenContext, regR, regA); + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, regR, regA); x64Gen_sub_reg64Low32_imm32(x64GenContext, regR, immS32); } else if (imlInstruction->operation == PPCREC_IML_OP_AND || @@ -1113,7 +1057,7 @@ bool PPCRecompilerX64Gen_imlInstruction_r_r_s32(PPCRecFunction_t* PPCRecFunction imlInstruction->operation == PPCREC_IML_OP_XOR) { if (regR != regA) - x64Gen_mov_reg64_reg64(x64GenContext, regR, regA); + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, regR, regA); if (imlInstruction->operation == PPCREC_IML_OP_AND) x64Gen_and_reg64Low32_imm32(x64GenContext, regR, immS32); else if (imlInstruction->operation == PPCREC_IML_OP_OR) @@ -1121,33 +1065,13 @@ bool PPCRecompilerX64Gen_imlInstruction_r_r_s32(PPCRecFunction_t* PPCRecFunction else // XOR x64Gen_xor_reg64Low32_imm32(x64GenContext, regR, immS32); } - else if( imlInstruction->operation == PPCREC_IML_OP_RLWIMI ) - { - // registerResult = ((registerResult<<op_r_r_s32.immS32; - uint32 mb = (vImm>>0)&0xFF; - uint32 me = (vImm>>8)&0xFF; - uint32 sh = (vImm>>16)&0xFF; - uint32 mask = ppc_mask(mb, me); - // copy rS to temporary register - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, regA); - // rotate destination register - if( sh ) - x64Gen_rol_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (uint8)sh&0x1F); - // AND destination register with inverted mask - x64Gen_and_reg64Low32_imm32(x64GenContext, regR, ~mask); - // AND temporary rS register with mask - x64Gen_and_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, mask); - // OR result with temporary - x64Gen_or_reg64Low32_reg64Low32(x64GenContext, regR, REG_RESV_TEMP); - } else if( imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_SIGNED ) { // registerResult = registerOperand * immS32 sint32 immS32 = (uint32)imlInstruction->op_r_r_s32.immS32; x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_TEMP, (sint64)immS32); // todo: Optimize if( regR != regA ) - x64Gen_mov_reg64_reg64(x64GenContext, regR, regA); + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, regR, regA); x64Gen_imul_reg64Low32_reg64Low32(x64GenContext, regR, REG_RESV_TEMP); } else if (imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT || @@ -1155,8 +1079,7 @@ bool PPCRecompilerX64Gen_imlInstruction_r_r_s32(PPCRecFunction_t* PPCRecFunction imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_S) { if( regA != regR ) - x64Gen_mov_reg64_reg64(x64GenContext, regR, regA); - + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, regR, regA); if (imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT) x64Gen_shl_reg64Low32_imm8(x64GenContext, regR, imlInstruction->op_r_r_s32.immS32); else if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_U) @@ -1178,19 +1101,25 @@ bool PPCRecompilerX64Gen_imlInstruction_r_r_s32_carry(PPCRecFunction_t* PPCRecFu auto regA = _reg32(imlInstruction->op_r_r_s32_carry.regA); sint32 immS32 = imlInstruction->op_r_r_s32_carry.immS32; auto regCarry = _reg32(imlInstruction->op_r_r_s32_carry.regCarry); - cemu_assert_debug(regCarry != regR && regCarry != regA); + cemu_assert_debug(regCarry != regR); // we dont allow two different outputs sharing the same register + + bool delayCarryInit = regCarry == regA; switch (imlInstruction->operation) { case PPCREC_IML_OP_ADD: - x64GenContext->emitter->XOR_dd(regCarry, regCarry); + if(!delayCarryInit) + x64GenContext->emitter->XOR_dd(regCarry, regCarry); if (regR != regA) x64GenContext->emitter->MOV_dd(regR, regA); x64GenContext->emitter->ADD_di32(regR, immS32); + if(delayCarryInit) + x64GenContext->emitter->MOV_di32(regCarry, 0); x64GenContext->emitter->SETcc_b(X86_CONDITION_B, _reg8_from_reg32(regCarry)); break; case PPCREC_IML_OP_ADD_WITH_CARRY: // assumes that carry is already correctly initialized as 0 or 1 + cemu_assert_debug(regCarry != regR); if (regR != regA) x64GenContext->emitter->MOV_dd(regR, regA); x64GenContext->emitter->BT_du8(regCarry, 0); // copy carry register to x86 carry flag @@ -1211,7 +1140,7 @@ bool PPCRecompilerX64Gen_imlInstruction_conditionalJumpCycleCheck(PPCRecFunction // 2) CMP [mem], 0 + JG has about equal (or slightly worse) performance than BT + JNC // BT - x64Gen_bt_mem8(x64GenContext, X86_REG_RSP, offsetof(PPCInterpreter_t, remainingCycles), 31); // check if negative + x64Gen_bt_mem8(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, remainingCycles), 31); // check if negative cemu_assert_debug(x64GenContext->currentSegment->GetBranchTaken()); PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, x64GenContext->currentSegment->GetBranchTaken()); x64Gen_jmpc_far(x64GenContext, X86_CONDITION_CARRY, 0); @@ -1226,48 +1155,48 @@ void PPCRecompilerX64Gen_imlInstruction_r_name(PPCRecFunction_t* PPCRecFunction, auto regR = _reg64(imlInstruction->op_r_name.regR); if (name >= PPCREC_NAME_R0 && name < PPCREC_NAME_R0 + 32) { - x64Emit_mov_reg64_mem32(x64GenContext, regR, X86_REG_RSP, offsetof(PPCInterpreter_t, gpr) + sizeof(uint32) * (name - PPCREC_NAME_R0)); + x64Emit_mov_reg64_mem32(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, gpr) + sizeof(uint32) * (name - PPCREC_NAME_R0)); } else if (name >= PPCREC_NAME_SPR0 && name < PPCREC_NAME_SPR0 + 999) { sint32 sprIndex = (name - PPCREC_NAME_SPR0); if (sprIndex == SPR_LR) - x64Emit_mov_reg64_mem32(x64GenContext, regR, X86_REG_RSP, offsetof(PPCInterpreter_t, spr.LR)); + x64Emit_mov_reg64_mem32(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, spr.LR)); else if (sprIndex == SPR_CTR) - x64Emit_mov_reg64_mem32(x64GenContext, regR, X86_REG_RSP, offsetof(PPCInterpreter_t, spr.CTR)); + x64Emit_mov_reg64_mem32(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, spr.CTR)); else if (sprIndex == SPR_XER) - x64Emit_mov_reg64_mem32(x64GenContext, regR, X86_REG_RSP, offsetof(PPCInterpreter_t, spr.XER)); + x64Emit_mov_reg64_mem32(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, spr.XER)); else if (sprIndex >= SPR_UGQR0 && sprIndex <= SPR_UGQR7) { sint32 memOffset = offsetof(PPCInterpreter_t, spr.UGQR) + sizeof(PPCInterpreter_t::spr.UGQR[0]) * (sprIndex - SPR_UGQR0); - x64Emit_mov_reg64_mem32(x64GenContext, regR, X86_REG_RSP, memOffset); + x64Emit_mov_reg64_mem32(x64GenContext, regR, REG_RESV_HCPU, memOffset); } else assert_dbg(); } else if (name >= PPCREC_NAME_TEMPORARY && name < PPCREC_NAME_TEMPORARY + 4) { - x64Emit_mov_reg64_mem32(x64GenContext, regR, X86_REG_RSP, offsetof(PPCInterpreter_t, temporaryGPR_reg) + sizeof(uint32) * (name - PPCREC_NAME_TEMPORARY)); + x64Emit_mov_reg64_mem32(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryGPR_reg) + sizeof(uint32) * (name - PPCREC_NAME_TEMPORARY)); } else if (name == PPCREC_NAME_XER_CA) { - x64Emit_movZX_reg64_mem8(x64GenContext, regR, X86_REG_RSP, offsetof(PPCInterpreter_t, xer_ca)); + x64Emit_movZX_reg64_mem8(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, xer_ca)); } else if (name == PPCREC_NAME_XER_SO) { - x64Emit_movZX_reg64_mem8(x64GenContext, regR, X86_REG_RSP, offsetof(PPCInterpreter_t, xer_so)); + x64Emit_movZX_reg64_mem8(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, xer_so)); } else if (name >= PPCREC_NAME_CR && name <= PPCREC_NAME_CR_LAST) { - x64Emit_movZX_reg64_mem8(x64GenContext, regR, X86_REG_RSP, offsetof(PPCInterpreter_t, cr) + (name - PPCREC_NAME_CR)); + x64Emit_movZX_reg64_mem8(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, cr) + (name - PPCREC_NAME_CR)); } else if (name == PPCREC_NAME_CPU_MEMRES_EA) { - x64Emit_mov_reg64_mem32(x64GenContext, regR, X86_REG_RSP, offsetof(PPCInterpreter_t, reservedMemAddr)); + x64Emit_mov_reg64_mem32(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, reservedMemAddr)); } else if (name == PPCREC_NAME_CPU_MEMRES_VAL) { - x64Emit_mov_reg64_mem32(x64GenContext, regR, X86_REG_RSP, offsetof(PPCInterpreter_t, reservedMemValue)); + x64Emit_mov_reg64_mem32(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, reservedMemValue)); } else assert_dbg(); @@ -1277,11 +1206,11 @@ void PPCRecompilerX64Gen_imlInstruction_r_name(PPCRecFunction_t* PPCRecFunction, auto regR = _regF64(imlInstruction->op_r_name.regR); if (name >= PPCREC_NAME_FPR0 && name < (PPCREC_NAME_FPR0 + 32)) { - x64Gen_movupd_xmmReg_memReg128(x64GenContext, regR, X86_REG_ESP, offsetof(PPCInterpreter_t, fpr) + sizeof(FPR_t) * (name - PPCREC_NAME_FPR0)); + x64Gen_movupd_xmmReg_memReg128(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, fpr) + sizeof(FPR_t) * (name - PPCREC_NAME_FPR0)); } else if (name >= PPCREC_NAME_TEMPORARY_FPR0 || name < (PPCREC_NAME_TEMPORARY_FPR0 + 8)) { - x64Gen_movupd_xmmReg_memReg128(x64GenContext, regR, X86_REG_ESP, offsetof(PPCInterpreter_t, temporaryFPR) + sizeof(FPR_t) * (name - PPCREC_NAME_TEMPORARY_FPR0)); + x64Gen_movupd_xmmReg_memReg128(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR) + sizeof(FPR_t) * (name - PPCREC_NAME_TEMPORARY_FPR0)); } else { @@ -1302,48 +1231,48 @@ void PPCRecompilerX64Gen_imlInstruction_name_r(PPCRecFunction_t* PPCRecFunction, auto regR = _reg64(imlInstruction->op_r_name.regR); if (name >= PPCREC_NAME_R0 && name < PPCREC_NAME_R0 + 32) { - x64Emit_mov_mem32_reg64(x64GenContext, X86_REG_RSP, offsetof(PPCInterpreter_t, gpr) + sizeof(uint32) * (name - PPCREC_NAME_R0), regR); + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, gpr) + sizeof(uint32) * (name - PPCREC_NAME_R0), regR); } else if (name >= PPCREC_NAME_SPR0 && name < PPCREC_NAME_SPR0 + 999) { uint32 sprIndex = (name - PPCREC_NAME_SPR0); if (sprIndex == SPR_LR) - x64Emit_mov_mem32_reg64(x64GenContext, X86_REG_RSP, offsetof(PPCInterpreter_t, spr.LR), regR); + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, spr.LR), regR); else if (sprIndex == SPR_CTR) - x64Emit_mov_mem32_reg64(x64GenContext, X86_REG_RSP, offsetof(PPCInterpreter_t, spr.CTR), regR); + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, spr.CTR), regR); else if (sprIndex == SPR_XER) - x64Emit_mov_mem32_reg64(x64GenContext, X86_REG_RSP, offsetof(PPCInterpreter_t, spr.XER), regR); + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, spr.XER), regR); else if (sprIndex >= SPR_UGQR0 && sprIndex <= SPR_UGQR7) { sint32 memOffset = offsetof(PPCInterpreter_t, spr.UGQR) + sizeof(PPCInterpreter_t::spr.UGQR[0]) * (sprIndex - SPR_UGQR0); - x64Emit_mov_mem32_reg64(x64GenContext, X86_REG_RSP, memOffset, regR); + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, memOffset, regR); } else assert_dbg(); } else if (name >= PPCREC_NAME_TEMPORARY && name < PPCREC_NAME_TEMPORARY + 4) { - x64Emit_mov_mem32_reg64(x64GenContext, X86_REG_RSP, offsetof(PPCInterpreter_t, temporaryGPR_reg) + sizeof(uint32) * (name - PPCREC_NAME_TEMPORARY), regR); + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryGPR_reg) + sizeof(uint32) * (name - PPCREC_NAME_TEMPORARY), regR); } else if (name == PPCREC_NAME_XER_CA) { - x64GenContext->emitter->MOV_bb_l(X86_REG_RSP, offsetof(PPCInterpreter_t, xer_ca), X86_REG_NONE, 0, _reg8_from_reg64(regR)); + x64GenContext->emitter->MOV_bb_l(REG_RESV_HCPU, offsetof(PPCInterpreter_t, xer_ca), X86_REG_NONE, 0, _reg8_from_reg64(regR)); } else if (name == PPCREC_NAME_XER_SO) { - x64GenContext->emitter->MOV_bb_l(X86_REG_RSP, offsetof(PPCInterpreter_t, xer_so), X86_REG_NONE, 0, _reg8_from_reg64(regR)); + x64GenContext->emitter->MOV_bb_l(REG_RESV_HCPU, offsetof(PPCInterpreter_t, xer_so), X86_REG_NONE, 0, _reg8_from_reg64(regR)); } else if (name >= PPCREC_NAME_CR && name <= PPCREC_NAME_CR_LAST) { - x64GenContext->emitter->MOV_bb_l(X86_REG_RSP, offsetof(PPCInterpreter_t, cr) + (name - PPCREC_NAME_CR), X86_REG_NONE, 0, _reg8_from_reg64(regR)); + x64GenContext->emitter->MOV_bb_l(REG_RESV_HCPU, offsetof(PPCInterpreter_t, cr) + (name - PPCREC_NAME_CR), X86_REG_NONE, 0, _reg8_from_reg64(regR)); } else if (name == PPCREC_NAME_CPU_MEMRES_EA) { - x64Emit_mov_mem32_reg64(x64GenContext, X86_REG_RSP, offsetof(PPCInterpreter_t, reservedMemAddr), regR); + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, reservedMemAddr), regR); } else if (name == PPCREC_NAME_CPU_MEMRES_VAL) { - x64Emit_mov_mem32_reg64(x64GenContext, X86_REG_RSP, offsetof(PPCInterpreter_t, reservedMemValue), regR); + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, reservedMemValue), regR); } else assert_dbg(); @@ -1354,11 +1283,11 @@ void PPCRecompilerX64Gen_imlInstruction_name_r(PPCRecFunction_t* PPCRecFunction, uint32 name = imlInstruction->op_r_name.name; if (name >= PPCREC_NAME_FPR0 && name < (PPCREC_NAME_FPR0 + 32)) { - x64Gen_movupd_memReg128_xmmReg(x64GenContext, regR, X86_REG_ESP, offsetof(PPCInterpreter_t, fpr) + sizeof(FPR_t) * (name - PPCREC_NAME_FPR0)); + x64Gen_movupd_memReg128_xmmReg(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, fpr) + sizeof(FPR_t) * (name - PPCREC_NAME_FPR0)); } else if (name >= PPCREC_NAME_TEMPORARY_FPR0 && name < (PPCREC_NAME_TEMPORARY_FPR0 + 8)) { - x64Gen_movupd_memReg128_xmmReg(x64GenContext, regR, X86_REG_ESP, offsetof(PPCInterpreter_t, temporaryFPR) + sizeof(FPR_t) * (name - PPCREC_NAME_TEMPORARY_FPR0)); + x64Gen_movupd_memReg128_xmmReg(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR) + sizeof(FPR_t) * (name - PPCREC_NAME_TEMPORARY_FPR0)); } else { @@ -1371,42 +1300,6 @@ void PPCRecompilerX64Gen_imlInstruction_name_r(PPCRecFunction_t* PPCRecFunction, } -//void PPCRecompilerX64Gen_imlInstruction_fpr_r_name(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) -//{ -// uint32 name = imlInstruction->op_r_name.name; -// uint32 fprReg = _regF64(imlInstruction->op_r_name.regR); -// if (name >= PPCREC_NAME_FPR0 && name < (PPCREC_NAME_FPR0 + 32)) -// { -// x64Gen_movupd_xmmReg_memReg128(x64GenContext, fprReg, X86_REG_ESP, offsetof(PPCInterpreter_t, fpr) + sizeof(FPR_t) * (name - PPCREC_NAME_FPR0)); -// } -// else if (name >= PPCREC_NAME_TEMPORARY_FPR0 || name < (PPCREC_NAME_TEMPORARY_FPR0 + 8)) -// { -// x64Gen_movupd_xmmReg_memReg128(x64GenContext, fprReg, X86_REG_ESP, offsetof(PPCInterpreter_t, temporaryFPR) + sizeof(FPR_t) * (name - PPCREC_NAME_TEMPORARY_FPR0)); -// } -// else -// { -// cemu_assert_debug(false); -// } -//} -// -//void PPCRecompilerX64Gen_imlInstruction_fpr_name_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) -//{ -// uint32 name = imlInstruction->op_r_name.name; -// uint32 fprReg = _regF64(imlInstruction->op_r_name.regR); -// if (name >= PPCREC_NAME_FPR0 && name < (PPCREC_NAME_FPR0 + 32)) -// { -// x64Gen_movupd_memReg128_xmmReg(x64GenContext, fprReg, X86_REG_ESP, offsetof(PPCInterpreter_t, fpr) + sizeof(FPR_t) * (name - PPCREC_NAME_FPR0)); -// } -// else if (name >= PPCREC_NAME_TEMPORARY_FPR0 && name < (PPCREC_NAME_TEMPORARY_FPR0 + 8)) -// { -// x64Gen_movupd_memReg128_xmmReg(x64GenContext, fprReg, X86_REG_ESP, offsetof(PPCInterpreter_t, temporaryFPR) + sizeof(FPR_t) * (name - PPCREC_NAME_TEMPORARY_FPR0)); -// } -// else -// { -// cemu_assert_debug(false); -// } -//} - uint8* codeMemoryBlock = nullptr; sint32 codeMemoryBlockIndex = 0; sint32 codeMemoryBlockSize = 0; @@ -1446,6 +1339,7 @@ bool PPCRecompiler_generateX64Code(PPCRecFunction_t* PPCRecFunction, ppcImlGenCo segIt->x64Offset = x64GenContext.emitter->GetWriteIndex(); for(size_t i=0; iimlList.size(); i++) { + x64GenContext.m_currentInstructionEmitIndex = i; IMLInstruction* imlInstruction = segIt->imlList.data() + i; if( imlInstruction->type == PPCREC_IML_TYPE_R_NAME ) @@ -1466,11 +1360,6 @@ bool PPCRecompiler_generateX64Code(PPCRecFunction_t* PPCRecFunction, ppcImlGenCo if (PPCRecompilerX64Gen_imlInstruction_r_s32(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false) codeGenerationFailed = true; } - else if (imlInstruction->type == PPCREC_IML_TYPE_CONDITIONAL_R_S32) - { - if (PPCRecompilerX64Gen_imlInstruction_conditional_r_s32(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false) - codeGenerationFailed = true; - } else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_S32) { if (PPCRecompilerX64Gen_imlInstruction_r_r_s32(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false) @@ -1491,19 +1380,21 @@ bool PPCRecompiler_generateX64Code(PPCRecFunction_t* PPCRecFunction, ppcImlGenCo if (PPCRecompilerX64Gen_imlInstruction_r_r_r_carry(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false) codeGenerationFailed = true; } - else if (imlInstruction->type == PPCREC_IML_TYPE_COMPARE) + else if (imlInstruction->type == PPCREC_IML_TYPE_COMPARE || imlInstruction->type == PPCREC_IML_TYPE_COMPARE_S32) { - PPCRecompilerX64Gen_imlInstruction_compare(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_COMPARE_S32) - { - PPCRecompilerX64Gen_imlInstruction_compare_s32(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); + sint32 extraInstructionsProcessed; + PPCRecompilerX64Gen_imlInstruction_compare_x(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, extraInstructionsProcessed); + i += extraInstructionsProcessed; } else if (imlInstruction->type == PPCREC_IML_TYPE_CONDITIONAL_JUMP) { if (PPCRecompilerX64Gen_imlInstruction_cjump2(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, segIt) == false) codeGenerationFailed = true; } + else if(imlInstruction->type == PPCREC_IML_TYPE_X86_EFLAGS_JCC) + { + PPCRecompilerX64Gen_imlInstruction_x86_eflags_jcc(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, segIt); + } else if (imlInstruction->type == PPCREC_IML_TYPE_JUMP) { if (PPCRecompilerX64Gen_imlInstruction_jump2(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, segIt) == false) @@ -1550,8 +1441,11 @@ bool PPCRecompiler_generateX64Code(PPCRecFunction_t* PPCRecFunction, ppcImlGenCo } else if (imlInstruction->type == PPCREC_IML_TYPE_ATOMIC_CMP_STORE) { - if (!PPCRecompilerX64Gen_imlInstruction_atomic_cmp_store(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction)) - codeGenerationFailed = true; + PPCRecompilerX64Gen_imlInstruction_atomic_cmp_store(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_CALL_IMM) + { + PPCRecompilerX64Gen_imlInstruction_call_imm(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); } else if( imlInstruction->type == PPCREC_IML_TYPE_NO_OP ) { @@ -1676,7 +1570,7 @@ void PPCRecompilerX64Gen_generateEnterRecompilerCode() { x64GenContext_t x64GenContext{}; - // start of recompiler entry function + // start of recompiler entry function (15 regs) x64Gen_push_reg64(&x64GenContext, X86_REG_RAX); x64Gen_push_reg64(&x64GenContext, X86_REG_RCX); x64Gen_push_reg64(&x64GenContext, X86_REG_RDX); @@ -1708,13 +1602,12 @@ void PPCRecompilerX64Gen_generateEnterRecompilerCode() x64Gen_writeU8(&x64GenContext, 0); // skip the distance until after the JMP x64Emit_mov_mem64_reg64(&x64GenContext, X86_REG_RDX, offsetof(PPCInterpreter_t, rspTemp), X86_REG_RSP); - // MOV RSP, RDX (ppc interpreter instance) - x64Gen_mov_reg64_reg64(&x64GenContext, X86_REG_RSP, X86_REG_RDX); + x64Gen_mov_reg64_reg64(&x64GenContext, REG_RESV_HCPU, X86_REG_RDX); // MOV R15, ppcRecompilerInstanceData - x64Gen_mov_reg64_imm64(&x64GenContext, X86_REG_R15, (uint64)ppcRecompilerInstanceData); + x64Gen_mov_reg64_imm64(&x64GenContext, REG_RESV_RECDATA, (uint64)ppcRecompilerInstanceData); // MOV R13, memory_base - x64Gen_mov_reg64_imm64(&x64GenContext, X86_REG_R13, (uint64)memory_base); + x64Gen_mov_reg64_imm64(&x64GenContext, REG_RESV_MEMBASE, (uint64)memory_base); //JMP recFunc x64Gen_jmp_reg64(&x64GenContext, X86_REG_RCX); // call argument 1 @@ -1753,11 +1646,9 @@ void* PPCRecompilerX64Gen_generateLeaveRecompilerCode() // update instruction pointer // LR is in EDX - x64Emit_mov_mem32_reg32(&x64GenContext, X86_REG_RSP, offsetof(PPCInterpreter_t, instructionPointer), X86_REG_EDX); - - // MOV RSP, [ppcRecompilerX64_rspTemp] + x64Emit_mov_mem32_reg32(&x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, instructionPointer), X86_REG_EDX); + // MOV RSP, [hCPU->rspTemp] x64Emit_mov_reg64_mem64(&x64GenContext, X86_REG_RSP, REG_RESV_HCPU, offsetof(PPCInterpreter_t, rspTemp)); - // RET x64Gen_ret(&x64GenContext); diff --git a/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.h b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.h index 1a0fffec..e4d1f5a9 100644 --- a/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.h +++ b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.h @@ -15,6 +15,7 @@ struct x64GenContext_t { IMLSegment* currentSegment{}; x86Assembler64* emitter; + sint32 m_currentInstructionEmitIndex; x64GenContext_t() { @@ -26,6 +27,14 @@ struct x64GenContext_t delete emitter; } + IMLInstruction* GetNextInstruction(sint32 relativeIndex = 1) + { + sint32 index = m_currentInstructionEmitIndex + relativeIndex; + if(index < 0 || index >= (sint32)currentSegment->imlList.size()) + return nullptr; + return currentSegment->imlList.data() + index; + } + // relocate offsets std::vector relocateOffsetTable2; }; @@ -62,9 +71,6 @@ enum X86_CONDITION_NONE, // no condition, jump always }; -#define PPC_X64_GPR_USABLE_REGISTERS (16-4) -#define PPC_X64_FPR_USABLE_REGISTERS (16-1) // Use XMM0 - XMM14, XMM15 is the temp register - bool PPCRecompiler_generateX64Code(struct PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext); void PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext_t* x64GenContext, sint32 jumpInstructionOffset, sint32 destinationOffset); diff --git a/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64FPU.cpp b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64FPU.cpp index cff46a2d..4d9a538d 100644 --- a/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64FPU.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64FPU.cpp @@ -70,7 +70,7 @@ void PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext_t* ppcImlGenContext, assert_dbg(); } // optimized code for ps float load - x64Emit_mov_reg64_mem64(x64GenContext, REG_RESV_TEMP, X86_REG_R13, memReg, memImmS32); + x64Emit_mov_reg64_mem64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, memReg, memImmS32); x64GenContext->emitter->BSWAP_q(REG_RESV_TEMP); x64Gen_rol_reg64_imm8(x64GenContext, REG_RESV_TEMP, 32); // swap upper and lower DWORD x64Gen_movq_xmmReg_reg64(x64GenContext, registerXMM, REG_RESV_TEMP); @@ -111,8 +111,8 @@ void PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext_t* ppcImlGenContext, } else { - x64Emit_mov_mem32_reg64(x64GenContext, X86_REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR), REG_RESV_TEMP); - x64Gen_movddup_xmmReg_memReg64(x64GenContext, REG_RESV_FPR_TEMP, X86_REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR), REG_RESV_TEMP); + x64Gen_movddup_xmmReg_memReg64(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)); } x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_FPR_TEMP); // load constant 1.0 into lower half and upper half of temp register @@ -174,7 +174,7 @@ void PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext_t* ppcImlGenContext, if (readSize == 16) { // half word - x64Gen_movZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext, REG_RESV_TEMP, X86_REG_R13, memReg, memOffset); + x64Gen_movZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, memReg, memOffset); x64Gen_rol_reg64Low16_imm8(x64GenContext, REG_RESV_TEMP, 8); // endian swap if (isSigned) x64Gen_movSignExtend_reg64Low32_reg64Low16(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP); @@ -184,7 +184,7 @@ void PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext_t* ppcImlGenContext, else if (readSize == 8) { // byte - x64Emit_mov_reg64b_mem8(x64GenContext, REG_RESV_TEMP, X86_REG_R13, memReg, memOffset); + x64Emit_mov_reg64b_mem8(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, memReg, memOffset); if (isSigned) x64Gen_movSignExtend_reg64Low32_reg64Low8(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP); else @@ -312,14 +312,14 @@ bool PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction_t* PPCRecFunctio x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem); x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem2); // load value - x64Emit_mov_reg64_mem64(x64GenContext, REG_RESV_TEMP, X86_REG_R13, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32+0); + x64Emit_mov_reg64_mem64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32+0); x64GenContext->emitter->BSWAP_q(REG_RESV_TEMP); x64Gen_movq_xmmReg_reg64(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_TEMP); x64Gen_movsd_xmmReg_xmmReg(x64GenContext, realRegisterXMM, REG_RESV_FPR_TEMP); } else { - x64Emit_mov_reg64_mem64(x64GenContext, REG_RESV_TEMP, X86_REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32+0); + x64Emit_mov_reg64_mem64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32+0); x64GenContext->emitter->BSWAP_q(REG_RESV_TEMP); x64Gen_movq_xmmReg_reg64(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_TEMP); x64Gen_movsd_xmmReg_xmmReg(x64GenContext, realRegisterXMM, REG_RESV_FPR_TEMP); @@ -333,31 +333,31 @@ bool PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction_t* PPCRecFunctio x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem); x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem2); // load double low part to temporaryFPR - x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, X86_REG_R13, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32+0); + x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32+0); x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); - x64Emit_mov_mem32_reg64(x64GenContext, X86_REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)+4, REG_RESV_TEMP); + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)+4, REG_RESV_TEMP); // calculate offset again x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem); x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem2); // load double high part to temporaryFPR - x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, X86_REG_R13, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32+4); + x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32+4); x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); - x64Emit_mov_mem32_reg64(x64GenContext, X86_REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)+0, REG_RESV_TEMP); + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)+0, REG_RESV_TEMP); // load double from temporaryFPR - x64Gen_movlpd_xmmReg_memReg64(x64GenContext, realRegisterXMM, X86_REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); + x64Gen_movlpd_xmmReg_memReg64(x64GenContext, realRegisterXMM, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)); } else { // load double low part to temporaryFPR - x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, X86_REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32+0); + x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32+0); x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); - x64Emit_mov_mem32_reg64(x64GenContext, X86_REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)+4, REG_RESV_TEMP); + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)+4, REG_RESV_TEMP); // load double high part to temporaryFPR - x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, X86_REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32+4); + x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32+4); x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); - x64Emit_mov_mem32_reg64(x64GenContext, X86_REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)+0, REG_RESV_TEMP); + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)+0, REG_RESV_TEMP); // load double from temporaryFPR - x64Gen_movlpd_xmmReg_memReg64(x64GenContext, realRegisterXMM, X86_REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); + x64Gen_movlpd_xmmReg_memReg64(x64GenContext, realRegisterXMM, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)); } } } @@ -416,9 +416,9 @@ void PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext_t* ppcImlGenContext x64Gen_add_reg64Low32_reg64Low32(x64GenContext, memReg, memRegEx); } if (g_CPUFeatures.x86.movbe) - x64Gen_movBETruncate_mem32Reg64PlusReg64_reg64(x64GenContext, X86_REG_R13, memReg, memImmS32, REG_RESV_TEMP); + x64Gen_movBETruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, memReg, memImmS32, REG_RESV_TEMP); else - x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, X86_REG_R13, memReg, memImmS32, REG_RESV_TEMP); + x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, memReg, memImmS32, REG_RESV_TEMP); if (indexed) { x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, memReg, memRegEx); @@ -433,7 +433,7 @@ void PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext_t* ppcImlGenContext x64Gen_movq_reg64_xmmReg(x64GenContext, REG_RESV_TEMP, REG_RESV_FPR_TEMP); x64Gen_rol_reg64_imm8(x64GenContext, REG_RESV_TEMP, 32); // swap upper and lower DWORD x64GenContext->emitter->BSWAP_q(REG_RESV_TEMP); - x64Gen_mov_mem64Reg64PlusReg64_reg64(x64GenContext, REG_RESV_TEMP, X86_REG_R13, memReg, memImmS32); + x64Gen_mov_mem64Reg64PlusReg64_reg64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, memReg, memImmS32); return; } // store as integer @@ -599,9 +599,9 @@ bool PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction_t* PPCRecFuncti x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); } if(g_CPUFeatures.x86.movbe) - x64Gen_movBETruncate_mem32Reg64PlusReg64_reg64(x64GenContext, X86_REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); + x64Gen_movBETruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); else - x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, X86_REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); + x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); if( indexed ) { x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); @@ -615,15 +615,15 @@ bool PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction_t* PPCRecFuncti assert_dbg(); x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); } - x64Gen_movsd_memReg64_xmmReg(x64GenContext, realRegisterXMM, X86_REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); + x64Gen_movsd_memReg64_xmmReg(x64GenContext, realRegisterXMM, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)); // store double low part - x64Emit_mov_reg64_mem32(x64GenContext, REG_RESV_TEMP, X86_REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)+0); + x64Emit_mov_reg64_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)+0); x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); - x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, X86_REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32+4, REG_RESV_TEMP); + x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32+4, REG_RESV_TEMP); // store double high part - x64Emit_mov_reg64_mem32(x64GenContext, REG_RESV_TEMP, X86_REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)+4); + x64Emit_mov_reg64_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)+4); x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); - x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, X86_REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32+0, REG_RESV_TEMP); + x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32+0, REG_RESV_TEMP); if( indexed ) { x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); @@ -635,15 +635,14 @@ bool PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction_t* PPCRecFuncti x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); if( indexed ) { - if( realRegisterMem == realRegisterMem2 ) - assert_dbg(); + cemu_assert_debug(realRegisterMem == realRegisterMem2); x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, X86_REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); + x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); } else { - x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, X86_REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); + x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); } } else if(mode == PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1 || @@ -780,18 +779,6 @@ void PPCRecompilerX64Gen_imlInstruction_fpr_r_r(PPCRecFunction_t* PPCRecFunction // move to FPR register x64Gen_movq_xmmReg_reg64(x64GenContext, regR, REG_RESV_TEMP); } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_BOTTOM_FRES_TO_BOTTOM_AND_TOP ) - { - // move register to XMM15 - x64Gen_movsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regA); - - // call assembly routine to calculate accurate FRES result in XMM15 - x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_TEMP, (uint64)recompiler_fres); - x64Gen_call_reg64(x64GenContext, REG_RESV_TEMP); - - // copy result to bottom and top half of result register - x64Gen_movddup_xmmReg_xmmReg(x64GenContext, regR, REG_RESV_FPR_TEMP); - } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_BOTTOM_RECIPROCAL_SQRT) { // move register to XMM15 diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IML.h b/src/Cafe/HW/Espresso/Recompiler/IML/IML.h index b58fdfa8..bc0c27c5 100644 --- a/src/Cafe/HW/Espresso/Recompiler/IML/IML.h +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IML.h @@ -3,14 +3,14 @@ #include "IMLInstruction.h" #include "IMLSegment.h" -// analyzer -bool IMLAnalyzer_IsTightFiniteLoop(IMLSegment* imlSegment); - // optimizer passes void IMLOptimizer_OptimizeDirectFloatCopies(struct ppcImlGenContext_t* ppcImlGenContext); void IMLOptimizer_OptimizeDirectIntegerCopies(struct ppcImlGenContext_t* ppcImlGenContext); void PPCRecompiler_optimizePSQLoadAndStore(struct ppcImlGenContext_t* ppcImlGenContext); +void IMLOptimizer_StandardOptimizationPass(ppcImlGenContext_t& ppcImlGenContext); + // debug +void IMLDebug_DisassembleInstruction(const IMLInstruction& inst, std::string& disassemblyLineOut); void IMLDebug_DumpSegment(struct ppcImlGenContext_t* ctx, IMLSegment* imlSegment, bool printLivenessRangeInfo = false); void IMLDebug_Dump(struct ppcImlGenContext_t* ppcImlGenContext, bool printLivenessRangeInfo = false); diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLAnalyzer.cpp b/src/Cafe/HW/Espresso/Recompiler/IML/IMLAnalyzer.cpp index 77403e1b..6ae4b591 100644 --- a/src/Cafe/HW/Espresso/Recompiler/IML/IMLAnalyzer.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLAnalyzer.cpp @@ -3,53 +3,3 @@ #include "util/helpers/fixedSizeList.h" #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" - -/* - * Analyzes a single segment and returns true if it is a finite loop - */ -bool IMLAnalyzer_IsTightFiniteLoop(IMLSegment* imlSegment) -{ - return false; // !!! DISABLED !!! - - bool isTightFiniteLoop = false; - // base criteria, must jump to beginning of same segment - if (imlSegment->nextSegmentBranchTaken != imlSegment) - return false; - // loops using BDNZ are assumed to always be finite - for(const IMLInstruction& instIt : imlSegment->imlList) - { - if (instIt.type == PPCREC_IML_TYPE_R_S32 && instIt.operation == PPCREC_IML_OP_SUB) - { - return true; - } - } - // for non-BDNZ loops, check for common patterns - // risky approach, look for ADD/SUB operations and assume that potential overflow means finite (does not include r_r_s32 ADD/SUB) - // this catches most loops with load-update and store-update instructions, but also those with decrementing counters - FixedSizeList list_modifiedRegisters; - for (const IMLInstruction& instIt : imlSegment->imlList) - { - if (instIt.type == PPCREC_IML_TYPE_R_S32 && (instIt.operation == PPCREC_IML_OP_ADD || instIt.operation == PPCREC_IML_OP_SUB) ) - { - list_modifiedRegisters.addUnique(instIt.op_r_immS32.regR); - } - } - if (list_modifiedRegisters.count > 0) - { - // remove all registers from the list that are modified by non-ADD/SUB instructions - // todo: We should also cover the case where ADD+SUB on the same register cancel the effect out - IMLUsedRegisters registersUsed; - for (const IMLInstruction& instIt : imlSegment->imlList) - { - if (instIt.type == PPCREC_IML_TYPE_R_S32 && (instIt.operation == PPCREC_IML_OP_ADD || instIt.operation == PPCREC_IML_OP_SUB)) - continue; - instIt.CheckRegisterUsage(®istersUsed); - registersUsed.ForEachWrittenGPR([&](IMLReg r) { list_modifiedRegisters.remove(r); }); - } - if (list_modifiedRegisters.count > 0) - { - return true; - } - } - return false; -} \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLDebug.cpp b/src/Cafe/HW/Espresso/Recompiler/IML/IMLDebug.cpp index d295f0aa..07fd4002 100644 --- a/src/Cafe/HW/Espresso/Recompiler/IML/IMLDebug.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLDebug.cpp @@ -75,12 +75,14 @@ void IMLDebug_AppendRegisterParam(StringBuf& strOutput, IMLReg virtualRegister, void IMLDebug_AppendS32Param(StringBuf& strOutput, sint32 val, bool isLast = false) { - if (isLast) + if (val < 0) { - strOutput.addFmt("0x{:08x}", val); - return; + strOutput.add("-"); + val = -val; } - strOutput.addFmt("0x{:08x}, ", val); + strOutput.addFmt("0x{:08x}", val); + if (!isLast) + strOutput.add(", "); } void IMLDebug_PrintLivenessRangeInfo(StringBuf& currentLineText, IMLSegment* imlSegment, sint32 offset) @@ -89,38 +91,39 @@ void IMLDebug_PrintLivenessRangeInfo(StringBuf& currentLineText, IMLSegment* iml sint32 index = currentLineText.getLen(); while (index < 70) { - debug_printf(" "); + currentLineText.add(" "); index++; } - raLivenessSubrange_t* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + raLivenessRange* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; while (subrangeItr) { - if (offset == subrangeItr->start.index) + if (subrangeItr->interval.start.GetInstructionIndexEx() == offset) { - if (false)//subrange->isDirtied && i == subrange->becomesDirtyAtIndex.index) - { - debug_printf("*%-2d", subrangeItr->range->virtualRegister); - } + if(subrangeItr->interval.start.IsInstructionIndex() && !subrangeItr->interval.start.IsOnInputEdge()) + currentLineText.add("."); else - { - debug_printf("|%-2d", subrangeItr->range->virtualRegister); - } + currentLineText.add("|"); + + currentLineText.addFmt("{:<4}", subrangeItr->GetVirtualRegister()); } - else if (false)//subrange->isDirtied && i == subrange->becomesDirtyAtIndex.index ) + else if (subrangeItr->interval.end.GetInstructionIndexEx() == offset) { - debug_printf("* "); + if(subrangeItr->interval.end.IsInstructionIndex() && !subrangeItr->interval.end.IsOnOutputEdge()) + currentLineText.add("* "); + else + currentLineText.add("| "); } - else if (offset >= subrangeItr->start.index && offset < subrangeItr->end.index) + else if (subrangeItr->interval.ContainsInstructionIndexEx(offset)) { - debug_printf("| "); + currentLineText.add("| "); } else { - debug_printf(" "); + currentLineText.add(" "); } - index += 3; + index += 5; // next - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + subrangeItr = subrangeItr->link_allSegmentRanges.next; } } @@ -163,37 +166,300 @@ std::string IMLDebug_GetConditionName(IMLCondition cond) return "ukn"; } +void IMLDebug_DisassembleInstruction(const IMLInstruction& inst, std::string& disassemblyLineOut) +{ + const sint32 lineOffsetParameters = 10;//18; + + StringBuf strOutput(1024); + strOutput.reset(); + if (inst.type == PPCREC_IML_TYPE_R_NAME || inst.type == PPCREC_IML_TYPE_NAME_R) + { + if (inst.type == PPCREC_IML_TYPE_R_NAME) + strOutput.add("R_NAME"); + else + strOutput.add("NAME_R"); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + + if(inst.type == PPCREC_IML_TYPE_R_NAME) + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_name.regR); + + strOutput.add("name_"); + if (inst.op_r_name.name >= PPCREC_NAME_R0 && inst.op_r_name.name < (PPCREC_NAME_R0 + 999)) + { + strOutput.addFmt("r{}", inst.op_r_name.name - PPCREC_NAME_R0); + } + else if (inst.op_r_name.name >= PPCREC_NAME_FPR0 && inst.op_r_name.name < (PPCREC_NAME_FPR0 + 999)) + { + strOutput.addFmt("f{}", inst.op_r_name.name - PPCREC_NAME_FPR0); + } + else if (inst.op_r_name.name >= PPCREC_NAME_SPR0 && inst.op_r_name.name < (PPCREC_NAME_SPR0 + 999)) + { + strOutput.addFmt("spr{}", inst.op_r_name.name - PPCREC_NAME_SPR0); + } + else if (inst.op_r_name.name >= PPCREC_NAME_CR && inst.op_r_name.name <= PPCREC_NAME_CR_LAST) + strOutput.addFmt("cr{}", inst.op_r_name.name - PPCREC_NAME_CR); + else if (inst.op_r_name.name == PPCREC_NAME_XER_CA) + strOutput.add("xer.ca"); + else if (inst.op_r_name.name == PPCREC_NAME_XER_SO) + strOutput.add("xer.so"); + else if (inst.op_r_name.name == PPCREC_NAME_XER_OV) + strOutput.add("xer.ov"); + else if (inst.op_r_name.name == PPCREC_NAME_CPU_MEMRES_EA) + strOutput.add("cpuReservation.ea"); + else if (inst.op_r_name.name == PPCREC_NAME_CPU_MEMRES_VAL) + strOutput.add("cpuReservation.value"); + else + { + strOutput.addFmt("name_ukn{}", inst.op_r_name.name); + } + if (inst.type != PPCREC_IML_TYPE_R_NAME) + { + strOutput.add(", "); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_name.regR, true); + } + + } + else if (inst.type == PPCREC_IML_TYPE_R_R) + { + strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst)); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r.regR); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r.regA, true); + } + else if (inst.type == PPCREC_IML_TYPE_R_R_R) + { + strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst)); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r.regR); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r.regA); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r.regB, true); + } + else if (inst.type == PPCREC_IML_TYPE_R_R_R_CARRY) + { + strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst)); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r_carry.regR); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r_carry.regA); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r_carry.regB); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r_carry.regCarry, true); + } + else if (inst.type == PPCREC_IML_TYPE_COMPARE) + { + strOutput.add("CMP "); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + IMLDebug_AppendRegisterParam(strOutput, inst.op_compare.regA); + IMLDebug_AppendRegisterParam(strOutput, inst.op_compare.regB); + strOutput.addFmt("{}", IMLDebug_GetConditionName(inst.op_compare.cond)); + strOutput.add(" -> "); + IMLDebug_AppendRegisterParam(strOutput, inst.op_compare.regR, true); + } + else if (inst.type == PPCREC_IML_TYPE_COMPARE_S32) + { + strOutput.add("CMP "); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + IMLDebug_AppendRegisterParam(strOutput, inst.op_compare_s32.regA); + strOutput.addFmt("{}", inst.op_compare_s32.immS32); + strOutput.addFmt(", {}", IMLDebug_GetConditionName(inst.op_compare_s32.cond)); + strOutput.add(" -> "); + IMLDebug_AppendRegisterParam(strOutput, inst.op_compare_s32.regR, true); + } + else if (inst.type == PPCREC_IML_TYPE_CONDITIONAL_JUMP) + { + strOutput.add("CJUMP "); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + IMLDebug_AppendRegisterParam(strOutput, inst.op_conditional_jump.registerBool, true); + if (!inst.op_conditional_jump.mustBeTrue) + strOutput.add("(inverted)"); + } + else if (inst.type == PPCREC_IML_TYPE_JUMP) + { + strOutput.add("JUMP"); + } + else if (inst.type == PPCREC_IML_TYPE_R_R_S32) + { + strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst)); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32.regR); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32.regA); + IMLDebug_AppendS32Param(strOutput, inst.op_r_r_s32.immS32, true); + } + else if (inst.type == PPCREC_IML_TYPE_R_R_S32_CARRY) + { + strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst)); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32_carry.regR); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32_carry.regA); + IMLDebug_AppendS32Param(strOutput, inst.op_r_r_s32_carry.immS32); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32_carry.regCarry, true); + } + else if (inst.type == PPCREC_IML_TYPE_R_S32) + { + strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst)); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_immS32.regR); + IMLDebug_AppendS32Param(strOutput, inst.op_r_immS32.immS32, true); + } + else if (inst.type == PPCREC_IML_TYPE_LOAD || inst.type == PPCREC_IML_TYPE_STORE || + inst.type == PPCREC_IML_TYPE_LOAD_INDEXED || inst.type == PPCREC_IML_TYPE_STORE_INDEXED) + { + if (inst.type == PPCREC_IML_TYPE_LOAD || inst.type == PPCREC_IML_TYPE_LOAD_INDEXED) + strOutput.add("LD_"); + else + strOutput.add("ST_"); + + if (inst.op_storeLoad.flags2.signExtend) + strOutput.add("S"); + else + strOutput.add("U"); + strOutput.addFmt("{}", inst.op_storeLoad.copyWidth); + + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + + IMLDebug_AppendRegisterParam(strOutput, inst.op_storeLoad.registerData); + + if (inst.type == PPCREC_IML_TYPE_LOAD_INDEXED || inst.type == PPCREC_IML_TYPE_STORE_INDEXED) + strOutput.addFmt("[{}+{}]", IMLDebug_GetRegName(inst.op_storeLoad.registerMem), IMLDebug_GetRegName(inst.op_storeLoad.registerMem2)); + else + strOutput.addFmt("[{}+{}]", IMLDebug_GetRegName(inst.op_storeLoad.registerMem), inst.op_storeLoad.immS32); + } + else if (inst.type == PPCREC_IML_TYPE_ATOMIC_CMP_STORE) + { + strOutput.add("ATOMIC_ST_U32"); + + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + + IMLDebug_AppendRegisterParam(strOutput, inst.op_atomic_compare_store.regEA); + IMLDebug_AppendRegisterParam(strOutput, inst.op_atomic_compare_store.regCompareValue); + IMLDebug_AppendRegisterParam(strOutput, inst.op_atomic_compare_store.regWriteValue); + IMLDebug_AppendRegisterParam(strOutput, inst.op_atomic_compare_store.regBoolOut, true); + } + else if (inst.type == PPCREC_IML_TYPE_NO_OP) + { + strOutput.add("NOP"); + } + else if (inst.type == PPCREC_IML_TYPE_MACRO) + { + if (inst.operation == PPCREC_IML_MACRO_B_TO_REG) + { + strOutput.addFmt("MACRO B_TO_REG {}", IMLDebug_GetRegName(inst.op_macro.paramReg)); + } + else if (inst.operation == PPCREC_IML_MACRO_BL) + { + strOutput.addFmt("MACRO BL 0x{:08x} -> 0x{:08x} cycles (depr): {}", inst.op_macro.param, inst.op_macro.param2, (sint32)inst.op_macro.paramU16); + } + else if (inst.operation == PPCREC_IML_MACRO_B_FAR) + { + strOutput.addFmt("MACRO B_FAR 0x{:08x} -> 0x{:08x} cycles (depr): {}", inst.op_macro.param, inst.op_macro.param2, (sint32)inst.op_macro.paramU16); + } + else if (inst.operation == PPCREC_IML_MACRO_LEAVE) + { + strOutput.addFmt("MACRO LEAVE ppc: 0x{:08x}", inst.op_macro.param); + } + else if (inst.operation == PPCREC_IML_MACRO_HLE) + { + strOutput.addFmt("MACRO HLE ppcAddr: 0x{:08x} funcId: 0x{:08x}", inst.op_macro.param, inst.op_macro.param2); + } + else if (inst.operation == PPCREC_IML_MACRO_COUNT_CYCLES) + { + strOutput.addFmt("MACRO COUNT_CYCLES cycles: {}", inst.op_macro.param); + } + else + { + strOutput.addFmt("MACRO ukn operation {}", inst.operation); + } + } + else if (inst.type == PPCREC_IML_TYPE_FPR_LOAD) + { + strOutput.addFmt("{} = ", IMLDebug_GetRegName(inst.op_storeLoad.registerData)); + if (inst.op_storeLoad.flags2.signExtend) + strOutput.add("S"); + else + strOutput.add("U"); + strOutput.addFmt("{} [{}+{}] mode {}", inst.op_storeLoad.copyWidth / 8, IMLDebug_GetRegName(inst.op_storeLoad.registerMem), inst.op_storeLoad.immS32, inst.op_storeLoad.mode); + if (inst.op_storeLoad.flags2.notExpanded) + { + strOutput.addFmt(" "); + } + } + else if (inst.type == PPCREC_IML_TYPE_FPR_STORE) + { + if (inst.op_storeLoad.flags2.signExtend) + strOutput.add("S"); + else + strOutput.add("U"); + strOutput.addFmt("{} [t{}+{}]", inst.op_storeLoad.copyWidth / 8, inst.op_storeLoad.registerMem.GetRegID(), inst.op_storeLoad.immS32); + strOutput.addFmt(" = {} mode {}", IMLDebug_GetRegName(inst.op_storeLoad.registerData), inst.op_storeLoad.mode); + } + else if (inst.type == PPCREC_IML_TYPE_FPR_R_R) + { + strOutput.addFmt("{:>6} ", IMLDebug_GetOpcodeName(&inst)); + strOutput.addFmt("{}, {}", IMLDebug_GetRegName(inst.op_fpr_r_r.regR), IMLDebug_GetRegName(inst.op_fpr_r_r.regA)); + } + else if (inst.type == PPCREC_IML_TYPE_FPR_R_R_R_R) + { + strOutput.addFmt("{:>6} ", IMLDebug_GetOpcodeName(&inst)); + strOutput.addFmt("{}, {}, {}, {}", IMLDebug_GetRegName(inst.op_fpr_r_r_r_r.regR), IMLDebug_GetRegName(inst.op_fpr_r_r_r_r.regA), IMLDebug_GetRegName(inst.op_fpr_r_r_r_r.regB), IMLDebug_GetRegName(inst.op_fpr_r_r_r_r.regC)); + } + else if (inst.type == PPCREC_IML_TYPE_FPR_R_R_R) + { + strOutput.addFmt("{:>6} ", IMLDebug_GetOpcodeName(&inst)); + strOutput.addFmt("{}, {}, {}", IMLDebug_GetRegName(inst.op_fpr_r_r_r.regR), IMLDebug_GetRegName(inst.op_fpr_r_r_r.regA), IMLDebug_GetRegName(inst.op_fpr_r_r_r.regB)); + } + else if (inst.type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK) + { + strOutput.addFmt("CYCLE_CHECK"); + } + else if (inst.type == PPCREC_IML_TYPE_X86_EFLAGS_JCC) + { + strOutput.addFmt("X86_JCC {}", IMLDebug_GetConditionName(inst.op_x86_eflags_jcc.cond)); + } + else + { + strOutput.addFmt("Unknown iml type {}", inst.type); + } + disassemblyLineOut.assign(strOutput.c_str()); +} + void IMLDebug_DumpSegment(ppcImlGenContext_t* ctx, IMLSegment* imlSegment, bool printLivenessRangeInfo) { - StringBuf strOutput(1024); + StringBuf strOutput(4096); strOutput.addFmt("SEGMENT {} | PPC=0x{:08x} Loop-depth {}", IMLDebug_GetSegmentName(ctx, imlSegment), imlSegment->ppcAddress, imlSegment->loopDepth); if (imlSegment->isEnterable) { strOutput.addFmt(" ENTERABLE (0x{:08x})", imlSegment->enterPPCAddress); } - //else if (imlSegment->isJumpDestination) - //{ - // strOutput.addFmt(" JUMP-DEST (0x{:08x})", imlSegment->jumpDestinationPPCAddress); - //} - - debug_printf("%s\n", strOutput.c_str()); - - //strOutput.reset(); - //strOutput.addFmt("SEGMENT NAME 0x{:016x}", (uintptr_t)imlSegment); - //debug_printf("%s", strOutput.c_str()); + if (imlSegment->deadCodeEliminationHintSeg) + { + strOutput.addFmt(" InheritOverwrite: {}", IMLDebug_GetSegmentName(ctx, imlSegment->deadCodeEliminationHintSeg)); + } + cemuLog_log(LogType::Force, "{}", strOutput.c_str()); if (printLivenessRangeInfo) { strOutput.reset(); IMLDebug_PrintLivenessRangeInfo(strOutput, imlSegment, RA_INTER_RANGE_START); - debug_printf("%s\n", strOutput.c_str()); + cemuLog_log(LogType::Force, "{}", strOutput.c_str()); } //debug_printf("\n"); strOutput.reset(); - sint32 lineOffsetParameters = 18; - + std::string disassemblyLine; for (sint32 i = 0; i < imlSegment->imlList.size(); i++) { const IMLInstruction& inst = imlSegment->imlList[i]; @@ -202,326 +468,54 @@ void IMLDebug_DumpSegment(ppcImlGenContext_t* ctx, IMLSegment* imlSegment, bool continue; strOutput.reset(); strOutput.addFmt("{:02x} ", i); - if (inst.type == PPCREC_IML_TYPE_R_NAME || inst.type == PPCREC_IML_TYPE_NAME_R) - { - if (inst.type == PPCREC_IML_TYPE_R_NAME) - strOutput.add("R_NAME"); - else - strOutput.add("NAME_R"); - while ((sint32)strOutput.getLen() < lineOffsetParameters) - strOutput.add(" "); - - if(inst.type == PPCREC_IML_TYPE_R_NAME) - IMLDebug_AppendRegisterParam(strOutput, inst.op_r_name.regR); - - strOutput.add("name_"); - if (inst.op_r_name.name >= PPCREC_NAME_R0 && inst.op_r_name.name < (PPCREC_NAME_R0 + 999)) - { - strOutput.addFmt("r{}", inst.op_r_name.name - PPCREC_NAME_R0); - } - else if (inst.op_r_name.name >= PPCREC_NAME_FPR0 && inst.op_r_name.name < (PPCREC_NAME_FPR0 + 999)) - { - strOutput.addFmt("f{}", inst.op_r_name.name - PPCREC_NAME_FPR0); - } - else if (inst.op_r_name.name >= PPCREC_NAME_SPR0 && inst.op_r_name.name < (PPCREC_NAME_SPR0 + 999)) - { - strOutput.addFmt("spr{}", inst.op_r_name.name - PPCREC_NAME_SPR0); - } - else if (inst.op_r_name.name >= PPCREC_NAME_CR && inst.op_r_name.name <= PPCREC_NAME_CR_LAST) - strOutput.addFmt("cr{}", inst.op_r_name.name - PPCREC_NAME_CR); - else if (inst.op_r_name.name == PPCREC_NAME_XER_CA) - strOutput.add("xer.ca"); - else if (inst.op_r_name.name == PPCREC_NAME_XER_SO) - strOutput.add("xer.so"); - else if (inst.op_r_name.name == PPCREC_NAME_XER_OV) - strOutput.add("xer.ov"); - else if (inst.op_r_name.name == PPCREC_NAME_CPU_MEMRES_EA) - strOutput.add("cpuReservation.ea"); - else if (inst.op_r_name.name == PPCREC_NAME_CPU_MEMRES_VAL) - strOutput.add("cpuReservation.value"); - else - { - strOutput.addFmt("name_ukn{}", inst.op_r_name.name); - } - if (inst.type != PPCREC_IML_TYPE_R_NAME) - { - strOutput.add(", "); - IMLDebug_AppendRegisterParam(strOutput, inst.op_r_name.regR, true); - } - - } - else if (inst.type == PPCREC_IML_TYPE_R_R) - { - strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst)); - while ((sint32)strOutput.getLen() < lineOffsetParameters) - strOutput.add(" "); - IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r.regR); - IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r.regA, true); - } - else if (inst.type == PPCREC_IML_TYPE_R_R_R) - { - strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst)); - while ((sint32)strOutput.getLen() < lineOffsetParameters) - strOutput.add(" "); - IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r.regR); - IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r.regA); - IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r.regB, true); - } - else if (inst.type == PPCREC_IML_TYPE_R_R_R_CARRY) - { - strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst)); - while ((sint32)strOutput.getLen() < lineOffsetParameters) - strOutput.add(" "); - IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r_carry.regR); - IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r_carry.regA); - IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r_carry.regB); - IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r_carry.regCarry, true); - } - else if (inst.type == PPCREC_IML_TYPE_COMPARE) - { - strOutput.add("CMP "); - while ((sint32)strOutput.getLen() < lineOffsetParameters) - strOutput.add(" "); - IMLDebug_AppendRegisterParam(strOutput, inst.op_compare.regA); - IMLDebug_AppendRegisterParam(strOutput, inst.op_compare.regB); - strOutput.addFmt(", {}", IMLDebug_GetConditionName(inst.op_compare.cond)); - strOutput.add(" -> "); - IMLDebug_AppendRegisterParam(strOutput, inst.op_compare.regR, true); - } - else if (inst.type == PPCREC_IML_TYPE_COMPARE_S32) - { - strOutput.add("CMP "); - while ((sint32)strOutput.getLen() < lineOffsetParameters) - strOutput.add(" "); - IMLDebug_AppendRegisterParam(strOutput, inst.op_compare_s32.regA); - strOutput.addFmt("{}", inst.op_compare_s32.immS32); - strOutput.addFmt(", {}", IMLDebug_GetConditionName(inst.op_compare_s32.cond)); - strOutput.add(" -> "); - IMLDebug_AppendRegisterParam(strOutput, inst.op_compare_s32.regR, true); - } - else if (inst.type == PPCREC_IML_TYPE_CONDITIONAL_JUMP) - { - strOutput.add("CJUMP "); - while ((sint32)strOutput.getLen() < lineOffsetParameters) - strOutput.add(" "); - IMLDebug_AppendRegisterParam(strOutput, inst.op_conditional_jump.registerBool, true); - if (!inst.op_conditional_jump.mustBeTrue) - strOutput.add("(inverted)"); - } - else if (inst.type == PPCREC_IML_TYPE_JUMP) - { - strOutput.add("JUMP"); - } - else if (inst.type == PPCREC_IML_TYPE_R_R_S32) - { - strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst)); - while ((sint32)strOutput.getLen() < lineOffsetParameters) - strOutput.add(" "); - - IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32.regR); - IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32.regA); - IMLDebug_AppendS32Param(strOutput, inst.op_r_r_s32.immS32, true); - } - else if (inst.type == PPCREC_IML_TYPE_R_R_S32_CARRY) - { - strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst)); - while ((sint32)strOutput.getLen() < lineOffsetParameters) - strOutput.add(" "); - - IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32_carry.regR); - IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32_carry.regA); - IMLDebug_AppendS32Param(strOutput, inst.op_r_r_s32_carry.immS32); - IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32_carry.regCarry, true); - } - else if (inst.type == PPCREC_IML_TYPE_R_S32) - { - strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst)); - while ((sint32)strOutput.getLen() < lineOffsetParameters) - strOutput.add(" "); - - IMLDebug_AppendRegisterParam(strOutput, inst.op_r_immS32.regR); - IMLDebug_AppendS32Param(strOutput, inst.op_r_immS32.immS32, true); - } - else if (inst.type == PPCREC_IML_TYPE_LOAD || inst.type == PPCREC_IML_TYPE_STORE || - inst.type == PPCREC_IML_TYPE_LOAD_INDEXED || inst.type == PPCREC_IML_TYPE_STORE_INDEXED) - { - if (inst.type == PPCREC_IML_TYPE_LOAD || inst.type == PPCREC_IML_TYPE_LOAD_INDEXED) - strOutput.add("LD_"); - else - strOutput.add("ST_"); - - if (inst.op_storeLoad.flags2.signExtend) - strOutput.add("S"); - else - strOutput.add("U"); - strOutput.addFmt("{}", inst.op_storeLoad.copyWidth); - - while ((sint32)strOutput.getLen() < lineOffsetParameters) - strOutput.add(" "); - - IMLDebug_AppendRegisterParam(strOutput, inst.op_storeLoad.registerData); - - if (inst.type == PPCREC_IML_TYPE_LOAD_INDEXED || inst.type == PPCREC_IML_TYPE_STORE_INDEXED) - strOutput.addFmt("[{}+{}]", IMLDebug_GetRegName(inst.op_storeLoad.registerMem), IMLDebug_GetRegName(inst.op_storeLoad.registerMem2)); - else - strOutput.addFmt("[{}+{}]", IMLDebug_GetRegName(inst.op_storeLoad.registerMem), inst.op_storeLoad.immS32); - } - else if (inst.type == PPCREC_IML_TYPE_ATOMIC_CMP_STORE) - { - strOutput.add("ATOMIC_ST_U32"); - - while ((sint32)strOutput.getLen() < lineOffsetParameters) - strOutput.add(" "); - - IMLDebug_AppendRegisterParam(strOutput, inst.op_atomic_compare_store.regEA); - IMLDebug_AppendRegisterParam(strOutput, inst.op_atomic_compare_store.regCompareValue); - IMLDebug_AppendRegisterParam(strOutput, inst.op_atomic_compare_store.regWriteValue); - IMLDebug_AppendRegisterParam(strOutput, inst.op_atomic_compare_store.regBoolOut, true); - } - else if (inst.type == PPCREC_IML_TYPE_NO_OP) - { - strOutput.add("NOP"); - } - else if (inst.type == PPCREC_IML_TYPE_MACRO) - { - if (inst.operation == PPCREC_IML_MACRO_B_TO_REG) - { - strOutput.addFmt("MACRO B_TO_REG {}", IMLDebug_GetRegName(inst.op_macro.paramReg)); - } - else if (inst.operation == PPCREC_IML_MACRO_BL) - { - strOutput.addFmt("MACRO BL 0x{:08x} -> 0x{:08x} cycles (depr): {}", inst.op_macro.param, inst.op_macro.param2, (sint32)inst.op_macro.paramU16); - } - else if (inst.operation == PPCREC_IML_MACRO_B_FAR) - { - strOutput.addFmt("MACRO B_FAR 0x{:08x} -> 0x{:08x} cycles (depr): {}", inst.op_macro.param, inst.op_macro.param2, (sint32)inst.op_macro.paramU16); - } - else if (inst.operation == PPCREC_IML_MACRO_LEAVE) - { - strOutput.addFmt("MACRO LEAVE ppc: 0x{:08x}", inst.op_macro.param); - } - else if (inst.operation == PPCREC_IML_MACRO_HLE) - { - strOutput.addFmt("MACRO HLE ppcAddr: 0x{:08x} funcId: 0x{:08x}", inst.op_macro.param, inst.op_macro.param2); - } - else if (inst.operation == PPCREC_IML_MACRO_MFTB) - { - strOutput.addFmt("MACRO MFTB ppcAddr: 0x{:08x} sprId: 0x{:08x}", inst.op_macro.param, inst.op_macro.param2); - } - else if (inst.operation == PPCREC_IML_MACRO_COUNT_CYCLES) - { - strOutput.addFmt("MACRO COUNT_CYCLES cycles: {}", inst.op_macro.param); - } - else - { - strOutput.addFmt("MACRO ukn operation {}", inst.operation); - } - } - else if (inst.type == PPCREC_IML_TYPE_FPR_LOAD) - { - strOutput.addFmt("{} = ", IMLDebug_GetRegName(inst.op_storeLoad.registerData)); - if (inst.op_storeLoad.flags2.signExtend) - strOutput.add("S"); - else - strOutput.add("U"); - strOutput.addFmt("{} [{}+{}] mode {}", inst.op_storeLoad.copyWidth / 8, IMLDebug_GetRegName(inst.op_storeLoad.registerMem), inst.op_storeLoad.immS32, inst.op_storeLoad.mode); - if (inst.op_storeLoad.flags2.notExpanded) - { - strOutput.addFmt(" "); - } - } - else if (inst.type == PPCREC_IML_TYPE_FPR_STORE) - { - if (inst.op_storeLoad.flags2.signExtend) - strOutput.add("S"); - else - strOutput.add("U"); - strOutput.addFmt("{} [t{}+{}]", inst.op_storeLoad.copyWidth / 8, inst.op_storeLoad.registerMem.GetRegID(), inst.op_storeLoad.immS32); - strOutput.addFmt(" = {} mode {}", IMLDebug_GetRegName(inst.op_storeLoad.registerData), inst.op_storeLoad.mode); - } - else if (inst.type == PPCREC_IML_TYPE_FPR_R_R) - { - strOutput.addFmt("{:>6} ", IMLDebug_GetOpcodeName(&inst)); - strOutput.addFmt("{}, {}", IMLDebug_GetRegName(inst.op_fpr_r_r.regR), IMLDebug_GetRegName(inst.op_fpr_r_r.regA)); - } - else if (inst.type == PPCREC_IML_TYPE_FPR_R_R_R_R) - { - strOutput.addFmt("{:>6} ", IMLDebug_GetOpcodeName(&inst)); - strOutput.addFmt("{}, {}, {}, {}", IMLDebug_GetRegName(inst.op_fpr_r_r_r_r.regR), IMLDebug_GetRegName(inst.op_fpr_r_r_r_r.regA), IMLDebug_GetRegName(inst.op_fpr_r_r_r_r.regB), IMLDebug_GetRegName(inst.op_fpr_r_r_r_r.regC)); - } - else if (inst.type == PPCREC_IML_TYPE_FPR_R_R_R) - { - strOutput.addFmt("{:>6} ", IMLDebug_GetOpcodeName(&inst)); - strOutput.addFmt("{}, {}, {}", IMLDebug_GetRegName(inst.op_fpr_r_r_r.regR), IMLDebug_GetRegName(inst.op_fpr_r_r_r.regA), IMLDebug_GetRegName(inst.op_fpr_r_r_r.regB)); - } - else if (inst.type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK) - { - strOutput.addFmt("CYCLE_CHECK"); - } - else if (inst.type == PPCREC_IML_TYPE_CONDITIONAL_R_S32) - { - strOutput.addFmt("{} ", IMLDebug_GetRegName(inst.op_conditional_r_s32.regR)); - bool displayAsHex = false; - if (inst.operation == PPCREC_IML_OP_ASSIGN) - { - displayAsHex = true; - strOutput.add("="); - } - else - strOutput.addFmt("(unknown operation CONDITIONAL_R_S32 {})", inst.operation); - if (displayAsHex) - strOutput.addFmt(" 0x{:x}", inst.op_conditional_r_s32.immS32); - else - strOutput.addFmt(" {}", inst.op_conditional_r_s32.immS32); - strOutput.add(" (conditional)"); - } - else - { - strOutput.addFmt("Unknown iml type {}", inst.type); - } - debug_printf("%s", strOutput.c_str()); + //cemuLog_log(LogType::Force, "{:02x} ", i); + disassemblyLine.clear(); + IMLDebug_DisassembleInstruction(inst, disassemblyLine); + strOutput.add(disassemblyLine); if (printLivenessRangeInfo) { IMLDebug_PrintLivenessRangeInfo(strOutput, imlSegment, i); } - debug_printf("\n"); + cemuLog_log(LogType::Force, "{}", strOutput.c_str()); } // all ranges if (printLivenessRangeInfo) { - debug_printf("Ranges-VirtReg "); - raLivenessSubrange_t* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + strOutput.reset(); + strOutput.add("Ranges-VirtReg "); + raLivenessRange* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; while (subrangeItr) { - debug_printf("v%-2d", subrangeItr->range->virtualRegister); - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + strOutput.addFmt("v{:<4}", (uint32)subrangeItr->GetVirtualRegister()); + subrangeItr = subrangeItr->link_allSegmentRanges.next; } - debug_printf("\n"); - debug_printf("Ranges-PhysReg "); + cemuLog_log(LogType::Force, "{}", strOutput.c_str()); + strOutput.reset(); + strOutput.add("Ranges-PhysReg "); subrangeItr = imlSegment->raInfo.linkedList_allSubranges; while (subrangeItr) { - debug_printf("p%-2d", subrangeItr->range->physicalRegister); - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + strOutput.addFmt("p{:<4}", subrangeItr->GetPhysicalRegister()); + subrangeItr = subrangeItr->link_allSegmentRanges.next; } - debug_printf("\n"); + cemuLog_log(LogType::Force, "{}", strOutput.c_str()); } // branch info - debug_printf("Links from: "); + strOutput.reset(); + strOutput.add("Links from: "); for (sint32 i = 0; i < imlSegment->list_prevSegments.size(); i++) { if (i) - debug_printf(", "); - debug_printf("%s", IMLDebug_GetSegmentName(ctx, imlSegment->list_prevSegments[i]).c_str()); + strOutput.add(", "); + strOutput.addFmt("{}", IMLDebug_GetSegmentName(ctx, imlSegment->list_prevSegments[i]).c_str()); } - debug_printf("\n"); + cemuLog_log(LogType::Force, "{}", strOutput.c_str()); if (imlSegment->nextSegmentBranchNotTaken) - debug_printf("BranchNotTaken: %s\n", IMLDebug_GetSegmentName(ctx, imlSegment->nextSegmentBranchNotTaken).c_str()); + cemuLog_log(LogType::Force, "BranchNotTaken: {}", IMLDebug_GetSegmentName(ctx, imlSegment->nextSegmentBranchNotTaken).c_str()); if (imlSegment->nextSegmentBranchTaken) - debug_printf("BranchTaken: %s\n", IMLDebug_GetSegmentName(ctx, imlSegment->nextSegmentBranchTaken).c_str()); + cemuLog_log(LogType::Force, "BranchTaken: {}", IMLDebug_GetSegmentName(ctx, imlSegment->nextSegmentBranchTaken).c_str()); if (imlSegment->nextSegmentIsUncertain) - debug_printf("Dynamic target\n"); - debug_printf("\n"); + cemuLog_log(LogType::Force, "Dynamic target"); } void IMLDebug_Dump(ppcImlGenContext_t* ppcImlGenContext, bool printLivenessRangeInfo) @@ -529,6 +523,6 @@ void IMLDebug_Dump(ppcImlGenContext_t* ppcImlGenContext, bool printLivenessRange for (size_t i = 0; i < ppcImlGenContext->segmentList2.size(); i++) { IMLDebug_DumpSegment(ppcImlGenContext, ppcImlGenContext->segmentList2[i], printLivenessRangeInfo); - debug_printf("\n"); + cemuLog_log(LogType::Force, ""); } } diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.cpp b/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.cpp index f2476e61..cb481043 100644 --- a/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.cpp @@ -4,18 +4,24 @@ #include "../PPCRecompiler.h" #include "../PPCRecompilerIml.h" +// return true if an instruction has side effects on top of just reading and writing registers +bool IMLInstruction::HasSideEffects() const +{ + bool hasSideEffects = true; + if(type == PPCREC_IML_TYPE_R_R || type == PPCREC_IML_TYPE_R_R_S32 || type == PPCREC_IML_TYPE_COMPARE || type == PPCREC_IML_TYPE_COMPARE_S32) + hasSideEffects = false; + // todo - add more cases + return hasSideEffects; +} + void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const { registersUsed->readGPR1 = IMLREG_INVALID; registersUsed->readGPR2 = IMLREG_INVALID; registersUsed->readGPR3 = IMLREG_INVALID; + registersUsed->readGPR4 = IMLREG_INVALID; registersUsed->writtenGPR1 = IMLREG_INVALID; registersUsed->writtenGPR2 = IMLREG_INVALID; - registersUsed->readFPR1 = IMLREG_INVALID; - registersUsed->readFPR2 = IMLREG_INVALID; - registersUsed->readFPR3 = IMLREG_INVALID; - registersUsed->readFPR4 = IMLREG_INVALID; - registersUsed->writtenFPR1 = IMLREG_INVALID; if (type == PPCREC_IML_TYPE_R_NAME) { registersUsed->writtenGPR1 = op_r_name.regR; @@ -26,7 +32,7 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const } else if (type == PPCREC_IML_TYPE_R_R) { - if (operation == PPCREC_IML_OP_DCBZ) + if (operation == PPCREC_IML_OP_X86_CMP) { // both operands are read only registersUsed->readGPR1 = op_r_r.regR; @@ -58,43 +64,26 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const if (operation == PPCREC_IML_OP_LEFT_ROTATE) { - // operand register is read and write + // register operand is read and write registersUsed->readGPR1 = op_r_immS32.regR; registersUsed->writtenGPR1 = op_r_immS32.regR; } + else if (operation == PPCREC_IML_OP_X86_CMP) + { + // register operand is read only + registersUsed->readGPR1 = op_r_immS32.regR; + } else { - // operand register is write only + // register operand is write only // todo - use explicit lists, avoid default cases registersUsed->writtenGPR1 = op_r_immS32.regR; } } - else if (type == PPCREC_IML_TYPE_CONDITIONAL_R_S32) - { - if (operation == PPCREC_IML_OP_ASSIGN) - { - // result is written, but also considered read (in case the condition is false the input is preserved) - registersUsed->readGPR1 = op_conditional_r_s32.regR; - registersUsed->writtenGPR1 = op_conditional_r_s32.regR; - } - else - cemu_assert_unimplemented(); - } else if (type == PPCREC_IML_TYPE_R_R_S32) { - if (operation == PPCREC_IML_OP_RLWIMI) - { - // result and operand register are both read, result is written - registersUsed->writtenGPR1 = op_r_r_s32.regR; - registersUsed->readGPR1 = op_r_r_s32.regR; - registersUsed->readGPR2 = op_r_r_s32.regA; - } - else - { - // result is write only and operand is read only - registersUsed->writtenGPR1 = op_r_r_s32.regR; - registersUsed->readGPR1 = op_r_r_s32.regA; - } + registersUsed->writtenGPR1 = op_r_r_s32.regR; + registersUsed->readGPR1 = op_r_r_s32.regA; } else if (type == PPCREC_IML_TYPE_R_R_S32_CARRY) { @@ -117,9 +106,13 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const else if (type == PPCREC_IML_TYPE_R_R_R) { // in all cases result is written and other operands are read only + // with the exception of XOR, where if regA == regB then all bits are zeroed out. So we don't consider it a read registersUsed->writtenGPR1 = op_r_r_r.regR; - registersUsed->readGPR1 = op_r_r_r.regA; - registersUsed->readGPR2 = op_r_r_r.regB; + if(!(operation == PPCREC_IML_OP_XOR && op_r_r_r.regA == op_r_r_r.regB)) + { + registersUsed->readGPR1 = op_r_r_r.regA; + registersUsed->readGPR2 = op_r_r_r.regB; + } } else if (type == PPCREC_IML_TYPE_R_R_R_CARRY) { @@ -150,7 +143,7 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const } else if (type == PPCREC_IML_TYPE_MACRO) { - if (operation == PPCREC_IML_MACRO_BL || operation == PPCREC_IML_MACRO_B_FAR || operation == PPCREC_IML_MACRO_LEAVE || operation == PPCREC_IML_MACRO_DEBUGBREAK || operation == PPCREC_IML_MACRO_COUNT_CYCLES || operation == PPCREC_IML_MACRO_HLE || operation == PPCREC_IML_MACRO_MFTB) + if (operation == PPCREC_IML_MACRO_BL || operation == PPCREC_IML_MACRO_B_FAR || operation == PPCREC_IML_MACRO_LEAVE || operation == PPCREC_IML_MACRO_DEBUGBREAK || operation == PPCREC_IML_MACRO_COUNT_CYCLES || operation == PPCREC_IML_MACRO_HLE) { // no effect on registers } @@ -216,10 +209,20 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const registersUsed->readGPR3 = op_atomic_compare_store.regWriteValue; registersUsed->writtenGPR1 = op_atomic_compare_store.regBoolOut; } + else if (type == PPCREC_IML_TYPE_CALL_IMM) + { + if (op_call_imm.regParam0.IsValid()) + registersUsed->readGPR1 = op_call_imm.regParam0; + if (op_call_imm.regParam1.IsValid()) + registersUsed->readGPR2 = op_call_imm.regParam1; + if (op_call_imm.regParam2.IsValid()) + registersUsed->readGPR3 = op_call_imm.regParam2; + registersUsed->writtenGPR1 = op_call_imm.regReturn; + } else if (type == PPCREC_IML_TYPE_FPR_LOAD) { // fpr load operation - registersUsed->writtenFPR1 = op_storeLoad.registerData; + registersUsed->writtenGPR1 = op_storeLoad.registerData; // address is in gpr register if (op_storeLoad.registerMem.IsValid()) registersUsed->readGPR1 = op_storeLoad.registerMem; @@ -233,8 +236,8 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const break; case PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0: // PS1 remains the same - registersUsed->readFPR4 = op_storeLoad.registerData; cemu_assert_debug(op_storeLoad.registerGQR.IsInvalid()); + registersUsed->readGPR2 = op_storeLoad.registerData; break; case PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1: case PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1: @@ -256,7 +259,7 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const else if (type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED) { // fpr load operation - registersUsed->writtenFPR1 = op_storeLoad.registerData; + registersUsed->writtenGPR1 = op_storeLoad.registerData; // address is in gpr registers if (op_storeLoad.registerMem.IsValid()) registersUsed->readGPR1 = op_storeLoad.registerMem; @@ -273,7 +276,7 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const case PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0: // PS1 remains the same cemu_assert_debug(op_storeLoad.registerGQR.IsInvalid()); - registersUsed->readFPR4 = op_storeLoad.registerData; + registersUsed->readGPR3 = op_storeLoad.registerData; break; case PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1: case PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1: @@ -294,31 +297,9 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const else if (type == PPCREC_IML_TYPE_FPR_STORE) { // fpr store operation - registersUsed->readFPR1 = op_storeLoad.registerData; + registersUsed->readGPR1 = op_storeLoad.registerData; if (op_storeLoad.registerMem.IsValid()) - registersUsed->readGPR1 = op_storeLoad.registerMem; - // PSQ generic stores also access GQR - switch (op_storeLoad.mode) - { - case PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0: - case PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1: - cemu_assert_debug(op_storeLoad.registerGQR.IsValid()); - registersUsed->readGPR2 = op_storeLoad.registerGQR; - break; - default: - cemu_assert_debug(op_storeLoad.registerGQR.IsInvalid()); - break; - } - } - else if (type == PPCREC_IML_TYPE_FPR_STORE_INDEXED) - { - // fpr store operation - registersUsed->readFPR1 = op_storeLoad.registerData; - // address is in gpr registers - if (op_storeLoad.registerMem.IsValid()) - registersUsed->readGPR1 = op_storeLoad.registerMem; - if (op_storeLoad.registerMem2.IsValid()) - registersUsed->readGPR2 = op_storeLoad.registerMem2; + registersUsed->readGPR2 = op_storeLoad.registerMem; // PSQ generic stores also access GQR switch (op_storeLoad.mode) { @@ -332,6 +313,28 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const break; } } + else if (type == PPCREC_IML_TYPE_FPR_STORE_INDEXED) + { + // fpr store operation + registersUsed->readGPR1 = op_storeLoad.registerData; + // address is in gpr registers + if (op_storeLoad.registerMem.IsValid()) + registersUsed->readGPR2 = op_storeLoad.registerMem; + if (op_storeLoad.registerMem2.IsValid()) + registersUsed->readGPR3 = op_storeLoad.registerMem2; + // PSQ generic stores also access GQR + switch (op_storeLoad.mode) + { + case PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0: + case PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1: + cemu_assert_debug(op_storeLoad.registerGQR.IsValid()); + registersUsed->readGPR4 = op_storeLoad.registerGQR; + break; + default: + cemu_assert_debug(op_storeLoad.registerGQR.IsInvalid()); + break; + } + } else if (type == PPCREC_IML_TYPE_FPR_R_R) { // fpr operation @@ -339,15 +342,14 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const operation == PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP || operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_AND_TOP_SWAPPED || operation == PPCREC_IML_OP_ASSIGN || - operation == PPCREC_IML_OP_FPR_BOTTOM_FRES_TO_BOTTOM_AND_TOP || operation == PPCREC_IML_OP_FPR_NEGATE_PAIR || operation == PPCREC_IML_OP_FPR_ABS_PAIR || operation == PPCREC_IML_OP_FPR_FRES_PAIR || operation == PPCREC_IML_OP_FPR_FRSQRTE_PAIR) { // operand read, result written - registersUsed->readFPR1 = op_fpr_r_r.regA; - registersUsed->writtenFPR1 = op_fpr_r_r.regR; + registersUsed->readGPR1 = op_fpr_r_r.regA; + registersUsed->writtenGPR1 = op_fpr_r_r.regR; } else if ( operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM || @@ -360,9 +362,9 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const ) { // operand read, result read and (partially) written - registersUsed->readFPR1 = op_fpr_r_r.regA; - registersUsed->readFPR4 = op_fpr_r_r.regR; - registersUsed->writtenFPR1 = op_fpr_r_r.regR; + registersUsed->readGPR1 = op_fpr_r_r.regA; + registersUsed->readGPR2 = op_fpr_r_r.regR; + registersUsed->writtenGPR1 = op_fpr_r_r.regR; } else if (operation == PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM || operation == PPCREC_IML_OP_FPR_MULTIPLY_PAIR || @@ -374,9 +376,9 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const operation == PPCREC_IML_OP_FPR_SUB_BOTTOM) { // operand read, result read and written - registersUsed->readFPR1 = op_fpr_r_r.regA; - registersUsed->readFPR2 = op_fpr_r_r.regR; - registersUsed->writtenFPR1 = op_fpr_r_r.regR; + registersUsed->readGPR1 = op_fpr_r_r.regA; + registersUsed->readGPR2 = op_fpr_r_r.regR; + registersUsed->writtenGPR1 = op_fpr_r_r.regR; } else if (operation == PPCREC_IML_OP_FPR_FCMPU_BOTTOM || @@ -384,8 +386,8 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const operation == PPCREC_IML_OP_FPR_FCMPO_BOTTOM) { // operand read, result read - registersUsed->readFPR1 = op_fpr_r_r.regA; - registersUsed->readFPR2 = op_fpr_r_r.regR; + registersUsed->readGPR1 = op_fpr_r_r.regA; + registersUsed->readGPR2 = op_fpr_r_r.regR; } else cemu_assert_unimplemented(); @@ -393,16 +395,16 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const else if (type == PPCREC_IML_TYPE_FPR_R_R_R) { // fpr operation - registersUsed->readFPR1 = op_fpr_r_r_r.regA; - registersUsed->readFPR2 = op_fpr_r_r_r.regB; - registersUsed->writtenFPR1 = op_fpr_r_r_r.regR; + registersUsed->readGPR1 = op_fpr_r_r_r.regA; + registersUsed->readGPR2 = op_fpr_r_r_r.regB; + registersUsed->writtenGPR1 = op_fpr_r_r_r.regR; // handle partially written result switch (operation) { case PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM: case PPCREC_IML_OP_FPR_ADD_BOTTOM: case PPCREC_IML_OP_FPR_SUB_BOTTOM: - registersUsed->readFPR4 = op_fpr_r_r_r.regR; + registersUsed->readGPR3 = op_fpr_r_r_r.regR; break; case PPCREC_IML_OP_FPR_SUB_PAIR: break; @@ -413,15 +415,15 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const else if (type == PPCREC_IML_TYPE_FPR_R_R_R_R) { // fpr operation - registersUsed->readFPR1 = op_fpr_r_r_r_r.regA; - registersUsed->readFPR2 = op_fpr_r_r_r_r.regB; - registersUsed->readFPR3 = op_fpr_r_r_r_r.regC; - registersUsed->writtenFPR1 = op_fpr_r_r_r_r.regR; + registersUsed->readGPR1 = op_fpr_r_r_r_r.regA; + registersUsed->readGPR2 = op_fpr_r_r_r_r.regB; + registersUsed->readGPR3 = op_fpr_r_r_r_r.regC; + registersUsed->writtenGPR1 = op_fpr_r_r_r_r.regR; // handle partially written result switch (operation) { case PPCREC_IML_OP_FPR_SELECT_BOTTOM: - registersUsed->readFPR4 = op_fpr_r_r_r_r.regR; + registersUsed->readGPR4 = op_fpr_r_r_r_r.regR; break; case PPCREC_IML_OP_FPR_SUM0: case PPCREC_IML_OP_FPR_SUM1: @@ -441,8 +443,8 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const operation == PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM || operation == PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_PAIR) { - registersUsed->readFPR1 = op_fpr_r.regR; - registersUsed->writtenFPR1 = op_fpr_r.regR; + registersUsed->readGPR1 = op_fpr_r.regR; + registersUsed->writtenGPR1 = op_fpr_r.regR; } else cemu_assert_unimplemented(); @@ -450,8 +452,12 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const else if (type == PPCREC_IML_TYPE_FPR_COMPARE) { registersUsed->writtenGPR1 = op_fpr_compare.regR; - registersUsed->readFPR1 = op_fpr_compare.regA; - registersUsed->readFPR2 = op_fpr_compare.regB; + registersUsed->readGPR1 = op_fpr_compare.regA; + registersUsed->readGPR2 = op_fpr_compare.regB; + } + else if (type == PPCREC_IML_TYPE_X86_EFLAGS_JCC) + { + // no registers read or written (except for the implicit eflags) } else { @@ -459,15 +465,6 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const } } -//#define replaceRegister(__x,__r,__n) (((__x)==(__r))?(__n):(__x)) -IMLReg replaceRegisterId(IMLReg reg, IMLRegID oldId, IMLRegID newId) -{ - if (reg.GetRegID() != oldId) - return reg; - reg.SetRegID(newId); - return reg; -} - IMLReg replaceRegisterIdMultiple(IMLReg reg, const std::unordered_map& translationTable) { if (reg.IsInvalid()) @@ -479,26 +476,6 @@ IMLReg replaceRegisterIdMultiple(IMLReg reg, const std::unordered_map& translationTable) { if (type == PPCREC_IML_TYPE_R_NAME) @@ -518,10 +495,6 @@ void IMLInstruction::RewriteGPR(const std::unordered_map& tr { op_r_immS32.regR = replaceRegisterIdMultiple(op_r_immS32.regR, translationTable); } - else if (type == PPCREC_IML_TYPE_CONDITIONAL_R_S32) - { - op_conditional_r_s32.regR = replaceRegisterIdMultiple(op_conditional_r_s32.regR, translationTable); - } else if (type == PPCREC_IML_TYPE_R_R_S32) { op_r_r_s32.regR = replaceRegisterIdMultiple(op_r_r_s32.regR, translationTable); @@ -571,7 +544,7 @@ void IMLInstruction::RewriteGPR(const std::unordered_map& tr } else if (type == PPCREC_IML_TYPE_MACRO) { - if (operation == PPCREC_IML_MACRO_BL || operation == PPCREC_IML_MACRO_B_FAR || operation == PPCREC_IML_MACRO_LEAVE || operation == PPCREC_IML_MACRO_DEBUGBREAK || operation == PPCREC_IML_MACRO_HLE || operation == PPCREC_IML_MACRO_MFTB || operation == PPCREC_IML_MACRO_COUNT_CYCLES) + if (operation == PPCREC_IML_MACRO_BL || operation == PPCREC_IML_MACRO_B_FAR || operation == PPCREC_IML_MACRO_LEAVE || operation == PPCREC_IML_MACRO_DEBUGBREAK || operation == PPCREC_IML_MACRO_HLE || operation == PPCREC_IML_MACRO_COUNT_CYCLES) { // no effect on registers } @@ -621,6 +594,16 @@ void IMLInstruction::RewriteGPR(const std::unordered_map& tr op_atomic_compare_store.regWriteValue = replaceRegisterIdMultiple(op_atomic_compare_store.regWriteValue, translationTable); op_atomic_compare_store.regBoolOut = replaceRegisterIdMultiple(op_atomic_compare_store.regBoolOut, translationTable); } + else if (type == PPCREC_IML_TYPE_CALL_IMM) + { + op_call_imm.regReturn = replaceRegisterIdMultiple(op_call_imm.regReturn, translationTable); + if (op_call_imm.regParam0.IsValid()) + op_call_imm.regParam0 = replaceRegisterIdMultiple(op_call_imm.regParam0, translationTable); + if (op_call_imm.regParam1.IsValid()) + op_call_imm.regParam1 = replaceRegisterIdMultiple(op_call_imm.regParam1, translationTable); + if (op_call_imm.regParam2.IsValid()) + op_call_imm.regParam2 = replaceRegisterIdMultiple(op_call_imm.regParam2, translationTable); + } else if (type == PPCREC_IML_TYPE_FPR_LOAD) { op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable); @@ -675,222 +658,9 @@ void IMLInstruction::RewriteGPR(const std::unordered_map& tr op_fpr_compare.regB = replaceRegisterIdMultiple(op_fpr_compare.regB, translationTable); op_fpr_compare.regR = replaceRegisterIdMultiple(op_fpr_compare.regR, translationTable); } - else + else if (type == PPCREC_IML_TYPE_X86_EFLAGS_JCC) { - cemu_assert_unimplemented(); - } -} - -void IMLInstruction::ReplaceFPRs(IMLReg fprRegisterSearched[4], IMLReg fprRegisterReplaced[4]) -{ - if (type == PPCREC_IML_TYPE_R_NAME) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_NAME_R) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_R_R) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_R_S32) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_R_R_S32) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_R_R_R) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_COMPARE || type == PPCREC_IML_TYPE_COMPARE_S32 || type == PPCREC_IML_TYPE_CONDITIONAL_JUMP || type == PPCREC_IML_TYPE_JUMP) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_NO_OP) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_MACRO) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_LOAD) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_LOAD_INDEXED) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_STORE) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_STORE_INDEXED) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_ATOMIC_CMP_STORE) - { - ; - } - else if (type == PPCREC_IML_TYPE_FPR_LOAD) - { - op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, fprRegisterSearched, fprRegisterReplaced); - } - else if (type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED) - { - op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, fprRegisterSearched, fprRegisterReplaced); - } - else if (type == PPCREC_IML_TYPE_FPR_STORE) - { - op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, fprRegisterSearched, fprRegisterReplaced); - } - else if (type == PPCREC_IML_TYPE_FPR_STORE_INDEXED) - { - op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, fprRegisterSearched, fprRegisterReplaced); - } - else if (type == PPCREC_IML_TYPE_FPR_R_R) - { - op_fpr_r_r.regR = replaceRegisterIdMultiple(op_fpr_r_r.regR, fprRegisterSearched, fprRegisterReplaced); - op_fpr_r_r.regA = replaceRegisterIdMultiple(op_fpr_r_r.regA, fprRegisterSearched, fprRegisterReplaced); - } - else if (type == PPCREC_IML_TYPE_FPR_R_R_R) - { - op_fpr_r_r_r.regR = replaceRegisterIdMultiple(op_fpr_r_r_r.regR, fprRegisterSearched, fprRegisterReplaced); - op_fpr_r_r_r.regA = replaceRegisterIdMultiple(op_fpr_r_r_r.regA, fprRegisterSearched, fprRegisterReplaced); - op_fpr_r_r_r.regB = replaceRegisterIdMultiple(op_fpr_r_r_r.regB, fprRegisterSearched, fprRegisterReplaced); - } - else if (type == PPCREC_IML_TYPE_FPR_R_R_R_R) - { - op_fpr_r_r_r_r.regR = replaceRegisterIdMultiple(op_fpr_r_r_r_r.regR, fprRegisterSearched, fprRegisterReplaced); - op_fpr_r_r_r_r.regA = replaceRegisterIdMultiple(op_fpr_r_r_r_r.regA, fprRegisterSearched, fprRegisterReplaced); - op_fpr_r_r_r_r.regB = replaceRegisterIdMultiple(op_fpr_r_r_r_r.regB, fprRegisterSearched, fprRegisterReplaced); - op_fpr_r_r_r_r.regC = replaceRegisterIdMultiple(op_fpr_r_r_r_r.regC, fprRegisterSearched, fprRegisterReplaced); - } - else if (type == PPCREC_IML_TYPE_FPR_R) - { - op_fpr_r.regR = replaceRegisterIdMultiple(op_fpr_r.regR, fprRegisterSearched, fprRegisterReplaced); - } - else if (type == PPCREC_IML_TYPE_FPR_COMPARE) - { - op_fpr_compare.regA = replaceRegisterIdMultiple(op_fpr_compare.regA, fprRegisterSearched, fprRegisterReplaced); - op_fpr_compare.regB = replaceRegisterIdMultiple(op_fpr_compare.regB, fprRegisterSearched, fprRegisterReplaced); - } - else - { - cemu_assert_unimplemented(); - } -} - -void IMLInstruction::ReplaceFPR(IMLRegID fprRegisterSearched, IMLRegID fprRegisterReplaced) -{ - if (type == PPCREC_IML_TYPE_R_NAME) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_NAME_R) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_R_R) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_R_S32) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_R_R_S32 || type == PPCREC_IML_TYPE_R_R_S32_CARRY) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_R_R_R || type == PPCREC_IML_TYPE_R_R_R_CARRY) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_COMPARE || type == PPCREC_IML_TYPE_COMPARE_S32 || type == PPCREC_IML_TYPE_CONDITIONAL_JUMP || type == PPCREC_IML_TYPE_JUMP) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_NO_OP) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_MACRO) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_LOAD) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_LOAD_INDEXED) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_STORE) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_STORE_INDEXED) - { - // not affected - } - else if (type == PPCREC_IML_TYPE_ATOMIC_CMP_STORE) - { - ; - } - else if (type == PPCREC_IML_TYPE_FPR_LOAD) - { - op_storeLoad.registerData = replaceRegisterId(op_storeLoad.registerData, fprRegisterSearched, fprRegisterReplaced); - } - else if (type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED) - { - op_storeLoad.registerData = replaceRegisterId(op_storeLoad.registerData, fprRegisterSearched, fprRegisterReplaced); - } - else if (type == PPCREC_IML_TYPE_FPR_STORE) - { - op_storeLoad.registerData = replaceRegisterId(op_storeLoad.registerData, fprRegisterSearched, fprRegisterReplaced); - } - else if (type == PPCREC_IML_TYPE_FPR_STORE_INDEXED) - { - op_storeLoad.registerData = replaceRegisterId(op_storeLoad.registerData, fprRegisterSearched, fprRegisterReplaced); - } - else if (type == PPCREC_IML_TYPE_FPR_R_R) - { - op_fpr_r_r.regR = replaceRegisterId(op_fpr_r_r.regR, fprRegisterSearched, fprRegisterReplaced); - op_fpr_r_r.regA = replaceRegisterId(op_fpr_r_r.regA, fprRegisterSearched, fprRegisterReplaced); - } - else if (type == PPCREC_IML_TYPE_FPR_R_R_R) - { - op_fpr_r_r_r.regR = replaceRegisterId(op_fpr_r_r_r.regR, fprRegisterSearched, fprRegisterReplaced); - op_fpr_r_r_r.regA = replaceRegisterId(op_fpr_r_r_r.regA, fprRegisterSearched, fprRegisterReplaced); - op_fpr_r_r_r.regB = replaceRegisterId(op_fpr_r_r_r.regB, fprRegisterSearched, fprRegisterReplaced); - } - else if (type == PPCREC_IML_TYPE_FPR_R_R_R_R) - { - op_fpr_r_r_r_r.regR = replaceRegisterId(op_fpr_r_r_r_r.regR, fprRegisterSearched, fprRegisterReplaced); - op_fpr_r_r_r_r.regA = replaceRegisterId(op_fpr_r_r_r_r.regA, fprRegisterSearched, fprRegisterReplaced); - op_fpr_r_r_r_r.regB = replaceRegisterId(op_fpr_r_r_r_r.regB, fprRegisterSearched, fprRegisterReplaced); - op_fpr_r_r_r_r.regC = replaceRegisterId(op_fpr_r_r_r_r.regC, fprRegisterSearched, fprRegisterReplaced); - } - else if (type == PPCREC_IML_TYPE_FPR_R) - { - op_fpr_r.regR = replaceRegisterId(op_fpr_r.regR, fprRegisterSearched, fprRegisterReplaced); + // no registers read or written (except for the implicit eflags) } else { diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.h b/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.h index 817fef19..3ba0a1af 100644 --- a/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.h +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.h @@ -1,6 +1,7 @@ #pragma once using IMLRegID = uint16; // 16 bit ID +using IMLPhysReg = sint32; // arbitrary value that is up to the architecture backend, usually this will be the register index. A value of -1 is reserved and means not assigned // format of IMLReg: // 0-15 (16 bit) IMLRegID @@ -98,6 +99,7 @@ private: }; static const IMLReg IMLREG_INVALID(IMLRegFormat::INVALID_FORMAT, IMLRegFormat::INVALID_FORMAT, 0, 0); +static const IMLRegID IMLRegID_INVALID(0xFFFF); using IMLName = uint32; @@ -120,11 +122,9 @@ enum PPCREC_IML_OP_RIGHT_SHIFT_U, // right shift operator (unsigned) PPCREC_IML_OP_RIGHT_SHIFT_S, // right shift operator (signed) // ppc - PPCREC_IML_OP_RLWIMI, // RLWIMI instruction (rotate, merge based on mask) PPCREC_IML_OP_SLW, // SLW (shift based on register by up to 63 bits) PPCREC_IML_OP_SRW, // SRW (shift based on register by up to 63 bits) PPCREC_IML_OP_CNTLZW, - PPCREC_IML_OP_DCBZ, // clear 32 bytes aligned to 0x20 // FPU PPCREC_IML_OP_FPR_ADD_BOTTOM, PPCREC_IML_OP_FPR_ADD_PAIR, @@ -142,7 +142,6 @@ enum PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM, // leave top of destination untouched PPCREC_IML_OP_FPR_COPY_BOTTOM_AND_TOP_SWAPPED, PPCREC_IML_OP_FPR_EXPAND_BOTTOM32_TO_BOTTOM64_AND_TOP64, // expand bottom f32 to f64 in bottom and top half - PPCREC_IML_OP_FPR_BOTTOM_FRES_TO_BOTTOM_AND_TOP, // calculate reciprocal with Espresso accuracy of source bottom half and write result to destination bottom and top half PPCREC_IML_OP_FPR_FCMPO_BOTTOM, // deprecated PPCREC_IML_OP_FPR_FCMPU_BOTTOM, // deprecated PPCREC_IML_OP_FPR_FCMPU_TOP, // deprecated @@ -180,6 +179,11 @@ enum // R_R_R_carry PPCREC_IML_OP_ADD_WITH_CARRY, // similar to ADD but also adds carry bit (0 or 1) + + // X86 extension + PPCREC_IML_OP_X86_CMP, // R_R and R_S32 + + PPCREC_IML_OP_INVALID }; #define PPCREC_IML_OP_FPR_COPY_PAIR (PPCREC_IML_OP_ASSIGN) @@ -192,7 +196,6 @@ enum PPCREC_IML_MACRO_B_FAR, // branch to different function PPCREC_IML_MACRO_COUNT_CYCLES, // decrease current remaining thread cycles by a certain amount PPCREC_IML_MACRO_HLE, // HLE function call - PPCREC_IML_MACRO_MFTB, // get TB register value (low or high) PPCREC_IML_MACRO_LEAVE, // leaves recompiler and switches to interpeter // debugging PPCREC_IML_MACRO_DEBUGBREAK, // throws a debugbreak @@ -247,8 +250,8 @@ enum // atomic PPCREC_IML_TYPE_ATOMIC_CMP_STORE, - // conditional (legacy) - PPCREC_IML_TYPE_CONDITIONAL_R_S32, + // function call + PPCREC_IML_TYPE_CALL_IMM, // call to fixed immediate address // FPR PPCREC_IML_TYPE_FPR_LOAD, // r* = (bitdepth) [r*+s32*] (single or paired single mode) @@ -261,6 +264,9 @@ enum PPCREC_IML_TYPE_FPR_R, PPCREC_IML_TYPE_FPR_COMPARE, // r* = r* CMP[cond] r* + + // X86 specific + PPCREC_IML_TYPE_X86_EFLAGS_JCC, }; enum // IMLName @@ -324,36 +330,8 @@ struct IMLUsedRegisters { IMLUsedRegisters() {}; - // GPR - union + bool IsWrittenByRegId(IMLRegID regId) const { - struct - { - IMLReg readGPR1; - IMLReg readGPR2; - IMLReg readGPR3; - IMLReg writtenGPR1; - IMLReg writtenGPR2; - }; - }; - // FPR - union - { - struct - { - // note: If destination operand is not fully written (PS0 and PS1) it will be added to the read registers - IMLReg readFPR1; - IMLReg readFPR2; - IMLReg readFPR3; - IMLReg readFPR4; - IMLReg writtenFPR1; - }; - }; - - bool IsBaseGPRWritten(IMLReg imlReg) const - { - cemu_assert_debug(imlReg.IsValid()); - auto regId = imlReg.GetRegID(); if (writtenGPR1.IsValid() && writtenGPR1.GetRegID() == regId) return true; if (writtenGPR2.IsValid() && writtenGPR2.GetRegID() == regId) @@ -361,6 +339,13 @@ struct IMLUsedRegisters return false; } + bool IsBaseGPRWritten(IMLReg imlReg) const + { + cemu_assert_debug(imlReg.IsValid()); + auto regId = imlReg.GetRegID(); + return IsWrittenByRegId(regId); + } + template void ForEachWrittenGPR(Fn F) const { @@ -379,6 +364,8 @@ struct IMLUsedRegisters F(readGPR2); if (readGPR3.IsValid()) F(readGPR3); + if (readGPR4.IsValid()) + F(readGPR4); } template @@ -391,23 +378,20 @@ struct IMLUsedRegisters F(readGPR2, false); if (readGPR3.IsValid()) F(readGPR3, false); + if (readGPR4.IsValid()) + F(readGPR4, false); if (writtenGPR1.IsValid()) F(writtenGPR1, true); if (writtenGPR2.IsValid()) F(writtenGPR2, true); - // FPRs - if (readFPR1.IsValid()) - F(readFPR1, false); - if (readFPR2.IsValid()) - F(readFPR2, false); - if (readFPR3.IsValid()) - F(readFPR3, false); - if (readFPR4.IsValid()) - F(readFPR4, false); - if (writtenFPR1.IsValid()) - F(writtenFPR1, true); } + IMLReg readGPR1; + IMLReg readGPR2; + IMLReg readGPR3; + IMLReg readGPR4; + IMLReg writtenGPR1; + IMLReg writtenGPR2; }; struct IMLInstruction @@ -491,6 +475,14 @@ struct IMLInstruction sint32 immS32; }op_storeLoad; struct + { + uintptr_t callAddress; + IMLReg regParam0; + IMLReg regParam1; + IMLReg regParam2; + IMLReg regReturn; + }op_call_imm; + struct { IMLReg regR; IMLReg regA; @@ -556,6 +548,12 @@ struct IMLInstruction uint8 crBitIndex; bool bitMustBeSet; }op_conditional_r_s32; + // X86 specific + struct + { + IMLCondition cond; + bool invertedCondition; + }op_x86_eflags_jcc; }; bool IsSuffixInstruction() const @@ -565,10 +563,10 @@ struct IMLInstruction type == PPCREC_IML_TYPE_MACRO && operation == PPCREC_IML_MACRO_B_TO_REG || type == PPCREC_IML_TYPE_MACRO && operation == PPCREC_IML_MACRO_LEAVE || type == PPCREC_IML_TYPE_MACRO && operation == PPCREC_IML_MACRO_HLE || - type == PPCREC_IML_TYPE_MACRO && operation == PPCREC_IML_MACRO_MFTB || type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK || type == PPCREC_IML_TYPE_JUMP || - type == PPCREC_IML_TYPE_CONDITIONAL_JUMP) + type == PPCREC_IML_TYPE_CONDITIONAL_JUMP || + type == PPCREC_IML_TYPE_X86_EFLAGS_JCC) return true; return false; } @@ -676,7 +674,7 @@ struct IMLInstruction void make_compare(IMLReg regA, IMLReg regB, IMLReg regR, IMLCondition cond) { this->type = PPCREC_IML_TYPE_COMPARE; - this->operation = -999; + this->operation = PPCREC_IML_OP_INVALID; this->op_compare.regR = regR; this->op_compare.regA = regA; this->op_compare.regB = regB; @@ -686,7 +684,7 @@ struct IMLInstruction void make_compare_s32(IMLReg regA, sint32 immS32, IMLReg regR, IMLCondition cond) { this->type = PPCREC_IML_TYPE_COMPARE_S32; - this->operation = -999; + this->operation = PPCREC_IML_OP_INVALID; this->op_compare_s32.regR = regR; this->op_compare_s32.regA = regA; this->op_compare_s32.immS32 = immS32; @@ -696,7 +694,7 @@ struct IMLInstruction void make_conditional_jump(IMLReg regBool, bool mustBeTrue) { this->type = PPCREC_IML_TYPE_CONDITIONAL_JUMP; - this->operation = -999; + this->operation = PPCREC_IML_OP_INVALID; this->op_conditional_jump.registerBool = regBool; this->op_conditional_jump.mustBeTrue = mustBeTrue; } @@ -704,7 +702,7 @@ struct IMLInstruction void make_jump() { this->type = PPCREC_IML_TYPE_JUMP; - this->operation = -999; + this->operation = PPCREC_IML_OP_INVALID; } // load from memory @@ -743,6 +741,17 @@ struct IMLInstruction this->op_atomic_compare_store.regBoolOut = regSuccessOutput; } + void make_call_imm(uintptr_t callAddress, IMLReg param0, IMLReg param1, IMLReg param2, IMLReg regReturn) + { + this->type = PPCREC_IML_TYPE_CALL_IMM; + this->operation = 0; + this->op_call_imm.callAddress = callAddress; + this->op_call_imm.regParam0 = param0; + this->op_call_imm.regParam1 = param1; + this->op_call_imm.regParam2 = param2; + this->op_call_imm.regReturn = regReturn; + } + void make_fpr_compare(IMLReg regA, IMLReg regB, IMLReg regR, IMLCondition cond) { this->type = PPCREC_IML_TYPE_FPR_COMPARE; @@ -753,12 +762,19 @@ struct IMLInstruction this->op_fpr_compare.cond = cond; } + /* X86 specific */ + void make_x86_eflags_jcc(IMLCondition cond, bool invertedCondition) + { + this->type = PPCREC_IML_TYPE_X86_EFLAGS_JCC; + this->operation = -999; + this->op_x86_eflags_jcc.cond = cond; + this->op_x86_eflags_jcc.invertedCondition = invertedCondition; + } + void CheckRegisterUsage(IMLUsedRegisters* registersUsed) const; + bool HasSideEffects() const; // returns true if the instruction has side effects beyond just reading and writing registers. Dead code elimination uses this to know if an instruction can be dropped when the regular register outputs are not used void RewriteGPR(const std::unordered_map& translationTable); - void ReplaceFPRs(IMLReg fprRegisterSearched[4], IMLReg fprRegisterReplaced[4]); - void ReplaceFPR(IMLRegID fprRegisterSearched, IMLRegID fprRegisterReplaced); - }; // architecture specific constants diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLOptimizer.cpp b/src/Cafe/HW/Espresso/Recompiler/IML/IMLOptimizer.cpp index cdf922ce..077dfb18 100644 --- a/src/Cafe/HW/Espresso/Recompiler/IML/IMLOptimizer.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLOptimizer.cpp @@ -6,6 +6,11 @@ #include "../PPCRecompilerIml.h" #include "../BackendX64/BackendX64.h" +#include "Common/FileStream.h" + +#include +#include + IMLReg _FPRRegFromID(IMLRegID regId) { return IMLReg(IMLRegFormat::F64, IMLRegFormat::F64, 0, regId); @@ -52,15 +57,15 @@ void PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext_t* ppcI // check if FPR is overwritten (we can actually ignore read operations?) imlInstruction->CheckRegisterUsage(®istersUsed); - if (registersUsed.writtenFPR1.IsValidAndSameRegID(fprIndex)) + if (registersUsed.writtenGPR1.IsValidAndSameRegID(fprIndex) || registersUsed.writtenGPR2.IsValidAndSameRegID(fprIndex)) break; - if (registersUsed.readFPR1.IsValidAndSameRegID(fprIndex)) + if (registersUsed.readGPR1.IsValidAndSameRegID(fprIndex)) break; - if (registersUsed.readFPR2.IsValidAndSameRegID(fprIndex)) + if (registersUsed.readGPR2.IsValidAndSameRegID(fprIndex)) break; - if (registersUsed.readFPR3.IsValidAndSameRegID(fprIndex)) + if (registersUsed.readGPR3.IsValidAndSameRegID(fprIndex)) break; - if (registersUsed.readFPR4.IsValidAndSameRegID(fprIndex)) + if (registersUsed.readGPR4.IsValidAndSameRegID(fprIndex)) break; } @@ -328,3 +333,464 @@ void PPCRecompiler_optimizePSQLoadAndStore(ppcImlGenContext_t* ppcImlGenContext) } } } + +// analyses register dependencies across the entire function +// per segment this will generate information about which registers need to be preserved and which ones don't (e.g. are overwritten) +class IMLOptimizerRegIOAnalysis +{ + public: + // constructor with segment pointer list as span + IMLOptimizerRegIOAnalysis(std::span segmentList, uint32 maxRegId) : m_segmentList(segmentList), m_maxRegId(maxRegId) + { + m_segRegisterInOutList.resize(segmentList.size()); + } + + struct IMLSegmentRegisterInOut + { + // todo - since our register ID range is usually pretty small (<64) we could use integer bitmasks to accelerate this? There is a helper class used in RA code already + std::unordered_set regWritten; // registers which are modified in this segment + std::unordered_set regImported; // registers which are read in this segment before they are written (importing value from previous segments) + std::unordered_set regForward; // registers which are not read or written in this segment, but are imported into a later segment (propagated info) + }; + + // calculate which registers are imported (read-before-written) and forwarded (read-before-written by a later segment) per segment + // then in a second step propagate the dependencies across linked segments + void ComputeDepedencies() + { + std::vector& segRegisterInOutList = m_segRegisterInOutList; + IMLSegmentRegisterInOut* segIO = segRegisterInOutList.data(); + uint32 index = 0; + for(auto& seg : m_segmentList) + { + seg->momentaryIndex = index; + index++; + for(auto& instr : seg->imlList) + { + IMLUsedRegisters registerUsage; + instr.CheckRegisterUsage(®isterUsage); + // registers are considered imported if they are read before being written in this seg + registerUsage.ForEachReadGPR([&](IMLReg gprReg) { + IMLRegID gprId = gprReg.GetRegID(); + if (!segIO->regWritten.contains(gprId)) + { + segIO->regImported.insert(gprId); + } + }); + registerUsage.ForEachWrittenGPR([&](IMLReg gprReg) { + IMLRegID gprId = gprReg.GetRegID(); + segIO->regWritten.insert(gprId); + }); + } + segIO++; + } + // for every exit segment, import all registers + for(auto& seg : m_segmentList) + { + if (!seg->nextSegmentIsUncertain) + continue; + if(seg->deadCodeEliminationHintSeg) + continue; + IMLSegmentRegisterInOut& segIO = segRegisterInOutList[seg->momentaryIndex]; + for(uint32 i=0; i<=m_maxRegId; i++) + { + segIO.regImported.insert((IMLRegID)i); + } + } + // broadcast dependencies across segment chains + std::unordered_set segIdsWhichNeedUpdate; + for (uint32 i = 0; i < m_segmentList.size(); i++) + { + segIdsWhichNeedUpdate.insert(i); + } + while(!segIdsWhichNeedUpdate.empty()) + { + auto firstIt = segIdsWhichNeedUpdate.begin(); + uint32 segId = *firstIt; + segIdsWhichNeedUpdate.erase(firstIt); + // forward regImported and regForward to earlier segments into their regForward, unless the register is written + auto& curSeg = m_segmentList[segId]; + IMLSegmentRegisterInOut& curSegIO = segRegisterInOutList[segId]; + for(auto& prevSeg : curSeg->list_prevSegments) + { + IMLSegmentRegisterInOut& prevSegIO = segRegisterInOutList[prevSeg->momentaryIndex]; + bool prevSegChanged = false; + for(auto& regId : curSegIO.regImported) + { + if (!prevSegIO.regWritten.contains(regId)) + prevSegChanged |= prevSegIO.regForward.insert(regId).second; + } + for(auto& regId : curSegIO.regForward) + { + if (!prevSegIO.regWritten.contains(regId)) + prevSegChanged |= prevSegIO.regForward.insert(regId).second; + } + if(prevSegChanged) + segIdsWhichNeedUpdate.insert(prevSeg->momentaryIndex); + } + // same for hint links + for(auto& prevSeg : curSeg->list_deadCodeHintBy) + { + IMLSegmentRegisterInOut& prevSegIO = segRegisterInOutList[prevSeg->momentaryIndex]; + bool prevSegChanged = false; + for(auto& regId : curSegIO.regImported) + { + if (!prevSegIO.regWritten.contains(regId)) + prevSegChanged |= prevSegIO.regForward.insert(regId).second; + } + for(auto& regId : curSegIO.regForward) + { + if (!prevSegIO.regWritten.contains(regId)) + prevSegChanged |= prevSegIO.regForward.insert(regId).second; + } + if(prevSegChanged) + segIdsWhichNeedUpdate.insert(prevSeg->momentaryIndex); + } + } + } + + std::unordered_set GetRegistersNeededAtEndOfSegment(IMLSegment& seg) + { + std::unordered_set regsNeeded; + if(seg.nextSegmentIsUncertain) + { + if(seg.deadCodeEliminationHintSeg) + { + auto& nextSegIO = m_segRegisterInOutList[seg.deadCodeEliminationHintSeg->momentaryIndex]; + regsNeeded.insert(nextSegIO.regImported.begin(), nextSegIO.regImported.end()); + regsNeeded.insert(nextSegIO.regForward.begin(), nextSegIO.regForward.end()); + } + else + { + // add all regs + for(uint32 i = 0; i <= m_maxRegId; i++) + regsNeeded.insert(i); + } + return regsNeeded; + } + if(seg.nextSegmentBranchTaken) + { + auto& nextSegIO = m_segRegisterInOutList[seg.nextSegmentBranchTaken->momentaryIndex]; + regsNeeded.insert(nextSegIO.regImported.begin(), nextSegIO.regImported.end()); + regsNeeded.insert(nextSegIO.regForward.begin(), nextSegIO.regForward.end()); + } + if(seg.nextSegmentBranchNotTaken) + { + auto& nextSegIO = m_segRegisterInOutList[seg.nextSegmentBranchNotTaken->momentaryIndex]; + regsNeeded.insert(nextSegIO.regImported.begin(), nextSegIO.regImported.end()); + regsNeeded.insert(nextSegIO.regForward.begin(), nextSegIO.regForward.end()); + } + return regsNeeded; + } + + bool IsRegisterNeededAtEndOfSegment(IMLSegment& seg, IMLRegID regId) + { + if(seg.nextSegmentIsUncertain) + { + if(!seg.deadCodeEliminationHintSeg) + return true; + auto& nextSegIO = m_segRegisterInOutList[seg.deadCodeEliminationHintSeg->momentaryIndex]; + if(nextSegIO.regImported.contains(regId)) + return true; + if(nextSegIO.regForward.contains(regId)) + return true; + return false; + } + if(seg.nextSegmentBranchTaken) + { + auto& nextSegIO = m_segRegisterInOutList[seg.nextSegmentBranchTaken->momentaryIndex]; + if(nextSegIO.regImported.contains(regId)) + return true; + if(nextSegIO.regForward.contains(regId)) + return true; + } + if(seg.nextSegmentBranchNotTaken) + { + auto& nextSegIO = m_segRegisterInOutList[seg.nextSegmentBranchNotTaken->momentaryIndex]; + if(nextSegIO.regImported.contains(regId)) + return true; + if(nextSegIO.regForward.contains(regId)) + return true; + } + return false; + } + + private: + std::span m_segmentList; + uint32 m_maxRegId; + + std::vector m_segRegisterInOutList; + +}; + +// scan backwards starting from index and return the index of the first found instruction which writes to the given register (by id) +sint32 IMLUtil_FindInstructionWhichWritesRegister(IMLSegment& seg, sint32 startIndex, IMLReg reg, sint32 maxScanDistance = -1) +{ + sint32 endIndex = std::max(startIndex - maxScanDistance, 0); + for (sint32 i = startIndex; i >= endIndex; i--) + { + IMLInstruction& imlInstruction = seg.imlList[i]; + IMLUsedRegisters registersUsed; + imlInstruction.CheckRegisterUsage(®istersUsed); + if (registersUsed.IsBaseGPRWritten(reg)) + return i; + } + return -1; +} + +// returns true if the instruction can safely be moved while keeping ordering constraints and data dependencies intact +// initialIndex is inclusive, targetIndex is exclusive +bool IMLUtil_CanMoveInstructionTo(IMLSegment& seg, sint32 initialIndex, sint32 targetIndex) +{ + boost::container::static_vector regsWritten; + boost::container::static_vector regsRead; + // get list of read and written registers + IMLUsedRegisters registersUsed; + seg.imlList[initialIndex].CheckRegisterUsage(®istersUsed); + registersUsed.ForEachAccessedGPR([&](IMLReg reg, bool isWritten) { + if (isWritten) + regsWritten.push_back(reg.GetRegID()); + else + regsRead.push_back(reg.GetRegID()); + }); + // check all the instructions inbetween + if(initialIndex < targetIndex) + { + sint32 scanStartIndex = initialIndex+1; // +1 to skip the moving instruction itself + sint32 scanEndIndex = targetIndex; + for (sint32 i = scanStartIndex; i < scanEndIndex; i++) + { + IMLUsedRegisters registersUsed; + seg.imlList[i].CheckRegisterUsage(®istersUsed); + // in order to be able to move an instruction past another instruction, any of the read registers must not be modified (written) + // and any of it's written registers must not be read + bool canMove = true; + registersUsed.ForEachAccessedGPR([&](IMLReg reg, bool isWritten) { + IMLRegID regId = reg.GetRegID(); + if (!isWritten) + canMove = canMove && std::find(regsWritten.begin(), regsWritten.end(), regId) == regsWritten.end(); + else + canMove = canMove && std::find(regsRead.begin(), regsRead.end(), regId) == regsRead.end(); + }); + if(!canMove) + return false; + } + } + else + { + cemu_assert_unimplemented(); // backwards scan is todo + return false; + } + return true; +} + +sint32 IMLUtil_CountRegisterReadsInRange(IMLSegment& seg, sint32 scanStartIndex, sint32 scanEndIndex, IMLRegID regId) +{ + cemu_assert_debug(scanStartIndex <= scanEndIndex); + cemu_assert_debug(scanEndIndex < seg.imlList.size()); + sint32 count = 0; + for (sint32 i = scanStartIndex; i <= scanEndIndex; i++) + { + IMLUsedRegisters registersUsed; + seg.imlList[i].CheckRegisterUsage(®istersUsed); + registersUsed.ForEachReadGPR([&](IMLReg reg) { + if (reg.GetRegID() == regId) + count++; + }); + } + return count; +} + +// move instruction from one index to another +// instruction will be inserted before the instruction at targetIndex +// returns the new instruction index of the moved instruction +sint32 IMLUtil_MoveInstructionTo(IMLSegment& seg, sint32 initialIndex, sint32 targetIndex) +{ + cemu_assert_debug(initialIndex != targetIndex); + IMLInstruction temp = seg.imlList[initialIndex]; + if (initialIndex < targetIndex) + { + cemu_assert_debug(targetIndex > 0); + targetIndex--; + for(size_t i=initialIndex; i regsNeeded = regIoAnalysis.GetRegistersNeededAtEndOfSegment(seg); + + // start with suffix instruction + if(seg.HasSuffixInstruction()) + { + IMLInstruction& imlInstruction = seg.imlList[seg.GetSuffixInstructionIndex()]; + IMLUsedRegisters registersUsed; + imlInstruction.CheckRegisterUsage(®istersUsed); + registersUsed.ForEachWrittenGPR([&](IMLReg reg) { + regsNeeded.erase(reg.GetRegID()); + }); + registersUsed.ForEachReadGPR([&](IMLReg reg) { + regsNeeded.insert(reg.GetRegID()); + }); + } + // iterate instructions backwards + for (sint32 i = seg.imlList.size() - (seg.HasSuffixInstruction() ? 2:1); i >= 0; i--) + { + IMLInstruction& imlInstruction = seg.imlList[i]; + IMLUsedRegisters registersUsed; + imlInstruction.CheckRegisterUsage(®istersUsed); + // register read -> remove from overwritten list + // register written -> add to overwritten list + + // check if this instruction only writes registers which will never be read + bool onlyWritesRedundantRegisters = true; + registersUsed.ForEachWrittenGPR([&](IMLReg reg) { + if (regsNeeded.contains(reg.GetRegID())) + onlyWritesRedundantRegisters = false; + }); + // check if any of the written registers are read after this point + registersUsed.ForEachWrittenGPR([&](IMLReg reg) { + regsNeeded.erase(reg.GetRegID()); + }); + registersUsed.ForEachReadGPR([&](IMLReg reg) { + regsNeeded.insert(reg.GetRegID()); + }); + if(!imlInstruction.HasSideEffects() && onlyWritesRedundantRegisters) + { + imlInstruction.make_no_op(); + } + } +} + +void IMLOptimizerX86_SubstituteCJumpForEflagsJump(IMLOptimizerRegIOAnalysis& regIoAnalysis, IMLSegment& seg) +{ + // convert and optimize bool condition jumps to eflags condition jumps + // - Moves eflag setter (e.g. cmp) closer to eflags consumer (conditional jump) if necessary. If not possible but required then exit early + // - Since we only rely on eflags, the boolean register can be optimized out if DCE considers it unused + // - Further detect and optimize patterns like DEC + CMP + JCC into fused ops (todo) + + // check if this segment ends with a conditional jump + if(!seg.HasSuffixInstruction()) + return; + sint32 cjmpInstIndex = seg.GetSuffixInstructionIndex(); + if(cjmpInstIndex < 0) + return; + IMLInstruction& cjumpInstr = seg.imlList[cjmpInstIndex]; + if( cjumpInstr.type != PPCREC_IML_TYPE_CONDITIONAL_JUMP ) + return; + IMLReg regCondBool = cjumpInstr.op_conditional_jump.registerBool; + bool invertedCondition = !cjumpInstr.op_conditional_jump.mustBeTrue; + // find the instruction which sets the bool + sint32 cmpInstrIndex = IMLUtil_FindInstructionWhichWritesRegister(seg, cjmpInstIndex-1, regCondBool, 20); + if(cmpInstrIndex < 0) + return; + // check if its an instruction combo which can be optimized (currently only cmp + cjump) and get the condition + IMLInstruction& condSetterInstr = seg.imlList[cmpInstrIndex]; + IMLCondition cond; + if(condSetterInstr.type == PPCREC_IML_TYPE_COMPARE) + cond = condSetterInstr.op_compare.cond; + else if(condSetterInstr.type == PPCREC_IML_TYPE_COMPARE_S32) + cond = condSetterInstr.op_compare_s32.cond; + else + return; + // check if instructions inbetween modify eflags + sint32 indexEflagsSafeStart = -1; // index of the first instruction which does not modify eflags up to cjump + for(sint32 i = cjmpInstIndex-1; i > cmpInstrIndex; i--) + { + if(IMLOptimizerX86_ModifiesEFlags(seg.imlList[i])) + { + indexEflagsSafeStart = i+1; + break; + } + } + if(indexEflagsSafeStart >= 0) + { + cemu_assert(indexEflagsSafeStart > 0); + // there are eflags-modifying instructions inbetween the bool setter and cjump + // try to move the eflags setter close enough to the cjump (to indexEflagsSafeStart) + bool canMove = IMLUtil_CanMoveInstructionTo(seg, cmpInstrIndex, indexEflagsSafeStart); + if(!canMove) + { + return; + } + else + { + cmpInstrIndex = IMLUtil_MoveInstructionTo(seg, cmpInstrIndex, indexEflagsSafeStart); + } + } + // we can turn the jump into an eflags jump + cjumpInstr.make_x86_eflags_jcc(cond, invertedCondition); + + if (IMLUtil_CountRegisterReadsInRange(seg, cmpInstrIndex, cjmpInstIndex, regCondBool.GetRegID()) > 1 || regIoAnalysis.IsRegisterNeededAtEndOfSegment(seg, regCondBool.GetRegID())) + return; // bool register is used beyond the CMP, we can't drop it + + auto& cmpInstr = seg.imlList[cmpInstrIndex]; + cemu_assert_debug(cmpInstr.type == PPCREC_IML_TYPE_COMPARE || cmpInstr.type == PPCREC_IML_TYPE_COMPARE_S32); + if(cmpInstr.type == PPCREC_IML_TYPE_COMPARE) + { + IMLReg regA = cmpInstr.op_compare.regA; + IMLReg regB = cmpInstr.op_compare.regB; + seg.imlList[cmpInstrIndex].make_r_r(PPCREC_IML_OP_X86_CMP, regA, regB); + } + else + { + IMLReg regA = cmpInstr.op_compare_s32.regA; + sint32 val = cmpInstr.op_compare_s32.immS32; + seg.imlList[cmpInstrIndex].make_r_s32(PPCREC_IML_OP_X86_CMP, regA, val); + } + +} + +void IMLOptimizer_StandardOptimizationPassForSegment(IMLOptimizerRegIOAnalysis& regIoAnalysis, IMLSegment& seg) +{ + IMLOptimizer_RemoveDeadCodeFromSegment(regIoAnalysis, seg); + +#ifdef ARCH_X86_64 + // x86 specific optimizations + IMLOptimizerX86_SubstituteCJumpForEflagsJump(regIoAnalysis, seg); // this pass should be applied late since it creates invisible eflags dependencies (which would break further register dependency analysis) +#endif +} + +void IMLOptimizer_StandardOptimizationPass(ppcImlGenContext_t& ppcImlGenContext) +{ + IMLOptimizerRegIOAnalysis regIoAnalysis(ppcImlGenContext.segmentList2, ppcImlGenContext.GetMaxRegId()); + regIoAnalysis.ComputeDepedencies(); + for (IMLSegment* segIt : ppcImlGenContext.segmentList2) + { + IMLOptimizer_StandardOptimizationPassForSegment(regIoAnalysis, *segIt); + } +} diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.cpp b/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.cpp index 077a0516..c0efe0eb 100644 --- a/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.cpp @@ -6,12 +6,22 @@ #include "IMLRegisterAllocatorRanges.h" #include "../BackendX64/BackendX64.h" +#ifdef __aarch64__ +#include "../BackendAArch64/BackendAArch64.h" +#endif +#include #include +#include "Common/cpu_features.h" + +#define DEBUG_RA_EXTRA_VALIDATION 0 // if set to non-zero, additional expensive validation checks will be performed +#define DEBUG_RA_INSTRUCTION_GEN 0 + struct IMLRARegAbstractLiveness // preliminary liveness info. One entry per register and segment { - IMLRARegAbstractLiveness(IMLRegFormat regBaseFormat, sint32 usageStart, sint32 usageEnd) : regBaseFormat(regBaseFormat), usageStart(usageStart), usageEnd(usageEnd) {}; + IMLRARegAbstractLiveness(IMLRegFormat regBaseFormat, sint32 usageStart, sint32 usageEnd) + : regBaseFormat(regBaseFormat), usageStart(usageStart), usageEnd(usageEnd) {}; void TrackInstruction(sint32 index) { @@ -30,10 +40,9 @@ struct IMLRegisterAllocatorContext IMLRegisterAllocatorParameters* raParam; ppcImlGenContext_t* deprGenContext; // deprecated. Try to decouple IMLRA from other parts of IML/PPCRec - std::unordered_map regIdToBaseFormat; // a vector would be more efficient but it also means that reg ids have to be continuous and not completely arbitrary + std::unordered_map regIdToBaseFormat; // first pass std::vector> perSegmentAbstractRanges; - // second pass // helper methods inline std::unordered_map& GetSegmentAbstractRangeMap(IMLSegment* imlSegment) @@ -47,13 +56,147 @@ struct IMLRegisterAllocatorContext cemu_assert_debug(it != regIdToBaseFormat.cend()); return it->second; } - }; -uint32 recRACurrentIterationIndex = 0; - -uint32 PPCRecRA_getNextIterationIndex() +struct IMLFixedRegisters { + struct Entry + { + Entry(IMLReg reg, IMLPhysRegisterSet physRegSet) + : reg(reg), physRegSet(physRegSet) {} + + IMLReg reg; + IMLPhysRegisterSet physRegSet; + }; + boost::container::small_vector listInput; // fixed register requirements for instruction input edge + boost::container::small_vector listOutput; // fixed register requirements for instruction output edge +}; + +static void SetupCallingConvention(const IMLInstruction* instruction, IMLFixedRegisters& fixedRegs, const IMLPhysReg intParamToPhysReg[3], const IMLPhysReg floatParamToPhysReg[3], const IMLPhysReg intReturnPhysReg, const IMLPhysReg floatReturnPhysReg, IMLPhysRegisterSet volatileRegisters) +{ + sint32 numIntParams = 0, numFloatParams = 0; + + auto AddParameterMapping = [&](IMLReg reg) { + if (!reg.IsValid()) + return; + if (reg.GetBaseFormat() == IMLRegFormat::I64) + { + IMLPhysRegisterSet ps; + ps.SetAvailable(intParamToPhysReg[numIntParams]); + fixedRegs.listInput.emplace_back(reg, ps); + numIntParams++; + } + else if (reg.GetBaseFormat() == IMLRegFormat::F64) + { + IMLPhysRegisterSet ps; + ps.SetAvailable(floatParamToPhysReg[numFloatParams]); + fixedRegs.listInput.emplace_back(reg, ps); + numFloatParams++; + } + else + { + cemu_assert_suspicious(); + } + }; + AddParameterMapping(instruction->op_call_imm.regParam0); + AddParameterMapping(instruction->op_call_imm.regParam1); + AddParameterMapping(instruction->op_call_imm.regParam2); + // return value + if (instruction->op_call_imm.regReturn.IsValid()) + { + IMLRegFormat returnFormat = instruction->op_call_imm.regReturn.GetBaseFormat(); + bool isIntegerFormat = returnFormat == IMLRegFormat::I64 || returnFormat == IMLRegFormat::I32 || returnFormat == IMLRegFormat::I16 || returnFormat == IMLRegFormat::I8; + IMLPhysRegisterSet ps; + if (isIntegerFormat) + { + ps.SetAvailable(intReturnPhysReg); + volatileRegisters.SetReserved(intReturnPhysReg); + } + else + { + ps.SetAvailable(floatReturnPhysReg); + volatileRegisters.SetReserved(floatReturnPhysReg); + } + fixedRegs.listOutput.emplace_back(instruction->op_call_imm.regReturn, ps); + } + // block volatile registers from being used on the output edge, this makes the register allocator store them during the call + fixedRegs.listOutput.emplace_back(IMLREG_INVALID, volatileRegisters); +} + +#if defined(__aarch64__) +// aarch64 +static void GetInstructionFixedRegisters(IMLInstruction* instruction, IMLFixedRegisters& fixedRegs) +{ + fixedRegs.listInput.clear(); + fixedRegs.listOutput.clear(); + + // The purpose of GetInstructionFixedRegisters() is to constraint virtual registers to specific physical registers for instructions which need it + // on x86 this is used for instructions like SHL , CL where the CL register is hardwired. On aarch it's probably only necessary for setting up the calling convention + if (instruction->type == PPCREC_IML_TYPE_CALL_IMM) + { + const IMLPhysReg intParamToPhysReg[3] = {IMLArchAArch64::PHYSREG_GPR_BASE + 0, IMLArchAArch64::PHYSREG_GPR_BASE + 1, IMLArchAArch64::PHYSREG_GPR_BASE + 2}; + const IMLPhysReg floatParamToPhysReg[3] = {IMLArchAArch64::PHYSREG_FPR_BASE + 0, IMLArchAArch64::PHYSREG_FPR_BASE + 1, IMLArchAArch64::PHYSREG_FPR_BASE + 2}; + IMLPhysRegisterSet volatileRegs; + for (int i = 0; i <= 17; i++) // x0 to x17 are volatile + volatileRegs.SetAvailable(IMLArchAArch64::PHYSREG_GPR_BASE + i); + // v0-v7 & v16-v31 are volatile. For v8-v15 only the high 64 bits are volatile. + for (int i = 0; i <= 7; i++) + volatileRegs.SetAvailable(IMLArchAArch64::PHYSREG_FPR_BASE + i); + for (int i = 16; i <= 31; i++) + volatileRegs.SetAvailable(IMLArchAArch64::PHYSREG_FPR_BASE + i); + SetupCallingConvention(instruction, fixedRegs, intParamToPhysReg, floatParamToPhysReg, IMLArchAArch64::PHYSREG_GPR_BASE + 0, IMLArchAArch64::PHYSREG_FPR_BASE + 0, volatileRegs); + } +} +#else +// x86-64 +static void GetInstructionFixedRegisters(IMLInstruction* instruction, IMLFixedRegisters& fixedRegs) +{ + fixedRegs.listInput.clear(); + fixedRegs.listOutput.clear(); + + if (instruction->type == PPCREC_IML_TYPE_R_R_R) + { + if (instruction->operation == PPCREC_IML_OP_LEFT_SHIFT || instruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_S || instruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_U) + { + if(!g_CPUFeatures.x86.bmi2) + { + IMLPhysRegisterSet ps; + ps.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_ECX); + fixedRegs.listInput.emplace_back(instruction->op_r_r_r.regB, ps); + } + } + } + else if (instruction->type == PPCREC_IML_TYPE_ATOMIC_CMP_STORE) + { + IMLPhysRegisterSet ps; + ps.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_EAX); + fixedRegs.listInput.emplace_back(IMLREG_INVALID, ps); // none of the inputs may use EAX + fixedRegs.listOutput.emplace_back(instruction->op_atomic_compare_store.regBoolOut, ps); // but we output to EAX + } + else if (instruction->type == PPCREC_IML_TYPE_CALL_IMM) + { + const IMLPhysReg intParamToPhysReg[3] = {IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RCX, IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RDX, IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R8}; + const IMLPhysReg floatParamToPhysReg[3] = {IMLArchX86::PHYSREG_FPR_BASE + 0, IMLArchX86::PHYSREG_FPR_BASE + 1, IMLArchX86::PHYSREG_FPR_BASE + 2}; + IMLPhysRegisterSet volatileRegs; + volatileRegs.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RAX); + volatileRegs.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RCX); + volatileRegs.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RDX); + volatileRegs.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R8); + volatileRegs.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R9); + volatileRegs.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R10); + volatileRegs.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R11); + // YMM0-YMM5 are volatile + for (int i = 0; i <= 5; i++) + volatileRegs.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + i); + // for YMM6-YMM15 only the upper 128 bits are volatile which we dont use + SetupCallingConvention(instruction, fixedRegs, intParamToPhysReg, floatParamToPhysReg, IMLArchX86::PHYSREG_GPR_BASE + X86_REG_EAX, IMLArchX86::PHYSREG_FPR_BASE + 0, volatileRegs); + } +} +#endif + +uint32 IMLRA_GetNextIterationIndex() +{ + static uint32 recRACurrentIterationIndex = 0; recRACurrentIterationIndex++; return recRACurrentIterationIndex; } @@ -68,21 +211,21 @@ bool _detectLoop(IMLSegment* currentSegment, sint32 depth, uint32 iterationIndex return false; currentSegment->raInfo.lastIterationIndex = iterationIndex; currentSegment->raInfo.isPartOfProcessedLoop = false; - + if (currentSegment->nextSegmentIsUncertain) return false; if (currentSegment->nextSegmentBranchNotTaken) { if (currentSegment->nextSegmentBranchNotTaken->momentaryIndex > currentSegment->momentaryIndex) { - currentSegment->raInfo.isPartOfProcessedLoop = _detectLoop(currentSegment->nextSegmentBranchNotTaken, depth + 1, iterationIndex, imlSegmentLoopBase); + currentSegment->raInfo.isPartOfProcessedLoop |= _detectLoop(currentSegment->nextSegmentBranchNotTaken, depth + 1, iterationIndex, imlSegmentLoopBase); } } if (currentSegment->nextSegmentBranchTaken) { if (currentSegment->nextSegmentBranchTaken->momentaryIndex > currentSegment->momentaryIndex) { - currentSegment->raInfo.isPartOfProcessedLoop = _detectLoop(currentSegment->nextSegmentBranchTaken, depth + 1, iterationIndex, imlSegmentLoopBase); + currentSegment->raInfo.isPartOfProcessedLoop |= _detectLoop(currentSegment->nextSegmentBranchTaken, depth + 1, iterationIndex, imlSegmentLoopBase); } } if (currentSegment->raInfo.isPartOfProcessedLoop) @@ -90,9 +233,9 @@ bool _detectLoop(IMLSegment* currentSegment, sint32 depth, uint32 iterationIndex return currentSegment->raInfo.isPartOfProcessedLoop; } -void PPCRecRA_detectLoop(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegmentLoopBase) +void IMLRA_DetectLoop(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegmentLoopBase) { - uint32 iterationIndex = PPCRecRA_getNextIterationIndex(); + uint32 iterationIndex = IMLRA_GetNextIterationIndex(); imlSegmentLoopBase->raInfo.lastIterationIndex = iterationIndex; if (_detectLoop(imlSegmentLoopBase->nextSegmentBranchTaken, 0, iterationIndex, imlSegmentLoopBase)) { @@ -100,7 +243,7 @@ void PPCRecRA_detectLoop(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSe } } -void PPCRecRA_identifyLoop(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment) +void IMLRA_IdentifyLoop(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment) { if (imlSegment->nextSegmentIsUncertain) return; @@ -114,161 +257,223 @@ void PPCRecRA_identifyLoop(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* iml // check if this segment has a branch that goes backwards (potential complex loop) if (imlSegment->nextSegmentBranchTaken && imlSegment->nextSegmentBranchTaken->momentaryIndex < imlSegment->momentaryIndex) { - PPCRecRA_detectLoop(ppcImlGenContext, imlSegment); + IMLRA_DetectLoop(ppcImlGenContext, imlSegment); } } -#define SUBRANGE_LIST_SIZE (128) +#define SUBRANGE_LIST_SIZE (128) -sint32 PPCRecRA_countInstructionsUntilNextUse(raLivenessSubrange_t* subrange, sint32 startIndex) +sint32 IMLRA_CountDistanceUntilNextUse(raLivenessRange* subrange, raInstructionEdge startPosition) { - for (sint32 i = 0; i < subrange->list_locations.size(); i++) + for (sint32 i = 0; i < subrange->list_accessLocations.size(); i++) { - if (subrange->list_locations.data()[i].index >= startIndex) - return subrange->list_locations.data()[i].index - startIndex; - } - return INT_MAX; -} - -// count how many instructions there are until physRegister is used by any subrange (returns 0 if register is in use at startIndex, and INT_MAX if not used for the remainder of the segment) -sint32 PPCRecRA_countInstructionsUntilNextLocalPhysRegisterUse(IMLSegment* imlSegment, sint32 startIndex, sint32 physRegister) -{ - sint32 minDistance = INT_MAX; - // next - raLivenessSubrange_t* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; - while(subrangeItr) - { - if (subrangeItr->range->physicalRegister != physRegister) + if (subrange->list_accessLocations[i].pos >= startPosition) { - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + auto& it = subrange->list_accessLocations[i]; + cemu_assert_debug(it.IsRead() != it.IsWrite()); // an access location can be either read or write + cemu_assert_debug(!startPosition.ConnectsToPreviousSegment() && !startPosition.ConnectsToNextSegment()); + return it.pos.GetRaw() - startPosition.GetRaw(); + } + } + cemu_assert_debug(subrange->imlSegment->imlList.size() < 10000); + return 10001 * 2; +} + +// returns -1 if there is no fixed register requirement on or after startPosition +sint32 IMLRA_CountDistanceUntilFixedRegUsageInRange(IMLSegment* imlSegment, raLivenessRange* range, raInstructionEdge startPosition, sint32 physRegister, bool& hasFixedAccess) +{ + hasFixedAccess = false; + cemu_assert_debug(startPosition.IsInstructionIndex()); + for (auto& fixedReqEntry : range->list_fixedRegRequirements) + { + if (fixedReqEntry.pos < startPosition) + continue; + if (fixedReqEntry.allowedReg.IsAvailable(physRegister)) + { + hasFixedAccess = true; + return fixedReqEntry.pos.GetRaw() - startPosition.GetRaw(); + } + } + cemu_assert_debug(range->interval.end.IsInstructionIndex()); + return range->interval.end.GetRaw() - startPosition.GetRaw(); +} + +sint32 IMLRA_CountDistanceUntilFixedRegUsage(IMLSegment* imlSegment, raInstructionEdge startPosition, sint32 maxDistance, IMLRegID ourRegId, sint32 physRegister) +{ + cemu_assert_debug(startPosition.IsInstructionIndex()); + raInstructionEdge lastPos2; + lastPos2.Set(imlSegment->imlList.size(), false); + + raInstructionEdge endPos; + endPos = startPosition + maxDistance; + if (endPos > lastPos2) + endPos = lastPos2; + IMLFixedRegisters fixedRegs; + if (startPosition.IsOnOutputEdge()) + GetInstructionFixedRegisters(imlSegment->imlList.data() + startPosition.GetInstructionIndex(), fixedRegs); + for (raInstructionEdge currentPos = startPosition; currentPos <= endPos; ++currentPos) + { + if (currentPos.IsOnInputEdge()) + { + GetInstructionFixedRegisters(imlSegment->imlList.data() + currentPos.GetInstructionIndex(), fixedRegs); + } + auto& fixedRegAccess = currentPos.IsOnInputEdge() ? fixedRegs.listInput : fixedRegs.listOutput; + for (auto& fixedRegLoc : fixedRegAccess) + { + if (fixedRegLoc.reg.IsInvalid() || fixedRegLoc.reg.GetRegID() != ourRegId) + { + cemu_assert_debug(fixedRegLoc.reg.IsInvalid() || fixedRegLoc.physRegSet.HasExactlyOneAvailable()); // this whole function only makes sense when there is only one fixed register, otherwise there are extra permutations to consider. Except for IMLREG_INVALID which is used to indicate reserved registers + if (fixedRegLoc.physRegSet.IsAvailable(physRegister)) + return currentPos.GetRaw() - startPosition.GetRaw(); + } + } + } + return endPos.GetRaw() - startPosition.GetRaw(); +} + +// count how many instructions there are until physRegister is used by any subrange or reserved for any fixed register requirement (returns 0 if register is in use at startIndex) +sint32 PPCRecRA_countDistanceUntilNextLocalPhysRegisterUse(IMLSegment* imlSegment, raInstructionEdge startPosition, sint32 physRegister) +{ + cemu_assert_debug(startPosition.IsInstructionIndex()); + sint32 minDistance = (sint32)imlSegment->imlList.size() * 2 - startPosition.GetRaw(); + // next + raLivenessRange* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + while (subrangeItr) + { + if (subrangeItr->GetPhysicalRegister() != physRegister) + { + subrangeItr = subrangeItr->link_allSegmentRanges.next; continue; } - if (startIndex >= subrangeItr->start.index && startIndex < subrangeItr->end.index) + if (subrangeItr->interval.ContainsEdge(startPosition)) return 0; - if (subrangeItr->start.index >= startIndex) + if (subrangeItr->interval.end < startPosition) { - minDistance = std::min(minDistance, (subrangeItr->start.index - startIndex)); + subrangeItr = subrangeItr->link_allSegmentRanges.next; + continue; } - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + cemu_assert_debug(startPosition <= subrangeItr->interval.start); + sint32 currentDist = subrangeItr->interval.start.GetRaw() - startPosition.GetRaw(); + minDistance = std::min(minDistance, currentDist); + subrangeItr = subrangeItr->link_allSegmentRanges.next; } return minDistance; } struct IMLRALivenessTimeline { -// IMLRALivenessTimeline(raLivenessSubrange_t* subrangeChain) -// { -//#ifdef CEMU_DEBUG_ASSERT -// raLivenessSubrange_t* it = subrangeChain; -// raLivenessSubrange_t* prevIt = it; -// while (it) -// { -// cemu_assert_debug(prevIt->start.index <= it->start.index); -// prevIt = it; -// it = it->link_segmentSubrangesGPR.next; -// } -//#endif -// } - IMLRALivenessTimeline() { } // manually add an active range - void AddActiveRange(raLivenessSubrange_t* subrange) + void AddActiveRange(raLivenessRange* subrange) { activeRanges.emplace_back(subrange); } - // remove all ranges from activeRanges with end <= instructionIndex - void ExpireRanges(sint32 instructionIndex) + void ExpireRanges(raInstructionEdge expireUpTo) { expiredRanges.clear(); size_t count = activeRanges.size(); for (size_t f = 0; f < count; f++) { - raLivenessSubrange_t* liverange = activeRanges[f]; - if (liverange->end.index <= instructionIndex) + raLivenessRange* liverange = activeRanges[f]; + if (liverange->interval.end < expireUpTo) // this was <= but since end is not inclusive we need to use < { #ifdef CEMU_DEBUG_ASSERT - if (instructionIndex != RA_INTER_RANGE_END && (liverange->subrangeBranchTaken || liverange->subrangeBranchNotTaken)) + if (!expireUpTo.ConnectsToNextSegment() && (liverange->subrangeBranchTaken || liverange->subrangeBranchNotTaken)) assert_dbg(); // infinite subranges should not expire #endif expiredRanges.emplace_back(liverange); // remove entry - activeRanges[f] = activeRanges[count-1]; + activeRanges[f] = activeRanges[count - 1]; f--; count--; } } - if(count != activeRanges.size()) + if (count != activeRanges.size()) activeRanges.resize(count); } - std::span GetExpiredRanges() + std::span GetExpiredRanges() { - return { expiredRanges.data(), expiredRanges.size() }; + return {expiredRanges.data(), expiredRanges.size()}; } - boost::container::small_vector activeRanges; + std::span GetActiveRanges() + { + return {activeRanges.data(), activeRanges.size()}; + } -private: - boost::container::small_vector expiredRanges; + raLivenessRange* GetActiveRangeByVirtualRegId(IMLRegID regId) + { + for (auto& it : activeRanges) + if (it->virtualRegister == regId) + return it; + return nullptr; + } + + raLivenessRange* GetActiveRangeByPhysicalReg(sint32 physReg) + { + cemu_assert_debug(physReg >= 0); + for (auto& it : activeRanges) + if (it->physicalRegister == physReg) + return it; + return nullptr; + } + + boost::container::small_vector activeRanges; + + private: + boost::container::small_vector expiredRanges; }; -bool IsRangeOverlapping(raLivenessSubrange_t* rangeA, raLivenessSubrange_t* rangeB) -{ - if (rangeA->start.index < rangeB->end.index && rangeA->end.index > rangeB->start.index) - return true; - if ((rangeA->start.index == RA_INTER_RANGE_START && rangeA->start.index == rangeB->start.index)) - return true; - if (rangeA->end.index == RA_INTER_RANGE_END && rangeA->end.index == rangeB->end.index) - return true; - return false; -} - // mark occupied registers by any overlapping range as unavailable in physRegSet -void PPCRecRA_MaskOverlappingPhysRegForGlobalRange(raLivenessRange_t* range, IMLPhysRegisterSet& physRegSet) +void PPCRecRA_MaskOverlappingPhysRegForGlobalRange(raLivenessRange* range2, IMLPhysRegisterSet& physRegSet) { - for (auto& subrange : range->list_subranges) + auto clusterRanges = range2->GetAllSubrangesInCluster(); + for (auto& subrange : clusterRanges) { IMLSegment* imlSegment = subrange->imlSegment; - raLivenessSubrange_t* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; - while(subrangeItr) + raLivenessRange* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + while (subrangeItr) { if (subrange == subrangeItr) { // next - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + subrangeItr = subrangeItr->link_allSegmentRanges.next; continue; } - if(IsRangeOverlapping(subrange, subrangeItr)) + if (subrange->interval.IsOverlapping(subrangeItr->interval)) { - if (subrangeItr->range->physicalRegister >= 0) - physRegSet.SetReserved(subrangeItr->range->physicalRegister); + if (subrangeItr->GetPhysicalRegister() >= 0) + physRegSet.SetReserved(subrangeItr->GetPhysicalRegister()); } // next - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + subrangeItr = subrangeItr->link_allSegmentRanges.next; } } } -bool _livenessRangeStartCompare(raLivenessSubrange_t* lhs, raLivenessSubrange_t* rhs) { return lhs->start.index < rhs->start.index; } +bool _livenessRangeStartCompare(raLivenessRange* lhs, raLivenessRange* rhs) +{ + return lhs->interval.start < rhs->interval.start; +} void _sortSegmentAllSubrangesLinkedList(IMLSegment* imlSegment) { - raLivenessSubrange_t* subrangeList[4096+1]; + raLivenessRange* subrangeList[4096 + 1]; sint32 count = 0; // disassemble linked list - raLivenessSubrange_t* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + raLivenessRange* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; while (subrangeItr) { - if (count >= 4096) - assert_dbg(); + cemu_assert(count < 4096); subrangeList[count] = subrangeItr; count++; // next - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + subrangeItr = subrangeItr->link_allSegmentRanges.next; } if (count == 0) { @@ -280,99 +485,630 @@ void _sortSegmentAllSubrangesLinkedList(IMLSegment* imlSegment) // reassemble linked list subrangeList[count] = nullptr; imlSegment->raInfo.linkedList_allSubranges = subrangeList[0]; - subrangeList[0]->link_segmentSubrangesGPR.prev = nullptr; - subrangeList[0]->link_segmentSubrangesGPR.next = subrangeList[1]; + subrangeList[0]->link_allSegmentRanges.prev = nullptr; + subrangeList[0]->link_allSegmentRanges.next = subrangeList[1]; for (sint32 i = 1; i < count; i++) { - subrangeList[i]->link_segmentSubrangesGPR.prev = subrangeList[i - 1]; - subrangeList[i]->link_segmentSubrangesGPR.next = subrangeList[i + 1]; + subrangeList[i]->link_allSegmentRanges.prev = subrangeList[i - 1]; + subrangeList[i]->link_allSegmentRanges.next = subrangeList[i + 1]; } // validate list -#ifdef CEMU_DEBUG_ASSERT +#if DEBUG_RA_EXTRA_VALIDATION sint32 count2 = 0; subrangeItr = imlSegment->raInfo.linkedList_allSubranges; - sint32 currentStartIndex = RA_INTER_RANGE_START; + raInstructionEdge currentStartPosition; + currentStartPosition.SetRaw(RA_INTER_RANGE_START); while (subrangeItr) { count2++; - if (subrangeItr->start.index < currentStartIndex) + if (subrangeItr->interval2.start < currentStartPosition) assert_dbg(); - currentStartIndex = subrangeItr->start.index; + currentStartPosition = subrangeItr->interval2.start; // next - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + subrangeItr = subrangeItr->link_allSegmentRanges.next; } if (count != count2) assert_dbg(); #endif } -std::unordered_map& IMLRA_GetSubrangeMap(IMLSegment* imlSegment) +std::unordered_map& IMLRA_GetSubrangeMap(IMLSegment* imlSegment) { - return imlSegment->raInfo.linkedList_perVirtualGPR2; + return imlSegment->raInfo.linkedList_perVirtualRegister; } -raLivenessSubrange_t* IMLRA_GetSubrange(IMLSegment* imlSegment, IMLRegID regId) +raLivenessRange* IMLRA_GetSubrange(IMLSegment* imlSegment, IMLRegID regId) { - auto it = imlSegment->raInfo.linkedList_perVirtualGPR2.find(regId); - if (it == imlSegment->raInfo.linkedList_perVirtualGPR2.end()) + auto it = imlSegment->raInfo.linkedList_perVirtualRegister.find(regId); + if (it == imlSegment->raInfo.linkedList_perVirtualRegister.end()) return nullptr; return it->second; } -raLivenessSubrange_t* _GetSubrangeByInstructionIndexAndVirtualReg(IMLSegment* imlSegment, IMLReg regToSearch, sint32 instructionIndex) +struct raFixedRegRequirementWithVGPR { - uint32 regId = regToSearch.GetRegID(); - raLivenessSubrange_t* subrangeItr = IMLRA_GetSubrange(imlSegment, regId); - while (subrangeItr) + raFixedRegRequirementWithVGPR(raInstructionEdge pos, IMLPhysRegisterSet allowedReg, IMLRegID regId) + : pos(pos), allowedReg(allowedReg), regId(regId) {} + + raInstructionEdge pos; + IMLPhysRegisterSet allowedReg; + IMLRegID regId; +}; + +std::vector IMLRA_BuildSegmentInstructionFixedRegList(IMLSegment* imlSegment) +{ + std::vector frrList; + size_t index = 0; + while (index < imlSegment->imlList.size()) { - if (subrangeItr->start.index <= instructionIndex && subrangeItr->end.index > instructionIndex) - return subrangeItr; - subrangeItr = subrangeItr->link_sameVirtualRegisterGPR.next; + IMLFixedRegisters fixedRegs; + GetInstructionFixedRegisters(&imlSegment->imlList[index], fixedRegs); + raInstructionEdge pos; + pos.Set(index, true); + for (auto& fixedRegAccess : fixedRegs.listInput) + { + frrList.emplace_back(pos, fixedRegAccess.physRegSet, fixedRegAccess.reg.IsValid() ? fixedRegAccess.reg.GetRegID() : IMLRegID_INVALID); + } + pos = pos + 1; + for (auto& fixedRegAccess : fixedRegs.listOutput) + { + frrList.emplace_back(pos, fixedRegAccess.physRegSet, fixedRegAccess.reg.IsValid() ? fixedRegAccess.reg.GetRegID() : IMLRegID_INVALID); + } + index++; } - return nullptr; + return frrList; } -void IMLRA_IsolateRangeOnInstruction(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment, raLivenessSubrange_t* subrange, sint32 instructionIndex) +boost::container::small_vector IMLRA_GetRangeWithFixedRegReservationOverlappingPos(IMLSegment* imlSegment, raInstructionEdge pos, IMLPhysReg physReg) { - DEBUG_BREAK; + boost::container::small_vector rangeList; + for (raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; currentRange; currentRange = currentRange->link_allSegmentRanges.next) + { + if (!currentRange->interval.ContainsEdge(pos)) + continue; + IMLPhysRegisterSet allowedRegs; + if (!currentRange->GetAllowedRegistersEx(allowedRegs)) + continue; + if (allowedRegs.IsAvailable(physReg)) + rangeList.emplace_back(currentRange); + } + return rangeList; } void IMLRA_HandleFixedRegisters(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment) { - // this works as a pre-pass to actual register allocation. Assigning registers in advance based on fixed requirements (e.g. calling conventions and operations with fixed-reg input/output like x86 DIV/MUL) - // algorithm goes as follows: - // 1) Iterate all instructions from beginning to end and keep a list of covering ranges - // 2) If we encounter an instruction with a fixed register we: - // 2.0) Check if there are any other ranges already using the same fixed-register and if yes, we split them and unassign the register for any follow-up instructions just prior to the current instruction - // 2.1) For inputs: Split the range that needs to be assigned a phys reg on the current instruction. Basically creating a 1-instruction long subrange that we can assign the physical register. RA will then schedule register allocation around that and avoid moves - // 2.2) For outputs: Split the range that needs to be assigned a phys reg on the current instruction - // Q: What if a specific fixed-register is used both for input and output and thus is destructive? A: Create temporary range - // Q: What if we have 3 different inputs that are all the same virtual register? A: Create temporary range - // Q: Assuming the above is implemented, do we even support overlapping two ranges of separate virtual regs on the same phys register? In theory the RA shouldn't care + // first pass - iterate over all ranges with fixed register requirements and split them if they cross the segment border + // todo - this pass currently creates suboptimal results by splitting all ranges that cross the segment border if they have any fixed register requirement. This can be avoided in some cases + for (raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; currentRange;) + { + IMLPhysRegisterSet allowedRegs; + if(currentRange->list_fixedRegRequirements.empty()) + { + currentRange = currentRange->link_allSegmentRanges.next; + continue; // since we run this pass for every segment we dont need to do global checks here for clusters which may not even have fixed register requirements + } + if (!currentRange->GetAllowedRegistersEx(allowedRegs)) + { + currentRange = currentRange->link_allSegmentRanges.next; + continue; + } + if (currentRange->interval.ExtendsPreviousSegment() || currentRange->interval.ExtendsIntoNextSegment()) + { + raLivenessRange* nextRange = currentRange->link_allSegmentRanges.next; + IMLRA_ExplodeRangeCluster(ppcImlGenContext, currentRange); + currentRange = nextRange; + continue; + } + currentRange = currentRange->link_allSegmentRanges.next; + } + // second pass - look for ranges with conflicting fixed register requirements and split these too (locally) + for (raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; currentRange; currentRange = currentRange->link_allSegmentRanges.next) + { + IMLPhysRegisterSet allowedRegs; + if (currentRange->list_fixedRegRequirements.empty()) + continue; // we dont need to check whole clusters because the pass above guarantees that there are no ranges with fixed register requirements that extend outside of this segment + if (!currentRange->GetAllowedRegistersEx(allowedRegs)) + continue; + if (allowedRegs.HasAnyAvailable()) + continue; + cemu_assert_unimplemented(); + } + // third pass - assign fixed registers, split ranges if needed + std::vector frr = IMLRA_BuildSegmentInstructionFixedRegList(imlSegment); + std::unordered_map lastVGPR; + for (size_t i = 0; i < frr.size(); i++) + { + raFixedRegRequirementWithVGPR& entry = frr[i]; + // we currently only handle fixed register requirements with a single register + // with one exception: When regId is IMLRegID_INVALID then the entry acts as a list of reserved registers + cemu_assert_debug(entry.regId == IMLRegID_INVALID || entry.allowedReg.HasExactlyOneAvailable()); + for (IMLPhysReg physReg = entry.allowedReg.GetFirstAvailableReg(); physReg >= 0; physReg = entry.allowedReg.GetNextAvailableReg(physReg + 1)) + { + // check if the assigned vGPR has changed + bool vgprHasChanged = false; + auto it = lastVGPR.find(physReg); + if (it != lastVGPR.end()) + vgprHasChanged = it->second != entry.regId; + else + vgprHasChanged = true; + lastVGPR[physReg] = entry.regId; - // experimental code - //for (size_t i = 0; i < imlSegment->imlList.size(); i++) - //{ - // IMLInstruction& inst = imlSegment->imlList[i]; - // if (inst.type == PPCREC_IML_TYPE_R_R_R) - // { - // if (inst.operation == PPCREC_IML_OP_LEFT_SHIFT) - // { - // // get the virtual reg which needs to be assigned a fixed register - // //IMLUsedRegisters usedReg; - // //inst.CheckRegisterUsage(&usedReg); - // IMLReg rB = inst.op_r_r_r.regB; - // // rB needs to use RCX/ECX - // raLivenessSubrange_t* subrange = _GetSubrangeByInstructionIndexAndVirtualReg(imlSegment, rB, i); - // cemu_assert_debug(subrange->range->physicalRegister < 0); // already has a phys reg assigned - // // make sure RCX/ECX is free - // // split before (if needed) and after instruction so that we get a new 1-instruction long range for which we can assign the physical register - // raLivenessSubrange_t* instructionRange = subrange->start.index < i ? PPCRecRA_splitLocalSubrange(ppcImlGenContext, subrange, i, false) : subrange; - // raLivenessSubrange_t* tailRange = PPCRecRA_splitLocalSubrange(ppcImlGenContext, instructionRange, i+1, false); + if (!vgprHasChanged) + continue; - // } - // } - //} + boost::container::small_vector overlappingRanges = IMLRA_GetRangeWithFixedRegReservationOverlappingPos(imlSegment, entry.pos, physReg); + if (entry.regId != IMLRegID_INVALID) + cemu_assert_debug(!overlappingRanges.empty()); // there should always be at least one range that overlaps corresponding to the fixed register requirement, except for IMLRegID_INVALID which is used to indicate reserved registers + + for (auto& range : overlappingRanges) + { + if (range->interval.start < entry.pos) + { + IMLRA_SplitRange(ppcImlGenContext, range, entry.pos, true); + } + } + } + } + // finally iterate ranges and assign fixed registers + for (raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; currentRange; currentRange = currentRange->link_allSegmentRanges.next) + { + IMLPhysRegisterSet allowedRegs; + if (currentRange->list_fixedRegRequirements.empty()) + continue; // we dont need to check whole clusters because the pass above guarantees that there are no ranges with fixed register requirements that extend outside of this segment + if (!currentRange->GetAllowedRegistersEx(allowedRegs)) + { + cemu_assert_debug(currentRange->list_fixedRegRequirements.empty()); + continue; + } + cemu_assert_debug(allowedRegs.HasExactlyOneAvailable()); + currentRange->SetPhysicalRegister(allowedRegs.GetFirstAvailableReg()); + } + // DEBUG - check for collisions and make sure all ranges with fixed register requirements got their physical register assigned +#if DEBUG_RA_EXTRA_VALIDATION + for (raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; currentRange; currentRange = currentRange->link_allSegmentRanges.next) + { + IMLPhysRegisterSet allowedRegs; + if (!currentRange->HasPhysicalRegister()) + continue; + for (raLivenessRange* currentRange2 = imlSegment->raInfo.linkedList_allSubranges; currentRange2; currentRange2 = currentRange2->link_allSegmentRanges.next) + { + if (currentRange == currentRange2) + continue; + if (currentRange->interval2.IsOverlapping(currentRange2->interval2)) + { + cemu_assert_debug(currentRange->GetPhysicalRegister() != currentRange2->GetPhysicalRegister()); + } + } + } +#endif +} + +// we should not split ranges on instructions with tied registers (i.e. where a register encoded as a single parameter is both input and output) +// otherwise the RA algorithm has to assign both ranges the same physical register (not supported yet) and the point of splitting to fit another range is nullified +void IMLRA_MakeSafeSplitPosition(IMLSegment* imlSegment, raInstructionEdge& pos) +{ + // we ignore the instruction for now and just always make it a safe split position + cemu_assert_debug(pos.IsInstructionIndex()); + if (pos.IsOnOutputEdge()) + pos = pos - 1; +} + +// convenience wrapper for IMLRA_MakeSafeSplitPosition +void IMLRA_MakeSafeSplitDistance(IMLSegment* imlSegment, raInstructionEdge startPos, sint32& distance) +{ + cemu_assert_debug(startPos.IsInstructionIndex()); + cemu_assert_debug(distance >= 0); + raInstructionEdge endPos = startPos + distance; + IMLRA_MakeSafeSplitPosition(imlSegment, endPos); + if (endPos < startPos) + { + distance = 0; + return; + } + distance = endPos.GetRaw() - startPos.GetRaw(); +} + +static void DbgVerifyAllRanges(IMLRegisterAllocatorContext& ctx); + +class RASpillStrategy +{ + public: + virtual void Apply(ppcImlGenContext_t* ctx, IMLSegment* imlSegment, raLivenessRange* currentRange) = 0; + + sint32 GetCost() + { + return strategyCost; + } + + protected: + void ResetCost() + { + strategyCost = INT_MAX; + } + + sint32 strategyCost; +}; + +class RASpillStrategy_LocalRangeHoleCutting : public RASpillStrategy +{ + public: + void Reset() + { + localRangeHoleCutting.distance = -1; + localRangeHoleCutting.largestHoleSubrange = nullptr; + ResetCost(); + } + + void Evaluate(IMLSegment* imlSegment, raLivenessRange* currentRange, const IMLRALivenessTimeline& timeline, const IMLPhysRegisterSet& allowedRegs) + { + raInstructionEdge currentRangeStart = currentRange->interval.start; + sint32 requiredSize2 = currentRange->interval.GetPreciseDistance(); + cemu_assert_debug(localRangeHoleCutting.distance == -1); + cemu_assert_debug(strategyCost == INT_MAX); + if (!currentRangeStart.ConnectsToPreviousSegment()) + { + cemu_assert_debug(currentRangeStart.GetRaw() >= 0); + for (auto candidate : timeline.activeRanges) + { + if (candidate->interval.ExtendsIntoNextSegment()) + continue; + // new checks (Oct 2024): + if (candidate == currentRange) + continue; + if (candidate->GetPhysicalRegister() < 0) + continue; + if (!allowedRegs.IsAvailable(candidate->GetPhysicalRegister())) + continue; + + sint32 distance2 = IMLRA_CountDistanceUntilNextUse(candidate, currentRangeStart); + IMLRA_MakeSafeSplitDistance(imlSegment, currentRangeStart, distance2); + if (distance2 < 2) + continue; + cemu_assert_debug(currentRangeStart.IsInstructionIndex()); + distance2 = std::min(distance2, imlSegment->imlList.size() * 2 - currentRangeStart.GetRaw()); // limit distance to end of segment + // calculate split cost of candidate + sint32 cost = IMLRA_CalculateAdditionalCostAfterSplit(candidate, currentRangeStart + distance2); + // calculate additional split cost of currentRange if hole is not large enough + if (distance2 < requiredSize2) + { + cost += IMLRA_CalculateAdditionalCostAfterSplit(currentRange, currentRangeStart + distance2); + // we also slightly increase cost in relation to the remaining length (in order to make the algorithm prefer larger holes) + cost += (requiredSize2 - distance2) / 10; + } + // compare cost with previous candidates + if (cost < strategyCost) + { + strategyCost = cost; + localRangeHoleCutting.distance = distance2; + localRangeHoleCutting.largestHoleSubrange = candidate; + } + } + } + } + + void Apply(ppcImlGenContext_t* ctx, IMLSegment* imlSegment, raLivenessRange* currentRange) override + { + cemu_assert_debug(strategyCost != INT_MAX); + sint32 requiredSize2 = currentRange->interval.GetPreciseDistance(); + raInstructionEdge currentRangeStart = currentRange->interval.start; + + raInstructionEdge holeStartPosition = currentRangeStart; + raInstructionEdge holeEndPosition = currentRangeStart + localRangeHoleCutting.distance; + raLivenessRange* collisionRange = localRangeHoleCutting.largestHoleSubrange; + + if (collisionRange->interval.start < holeStartPosition) + { + collisionRange = IMLRA_SplitRange(nullptr, collisionRange, holeStartPosition, true); + cemu_assert_debug(!collisionRange || collisionRange->interval.start >= holeStartPosition); // verify if splitting worked at all, tail must be on or after the split point + cemu_assert_debug(!collisionRange || collisionRange->interval.start >= holeEndPosition); // also verify that the trimmed hole is actually big enough + } + else + { + cemu_assert_unimplemented(); // we still need to trim? + } + // we may also have to cut the current range to fit partially into the hole + if (requiredSize2 > localRangeHoleCutting.distance) + { + raLivenessRange* tailRange = IMLRA_SplitRange(nullptr, currentRange, currentRangeStart + localRangeHoleCutting.distance, true); + if (tailRange) + { + cemu_assert_debug(tailRange->list_fixedRegRequirements.empty()); // we are not allowed to unassign fixed registers + tailRange->UnsetPhysicalRegister(); + } + } + // verify that the hole is large enough + if (collisionRange) + { + cemu_assert_debug(!collisionRange->interval.IsOverlapping(currentRange->interval)); + } + } + + private: + struct + { + sint32 distance; + raLivenessRange* largestHoleSubrange; + } localRangeHoleCutting; +}; + +class RASpillStrategy_AvailableRegisterHole : public RASpillStrategy +{ + // split current range (this is generally only a good choice when the current range is long but has few usages) + public: + void Reset() + { + ResetCost(); + availableRegisterHole.distance = -1; + availableRegisterHole.physRegister = -1; + } + + void Evaluate(IMLSegment* imlSegment, raLivenessRange* currentRange, const IMLRALivenessTimeline& timeline, const IMLPhysRegisterSet& localAvailableRegsMask, const IMLPhysRegisterSet& allowedRegs) + { + sint32 requiredSize2 = currentRange->interval.GetPreciseDistance(); + + raInstructionEdge currentRangeStart = currentRange->interval.start; + cemu_assert_debug(strategyCost == INT_MAX); + availableRegisterHole.distance = -1; + availableRegisterHole.physRegister = -1; + if (currentRangeStart.GetRaw() >= 0) + { + if (localAvailableRegsMask.HasAnyAvailable()) + { + sint32 physRegItr = -1; + while (true) + { + physRegItr = localAvailableRegsMask.GetNextAvailableReg(physRegItr + 1); + if (physRegItr < 0) + break; + if (!allowedRegs.IsAvailable(physRegItr)) + continue; + // get size of potential hole for this register + sint32 distance = PPCRecRA_countDistanceUntilNextLocalPhysRegisterUse(imlSegment, currentRangeStart, physRegItr); + + // some instructions may require the same register for another range, check the distance here + sint32 distUntilFixedReg = IMLRA_CountDistanceUntilFixedRegUsage(imlSegment, currentRangeStart, distance, currentRange->GetVirtualRegister(), physRegItr); + if (distUntilFixedReg < distance) + distance = distUntilFixedReg; + + IMLRA_MakeSafeSplitDistance(imlSegment, currentRangeStart, distance); + if (distance < 2) + continue; + // calculate additional cost due to split + cemu_assert_debug(distance < requiredSize2); // should always be true otherwise previous step would have selected this register? + sint32 cost = IMLRA_CalculateAdditionalCostAfterSplit(currentRange, currentRangeStart + distance); + // add small additional cost for the remaining range (prefer larger holes) + cost += ((requiredSize2 - distance) / 2) / 10; + if (cost < strategyCost) + { + strategyCost = cost; + availableRegisterHole.distance = distance; + availableRegisterHole.physRegister = physRegItr; + } + } + } + } + } + + void Apply(ppcImlGenContext_t* ctx, IMLSegment* imlSegment, raLivenessRange* currentRange) override + { + cemu_assert_debug(strategyCost != INT_MAX); + raInstructionEdge currentRangeStart = currentRange->interval.start; + // use available register + raLivenessRange* tailRange = IMLRA_SplitRange(nullptr, currentRange, currentRangeStart + availableRegisterHole.distance, true); + if (tailRange) + { + cemu_assert_debug(tailRange->list_fixedRegRequirements.empty()); // we are not allowed to unassign fixed registers + tailRange->UnsetPhysicalRegister(); + } + } + + private: + struct + { + sint32 physRegister; + sint32 distance; // size of hole + } availableRegisterHole; +}; + +class RASpillStrategy_ExplodeRange : public RASpillStrategy +{ + public: + void Reset() + { + ResetCost(); + explodeRange.range = nullptr; + explodeRange.distance = -1; + } + + void Evaluate(IMLSegment* imlSegment, raLivenessRange* currentRange, const IMLRALivenessTimeline& timeline, const IMLPhysRegisterSet& allowedRegs) + { + raInstructionEdge currentRangeStart = currentRange->interval.start; + if (currentRangeStart.ConnectsToPreviousSegment()) + currentRangeStart.Set(0, true); + sint32 requiredSize2 = currentRange->interval.GetPreciseDistance(); + cemu_assert_debug(strategyCost == INT_MAX); + explodeRange.range = nullptr; + explodeRange.distance = -1; + for (auto candidate : timeline.activeRanges) + { + if (!candidate->interval.ExtendsIntoNextSegment()) + continue; + // new checks (Oct 2024): + if (candidate == currentRange) + continue; + if (candidate->GetPhysicalRegister() < 0) + continue; + if (!allowedRegs.IsAvailable(candidate->GetPhysicalRegister())) + continue; + + sint32 distance = IMLRA_CountDistanceUntilNextUse(candidate, currentRangeStart); + IMLRA_MakeSafeSplitDistance(imlSegment, currentRangeStart, distance); + if (distance < 2) + continue; + sint32 cost = IMLRA_CalculateAdditionalCostOfRangeExplode(candidate); + // if the hole is not large enough, add cost of splitting current subrange + if (distance < requiredSize2) + { + cost += IMLRA_CalculateAdditionalCostAfterSplit(currentRange, currentRangeStart + distance); + // add small additional cost for the remaining range (prefer larger holes) + cost += ((requiredSize2 - distance) / 2) / 10; + } + // compare with current best candidate for this strategy + if (cost < strategyCost) + { + strategyCost = cost; + explodeRange.distance = distance; + explodeRange.range = candidate; + } + } + } + + void Apply(ppcImlGenContext_t* ctx, IMLSegment* imlSegment, raLivenessRange* currentRange) override + { + raInstructionEdge currentRangeStart = currentRange->interval.start; + if (currentRangeStart.ConnectsToPreviousSegment()) + currentRangeStart.Set(0, true); + sint32 requiredSize2 = currentRange->interval.GetPreciseDistance(); + // explode range + IMLRA_ExplodeRangeCluster(nullptr, explodeRange.range); + // split current subrange if necessary + if (requiredSize2 > explodeRange.distance) + { + raLivenessRange* tailRange = IMLRA_SplitRange(nullptr, currentRange, currentRangeStart + explodeRange.distance, true); + if (tailRange) + { + cemu_assert_debug(tailRange->list_fixedRegRequirements.empty()); // we are not allowed to unassign fixed registers + tailRange->UnsetPhysicalRegister(); + } + } + } + + private: + struct + { + raLivenessRange* range; + sint32 distance; // size of hole + // note: If we explode a range, we still have to check the size of the hole that becomes available, if too small then we need to add cost of splitting local subrange + } explodeRange; +}; + +class RASpillStrategy_ExplodeRangeInter : public RASpillStrategy +{ + public: + void Reset() + { + ResetCost(); + explodeRange.range = nullptr; + explodeRange.distance = -1; + } + + void Evaluate(IMLSegment* imlSegment, raLivenessRange* currentRange, const IMLRALivenessTimeline& timeline, const IMLPhysRegisterSet& allowedRegs) + { + // explode the range with the least cost + cemu_assert_debug(strategyCost == INT_MAX); + cemu_assert_debug(explodeRange.range == nullptr && explodeRange.distance == -1); + for (auto candidate : timeline.activeRanges) + { + if (!candidate->interval.ExtendsIntoNextSegment()) + continue; + // only select candidates that clash with current subrange + if (candidate->GetPhysicalRegister() < 0 && candidate != currentRange) + continue; + // and also filter any that dont meet fixed register requirements + if (!allowedRegs.IsAvailable(candidate->GetPhysicalRegister())) + continue; + sint32 cost; + cost = IMLRA_CalculateAdditionalCostOfRangeExplode(candidate); + // compare with current best candidate for this strategy + if (cost < strategyCost) + { + strategyCost = cost; + explodeRange.distance = INT_MAX; + explodeRange.range = candidate; + } + } + // add current range as a candidate too + sint32 ownCost; + ownCost = IMLRA_CalculateAdditionalCostOfRangeExplode(currentRange); + if (ownCost < strategyCost) + { + strategyCost = ownCost; + explodeRange.distance = INT_MAX; + explodeRange.range = currentRange; + } + } + + void Apply(ppcImlGenContext_t* ctx, IMLSegment* imlSegment, raLivenessRange* currentRange) override + { + cemu_assert_debug(strategyCost != INT_MAX); + IMLRA_ExplodeRangeCluster(ctx, explodeRange.range); + } + + private: + struct + { + raLivenessRange* range; + sint32 distance; // size of hole + // note: If we explode a range, we still have to check the size of the hole that becomes available, if too small then we need to add cost of splitting local subrange + }explodeRange; +}; + +// filter any registers from candidatePhysRegSet which cannot be used by currentRange due to fixed register requirements within the range that it occupies +void IMLRA_FilterReservedFixedRegisterRequirementsForSegment(IMLRegisterAllocatorContext& ctx, raLivenessRange* currentRange, IMLPhysRegisterSet& candidatePhysRegSet) +{ + IMLSegment* seg = currentRange->imlSegment; + if (seg->imlList.empty()) + return; // there can be no fixed register requirements if there are no instructions + + raInstructionEdge firstPos = currentRange->interval.start; + if (currentRange->interval.start.ConnectsToPreviousSegment()) + firstPos.SetRaw(0); + else if (currentRange->interval.start.ConnectsToNextSegment()) + firstPos.Set(seg->imlList.size() - 1, false); + + raInstructionEdge lastPos = currentRange->interval.end; + if (currentRange->interval.end.ConnectsToPreviousSegment()) + lastPos.SetRaw(0); + else if (currentRange->interval.end.ConnectsToNextSegment()) + lastPos.Set(seg->imlList.size() - 1, false); + cemu_assert_debug(firstPos <= lastPos); + + IMLRegID ourRegId = currentRange->GetVirtualRegister(); + + IMLFixedRegisters fixedRegs; + if (firstPos.IsOnOutputEdge()) + GetInstructionFixedRegisters(seg->imlList.data() + firstPos.GetInstructionIndex(), fixedRegs); + for (raInstructionEdge currentPos = firstPos; currentPos <= lastPos; ++currentPos) + { + if (currentPos.IsOnInputEdge()) + { + GetInstructionFixedRegisters(seg->imlList.data() + currentPos.GetInstructionIndex(), fixedRegs); + } + auto& fixedRegAccess = currentPos.IsOnInputEdge() ? fixedRegs.listInput : fixedRegs.listOutput; + for (auto& fixedRegLoc : fixedRegAccess) + { + if (fixedRegLoc.reg.IsInvalid() || fixedRegLoc.reg.GetRegID() != ourRegId) + candidatePhysRegSet.RemoveRegisters(fixedRegLoc.physRegSet); + } + } +} + +// filter out any registers along the range cluster +void IMLRA_FilterReservedFixedRegisterRequirementsForCluster(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment, raLivenessRange* currentRange, IMLPhysRegisterSet& candidatePhysRegSet) +{ + cemu_assert_debug(currentRange->imlSegment == imlSegment); + if (currentRange->interval.ExtendsPreviousSegment() || currentRange->interval.ExtendsIntoNextSegment()) + { + auto clusterRanges = currentRange->GetAllSubrangesInCluster(); + for (auto& rangeIt : clusterRanges) + { + IMLRA_FilterReservedFixedRegisterRequirementsForSegment(ctx, rangeIt, candidatePhysRegSet); + if (!candidatePhysRegSet.HasAnyAvailable()) + break; + } + return; + } + IMLRA_FilterReservedFixedRegisterRequirementsForSegment(ctx, currentRange, candidatePhysRegSet); } bool IMLRA_AssignSegmentRegisters(IMLRegisterAllocatorContext& ctx, ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment) @@ -381,261 +1117,130 @@ bool IMLRA_AssignSegmentRegisters(IMLRegisterAllocatorContext& ctx, ppcImlGenCon _sortSegmentAllSubrangesLinkedList(imlSegment); IMLRALivenessTimeline livenessTimeline; - raLivenessSubrange_t* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; - while(subrangeItr) + raLivenessRange* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + raInstructionEdge lastInstructionEdge; + lastInstructionEdge.SetRaw(RA_INTER_RANGE_END); + + struct { - sint32 currentIndex = subrangeItr->start.index; + RASpillStrategy_LocalRangeHoleCutting localRangeHoleCutting; + RASpillStrategy_AvailableRegisterHole availableRegisterHole; + RASpillStrategy_ExplodeRange explodeRange; + // for ranges that connect to follow up segments: + RASpillStrategy_ExplodeRangeInter explodeRangeInter; + } strategy; + + while (subrangeItr) + { + raInstructionEdge currentRangeStart = subrangeItr->interval.start; // used to be currentIndex before refactor PPCRecRA_debugValidateSubrange(subrangeItr); - livenessTimeline.ExpireRanges(std::min(currentIndex, RA_INTER_RANGE_END-1)); // expire up to currentIndex (inclusive), but exclude infinite ranges + + livenessTimeline.ExpireRanges((currentRangeStart > lastInstructionEdge) ? lastInstructionEdge : currentRangeStart); // expire up to currentIndex (inclusive), but exclude infinite ranges + // if subrange already has register assigned then add it to the active list and continue - if (subrangeItr->range->physicalRegister >= 0) + if (subrangeItr->GetPhysicalRegister() >= 0) { // verify if register is actually available -#ifdef CEMU_DEBUG_ASSERT +#if DEBUG_RA_EXTRA_VALIDATION for (auto& liverangeItr : livenessTimeline.activeRanges) { // check for register mismatch - cemu_assert_debug(liverangeItr->range->physicalRegister != subrangeItr->range->physicalRegister); + cemu_assert_debug(liverangeItr->GetPhysicalRegister() != subrangeItr->GetPhysicalRegister()); } #endif livenessTimeline.AddActiveRange(subrangeItr); - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + subrangeItr = subrangeItr->link_allSegmentRanges.next; continue; } + // ranges with fixed register requirements should already have a phys register assigned + if (!subrangeItr->list_fixedRegRequirements.empty()) + { + cemu_assert_debug(subrangeItr->HasPhysicalRegister()); + } // find free register for current subrangeItr and segment - IMLRegFormat regBaseFormat = ctx.GetBaseFormatByRegId(subrangeItr->range->virtualRegister); - IMLPhysRegisterSet physRegSet = ctx.raParam->GetPhysRegPool(regBaseFormat); - cemu_assert_debug(physRegSet.HasAnyAvailable()); // register uses type with no valid pool + IMLRegFormat regBaseFormat = ctx.GetBaseFormatByRegId(subrangeItr->GetVirtualRegister()); + IMLPhysRegisterSet candidatePhysRegSet = ctx.raParam->GetPhysRegPool(regBaseFormat); + cemu_assert_debug(candidatePhysRegSet.HasAnyAvailable()); // no valid pool provided for this register type + + IMLPhysRegisterSet allowedRegs = subrangeItr->GetAllowedRegisters(candidatePhysRegSet); + cemu_assert_debug(allowedRegs.HasAnyAvailable()); // if zero regs are available, then this range needs to be split to avoid mismatching register requirements (do this in the initial pass to keep the code here simpler) + candidatePhysRegSet &= allowedRegs; + for (auto& liverangeItr : livenessTimeline.activeRanges) { - cemu_assert_debug(liverangeItr->range->physicalRegister >= 0); - physRegSet.SetReserved(liverangeItr->range->physicalRegister); + cemu_assert_debug(liverangeItr->GetPhysicalRegister() >= 0); + candidatePhysRegSet.SetReserved(liverangeItr->GetPhysicalRegister()); } // check intersections with other ranges and determine allowed registers - IMLPhysRegisterSet localAvailableRegsMask = physRegSet; // mask of registers that are currently not used (does not include range checks in other segments) - if(physRegSet.HasAnyAvailable()) + IMLPhysRegisterSet localAvailableRegsMask = candidatePhysRegSet; // mask of registers that are currently not used (does not include range checks in other segments) + if (candidatePhysRegSet.HasAnyAvailable()) { - // check globally in all segments - PPCRecRA_MaskOverlappingPhysRegForGlobalRange(subrangeItr->range, physRegSet); + // check for overlaps on a global scale (subrangeItr can be part of a larger range cluster across multiple segments) + PPCRecRA_MaskOverlappingPhysRegForGlobalRange(subrangeItr, candidatePhysRegSet); } - if (!physRegSet.HasAnyAvailable()) + // some target instructions may enforce specific registers (e.g. common on X86 where something like SHL , CL forces CL as the count register) + // we determine the list of allowed registers here + // this really only works if we assume single-register requirements (otherwise its better not to filter out early and instead allow register corrections later but we don't support this yet) + if (candidatePhysRegSet.HasAnyAvailable()) { - struct - { - // estimated costs and chosen candidates for the different spill strategies - // hole cutting into a local range - struct - { - sint32 distance; - raLivenessSubrange_t* largestHoleSubrange; - sint32 cost; // additional cost of choosing this candidate - }localRangeHoleCutting; - // split current range (this is generally only a good choice when the current range is long but rarely used) - struct - { - sint32 cost; - sint32 physRegister; - sint32 distance; // size of hole - }availableRegisterHole; - // explode a inter-segment range (prefer ranges that are not read/written in this segment) - struct - { - raLivenessRange_t* range; - sint32 cost; - sint32 distance; // size of hole - // note: If we explode a range, we still have to check the size of the hole that becomes available, if too small then we need to add cost of splitting local subrange - }explodeRange; - // todo - add more strategies, make cost estimation smarter (for example, in some cases splitting can have reduced or no cost if read/store can be avoided due to data flow) - }spillStrategies; - // cant assign register - // there might be registers available, we just can't use them due to range conflicts - if (subrangeItr->end.index != RA_INTER_RANGE_END) - { - // range ends in current segment - - // Current algo looks like this: - // 1) Get the size of the largest possible hole that we can cut into any of the live local subranges - // 1.1) Check if the hole is large enough to hold the current subrange - // 2) If yes, cut hole and return false (full retry) - // 3) If no, try to reuse free register (need to determine how large the region is we can use) - // 4) If there is no free register or the range is extremely short go back to step 1+2 but additionally split the current subrange at where the hole ends - - cemu_assert_debug(currentIndex == subrangeItr->start.index); - - sint32 requiredSize = subrangeItr->end.index - subrangeItr->start.index; - // evaluate strategy: Cut hole into local subrange - spillStrategies.localRangeHoleCutting.distance = -1; - spillStrategies.localRangeHoleCutting.largestHoleSubrange = nullptr; - spillStrategies.localRangeHoleCutting.cost = INT_MAX; - if (currentIndex >= 0) - { - for (auto candidate : livenessTimeline.activeRanges) - { - if (candidate->end.index == RA_INTER_RANGE_END) - continue; - sint32 distance = PPCRecRA_countInstructionsUntilNextUse(candidate, currentIndex); - if (distance < 2) - continue; // not even worth the consideration - // calculate split cost of candidate - sint32 cost = PPCRecRARange_estimateAdditionalCostAfterSplit(candidate, currentIndex + distance); - // calculate additional split cost of currentRange if hole is not large enough - if (distance < requiredSize) - { - cost += PPCRecRARange_estimateAdditionalCostAfterSplit(subrangeItr, currentIndex + distance); - // we also slightly increase cost in relation to the remaining length (in order to make the algorithm prefer larger holes) - cost += (requiredSize - distance) / 10; - } - // compare cost with previous candidates - if (cost < spillStrategies.localRangeHoleCutting.cost) - { - spillStrategies.localRangeHoleCutting.cost = cost; - spillStrategies.localRangeHoleCutting.distance = distance; - spillStrategies.localRangeHoleCutting.largestHoleSubrange = candidate; - } - } - } - // evaluate strategy: Split current range to fit in available holes - // todo - are checks required to avoid splitting on the suffix instruction? - spillStrategies.availableRegisterHole.cost = INT_MAX; - spillStrategies.availableRegisterHole.distance = -1; - spillStrategies.availableRegisterHole.physRegister = -1; - if (currentIndex >= 0) - { - if (localAvailableRegsMask.HasAnyAvailable()) - { - sint32 physRegItr = -1; - while (true) - { - physRegItr = localAvailableRegsMask.GetNextAvailableReg(physRegItr + 1); - if (physRegItr < 0) - break; - // get size of potential hole for this register - sint32 distance = PPCRecRA_countInstructionsUntilNextLocalPhysRegisterUse(imlSegment, currentIndex, physRegItr); - if (distance < 2) - continue; // not worth consideration - // calculate additional cost due to split - if (distance >= requiredSize) - assert_dbg(); // should not happen or else we would have selected this register - sint32 cost = PPCRecRARange_estimateAdditionalCostAfterSplit(subrangeItr, currentIndex + distance); - // add small additional cost for the remaining range (prefer larger holes) - cost += (requiredSize - distance) / 10; - if (cost < spillStrategies.availableRegisterHole.cost) - { - spillStrategies.availableRegisterHole.cost = cost; - spillStrategies.availableRegisterHole.distance = distance; - spillStrategies.availableRegisterHole.physRegister = physRegItr; - } - } - } - } - // evaluate strategy: Explode inter-segment ranges - spillStrategies.explodeRange.cost = INT_MAX; - spillStrategies.explodeRange.range = nullptr; - spillStrategies.explodeRange.distance = -1; - for (auto candidate : livenessTimeline.activeRanges) - { - if (candidate->end.index != RA_INTER_RANGE_END) - continue; - sint32 distance = PPCRecRA_countInstructionsUntilNextUse(candidate, currentIndex); - if( distance < 2) - continue; - sint32 cost; - cost = PPCRecRARange_estimateAdditionalCostAfterRangeExplode(candidate->range); - // if the hole is not large enough, add cost of splitting current subrange - if (distance < requiredSize) - { - cost += PPCRecRARange_estimateAdditionalCostAfterSplit(subrangeItr, currentIndex + distance); - // add small additional cost for the remaining range (prefer larger holes) - cost += (requiredSize - distance) / 10; - } - // compare with current best candidate for this strategy - if (cost < spillStrategies.explodeRange.cost) - { - spillStrategies.explodeRange.cost = cost; - spillStrategies.explodeRange.distance = distance; - spillStrategies.explodeRange.range = candidate->range; - } - } - // choose strategy - if (spillStrategies.explodeRange.cost != INT_MAX && spillStrategies.explodeRange.cost <= spillStrategies.localRangeHoleCutting.cost && spillStrategies.explodeRange.cost <= spillStrategies.availableRegisterHole.cost) - { - // explode range - PPCRecRA_explodeRange(ppcImlGenContext, spillStrategies.explodeRange.range); - // split current subrange if necessary - if( requiredSize > spillStrategies.explodeRange.distance) - PPCRecRA_splitLocalSubrange(ppcImlGenContext, subrangeItr, currentIndex+spillStrategies.explodeRange.distance, true); - } - else if (spillStrategies.availableRegisterHole.cost != INT_MAX && spillStrategies.availableRegisterHole.cost <= spillStrategies.explodeRange.cost && spillStrategies.availableRegisterHole.cost <= spillStrategies.localRangeHoleCutting.cost) - { - // use available register - PPCRecRA_splitLocalSubrange(ppcImlGenContext, subrangeItr, currentIndex + spillStrategies.availableRegisterHole.distance, true); - } - else if (spillStrategies.localRangeHoleCutting.cost != INT_MAX && spillStrategies.localRangeHoleCutting.cost <= spillStrategies.explodeRange.cost && spillStrategies.localRangeHoleCutting.cost <= spillStrategies.availableRegisterHole.cost) - { - // cut hole - PPCRecRA_splitLocalSubrange(ppcImlGenContext, spillStrategies.localRangeHoleCutting.largestHoleSubrange, currentIndex + spillStrategies.localRangeHoleCutting.distance, true); - // split current subrange if necessary - if (requiredSize > spillStrategies.localRangeHoleCutting.distance) - PPCRecRA_splitLocalSubrange(ppcImlGenContext, subrangeItr, currentIndex + spillStrategies.localRangeHoleCutting.distance, true); - } - else if (subrangeItr->start.index == RA_INTER_RANGE_START) - { - // alternative strategy if we have no other choice: explode current range - PPCRecRA_explodeRange(ppcImlGenContext, subrangeItr->range); - } - else - assert_dbg(); - - return false; - } - else - { - // range exceeds segment border - // simple but bad solution -> explode the entire range (no longer allow it to cross segment boundaries) - // better solutions: 1) Depending on the situation, we can explode other ranges to resolve the conflict. Thus we should explode the range with the lowest extra cost - // 2) Or we explode the range only partially - // explode the range with the least cost - spillStrategies.explodeRange.cost = INT_MAX; - spillStrategies.explodeRange.range = nullptr; - spillStrategies.explodeRange.distance = -1; - for(auto candidate : livenessTimeline.activeRanges) - { - if (candidate->end.index != RA_INTER_RANGE_END) - continue; - // only select candidates that clash with current subrange - if (candidate->range->physicalRegister < 0 && candidate != subrangeItr) - continue; - - sint32 cost; - cost = PPCRecRARange_estimateAdditionalCostAfterRangeExplode(candidate->range); - // compare with current best candidate for this strategy - if (cost < spillStrategies.explodeRange.cost) - { - spillStrategies.explodeRange.cost = cost; - spillStrategies.explodeRange.distance = INT_MAX; - spillStrategies.explodeRange.range = candidate->range; - } - } - // add current range as a candidate too - sint32 ownCost; - ownCost = PPCRecRARange_estimateAdditionalCostAfterRangeExplode(subrangeItr->range); - if (ownCost < spillStrategies.explodeRange.cost) - { - spillStrategies.explodeRange.cost = ownCost; - spillStrategies.explodeRange.distance = INT_MAX; - spillStrategies.explodeRange.range = subrangeItr->range; - } - if (spillStrategies.explodeRange.cost == INT_MAX) - assert_dbg(); // should not happen - PPCRecRA_explodeRange(ppcImlGenContext, spillStrategies.explodeRange.range); - } - return false; + IMLRA_FilterReservedFixedRegisterRequirementsForCluster(ctx, imlSegment, subrangeItr, candidatePhysRegSet); } - // assign register to range - subrangeItr->range->physicalRegister = physRegSet.GetFirstAvailableReg(); - livenessTimeline.AddActiveRange(subrangeItr); - // next - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + if (candidatePhysRegSet.HasAnyAvailable()) + { + // use free register + subrangeItr->SetPhysicalRegisterForCluster(candidatePhysRegSet.GetFirstAvailableReg()); + livenessTimeline.AddActiveRange(subrangeItr); + subrangeItr = subrangeItr->link_allSegmentRanges.next; // next + continue; + } + // there is no free register for the entire range + // evaluate different strategies of splitting ranges to free up another register or shorten the current range + strategy.localRangeHoleCutting.Reset(); + strategy.availableRegisterHole.Reset(); + strategy.explodeRange.Reset(); + // cant assign register + // there might be registers available, we just can't use them due to range conflicts + RASpillStrategy* selectedStrategy = nullptr; + auto SelectStrategyIfBetter = [&selectedStrategy](RASpillStrategy& newStrategy) { + if (newStrategy.GetCost() == INT_MAX) + return; + if (selectedStrategy == nullptr || newStrategy.GetCost() < selectedStrategy->GetCost()) + selectedStrategy = &newStrategy; + }; + + if (!subrangeItr->interval.ExtendsIntoNextSegment()) + { + // range ends in current segment, use local strategies + // evaluate strategy: Cut hole into local subrange + strategy.localRangeHoleCutting.Evaluate(imlSegment, subrangeItr, livenessTimeline, allowedRegs); + SelectStrategyIfBetter(strategy.localRangeHoleCutting); + // evaluate strategy: Split current range to fit in available holes + // todo - are checks required to avoid splitting on the suffix instruction? + strategy.availableRegisterHole.Evaluate(imlSegment, subrangeItr, livenessTimeline, localAvailableRegsMask, allowedRegs); + SelectStrategyIfBetter(strategy.availableRegisterHole); + // evaluate strategy: Explode inter-segment ranges + strategy.explodeRange.Evaluate(imlSegment, subrangeItr, livenessTimeline, allowedRegs); + SelectStrategyIfBetter(strategy.explodeRange); + } + else // if subrangeItr->interval2.ExtendsIntoNextSegment() + { + strategy.explodeRangeInter.Reset(); + strategy.explodeRangeInter.Evaluate(imlSegment, subrangeItr, livenessTimeline, allowedRegs); + SelectStrategyIfBetter(strategy.explodeRangeInter); + } + // choose strategy + if (selectedStrategy) + { + selectedStrategy->Apply(ppcImlGenContext, imlSegment, subrangeItr); + } + else + { + // none of the evulated strategies can be applied, this should only happen if the segment extends into the next segment(s) for which we have no good strategy + cemu_assert_debug(subrangeItr->interval.ExtendsPreviousSegment()); + // alternative strategy if we have no other choice: explode current range + IMLRA_ExplodeRangeCluster(ppcImlGenContext, subrangeItr); + } + return false; } return true; } @@ -651,6 +1256,22 @@ void IMLRA_AssignRegisters(IMLRegisterAllocatorContext& ctx, ppcImlGenContext_t* // assign fixed registers first for (IMLSegment* segIt : ppcImlGenContext->segmentList2) IMLRA_HandleFixedRegisters(ppcImlGenContext, segIt); +#if DEBUG_RA_EXTRA_VALIDATION + // fixed registers are currently handled per-segment, but here we validate that they are assigned correctly on a global scope as well + for (IMLSegment* imlSegment : ppcImlGenContext->segmentList2) + { + for (raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; currentRange; currentRange = currentRange->link_allSegmentRanges.next) + { + IMLPhysRegisterSet allowedRegs; + if (!currentRange->GetAllowedRegistersEx(allowedRegs)) + { + cemu_assert_debug(currentRange->list_fixedRegRequirements.empty()); + continue; + } + cemu_assert_debug(currentRange->HasPhysicalRegister() && allowedRegs.IsAvailable(currentRange->GetPhysicalRegister())); + } + } +#endif while (true) { @@ -673,261 +1294,6 @@ void IMLRA_AssignRegisters(IMLRegisterAllocatorContext& ctx, ppcImlGenContext_t* } } -struct subrangeEndingInfo_t -{ - //boost::container::small_vector subrangeList2; - raLivenessSubrange_t* subrangeList[SUBRANGE_LIST_SIZE]; - sint32 subrangeCount; - - bool hasUndefinedEndings; -}; - -void _findSubrangeWriteEndings(raLivenessSubrange_t* subrange, uint32 iterationIndex, sint32 depth, subrangeEndingInfo_t* info) -{ - if (depth >= 30) - { - info->hasUndefinedEndings = true; - return; - } - if (subrange->lastIterationIndex == iterationIndex) - return; // already processed - subrange->lastIterationIndex = iterationIndex; - if (subrange->hasStoreDelayed) - return; // no need to traverse this subrange - IMLSegment* imlSegment = subrange->imlSegment; - if (subrange->end.index != RA_INTER_RANGE_END) - { - // ending segment - if (info->subrangeCount >= SUBRANGE_LIST_SIZE) - { - info->hasUndefinedEndings = true; - return; - } - else - { - info->subrangeList[info->subrangeCount] = subrange; - info->subrangeCount++; - } - return; - } - - // traverse next subranges in flow - if (imlSegment->nextSegmentBranchNotTaken) - { - if (subrange->subrangeBranchNotTaken == nullptr) - { - info->hasUndefinedEndings = true; - } - else - { - _findSubrangeWriteEndings(subrange->subrangeBranchNotTaken, iterationIndex, depth + 1, info); - } - } - if (imlSegment->nextSegmentBranchTaken) - { - if (subrange->subrangeBranchTaken == nullptr) - { - info->hasUndefinedEndings = true; - } - else - { - _findSubrangeWriteEndings(subrange->subrangeBranchTaken, iterationIndex, depth + 1, info); - } - } -} - -void _analyzeRangeDataFlow(raLivenessSubrange_t* subrange) -{ - if (subrange->end.index != RA_INTER_RANGE_END) - return; - // analyze data flow across segments (if this segment has writes) - if (subrange->hasStore) - { - subrangeEndingInfo_t writeEndingInfo; - writeEndingInfo.subrangeCount = 0; - writeEndingInfo.hasUndefinedEndings = false; - _findSubrangeWriteEndings(subrange, PPCRecRA_getNextIterationIndex(), 0, &writeEndingInfo); - if (writeEndingInfo.hasUndefinedEndings == false) - { - // get cost of delaying store into endings - sint32 delayStoreCost = 0; - bool alreadyStoredInAllEndings = true; - for (sint32 i = 0; i < writeEndingInfo.subrangeCount; i++) - { - raLivenessSubrange_t* subrangeItr = writeEndingInfo.subrangeList[i]; - if( subrangeItr->hasStore ) - continue; // this ending already stores, no extra cost - alreadyStoredInAllEndings = false; - sint32 storeCost = PPCRecRARange_getReadWriteCost(subrangeItr->imlSegment); - delayStoreCost = std::max(storeCost, delayStoreCost); - } - if (alreadyStoredInAllEndings) - { - subrange->hasStore = false; - subrange->hasStoreDelayed = true; - } - else if (delayStoreCost <= PPCRecRARange_getReadWriteCost(subrange->imlSegment)) - { - subrange->hasStore = false; - subrange->hasStoreDelayed = true; - for (sint32 i = 0; i < writeEndingInfo.subrangeCount; i++) - { - raLivenessSubrange_t* subrangeItr = writeEndingInfo.subrangeList[i]; - subrangeItr->hasStore = true; - } - } - } - } -} - -inline IMLReg _MakeNativeReg(IMLRegFormat baseFormat, IMLRegID regId) -{ - return IMLReg(baseFormat, baseFormat, 0, regId); -} - -void PPCRecRA_insertGPRLoadInstructions(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment, sint32 insertIndex, std::span loadList) -{ - PPCRecompiler_pushBackIMLInstructions(imlSegment, insertIndex, loadList.size()); - for (sint32 i = 0; i < loadList.size(); i++) - { - IMLRegFormat baseFormat = ctx.regIdToBaseFormat[loadList[i]->range->virtualRegister]; - cemu_assert_debug(baseFormat != IMLRegFormat::INVALID_FORMAT); - imlSegment->imlList[insertIndex + i].make_r_name(_MakeNativeReg(baseFormat, loadList[i]->range->physicalRegister), loadList[i]->range->name); - } -} - -void PPCRecRA_insertGPRStoreInstructions(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment, sint32 insertIndex, std::span storeList) -{ - PPCRecompiler_pushBackIMLInstructions(imlSegment, insertIndex, storeList.size()); - for (size_t i = 0; i < storeList.size(); i++) - { - IMLRegFormat baseFormat = ctx.regIdToBaseFormat[storeList[i]->range->virtualRegister]; - cemu_assert_debug(baseFormat != IMLRegFormat::INVALID_FORMAT); - imlSegment->imlList[insertIndex + i].make_name_r(storeList[i]->range->name, _MakeNativeReg(baseFormat, storeList[i]->range->physicalRegister)); - } -} - -void IMLRA_GenerateSegmentMoveInstructions(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment) -{ - std::unordered_map virtId2PhysRegIdMap; // key = virtual register, value = physical register - IMLRALivenessTimeline livenessTimeline; - sint32 index = 0; - sint32 suffixInstructionCount = imlSegment->HasSuffixInstruction() ? 1 : 0; - // load register ranges that are supplied from previous segments - raLivenessSubrange_t* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; - while(subrangeItr) - { - if (subrangeItr->start.index == RA_INTER_RANGE_START) - { - livenessTimeline.AddActiveRange(subrangeItr); -#ifdef CEMU_DEBUG_ASSERT - // load GPR - if (subrangeItr->_noLoad == false) - { - assert_dbg(); - } - // update translation table - cemu_assert_debug(!virtId2PhysRegIdMap.contains(subrangeItr->range->virtualRegister)); -#endif - virtId2PhysRegIdMap.try_emplace(subrangeItr->range->virtualRegister, subrangeItr->range->physicalRegister); - } - // next - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; - } - // process instructions - while(index < imlSegment->imlList.size() + 1) - { - // expire ranges - livenessTimeline.ExpireRanges(index); - for (auto& expiredRange : livenessTimeline.GetExpiredRanges()) - { - // update translation table - virtId2PhysRegIdMap.erase(expiredRange->range->virtualRegister); - // store GPR if required - // special care has to be taken to execute any stores before the suffix instruction since trailing instructions may not get executed - if (expiredRange->hasStore) - { - PPCRecRA_insertGPRStoreInstructions(ctx, imlSegment, std::min(index, imlSegment->imlList.size() - suffixInstructionCount), {&expiredRange, 1}); - index++; - } - } - - // load new ranges - subrangeItr = imlSegment->raInfo.linkedList_allSubranges; - while(subrangeItr) - { - if (subrangeItr->start.index == index) - { - livenessTimeline.AddActiveRange(subrangeItr); - // load GPR - // similar to stores, any loads for the next segment need to happen before the suffix instruction - // however, ranges that exit the segment at the end but do not cover the suffix instruction are illegal (e.g. RA_INTER_RANGE_END to RA_INTER_RANGE_END subrange) - // this is to prevent the RA from inserting store/load instructions after the suffix instruction - if (imlSegment->HasSuffixInstruction()) - { - cemu_assert_debug(subrangeItr->start.index <= imlSegment->GetSuffixInstructionIndex()); - } - if (subrangeItr->_noLoad == false) - { - PPCRecRA_insertGPRLoadInstructions(ctx, imlSegment, std::min(index, imlSegment->imlList.size() - suffixInstructionCount), {&subrangeItr , 1}); - index++; - subrangeItr->start.index--; - } - // update translation table - virtId2PhysRegIdMap.insert_or_assign(subrangeItr->range->virtualRegister, subrangeItr->range->physicalRegister); - } - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; - } - // rewrite registers - if (index < imlSegment->imlList.size()) - imlSegment->imlList[index].RewriteGPR(virtId2PhysRegIdMap); - // next iml instruction - index++; - } - // expire infinite subranges (subranges which cross the segment border) - std::vector loadStoreList; - livenessTimeline.ExpireRanges(RA_INTER_RANGE_END); - for (auto liverange : livenessTimeline.GetExpiredRanges()) - { - // update translation table - virtId2PhysRegIdMap.erase(liverange->range->virtualRegister); - // store GPR - if (liverange->hasStore) - loadStoreList.emplace_back(liverange); - } - cemu_assert_debug(livenessTimeline.activeRanges.empty()); - if (!loadStoreList.empty()) - PPCRecRA_insertGPRStoreInstructions(ctx, imlSegment, imlSegment->imlList.size() - suffixInstructionCount, loadStoreList); - // load subranges for next segments - subrangeItr = imlSegment->raInfo.linkedList_allSubranges; - loadStoreList.clear(); - while(subrangeItr) - { - if (subrangeItr->start.index == RA_INTER_RANGE_END) - { - livenessTimeline.AddActiveRange(subrangeItr); - // load GPR - if (subrangeItr->_noLoad == false) - loadStoreList.emplace_back(subrangeItr); - // update translation table - virtId2PhysRegIdMap.try_emplace(subrangeItr->range->virtualRegister, subrangeItr->range->physicalRegister); - } - // next - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; - } - if (!loadStoreList.empty()) - PPCRecRA_insertGPRLoadInstructions(ctx, imlSegment, imlSegment->imlList.size() - suffixInstructionCount, loadStoreList); -} - -void IMLRA_GenerateMoveInstructions(IMLRegisterAllocatorContext& ctx) -{ - for (size_t s = 0; s < ctx.deprGenContext->segmentList2.size(); s++) - { - IMLSegment* imlSegment = ctx.deprGenContext->segmentList2[s]; - IMLRA_GenerateSegmentMoveInstructions(ctx, imlSegment); - } -} - void IMLRA_ReshapeForRegisterAllocation(ppcImlGenContext_t* ppcImlGenContext) { // insert empty segments after every non-taken branch if the linked segment has more than one input @@ -974,7 +1340,7 @@ void IMLRA_ReshapeForRegisterAllocation(ppcImlGenContext_t* ppcImlGenContext) for (size_t s = 0; s < ppcImlGenContext->segmentList2.size(); s++) { IMLSegment* imlSegment = ppcImlGenContext->segmentList2[s]; - PPCRecRA_identifyLoop(ppcImlGenContext, imlSegment); + IMLRA_IdentifyLoop(ppcImlGenContext, imlSegment); } } @@ -1009,7 +1375,7 @@ void IMLRA_CalculateSegmentMinMaxAbstractRanges(IMLRegisterAllocatorContext& ctx cemu_assert_debug(ctx.regIdToBaseFormat[gprId] == gprReg.GetBaseFormat()); // the base type per register always has to be the same #endif } - }); + }); instructionIndex++; } } @@ -1026,7 +1392,7 @@ void IMLRA_CalculateLivenessRanges(IMLRegisterAllocatorContext& ctx) } } -raLivenessSubrange_t* PPCRecRA_convertToMappedRanges(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment, sint32 vGPR, raLivenessRange_t* range) +raLivenessRange* PPCRecRA_convertToMappedRanges(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment, IMLRegID vGPR, IMLName name) { IMLRARegAbstractLiveness* abstractRange = _GetAbstractRange(ctx, imlSegment, vGPR); if (!abstractRange) @@ -1034,16 +1400,22 @@ raLivenessSubrange_t* PPCRecRA_convertToMappedRanges(IMLRegisterAllocatorContext if (abstractRange->isProcessed) { // return already existing segment - raLivenessSubrange_t* existingRange = IMLRA_GetSubrange(imlSegment, vGPR); + raLivenessRange* existingRange = IMLRA_GetSubrange(imlSegment, vGPR); cemu_assert_debug(existingRange); return existingRange; } abstractRange->isProcessed = true; // create subrange -#ifdef CEMU_DEBUG_ASSERT cemu_assert_debug(IMLRA_GetSubrange(imlSegment, vGPR) == nullptr); -#endif - raLivenessSubrange_t* subrange = PPCRecRA_createSubrange(ctx.deprGenContext, range, imlSegment, abstractRange->usageStart, abstractRange->usageEnd); + cemu_assert_debug( + (abstractRange->usageStart == abstractRange->usageEnd && (abstractRange->usageStart == RA_INTER_RANGE_START || abstractRange->usageStart == RA_INTER_RANGE_END)) || + abstractRange->usageStart < abstractRange->usageEnd); // usageEnd is exclusive so it should always be larger + sint32 inclusiveEnd = abstractRange->usageEnd; + if (inclusiveEnd != RA_INTER_RANGE_START && inclusiveEnd != RA_INTER_RANGE_END) + inclusiveEnd--; // subtract one, because usageEnd is exclusive, but the end value of the interval passed to createSubrange is inclusive + raInterval interval; + interval.SetInterval(abstractRange->usageStart, true, inclusiveEnd, true); + raLivenessRange* subrange = IMLRA_CreateRange(ctx.deprGenContext, imlSegment, vGPR, name, interval.start, interval.end); // traverse forward if (abstractRange->usageEnd == RA_INTER_RANGE_END) { @@ -1052,8 +1424,9 @@ raLivenessSubrange_t* PPCRecRA_convertToMappedRanges(IMLRegisterAllocatorContext IMLRARegAbstractLiveness* branchTakenRange = _GetAbstractRange(ctx, imlSegment->nextSegmentBranchTaken, vGPR); if (branchTakenRange && branchTakenRange->usageStart == RA_INTER_RANGE_START) { - subrange->subrangeBranchTaken = PPCRecRA_convertToMappedRanges(ctx, imlSegment->nextSegmentBranchTaken, vGPR, range); - cemu_assert_debug(subrange->subrangeBranchTaken->start.index == RA_INTER_RANGE_START); + subrange->subrangeBranchTaken = PPCRecRA_convertToMappedRanges(ctx, imlSegment->nextSegmentBranchTaken, vGPR, name); + subrange->subrangeBranchTaken->previousRanges.push_back(subrange); + cemu_assert_debug(subrange->subrangeBranchTaken->interval.ExtendsPreviousSegment()); } } if (imlSegment->nextSegmentBranchNotTaken) @@ -1061,8 +1434,9 @@ raLivenessSubrange_t* PPCRecRA_convertToMappedRanges(IMLRegisterAllocatorContext IMLRARegAbstractLiveness* branchNotTakenRange = _GetAbstractRange(ctx, imlSegment->nextSegmentBranchNotTaken, vGPR); if (branchNotTakenRange && branchNotTakenRange->usageStart == RA_INTER_RANGE_START) { - subrange->subrangeBranchNotTaken = PPCRecRA_convertToMappedRanges(ctx, imlSegment->nextSegmentBranchNotTaken, vGPR, range); - cemu_assert_debug(subrange->subrangeBranchNotTaken->start.index == RA_INTER_RANGE_START); + subrange->subrangeBranchNotTaken = PPCRecRA_convertToMappedRanges(ctx, imlSegment->nextSegmentBranchNotTaken, vGPR, name); + subrange->subrangeBranchNotTaken->previousRanges.push_back(subrange); + cemu_assert_debug(subrange->subrangeBranchNotTaken->interval.ExtendsPreviousSegment()); } } } @@ -1072,60 +1446,85 @@ raLivenessSubrange_t* PPCRecRA_convertToMappedRanges(IMLRegisterAllocatorContext for (auto& it : imlSegment->list_prevSegments) { IMLRARegAbstractLiveness* prevRange = _GetAbstractRange(ctx, it, vGPR); - if(!prevRange) + if (!prevRange) continue; if (prevRange->usageEnd == RA_INTER_RANGE_END) - PPCRecRA_convertToMappedRanges(ctx, it, vGPR, range); - } - } - // for subranges which exit the segment at the end there is a hard requirement that they cover the suffix instruction - // this is due to range load instructions being inserted before the suffix instruction - if (subrange->end.index == RA_INTER_RANGE_END) - { - if (imlSegment->HasSuffixInstruction()) - { - cemu_assert_debug(subrange->start.index <= imlSegment->GetSuffixInstructionIndex()); + PPCRecRA_convertToMappedRanges(ctx, it, vGPR, name); } } return subrange; } +void IMLRA_UpdateOrAddSubrangeLocation(raLivenessRange* subrange, raInstructionEdge pos) +{ + if (subrange->list_accessLocations.empty()) + { + subrange->list_accessLocations.emplace_back(pos); + return; + } + if(subrange->list_accessLocations.back().pos == pos) + return; + cemu_assert_debug(subrange->list_accessLocations.back().pos < pos); + subrange->list_accessLocations.emplace_back(pos); +} + // take abstract range data and create LivenessRanges void IMLRA_ConvertAbstractToLivenessRanges(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment) { + const std::unordered_map& regToSubrange = IMLRA_GetSubrangeMap(imlSegment); + + auto AddOrUpdateFixedRegRequirement = [&](IMLRegID regId, sint32 instructionIndex, bool isInput, const IMLPhysRegisterSet& physRegSet) { + raLivenessRange* subrange = regToSubrange.find(regId)->second; + cemu_assert_debug(subrange); + raFixedRegRequirement tmp; + tmp.pos.Set(instructionIndex, isInput); + tmp.allowedReg = physRegSet; + if (subrange->list_fixedRegRequirements.empty() || subrange->list_fixedRegRequirements.back().pos != tmp.pos) + subrange->list_fixedRegRequirements.push_back(tmp); + }; + // convert abstract min-max ranges to liveness range objects auto& segMap = ctx.GetSegmentAbstractRangeMap(imlSegment); for (auto& it : segMap) { - if(it.second.isProcessed) + if (it.second.isProcessed) continue; IMLRegID regId = it.first; - raLivenessRange_t* range = PPCRecRA_createRangeBase(ctx.deprGenContext, regId, ctx.raParam->regIdToName.find(regId)->second); - PPCRecRA_convertToMappedRanges(ctx, imlSegment, regId, range); + PPCRecRA_convertToMappedRanges(ctx, imlSegment, regId, ctx.raParam->regIdToName.find(regId)->second); } // fill created ranges with read/write location indices // note that at this point there is only one range per register per segment // and the algorithm below relies on this - const std::unordered_map& regToSubrange = IMLRA_GetSubrangeMap(imlSegment); size_t index = 0; IMLUsedRegisters gprTracking; while (index < imlSegment->imlList.size()) { imlSegment->imlList[index].CheckRegisterUsage(&gprTracking); - gprTracking.ForEachAccessedGPR([&](IMLReg gprReg, bool isWritten) { + raInstructionEdge pos((sint32)index, true); + gprTracking.ForEachReadGPR([&](IMLReg gprReg) { IMLRegID gprId = gprReg.GetRegID(); - raLivenessSubrange_t* subrange = regToSubrange.find(gprId)->second; - PPCRecRA_updateOrAddSubrangeLocation(subrange, index, !isWritten, isWritten); -#ifdef CEMU_DEBUG_ASSERT - if ((sint32)index < subrange->start.index) + raLivenessRange* subrange = regToSubrange.find(gprId)->second; + IMLRA_UpdateOrAddSubrangeLocation(subrange, pos); + }); + pos = {(sint32)index, false}; + gprTracking.ForEachWrittenGPR([&](IMLReg gprReg) { + IMLRegID gprId = gprReg.GetRegID(); + raLivenessRange* subrange = regToSubrange.find(gprId)->second; + IMLRA_UpdateOrAddSubrangeLocation(subrange, pos); + }); + // check fixed register requirements + IMLFixedRegisters fixedRegs; + GetInstructionFixedRegisters(&imlSegment->imlList[index], fixedRegs); + for (auto& fixedRegAccess : fixedRegs.listInput) { - IMLRARegAbstractLiveness* dbgAbstractRange = _GetAbstractRange(ctx, imlSegment, gprId); - assert_dbg(); + if (fixedRegAccess.reg != IMLREG_INVALID) + AddOrUpdateFixedRegRequirement(fixedRegAccess.reg.GetRegID(), index, true, fixedRegAccess.physRegSet); + } + for (auto& fixedRegAccess : fixedRegs.listOutput) + { + if (fixedRegAccess.reg != IMLREG_INVALID) + AddOrUpdateFixedRegRequirement(fixedRegAccess.reg.GetRegID(), index, false, fixedRegAccess.physRegSet); } - if ((sint32)index + 1 > subrange->end.index) - assert_dbg(); -#endif - }); index++; } } @@ -1137,7 +1536,7 @@ void IMLRA_extendAbstractRangeToEndOfSegment(IMLRegisterAllocatorContext& ctx, I if (it == segDistMap.end()) { sint32 startIndex; - if(imlSegment->HasSuffixInstruction()) + if (imlSegment->HasSuffixInstruction()) startIndex = imlSegment->GetSuffixInstructionIndex(); else startIndex = RA_INTER_RANGE_END; @@ -1190,7 +1589,7 @@ void _IMLRA_checkAndTryExtendRange(IMLRegisterAllocatorContext& ctx, IMLSegment* { if (routeDepth >= 64) { - cemuLog_log(LogType::Recompiler, "Recompiler RA route maximum depth exceeded\n"); + cemuLog_logDebug(LogType::Force, "Recompiler RA route maximum depth exceeded\n"); return; } route[routeDepth] = currentSegment; @@ -1267,11 +1666,15 @@ void PPCRecRA_followFlowAndExtendRanges(IMLRegisterAllocatorContext& ctx, IMLSeg std::vector list_segments; std::vector list_processedSegment; size_t segmentCount = ctx.deprGenContext->segmentList2.size(); - list_segments.reserve(segmentCount+1); + list_segments.reserve(segmentCount + 1); list_processedSegment.resize(segmentCount); - auto markSegProcessed = [&list_processedSegment](IMLSegment* seg) {list_processedSegment[seg->momentaryIndex] = true; }; - auto isSegProcessed = [&list_processedSegment](IMLSegment* seg) -> bool { return list_processedSegment[seg->momentaryIndex]; }; + auto markSegProcessed = [&list_processedSegment](IMLSegment* seg) { + list_processedSegment[seg->momentaryIndex] = true; + }; + auto isSegProcessed = [&list_processedSegment](IMLSegment* seg) -> bool { + return list_processedSegment[seg->momentaryIndex]; + }; markSegProcessed(imlSegment); sint32 index = 0; @@ -1295,7 +1698,7 @@ void PPCRecRA_followFlowAndExtendRanges(IMLRegisterAllocatorContext& ctx, IMLSeg } } -void IMLRA_mergeCloseAbstractRanges(IMLRegisterAllocatorContext& ctx) +void IMLRA_MergeCloseAbstractRanges(IMLRegisterAllocatorContext& ctx) { for (size_t s = 0; s < ctx.deprGenContext->segmentList2.size(); s++) { @@ -1306,7 +1709,7 @@ void IMLRA_mergeCloseAbstractRanges(IMLRegisterAllocatorContext& ctx) } } -void IMLRA_extendAbstracRangesOutOfLoops(IMLRegisterAllocatorContext& ctx) +void IMLRA_ExtendAbstractRangesOutOfLoops(IMLRegisterAllocatorContext& ctx) { for (size_t s = 0; s < ctx.deprGenContext->segmentList2.size(); s++) { @@ -1327,11 +1730,11 @@ void IMLRA_extendAbstracRangesOutOfLoops(IMLRegisterAllocatorContext& ctx) if (hasLoopExit == false) continue; - // extend looping ranges into all exits (this allows the data flow analyzer to move stores out of the loop) + // extend looping ranges into all exits (this allows the data flow analyzer to move stores out of the loop) auto& segMap = ctx.GetSegmentAbstractRangeMap(imlSegment); for (auto& it : segMap) { - if(it.second.usageEnd != RA_INTER_RANGE_END) + if (it.second.usageEnd != RA_INTER_RANGE_END) continue; if (imlSegment->nextSegmentBranchTaken) IMLRA_extendAbstractRangeToBeginningOfSegment(ctx, imlSegment->nextSegmentBranchTaken, it.first); @@ -1343,26 +1746,26 @@ void IMLRA_extendAbstracRangesOutOfLoops(IMLRegisterAllocatorContext& ctx) void IMLRA_ProcessFlowAndCalculateLivenessRanges(IMLRegisterAllocatorContext& ctx) { - IMLRA_mergeCloseAbstractRanges(ctx); - // extra pass to move register stores out of loops - IMLRA_extendAbstracRangesOutOfLoops(ctx); + IMLRA_MergeCloseAbstractRanges(ctx); + // extra pass to move register loads and stores out of loops + IMLRA_ExtendAbstractRangesOutOfLoops(ctx); // calculate liveness ranges for (auto& segIt : ctx.deprGenContext->segmentList2) IMLRA_ConvertAbstractToLivenessRanges(ctx, segIt); } -void PPCRecRA_analyzeSubrangeDataDependencyV2(raLivenessSubrange_t* subrange) +void IMLRA_AnalyzeSubrangeDataDependency(raLivenessRange* subrange) { bool isRead = false; bool isWritten = false; bool isOverwritten = false; - for (auto& location : subrange->list_locations) + for (auto& location : subrange->list_accessLocations) { - if (location.isRead) + if (location.IsRead()) { isRead = true; } - if (location.isWrite) + if (location.IsWrite()) { if (isRead == false) isOverwritten = true; @@ -1372,31 +1775,412 @@ void PPCRecRA_analyzeSubrangeDataDependencyV2(raLivenessSubrange_t* subrange) subrange->_noLoad = isOverwritten; subrange->hasStore = isWritten; - if (subrange->start.index == RA_INTER_RANGE_START) + if (subrange->interval.ExtendsPreviousSegment()) subrange->_noLoad = true; } +struct subrangeEndingInfo_t +{ + raLivenessRange* subrangeList[SUBRANGE_LIST_SIZE]; + sint32 subrangeCount; + + bool hasUndefinedEndings; +}; + +void _findSubrangeWriteEndings(raLivenessRange* subrange, uint32 iterationIndex, sint32 depth, subrangeEndingInfo_t* info) +{ + if (depth >= 30) + { + info->hasUndefinedEndings = true; + return; + } + if (subrange->lastIterationIndex == iterationIndex) + return; // already processed + subrange->lastIterationIndex = iterationIndex; + if (subrange->hasStoreDelayed) + return; // no need to traverse this subrange + IMLSegment* imlSegment = subrange->imlSegment; + if (!subrange->interval.ExtendsIntoNextSegment()) + { + // ending segment + if (info->subrangeCount >= SUBRANGE_LIST_SIZE) + { + info->hasUndefinedEndings = true; + return; + } + else + { + info->subrangeList[info->subrangeCount] = subrange; + info->subrangeCount++; + } + return; + } + + // traverse next subranges in flow + if (imlSegment->nextSegmentBranchNotTaken) + { + if (subrange->subrangeBranchNotTaken == nullptr) + { + info->hasUndefinedEndings = true; + } + else + { + _findSubrangeWriteEndings(subrange->subrangeBranchNotTaken, iterationIndex, depth + 1, info); + } + } + if (imlSegment->nextSegmentBranchTaken) + { + if (subrange->subrangeBranchTaken == nullptr) + { + info->hasUndefinedEndings = true; + } + else + { + _findSubrangeWriteEndings(subrange->subrangeBranchTaken, iterationIndex, depth + 1, info); + } + } +} + +static void IMLRA_AnalyzeRangeDataFlow(raLivenessRange* subrange) +{ + if (!subrange->interval.ExtendsIntoNextSegment()) + return; + // analyze data flow across segments (if this segment has writes) + if (subrange->hasStore) + { + subrangeEndingInfo_t writeEndingInfo; + writeEndingInfo.subrangeCount = 0; + writeEndingInfo.hasUndefinedEndings = false; + _findSubrangeWriteEndings(subrange, IMLRA_GetNextIterationIndex(), 0, &writeEndingInfo); + if (writeEndingInfo.hasUndefinedEndings == false) + { + // get cost of delaying store into endings + sint32 delayStoreCost = 0; + bool alreadyStoredInAllEndings = true; + for (sint32 i = 0; i < writeEndingInfo.subrangeCount; i++) + { + raLivenessRange* subrangeItr = writeEndingInfo.subrangeList[i]; + if (subrangeItr->hasStore) + continue; // this ending already stores, no extra cost + alreadyStoredInAllEndings = false; + sint32 storeCost = IMLRA_GetSegmentReadWriteCost(subrangeItr->imlSegment); + delayStoreCost = std::max(storeCost, delayStoreCost); + } + if (alreadyStoredInAllEndings) + { + subrange->hasStore = false; + subrange->hasStoreDelayed = true; + } + else if (delayStoreCost <= IMLRA_GetSegmentReadWriteCost(subrange->imlSegment)) + { + subrange->hasStore = false; + subrange->hasStoreDelayed = true; + for (sint32 i = 0; i < writeEndingInfo.subrangeCount; i++) + { + raLivenessRange* subrangeItr = writeEndingInfo.subrangeList[i]; + subrangeItr->hasStore = true; + } + } + } + } +} + void IMLRA_AnalyzeRangeDataFlow(ppcImlGenContext_t* ppcImlGenContext) { - // this function is called after _assignRegisters(), which means that all ranges are already final and wont change anymore - // first do a per-subrange pass - for (auto& range : ppcImlGenContext->raInfo.list_ranges) + // this function is called after _AssignRegisters(), which means that all liveness ranges are already final and must not be modified anymore + // track read/write dependencies per segment + for (auto& seg : ppcImlGenContext->segmentList2) { - for (auto& subrange : range->list_subranges) + raLivenessRange* subrange = seg->raInfo.linkedList_allSubranges; + while (subrange) { - PPCRecRA_analyzeSubrangeDataDependencyV2(subrange); + IMLRA_AnalyzeSubrangeDataDependency(subrange); + subrange = subrange->link_allSegmentRanges.next; } } - // then do a second pass where we scan along subrange flow - for (auto& range : ppcImlGenContext->raInfo.list_ranges) + // propagate information across segment boundaries + for (auto& seg : ppcImlGenContext->segmentList2) { - for (auto& subrange : range->list_subranges) // todo - traversing this backwards should be faster and yield better results due to the nature of the algorithm + raLivenessRange* subrange = seg->raInfo.linkedList_allSubranges; + while (subrange) { - _analyzeRangeDataFlow(subrange); + IMLRA_AnalyzeRangeDataFlow(subrange); + subrange = subrange->link_allSegmentRanges.next; } } } +/* Generate move instructions */ + +inline IMLReg _MakeNativeReg(IMLRegFormat baseFormat, IMLRegID regId) +{ + return IMLReg(baseFormat, baseFormat, 0, regId); +} + +// prepass for IMLRA_GenerateSegmentMoveInstructions which updates all virtual registers to their physical counterparts +void IMLRA_RewriteRegisters(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment) +{ + std::unordered_map virtId2PhysReg; + boost::container::small_vector activeRanges; + raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; + raInstructionEdge currentEdge; + for (size_t i = 0; i < imlSegment->imlList.size(); i++) + { + currentEdge.Set(i, false); // set to instruction index on output edge + // activate ranges which begin before or during this instruction + while (currentRange && currentRange->interval.start <= currentEdge) + { + cemu_assert_debug(virtId2PhysReg.find(currentRange->GetVirtualRegister()) == virtId2PhysReg.end() || virtId2PhysReg[currentRange->GetVirtualRegister()] == currentRange->GetPhysicalRegister()); // check for register conflict + + virtId2PhysReg[currentRange->GetVirtualRegister()] = currentRange->GetPhysicalRegister(); + activeRanges.push_back(currentRange); + currentRange = currentRange->link_allSegmentRanges.next; + } + // rewrite registers + imlSegment->imlList[i].RewriteGPR(virtId2PhysReg); + // deactivate ranges which end during this instruction + auto it = activeRanges.begin(); + while (it != activeRanges.end()) + { + if ((*it)->interval.end <= currentEdge) + { + virtId2PhysReg.erase((*it)->GetVirtualRegister()); + it = activeRanges.erase(it); + } + else + ++it; + } + } +} + +void IMLRA_GenerateSegmentMoveInstructions2(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment) +{ + IMLRA_RewriteRegisters(ctx, imlSegment); + +#if DEBUG_RA_INSTRUCTION_GEN + cemuLog_log(LogType::Force, ""); + cemuLog_log(LogType::Force, "[Seg before RA]"); + IMLDebug_DumpSegment(nullptr, imlSegment, true); +#endif + + bool hadSuffixInstruction = imlSegment->HasSuffixInstruction(); + + std::vector rebuiltInstructions; + sint32 numInstructionsWithoutSuffix = (sint32)imlSegment->imlList.size() - (imlSegment->HasSuffixInstruction() ? 1 : 0); + + if (imlSegment->imlList.empty()) + { + // empty segments need special handling (todo - look into merging this with the core logic below eventually) + // store all ranges + raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; + while (currentRange) + { + if (currentRange->hasStore) + rebuiltInstructions.emplace_back().make_name_r(currentRange->GetName(), _MakeNativeReg(ctx.regIdToBaseFormat[currentRange->GetVirtualRegister()], currentRange->GetPhysicalRegister())); + currentRange = currentRange->link_allSegmentRanges.next; + } + // load ranges + currentRange = imlSegment->raInfo.linkedList_allSubranges; + while (currentRange) + { + if (!currentRange->_noLoad) + { + cemu_assert_debug(currentRange->interval.ExtendsIntoNextSegment()); + rebuiltInstructions.emplace_back().make_r_name(_MakeNativeReg(ctx.regIdToBaseFormat[currentRange->GetVirtualRegister()], currentRange->GetPhysicalRegister()), currentRange->GetName()); + } + currentRange = currentRange->link_allSegmentRanges.next; + } + imlSegment->imlList = std::move(rebuiltInstructions); + return; + } + + // make sure that no range exceeds the suffix instruction input edge except if they need to be loaded for the next segment (todo - for those, set the start point accordingly?) + { + raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; + raInstructionEdge edge; + if (imlSegment->HasSuffixInstruction()) + edge.Set(numInstructionsWithoutSuffix, true); + else + edge.Set(numInstructionsWithoutSuffix - 1, false); + + while (currentRange) + { + if (!currentRange->interval.IsNextSegmentOnly() && currentRange->interval.end > edge) + { + currentRange->interval.SetEnd(edge); + } + currentRange = currentRange->link_allSegmentRanges.next; + } + } + +#if DEBUG_RA_INSTRUCTION_GEN + cemuLog_log(LogType::Force, ""); + cemuLog_log(LogType::Force, "--- Intermediate liveness info ---"); + { + raLivenessRange* dbgRange = imlSegment->raInfo.linkedList_allSubranges; + while (dbgRange) + { + cemuLog_log(LogType::Force, "Range i{}: {}-{}", dbgRange->GetVirtualRegister(), dbgRange->interval2.start.GetDebugString(), dbgRange->interval2.end.GetDebugString()); + dbgRange = dbgRange->link_allSegmentRanges.next; + } + } +#endif + + boost::container::small_vector activeRanges; + // first we add all the ranges that extend from the previous segment, some of these will end immediately at the first instruction so we might need to store them early + raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; + // make all ranges active that start on RA_INTER_RANGE_START + while (currentRange && currentRange->interval.start.ConnectsToPreviousSegment()) + { + activeRanges.push_back(currentRange); + currentRange = currentRange->link_allSegmentRanges.next; + } + // store all ranges that end before the first output edge (includes RA_INTER_RANGE_START) + auto it = activeRanges.begin(); + raInstructionEdge firstOutputEdge; + firstOutputEdge.Set(0, false); + while (it != activeRanges.end()) + { + if ((*it)->interval.end < firstOutputEdge) + { + raLivenessRange* storedRange = *it; + if (storedRange->hasStore) + rebuiltInstructions.emplace_back().make_name_r(storedRange->GetName(), _MakeNativeReg(ctx.regIdToBaseFormat[storedRange->GetVirtualRegister()], storedRange->GetPhysicalRegister())); + it = activeRanges.erase(it); + continue; + } + ++it; + } + + sint32 numInstructions = (sint32)imlSegment->imlList.size(); + for (sint32 i = 0; i < numInstructions; i++) + { + raInstructionEdge curEdge; + // input edge + curEdge.SetRaw(i * 2 + 1); // +1 to include ranges that start at the output of the instruction + while (currentRange && currentRange->interval.start <= curEdge) + { + if (!currentRange->_noLoad) + { + rebuiltInstructions.emplace_back().make_r_name(_MakeNativeReg(ctx.regIdToBaseFormat[currentRange->GetVirtualRegister()], currentRange->GetPhysicalRegister()), currentRange->GetName()); + } + activeRanges.push_back(currentRange); + currentRange = currentRange->link_allSegmentRanges.next; + } + // copy instruction + rebuiltInstructions.push_back(imlSegment->imlList[i]); + // output edge + curEdge.SetRaw(i * 2 + 1 + 1); + // also store ranges that end on the next input edge, we handle this by adding an extra 1 above + auto it = activeRanges.begin(); + while (it != activeRanges.end()) + { + if ((*it)->interval.end <= curEdge) + { + // range expires + // todo - check hasStore + raLivenessRange* storedRange = *it; + if (storedRange->hasStore) + { + cemu_assert_debug(i != numInstructionsWithoutSuffix); // not allowed to emit after suffix + rebuiltInstructions.emplace_back().make_name_r(storedRange->GetName(), _MakeNativeReg(ctx.regIdToBaseFormat[storedRange->GetVirtualRegister()], storedRange->GetPhysicalRegister())); + } + it = activeRanges.erase(it); + continue; + } + ++it; + } + } + // if there is no suffix instruction we currently need to handle the final loads here + cemu_assert_debug(hadSuffixInstruction == imlSegment->HasSuffixInstruction()); + if (imlSegment->HasSuffixInstruction()) + { + cemu_assert_debug(!currentRange); // currentRange should be NULL? + for (auto& remainingRange : activeRanges) + { + cemu_assert_debug(!remainingRange->hasStore); + } + } + else + { + for (auto& remainingRange : activeRanges) + { + cemu_assert_debug(!remainingRange->hasStore); // this range still needs to be stored + } + while (currentRange) + { + cemu_assert_debug(currentRange->interval.IsNextSegmentOnly()); + cemu_assert_debug(!currentRange->_noLoad); + rebuiltInstructions.emplace_back().make_r_name(_MakeNativeReg(ctx.regIdToBaseFormat[currentRange->GetVirtualRegister()], currentRange->GetPhysicalRegister()), currentRange->GetName()); + currentRange = currentRange->link_allSegmentRanges.next; + } + } + + imlSegment->imlList = std::move(rebuiltInstructions); + cemu_assert_debug(hadSuffixInstruction == imlSegment->HasSuffixInstruction()); + +#if DEBUG_RA_INSTRUCTION_GEN + cemuLog_log(LogType::Force, ""); + cemuLog_log(LogType::Force, "[Seg after RA]"); + IMLDebug_DumpSegment(nullptr, imlSegment, false); +#endif +} + +void IMLRA_GenerateMoveInstructions(IMLRegisterAllocatorContext& ctx) +{ + for (size_t s = 0; s < ctx.deprGenContext->segmentList2.size(); s++) + { + IMLSegment* imlSegment = ctx.deprGenContext->segmentList2[s]; + IMLRA_GenerateSegmentMoveInstructions2(ctx, imlSegment); + } +} + +static void DbgVerifyFixedRegRequirements(IMLSegment* imlSegment) +{ +#if DEBUG_RA_EXTRA_VALIDATION + std::vector frr = IMLRA_BuildSegmentInstructionFixedRegList(imlSegment); + for(auto& fixedReq : frr) + { + for (raLivenessRange* range = imlSegment->raInfo.linkedList_allSubranges; range; range = range->link_allSegmentRanges.next) + { + if (!range->interval2.ContainsEdge(fixedReq.pos)) + continue; + // verify if the requirement is compatible + if(range->GetVirtualRegister() == fixedReq.regId) + { + cemu_assert(range->HasPhysicalRegister()); + cemu_assert(fixedReq.allowedReg.IsAvailable(range->GetPhysicalRegister())); // virtual register matches, but not assigned the right physical register + } + else + { + cemu_assert(!fixedReq.allowedReg.IsAvailable(range->GetPhysicalRegister())); // virtual register does not match, but using the reserved physical register + } + } + } +#endif +} + +static void DbgVerifyAllRanges(IMLRegisterAllocatorContext& ctx) +{ +#if DEBUG_RA_EXTRA_VALIDATION + for (size_t s = 0; s < ctx.deprGenContext->segmentList2.size(); s++) + { + IMLSegment* imlSegment = ctx.deprGenContext->segmentList2[s]; + raLivenessRange* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + while (subrangeItr) + { + PPCRecRA_debugValidateSubrange(subrangeItr); + subrangeItr = subrangeItr->link_allSegmentRanges.next; + } + } + // check that no range validates register requirements + for (size_t s = 0; s < ctx.deprGenContext->segmentList2.size(); s++) + { + DbgVerifyFixedRegRequirements(ctx.deprGenContext->segmentList2[s]); + } +#endif +} + void IMLRegisterAllocator_AllocateRegisters(ppcImlGenContext_t* ppcImlGenContext, IMLRegisterAllocatorParameters& raParam) { IMLRegisterAllocatorContext ctx; @@ -1404,19 +2188,14 @@ void IMLRegisterAllocator_AllocateRegisters(ppcImlGenContext_t* ppcImlGenContext ctx.deprGenContext = ppcImlGenContext; IMLRA_ReshapeForRegisterAllocation(ppcImlGenContext); - ppcImlGenContext->UpdateSegmentIndices(); // update momentaryIndex of each segment - - ppcImlGenContext->raInfo.list_ranges = std::vector(); - ctx.perSegmentAbstractRanges.resize(ppcImlGenContext->segmentList2.size()); - IMLRA_CalculateLivenessRanges(ctx); IMLRA_ProcessFlowAndCalculateLivenessRanges(ctx); IMLRA_AssignRegisters(ctx, ppcImlGenContext); - + DbgVerifyAllRanges(ctx); IMLRA_AnalyzeRangeDataFlow(ppcImlGenContext); IMLRA_GenerateMoveInstructions(ctx); - PPCRecRA_deleteAllRanges(ppcImlGenContext); + IMLRA_DeleteAllRanges(ppcImlGenContext); } diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.h b/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.h index 52b20397..0a54e4cb 100644 --- a/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.h +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.h @@ -1,6 +1,7 @@ +#pragma once // container for storing a set of register indices -// specifically optimized towards storing physical register indices (expected to be below 64) +// specifically optimized towards storing typical range of physical register indices (expected to be below 64) class IMLPhysRegisterSet { public: @@ -16,9 +17,19 @@ public: m_regBitmask &= ~((uint64)1 << index); } + void SetAllAvailable() + { + m_regBitmask = ~0ull; + } + + bool HasAllAvailable() const + { + return m_regBitmask == ~0ull; + } + bool IsAvailable(uint32 index) const { - return (m_regBitmask & (1 << index)) != 0; + return (m_regBitmask & ((uint64)1 << index)) != 0; } IMLPhysRegisterSet& operator&=(const IMLPhysRegisterSet& other) @@ -33,16 +44,26 @@ public: return *this; } + void RemoveRegisters(const IMLPhysRegisterSet& other) + { + this->m_regBitmask &= ~other.m_regBitmask; + } + bool HasAnyAvailable() const { return m_regBitmask != 0; } + bool HasExactlyOneAvailable() const + { + return m_regBitmask != 0 && (m_regBitmask & (m_regBitmask - 1)) == 0; + } + // returns index of first available register. Do not call when HasAnyAvailable() == false - uint32 GetFirstAvailableReg() + IMLPhysReg GetFirstAvailableReg() { cemu_assert_debug(m_regBitmask != 0); - uint32 regIndex = 0; + sint32 regIndex = 0; auto tmp = m_regBitmask; while ((tmp & 0xFF) == 0) { @@ -59,7 +80,7 @@ public: // returns index of next available register (search includes any register index >= startIndex) // returns -1 if there is no more register - sint32 GetNextAvailableReg(sint32 startIndex) + IMLPhysReg GetNextAvailableReg(sint32 startIndex) const { if (startIndex >= 64) return -1; @@ -81,11 +102,15 @@ public: return regIndex; } + sint32 CountAvailableRegs() const + { + return std::popcount(m_regBitmask); + } + private: uint64 m_regBitmask{ 0 }; }; - struct IMLRegisterAllocatorParameters { inline IMLPhysRegisterSet& GetPhysRegPool(IMLRegFormat regFormat) diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.cpp b/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.cpp index f722e7ca..583d5905 100644 --- a/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.cpp @@ -3,45 +3,169 @@ #include "IMLRegisterAllocatorRanges.h" #include "util/helpers/MemoryPool.h" -void PPCRecRARange_addLink_perVirtualGPR(std::unordered_map& root, raLivenessSubrange_t* subrange) +uint32 IMLRA_GetNextIterationIndex(); + +IMLRegID raLivenessRange::GetVirtualRegister() const { - IMLRegID regId = subrange->range->virtualRegister; + return virtualRegister; +} + +sint32 raLivenessRange::GetPhysicalRegister() const +{ + return physicalRegister; +} + +IMLName raLivenessRange::GetName() const +{ + return name; +} + +void raLivenessRange::SetPhysicalRegister(IMLPhysReg physicalRegister) +{ + this->physicalRegister = physicalRegister; +} + +void raLivenessRange::SetPhysicalRegisterForCluster(IMLPhysReg physicalRegister) +{ + auto clusterRanges = GetAllSubrangesInCluster(); + for(auto& range : clusterRanges) + range->physicalRegister = physicalRegister; +} + +boost::container::small_vector raLivenessRange::GetAllSubrangesInCluster() +{ + uint32 iterationIndex = IMLRA_GetNextIterationIndex(); + boost::container::small_vector subranges; + subranges.push_back(this); + this->lastIterationIndex = iterationIndex; + size_t i = 0; + while(isubrangeBranchTaken && cur->subrangeBranchTaken->lastIterationIndex != iterationIndex) + { + cur->subrangeBranchTaken->lastIterationIndex = iterationIndex; + subranges.push_back(cur->subrangeBranchTaken); + } + if(cur->subrangeBranchNotTaken && cur->subrangeBranchNotTaken->lastIterationIndex != iterationIndex) + { + cur->subrangeBranchNotTaken->lastIterationIndex = iterationIndex; + subranges.push_back(cur->subrangeBranchNotTaken); + } + // check predecessors + for(auto& prev : cur->previousRanges) + { + if(prev->lastIterationIndex != iterationIndex) + { + prev->lastIterationIndex = iterationIndex; + subranges.push_back(prev); + } + } + } + return subranges; +} + +void raLivenessRange::GetAllowedRegistersExRecursive(raLivenessRange* range, uint32 iterationIndex, IMLPhysRegisterSet& allowedRegs) +{ + range->lastIterationIndex = iterationIndex; + for (auto& it : range->list_fixedRegRequirements) + allowedRegs &= it.allowedReg; + // check successors + if (range->subrangeBranchTaken && range->subrangeBranchTaken->lastIterationIndex != iterationIndex) + GetAllowedRegistersExRecursive(range->subrangeBranchTaken, iterationIndex, allowedRegs); + if (range->subrangeBranchNotTaken && range->subrangeBranchNotTaken->lastIterationIndex != iterationIndex) + GetAllowedRegistersExRecursive(range->subrangeBranchNotTaken, iterationIndex, allowedRegs); + // check predecessors + for (auto& prev : range->previousRanges) + { + if (prev->lastIterationIndex != iterationIndex) + GetAllowedRegistersExRecursive(prev, iterationIndex, allowedRegs); + } +}; + +bool raLivenessRange::GetAllowedRegistersEx(IMLPhysRegisterSet& allowedRegisters) +{ + uint32 iterationIndex = IMLRA_GetNextIterationIndex(); + allowedRegisters.SetAllAvailable(); + GetAllowedRegistersExRecursive(this, iterationIndex, allowedRegisters); + return !allowedRegisters.HasAllAvailable(); +} + +IMLPhysRegisterSet raLivenessRange::GetAllowedRegisters(IMLPhysRegisterSet regPool) +{ + IMLPhysRegisterSet fixedRegRequirements = regPool; + if(interval.ExtendsPreviousSegment() || interval.ExtendsIntoNextSegment()) + { + auto clusterRanges = GetAllSubrangesInCluster(); + for(auto& subrange : clusterRanges) + { + for(auto& fixedRegLoc : subrange->list_fixedRegRequirements) + fixedRegRequirements &= fixedRegLoc.allowedReg; + } + return fixedRegRequirements; + } + for(auto& fixedRegLoc : list_fixedRegRequirements) + fixedRegRequirements &= fixedRegLoc.allowedReg; + return fixedRegRequirements; +} + +void PPCRecRARange_addLink_perVirtualGPR(std::unordered_map& root, raLivenessRange* subrange) +{ + IMLRegID regId = subrange->GetVirtualRegister(); auto it = root.find(regId); if (it == root.end()) { // new single element root.try_emplace(regId, subrange); - subrange->link_sameVirtualRegisterGPR.prev = nullptr; - subrange->link_sameVirtualRegisterGPR.next = nullptr; + subrange->link_sameVirtualRegister.prev = nullptr; + subrange->link_sameVirtualRegister.next = nullptr; } else { // insert in first position - subrange->link_sameVirtualRegisterGPR.next = it->second; + raLivenessRange* priorFirst = it->second; + subrange->link_sameVirtualRegister.next = priorFirst; it->second = subrange; - subrange->link_sameVirtualRegisterGPR.prev = subrange; + subrange->link_sameVirtualRegister.prev = nullptr; + priorFirst->link_sameVirtualRegister.prev = subrange; } } -void PPCRecRARange_addLink_allSubrangesGPR(raLivenessSubrange_t** root, raLivenessSubrange_t* subrange) +void PPCRecRARange_addLink_allSegmentRanges(raLivenessRange** root, raLivenessRange* subrange) { - subrange->link_segmentSubrangesGPR.next = *root; + subrange->link_allSegmentRanges.next = *root; if (*root) - (*root)->link_segmentSubrangesGPR.prev = subrange; - subrange->link_segmentSubrangesGPR.prev = nullptr; + (*root)->link_allSegmentRanges.prev = subrange; + subrange->link_allSegmentRanges.prev = nullptr; *root = subrange; } -void PPCRecRARange_removeLink_perVirtualGPR(std::unordered_map& root, raLivenessSubrange_t* subrange) +void PPCRecRARange_removeLink_perVirtualGPR(std::unordered_map& root, raLivenessRange* subrange) { - IMLRegID regId = subrange->range->virtualRegister; - raLivenessSubrange_t* nextRange = subrange->link_sameVirtualRegisterGPR.next; - raLivenessSubrange_t* prevRange = subrange->link_sameVirtualRegisterGPR.prev; - raLivenessSubrange_t* newBase = prevRange ? prevRange : nextRange; +#ifdef CEMU_DEBUG_ASSERT + raLivenessRange* cur = root.find(subrange->GetVirtualRegister())->second; + bool hasRangeFound = false; + while(cur) + { + if(cur == subrange) + { + hasRangeFound = true; + break; + } + cur = cur->link_sameVirtualRegister.next; + } + cemu_assert_debug(hasRangeFound); +#endif + IMLRegID regId = subrange->GetVirtualRegister(); + raLivenessRange* nextRange = subrange->link_sameVirtualRegister.next; + raLivenessRange* prevRange = subrange->link_sameVirtualRegister.prev; + raLivenessRange* newBase = prevRange ? prevRange : nextRange; if (prevRange) - prevRange->link_sameVirtualRegisterGPR.next = subrange->link_sameVirtualRegisterGPR.next; + prevRange->link_sameVirtualRegister.next = subrange->link_sameVirtualRegister.next; if (nextRange) - nextRange->link_sameVirtualRegisterGPR.prev = subrange->link_sameVirtualRegisterGPR.prev; + nextRange->link_sameVirtualRegister.prev = subrange->link_sameVirtualRegister.prev; if (!prevRange) { @@ -51,376 +175,461 @@ void PPCRecRARange_removeLink_perVirtualGPR(std::unordered_mapsecond == subrange); root.erase(regId); } } #ifdef CEMU_DEBUG_ASSERT - subrange->link_sameVirtualRegisterGPR.prev = (raLivenessSubrange_t*)1; - subrange->link_sameVirtualRegisterGPR.next = (raLivenessSubrange_t*)1; + subrange->link_sameVirtualRegister.prev = (raLivenessRange*)1; + subrange->link_sameVirtualRegister.next = (raLivenessRange*)1; #endif } -void PPCRecRARange_removeLink_allSubrangesGPR(raLivenessSubrange_t** root, raLivenessSubrange_t* subrange) +void PPCRecRARange_removeLink_allSegmentRanges(raLivenessRange** root, raLivenessRange* subrange) { - raLivenessSubrange_t* tempPrev = subrange->link_segmentSubrangesGPR.prev; - if (subrange->link_segmentSubrangesGPR.prev) - subrange->link_segmentSubrangesGPR.prev->link_segmentSubrangesGPR.next = subrange->link_segmentSubrangesGPR.next; + raLivenessRange* tempPrev = subrange->link_allSegmentRanges.prev; + if (subrange->link_allSegmentRanges.prev) + subrange->link_allSegmentRanges.prev->link_allSegmentRanges.next = subrange->link_allSegmentRanges.next; else - (*root) = subrange->link_segmentSubrangesGPR.next; - if (subrange->link_segmentSubrangesGPR.next) - subrange->link_segmentSubrangesGPR.next->link_segmentSubrangesGPR.prev = tempPrev; + (*root) = subrange->link_allSegmentRanges.next; + if (subrange->link_allSegmentRanges.next) + subrange->link_allSegmentRanges.next->link_allSegmentRanges.prev = tempPrev; #ifdef CEMU_DEBUG_ASSERT - subrange->link_segmentSubrangesGPR.prev = (raLivenessSubrange_t*)1; - subrange->link_segmentSubrangesGPR.next = (raLivenessSubrange_t*)1; + subrange->link_allSegmentRanges.prev = (raLivenessRange*)1; + subrange->link_allSegmentRanges.next = (raLivenessRange*)1; #endif } -MemoryPoolPermanentObjects memPool_livenessRange(4096); -MemoryPoolPermanentObjects memPool_livenessSubrange(4096); +MemoryPoolPermanentObjects memPool_livenessSubrange(4096); -raLivenessRange_t* PPCRecRA_createRangeBase(ppcImlGenContext_t* ppcImlGenContext, uint32 virtualRegister, uint32 name) +// startPosition and endPosition are inclusive +raLivenessRange* IMLRA_CreateRange(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment, IMLRegID virtualRegister, IMLName name, raInstructionEdge startPosition, raInstructionEdge endPosition) { - raLivenessRange_t* livenessRange = memPool_livenessRange.acquireObj(); - livenessRange->list_subranges.resize(0); - livenessRange->virtualRegister = virtualRegister; - livenessRange->name = name; - livenessRange->physicalRegister = -1; - ppcImlGenContext->raInfo.list_ranges.push_back(livenessRange); - return livenessRange; -} + raLivenessRange* range = memPool_livenessSubrange.acquireObj(); + range->previousRanges.clear(); + range->list_accessLocations.clear(); + range->list_fixedRegRequirements.clear(); + range->imlSegment = imlSegment; -raLivenessSubrange_t* PPCRecRA_createSubrange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range, IMLSegment* imlSegment, sint32 startIndex, sint32 endIndex) -{ - raLivenessSubrange_t* livenessSubrange = memPool_livenessSubrange.acquireObj(); - livenessSubrange->list_locations.resize(0); - livenessSubrange->range = range; - livenessSubrange->imlSegment = imlSegment; - PPCRecompilerIml_setSegmentPoint(&livenessSubrange->start, imlSegment, startIndex); - PPCRecompilerIml_setSegmentPoint(&livenessSubrange->end, imlSegment, endIndex); + cemu_assert_debug(startPosition <= endPosition); + range->interval.start = startPosition; + range->interval.end = endPosition; + + // register mapping + range->virtualRegister = virtualRegister; + range->name = name; + range->physicalRegister = -1; // default values - livenessSubrange->hasStore = false; - livenessSubrange->hasStoreDelayed = false; - livenessSubrange->lastIterationIndex = 0; - livenessSubrange->subrangeBranchNotTaken = nullptr; - livenessSubrange->subrangeBranchTaken = nullptr; - livenessSubrange->_noLoad = false; - // add to range - range->list_subranges.push_back(livenessSubrange); - // add to segment - PPCRecRARange_addLink_perVirtualGPR(imlSegment->raInfo.linkedList_perVirtualGPR2, livenessSubrange); - PPCRecRARange_addLink_allSubrangesGPR(&imlSegment->raInfo.linkedList_allSubranges, livenessSubrange); - return livenessSubrange; + range->hasStore = false; + range->hasStoreDelayed = false; + range->lastIterationIndex = 0; + range->subrangeBranchNotTaken = nullptr; + range->subrangeBranchTaken = nullptr; + cemu_assert_debug(range->previousRanges.empty()); + range->_noLoad = false; + // add to segment linked lists + PPCRecRARange_addLink_perVirtualGPR(imlSegment->raInfo.linkedList_perVirtualRegister, range); + PPCRecRARange_addLink_allSegmentRanges(&imlSegment->raInfo.linkedList_allSubranges, range); + return range; } -void _unlinkSubrange(raLivenessSubrange_t* subrange) +void _unlinkSubrange(raLivenessRange* range) { - IMLSegment* imlSegment = subrange->imlSegment; - PPCRecRARange_removeLink_perVirtualGPR(imlSegment->raInfo.linkedList_perVirtualGPR2, subrange); - PPCRecRARange_removeLink_allSubrangesGPR(&imlSegment->raInfo.linkedList_allSubranges, subrange); -} - -void PPCRecRA_deleteSubrange(ppcImlGenContext_t* ppcImlGenContext, raLivenessSubrange_t* subrange) -{ - _unlinkSubrange(subrange); - subrange->range->list_subranges.erase(std::find(subrange->range->list_subranges.begin(), subrange->range->list_subranges.end(), subrange)); - subrange->list_locations.clear(); - PPCRecompilerIml_removeSegmentPoint(&subrange->start); - PPCRecompilerIml_removeSegmentPoint(&subrange->end); - memPool_livenessSubrange.releaseObj(subrange); -} - -void _PPCRecRA_deleteSubrangeNoUnlinkFromRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessSubrange_t* subrange) -{ - _unlinkSubrange(subrange); - PPCRecompilerIml_removeSegmentPoint(&subrange->start); - PPCRecompilerIml_removeSegmentPoint(&subrange->end); - memPool_livenessSubrange.releaseObj(subrange); -} - -void PPCRecRA_deleteRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range) -{ - for (auto& subrange : range->list_subranges) + IMLSegment* imlSegment = range->imlSegment; + PPCRecRARange_removeLink_perVirtualGPR(imlSegment->raInfo.linkedList_perVirtualRegister, range); + PPCRecRARange_removeLink_allSegmentRanges(&imlSegment->raInfo.linkedList_allSubranges, range); + // unlink reverse references + if(range->subrangeBranchTaken) + range->subrangeBranchTaken->previousRanges.erase(std::find(range->subrangeBranchTaken->previousRanges.begin(), range->subrangeBranchTaken->previousRanges.end(), range)); + if(range->subrangeBranchNotTaken) + range->subrangeBranchNotTaken->previousRanges.erase(std::find(range->subrangeBranchNotTaken->previousRanges.begin(), range->subrangeBranchNotTaken->previousRanges.end(), range)); + range->subrangeBranchTaken = (raLivenessRange*)(uintptr_t)-1; + range->subrangeBranchNotTaken = (raLivenessRange*)(uintptr_t)-1; + // remove forward references + for(auto& prev : range->previousRanges) { - _PPCRecRA_deleteSubrangeNoUnlinkFromRange(ppcImlGenContext, subrange); + if(prev->subrangeBranchTaken == range) + prev->subrangeBranchTaken = nullptr; + if(prev->subrangeBranchNotTaken == range) + prev->subrangeBranchNotTaken = nullptr; } - ppcImlGenContext->raInfo.list_ranges.erase(std::find(ppcImlGenContext->raInfo.list_ranges.begin(), ppcImlGenContext->raInfo.list_ranges.end(), range)); - memPool_livenessRange.releaseObj(range); + range->previousRanges.clear(); } -void PPCRecRA_deleteRangeNoUnlink(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range) +void IMLRA_DeleteRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* range) { - for (auto& subrange : range->list_subranges) - { - _PPCRecRA_deleteSubrangeNoUnlinkFromRange(ppcImlGenContext, subrange); - } - memPool_livenessRange.releaseObj(range); + _unlinkSubrange(range); + range->list_accessLocations.clear(); + range->list_fixedRegRequirements.clear(); + memPool_livenessSubrange.releaseObj(range); } -void PPCRecRA_deleteAllRanges(ppcImlGenContext_t* ppcImlGenContext) +void IMLRA_DeleteRangeCluster(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* range) { - for(auto& range : ppcImlGenContext->raInfo.list_ranges) - { - PPCRecRA_deleteRangeNoUnlink(ppcImlGenContext, range); - } - ppcImlGenContext->raInfo.list_ranges.clear(); + auto clusterRanges = range->GetAllSubrangesInCluster(); + for (auto& subrange : clusterRanges) + IMLRA_DeleteRange(ppcImlGenContext, subrange); } -void PPCRecRA_mergeRanges(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range, raLivenessRange_t* absorbedRange) +void IMLRA_DeleteAllRanges(ppcImlGenContext_t* ppcImlGenContext) { - cemu_assert_debug(range != absorbedRange); - cemu_assert_debug(range->virtualRegister == absorbedRange->virtualRegister); - // move all subranges from absorbedRange to range - for (auto& subrange : absorbedRange->list_subranges) + for(auto& seg : ppcImlGenContext->segmentList2) { - range->list_subranges.push_back(subrange); - subrange->range = range; + raLivenessRange* cur; + while(cur = seg->raInfo.linkedList_allSubranges) + IMLRA_DeleteRange(ppcImlGenContext, cur); + seg->raInfo.linkedList_allSubranges = nullptr; + seg->raInfo.linkedList_perVirtualRegister.clear(); } - absorbedRange->list_subranges.clear(); - PPCRecRA_deleteRange(ppcImlGenContext, absorbedRange); } -void PPCRecRA_mergeSubranges(ppcImlGenContext_t* ppcImlGenContext, raLivenessSubrange_t* subrange, raLivenessSubrange_t* absorbedSubrange) +void IMLRA_MergeSubranges(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* subrange, raLivenessRange* absorbedSubrange) { #ifdef CEMU_DEBUG_ASSERT PPCRecRA_debugValidateSubrange(subrange); PPCRecRA_debugValidateSubrange(absorbedSubrange); if (subrange->imlSegment != absorbedSubrange->imlSegment) assert_dbg(); - if (subrange->end.index > absorbedSubrange->start.index) - assert_dbg(); + cemu_assert_debug(subrange->interval.end == absorbedSubrange->interval.start); + if (subrange->subrangeBranchTaken || subrange->subrangeBranchNotTaken) assert_dbg(); if (subrange == absorbedSubrange) assert_dbg(); #endif + // update references subrange->subrangeBranchTaken = absorbedSubrange->subrangeBranchTaken; subrange->subrangeBranchNotTaken = absorbedSubrange->subrangeBranchNotTaken; + absorbedSubrange->subrangeBranchTaken = nullptr; + absorbedSubrange->subrangeBranchNotTaken = nullptr; + if(subrange->subrangeBranchTaken) + *std::find(subrange->subrangeBranchTaken->previousRanges.begin(), subrange->subrangeBranchTaken->previousRanges.end(), absorbedSubrange) = subrange; + if(subrange->subrangeBranchNotTaken) + *std::find(subrange->subrangeBranchNotTaken->previousRanges.begin(), subrange->subrangeBranchNotTaken->previousRanges.end(), absorbedSubrange) = subrange; // merge usage locations - for (auto& location : absorbedSubrange->list_locations) + for (auto& accessLoc : absorbedSubrange->list_accessLocations) + subrange->list_accessLocations.push_back(accessLoc); + absorbedSubrange->list_accessLocations.clear(); + // merge fixed reg locations +#ifdef CEMU_DEBUG_ASSERT + if(!subrange->list_fixedRegRequirements.empty() && !absorbedSubrange->list_fixedRegRequirements.empty()) { - subrange->list_locations.push_back(location); + cemu_assert_debug(subrange->list_fixedRegRequirements.back().pos < absorbedSubrange->list_fixedRegRequirements.front().pos); } - absorbedSubrange->list_locations.clear(); +#endif + for (auto& fixedReg : absorbedSubrange->list_fixedRegRequirements) + subrange->list_fixedRegRequirements.push_back(fixedReg); + absorbedSubrange->list_fixedRegRequirements.clear(); - subrange->end.index = absorbedSubrange->end.index; + subrange->interval.end = absorbedSubrange->interval.end; PPCRecRA_debugValidateSubrange(subrange); - PPCRecRA_deleteSubrange(ppcImlGenContext, absorbedSubrange); + IMLRA_DeleteRange(ppcImlGenContext, absorbedSubrange); } -// remove all inter-segment connections from the range and split it into local ranges (also removes empty ranges) -void PPCRecRA_explodeRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range) +// remove all inter-segment connections from the range cluster and split it into local ranges. Ranges are trimmed and if they have no access location they will be removed +void IMLRA_ExplodeRangeCluster(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* originRange) { - if (range->list_subranges.size() == 1) - assert_dbg(); - for (auto& subrange : range->list_subranges) + cemu_assert_debug(originRange->interval.ExtendsPreviousSegment() || originRange->interval.ExtendsIntoNextSegment()); // only call this on ranges that span multiple segments + auto clusterRanges = originRange->GetAllSubrangesInCluster(); + for (auto& subrange : clusterRanges) { - if (subrange->list_locations.empty()) + if (subrange->list_accessLocations.empty()) continue; - raLivenessRange_t* newRange = PPCRecRA_createRangeBase(ppcImlGenContext, range->virtualRegister, range->name); - raLivenessSubrange_t* newSubrange = PPCRecRA_createSubrange(ppcImlGenContext, newRange, subrange->imlSegment, subrange->list_locations.data()[0].index, subrange->list_locations.data()[subrange->list_locations.size() - 1].index + 1); - // copy locations - for (auto& location : subrange->list_locations) + raInterval interval; + interval.SetInterval(subrange->list_accessLocations.front().pos, subrange->list_accessLocations.back().pos); + raLivenessRange* newSubrange = IMLRA_CreateRange(ppcImlGenContext, subrange->imlSegment, subrange->GetVirtualRegister(), subrange->GetName(), interval.start, interval.end); + // copy locations and fixed reg indices + newSubrange->list_accessLocations = subrange->list_accessLocations; + newSubrange->list_fixedRegRequirements = subrange->list_fixedRegRequirements; + if(originRange->HasPhysicalRegister()) { - newSubrange->list_locations.push_back(location); + cemu_assert_debug(subrange->list_fixedRegRequirements.empty()); // avoid unassigning a register from a range with a fixed register requirement + } + // validate + if(!newSubrange->list_accessLocations.empty()) + { + cemu_assert_debug(newSubrange->list_accessLocations.front().pos >= newSubrange->interval.start); + cemu_assert_debug(newSubrange->list_accessLocations.back().pos <= newSubrange->interval.end); + } + if(!newSubrange->list_fixedRegRequirements.empty()) + { + cemu_assert_debug(newSubrange->list_fixedRegRequirements.front().pos >= newSubrange->interval.start); // fixed register requirements outside of the actual access range probably means there is a mistake in GetInstructionFixedRegisters() + cemu_assert_debug(newSubrange->list_fixedRegRequirements.back().pos <= newSubrange->interval.end); } } - // remove original range - PPCRecRA_deleteRange(ppcImlGenContext, range); + // delete the original range cluster + IMLRA_DeleteRangeCluster(ppcImlGenContext, originRange); } #ifdef CEMU_DEBUG_ASSERT -void PPCRecRA_debugValidateSubrange(raLivenessSubrange_t* subrange) +void PPCRecRA_debugValidateSubrange(raLivenessRange* range) { // validate subrange - if (subrange->subrangeBranchTaken && subrange->subrangeBranchTaken->imlSegment != subrange->imlSegment->nextSegmentBranchTaken) + if (range->subrangeBranchTaken && range->subrangeBranchTaken->imlSegment != range->imlSegment->nextSegmentBranchTaken) assert_dbg(); - if (subrange->subrangeBranchNotTaken && subrange->subrangeBranchNotTaken->imlSegment != subrange->imlSegment->nextSegmentBranchNotTaken) + if (range->subrangeBranchNotTaken && range->subrangeBranchNotTaken->imlSegment != range->imlSegment->nextSegmentBranchNotTaken) assert_dbg(); + + if(range->subrangeBranchTaken || range->subrangeBranchNotTaken) + { + cemu_assert_debug(range->interval.end.ConnectsToNextSegment()); + } + if(!range->previousRanges.empty()) + { + cemu_assert_debug(range->interval.start.ConnectsToPreviousSegment()); + } + // validate locations + if (!range->list_accessLocations.empty()) + { + cemu_assert_debug(range->list_accessLocations.front().pos >= range->interval.start); + cemu_assert_debug(range->list_accessLocations.back().pos <= range->interval.end); + } + // validate fixed reg requirements + if (!range->list_fixedRegRequirements.empty()) + { + cemu_assert_debug(range->list_fixedRegRequirements.front().pos >= range->interval.start); + cemu_assert_debug(range->list_fixedRegRequirements.back().pos <= range->interval.end); + for(sint32 i = 0; i < (sint32)range->list_fixedRegRequirements.size()-1; i++) + cemu_assert_debug(range->list_fixedRegRequirements[i].pos < range->list_fixedRegRequirements[i+1].pos); + } + } #else -void PPCRecRA_debugValidateSubrange(raLivenessSubrange_t* subrange) {} +void PPCRecRA_debugValidateSubrange(raLivenessRange* range) {} #endif -// split subrange at the given index -// After the split there will be two ranges and subranges: +// trim start and end of range to match first and last read/write locations +// does not trim start/endpoints which extend into the next/previous segment +void IMLRA_TrimRangeToUse(raLivenessRange* range) +{ + if(range->list_accessLocations.empty()) + { + // special case where we trim ranges extending from other segments to a single instruction edge + cemu_assert_debug(!range->interval.start.IsInstructionIndex() || !range->interval.end.IsInstructionIndex()); + if(range->interval.start.IsInstructionIndex()) + range->interval.start = range->interval.end; + if(range->interval.end.IsInstructionIndex()) + range->interval.end = range->interval.start; + return; + } + // trim start and end + raInterval prevInterval = range->interval; + if(range->interval.start.IsInstructionIndex()) + range->interval.start = range->list_accessLocations.front().pos; + if(range->interval.end.IsInstructionIndex()) + range->interval.end = range->list_accessLocations.back().pos; + // extra checks +#ifdef CEMU_DEBUG_ASSERT + cemu_assert_debug(range->interval.start <= range->interval.end); + for(auto& loc : range->list_accessLocations) + { + cemu_assert_debug(range->interval.ContainsEdge(loc.pos)); + } + cemu_assert_debug(prevInterval.ContainsWholeInterval(range->interval)); +#endif +} + +// split range at the given position +// After the split there will be two ranges: // head -> subrange is shortened to end at splitIndex (exclusive) // tail -> a new subrange that ranges from splitIndex (inclusive) to the end of the original subrange // if head has a physical register assigned it will not carry over to tail -// The return value is the tail subrange -// If trimToHole is true, the end of the head subrange and the start of the tail subrange will be moved to fit the locations -// Ranges that begin at RA_INTER_RANGE_START are allowed and can be split -raLivenessSubrange_t* PPCRecRA_splitLocalSubrange(ppcImlGenContext_t* ppcImlGenContext, raLivenessSubrange_t* subrange, sint32 splitIndex, bool trimToHole) +// The return value is the tail range +// If trimToUsage is true, the end of the head subrange and the start of the tail subrange will be shrunk to fit the read/write locations within. If there are no locations then the range will be deleted +raLivenessRange* IMLRA_SplitRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange*& subrange, raInstructionEdge splitPosition, bool trimToUsage) { - // validation -#ifdef CEMU_DEBUG_ASSERT - //if (subrange->end.index == RA_INTER_RANGE_END || subrange->end.index == RA_INTER_RANGE_START) - // assert_dbg(); - if (subrange->start.index == RA_INTER_RANGE_END || subrange->end.index == RA_INTER_RANGE_START) - assert_dbg(); - if (subrange->start.index >= splitIndex) - assert_dbg(); - if (subrange->end.index <= splitIndex) - assert_dbg(); -#endif + cemu_assert_debug(splitPosition.IsInstructionIndex()); + cemu_assert_debug(!subrange->interval.IsNextSegmentOnly() && !subrange->interval.IsPreviousSegmentOnly()); + cemu_assert_debug(subrange->interval.ContainsEdge(splitPosition)); + // determine new intervals + raInterval headInterval, tailInterval; + headInterval.SetInterval(subrange->interval.start, splitPosition-1); + tailInterval.SetInterval(splitPosition, subrange->interval.end); + cemu_assert_debug(headInterval.start <= headInterval.end); + cemu_assert_debug(tailInterval.start <= tailInterval.end); // create tail - raLivenessRange_t* tailRange = PPCRecRA_createRangeBase(ppcImlGenContext, subrange->range->virtualRegister, subrange->range->name); - raLivenessSubrange_t* tailSubrange = PPCRecRA_createSubrange(ppcImlGenContext, tailRange, subrange->imlSegment, splitIndex, subrange->end.index); - // copy locations - for (auto& location : subrange->list_locations) + raLivenessRange* tailSubrange = IMLRA_CreateRange(ppcImlGenContext, subrange->imlSegment, subrange->GetVirtualRegister(), subrange->GetName(), tailInterval.start, tailInterval.end); + tailSubrange->SetPhysicalRegister(subrange->GetPhysicalRegister()); + // carry over branch targets and update reverse references + tailSubrange->subrangeBranchTaken = subrange->subrangeBranchTaken; + tailSubrange->subrangeBranchNotTaken = subrange->subrangeBranchNotTaken; + subrange->subrangeBranchTaken = nullptr; + subrange->subrangeBranchNotTaken = nullptr; + if(tailSubrange->subrangeBranchTaken) + *std::find(tailSubrange->subrangeBranchTaken->previousRanges.begin(), tailSubrange->subrangeBranchTaken->previousRanges.end(), subrange) = tailSubrange; + if(tailSubrange->subrangeBranchNotTaken) + *std::find(tailSubrange->subrangeBranchNotTaken->previousRanges.begin(), tailSubrange->subrangeBranchNotTaken->previousRanges.end(), subrange) = tailSubrange; + // we assume that list_locations is ordered by instruction index and contains no duplicate indices, so lets check that here just in case +#ifdef CEMU_DEBUG_ASSERT + if(subrange->list_accessLocations.size() > 1) { - if (location.index >= splitIndex) - tailSubrange->list_locations.push_back(location); - } - // remove tail locations from head - for (sint32 i = 0; i < subrange->list_locations.size(); i++) - { - raLivenessLocation_t* location = subrange->list_locations.data() + i; - if (location->index >= splitIndex) + for(size_t i=0; ilist_accessLocations.size()-1; i++) { - subrange->list_locations.resize(i); + cemu_assert_debug(subrange->list_accessLocations[i].pos < subrange->list_accessLocations[i+1].pos); + } + } +#endif + // split locations + auto it = std::lower_bound( + subrange->list_accessLocations.begin(), subrange->list_accessLocations.end(), splitPosition, + [](const raAccessLocation& accessLoc, raInstructionEdge value) { return accessLoc.pos < value; } + ); + size_t originalCount = subrange->list_accessLocations.size(); + tailSubrange->list_accessLocations.insert(tailSubrange->list_accessLocations.end(), it, subrange->list_accessLocations.end()); + subrange->list_accessLocations.erase(it, subrange->list_accessLocations.end()); + cemu_assert_debug(subrange->list_accessLocations.empty() || subrange->list_accessLocations.back().pos < splitPosition); + cemu_assert_debug(tailSubrange->list_accessLocations.empty() || tailSubrange->list_accessLocations.front().pos >= splitPosition); + cemu_assert_debug(subrange->list_accessLocations.size() + tailSubrange->list_accessLocations.size() == originalCount); + // split fixed reg requirements + for (sint32 i = 0; i < subrange->list_fixedRegRequirements.size(); i++) + { + raFixedRegRequirement* fixedReg = subrange->list_fixedRegRequirements.data() + i; + if (tailInterval.ContainsEdge(fixedReg->pos)) + { + tailSubrange->list_fixedRegRequirements.push_back(*fixedReg); + } + } + // remove tail fixed reg requirements from head + for (sint32 i = 0; i < subrange->list_fixedRegRequirements.size(); i++) + { + raFixedRegRequirement* fixedReg = subrange->list_fixedRegRequirements.data() + i; + if (!headInterval.ContainsEdge(fixedReg->pos)) + { + subrange->list_fixedRegRequirements.resize(i); break; } } - // adjust start/end - if (trimToHole) + // adjust intervals + subrange->interval = headInterval; + tailSubrange->interval = tailInterval; + // trim to hole + if(trimToUsage) { - if (subrange->list_locations.empty()) + if(subrange->list_accessLocations.empty() && (subrange->interval.start.IsInstructionIndex() && subrange->interval.end.IsInstructionIndex())) { - subrange->end.index = subrange->start.index+1; + IMLRA_DeleteRange(ppcImlGenContext, subrange); + subrange = nullptr; } else { - subrange->end.index = subrange->list_locations.back().index + 1; + IMLRA_TrimRangeToUse(subrange); } - if (tailSubrange->list_locations.empty()) + if(tailSubrange->list_accessLocations.empty() && (tailSubrange->interval.start.IsInstructionIndex() && tailSubrange->interval.end.IsInstructionIndex())) { - assert_dbg(); // should not happen? (In this case we can just avoid generating a tail at all) + IMLRA_DeleteRange(ppcImlGenContext, tailSubrange); + tailSubrange = nullptr; } else { - tailSubrange->start.index = tailSubrange->list_locations.front().index; + IMLRA_TrimRangeToUse(tailSubrange); } } - else - { - // set head range to end at split index - subrange->end.index = splitIndex; - } + // validation + cemu_assert_debug(!subrange || subrange->interval.start <= subrange->interval.end); + cemu_assert_debug(!tailSubrange || tailSubrange->interval.start <= tailSubrange->interval.end); + cemu_assert_debug(!tailSubrange || tailSubrange->interval.start >= splitPosition); + if (!trimToUsage) + cemu_assert_debug(!tailSubrange || tailSubrange->interval.start == splitPosition); + + if(subrange) + PPCRecRA_debugValidateSubrange(subrange); + if(tailSubrange) + PPCRecRA_debugValidateSubrange(tailSubrange); return tailSubrange; } -void PPCRecRA_updateOrAddSubrangeLocation(raLivenessSubrange_t* subrange, sint32 index, bool isRead, bool isWrite) -{ - if (subrange->list_locations.empty()) - { - subrange->list_locations.emplace_back(index, isRead, isWrite); - return; - } - raLivenessLocation_t* lastLocation = subrange->list_locations.data() + (subrange->list_locations.size() - 1); - cemu_assert_debug(lastLocation->index <= index); - if (lastLocation->index == index) - { - // update - lastLocation->isRead = lastLocation->isRead || isRead; - lastLocation->isWrite = lastLocation->isWrite || isWrite; - return; - } - // add new - subrange->list_locations.emplace_back(index, isRead, isWrite); -} - -sint32 PPCRecRARange_getReadWriteCost(IMLSegment* imlSegment) +sint32 IMLRA_GetSegmentReadWriteCost(IMLSegment* imlSegment) { sint32 v = imlSegment->loopDepth + 1; v *= 5; return v*v; // 25, 100, 225, 400 } -// calculate cost of entire range -// ignores data flow and does not detect avoidable reads/stores -sint32 PPCRecRARange_estimateCost(raLivenessRange_t* range) +// calculate additional cost of range that it would have after calling _ExplodeRange() on it +sint32 IMLRA_CalculateAdditionalCostOfRangeExplode(raLivenessRange* subrange) { - sint32 cost = 0; - - // todo - this algorithm isn't accurate. If we have 10 parallel branches with a load each then the actual cost is still only that of one branch (plus minimal extra cost for generating more code). - - // currently we calculate the cost based on the most expensive entry/exit point - - sint32 mostExpensiveRead = 0; - sint32 mostExpensiveWrite = 0; - sint32 readCount = 0; - sint32 writeCount = 0; - - for (auto& subrange : range->list_subranges) + auto ranges = subrange->GetAllSubrangesInCluster(); + sint32 cost = 0;//-PPCRecRARange_estimateTotalCost(ranges); + for (auto& subrange : ranges) { - if (subrange->start.index != RA_INTER_RANGE_START) + if (subrange->list_accessLocations.empty()) + continue; // this range would be deleted and thus has no cost + sint32 segmentLoadStoreCost = IMLRA_GetSegmentReadWriteCost(subrange->imlSegment); + bool hasAdditionalLoad = subrange->interval.ExtendsPreviousSegment(); + bool hasAdditionalStore = subrange->interval.ExtendsIntoNextSegment(); + if(hasAdditionalLoad && subrange->list_accessLocations.front().IsWrite()) // if written before read then a load isn't necessary { - //cost += PPCRecRARange_getReadWriteCost(subrange->imlSegment); - mostExpensiveRead = std::max(mostExpensiveRead, PPCRecRARange_getReadWriteCost(subrange->imlSegment)); - readCount++; + cemu_assert_debug(!subrange->list_accessLocations.front().IsRead()); + cost += segmentLoadStoreCost; } - if (subrange->end.index != RA_INTER_RANGE_END) + if(hasAdditionalStore) { - //cost += PPCRecRARange_getReadWriteCost(subrange->imlSegment); - mostExpensiveWrite = std::max(mostExpensiveWrite, PPCRecRARange_getReadWriteCost(subrange->imlSegment)); - writeCount++; + bool hasWrite = std::find_if(subrange->list_accessLocations.begin(), subrange->list_accessLocations.end(), [](const raAccessLocation& loc) { return loc.IsWrite(); }) != subrange->list_accessLocations.end(); + if(!hasWrite) // ranges which don't modify their value do not need to be stored + cost += segmentLoadStoreCost; } } - cost = mostExpensiveRead + mostExpensiveWrite; - cost = cost + (readCount + writeCount) / 10; + // todo - properly calculating all the data-flow dependency based costs is more complex so this currently is an approximation return cost; } -// calculate cost of range that it would have after calling PPCRecRA_explodeRange() on it -sint32 PPCRecRARange_estimateAdditionalCostAfterRangeExplode(raLivenessRange_t* range) -{ - sint32 cost = -PPCRecRARange_estimateCost(range); - for (auto& subrange : range->list_subranges) - { - if (subrange->list_locations.empty()) - continue; - cost += PPCRecRARange_getReadWriteCost(subrange->imlSegment) * 2; // we assume a read and a store - } - return cost; -} - -sint32 PPCRecRARange_estimateAdditionalCostAfterSplit(raLivenessSubrange_t* subrange, sint32 splitIndex) +sint32 IMLRA_CalculateAdditionalCostAfterSplit(raLivenessRange* subrange, raInstructionEdge splitPosition) { // validation #ifdef CEMU_DEBUG_ASSERT - if (subrange->end.index == RA_INTER_RANGE_END) + if (subrange->interval.ExtendsIntoNextSegment()) assert_dbg(); #endif + cemu_assert_debug(splitPosition.IsInstructionIndex()); sint32 cost = 0; // find split position in location list - if (subrange->list_locations.empty()) + if (subrange->list_accessLocations.empty()) + return 0; + if (splitPosition <= subrange->list_accessLocations.front().pos) + return 0; + if (splitPosition > subrange->list_accessLocations.back().pos) + return 0; + + size_t firstTailLocationIndex = 0; + for (size_t i = 0; i < subrange->list_accessLocations.size(); i++) { - assert_dbg(); // should not happen? - return 0; + if (subrange->list_accessLocations[i].pos >= splitPosition) + { + firstTailLocationIndex = i; + break; + } } - if (splitIndex <= subrange->list_locations.front().index) - return 0; - if (splitIndex > subrange->list_locations.back().index) - return 0; + std::span headLocations{subrange->list_accessLocations.data(), firstTailLocationIndex}; + std::span tailLocations{subrange->list_accessLocations.data() + firstTailLocationIndex, subrange->list_accessLocations.size() - firstTailLocationIndex}; + cemu_assert_debug(headLocations.empty() || headLocations.back().pos < splitPosition); + cemu_assert_debug(tailLocations.empty() || tailLocations.front().pos >= splitPosition); - // todo - determine exact cost of split subranges + sint32 segmentLoadStoreCost = IMLRA_GetSegmentReadWriteCost(subrange->imlSegment); - cost += PPCRecRARange_getReadWriteCost(subrange->imlSegment) * 2; // currently we assume that the additional region will require a read and a store + auto CalculateCostFromLocationRange = [segmentLoadStoreCost](std::span locations, bool trackLoadCost = true, bool trackStoreCost = true) -> sint32 + { + if(locations.empty()) + return 0; + sint32 cost = 0; + if(locations.front().IsRead() && trackLoadCost) + cost += segmentLoadStoreCost; // not overwritten, so there is a load cost + bool hasWrite = std::find_if(locations.begin(), locations.end(), [](const raAccessLocation& loc) { return loc.IsWrite(); }) != locations.end(); + if(hasWrite && trackStoreCost) + cost += segmentLoadStoreCost; // modified, so there is a store cost + return cost; + }; - //for (sint32 f = 0; f < subrange->list_locations.size(); f++) - //{ - // raLivenessLocation_t* location = subrange->list_locations.data() + f; - // if (location->index >= splitIndex) - // { - // ... - // return cost; - // } - //} + sint32 baseCost = CalculateCostFromLocationRange(subrange->list_accessLocations); + + bool tailOverwritesValue = !tailLocations.empty() && !tailLocations.front().IsRead() && tailLocations.front().IsWrite(); + + sint32 newCost = CalculateCostFromLocationRange(headLocations) + CalculateCostFromLocationRange(tailLocations, !tailOverwritesValue, true); + cemu_assert_debug(newCost >= baseCost); + cost = newCost - baseCost; return cost; -} - +} \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.h b/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.h index 28fbe906..b0685cc5 100644 --- a/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.h +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.h @@ -1,27 +1,364 @@ #pragma once +#include "IMLRegisterAllocator.h" -raLivenessRange_t* PPCRecRA_createRangeBase(ppcImlGenContext_t* ppcImlGenContext, uint32 virtualRegister, uint32 name); -raLivenessSubrange_t* PPCRecRA_createSubrange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range, IMLSegment* imlSegment, sint32 startIndex, sint32 endIndex); -void PPCRecRA_deleteSubrange(ppcImlGenContext_t* ppcImlGenContext, raLivenessSubrange_t* subrange); -void PPCRecRA_deleteRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range); -void PPCRecRA_deleteAllRanges(ppcImlGenContext_t* ppcImlGenContext); +struct raLivenessSubrangeLink +{ + struct raLivenessRange* prev; + struct raLivenessRange* next; +}; -void PPCRecRA_mergeRanges(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range, raLivenessRange_t* absorbedRange); -void PPCRecRA_explodeRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range); +struct raInstructionEdge +{ + friend struct raInterval; +public: + raInstructionEdge() + { + index = 0; + } -void PPCRecRA_mergeSubranges(ppcImlGenContext_t* ppcImlGenContext, raLivenessSubrange_t* subrange, raLivenessSubrange_t* absorbedSubrange); + raInstructionEdge(sint32 instructionIndex, bool isInputEdge) + { + Set(instructionIndex, isInputEdge); + } -raLivenessSubrange_t* PPCRecRA_splitLocalSubrange(ppcImlGenContext_t* ppcImlGenContext, raLivenessSubrange_t* subrange, sint32 splitIndex, bool trimToHole = false); + void Set(sint32 instructionIndex, bool isInputEdge) + { + if(instructionIndex == RA_INTER_RANGE_START || instructionIndex == RA_INTER_RANGE_END) + { + index = instructionIndex; + return; + } + index = instructionIndex * 2 + (isInputEdge ? 0 : 1); + cemu_assert_debug(index >= 0 && index < 0x100000*2); // make sure index value is sane + } -void PPCRecRA_updateOrAddSubrangeLocation(raLivenessSubrange_t* subrange, sint32 index, bool isRead, bool isWrite); -void PPCRecRA_debugValidateSubrange(raLivenessSubrange_t* subrange); + void SetRaw(sint32 index) + { + this->index = index; + cemu_assert_debug(index == RA_INTER_RANGE_START || index == RA_INTER_RANGE_END || (index >= 0 && index < 0x100000*2)); // make sure index value is sane + } + + // sint32 GetRaw() + // { + // this->index = index; + // } + + std::string GetDebugString() + { + if(index == RA_INTER_RANGE_START) + return "RA_START"; + else if(index == RA_INTER_RANGE_END) + return "RA_END"; + std::string str = fmt::format("{}", GetInstructionIndex()); + if(IsOnInputEdge()) + str += "i"; + else if(IsOnOutputEdge()) + str += "o"; + return str; + } + + sint32 GetInstructionIndex() const + { + cemu_assert_debug(index != RA_INTER_RANGE_START && index != RA_INTER_RANGE_END); + return index >> 1; + } + + // returns instruction index or RA_INTER_RANGE_START/RA_INTER_RANGE_END + sint32 GetInstructionIndexEx() const + { + if(index == RA_INTER_RANGE_START || index == RA_INTER_RANGE_END) + return index; + return index >> 1; + } + + sint32 GetRaw() const + { + return index; + } + + bool IsOnInputEdge() const + { + cemu_assert_debug(index != RA_INTER_RANGE_START && index != RA_INTER_RANGE_END); + return (index&1) == 0; + } + + bool IsOnOutputEdge() const + { + cemu_assert_debug(index != RA_INTER_RANGE_START && index != RA_INTER_RANGE_END); + return (index&1) != 0; + } + + bool ConnectsToPreviousSegment() const + { + return index == RA_INTER_RANGE_START; + } + + bool ConnectsToNextSegment() const + { + return index == RA_INTER_RANGE_END; + } + + bool IsInstructionIndex() const + { + return index != RA_INTER_RANGE_START && index != RA_INTER_RANGE_END; + } + + // comparison operators + bool operator>(const raInstructionEdge& other) const + { + return index > other.index; + } + bool operator<(const raInstructionEdge& other) const + { + return index < other.index; + } + bool operator<=(const raInstructionEdge& other) const + { + return index <= other.index; + } + bool operator>=(const raInstructionEdge& other) const + { + return index >= other.index; + } + bool operator==(const raInstructionEdge& other) const + { + return index == other.index; + } + + raInstructionEdge operator+(sint32 offset) const + { + cemu_assert_debug(IsInstructionIndex()); + cemu_assert_debug(offset >= 0 && offset < RA_INTER_RANGE_END); + raInstructionEdge edge; + edge.index = index + offset; + return edge; + } + + raInstructionEdge operator-(sint32 offset) const + { + cemu_assert_debug(IsInstructionIndex()); + cemu_assert_debug(offset >= 0 && offset < RA_INTER_RANGE_END); + raInstructionEdge edge; + edge.index = index - offset; + return edge; + } + + raInstructionEdge& operator++() + { + cemu_assert_debug(IsInstructionIndex()); + index++; + return *this; + } + +private: + sint32 index; // can also be RA_INTER_RANGE_START or RA_INTER_RANGE_END, otherwise contains instruction index * 2 + +}; + +struct raAccessLocation +{ + raAccessLocation(raInstructionEdge pos) : pos(pos) {} + + bool IsRead() const + { + return pos.IsOnInputEdge(); + } + + bool IsWrite() const + { + return pos.IsOnOutputEdge(); + } + + raInstructionEdge pos; +}; + +struct raInterval +{ + raInterval() + { + + } + + raInterval(raInstructionEdge start, raInstructionEdge end) + { + SetInterval(start, end); + } + + // isStartOnInput = Input+Output edge on first instruction. If false then only output + // isEndOnOutput = Input+Output edge on last instruction. If false then only input + void SetInterval(sint32 start, bool isStartOnInput, sint32 end, bool isEndOnOutput) + { + this->start.Set(start, isStartOnInput); + this->end.Set(end, !isEndOnOutput); + } + + void SetInterval(raInstructionEdge start, raInstructionEdge end) + { + cemu_assert_debug(start <= end); + this->start = start; + this->end = end; + } + + void SetStart(const raInstructionEdge& edge) + { + start = edge; + } + + void SetEnd(const raInstructionEdge& edge) + { + end = edge; + } + + sint32 GetStartIndex() const + { + return start.GetInstructionIndex(); + } + + sint32 GetEndIndex() const + { + return end.GetInstructionIndex(); + } + + bool ExtendsPreviousSegment() const + { + return start.ConnectsToPreviousSegment(); + } + + bool ExtendsIntoNextSegment() const + { + return end.ConnectsToNextSegment(); + } + + bool IsNextSegmentOnly() const + { + return start.ConnectsToNextSegment() && end.ConnectsToNextSegment(); + } + + bool IsPreviousSegmentOnly() const + { + return start.ConnectsToPreviousSegment() && end.ConnectsToPreviousSegment(); + } + + // returns true if range is contained within a single segment + bool IsLocal() const + { + return start.GetRaw() > RA_INTER_RANGE_START && end.GetRaw() < RA_INTER_RANGE_END; + } + + bool ContainsInstructionIndex(sint32 instructionIndex) const + { + cemu_assert_debug(instructionIndex != RA_INTER_RANGE_START && instructionIndex != RA_INTER_RANGE_END); + return instructionIndex >= start.GetInstructionIndexEx() && instructionIndex <= end.GetInstructionIndexEx(); + } + + // similar to ContainsInstructionIndex, but allows RA_INTER_RANGE_START/END as input + bool ContainsInstructionIndexEx(sint32 instructionIndex) const + { + if(instructionIndex == RA_INTER_RANGE_START) + return start.ConnectsToPreviousSegment(); + if(instructionIndex == RA_INTER_RANGE_END) + return end.ConnectsToNextSegment(); + return instructionIndex >= start.GetInstructionIndexEx() && instructionIndex <= end.GetInstructionIndexEx(); + } + + bool ContainsEdge(const raInstructionEdge& edge) const + { + return edge >= start && edge <= end; + } + + bool ContainsWholeInterval(const raInterval& other) const + { + return other.start >= start && other.end <= end; + } + + bool IsOverlapping(const raInterval& other) const + { + return start <= other.end && end >= other.start; + } + + sint32 GetPreciseDistance() + { + cemu_assert_debug(!start.ConnectsToNextSegment()); // how to handle this? + if(start == end) + return 1; + cemu_assert_debug(!end.ConnectsToPreviousSegment() && !end.ConnectsToNextSegment()); + if(start.ConnectsToPreviousSegment()) + return end.GetRaw() + 1; + + return end.GetRaw() - start.GetRaw() + 1; // +1 because end is inclusive + } + +//private: not making these directly accessible only forces us to create loads of verbose getters and setters + raInstructionEdge start; + raInstructionEdge end; +}; + +struct raFixedRegRequirement +{ + raInstructionEdge pos; + IMLPhysRegisterSet allowedReg; +}; + +struct raLivenessRange +{ + IMLSegment* imlSegment; + raInterval interval; + + // dirty state tracking + bool _noLoad; + bool hasStore; + bool hasStoreDelayed; + // next + raLivenessRange* subrangeBranchTaken; + raLivenessRange* subrangeBranchNotTaken; + // reverse counterpart of BranchTaken/BranchNotTaken + boost::container::small_vector previousRanges; + // processing + uint32 lastIterationIndex; + // instruction read/write locations + std::vector list_accessLocations; + // ordered list of all raInstructionEdge indices which require a fixed register + std::vector list_fixedRegRequirements; + // linked list (subranges with same GPR virtual register) + raLivenessSubrangeLink link_sameVirtualRegister; + // linked list (all subranges for this segment) + raLivenessSubrangeLink link_allSegmentRanges; + // register info + IMLRegID virtualRegister; + IMLName name; + // register allocator result + IMLPhysReg physicalRegister; + + boost::container::small_vector GetAllSubrangesInCluster(); + bool GetAllowedRegistersEx(IMLPhysRegisterSet& allowedRegisters); // if the cluster has fixed register requirements in any instruction this returns the combined register mask. Otherwise returns false in which case allowedRegisters is left undefined + IMLPhysRegisterSet GetAllowedRegisters(IMLPhysRegisterSet regPool); // return regPool with fixed register requirements filtered out + + IMLRegID GetVirtualRegister() const; + sint32 GetPhysicalRegister() const; + bool HasPhysicalRegister() const { return physicalRegister >= 0; } + IMLName GetName() const; + void SetPhysicalRegister(IMLPhysReg physicalRegister); + void SetPhysicalRegisterForCluster(IMLPhysReg physicalRegister); + void UnsetPhysicalRegister() { physicalRegister = -1; } + + private: + void GetAllowedRegistersExRecursive(raLivenessRange* range, uint32 iterationIndex, IMLPhysRegisterSet& allowedRegs); +}; + +raLivenessRange* IMLRA_CreateRange(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment, IMLRegID virtualRegister, IMLName name, raInstructionEdge startPosition, raInstructionEdge endPosition); +void IMLRA_DeleteRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* subrange); +void IMLRA_DeleteAllRanges(ppcImlGenContext_t* ppcImlGenContext); + +void IMLRA_ExplodeRangeCluster(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* originRange); + +void IMLRA_MergeSubranges(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* subrange, raLivenessRange* absorbedSubrange); + +raLivenessRange* IMLRA_SplitRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange*& subrange, raInstructionEdge splitPosition, bool trimToUsage = false); + +void PPCRecRA_debugValidateSubrange(raLivenessRange* subrange); // cost estimation -sint32 PPCRecRARange_getReadWriteCost(IMLSegment* imlSegment); -sint32 PPCRecRARange_estimateCost(raLivenessRange_t* range); -sint32 PPCRecRARange_estimateAdditionalCostAfterRangeExplode(raLivenessRange_t* range); -sint32 PPCRecRARange_estimateAdditionalCostAfterSplit(raLivenessSubrange_t* subrange, sint32 splitIndex); - -// special values to mark the index of ranges that reach across the segment border -#define RA_INTER_RANGE_START (-1) -#define RA_INTER_RANGE_END (0x70000000) +sint32 IMLRA_GetSegmentReadWriteCost(IMLSegment* imlSegment); +sint32 IMLRA_CalculateAdditionalCostOfRangeExplode(raLivenessRange* subrange); +//sint32 PPCRecRARange_estimateAdditionalCostAfterSplit(raLivenessRange* subrange, sint32 splitIndex); +sint32 IMLRA_CalculateAdditionalCostAfterSplit(raLivenessRange* subrange, raInstructionEdge splitPosition); \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLSegment.h b/src/Cafe/HW/Espresso/Recompiler/IML/IMLSegment.h index bf1868cf..10e3dc06 100644 --- a/src/Cafe/HW/Espresso/Recompiler/IML/IMLSegment.h +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLSegment.h @@ -1,61 +1,123 @@ #pragma once #include "IMLInstruction.h" +#include + +// special values to mark the index of ranges that reach across the segment border +#define RA_INTER_RANGE_START (-1) +#define RA_INTER_RANGE_END (0x70000000) + struct IMLSegmentPoint { + friend struct IMLSegmentInterval; + sint32 index; - struct IMLSegment* imlSegment; + struct IMLSegment* imlSegment; // do we really need to track this? SegmentPoints are always accessed via the segment that they are part of IMLSegmentPoint* next; IMLSegmentPoint* prev; + + // the index is the instruction index times two. + // this gives us the ability to cover half an instruction with RA ranges + // covering only the first half of an instruction (0-0) means that the register is read, but not preserved + // covering first and the second half means the register is read and preserved + // covering only the second half means the register is written but not read + + sint32 GetInstructionIndex() const + { + return index; + } + + void SetInstructionIndex(sint32 index) + { + this->index = index; + } + + void ShiftIfAfter(sint32 instructionIndex, sint32 shiftCount) + { + if (!IsPreviousSegment() && !IsNextSegment()) + { + if (GetInstructionIndex() >= instructionIndex) + index += shiftCount; + } + } + + void DecrementByOneInstruction() + { + index--; + } + + // the segment point can point beyond the first and last instruction which indicates that it is an infinite range reaching up to the previous or next segment + bool IsPreviousSegment() const { return index == RA_INTER_RANGE_START; } + bool IsNextSegment() const { return index == RA_INTER_RANGE_END; } + + // overload operand > and < + bool operator>(const IMLSegmentPoint& other) const { return index > other.index; } + bool operator<(const IMLSegmentPoint& other) const { return index < other.index; } + bool operator==(const IMLSegmentPoint& other) const { return index == other.index; } + bool operator!=(const IMLSegmentPoint& other) const { return index != other.index; } + + // overload comparison operands for sint32 + bool operator>(const sint32 other) const { return index > other; } + bool operator<(const sint32 other) const { return index < other; } + bool operator<=(const sint32 other) const { return index <= other; } + bool operator>=(const sint32 other) const { return index >= other; } }; -struct raLivenessLocation_t +struct IMLSegmentInterval { - sint32 index; - bool isRead; - bool isWrite; - - raLivenessLocation_t() = default; - - raLivenessLocation_t(sint32 index, bool isRead, bool isWrite) - : index(index), isRead(isRead), isWrite(isWrite) {}; -}; - -struct raLivenessSubrangeLink_t -{ - struct raLivenessSubrange_t* prev; - struct raLivenessSubrange_t* next; -}; - -struct raLivenessSubrange_t -{ - struct raLivenessRange_t* range; - IMLSegment* imlSegment; IMLSegmentPoint start; IMLSegmentPoint end; - // dirty state tracking - bool _noLoad; - bool hasStore; - bool hasStoreDelayed; - // next - raLivenessSubrange_t* subrangeBranchTaken; - raLivenessSubrange_t* subrangeBranchNotTaken; - // processing - uint32 lastIterationIndex; - // instruction locations - std::vector list_locations; - // linked list (subranges with same GPR virtual register) - raLivenessSubrangeLink_t link_sameVirtualRegisterGPR; - // linked list (all subranges for this segment) - raLivenessSubrangeLink_t link_segmentSubrangesGPR; -}; -struct raLivenessRange_t -{ - IMLRegID virtualRegister; - sint32 physicalRegister; - IMLName name; - std::vector list_subranges; + bool ContainsInstructionIndex(sint32 offset) const { return start <= offset && end > offset; } + + bool IsRangeOverlapping(const IMLSegmentInterval& other) + { + // todo - compare the raw index + sint32 r1start = this->start.GetInstructionIndex(); + sint32 r1end = this->end.GetInstructionIndex(); + sint32 r2start = other.start.GetInstructionIndex(); + sint32 r2end = other.end.GetInstructionIndex(); + if (r1start < r2end && r1end > r2start) + return true; + if (this->start.IsPreviousSegment() && r1start == r2start) + return true; + if (this->end.IsNextSegment() && r1end == r2end) + return true; + return false; + } + + bool ExtendsIntoPreviousSegment() const + { + return start.IsPreviousSegment(); + } + + bool ExtendsIntoNextSegment() const + { + return end.IsNextSegment(); + } + + bool IsNextSegmentOnly() const + { + if(!start.IsNextSegment()) + return false; + cemu_assert_debug(end.IsNextSegment()); + return true; + } + + bool IsPreviousSegmentOnly() const + { + if (!end.IsPreviousSegment()) + return false; + cemu_assert_debug(start.IsPreviousSegment()); + return true; + } + + sint32 GetDistance() const + { + // todo - assert if either start or end is outside the segment + // we may also want to switch this to raw indices? + return end.GetInstructionIndex() - start.GetInstructionIndex(); + } }; struct PPCSegmentRegisterAllocatorInfo_t @@ -64,8 +126,8 @@ struct PPCSegmentRegisterAllocatorInfo_t bool isPartOfProcessedLoop{}; sint32 lastIterationIndex{}; // linked lists - raLivenessSubrange_t* linkedList_allSubranges{}; - std::unordered_map linkedList_perVirtualGPR2; + struct raLivenessRange* linkedList_allSubranges{}; + std::unordered_map linkedList_perVirtualRegister; }; struct IMLSegment @@ -81,6 +143,10 @@ struct IMLSegment IMLSegment* nextSegmentBranchTaken{}; bool nextSegmentIsUncertain{}; std::vector list_prevSegments{}; + // source for overwrite analysis (if nextSegmentIsUncertain is true) + // sometimes a segment is marked as an exit point, but for the purposes of dead code elimination we know the next segment + IMLSegment* deadCodeEliminationHintSeg{}; + std::vector list_deadCodeHintBy{}; // enterable segments bool isEnterable{}; // this segment can be entered from outside the recompiler (no preloaded registers necessary) uint32 enterPPCAddress{}; // used if isEnterable is true @@ -101,6 +167,14 @@ struct IMLSegment return nextSegmentBranchNotTaken; } + void SetNextSegmentForOverwriteHints(IMLSegment* seg) + { + cemu_assert_debug(!deadCodeEliminationHintSeg); + deadCodeEliminationHintSeg = seg; + if (seg) + seg->list_deadCodeHintBy.push_back(this); + } + // instruction API IMLInstruction* AppendInstruction(); diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp index 1c097873..6e9e317a 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp @@ -16,10 +16,13 @@ #include "IML/IML.h" #include "IML/IMLRegisterAllocator.h" #include "BackendX64/BackendX64.h" - -#if defined(__aarch64__) +#ifdef __aarch64__ #include "BackendAArch64/BackendAArch64.h" #endif +#include "util/highresolutiontimer/HighResolutionTimer.h" + +#define PPCREC_FORCE_SYNCHRONOUS_COMPILATION 0 // if 1, then function recompilation will block and execute on the thread that called PPCRecompiler_visitAddressNoBlock +#define PPCREC_LOG_RECOMPILATION_RESULTS 0 struct PPCInvalidationRange { @@ -44,14 +47,36 @@ void ATTR_MS_ABI (*PPCRecompiler_leaveRecompilerCode_unvisited)(); PPCRecompilerInstanceData_t* ppcRecompilerInstanceData; -#if defined(__aarch64__) -std::list> s_aarch64CodeCtxs; +#if PPCREC_FORCE_SYNCHRONOUS_COMPILATION +static std::mutex s_singleRecompilationMutex; #endif bool ppcRecompilerEnabled = false; + +void PPCRecompiler_recompileAtAddress(uint32 address); + // this function does never block and can fail if the recompiler lock cannot be acquired immediately void PPCRecompiler_visitAddressNoBlock(uint32 enterAddress) { +#if PPCREC_FORCE_SYNCHRONOUS_COMPILATION + if (ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4] != PPCRecompiler_leaveRecompilerCode_unvisited) + return; + PPCRecompilerState.recompilerSpinlock.lock(); + if (ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4] != PPCRecompiler_leaveRecompilerCode_unvisited) + { + PPCRecompilerState.recompilerSpinlock.unlock(); + return; + } + ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4] = PPCRecompiler_leaveRecompilerCode_visited; + PPCRecompilerState.recompilerSpinlock.unlock(); + s_singleRecompilationMutex.lock(); + if (ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4] == PPCRecompiler_leaveRecompilerCode_visited) + { + PPCRecompiler_recompileAtAddress(enterAddress); + } + s_singleRecompilationMutex.unlock(); + return; +#endif // quick read-only check without lock if (ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4] != PPCRecompiler_leaveRecompilerCode_unvisited) return; @@ -125,7 +150,6 @@ void PPCRecompiler_attemptEnter(PPCInterpreter_t* hCPU, uint32 enterAddress) return; if (hCPU->remainingCycles <= 0) return; - auto funcPtr = ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4]; if (funcPtr == PPCRecompiler_leaveRecompilerCode_unvisited) { @@ -147,7 +171,6 @@ PPCRecFunction_t* PPCRecompiler_recompileFunction(PPCFunctionBoundaryTracker::PP cemuLog_log(LogType::Force, "Attempting to recompile function outside of allowed code area"); return nullptr; } - uint32 codeGenRangeStart; uint32 codeGenRangeSize = 0; coreinit::OSGetCodegenVirtAddrRangeInternal(codeGenRangeStart, codeGenRangeSize); @@ -166,8 +189,14 @@ PPCRecFunction_t* PPCRecompiler_recompileFunction(PPCFunctionBoundaryTracker::PP ppcRecFunc->ppcAddress = range.startAddress; ppcRecFunc->ppcSize = range.length; +#if PPCREC_LOG_RECOMPILATION_RESULTS + BenchmarkTimer bt; + bt.Start(); +#endif + // generate intermediate code ppcImlGenContext_t ppcImlGenContext = { 0 }; + ppcImlGenContext.debug_entryPPCAddress = range.startAddress; bool compiledSuccessfully = PPCRecompiler_generateIntermediateCode(ppcImlGenContext, ppcRecFunc, entryAddresses, boundaryTracker); if (compiledSuccessfully == false) { @@ -194,38 +223,6 @@ PPCRecFunction_t* PPCRecompiler_recompileFunction(PPCFunctionBoundaryTracker::PP return nullptr; } - //if (ppcRecFunc->ppcAddress == 0x30DF5F8) - //{ - // debug_printf("----------------------------------------\n"); - // IMLDebug_Dump(&ppcImlGenContext); - // __debugbreak(); - //} - - - //if (ppcRecFunc->ppcAddress == 0x11223344) - //{ - // //debug_printf("----------------------------------------\n"); - // //IMLDebug_Dump(&ppcImlGenContext); - // //__debugbreak(); - //} - //else - //{ - // delete ppcRecFunc; - // return nullptr; - //} - - //if (ppcRecFunc->ppcAddress == 0x03C26844) - //{ - // __debugbreak(); - // IMLDebug_Dump(&ppcImlGenContext); - // __debugbreak(); - //} - // 31A8778 - - // Functions for testing (botw): - // 3B4049C (large with switch case) - // 30BF118 (has a bndz copy loop + some float instructions at the end) - #if defined(ARCH_X86_64) // emit x64 code bool x64GenerationSuccess = PPCRecompiler_generateX64Code(ppcRecFunc, &ppcImlGenContext); @@ -234,13 +231,21 @@ PPCRecFunction_t* PPCRecompiler_recompileFunction(PPCFunctionBoundaryTracker::PP return nullptr; } #elif defined(__aarch64__) - auto aarch64CodeCtx = PPCRecompiler_generateAArch64Code(ppcRecFunc, &ppcImlGenContext); - if (aarch64CodeCtx == nullptr) + bool aarch64GenerationSuccess = PPCRecompiler_generateAArch64Code(ppcRecFunc, &ppcImlGenContext); + if (aarch64GenerationSuccess == false) { return nullptr; } - s_aarch64CodeCtxs.push_back(std::move(aarch64CodeCtx)); #endif + if (ActiveSettings::DumpRecompilerFunctionsEnabled()) + { + FileStream* fs = FileStream::createFile2(ActiveSettings::GetUserDataPath(fmt::format("dump/recompiler/ppc_{:08x}.bin", ppcRecFunc->ppcAddress))); + if (fs) + { + fs->writeData(ppcRecFunc->x86Code, ppcRecFunc->x86Size); + delete fs; + } + } // collect list of PPC-->x64 entry points entryPointsOut.clear(); @@ -255,6 +260,17 @@ PPCRecFunction_t* PPCRecompiler_recompileFunction(PPCFunctionBoundaryTracker::PP entryPointsOut.emplace_back(ppcEnterOffset, x64Offset); } +#if PPCREC_LOG_RECOMPILATION_RESULTS + bt.Stop(); + uint32 codeHash = 0; + for (uint32 i = 0; i < ppcRecFunc->x86Size; i++) + { + codeHash = _rotr(codeHash, 3); + codeHash += ((uint8*)ppcRecFunc->x86Code)[i]; + } + cemuLog_log(LogType::Force, "[Recompiler] PPC 0x{:08x} -> x64: 0x{:x} Took {:.4}ms | Size {:04x} CodeHash {:08x}", (uint32)ppcRecFunc->ppcAddress, (uint64)(uintptr_t)ppcRecFunc->x86Code, bt.GetElapsedMilliseconds(), ppcRecFunc->x86Size, codeHash); +#endif + return ppcRecFunc; } @@ -264,6 +280,7 @@ void PPCRecompiler_NativeRegisterAllocatorPass(ppcImlGenContext_t& ppcImlGenCont for (auto& it : ppcImlGenContext.mappedRegs) raParam.regIdToName.try_emplace(it.second.GetRegID(), it.first); + #if defined(ARCH_X86_64) auto& gprPhysPool = raParam.GetPhysRegPool(IMLRegFormat::I64); gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RAX); @@ -298,11 +315,15 @@ void PPCRecompiler_NativeRegisterAllocatorPass(ppcImlGenContext_t& ppcImlGenCont fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 14); #elif defined(__aarch64__) auto& gprPhysPool = raParam.GetPhysRegPool(IMLRegFormat::I64); - for (int i = IMLArchAArch64::PHYSREG_GPR_BASE; i < IMLArchAArch64::PHYSREG_GPR_BASE + IMLArchAArch64::PHYSREG_GPR_COUNT; i++) + for (auto i = IMLArchAArch64::PHYSREG_GPR_BASE; i < IMLArchAArch64::PHYSREG_GPR_BASE + IMLArchAArch64::PHYSREG_GPR_COUNT; i++) + { + if (i == IMLArchAArch64::PHYSREG_GPR_BASE + 18) + continue; // Skip reserved platform register gprPhysPool.SetAvailable(i); + } auto& fprPhysPool = raParam.GetPhysRegPool(IMLRegFormat::F64); - for (int i = IMLArchAArch64::PHYSREG_FPR_BASE; i < IMLArchAArch64::PHYSREG_FPR_BASE + IMLArchAArch64::PHYSREG_FPR_COUNT; i++) + for (auto i = IMLArchAArch64::PHYSREG_FPR_BASE; i < IMLArchAArch64::PHYSREG_FPR_BASE + IMLArchAArch64::PHYSREG_FPR_COUNT; i++) fprPhysPool.SetAvailable(i); #endif @@ -323,10 +344,9 @@ bool PPCRecompiler_ApplyIMLPasses(ppcImlGenContext_t& ppcImlGenContext) // delay byte swapping for certain load+store patterns IMLOptimizer_OptimizeDirectIntegerCopies(&ppcImlGenContext); - PPCRecompiler_NativeRegisterAllocatorPass(ppcImlGenContext); + IMLOptimizer_StandardOptimizationPass(ppcImlGenContext); - //PPCRecompiler_reorderConditionModifyInstructions(&ppcImlGenContext); - //PPCRecompiler_removeRedundantCRUpdates(&ppcImlGenContext); + PPCRecompiler_NativeRegisterAllocatorPass(ppcImlGenContext); return true; } @@ -437,6 +457,10 @@ std::atomic_bool s_recompilerThreadStopSignal{false}; void PPCRecompiler_thread() { SetThreadName("PPCRecompiler"); +#if PPCREC_FORCE_SYNCHRONOUS_COMPILATION + return; +#endif + while (true) { if(s_recompilerThreadStopSignal) @@ -490,6 +514,7 @@ void PPCRecompiler_reserveLookupTableBlock(uint32 offset) if (ppcRecompiler_reservedBlockMask[blockIndex]) return; ppcRecompiler_reservedBlockMask[blockIndex] = true; + void* p1 = MemMapper::AllocateMemory(&(ppcRecompilerInstanceData->ppcRecompilerFuncTable[offset/4]), (PPC_REC_ALLOC_BLOCK_SIZE/4)*sizeof(void*), MemMapper::PAGE_PERMISSION::P_RW, true); void* p3 = MemMapper::AllocateMemory(&(ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[offset/4]), (PPC_REC_ALLOC_BLOCK_SIZE/4)*sizeof(void*), MemMapper::PAGE_PERMISSION::P_RW, true); if( !p1 || !p3 ) @@ -682,9 +707,9 @@ void PPCRecompiler_init() debug_printf("Allocating %dMB for recompiler instance data...\n", (sint32)(sizeof(PPCRecompilerInstanceData_t) / 1024 / 1024)); ppcRecompilerInstanceData = (PPCRecompilerInstanceData_t*)MemMapper::ReserveMemory(nullptr, sizeof(PPCRecompilerInstanceData_t), MemMapper::PAGE_PERMISSION::P_RW); MemMapper::AllocateMemory(&(ppcRecompilerInstanceData->_x64XMM_xorNegateMaskBottom), sizeof(PPCRecompilerInstanceData_t) - offsetof(PPCRecompilerInstanceData_t, _x64XMM_xorNegateMaskBottom), MemMapper::PAGE_PERMISSION::P_RW, true); -#if defined(ARCH_X86_64) +#ifdef ARCH_X86_64 PPCRecompilerX64Gen_generateRecompilerInterfaceFunctions(); -#else +#elif defined(__aarch64__) PPCRecompilerAArch64Gen_generateRecompilerInterfaceFunctions(); #endif PPCRecompiler_allocateRange(0, 0x1000); // the first entry is used for fallback to interpreter @@ -766,7 +791,4 @@ void PPCRecompiler_Shutdown() // mark as unmapped ppcRecompiler_reservedBlockMask[i] = false; } -#if defined(__aarch64__) - s_aarch64CodeCtxs.clear(); -#endif -} \ No newline at end of file +} diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h index 189fbe8d..706855d4 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h @@ -1,7 +1,5 @@ #pragma once -#include -#include #define PPC_REC_CODE_AREA_START (0x00000000) // lower bound of executable memory area. Recompiler expects this address to be 0 #define PPC_REC_CODE_AREA_END (0x10000000) // upper bound of executable memory area #define PPC_REC_CODE_AREA_SIZE (PPC_REC_CODE_AREA_END - PPC_REC_CODE_AREA_START) @@ -43,27 +41,26 @@ struct ppcImlGenContext_t bool PSE{ true }; // cycle counter uint32 cyclesSinceLastBranch; // used to track ppc cycles - // temporary general purpose registers - //uint32 mappedRegister[PPC_REC_MAX_VIRTUAL_GPR]; - // temporary floating point registers (single and double precision) - //uint32 mappedFPRRegister[256]; - std::unordered_map mappedRegs; + uint32 GetMaxRegId() const + { + if (mappedRegs.empty()) + return 0; + return mappedRegs.size()-1; + } + // list of segments std::vector segmentList2; // code generation control bool hasFPUInstruction; // if true, PPCEnter macro will create FP_UNAVAIL checks -> Not needed in user mode - // register allocator info - struct - { - std::vector list_ranges; - }raInfo; // analysis info struct { bool modifiesGQR[8]; }tracking; + // debug helpers + uint32 debug_entryPPCAddress{0}; ~ppcImlGenContext_t() { diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGen.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGen.cpp index 6c8374d4..a705baf8 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGen.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGen.cpp @@ -6,6 +6,7 @@ #include "IML/IML.h" #include "IML/IMLRegisterAllocatorRanges.h" #include "PPCFunctionBoundaryTracker.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext); @@ -53,23 +54,6 @@ IMLInstruction* PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext return &inst; } -void PPCRecompilerImlGen_generateNewInstruction_conditional_r_s32(ppcImlGenContext_t* ppcImlGenContext, IMLInstruction* imlInstruction, uint32 operation, IMLReg registerIndex, sint32 immS32, uint32 crRegisterIndex, uint32 crBitIndex, bool bitMustBeSet) -{ - if(imlInstruction == NULL) - imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - else - memset(imlInstruction, 0, sizeof(IMLInstruction)); - imlInstruction->type = PPCREC_IML_TYPE_CONDITIONAL_R_S32; - imlInstruction->operation = operation; - // r_s32 operation - imlInstruction->op_conditional_r_s32.regR = registerIndex; - imlInstruction->op_conditional_r_s32.immS32 = immS32; - // condition - imlInstruction->op_conditional_r_s32.crRegisterIndex = crRegisterIndex; - imlInstruction->op_conditional_r_s32.crBitIndex = crBitIndex; - imlInstruction->op_conditional_r_s32.bitMustBeSet = bitMustBeSet; -} - void PPCRecompilerImlGen_generateNewInstruction_r_memory_indexed(ppcImlGenContext_t* ppcImlGenContext, IMLReg registerDestination, IMLReg registerMemory1, IMLReg registerMemory2, uint32 copyWidth, bool signExtend, bool switchEndian) { cemu_assert_debug(registerMemory1.IsValid()); @@ -398,27 +382,47 @@ bool PPCRecompilerImlGen_MFSPR(ppcImlGenContext_t* ppcImlGenContext, uint32 opco return true; } +ATTR_MS_ABI uint32 PPCRecompiler_GetTBL() +{ + return (uint32)coreinit::OSGetSystemTime(); +} + +ATTR_MS_ABI uint32 PPCRecompiler_GetTBU() +{ + return (uint32)(coreinit::OSGetSystemTime() >> 32); +} + bool PPCRecompilerImlGen_MFTB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { - printf("PPCRecompilerImlGen_MFTB(): Not supported\n"); - return false; - uint32 rD, spr1, spr2, spr; PPC_OPC_TEMPL_XO(opcode, rD, spr1, spr2); spr = spr1 | (spr2<<5); - if (spr == 268 || spr == 269) + if( spr == SPR_TBL || spr == SPR_TBU ) { - // TBL / TBU - uint32 param2 = spr | (rD << 16); - ppcImlGenContext->emitInst().make_macro(PPCREC_IML_MACRO_MFTB, ppcImlGenContext->ppcAddressOfCurrentInstruction, param2, 0, IMLREG_INVALID); - IMLSegment* middleSeg = PPCIMLGen_CreateSplitSegmentAtEnd(*ppcImlGenContext, *ppcImlGenContext->currentBasicBlock); - + IMLReg resultReg = _GetRegGPR(ppcImlGenContext, rD); + ppcImlGenContext->emitInst().make_call_imm(spr == SPR_TBL ? (uintptr_t)PPCRecompiler_GetTBL : (uintptr_t)PPCRecompiler_GetTBU, IMLREG_INVALID, IMLREG_INVALID, IMLREG_INVALID, resultReg); return true; } return false; } +void PPCRecompilerImlGen_MCRF(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + uint32 crD, crS, b; + PPC_OPC_TEMPL_X(opcode, crD, crS, b); + cemu_assert_debug((crD&3) == 0); + cemu_assert_debug((crS&3) == 0); + crD >>= 2; + crS >>= 2; + for (sint32 i = 0; i<4; i++) + { + IMLReg regCrSrcBit = _GetRegCR(ppcImlGenContext, crS * 4 + i); + IMLReg regCrDstBit = _GetRegCR(ppcImlGenContext, crD * 4 + i); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regCrDstBit, regCrSrcBit); + } +} + bool PPCRecompilerImlGen_MFCR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rD, rA, rB; @@ -521,7 +525,6 @@ bool PPCRecompilerImlGen_B(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { // function call ppcImlGenContext->emitInst().make_macro(PPCREC_IML_MACRO_BL, ppcImlGenContext->ppcAddressOfCurrentInstruction, jumpAddressDest, ppcImlGenContext->cyclesSinceLastBranch, IMLREG_INVALID); - //cemuLog_log(LogType::Force, "Inline func 0x{:08x} at {:08x}", jumpAddressDest, ppcImlGenContext->ppcAddressOfCurrentInstruction); return true; } // is jump destination within recompiled function? @@ -539,7 +542,6 @@ bool PPCRecompilerImlGen_BC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) uint32 BO, BI, BD; PPC_OPC_TEMPL_B(opcode, BO, BI, BD); - // decodeOp_BC(uint32 opcode, uint32& BD, BOField& BO, uint32& BI, bool& AA, bool& LK) Espresso::BOField boField(BO); uint32 crRegister = BI/4; @@ -962,12 +964,12 @@ bool PPCRecompilerImlGen_DIVWU(ppcImlGenContext_t* ppcImlGenContext, uint32 opco bool PPCRecompilerImlGen_RLWINM(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { - int rS, rA, SH, MB, ME; + sint32 rS, rA, SH, MB, ME; PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); uint32 mask = ppc_mask(MB, ME); IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); - IMLReg regA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); if( ME == (31-SH) && MB == 0 ) { // SLWI @@ -995,16 +997,22 @@ bool PPCRecompilerImlGen_RLWINM(ppcImlGenContext_t* ppcImlGenContext, uint32 opc bool PPCRecompilerImlGen_RLWIMI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { - int rS, rA, SH, MB, ME; + sint32 rS, rA, SH, MB, ME; PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); - - IMLReg regS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); - IMLReg regA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - // pack RLWIMI parameters into single integer - uint32 vImm = MB|(ME<<8)|(SH<<16); - ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_RLWIMI, regA, regS, (sint32)vImm); + IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); + IMLReg regR = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regTmp = _GetRegTemporary(ppcImlGenContext, 0); + uint32 mask = ppc_mask(MB, ME); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regTmp, regS); + if (SH) + ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_LEFT_ROTATE, regTmp, SH); + if (mask != 0) + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, regR, regR, (sint32)~mask); + if (mask != 0xFFFFFFFF) + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, regTmp, regTmp, (sint32)mask); + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_OR, regR, regR, regTmp); if (opcode & PPC_OPC_RC) - PPCImlGen_UpdateCR0(ppcImlGenContext, regA); + PPCImlGen_UpdateCR0(ppcImlGenContext, regR); return true; } @@ -1196,12 +1204,12 @@ bool PPCRecompilerImlGen_LOAD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod return true; } -bool PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, uint32 bitWidth, bool signExtend, bool isBigEndian, bool updateAddrReg) +void PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, uint32 bitWidth, bool signExtend, bool isBigEndian, bool updateAddrReg) { + // if rA == rD, then the EA wont be stored to rA. We could set updateAddrReg to false in such cases but the end result is the same since the loaded value would overwrite rA sint32 rA, rD, rB; PPC_OPC_TEMPL_X(opcode, rD, rA, rB); - if (updateAddrReg && (rA == 0 || rD == rB)) - return false; // invalid instruction form + updateAddrReg = updateAddrReg && (rA != 0); IMLReg regA = rA != 0 ? _GetRegGPR(ppcImlGenContext, rA) : IMLREG_INVALID; IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); IMLReg regDst = _GetRegGPR(ppcImlGenContext, rD); @@ -1216,7 +1224,6 @@ bool PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext_t* ppcImlGenContext, uint PPCRecompilerImlGen_generateNewInstruction_r_memory_indexed(ppcImlGenContext, regDst, regA, regB, bitWidth, signExtend, isBigEndian); else ppcImlGenContext->emitInst().make_r_memory(regDst, regB, 0, bitWidth, signExtend, isBigEndian); - return true; } bool PPCRecompilerImlGen_STORE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, uint32 bitWidth, bool isBigEndian, bool updateAddrReg) @@ -1483,13 +1490,21 @@ bool PPCRecompilerImlGen_DCBZ(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod rA = (opcode>>16)&0x1F; rB = (opcode>>11)&0x1F; // prepare registers - IMLReg gprRegisterA = rA!=0?PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA):IMLREG_INVALID; - IMLReg gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); - // store - if( rA != 0 ) - ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_DCBZ, gprRegisterA, gprRegisterB); + IMLReg regA = rA!=0?PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA):IMLREG_INVALID; + IMLReg regB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); + // load zero into a temporary register + IMLReg regZero = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); + ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regZero, 0); + // prepare EA and align it to cacheline + IMLReg regMemResEA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 1); + if(rA != 0) + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, regMemResEA, regA, regB); else - ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_DCBZ, gprRegisterB, gprRegisterB); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regMemResEA, regB); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, regMemResEA, regMemResEA, ~31); + // zero out the cacheline + for(sint32 i = 0; i < 32; i += 4) + ppcImlGenContext->emitInst().make_memory_r(regZero, regMemResEA, i, 32, false); return true; } @@ -1747,7 +1762,7 @@ uint32 PPCRecompiler_getPreviousInstruction(ppcImlGenContext_t* ppcImlGenContext void PPCRecompilerIml_setSegmentPoint(IMLSegmentPoint* segmentPoint, IMLSegment* imlSegment, sint32 index) { segmentPoint->imlSegment = imlSegment; - segmentPoint->index = index; + segmentPoint->SetInstructionIndex(index); if (imlSegment->segmentPointList) imlSegment->segmentPointList->prev = segmentPoint; segmentPoint->prev = nullptr; @@ -1767,7 +1782,7 @@ void PPCRecompilerIml_removeSegmentPoint(IMLSegmentPoint* segmentPoint) /* * Insert multiple no-op instructions -* Warning: Can invalidate any previous instruction structs from the same segment +* Warning: Can invalidate any previous instruction pointers from the same segment */ void PPCRecompiler_pushBackIMLInstructions(IMLSegment* imlSegment, sint32 index, sint32 shiftBackCount) { @@ -1789,12 +1804,7 @@ void PPCRecompiler_pushBackIMLInstructions(IMLSegment* imlSegment, sint32 index, IMLSegmentPoint* segmentPoint = imlSegment->segmentPointList; while (segmentPoint) { - if (segmentPoint->index != RA_INTER_RANGE_START && segmentPoint->index != RA_INTER_RANGE_END) - { - if (segmentPoint->index >= index) - segmentPoint->index += shiftBackCount; - } - // next + segmentPoint->ShiftIfAfter(index, shiftBackCount); segmentPoint = segmentPoint->next; } } @@ -2059,6 +2069,9 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) case 19: // opcode category 19 switch (PPC_getBits(opcode, 30, 10)) { + case 0: + PPCRecompilerImlGen_MCRF(ppcImlGenContext, opcode); + break; case 16: // BCLR if (PPCRecompilerImlGen_BCSPR(ppcImlGenContext, opcode, SPR_LR) == false) unsupportedInstructionFound = true; @@ -2160,8 +2173,7 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) unsupportedInstructionFound = true; break; case 23: // LWZX - if (!PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 32, false, true, false)) - unsupportedInstructionFound = true; + PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 32, false, true, false); break; case 24: if (PPCRecompilerImlGen_SLW(ppcImlGenContext, opcode) == false) @@ -2186,8 +2198,7 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) // DBCST - Generates no code break; case 55: // LWZUX - if (!PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 32, false, true, true)) - unsupportedInstructionFound = true; + PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 32, false, true, true); break; case 60: // ANDC if (!PPCRecompilerImlGen_ANDC(ppcImlGenContext, opcode)) @@ -2201,16 +2212,14 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) // DCBF -> No-Op break; case 87: // LBZX - if (!PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 8, false, true, false)) - unsupportedInstructionFound = true; + PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 8, false, true, false); break; case 104: if (PPCRecompilerImlGen_NEG(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 119: // LBZUX - if (!PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 8, false, true, true)) - unsupportedInstructionFound = true; + PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 8, false, true, true); break; case 124: // NOR if (!PPCRecompilerImlGen_OR_NOR(ppcImlGenContext, opcode, true)) @@ -2269,16 +2278,14 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) unsupportedInstructionFound = true; break; case 279: // LHZX - if (!PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 16, false, true, false)) - unsupportedInstructionFound = true; + PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 16, false, true, false); break; case 284: // EQV (alias to NXOR) if (!PPCRecompilerImlGen_XOR(ppcImlGenContext, opcode, true)) unsupportedInstructionFound = true; break; case 311: // LHZUX - if (!PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 16, false, true, true)) - unsupportedInstructionFound = true; + PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 16, false, true, true); break; case 316: // XOR if (!PPCRecompilerImlGen_XOR(ppcImlGenContext, opcode, false)) @@ -2289,16 +2296,14 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) unsupportedInstructionFound = true; break; case 343: // LHAX - if (!PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 16, true, true, false)) - unsupportedInstructionFound = true; + PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 16, true, true, false); break; case 371: if (PPCRecompilerImlGen_MFTB(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 375: // LHAUX - if (!PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 16, true, true, true)) - unsupportedInstructionFound = true; + PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 16, true, true, true); break; case 407: // STHX if (!PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 16, true, false)) @@ -2332,8 +2337,7 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) unsupportedInstructionFound = true; break; case 534: // LWBRX - if (!PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 32, false, false, false)) - unsupportedInstructionFound = true; + PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 32, false, false, false); break; case 535: if (PPCRecompilerImlGen_LFSX(ppcImlGenContext, opcode) == false) @@ -2387,8 +2391,7 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) unsupportedInstructionFound = true; break; case 790: // LHBRX - if (!PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 16, false, false, false)) - unsupportedInstructionFound = true; + PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 16, false, false, false); break; case 792: if (PPCRecompilerImlGen_SRAW(ppcImlGenContext, opcode) == false) @@ -2865,9 +2868,10 @@ bool PPCIMLGen_FillBasicBlock(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBloc { uint32 addressOfCurrentInstruction = (uint32)((uint8*)ppcImlGenContext.currentInstruction - memory_base); ppcImlGenContext.ppcAddressOfCurrentInstruction = addressOfCurrentInstruction; + if (PPCRecompiler_decodePPCInstruction(&ppcImlGenContext)) { - debug_printf("Recompiler encountered unsupported instruction at 0x%08x\n", addressOfCurrentInstruction); + cemuLog_logDebug(LogType::Force, "PPCRecompiler: Unsupported instruction at 0x{:08x}", addressOfCurrentInstruction); ppcImlGenContext.currentOutputSegment = nullptr; return false; } @@ -2914,6 +2918,30 @@ void PPCIMLGen_AssertIfNotLastSegmentInstruction(ppcImlGenContext_t& ppcImlGenCo cemu_assert_debug(ppcImlGenContext.currentBasicBlock->lastAddress == ppcImlGenContext.ppcAddressOfCurrentInstruction); } +bool PPCRecompiler_IsBasicBlockATightFiniteLoop(IMLSegment* imlSegment, PPCBasicBlockInfo& basicBlockInfo) +{ + // if we detect a finite loop we can skip generating the cycle check + // currently we only check for BDNZ loops since thats reasonably safe to rely on + // however there are other forms of loops that can be classified as finite, + // but detecting those involves analyzing PPC code and we dont have the infrastructure for that (e.g. IML has CheckRegisterUsage but we dont have an equivalent for PPC code) + + // base criteria, must jump to beginning of same segment + if (imlSegment->nextSegmentBranchTaken != imlSegment) + return false; + + uint32 opcode = *(uint32be*)(memory_base + basicBlockInfo.lastAddress); + if (Espresso::GetPrimaryOpcode(opcode) != Espresso::PrimaryOpcode::BC) + return false; + uint32 BO, BI, BD; + PPC_OPC_TEMPL_B(opcode, BO, BI, BD); + Espresso::BOField boField(BO); + if(!boField.conditionIgnore() || boField.branchAlways()) + return false; + if(boField.decrementerIgnore()) + return false; + return true; +} + void PPCRecompiler_HandleCycleCheckCount(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo) { IMLSegment* imlSegment = basicBlockInfo.GetFirstSegmentInChain(); @@ -2922,8 +2950,7 @@ void PPCRecompiler_HandleCycleCheckCount(ppcImlGenContext_t& ppcImlGenContext, P if (basicBlockInfo.branchTarget > basicBlockInfo.startAddress) return; - // exclude non-infinite tight loops - if (IMLAnalyzer_IsTightFiniteLoop(imlSegment)) + if (PPCRecompiler_IsBasicBlockATightFiniteLoop(imlSegment, basicBlockInfo)) return; // make the segment enterable so execution can return after passing a check @@ -2936,13 +2963,16 @@ void PPCRecompiler_HandleCycleCheckCount(ppcImlGenContext_t& ppcImlGenContext, P splitSeg->SetLinkBranchTaken(exitSegment); exitSegment->AppendInstruction()->make_macro(PPCREC_IML_MACRO_LEAVE, basicBlockInfo.startAddress, 0, 0, IMLREG_INVALID); + + cemu_assert_debug(splitSeg->nextSegmentBranchNotTaken); + // let the IML optimizer and RA know that the original segment should be used during analysis for dead code elimination + exitSegment->SetNextSegmentForOverwriteHints(splitSeg->nextSegmentBranchNotTaken); } void PPCRecompiler_SetSegmentsUncertainFlow(ppcImlGenContext_t& ppcImlGenContext) { for (IMLSegment* segIt : ppcImlGenContext.segmentList2) { - bool isLastSegment = segIt == ppcImlGenContext.segmentList2.back(); // handle empty segment if (segIt->imlList.empty()) { @@ -2965,7 +2995,6 @@ void PPCRecompiler_SetSegmentsUncertainFlow(ppcImlGenContext_t& ppcImlGenContext break; case PPCREC_IML_MACRO_DEBUGBREAK: case PPCREC_IML_MACRO_COUNT_CYCLES: - case PPCREC_IML_MACRO_MFTB: break; default: cemu_assert_unimplemented(); @@ -3136,103 +3165,12 @@ bool PPCRecompiler_GenerateIML(ppcImlGenContext_t& ppcImlGenContext, PPCFunction return true; } -void IMLOptimizer_replaceWithConditionalMov(ppcImlGenContext_t& ppcImlGenContext) -{ - // optimization pass - replace segments with conditional MOVs if possible - //for (IMLSegment* segIt : ppcImlGenContext.segmentList2) - //{ - // if (segIt->nextSegmentBranchNotTaken == nullptr || segIt->nextSegmentBranchTaken == nullptr) - // continue; // not a branching segment - // IMLInstruction* lastInstruction = segIt->GetLastInstruction(); - // if (lastInstruction->type != PPCREC_IML_TYPE_CJUMP || lastInstruction->op_conditionalJump.crRegisterIndex != 0) - // continue; - // IMLSegment* conditionalSegment = segIt->nextSegmentBranchNotTaken; - // IMLSegment* finalSegment = segIt->nextSegmentBranchTaken; - // if (segIt->nextSegmentBranchTaken != segIt->nextSegmentBranchNotTaken->nextSegmentBranchNotTaken) - // continue; - // if (segIt->nextSegmentBranchNotTaken->imlList.size() > 4) - // continue; - // if (conditionalSegment->list_prevSegments.size() != 1) - // continue; // the reduced segment must not be the target of any other branch - // if (conditionalSegment->isEnterable) - // continue; - // // check if the segment contains only iml instructions that can be turned into conditional moves (Value assignment, register assignment) - // bool canReduceSegment = true; - // for (sint32 f = 0; f < conditionalSegment->imlList.size(); f++) - // { - // IMLInstruction* imlInstruction = conditionalSegment->imlList.data() + f; - // if (imlInstruction->type == PPCREC_IML_TYPE_R_S32 && imlInstruction->operation == PPCREC_IML_OP_ASSIGN) - // continue; - // // todo: Register to register copy - // canReduceSegment = false; - // break; - // } - - // if (canReduceSegment == false) - // continue; - - // // remove the branch instruction - // uint8 branchCond_crRegisterIndex = lastInstruction->op_conditionalJump.crRegisterIndex; - // uint8 branchCond_crBitIndex = lastInstruction->op_conditionalJump.crBitIndex; - // bool branchCond_bitMustBeSet = lastInstruction->op_conditionalJump.bitMustBeSet; - // lastInstruction->make_no_op(); - - // // append conditional moves based on branch condition - // for (sint32 f = 0; f < conditionalSegment->imlList.size(); f++) - // { - // IMLInstruction* imlInstruction = conditionalSegment->imlList.data() + f; - // if (imlInstruction->type == PPCREC_IML_TYPE_R_S32 && imlInstruction->operation == PPCREC_IML_OP_ASSIGN) - // PPCRecompilerImlGen_generateNewInstruction_conditional_r_s32(&ppcImlGenContext, PPCRecompiler_appendInstruction(segIt), PPCREC_IML_OP_ASSIGN, imlInstruction->op_r_immS32.registerIndex, imlInstruction->op_r_immS32.immS32, branchCond_crRegisterIndex, branchCond_crBitIndex, !branchCond_bitMustBeSet); - // else - // assert_dbg(); - // } - // // update segment links - // // source segment: imlSegment, conditional/removed segment: conditionalSegment, final segment: finalSegment - // IMLSegment_RemoveLink(segIt, conditionalSegment); - // IMLSegment_RemoveLink(segIt, finalSegment); - // IMLSegment_RemoveLink(conditionalSegment, finalSegment); - // IMLSegment_SetLinkBranchNotTaken(segIt, finalSegment); - // // remove all instructions from conditional segment - // conditionalSegment->imlList.clear(); - - // // if possible, merge imlSegment with finalSegment - // if (finalSegment->isEnterable == false && finalSegment->list_prevSegments.size() == 1) - // { - // // todo: Clean this up and move into separate function PPCRecompilerIML_mergeSegments() - // IMLSegment_RemoveLink(segIt, finalSegment); - // if (finalSegment->nextSegmentBranchNotTaken) - // { - // IMLSegment* tempSegment = finalSegment->nextSegmentBranchNotTaken; - // IMLSegment_RemoveLink(finalSegment, tempSegment); - // IMLSegment_SetLinkBranchNotTaken(segIt, tempSegment); - // } - // if (finalSegment->nextSegmentBranchTaken) - // { - // IMLSegment* tempSegment = finalSegment->nextSegmentBranchTaken; - // IMLSegment_RemoveLink(finalSegment, tempSegment); - // IMLSegment_SetLinkBranchTaken(segIt, tempSegment); - // } - // // copy IML instructions - // cemu_assert_debug(segIt != finalSegment); - // for (sint32 f = 0; f < finalSegment->imlList.size(); f++) - // { - // memcpy(PPCRecompiler_appendInstruction(segIt), finalSegment->imlList.data() + f, sizeof(IMLInstruction)); - // } - // finalSegment->imlList.clear(); - // } - - // // todo: If possible, merge with the segment following conditionalSegment (merging is only possible if the segment is not an entry point or has no other jump sources) - //} -} - bool PPCRecompiler_generateIntermediateCode(ppcImlGenContext_t& ppcImlGenContext, PPCRecFunction_t* ppcRecFunc, std::set& entryAddresses, PPCFunctionBoundaryTracker& boundaryTracker) { ppcImlGenContext.boundaryTracker = &boundaryTracker; if (!PPCRecompiler_GenerateIML(ppcImlGenContext, boundaryTracker, entryAddresses)) return false; - // IMLOptimizer_replaceWithConditionalMov(ppcImlGenContext); - // set range // todo - support non-continuous functions for the range tracking? ppcRecRange_t recRange; diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp index ffee73ea..96a7b560 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp @@ -4,6 +4,9 @@ #include "PPCRecompilerIml.h" #include "Cafe/GameProfile/GameProfile.h" +ATTR_MS_ABI double frsqrte_espresso(double input); +ATTR_MS_ABI double fres_espresso(double input); + IMLReg _GetRegCR(ppcImlGenContext_t* ppcImlGenContext, uint8 crReg, uint8 crBit); void PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext_t* ppcImlGenContext, IMLReg registerDestination, IMLReg registerMemory, sint32 immS32, uint32 mode, bool switchEndian, IMLReg registerGQR = IMLREG_INVALID) @@ -1007,9 +1010,12 @@ bool PPCRecompilerImlGen_FRES(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod // load registers IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_BOTTOM_FRES_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterB); + ppcImlGenContext->emitInst().make_call_imm((uintptr_t)fres_espresso, fprRegisterB, IMLREG_INVALID, IMLREG_INVALID, fprRegisterD); // adjust accuracy PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + // copy result to top + if( ppcImlGenContext->PSE ) + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterD); return true; } @@ -1026,9 +1032,7 @@ bool PPCRecompilerImlGen_FRSP(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod } PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL,PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM, fprRegisterD); if( ppcImlGenContext->PSE ) - { PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterD); - } return true; } @@ -1075,7 +1079,7 @@ bool PPCRecompilerImlGen_FRSQRTE(ppcImlGenContext_t* ppcImlGenContext, uint32 op // hCPU->fpr[frD].fpr = 1.0 / sqrt(hCPU->fpr[frB].fpr); IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_BOTTOM_RECIPROCAL_SQRT, fprRegisterD, fprRegisterB); + ppcImlGenContext->emitInst().make_call_imm((uintptr_t)frsqrte_espresso, fprRegisterB, IMLREG_INVALID, IMLREG_INVALID, fprRegisterD); // adjust accuracy PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); return true; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h index 9c7e03f3..7dcd3ebc 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h @@ -221,11 +221,14 @@ public: VKRObjectPipeline(); ~VKRObjectPipeline() override; - void setPipeline(VkPipeline newPipeline); + void SetPipeline(VkPipeline newPipeline); + VkPipeline GetPipeline() const { return m_pipeline; } - VkPipeline pipeline = VK_NULL_HANDLE; - VkDescriptorSetLayout vertexDSL = VK_NULL_HANDLE, pixelDSL = VK_NULL_HANDLE, geometryDSL = VK_NULL_HANDLE; - VkPipelineLayout pipeline_layout = VK_NULL_HANDLE; + VkDescriptorSetLayout m_vertexDSL = VK_NULL_HANDLE, m_pixelDSL = VK_NULL_HANDLE, m_geometryDSL = VK_NULL_HANDLE; + VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE; + +private: + VkPipeline m_pipeline = VK_NULL_HANDLE; }; class VKRObjectDescriptorSet : public VKRDestructibleObject diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp index fd5a5b78..b316b9c5 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp @@ -26,7 +26,6 @@ PipelineInfo::PipelineInfo(uint64 minimalStateHash, uint64 pipelineHash, LatteFe // init VKRObjPipeline m_vkrObjPipeline = new VKRObjectPipeline(); - m_vkrObjPipeline->pipeline = VK_NULL_HANDLE; // track dependency with shaders if (vertexShaderVk) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp index ba094a84..1ea522dc 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp @@ -558,8 +558,8 @@ void PipelineCompiler::InitRasterizerState(const LatteContextRegister& latteRegi rasterizerExt.flags = 0; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; - rasterizer.pNext = &rasterizerExt; rasterizer.rasterizerDiscardEnable = LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_DX_RASTERIZATION_KILL(); + rasterizer.pNext = VulkanRenderer::GetInstance()->m_featureControl.deviceExtensions.depth_clip_enable ? &rasterizerExt : nullptr; // GX2SetSpecialState(0, true) workaround if (!LatteGPUState.contextNew.PA_CL_VTE_CNTL.get_VPORT_X_OFFSET_ENA()) rasterizer.rasterizerDiscardEnable = false; @@ -730,7 +730,7 @@ void PipelineCompiler::InitDescriptorSetLayouts(VulkanRenderer* vkRenderer, Pipe { cemu_assert_debug(descriptorSetLayoutCount == 0); CreateDescriptorSetLayout(vkRenderer, vertexShader, descriptorSetLayout[descriptorSetLayoutCount], vkrPipelineInfo); - vkObjPipeline->vertexDSL = descriptorSetLayout[descriptorSetLayoutCount]; + vkObjPipeline->m_vertexDSL = descriptorSetLayout[descriptorSetLayoutCount]; descriptorSetLayoutCount++; } @@ -738,7 +738,7 @@ void PipelineCompiler::InitDescriptorSetLayouts(VulkanRenderer* vkRenderer, Pipe { cemu_assert_debug(descriptorSetLayoutCount == 1); CreateDescriptorSetLayout(vkRenderer, pixelShader, descriptorSetLayout[descriptorSetLayoutCount], vkrPipelineInfo); - vkObjPipeline->pixelDSL = descriptorSetLayout[descriptorSetLayoutCount]; + vkObjPipeline->m_pixelDSL = descriptorSetLayout[descriptorSetLayoutCount]; descriptorSetLayoutCount++; } else if (geometryShader) @@ -757,7 +757,7 @@ void PipelineCompiler::InitDescriptorSetLayouts(VulkanRenderer* vkRenderer, Pipe { cemu_assert_debug(descriptorSetLayoutCount == 2); CreateDescriptorSetLayout(vkRenderer, geometryShader, descriptorSetLayout[descriptorSetLayoutCount], vkrPipelineInfo); - vkObjPipeline->geometryDSL = descriptorSetLayout[descriptorSetLayoutCount]; + vkObjPipeline->m_geometryDSL = descriptorSetLayout[descriptorSetLayoutCount]; descriptorSetLayoutCount++; } } @@ -918,7 +918,7 @@ bool PipelineCompiler::InitFromCurrentGPUState(PipelineInfo* pipelineInfo, const pipelineLayoutInfo.pPushConstantRanges = nullptr; pipelineLayoutInfo.pushConstantRangeCount = 0; - VkResult result = vkCreatePipelineLayout(vkRenderer->m_logicalDevice, &pipelineLayoutInfo, nullptr, &m_pipeline_layout); + VkResult result = vkCreatePipelineLayout(vkRenderer->m_logicalDevice, &pipelineLayoutInfo, nullptr, &m_pipelineLayout); if (result != VK_SUCCESS) { cemuLog_log(LogType::Force, "Failed to create pipeline layout: {}", result); @@ -936,7 +936,7 @@ bool PipelineCompiler::InitFromCurrentGPUState(PipelineInfo* pipelineInfo, const // ########################################################################################################################################## - pipelineInfo->m_vkrObjPipeline->pipeline_layout = m_pipeline_layout; + pipelineInfo->m_vkrObjPipeline->m_pipelineLayout = m_pipelineLayout; // increment ref counter for vkrObjPipeline and renderpass object to make sure they dont get released while we are using them m_vkrObjPipeline->incRef(); @@ -989,7 +989,7 @@ bool PipelineCompiler::Compile(bool forceCompile, bool isRenderThread, bool show pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pColorBlendState = &colorBlending; - pipelineInfo.layout = m_pipeline_layout; + pipelineInfo.layout = m_pipelineLayout; pipelineInfo.renderPass = m_renderPassObj->m_renderPass; pipelineInfo.pDepthStencilState = &depthStencilState; pipelineInfo.subpass = 0; @@ -1037,7 +1037,7 @@ bool PipelineCompiler::Compile(bool forceCompile, bool isRenderThread, bool show } else if (result == VK_SUCCESS) { - m_vkrObjPipeline->setPipeline(pipeline); + m_vkrObjPipeline->SetPipeline(pipeline); } else { diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h index 304a7b31..7879b932 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h @@ -41,7 +41,7 @@ public: bool InitFromCurrentGPUState(PipelineInfo* pipelineInfo, const LatteContextRegister& latteRegister, VKRObjectRenderPass* renderPassObj); void TrackAsCached(uint64 baseHash, uint64 pipelineStateHash); // stores pipeline to permanent cache if not yet cached. Must be called synchronously from render thread due to dependency on GPU state - VkPipelineLayout m_pipeline_layout; + VkPipelineLayout m_pipelineLayout; VKRObjectRenderPass* m_renderPassObj{}; /* shader stages */ diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 2e26ef53..e216fa03 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -46,7 +46,8 @@ const std::vector kOptionalDeviceExtensions = VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME, VK_KHR_PRESENT_WAIT_EXTENSION_NAME, - VK_KHR_PRESENT_ID_EXTENSION_NAME + VK_KHR_PRESENT_ID_EXTENSION_NAME, + VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME }; const std::vector kRequiredDeviceExtensions = @@ -81,8 +82,6 @@ VKAPI_ATTR VkBool32 VKAPI_CALL DebugUtilsCallback(VkDebugUtilsMessageSeverityFla if (strstr(pCallbackData->pMessage, "Number of currently valid sampler objects is not less than the maximum allowed")) return VK_FALSE; - assert_dbg(); - #endif cemuLog_log(LogType::Force, (char*)pCallbackData->pMessage); @@ -318,7 +317,10 @@ void VulkanRenderer::GetDeviceFeatures() cemuLog_log(LogType::Force, "VK_EXT_custom_border_color not supported. Cannot emulate arbitrary border color"); } } - + if (!m_featureControl.deviceExtensions.depth_clip_enable) + { + cemuLog_log(LogType::Force, "VK_EXT_depth_clip_enable not supported"); + } // get limits m_featureControl.limits.minUniformBufferOffsetAlignment = std::max(prop2.properties.limits.minUniformBufferOffsetAlignment, (VkDeviceSize)4); m_featureControl.limits.nonCoherentAtomSize = std::max(prop2.properties.limits.nonCoherentAtomSize, (VkDeviceSize)4); @@ -1130,10 +1132,13 @@ VkDeviceCreateInfo VulkanRenderer::CreateDeviceCreateInfo(const std::vector need reinit - VkAttachmentDescription colorAttachment = {}; - colorAttachment.format = m_mainSwapchainInfo->m_surfaceFormat.format; - colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; - colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - colorAttachment.initialLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + VkRenderPass prevRenderPass = m_imguiRenderPass; - VkAttachmentReference colorAttachmentRef = {}; - colorAttachmentRef.attachment = 0; - colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - VkSubpassDescription subpass = {}; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorAttachmentRef; + VkAttachmentDescription colorAttachment = {}; + colorAttachment.format = m_mainSwapchainInfo->m_surfaceFormat.format; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - VkRenderPassCreateInfo renderPassInfo = {}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassInfo.attachmentCount = 1; - renderPassInfo.pAttachments = &colorAttachment; - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpass; - const auto result = vkCreateRenderPass(m_logicalDevice, &renderPassInfo, nullptr, &m_imguiRenderPass); - if (result != VK_SUCCESS) - throw VkException(result, "can't create imgui renderpass"); - } + VkAttachmentReference colorAttachmentRef = {}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + VkSubpassDescription subpass = {}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkRenderPassCreateInfo renderPassInfo = {}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + const auto result = vkCreateRenderPass(m_logicalDevice, &renderPassInfo, nullptr, &m_imguiRenderPass); + if (result != VK_SUCCESS) + throw VkException(result, "can't create imgui renderpass"); ImGui_ImplVulkan_InitInfo info{}; info.Instance = m_instance; @@ -1678,6 +1682,9 @@ void VulkanRenderer::ImguiInit() info.ImageCount = info.MinImageCount; ImGui_ImplVulkan_Init(&info, m_imguiRenderPass); + + if (prevRenderPass != VK_NULL_HANDLE) + vkDestroyRenderPass(GetLogicalDevice(), prevRenderPass, nullptr); } void VulkanRenderer::Initialize() @@ -4251,33 +4258,36 @@ VKRObjectFramebuffer::~VKRObjectFramebuffer() VKRObjectPipeline::VKRObjectPipeline() { - // todo } -void VKRObjectPipeline::setPipeline(VkPipeline newPipeline) +void VKRObjectPipeline::SetPipeline(VkPipeline newPipeline) { - cemu_assert_debug(pipeline == VK_NULL_HANDLE); - pipeline = newPipeline; - if(newPipeline != VK_NULL_HANDLE) + if (m_pipeline == newPipeline) + return; + cemu_assert_debug(m_pipeline == VK_NULL_HANDLE); // replacing an already assigned pipeline is not intended + if(m_pipeline == VK_NULL_HANDLE && newPipeline != VK_NULL_HANDLE) performanceMonitor.vk.numGraphicPipelines.increment(); + else if(m_pipeline != VK_NULL_HANDLE && newPipeline == VK_NULL_HANDLE) + performanceMonitor.vk.numGraphicPipelines.decrement(); + m_pipeline = newPipeline; } VKRObjectPipeline::~VKRObjectPipeline() { auto vkr = VulkanRenderer::GetInstance(); - if (pipeline != VK_NULL_HANDLE) + if (m_pipeline != VK_NULL_HANDLE) { - vkDestroyPipeline(vkr->GetLogicalDevice(), pipeline, nullptr); + vkDestroyPipeline(vkr->GetLogicalDevice(), m_pipeline, nullptr); performanceMonitor.vk.numGraphicPipelines.decrement(); } - if (vertexDSL != VK_NULL_HANDLE) - vkDestroyDescriptorSetLayout(vkr->GetLogicalDevice(), vertexDSL, nullptr); - if (pixelDSL != VK_NULL_HANDLE) - vkDestroyDescriptorSetLayout(vkr->GetLogicalDevice(), pixelDSL, nullptr); - if (geometryDSL != VK_NULL_HANDLE) - vkDestroyDescriptorSetLayout(vkr->GetLogicalDevice(), geometryDSL, nullptr); - if (pipeline_layout != VK_NULL_HANDLE) - vkDestroyPipelineLayout(vkr->GetLogicalDevice(), pipeline_layout, nullptr); + if (m_vertexDSL != VK_NULL_HANDLE) + vkDestroyDescriptorSetLayout(vkr->GetLogicalDevice(), m_vertexDSL, nullptr); + if (m_pixelDSL != VK_NULL_HANDLE) + vkDestroyDescriptorSetLayout(vkr->GetLogicalDevice(), m_pixelDSL, nullptr); + if (m_geometryDSL != VK_NULL_HANDLE) + vkDestroyDescriptorSetLayout(vkr->GetLogicalDevice(), m_geometryDSL, nullptr); + if (m_pipelineLayout != VK_NULL_HANDLE) + vkDestroyPipelineLayout(vkr->GetLogicalDevice(), m_pipelineLayout, nullptr); } VKRObjectDescriptorSet::VKRObjectDescriptorSet() diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 6751f171..83f8c834 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -450,7 +450,6 @@ private: bool tooling_info = false; // VK_EXT_tooling_info bool transform_feedback = false; bool depth_range_unrestricted = false; - bool depth_clip_enable = false; bool nv_fill_rectangle = false; // NV_fill_rectangle bool pipeline_feedback = false; bool pipeline_creation_cache_control = false; // VK_EXT_pipeline_creation_cache_control @@ -463,6 +462,7 @@ private: bool dynamic_rendering = false; // VK_KHR_dynamic_rendering bool shader_float_controls = false; // VK_KHR_shader_float_controls bool present_wait = false; // VK_KHR_present_wait + bool depth_clip_enable = false; // VK_EXT_depth_clip_enable }deviceExtensions; struct diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index 3e23b0aa..bd49a69e 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -603,7 +603,7 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo* const auto it = pipeline_info->vertex_ds_cache.find(stateHash); if (it != pipeline_info->vertex_ds_cache.cend()) return it->second; - descriptor_set_layout = pipeline_info->m_vkrObjPipeline->vertexDSL; + descriptor_set_layout = pipeline_info->m_vkrObjPipeline->m_vertexDSL; break; } case LatteConst::ShaderType::Pixel: @@ -611,7 +611,7 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo* const auto it = pipeline_info->pixel_ds_cache.find(stateHash); if (it != pipeline_info->pixel_ds_cache.cend()) return it->second; - descriptor_set_layout = pipeline_info->m_vkrObjPipeline->pixelDSL; + descriptor_set_layout = pipeline_info->m_vkrObjPipeline->m_pixelDSL; break; } case LatteConst::ShaderType::Geometry: @@ -619,7 +619,7 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo* const auto it = pipeline_info->geometry_ds_cache.find(stateHash); if (it != pipeline_info->geometry_ds_cache.cend()) return it->second; - descriptor_set_layout = pipeline_info->m_vkrObjPipeline->geometryDSL; + descriptor_set_layout = pipeline_info->m_vkrObjPipeline->m_geometryDSL; break; } default: @@ -1481,8 +1481,7 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 } auto vkObjPipeline = pipeline_info->m_vkrObjPipeline; - - if (vkObjPipeline->pipeline == VK_NULL_HANDLE) + if (vkObjPipeline->GetPipeline() == VK_NULL_HANDLE) { // invalid/uninitialized pipeline m_state.activeVertexDS = nullptr; @@ -1509,11 +1508,11 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 draw_setRenderPass(); - if (m_state.currentPipeline != vkObjPipeline->pipeline) + if (m_state.currentPipeline != vkObjPipeline->GetPipeline()) { - vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->pipeline); + vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->GetPipeline()); vkObjPipeline->flagForCurrentCommandBuffer(); - m_state.currentPipeline = vkObjPipeline->pipeline; + m_state.currentPipeline = vkObjPipeline->GetPipeline(); // depth bias if (pipeline_info->usesDepthBias) draw_updateDepthBias(true); @@ -1545,7 +1544,7 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 dsArray[1] = pixelDS->m_vkObjDescriptorSet->descriptorSet; vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - vkObjPipeline->pipeline_layout, 0, 2, dsArray, numDynOffsetsVS + numDynOffsetsPS, + vkObjPipeline->m_pipelineLayout, 0, 2, dsArray, numDynOffsetsVS + numDynOffsetsPS, dynamicOffsets); } else if (vertexDS) @@ -1554,7 +1553,7 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, dynamicOffsets, numDynOffsets, pipeline_info); vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - vkObjPipeline->pipeline_layout, 0, 1, &vertexDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, + vkObjPipeline->m_pipelineLayout, 0, 1, &vertexDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, dynamicOffsets); } else if (pixelDS) @@ -1563,7 +1562,7 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, dynamicOffsets, numDynOffsets, pipeline_info); vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - vkObjPipeline->pipeline_layout, 1, 1, &pixelDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, + vkObjPipeline->m_pipelineLayout, 1, 1, &pixelDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, dynamicOffsets); } if (geometryDS) @@ -1572,7 +1571,7 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, dynamicOffsets, numDynOffsets, pipeline_info); vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - vkObjPipeline->pipeline_layout, 2, 1, &geometryDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, + vkObjPipeline->m_pipelineLayout, 2, 1, &geometryDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, dynamicOffsets); } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp index f98eb452..e3e42012 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp @@ -357,7 +357,7 @@ CopySurfacePipelineInfo* VulkanRenderer::copySurface_getOrCreateGraphicsPipeline layoutInfo.bindingCount = (uint32_t)descriptorSetLayoutBindings.size(); layoutInfo.pBindings = descriptorSetLayoutBindings.data(); - if (vkCreateDescriptorSetLayout(m_logicalDevice, &layoutInfo, nullptr, &vkObjPipeline->pixelDSL) != VK_SUCCESS) + if (vkCreateDescriptorSetLayout(m_logicalDevice, &layoutInfo, nullptr, &vkObjPipeline->m_pixelDSL) != VK_SUCCESS) UnrecoverableError(fmt::format("Failed to create descriptor set layout for surface copy shader").c_str()); // ########################################################################################################################################## @@ -370,15 +370,15 @@ CopySurfacePipelineInfo* VulkanRenderer::copySurface_getOrCreateGraphicsPipeline VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 1; - pipelineLayoutInfo.pSetLayouts = &vkObjPipeline->pixelDSL; + pipelineLayoutInfo.pSetLayouts = &vkObjPipeline->m_pixelDSL; pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange; pipelineLayoutInfo.pushConstantRangeCount = 1; - VkResult result = vkCreatePipelineLayout(m_logicalDevice, &pipelineLayoutInfo, nullptr, &vkObjPipeline->pipeline_layout); + VkResult result = vkCreatePipelineLayout(m_logicalDevice, &pipelineLayoutInfo, nullptr, &vkObjPipeline->m_pipelineLayout); if (result != VK_SUCCESS) { cemuLog_log(LogType::Force, "Failed to create pipeline layout: {}", result); - vkObjPipeline->pipeline = VK_NULL_HANDLE; + vkObjPipeline->SetPipeline(VK_NULL_HANDLE); return copyPipeline; } @@ -425,7 +425,7 @@ CopySurfacePipelineInfo* VulkanRenderer::copySurface_getOrCreateGraphicsPipeline pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pColorBlendState = state.destinationTexture->isDepth?nullptr:&colorBlending; - pipelineInfo.layout = vkObjPipeline->pipeline_layout; + pipelineInfo.layout = vkObjPipeline->m_pipelineLayout; pipelineInfo.renderPass = copyPipeline->vkObjRenderPass->m_renderPass; pipelineInfo.pDepthStencilState = &depthStencilState; pipelineInfo.subpass = 0; @@ -434,17 +434,16 @@ CopySurfacePipelineInfo* VulkanRenderer::copySurface_getOrCreateGraphicsPipeline copyPipeline->vkObjPipeline = vkObjPipeline; - result = vkCreateGraphicsPipelines(m_logicalDevice, m_pipeline_cache, 1, &pipelineInfo, nullptr, ©Pipeline->vkObjPipeline->pipeline); + VkPipeline pipeline = VK_NULL_HANDLE; + result = vkCreateGraphicsPipelines(m_logicalDevice, m_pipeline_cache, 1, &pipelineInfo, nullptr, &pipeline); if (result != VK_SUCCESS) { + copyPipeline->vkObjPipeline->SetPipeline(nullptr); cemuLog_log(LogType::Force, "Failed to create graphics pipeline for surface copy. Error {} Info:", (sint32)result); - cemu_assert_debug(false); - copyPipeline->vkObjPipeline->pipeline = VK_NULL_HANDLE; + cemu_assert_suspicious(); } - //performanceMonitor.vk.numGraphicPipelines.increment(); - - //m_pipeline_cache_semaphore.notify(); - + else + copyPipeline->vkObjPipeline->SetPipeline(pipeline); return copyPipeline; } @@ -522,7 +521,7 @@ VKRObjectDescriptorSet* VulkanRenderer::surfaceCopy_getOrCreateDescriptorSet(VkC allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; allocInfo.descriptorPool = m_descriptorPool; allocInfo.descriptorSetCount = 1; - allocInfo.pSetLayouts = &(pipelineInfo->vkObjPipeline->pixelDSL); + allocInfo.pSetLayouts = &pipelineInfo->vkObjPipeline->m_pixelDSL; if (vkAllocateDescriptorSets(m_logicalDevice, &allocInfo, &vkObjDescriptorSet->descriptorSet) != VK_SUCCESS) { @@ -644,7 +643,7 @@ void VulkanRenderer::surfaceCopy_viaDrawcall(LatteTextureVk* srcTextureVk, sint3 pushConstantData.srcTexelOffset[0] = 0; pushConstantData.srcTexelOffset[1] = 0; - vkCmdPushConstants(m_state.currentCommandBuffer, copySurfacePipelineInfo->vkObjPipeline->pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(pushConstantData), &pushConstantData); + vkCmdPushConstants(m_state.currentCommandBuffer, copySurfacePipelineInfo->vkObjPipeline->m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(pushConstantData), &pushConstantData); // draw VkRenderPassBeginInfo renderPassInfo{}; @@ -680,13 +679,13 @@ void VulkanRenderer::surfaceCopy_viaDrawcall(LatteTextureVk* srcTextureVk, sint3 vkCmdBeginRenderPass(m_state.currentCommandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, copySurfacePipelineInfo->vkObjPipeline->pipeline); + vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, copySurfacePipelineInfo->vkObjPipeline->GetPipeline()); copySurfacePipelineInfo->vkObjPipeline->flagForCurrentCommandBuffer(); - m_state.currentPipeline = copySurfacePipelineInfo->vkObjPipeline->pipeline; + m_state.currentPipeline = copySurfacePipelineInfo->vkObjPipeline->GetPipeline(); vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - copySurfacePipelineInfo->vkObjPipeline->pipeline_layout, 0, 1, &vkObjDescriptorSet->descriptorSet, 0, nullptr); + copySurfacePipelineInfo->vkObjPipeline->m_pipelineLayout, 0, 1, &vkObjDescriptorSet->descriptorSet, 0, nullptr); vkObjDescriptorSet->flagForCurrentCommandBuffer(); vkCmdDraw(m_state.currentCommandBuffer, 6, 1, 0, 0); diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index cf5f4787..748dbdb4 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -39,15 +39,14 @@ enum class LogType : sint32 NN_SL = 26, TextureReadback = 29, - ProcUi = 39, nlibcurl = 41, PRUDP = 40, - Recompiler = 60, NFC = 41, NTAG = 42, + Recompiler = 60, }; template <> diff --git a/src/asm/CMakeLists.txt b/src/asm/CMakeLists.txt index 509d4491..d90a3318 100644 --- a/src/asm/CMakeLists.txt +++ b/src/asm/CMakeLists.txt @@ -52,5 +52,7 @@ elseif(CEMU_ASM_ARCHITECTURE MATCHES "(aarch64)|(AARCH64)|(arm64)|(ARM64)") enable_language(C ASM) add_library(CemuAsm aarch64util.s) else() + add_compile_definitions(STUB_RECOMPILER_ASM_UTIL) + add_library(CemuAsm stub.cpp) message(STATUS "CemuAsm - Unsupported arch: ${CEMU_ASM_ARCHITECTURE}") endif() diff --git a/src/asm/x64util.h b/src/asm/x64util.h index 1a5e165c..c4e5e03d 100644 --- a/src/asm/x64util.h +++ b/src/asm/x64util.h @@ -1,14 +1,19 @@ #pragma once +#if defined(STUB_RECOMPILER_ASM_UTIL) + +static void recompiler_fres() +{ + cemu_assert_unimplemented(); +} +static void recompiler_frsqrte() +{ + cemu_assert_unimplemented(); +} + +#else extern "C" void recompiler_fres(); extern "C" void recompiler_frsqrte(); -// #if defined(ARCH_X86_64) -// #else -// // stubbed on non-x86 for now -// static void recompiler_frsqrte() -// { -// cemu_assert_unimplemented(); -// } -// #endif +#endif diff --git a/src/audio/CubebAPI.cpp b/src/audio/CubebAPI.cpp index f98fa601..f6d5d516 100644 --- a/src/audio/CubebAPI.cpp +++ b/src/audio/CubebAPI.cpp @@ -183,17 +183,17 @@ void CubebAPI::Destroy() std::vector CubebAPI::GetDevices() { - cubeb_device_collection devices; - if (cubeb_enumerate_devices(s_context, CUBEB_DEVICE_TYPE_OUTPUT, &devices) != CUBEB_OK) - return {}; - std::vector result; - result.reserve(devices.count + 1); // Reserve space for the default device - // Add the default device to the list auto defaultDevice = std::make_shared(nullptr, "default", L"Default Device"); result.emplace_back(defaultDevice); + cubeb_device_collection devices; + if (cubeb_enumerate_devices(s_context, CUBEB_DEVICE_TYPE_OUTPUT, &devices) != CUBEB_OK) + return result; + + result.reserve(devices.count + 1); // The default device already occupies one element + for (size_t i = 0; i < devices.count; ++i) { // const auto& device = devices.device[i]; diff --git a/src/audio/CubebInputAPI.cpp b/src/audio/CubebInputAPI.cpp index c0fa73f4..a9faa9c8 100644 --- a/src/audio/CubebInputAPI.cpp +++ b/src/audio/CubebInputAPI.cpp @@ -175,17 +175,17 @@ void CubebInputAPI::Destroy() std::vector CubebInputAPI::GetDevices() { - cubeb_device_collection devices; - if (cubeb_enumerate_devices(s_context, CUBEB_DEVICE_TYPE_INPUT, &devices) != CUBEB_OK) - return {}; - std::vector result; - result.reserve(devices.count + 1); // Reserve space for the default device - // Add the default device to the list auto defaultDevice = std::make_shared(nullptr, "default", L"Default Device"); result.emplace_back(defaultDevice); + cubeb_device_collection devices; + if (cubeb_enumerate_devices(s_context, CUBEB_DEVICE_TYPE_INPUT, &devices) != CUBEB_OK) + return result; + + result.reserve(devices.count + 1); // The default device already occupies one element + for (size_t i = 0; i < devices.count; ++i) { // const auto& device = devices.device[i]; diff --git a/src/config/ActiveSettings.cpp b/src/config/ActiveSettings.cpp index a72e9046..c081b5e8 100644 --- a/src/config/ActiveSettings.cpp +++ b/src/config/ActiveSettings.cpp @@ -165,6 +165,11 @@ bool ActiveSettings::DumpTexturesEnabled() return s_dump_textures; } +bool ActiveSettings::DumpRecompilerFunctionsEnabled() +{ + return s_dump_recompiler_functions; +} + bool ActiveSettings::DumpLibcurlRequestsEnabled() { return s_dump_libcurl_requests; @@ -180,6 +185,11 @@ void ActiveSettings::EnableDumpTextures(bool state) s_dump_textures = state; } +void ActiveSettings::EnableDumpRecompilerFunctions(bool state) +{ + s_dump_recompiler_functions = state; +} + void ActiveSettings::EnableDumpLibcurlRequests(bool state) { s_dump_libcurl_requests = state; diff --git a/src/config/ActiveSettings.h b/src/config/ActiveSettings.h index c0a63bca..9ba1f3e8 100644 --- a/src/config/ActiveSettings.h +++ b/src/config/ActiveSettings.h @@ -127,9 +127,11 @@ public: // dump options [[nodiscard]] static bool DumpShadersEnabled(); [[nodiscard]] static bool DumpTexturesEnabled(); + [[nodiscard]] static bool DumpRecompilerFunctionsEnabled(); [[nodiscard]] static bool DumpLibcurlRequestsEnabled(); static void EnableDumpShaders(bool state); static void EnableDumpTextures(bool state); + static void EnableDumpRecompilerFunctions(bool state); static void EnableDumpLibcurlRequests(bool state); // hacks @@ -143,6 +145,7 @@ private: // dump options inline static bool s_dump_shaders = false; inline static bool s_dump_textures = false; + inline static bool s_dump_recompiler_functions = false; inline static bool s_dump_libcurl_requests = false; // timer speed diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index 2432189b..4dfe5750 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -189,7 +189,7 @@ ENABLE_ENUM_ITERATORS(CrashDump, CrashDump::Disabled, CrashDump::Enabled); #endif template <> -struct fmt::formatter : formatter { +struct fmt::formatter : formatter { template auto format(const PrecompiledShaderOption c, FormatContext &ctx) const { string_view name; @@ -204,7 +204,7 @@ struct fmt::formatter : formatter { } }; template <> -struct fmt::formatter : formatter { +struct fmt::formatter : formatter { template auto format(const AccurateShaderMulOption c, FormatContext &ctx) const { string_view name; @@ -218,7 +218,7 @@ struct fmt::formatter : formatter { } }; template <> -struct fmt::formatter : formatter { +struct fmt::formatter : formatter { template auto format(const CPUMode c, FormatContext &ctx) const { string_view name; @@ -235,7 +235,7 @@ struct fmt::formatter : formatter { } }; template <> -struct fmt::formatter : formatter { +struct fmt::formatter : formatter { template auto format(const CPUModeLegacy c, FormatContext &ctx) const { string_view name; @@ -252,7 +252,7 @@ struct fmt::formatter : formatter { } }; template <> -struct fmt::formatter : formatter { +struct fmt::formatter : formatter { template auto format(const CafeConsoleRegion v, FormatContext &ctx) const { string_view name; @@ -273,7 +273,7 @@ struct fmt::formatter : formatter { } }; template <> -struct fmt::formatter : formatter { +struct fmt::formatter : formatter { template auto format(const CafeConsoleLanguage v, FormatContext &ctx) { string_view name; @@ -299,7 +299,7 @@ struct fmt::formatter : formatter { #if BOOST_OS_WINDOWS template <> -struct fmt::formatter : formatter { +struct fmt::formatter : formatter { template auto format(const CrashDump v, FormatContext &ctx) { string_view name; @@ -316,7 +316,7 @@ struct fmt::formatter : formatter { }; #elif BOOST_OS_UNIX template <> -struct fmt::formatter : formatter { +struct fmt::formatter : formatter { template auto format(const CrashDump v, FormatContext &ctx) { string_view name; diff --git a/src/config/LaunchSettings.cpp b/src/config/LaunchSettings.cpp index 925b22d7..0bf647be 100644 --- a/src/config/LaunchSettings.cpp +++ b/src/config/LaunchSettings.cpp @@ -202,7 +202,6 @@ bool LaunchSettings::HandleCommandline(const std::vector& args) if(ppcRec_limitLowerAddr != 0 && ppcRec_limitUpperAddr != 0) cemuLog_log(LogType::Force, "PPCRec range limited to 0x{:08x}-0x{:08x}", ppcRec_limitLowerAddr, ppcRec_limitUpperAddr); - if(!extract_path.empty()) { ExtractorTool(extract_path, output_path, log_path); diff --git a/src/config/LaunchSettings.h b/src/config/LaunchSettings.h index 50e52381..074fbb91 100644 --- a/src/config/LaunchSettings.h +++ b/src/config/LaunchSettings.h @@ -46,6 +46,7 @@ private: inline static bool s_force_interpreter = false; inline static std::optional s_persistent_id{}; + // for recompiler debugging inline static uint32 ppcRec_limitLowerAddr{}; inline static uint32 ppcRec_limitUpperAddr{}; diff --git a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp index c77ae081..d40e5e5e 100644 --- a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp +++ b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp @@ -146,17 +146,15 @@ wxPanel* EmulatedUSBDeviceFrame::AddDimensionsPage(wxNotebook* notebook) auto* top_row = new wxBoxSizer(wxHORIZONTAL); auto* bottom_row = new wxBoxSizer(wxHORIZONTAL); - auto* dummy = new wxStaticText(box, wxID_ANY, ""); - top_row->Add(AddDimensionPanel(2, 0, box), 1, wxEXPAND | wxALL, 2); - top_row->Add(dummy, 1, wxEXPAND | wxLEFT | wxRIGHT, 2); + top_row->Add(0, 0, 1, wxEXPAND | wxLEFT | wxRIGHT, 2); top_row->Add(AddDimensionPanel(1, 1, box), 1, wxEXPAND | wxALL, 2); - top_row->Add(dummy, 1, wxEXPAND | wxLEFT | wxRIGHT, 2); + top_row->Add(0, 0, 1, wxEXPAND | wxLEFT | wxRIGHT, 2); top_row->Add(AddDimensionPanel(3, 2, box), 1, wxEXPAND | wxALL, 2); bottom_row->Add(AddDimensionPanel(2, 3, box), 1, wxEXPAND | wxALL, 2); bottom_row->Add(AddDimensionPanel(2, 4, box), 1, wxEXPAND | wxALL, 2); - bottom_row->Add(dummy, 1, wxEXPAND | wxLEFT | wxRIGHT, 0); + bottom_row->Add(0, 0, 1, wxEXPAND | wxLEFT | wxRIGHT, 0); bottom_row->Add(AddDimensionPanel(3, 5, box), 1, wxEXPAND | wxALL, 2); bottom_row->Add(AddDimensionPanel(3, 6, box), 1, wxEXPAND | wxALL, 2); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 9977b6b9..70af2851 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -145,6 +145,7 @@ enum // debug->dump MAINFRAME_MENU_ID_DEBUG_DUMP_TEXTURES = 21600, MAINFRAME_MENU_ID_DEBUG_DUMP_SHADERS, + MAINFRAME_MENU_ID_DEBUG_DUMP_RECOMPILER_FUNCTIONS, MAINFRAME_MENU_ID_DEBUG_DUMP_RAM, MAINFRAME_MENU_ID_DEBUG_DUMP_FST, MAINFRAME_MENU_ID_DEBUG_DUMP_CURL_REQUESTS, @@ -207,8 +208,9 @@ EVT_MENU_RANGE(MAINFRAME_MENU_ID_NFC_RECENT_0 + 0, MAINFRAME_MENU_ID_NFC_RECENT_ EVT_MENU_RANGE(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + 0, MAINFRAME_MENU_ID_DEBUG_LOGGING0 + 98, MainWindow::OnDebugLoggingToggleFlagGeneric) EVT_MENU(MAINFRAME_MENU_ID_DEBUG_ADVANCED_PPC_INFO, MainWindow::OnPPCInfoToggle) // debug -> dump menu -EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_TEXTURES, MainWindow::OnDebugDumpUsedTextures) -EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_SHADERS, MainWindow::OnDebugDumpUsedShaders) +EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_TEXTURES, MainWindow::OnDebugDumpGeneric) +EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_SHADERS, MainWindow::OnDebugDumpGeneric) +EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_RECOMPILER_FUNCTIONS, MainWindow::OnDebugDumpGeneric) EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_CURL_REQUESTS, MainWindow::OnDebugSetting) // debug -> Other options EVT_MENU(MAINFRAME_MENU_ID_DEBUG_RENDER_UPSIDE_DOWN, MainWindow::OnDebugSetting) @@ -1178,31 +1180,29 @@ void MainWindow::OnPPCInfoToggle(wxCommandEvent& event) g_config.Save(); } -void MainWindow::OnDebugDumpUsedTextures(wxCommandEvent& event) +void MainWindow::OnDebugDumpGeneric(wxCommandEvent& event) { - const bool value = event.IsChecked(); - ActiveSettings::EnableDumpTextures(value); - if (value) + std::string dumpSubpath; + std::function setDumpState; + switch(event.GetId()) { - try - { - // create directory - const fs::path path(ActiveSettings::GetUserDataPath()); - fs::create_directories(path / "dump" / "textures"); - } - catch (const std::exception& ex) - { - SystemException sys(ex); - cemuLog_log(LogType::Force, "can't create texture dump folder: {}", ex.what()); - ActiveSettings::EnableDumpTextures(false); - } + case MAINFRAME_MENU_ID_DEBUG_DUMP_TEXTURES: + dumpSubpath = "dump/textures"; + setDumpState = ActiveSettings::EnableDumpTextures; + break; + case MAINFRAME_MENU_ID_DEBUG_DUMP_SHADERS: + dumpSubpath = "dump/shaders"; + setDumpState = ActiveSettings::EnableDumpShaders; + break; + case MAINFRAME_MENU_ID_DEBUG_DUMP_RECOMPILER_FUNCTIONS: + dumpSubpath = "dump/recompiler"; + setDumpState = ActiveSettings::EnableDumpRecompilerFunctions; + break; + default: + UNREACHABLE; } -} - -void MainWindow::OnDebugDumpUsedShaders(wxCommandEvent& event) -{ const bool value = event.IsChecked(); - ActiveSettings::EnableDumpShaders(value); + setDumpState(value); if (value) { try @@ -2425,6 +2425,7 @@ void MainWindow::RecreateMenu() wxMenu* debugDumpMenu = new wxMenu; debugDumpMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_DUMP_TEXTURES, _("&Textures"), wxEmptyString)->Check(ActiveSettings::DumpTexturesEnabled()); debugDumpMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_DUMP_SHADERS, _("&Shaders"), wxEmptyString)->Check(ActiveSettings::DumpShadersEnabled()); + debugDumpMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_DUMP_RECOMPILER_FUNCTIONS, _("&Recompiled functions"), wxEmptyString)->Check(ActiveSettings::DumpRecompilerFunctionsEnabled()); debugDumpMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_DUMP_CURL_REQUESTS, _("&nlibcurl HTTP/HTTPS requests"), wxEmptyString); // debug submenu wxMenu* debugMenu = new wxMenu(); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index a64af72d..cf907e3a 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -111,8 +111,7 @@ public: void OnDebugSetting(wxCommandEvent& event); void OnDebugLoggingToggleFlagGeneric(wxCommandEvent& event); void OnPPCInfoToggle(wxCommandEvent& event); - void OnDebugDumpUsedTextures(wxCommandEvent& event); - void OnDebugDumpUsedShaders(wxCommandEvent& event); + void OnDebugDumpGeneric(wxCommandEvent& event); void OnLoggingWindow(wxCommandEvent& event); void OnGDBStubToggle(wxCommandEvent& event); void OnDebugViewPPCThreads(wxCommandEvent& event); diff --git a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp index 28a123f3..a6bdf574 100644 --- a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp +++ b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp @@ -23,15 +23,15 @@ static bool AttemptSetNonBlock(int sockFd) return fcntl(sockFd, F_SETFL, fcntl(sockFd, F_GETFL) | O_NONBLOCK) == 0; } -L2CapWiimote::L2CapWiimote(int recvFd, int sendFd, bdaddr_t addr) - : m_recvFd(recvFd), m_sendFd(sendFd), m_addr(addr) +L2CapWiimote::L2CapWiimote(int controlFd, int dataFd, bdaddr_t addr) + : m_controlFd(controlFd), m_dataFd(dataFd), m_addr(addr) { } L2CapWiimote::~L2CapWiimote() { - close(m_recvFd); - close(m_sendFd); + close(m_dataFd); + close(m_controlFd); const auto& b = m_addr.b; cemuLog_logDebug(LogType::Force, "Wiimote at {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x} disconnected", b[5], b[4], b[3], b[2], b[1], b[0]); @@ -61,51 +61,51 @@ std::vector L2CapWiimote::get_devices() std::vector outDevices; for (const auto& addr : unconnected) { - // Socket for sending data to controller, PSM 0x11 - auto sendFd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); - if (sendFd < 0) + // Control socket, PSM 0x11, needs to be open for the data socket to be opened + auto controlFd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (controlFd < 0) { - cemuLog_logDebug(LogType::Force, "Failed to open send socket: {}", strerror(errno)); + cemuLog_logDebug(LogType::Force, "Failed to open control socket: {}", strerror(errno)); continue; } - sockaddr_l2 sendAddr{}; - sendAddr.l2_family = AF_BLUETOOTH; - sendAddr.l2_psm = htobs(0x11); - sendAddr.l2_bdaddr = addr; + sockaddr_l2 controlAddr{}; + controlAddr.l2_family = AF_BLUETOOTH; + controlAddr.l2_psm = htobs(0x11); + controlAddr.l2_bdaddr = addr; - if (!AttemptConnect(sendFd, sendAddr) || !AttemptSetNonBlock(sendFd)) + if (!AttemptConnect(controlFd, controlAddr) || !AttemptSetNonBlock(controlFd)) { const auto& b = addr.b; - cemuLog_logDebug(LogType::Force, "Failed to connect send socket to '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}': {}", + cemuLog_logDebug(LogType::Force, "Failed to connect control socket to '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}': {}", b[5], b[4], b[3], b[2], b[1], b[0], strerror(errno)); - close(sendFd); + close(controlFd); continue; } - // Socket for receiving data from controller, PSM 0x13 - auto recvFd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); - if (recvFd < 0) + // Socket for sending and receiving data from controller, PSM 0x13 + auto dataFd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (dataFd < 0) { - cemuLog_logDebug(LogType::Force, "Failed to open recv socket: {}", strerror(errno)); - close(sendFd); + cemuLog_logDebug(LogType::Force, "Failed to open data socket: {}", strerror(errno)); + close(controlFd); continue; } - sockaddr_l2 recvAddr{}; - recvAddr.l2_family = AF_BLUETOOTH; - recvAddr.l2_psm = htobs(0x13); - recvAddr.l2_bdaddr = addr; + sockaddr_l2 dataAddr{}; + dataAddr.l2_family = AF_BLUETOOTH; + dataAddr.l2_psm = htobs(0x13); + dataAddr.l2_bdaddr = addr; - if (!AttemptConnect(recvFd, recvAddr) || !AttemptSetNonBlock(recvFd)) + if (!AttemptConnect(dataFd, dataAddr) || !AttemptSetNonBlock(dataFd)) { const auto& b = addr.b; - cemuLog_logDebug(LogType::Force, "Failed to connect recv socket to '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}': {}", + cemuLog_logDebug(LogType::Force, "Failed to connect data socket to '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}': {}", b[5], b[4], b[3], b[2], b[1], b[0], strerror(errno)); - close(sendFd); - close(recvFd); + close(dataFd); + close(controlFd); continue; } - outDevices.emplace_back(std::make_shared(sendFd, recvFd, addr)); + outDevices.emplace_back(std::make_shared(controlFd, dataFd, addr)); s_addressMutex.lock(); s_addresses[addr] = true; @@ -123,13 +123,13 @@ bool L2CapWiimote::write_data(const std::vector& data) buffer[0] = 0xA2; std::memcpy(buffer + 1, data.data(), size); const auto outSize = size + 1; - return send(m_sendFd, buffer, outSize, 0) == outSize; + return send(m_dataFd, buffer, outSize, 0) == outSize; } std::optional> L2CapWiimote::read_data() { uint8 buffer[23]; - const auto nBytes = recv(m_sendFd, buffer, 23, 0); + const auto nBytes = recv(m_dataFd, buffer, 23, 0); if (nBytes < 0 && errno == EWOULDBLOCK) return std::vector{}; diff --git a/src/input/api/Wiimote/l2cap/L2CapWiimote.h b/src/input/api/Wiimote/l2cap/L2CapWiimote.h index cc8d071b..0b6c5c19 100644 --- a/src/input/api/Wiimote/l2cap/L2CapWiimote.h +++ b/src/input/api/Wiimote/l2cap/L2CapWiimote.h @@ -5,7 +5,7 @@ class L2CapWiimote : public WiimoteDevice { public: - L2CapWiimote(int recvFd, int sendFd, bdaddr_t addr); + L2CapWiimote(int controlFd, int dataFd, bdaddr_t addr); ~L2CapWiimote() override; bool write_data(const std::vector& data) override; @@ -15,8 +15,8 @@ class L2CapWiimote : public WiimoteDevice static void AddCandidateAddress(bdaddr_t addr); static std::vector get_devices(); private: - int m_recvFd; - int m_sendFd; + int m_controlFd; + int m_dataFd; bdaddr_t m_addr; }; diff --git a/src/util/helpers/StringBuf.h b/src/util/helpers/StringBuf.h index e2f723d3..6242fa4c 100644 --- a/src/util/helpers/StringBuf.h +++ b/src/util/helpers/StringBuf.h @@ -44,10 +44,9 @@ public: void add(std::string_view appendedStr) { - size_t remainingLen = this->limit - this->length; size_t copyLen = appendedStr.size(); - if (remainingLen < copyLen) - copyLen = remainingLen; + if (this->length + copyLen + 1 >= this->limit) + _reserve(std::max(this->length + copyLen + 64, this->limit + this->limit / 2)); char* outputStart = (char*)(this->str + this->length); std::copy(appendedStr.data(), appendedStr.data() + copyLen, outputStart); length += copyLen;