mirror of
https://github.com/izzy2lost/WeeU.git
synced 2026-07-06 00:19:59 -07:00
Merge branch 'main' into android
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
|
||||
|
||||
<Type Name="boost::container::small_vector<*>">
|
||||
<Expand>
|
||||
<Item Name="[size]">m_holder.m_size</Item>
|
||||
<ArrayItems>
|
||||
<Size>m_holder.m_size</Size>
|
||||
<ValuePointer>m_holder.m_start</ValuePointer>
|
||||
</ArrayItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<Type Name="boost::container::static_vector<*>">
|
||||
<DisplayString>{{ size={m_holder.m_size} }}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="[size]" ExcludeView="simple">m_holder.m_size</Item>
|
||||
<Item Name="[capacity]" ExcludeView="simple">static_capacity</Item>
|
||||
<ArrayItems>
|
||||
<Size>m_holder.m_size</Size>
|
||||
<ValuePointer>($T1*)m_holder.storage.data</ValuePointer>
|
||||
</ArrayItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
</AutoVisualizer>
|
||||
Vendored
+1
-1
Submodule dependencies/vcpkg updated: a4275b7eee...533a5fda5c
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,12 +2,9 @@
|
||||
|
||||
#include "HW/Espresso/Recompiler/IML/IMLInstruction.h"
|
||||
#include "../PPCRecompiler.h"
|
||||
struct CodeContext
|
||||
{
|
||||
virtual ~CodeContext() = default;
|
||||
};
|
||||
|
||||
std::unique_ptr<CodeContext> 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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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<x64RelocEntry_t> 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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<IMLReg, 64, true> 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;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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<typename Fn>
|
||||
void ForEachWrittenGPR(Fn F) const
|
||||
{
|
||||
@@ -379,6 +364,8 @@ struct IMLUsedRegisters
|
||||
F(readGPR2);
|
||||
if (readGPR3.IsValid())
|
||||
F(readGPR3);
|
||||
if (readGPR4.IsValid())
|
||||
F(readGPR4);
|
||||
}
|
||||
|
||||
template<typename Fn>
|
||||
@@ -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<IMLRegID, IMLRegID>& translationTable);
|
||||
void ReplaceFPRs(IMLReg fprRegisterSearched[4], IMLReg fprRegisterReplaced[4]);
|
||||
void ReplaceFPR(IMLRegID fprRegisterSearched, IMLRegID fprRegisterReplaced);
|
||||
|
||||
};
|
||||
|
||||
// architecture specific constants
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
#include "../PPCRecompilerIml.h"
|
||||
#include "../BackendX64/BackendX64.h"
|
||||
|
||||
#include "Common/FileStream.h"
|
||||
|
||||
#include <boost/container/static_vector.hpp>
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
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<IMLSegment*> 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<IMLRegID> regWritten; // registers which are modified in this segment
|
||||
std::unordered_set<IMLRegID> regImported; // registers which are read in this segment before they are written (importing value from previous segments)
|
||||
std::unordered_set<IMLRegID> 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<IMLSegmentRegisterInOut>& 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<uint32> 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<IMLRegID> GetRegistersNeededAtEndOfSegment(IMLSegment& seg)
|
||||
{
|
||||
std::unordered_set<IMLRegID> 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<IMLSegment*> m_segmentList;
|
||||
uint32 m_maxRegId;
|
||||
|
||||
std::vector<IMLSegmentRegisterInOut> 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<sint32>(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<IMLRegID, 8> regsWritten;
|
||||
boost::container::static_vector<IMLRegID, 8> 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<targetIndex; i++)
|
||||
seg.imlList[i] = seg.imlList[i+1];
|
||||
seg.imlList[targetIndex] = temp;
|
||||
return targetIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
cemu_assert_unimplemented(); // testing needed
|
||||
std::copy(seg.imlList.begin() + targetIndex, seg.imlList.begin() + initialIndex, seg.imlList.begin() + targetIndex + 1);
|
||||
seg.imlList[targetIndex] = temp;
|
||||
return targetIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// x86 specific
|
||||
bool IMLOptimizerX86_ModifiesEFlags(IMLInstruction& inst)
|
||||
{
|
||||
// this is a very conservative implementation. There are more cases but this is good enough for now
|
||||
if(inst.type == PPCREC_IML_TYPE_NAME_R || inst.type == PPCREC_IML_TYPE_R_NAME)
|
||||
return false;
|
||||
if((inst.type == PPCREC_IML_TYPE_R_R || inst.type == PPCREC_IML_TYPE_R_S32) && inst.operation == PPCREC_IML_OP_ASSIGN)
|
||||
return false;
|
||||
return true; // if we dont know for sure, assume it does
|
||||
}
|
||||
|
||||
void IMLOptimizer_DebugPrintSeg(ppcImlGenContext_t& ppcImlGenContext, IMLSegment& seg)
|
||||
{
|
||||
printf("----------------\n");
|
||||
IMLDebug_DumpSegment(&ppcImlGenContext, &seg);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void IMLOptimizer_RemoveDeadCodeFromSegment(IMLOptimizerRegIOAnalysis& regIoAnalysis, IMLSegment& seg)
|
||||
{
|
||||
// algorithm works like this:
|
||||
// Calculate which registers need to be preserved at the end of each segment
|
||||
// Then for each segment:
|
||||
// - Iterate instructions backwards
|
||||
// - Maintain a list of registers which are read at a later point (initially this is the list from the first step)
|
||||
// - If an instruction only modifies registers which are not in the read list and has no side effects, then it is dead code and can be replaced with a no-op
|
||||
|
||||
std::unordered_set<IMLRegID> 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);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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<raLivenessRange*, 4> previousRanges;
|
||||
// processing
|
||||
uint32 lastIterationIndex;
|
||||
// instruction read/write locations
|
||||
std::vector<raAccessLocation> list_accessLocations;
|
||||
// ordered list of all raInstructionEdge indices which require a fixed register
|
||||
std::vector<raFixedRegRequirement> 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<raLivenessRange*, 128> 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);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user