From eeff110c0fb0e33a395783057263d15c8fef72a8 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 7 Dec 2014 21:09:08 -0800 Subject: [PATCH] jit: Improve and unify GPR spill logic. Now the same logic on x86 and ARM, and handles HI/LO/etc. better. --- Core/MIPS/ARM/ArmRegCache.cpp | 18 ++------ Core/MIPS/MIPSAnalyst.cpp | 80 +++++++++++++++++++++++++++++------ Core/MIPS/MIPSAnalyst.h | 2 + Core/MIPS/x86/RegCache.cpp | 47 ++++++++++++++------ Core/MIPS/x86/RegCache.h | 1 + 5 files changed, 109 insertions(+), 39 deletions(-) diff --git a/Core/MIPS/ARM/ArmRegCache.cpp b/Core/MIPS/ARM/ArmRegCache.cpp index e89de66347..82272bafdd 100644 --- a/Core/MIPS/ARM/ArmRegCache.cpp +++ b/Core/MIPS/ARM/ArmRegCache.cpp @@ -197,26 +197,16 @@ ARMReg ArmRegCache::FindBestToSpill(bool unusedOnly) { int allocCount; const ARMReg *allocOrder = GetMIPSAllocationOrder(allocCount); - static const int UNUSED_LOOKAHEAD_OPS = 3; + static const int UNUSED_LOOKAHEAD_OPS = 30; for (int i = 0; i < allocCount; i++) { ARMReg reg = allocOrder[i]; if (ar[reg].mipsReg != MIPS_REG_INVALID && mr[ar[reg].mipsReg].spillLock) continue; - if (unusedOnly) { - bool unused = true; - for (int ahead = 1; ahead <= UNUSED_LOOKAHEAD_OPS; ++ahead) { - MIPSOpcode laterOp = Memory::Read_Instruction(compilerPC_ + ahead * sizeof(u32)); - // If read, it might need to be mapped again. If output, it might not need to be stored. - if (MIPSAnalyst::ReadsFromGPReg(laterOp, ar[reg].mipsReg) || MIPSAnalyst::GetOutGPReg(laterOp) == ar[reg].mipsReg) { - unused = false; - } - } - - if (!unused) { - continue; - } + // Not awesome. A used reg. Let's try to avoid spilling. + if (unusedOnly && MIPSAnalyst::IsRegisterUsed(ar[reg].mipsReg, compilerPC_, UNUSED_LOOKAHEAD_OPS)) { + continue; } return reg; diff --git a/Core/MIPS/MIPSAnalyst.cpp b/Core/MIPS/MIPSAnalyst.cpp index 8ba9b43358..00ab314348 100644 --- a/Core/MIPS/MIPSAnalyst.cpp +++ b/Core/MIPS/MIPSAnalyst.cpp @@ -680,25 +680,25 @@ namespace MIPSAnalyst { } } - bool IsRegisterUsed(MIPSGPReg reg, u32 addr, int instrs) { + enum RegisterUsage { + USAGE_CLOBBERED, + USAGE_INPUT, + USAGE_UNKNOWN, + }; + + static RegisterUsage DetermineInOutUsage(u64 inFlag, u64 outFlag, u32 addr, int instrs) { u32 end = addr + instrs * sizeof(u32); while (addr < end) { const MIPSOpcode op = Memory::Read_Instruction(addr, true); const MIPSInfo info = MIPSGetInfo(op); // Yes, used. - if ((info & IN_RS) && (MIPS_GET_RS(op) == reg)) - return true; - if ((info & IN_RT) && (MIPS_GET_RT(op) == reg)) - return true; + if (info & inFlag) + return USAGE_INPUT; // Clobbered, so not used. - if ((info & OUT_RT) && (MIPS_GET_RT(op) == reg)) - return false; - if ((info & OUT_RD) && (MIPS_GET_RD(op) == reg)) - return false; - if ((info & OUT_RA) && (reg == MIPS_REG_RA)) - return false; + if (info & outFlag) + return USAGE_CLOBBERED; // Bail early if we hit a branch (could follow each path for continuing?) if ((info & IS_CONDBRANCH) || (info & IS_JUMP)) { @@ -708,7 +708,63 @@ namespace MIPSAnalyst { } addr += 4; } - return false; + return USAGE_UNKNOWN; + } + + static RegisterUsage DetermineRegisterUsage(MIPSGPReg reg, u32 addr, int instrs) { + switch (reg) { + case MIPS_REG_HI: + return DetermineInOutUsage(IN_HI, OUT_HI, addr, instrs); + case MIPS_REG_LO: + return DetermineInOutUsage(IN_LO, OUT_LO, addr, instrs); + case MIPS_REG_FPCOND: + return DetermineInOutUsage(IN_FPUFLAG, OUT_FPUFLAG, addr, instrs); + case MIPS_REG_VFPUCC: + return DetermineInOutUsage(IN_VFPU_CC, OUT_VFPU_CC, addr, instrs); + default: + break; + } + + if (reg > 32) { + return USAGE_UNKNOWN; + } + + u32 end = addr + instrs * sizeof(u32); + while (addr < end) { + const MIPSOpcode op = Memory::Read_Instruction(addr, true); + const MIPSInfo info = MIPSGetInfo(op); + + // Yes, used. + if ((info & IN_RS) && (MIPS_GET_RS(op) == reg)) + return USAGE_INPUT; + if ((info & IN_RT) && (MIPS_GET_RT(op) == reg)) + return USAGE_INPUT; + + // Clobbered, so not used. + if ((info & OUT_RT) && (MIPS_GET_RT(op) == reg)) + return USAGE_CLOBBERED; + if ((info & OUT_RD) && (MIPS_GET_RD(op) == reg)) + return USAGE_CLOBBERED; + if ((info & OUT_RA) && (reg == MIPS_REG_RA)) + return USAGE_CLOBBERED; + + // Bail early if we hit a branch (could follow each path for continuing?) + if ((info & IS_CONDBRANCH) || (info & IS_JUMP)) { + // Still need to check the delay slot (so end after it.) + // We'll assume likely are taken. + end = addr + 8; + } + addr += 4; + } + return USAGE_UNKNOWN; + } + + bool IsRegisterUsed(MIPSGPReg reg, u32 addr, int instrs) { + return DetermineRegisterUsage(reg, addr, instrs) == USAGE_INPUT; + } + + bool IsRegisterClobbered(MIPSGPReg reg, u32 addr, int instrs) { + return DetermineRegisterUsage(reg, addr, instrs) == USAGE_CLOBBERED; } void HashFunctions() { diff --git a/Core/MIPS/MIPSAnalyst.h b/Core/MIPS/MIPSAnalyst.h index 72c1ba892f..e78bf1456b 100644 --- a/Core/MIPS/MIPSAnalyst.h +++ b/Core/MIPS/MIPSAnalyst.h @@ -79,6 +79,8 @@ namespace MIPSAnalyst // This tells us if the reg is used within intrs of addr (also includes likely delay slots.) bool IsRegisterUsed(MIPSGPReg reg, u32 addr, int instrs); + // This tells us if the reg is clobbered within intrs of addr (e.g. it is surely not used.) + bool IsRegisterClobbered(MIPSGPReg reg, u32 addr, int instrs); struct AnalyzedFunction { u32 start; diff --git a/Core/MIPS/x86/RegCache.cpp b/Core/MIPS/x86/RegCache.cpp index d1e7db3745..46cae1862e 100644 --- a/Core/MIPS/x86/RegCache.cpp +++ b/Core/MIPS/x86/RegCache.cpp @@ -122,6 +122,30 @@ void GPRRegCache::UnlockAllX() { xregs[i].allocLocked = false; } +X64Reg GPRRegCache::FindBestToSpill(bool unusedOnly) { + int allocCount; + const int *allocOrder = GetAllocationOrder(allocCount); + + static const int UNUSED_LOOKAHEAD_OPS = 30; + + for (int i = 0; i < allocCount; i++) { + X64Reg reg = (X64Reg)allocOrder[i]; + if (xregs[reg].allocLocked) + continue; + if (xregs[reg].mipsReg != MIPS_REG_INVALID && regs[xregs[reg].mipsReg].locked) + continue; + + // Not awesome. A used reg. Let's try to avoid spilling. + if (unusedOnly && MIPSAnalyst::IsRegisterUsed(xregs[reg].mipsReg, js_->compilerPC, UNUSED_LOOKAHEAD_OPS)) { + continue; + } + + return reg; + } + + return INVALID_REG; +} + X64Reg GPRRegCache::GetFreeXReg() { int aCount; @@ -134,21 +158,18 @@ X64Reg GPRRegCache::GetFreeXReg() return (X64Reg)xr; } } - //Okay, not found :( Force grab one - //TODO - add a pass to grab xregs whose mipsreg is not used in the next 3 instructions - for (int i = 0; i < aCount; i++) - { - X64Reg xr = (X64Reg)aOrder[i]; - if (xregs[xr].allocLocked) - continue; - MIPSGPReg preg = xregs[xr].mipsReg; - if (!regs[preg].locked) - { - StoreFromRegister(preg); - return xr; - } + //Okay, not found :( Force grab one + X64Reg bestToSpill = FindBestToSpill(true); + if (bestToSpill == INVALID_REG) { + bestToSpill = FindBestToSpill(false); } + + if (bestToSpill != INVALID_REG) { + StoreFromRegister(xregs[bestToSpill].mipsReg); + return bestToSpill; + } + //Still no dice? Die! _assert_msg_(JIT, 0, "Regcache ran out of regs"); return (X64Reg) -1; diff --git a/Core/MIPS/x86/RegCache.h b/Core/MIPS/x86/RegCache.h index f74700f6f8..4dff782674 100644 --- a/Core/MIPS/x86/RegCache.h +++ b/Core/MIPS/x86/RegCache.h @@ -115,6 +115,7 @@ public: private: Gen::X64Reg GetFreeXReg(); + Gen::X64Reg FindBestToSpill(bool unusedOnly); const int *GetAllocationOrder(int &count); MIPSCachedReg regs[NUM_MIPS_GPRS];