Add BitSet and, as a test, convert some JitRegCache stuff to it.

This is a higher level, more concise wrapper for bitsets which supports
efficiently counting and iterating over set bits.  It's similar to
std::bitset, but the latter does not support efficient iteration (and at
least in libc++, the count algorithm is subpar, not that it really
matters).  The converted uses include both bitsets and, notably,
considerably less efficient regular arrays (for in/out registers in
PPCAnalyst).

Unfortunately, this may slightly pessimize unoptimized builds.
This commit is contained in:
comex committed 2014-10-25 16:56:51 -04:00
1 parent e51676fdf1
commit b6a7438053
8 files changed
+236 -109

No files matched your search

+13 -14
View File
@@ -736,29 +736,28 @@ const u8* Jit64::DoJit(u32 em_address, PPCAnalyst::CodeBuffer *code_buf, JitBloc
// output, which needs to be bound in the actual instruction compilation.
// TODO: make this smarter in the case that we're actually register-starved, i.e.
// prioritize the more important registers.
for (int k = 0; k < 3 && gpr.NumFreeRegisters() >= 2; k++)
for (int reg : ops[i].regsIn)
{
int reg = ops[i].regsIn[k];
if (reg >= 0 && (ops[i].gprInReg & (1 << reg)) && !gpr.R(reg).IsImm())
if (gpr.NumFreeRegisters() < 2)
break;
if (ops[i].gprInReg[reg] && !gpr.R(reg).IsImm())
gpr.BindToRegister(reg, true, false);
}
for (int k = 0; k < 4 && fpr.NumFreeRegisters() >= 2; k++)
for (int reg : ops[i].regsOut)
{
int reg = ops[i].fregsIn[k];
if (reg >= 0 && (ops[i].fprInXmm & (1 << reg)))
fpr.BindToRegister(reg, true, false);
if (fpr.NumFreeRegisters() < 2)
break;
if (ops[i].fprInXmm[reg])
gpr.BindToRegister(reg, true, false);
}
Jit64Tables::CompileInstruction(ops[i]);
// If we have a register that will never be used again, flush it.
for (int j = 0; j < 32; j++)
{
if (!(ops[i].gprInUse & (1 << j)))
gpr.StoreFromRegister(j);
if (!(ops[i].fprInUse & (1 << j)))
fpr.StoreFromRegister(j);
}
for (int j : ~ops[i].gprInUse)
gpr.StoreFromRegister(j);
for (int j : ~ops[i].fprInUse)
fpr.StoreFromRegister(j);
if (js.memcheck && (opinfo->flags & FL_LOADSTORE))
{
+16 -23
View File
@@ -95,42 +95,38 @@ void RegCache::UnlockAllX()
xreg.locked = false;
}
u32 GPRRegCache::GetRegUtilization()
BitSet32 GPRRegCache::GetRegUtilization()
{
return jit->js.op->gprInReg;
}
u32 FPURegCache::GetRegUtilization()
BitSet32 FPURegCache::GetRegUtilization()
{
return jit->js.op->gprInReg;
}
u32 GPRRegCache::CountRegsIn(size_t preg, u32 lookahead)
BitSet32 GPRRegCache::CountRegsIn(size_t preg, u32 lookahead)
{
u32 regsUsed = 0;
BitSet32 regsUsed;
for (u32 i = 1; i < lookahead; i++)
{
for (int j = 0; j < 3; j++)
if (jit->js.op[i].regsIn[j] >= 0)
regsUsed |= 1 << jit->js.op[i].regsIn[j];
for (int j = 0; j < 3; j++)
if ((size_t)jit->js.op[i].regsIn[j] == preg)
return regsUsed;
BitSet32 regsIn = jit->js.op[i].regsIn;
regsUsed |= regsIn;
if (regsIn[preg])
return regsUsed;
}
return regsUsed;
}
u32 FPURegCache::CountRegsIn(size_t preg, u32 lookahead)
BitSet32 FPURegCache::CountRegsIn(size_t preg, u32 lookahead)
{
u32 regsUsed = 0;
BitSet32 regsUsed;
for (u32 i = 1; i < lookahead; i++)
{
for (int j = 0; j < 4; j++)
if (jit->js.op[i].fregsIn[j] >= 0)
regsUsed |= 1 << jit->js.op[i].fregsIn[j];
for (int j = 0; j < 4; j++)
if ((size_t)jit->js.op[i].fregsIn[j] == preg)
return regsUsed;
BitSet32 regsIn = jit->js.op[i].fregsIn;
regsUsed |= regsIn;
if (regsIn[preg])
return regsUsed;
}
return regsUsed;
}
@@ -151,17 +147,14 @@ float RegCache::ScoreRegister(X64Reg xr)
// If the register isn't actually needed in a physical register for a later instruction,
// writing it back to the register file isn't quite as bad.
if (GetRegUtilization() & (1 << preg))
if (GetRegUtilization()[preg])
{
// Don't look too far ahead; we don't want to have quadratic compilation times for
// enormous block sizes!
// This actually improves register allocation a tiny bit; I'm not sure why.
u32 lookahead = std::min(jit->js.instructionsLeft, 64);
// Count how many other registers are going to be used before we need this one again.
u32 regs_in = CountRegsIn(preg, lookahead);
u32 regs_in_count = 0;
for (int i = 0; i < 32; i++)
regs_in_count += !!(regs_in & (1 << i));
u32 regs_in_count = CountRegsIn(preg, lookahead).Count();
// Totally ad-hoc heuristic to bias based on how many other registers we'll need
// before this one gets used again.
score += 1 + 2 * (5 - log2f(1 + (float)regs_in_count));
+6 -6
View File
@@ -44,8 +44,8 @@ protected:
virtual const int *GetAllocationOrder(size_t& count) = 0;
virtual u32 GetRegUtilization() = 0;
virtual u32 CountRegsIn(size_t preg, u32 lookahead) = 0;
virtual BitSet32 GetRegUtilization() = 0;
virtual BitSet32 CountRegsIn(size_t preg, u32 lookahead) = 0;
Gen::XEmitter *emit;
@@ -137,8 +137,8 @@ public:
Gen::OpArg GetDefaultLocation(size_t reg) const override;
const int* GetAllocationOrder(size_t& count) override;
void SetImmediate32(size_t preg, u32 immValue);
u32 GetRegUtilization();
u32 CountRegsIn(size_t preg, u32 lookahead);
BitSet32 GetRegUtilization() override;
BitSet32 CountRegsIn(size_t preg, u32 lookahead) override;
};
@@ -149,6 +149,6 @@ public:
void LoadRegister(size_t preg, Gen::X64Reg newLoc) override;
const int* GetAllocationOrder(size_t& count) override;
Gen::OpArg GetDefaultLocation(size_t reg) const override;
u32 GetRegUtilization();
u32 CountRegsIn(size_t preg, u32 lookahead);
BitSet32 GetRegUtilization() override;
BitSet32 CountRegsIn(size_t preg, u32 lookahead) override;
};
+33 -57
View File
@@ -249,21 +249,15 @@ static bool CanSwapAdjacentOps(const CodeOp &a, const CodeOp &b)
// That is, check that none of b's outputs matches any of a's inputs,
// and that none of a's outputs matches any of b's inputs.
// The latter does not apply if a is a cmp, of course, but doesn't hurt to check.
for (int j = 0; j < 3; j++)
{
int regInA = a.regsIn[j];
int regInB = b.regsIn[j];
// register collision: b outputs to one of a's inputs
if (regInA >= 0 && (b.regsOut[0] == regInA || b.regsOut[1] == regInA))
return false;
// register collision: a outputs to one of b's inputs
if (regInB >= 0 && (a.regsOut[0] == regInB || a.regsOut[1] == regInB))
return false;
// register collision: b outputs to one of a's outputs (overwriting it)
for (int k = 0; k < 2; k++)
if (b.regsOut[k] >= 0 && (b.regsOut[k] == a.regsOut[0] || b.regsOut[k] == a.regsOut[1]))
return false;
}
// register collision: b outputs to one of a's inputs
if (b.regsOut & a.regsIn)
return false;
// register collision: a outputs to one of b's inputs
if (a.regsOut & b.regsIn)
return false;
// register collision: b outputs to one of a's outputs (overwriting it)
if (b.regsOut & a.regsOut)
return false;
return true;
}
@@ -520,42 +514,41 @@ void PPCAnalyzer::SetInstructionStats(CodeBlock *block, CodeOp *code, GekkoOPInf
if (code->inst.OPCD == 31 && code->inst.SUBOP10 == 467) // mtspr
code->outputCA = ((code->inst.SPRU << 5) | (code->inst.SPRL & 0x1F)) == SPR_XER;
int numOut = 0;
int numIn = 0;
int numFloatIn = 0;
code->regsIn = BitSet32(0);
code->regsOut = BitSet32(0);
if (opinfo->flags & FL_OUT_A)
{
code->regsOut[numOut++] = code->inst.RA;
code->regsOut[code->inst.RA] = true;
block->m_gpa->SetOutputRegister(code->inst.RA, index);
}
if (opinfo->flags & FL_OUT_D)
{
code->regsOut[numOut++] = code->inst.RD;
code->regsOut[code->inst.RD] = true;
block->m_gpa->SetOutputRegister(code->inst.RD, index);
}
if (opinfo->flags & FL_OUT_S)
{
code->regsOut[numOut++] = code->inst.RS;
code->regsOut[code->inst.RS] = true;
block->m_gpa->SetOutputRegister(code->inst.RS, index);
}
if ((opinfo->flags & FL_IN_A) || ((opinfo->flags & FL_IN_A0) && code->inst.RA != 0))
{
code->regsIn[numIn++] = code->inst.RA;
code->regsIn[code->inst.RA] = true;
block->m_gpa->SetInputRegister(code->inst.RA, index);
}
if (opinfo->flags & FL_IN_B)
{
code->regsIn[numIn++] = code->inst.RB;
code->regsIn[code->inst.RB] = true;
block->m_gpa->SetInputRegister(code->inst.RB, index);
}
if (opinfo->flags & FL_IN_C)
{
code->regsIn[numIn++] = code->inst.RC;
code->regsIn[code->inst.RC] = true;
block->m_gpa->SetInputRegister(code->inst.RC, index);
}
if (opinfo->flags & FL_IN_S)
{
code->regsIn[numIn++] = code->inst.RS;
code->regsIn[code->inst.RS] = true;
block->m_gpa->SetInputRegister(code->inst.RS, index);
}
@@ -564,24 +557,17 @@ void PPCAnalyzer::SetInstructionStats(CodeBlock *block, CodeOp *code, GekkoOPInf
code->fregOut = code->inst.FD;
else if (opinfo->flags & FL_OUT_FLOAT_S)
code->fregOut = code->inst.FS;
code->fregsIn = BitSet32(0);
if (opinfo->flags & FL_IN_FLOAT_A)
code->fregsIn[numFloatIn++] = code->inst.FA;
code->fregsIn[code->inst.FA] = true;
if (opinfo->flags & FL_IN_FLOAT_B)
code->fregsIn[numFloatIn++] = code->inst.FB;
code->fregsIn[code->inst.FB] = true;
if (opinfo->flags & FL_IN_FLOAT_C)
code->fregsIn[numFloatIn++] = code->inst.FC;
code->fregsIn[code->inst.FC] = true;
if (opinfo->flags & FL_IN_FLOAT_D)
code->fregsIn[numFloatIn++] = code->inst.FD;
code->fregsIn[code->inst.FD] = true;
if (opinfo->flags & FL_IN_FLOAT_S)
code->fregsIn[numFloatIn++] = code->inst.FS;
// Set remaining register slots as unused (-1)
for (int j = numIn; j < 3; j++)
code->regsIn[j] = -1;
for (int j = numOut; j < 2; j++)
code->regsOut[j] = -1;
for (int j = numFloatIn; j < 4; j++)
code->fregsIn[j] = -1;
code->fregsIn[code->inst.FS] = true;
switch (opinfo->type)
{
@@ -797,7 +783,7 @@ u32 PPCAnalyzer::Analyze(u32 address, CodeBlock *block, CodeBuffer *buffer, u32
// Scan for flag dependencies; assume the next block (or any branch that can leave the block)
// wants flags, to be safe.
bool wantsCR0 = true, wantsCR1 = true, wantsFPRF = true, wantsCA = true;
u32 fprInUse = 0, gprInUse = 0, gprInReg = 0, fprInXmm = 0;
BitSet32 fprInUse, gprInUse, gprInReg, fprInXmm;
for (int i = block->m_num_instructions - 1; i >= 0; i--)
{
bool opWantsCR0 = code[i].wantsCR0;
@@ -822,30 +808,20 @@ u32 PPCAnalyzer::Analyze(u32 address, CodeBlock *block, CodeBuffer *buffer, u32
code[i].fprInXmm = fprInXmm;
// TODO: if there's no possible endblocks or exceptions in between, tell the regcache
// we can throw away a register if it's going to be overwritten later.
for (int j = 0; j < 3; j++)
if (code[i].regsIn[j] >= 0)
{
gprInUse |= 1 << code[i].regsIn[j];
gprInReg |= 1 << code[i].regsIn[j];
}
for (int j = 0; j < 4; j++)
if (code[i].fregsIn[j] >= 0)
{
fprInUse |= 1 << code[i].fregsIn[j];
if (strncmp(code[i].opinfo->opname, "stfd", 4))
fprInXmm |= 1 << code[i].fregsIn[j];
}
gprInUse |= code[i].regsIn;
gprInReg |= code[i].regsIn;
fprInUse |= code[i].fregsIn;
if (strncmp(code[i].opinfo->opname, "stfd", 4))
fprInXmm |= code[i].fregsIn;
// For now, we need to count output registers as "used" though; otherwise the flush
// will result in a redundant store (e.g. store to regcache, then store again to
// the same location later).
for (int j = 0; j < 2; j++)
if (code[i].regsOut[j] >= 0)
gprInUse |= 1 << code[i].regsOut[j];
gprInUse |= code[i].regsOut;
if (code[i].fregOut >= 0)
{
fprInUse |= 1 << code[i].fregOut;
fprInUse[code[i].fregOut] = true;
if (strncmp(code[i].opinfo->opname, "stfd", 4))
fprInXmm |= 1 << code[i].fregOut;
fprInXmm[code[i].fregOut] = true;
}
}
return address;
+8 -7
View File
@@ -10,6 +10,7 @@
#include <string>
#include <vector>
#include "Common/BitSet.h"
#include "Common/CommonTypes.h"
#include "Core/PowerPC/PPCTables.h"
@@ -26,10 +27,10 @@ struct CodeOp //16B
u32 address;
u32 branchTo; //if 0, not a branch
int branchToIndex; //index of target block
s8 regsOut[2];
s8 regsIn[3];
BitSet32 regsOut;
BitSet32 regsIn;
BitSet32 fregsIn;
s8 fregOut;
s8 fregsIn[4];
bool isBranchTarget;
bool wantsCR0;
bool wantsCR1;
@@ -43,13 +44,13 @@ struct CodeOp //16B
bool canEndBlock;
bool skip; // followed BL-s for example
// which registers are still needed after this instruction in this block
u32 fprInUse;
u32 gprInUse;
BitSet32 fprInUse;
BitSet32 gprInUse;
// just because a register is in use doesn't mean we actually need or want it in an x86 register.
u32 gprInReg;
BitSet32 gprInReg;
// we do double stores from GPRs, so we don't want to load a PowerPC floating point register into
// an XMM only to move it again to a GPR afterwards.
u32 fprInXmm;
BitSet32 fprInXmm;
};
struct BlockStats