Merge branch 'main' into android

This commit is contained in:
SSimco
2025-05-03 19:57:43 +03:00
49 changed files with 4475 additions and 2998 deletions
+26
View File
@@ -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&lt;*&gt;">
<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&lt;*&gt;">
<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>
+1
View File
@@ -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 -3
View File
@@ -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(&registersUsed);
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(&registersUsed);
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(&registerUsage);
// 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(&registersUsed);
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(&registersUsed);
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(&registersUsed);
// 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(&registersUsed);
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(&registersUsed);
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(&registersUsed);
// 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