mirror of
https://github.com/hrydgard/ppsspp.git
synced 2024-11-28 16:00:58 +00:00
444 lines
12 KiB
C++
444 lines
12 KiB
C++
// Copyright (c) 2012- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
#include <cstring>
|
|
|
|
#include "Common/x64Emitter.h"
|
|
#include "Core/Reporting.h"
|
|
#include "Core/MIPS/MIPS.h"
|
|
#include "Core/MIPS/MIPSTables.h"
|
|
#include "Core/MIPS/MIPSAnalyst.h"
|
|
#include "Core/MIPS/x86/Jit.h"
|
|
#include "Core/MIPS/x86/RegCache.h"
|
|
|
|
using namespace Gen;
|
|
using namespace X64JitConstants;
|
|
|
|
static const X64Reg allocationOrder[] = {
|
|
// R12, when used as base register, for example in a LEA, can generate bad code! Need to look into this.
|
|
// On x64, RCX and RDX are the first args. CallProtectedFunction() assumes they're not regcached.
|
|
#ifdef _M_X64
|
|
#ifdef _WIN32
|
|
RSI, RDI, R8, R9, R10, R11, R12, R13,
|
|
#else
|
|
RBP, R8, R9, R10, R11, R12, R13,
|
|
#endif
|
|
#elif _M_IX86
|
|
ESI, EDI, EDX, ECX, EBX,
|
|
#endif
|
|
};
|
|
|
|
#ifdef _M_X64
|
|
static X64Reg allocationOrderR15[ARRAY_SIZE(allocationOrder) + 1] = {INVALID_REG};
|
|
#endif
|
|
|
|
void GPRRegCache::FlushBeforeCall() {
|
|
// TODO: Only flush the non-preserved-by-callee registers.
|
|
Flush();
|
|
}
|
|
|
|
GPRRegCache::GPRRegCache() : mips(0), emit(0) {
|
|
memset(regs, 0, sizeof(regs));
|
|
memset(xregs, 0, sizeof(xregs));
|
|
}
|
|
|
|
void GPRRegCache::Start(MIPSState *mips, MIPSComp::JitState *js, MIPSComp::JitOptions *jo, MIPSAnalyst::AnalysisResults &stats) {
|
|
#ifdef _M_X64
|
|
if (allocationOrderR15[0] == INVALID_REG) {
|
|
memcpy(allocationOrderR15, allocationOrder, sizeof(allocationOrder));
|
|
allocationOrderR15[ARRAY_SIZE(allocationOrderR15) - 1] = R15;
|
|
}
|
|
#endif
|
|
|
|
this->mips = mips;
|
|
for (int i = 0; i < NUM_X_REGS; i++) {
|
|
xregs[i].free = true;
|
|
xregs[i].dirty = false;
|
|
xregs[i].allocLocked = false;
|
|
}
|
|
memset(regs, 0, sizeof(regs));
|
|
OpArg base = GetDefaultLocation(MIPS_REG_ZERO);
|
|
for (int i = 0; i < 32; i++) {
|
|
regs[i].location = base;
|
|
base.IncreaseOffset(sizeof(u32));
|
|
}
|
|
for (int i = 32; i < NUM_MIPS_GPRS; i++) {
|
|
regs[i].location = GetDefaultLocation(MIPSGPReg(i));
|
|
}
|
|
SetImm(MIPS_REG_ZERO, 0);
|
|
|
|
// todo: sort to find the most popular regs
|
|
/*
|
|
int maxPreload = 2;
|
|
for (int i = 0; i < NUM_MIPS_GPRS; i++)
|
|
{
|
|
if (stats.numReads[i] > 2 || stats.numWrites[i] >= 2)
|
|
{
|
|
LoadToX64(i, true, false); //stats.firstRead[i] <= stats.firstWrite[i], false);
|
|
maxPreload--;
|
|
if (!maxPreload)
|
|
break;
|
|
}
|
|
}*/
|
|
//Find top regs - preload them (load bursts ain't bad)
|
|
//But only preload IF written OR reads >= 3
|
|
|
|
js_ = js;
|
|
jo_ = jo;
|
|
}
|
|
|
|
|
|
// these are MIPS reg indices
|
|
void GPRRegCache::Lock(MIPSGPReg p1, MIPSGPReg p2, MIPSGPReg p3, MIPSGPReg p4) {
|
|
regs[p1].locked = true;
|
|
if (p2 != MIPS_REG_INVALID) regs[p2].locked = true;
|
|
if (p3 != MIPS_REG_INVALID) regs[p3].locked = true;
|
|
if (p4 != MIPS_REG_INVALID) regs[p4].locked = true;
|
|
}
|
|
|
|
// these are x64 reg indices
|
|
void GPRRegCache::LockX(int x1, int x2, int x3, int x4) {
|
|
if (xregs[x1].allocLocked) {
|
|
PanicAlert("RegCache: x %i already locked!", x1);
|
|
}
|
|
xregs[x1].allocLocked = true;
|
|
if (x2 != 0xFF) xregs[x2].allocLocked = true;
|
|
if (x3 != 0xFF) xregs[x3].allocLocked = true;
|
|
if (x4 != 0xFF) xregs[x4].allocLocked = true;
|
|
}
|
|
|
|
void GPRRegCache::UnlockAll() {
|
|
for (int i = 0; i < NUM_MIPS_GPRS; i++)
|
|
regs[i].locked = false;
|
|
// In case it was stored, discard it now.
|
|
SetImm(MIPS_REG_ZERO, 0);
|
|
}
|
|
|
|
void GPRRegCache::UnlockAllX() {
|
|
for (int i = 0; i < NUM_X_REGS; i++)
|
|
xregs[i].allocLocked = false;
|
|
}
|
|
|
|
X64Reg GPRRegCache::FindBestToSpill(bool unusedOnly, bool *clobbered) {
|
|
int allocCount;
|
|
const X64Reg *allocOrder = GetAllocationOrder(allocCount);
|
|
|
|
static const int UNUSED_LOOKAHEAD_OPS = 30;
|
|
|
|
*clobbered = false;
|
|
for (int i = 0; i < allocCount; i++) {
|
|
X64Reg reg = allocOrder[i];
|
|
if (xregs[reg].allocLocked)
|
|
continue;
|
|
if (xregs[reg].mipsReg != MIPS_REG_INVALID && regs[xregs[reg].mipsReg].locked)
|
|
continue;
|
|
|
|
// Awesome, a clobbered reg. Let's use it.
|
|
if (MIPSAnalyst::IsRegisterClobbered(xregs[reg].mipsReg, js_->compilerPC, UNUSED_LOOKAHEAD_OPS)) {
|
|
*clobbered = true;
|
|
return reg;
|
|
}
|
|
|
|
// 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;
|
|
const X64Reg *aOrder = GetAllocationOrder(aCount);
|
|
for (int i = 0; i < aCount; i++)
|
|
{
|
|
X64Reg xr = aOrder[i];
|
|
if (!xregs[xr].allocLocked && xregs[xr].free)
|
|
{
|
|
return xr;
|
|
}
|
|
}
|
|
|
|
//Okay, not found :( Force grab one
|
|
bool clobbered;
|
|
X64Reg bestToSpill = FindBestToSpill(true, &clobbered);
|
|
if (bestToSpill == INVALID_REG) {
|
|
bestToSpill = FindBestToSpill(false, &clobbered);
|
|
}
|
|
|
|
if (bestToSpill != INVALID_REG) {
|
|
// TODO: Broken somehow in Dante's Inferno, but most games work. Bad flags in MIPSTables somewhere?
|
|
if (clobbered) {
|
|
DiscardRegContentsIfCached(xregs[bestToSpill].mipsReg);
|
|
} else {
|
|
StoreFromRegister(xregs[bestToSpill].mipsReg);
|
|
}
|
|
return bestToSpill;
|
|
}
|
|
|
|
//Still no dice? Die!
|
|
_assert_msg_(JIT, 0, "Regcache ran out of regs");
|
|
return (X64Reg) -1;
|
|
}
|
|
|
|
void GPRRegCache::FlushR(X64Reg reg)
|
|
{
|
|
if (reg >= NUM_X_REGS)
|
|
PanicAlert("Flushing non existent reg");
|
|
else if (!xregs[reg].free)
|
|
StoreFromRegister(xregs[reg].mipsReg);
|
|
}
|
|
|
|
void GPRRegCache::FlushRemap(MIPSGPReg oldreg, MIPSGPReg newreg) {
|
|
OpArg oldLocation = regs[oldreg].location;
|
|
if (!oldLocation.IsSimpleReg()) {
|
|
PanicAlert("FlushRemap: Must already be in an x86 register");
|
|
}
|
|
|
|
X64Reg xr = oldLocation.GetSimpleReg();
|
|
|
|
if (oldreg == newreg) {
|
|
xregs[xr].dirty = true;
|
|
return;
|
|
}
|
|
|
|
StoreFromRegister(oldreg);
|
|
|
|
// Now, if newreg already was mapped somewhere, get rid of that.
|
|
DiscardRegContentsIfCached(newreg);
|
|
|
|
// Now, take over the old register.
|
|
regs[newreg].location = oldLocation;
|
|
regs[newreg].away = true;
|
|
regs[newreg].locked = true;
|
|
xregs[xr].mipsReg = newreg;
|
|
xregs[xr].dirty = true;
|
|
xregs[xr].free = false;
|
|
}
|
|
|
|
int GPRRegCache::SanityCheck() const {
|
|
for (int i = 0; i < NUM_MIPS_GPRS; i++) {
|
|
const MIPSGPReg r = MIPSGPReg(i);
|
|
if (regs[i].away) {
|
|
if (regs[i].location.IsSimpleReg()) {
|
|
Gen::X64Reg simple = regs[i].location.GetSimpleReg();
|
|
if (xregs[simple].allocLocked)
|
|
return 1;
|
|
if (xregs[simple].mipsReg != r)
|
|
return 2;
|
|
}
|
|
else if (regs[i].location.IsImm())
|
|
return 3;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void GPRRegCache::DiscardRegContentsIfCached(MIPSGPReg preg) {
|
|
if (regs[preg].away && regs[preg].location.IsSimpleReg()) {
|
|
X64Reg xr = regs[preg].location.GetSimpleReg();
|
|
xregs[xr].free = true;
|
|
xregs[xr].dirty = false;
|
|
xregs[xr].mipsReg = MIPS_REG_INVALID;
|
|
regs[preg].away = false;
|
|
if (preg == MIPS_REG_ZERO) {
|
|
regs[preg].location = Imm32(0);
|
|
} else {
|
|
regs[preg].location = GetDefaultLocation(preg);
|
|
}
|
|
}
|
|
}
|
|
|
|
void GPRRegCache::DiscardR(MIPSGPReg preg) {
|
|
if (regs[preg].away) {
|
|
if (regs[preg].location.IsSimpleReg()) {
|
|
DiscardRegContentsIfCached(preg);
|
|
} else {
|
|
regs[preg].away = false;
|
|
if (preg == MIPS_REG_ZERO) {
|
|
regs[preg].location = Imm32(0);
|
|
} else {
|
|
regs[preg].location = GetDefaultLocation(preg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void GPRRegCache::SetImm(MIPSGPReg preg, u32 immValue) {
|
|
// ZERO is always zero. Let's just make sure.
|
|
if (preg == MIPS_REG_ZERO)
|
|
immValue = 0;
|
|
|
|
DiscardRegContentsIfCached(preg);
|
|
regs[preg].away = true;
|
|
regs[preg].location = Imm32(immValue);
|
|
}
|
|
|
|
bool GPRRegCache::IsImm(MIPSGPReg preg) const {
|
|
// Note that ZERO is generally always imm.
|
|
return regs[preg].location.IsImm();
|
|
}
|
|
|
|
u32 GPRRegCache::GetImm(MIPSGPReg preg) const {
|
|
_dbg_assert_msg_(JIT, IsImm(preg), "Reg %d must be an immediate.", preg);
|
|
// Always 0 for ZERO.
|
|
if (preg == MIPS_REG_ZERO)
|
|
return 0;
|
|
return regs[preg].location.GetImmValue();
|
|
}
|
|
|
|
const X64Reg *GPRRegCache::GetAllocationOrder(int &count) {
|
|
#ifdef _M_X64
|
|
if (!jo_->reserveR15ForAsm) {
|
|
count = ARRAY_SIZE(allocationOrderR15);
|
|
return allocationOrderR15;
|
|
}
|
|
#endif
|
|
count = ARRAY_SIZE(allocationOrder);
|
|
return allocationOrder;
|
|
}
|
|
|
|
|
|
OpArg GPRRegCache::GetDefaultLocation(MIPSGPReg reg) const {
|
|
if (reg < 32) {
|
|
return MDisp(CTXREG, -128 + reg * 4);
|
|
}
|
|
switch (reg) {
|
|
case MIPS_REG_HI:
|
|
return M(&mips->hi);
|
|
case MIPS_REG_LO:
|
|
return M(&mips->lo);
|
|
case MIPS_REG_FPCOND:
|
|
return M(&mips->fpcond);
|
|
case MIPS_REG_VFPUCC:
|
|
return M(&mips->vfpuCtrl[VFPU_CTRL_CC]);
|
|
default:
|
|
ERROR_LOG_REPORT(JIT, "bad mips register %i", reg);
|
|
return M(&mips->r[0]);
|
|
}
|
|
}
|
|
|
|
|
|
void GPRRegCache::KillImmediate(MIPSGPReg preg, bool doLoad, bool makeDirty) {
|
|
if (regs[preg].away) {
|
|
if (regs[preg].location.IsImm())
|
|
MapReg(preg, doLoad, makeDirty);
|
|
else if (regs[preg].location.IsSimpleReg())
|
|
xregs[RX(preg)].dirty |= makeDirty;
|
|
}
|
|
}
|
|
|
|
void GPRRegCache::MapReg(MIPSGPReg i, bool doLoad, bool makeDirty) {
|
|
if (!regs[i].away && regs[i].location.IsImm())
|
|
PanicAlert("Bad immediate");
|
|
|
|
if (!regs[i].away || (regs[i].away && regs[i].location.IsImm())) {
|
|
X64Reg xr = GetFreeXReg();
|
|
if (xregs[xr].dirty) PanicAlert("Xreg already dirty");
|
|
if (xregs[xr].allocLocked) PanicAlert("GetFreeXReg returned locked register");
|
|
xregs[xr].free = false;
|
|
xregs[xr].mipsReg = i;
|
|
xregs[xr].dirty = makeDirty || regs[i].location.IsImm();
|
|
OpArg newloc = ::Gen::R(xr);
|
|
if (doLoad) {
|
|
// Force ZERO to be 0.
|
|
if (i == MIPS_REG_ZERO)
|
|
emit->MOV(32, newloc, Imm32(0));
|
|
else
|
|
emit->MOV(32, newloc, regs[i].location);
|
|
}
|
|
for (int j = 0; j < 32; j++) {
|
|
if (i != MIPSGPReg(j) && regs[j].location.IsSimpleReg(xr)) {
|
|
ERROR_LOG(JIT, "BindToRegister: Strange condition");
|
|
Crash();
|
|
}
|
|
}
|
|
regs[i].away = true;
|
|
regs[i].location = newloc;
|
|
} else {
|
|
// reg location must be simplereg; memory locations
|
|
// and immediates are taken care of above.
|
|
xregs[RX(i)].dirty |= makeDirty;
|
|
}
|
|
if (xregs[RX(i)].allocLocked) {
|
|
PanicAlert("Seriously WTF, this reg should have been flushed");
|
|
}
|
|
}
|
|
|
|
void GPRRegCache::StoreFromRegister(MIPSGPReg i) {
|
|
if (regs[i].away) {
|
|
bool doStore;
|
|
if (regs[i].location.IsSimpleReg()) {
|
|
X64Reg xr = RX(i);
|
|
xregs[xr].free = true;
|
|
xregs[xr].mipsReg = MIPS_REG_INVALID;
|
|
doStore = xregs[xr].dirty;
|
|
xregs[xr].dirty = false;
|
|
} else {
|
|
//must be immediate - do nothing
|
|
doStore = true;
|
|
}
|
|
OpArg newLoc = GetDefaultLocation(i);
|
|
// But never store to ZERO.
|
|
if (doStore && i != MIPS_REG_ZERO)
|
|
emit->MOV(32, newLoc, regs[i].location);
|
|
regs[i].location = newLoc;
|
|
regs[i].away = false;
|
|
}
|
|
}
|
|
|
|
void GPRRegCache::Flush() {
|
|
for (int i = 0; i < NUM_X_REGS; i++) {
|
|
if (xregs[i].allocLocked)
|
|
PanicAlert("Someone forgot to unlock X64 reg %i.", i);
|
|
}
|
|
SetImm(MIPS_REG_ZERO, 0);
|
|
for (int i = 1; i < NUM_MIPS_GPRS; i++) {
|
|
const MIPSGPReg r = MIPSGPReg(i);
|
|
if (regs[i].locked) {
|
|
PanicAlert("Somebody forgot to unlock MIPS reg %i.", i);
|
|
}
|
|
if (regs[i].away) {
|
|
if (regs[i].location.IsSimpleReg()) {
|
|
X64Reg xr = RX(r);
|
|
StoreFromRegister(r);
|
|
xregs[xr].dirty = false;
|
|
}
|
|
else if (regs[i].location.IsImm()) {
|
|
StoreFromRegister(r);
|
|
} else {
|
|
_assert_msg_(JIT,0,"Jit64 - Flush unhandled case, reg %i PC: %08x", i, mips->pc);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GPRRegCache::GetState(GPRRegCacheState &state) const {
|
|
memcpy(state.regs, regs, sizeof(regs));
|
|
memcpy(state.xregs, xregs, sizeof(xregs));
|
|
}
|
|
|
|
void GPRRegCache::RestoreState(const GPRRegCacheState& state) {
|
|
memcpy(regs, state.regs, sizeof(regs));
|
|
memcpy(xregs, state.xregs, sizeof(xregs));
|
|
}
|