riscv: Add FPU regcache.

This commit is contained in:
Unknown W. Brackets 2023-07-24 20:48:17 -07:00
parent a8edf5fa24
commit 067a033dc0
9 changed files with 516 additions and 41 deletions

View file

@ -180,7 +180,7 @@ void Arm64RegCache::MapRegTo(ARM64Reg reg, MIPSGPReg mipsReg, int mapFlags) {
ar[reg].isDirty = (mapFlags & MAP_DIRTY) ? true : false;
if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) {
if (mipsReg == MIPS_REG_ZERO) {
// If we get a request to load the zero register, at least we won't spend
// If we get a request to map the zero register, at least we won't spend
// time on a memory access...
emit_->MOVI2R(reg, 0);

View file

@ -319,6 +319,7 @@ void Arm64RegCacheFPU::FlushR(MIPSReg r) {
if (mr[r].reg == INVALID_REG) {
ERROR_LOG(JIT, "FlushR: MipsReg had bad ArmReg");
}
FlushArmReg((ARM64Reg)(S0 + mr[r].reg));
break;
case ML_MEM:
@ -329,8 +330,6 @@ void Arm64RegCacheFPU::FlushR(MIPSReg r) {
//BAD
break;
}
mr[r].loc = ML_MEM;
mr[r].reg = (int)INVALID_REG;
}
Arm64Gen::ARM64Reg Arm64RegCacheFPU::ARM64RegForFlush(int r) {

View file

@ -17,8 +17,6 @@
#pragma once
#pragma once
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/ARM64/Arm64RegCache.h"
#include "Core/MIPS/MIPSVFPUUtils.h"
@ -165,7 +163,6 @@ private:
MIPSComp::JitOptions *jo_;
int numARMFpuReg_;
int qTime_;
enum {
// On ARM64, each of the 32 registers are full 128-bit. No sharing of components!

View file

@ -26,7 +26,7 @@ namespace MIPSComp {
using namespace RiscVGen;
using namespace RiscVJitConstants;
RiscVJit::RiscVJit(MIPSState *mipsState) : IRJit(mipsState), gpr(mipsState, &jo) {
RiscVJit::RiscVJit(MIPSState *mipsState) : IRJit(mipsState), gpr(mipsState, &jo), fpr(mipsState, &jo) {
// Automatically disable incompatible options.
if (((intptr_t)Memory::base & 0x00000000FFFFFFFFUL) != 0) {
jo.enablePointerify = false;
@ -40,7 +40,7 @@ RiscVJit::RiscVJit(MIPSState *mipsState) : IRJit(mipsState), gpr(mipsState, &jo)
memset(blockStartAddrs_, 0, sizeof(blockStartAddrs_[0]) * MAX_ALLOWED_JIT_BLOCKS);
gpr.Init(this);
// TODO: fpr
fpr.Init(this);
GenerateFixedCode(jo);
}
@ -79,7 +79,7 @@ bool RiscVJit::CompileBlock(u32 em_address, std::vector<IRInst> &instructions, u
blockStartAddrs_[block_num] = GetCodePointer();
gpr.Start();
// TODO: fpr.
fpr.Start();
for (const IRInst &inst : instructions) {
CompileIRInst(inst);
@ -87,9 +87,8 @@ bool RiscVJit::CompileBlock(u32 em_address, std::vector<IRInst> &instructions, u
if (jo.Disabled(JitDisable::REGALLOC_GPR)) {
gpr.FlushAll();
}
// TODO
if (jo.Disabled(JitDisable::REGALLOC_FPR)) {
//fpr.FlushAll();
fpr.FlushAll();
}
// Safety check, in case we get a bunch of really large jit ops without a lot of branching.
@ -419,7 +418,7 @@ void RiscVJit::CompIR_Generic(IRInst inst) {
void RiscVJit::FlushAll() {
gpr.FlushAll();
// TODO: fpr.
fpr.FlushAll();
}
bool RiscVJit::DescribeCodePtr(const u8 *ptr, std::string &name) {

View file

@ -24,6 +24,7 @@
#include "Core/MIPS/JitCommon/JitState.h"
#include "Core/MIPS/JitCommon/JitCommon.h"
#include "Core/MIPS/RiscV/RiscVRegCache.h"
#include "Core/MIPS/RiscV/RiscVRegCacheFPU.h"
namespace MIPSComp {
@ -113,6 +114,7 @@ private:
RiscVGen::RiscVReg NormalizeR(IRRegIndex rs, IRRegIndex rd, RiscVGen::RiscVReg tempReg);
RiscVRegCache gpr;
RiscVRegCacheFPU fpr;
static constexpr int MAX_ALLOWED_JIT_BLOCKS = 262144;

View file

@ -15,15 +15,15 @@
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#ifndef offsetof
#include <cstddef>
#endif
#include "Common/CPUDetect.h"
#include "Core/MIPS/RiscV/RiscVRegCache.h"
#include "Core/MIPS/JitCommon/JitState.h"
#include "Core/Reporting.h"
#ifndef offsetof
#include "stddef.h"
#endif
using namespace RiscVGen;
using namespace RiscVJitConstants;
@ -36,20 +36,14 @@ void RiscVRegCache::Init(RiscVEmitter *emitter) {
}
void RiscVRegCache::Start() {
for (int i = 0; i < NUM_RVREG; i++) {
ar[i].mipsReg = IRREG_INVALID;
ar[i].isDirty = false;
ar[i].pointerified = false;
ar[i].tempLocked = false;
ar[i].normalized32 = false;
}
for (int i = 0; i < NUM_MIPSREG; i++) {
mr[i].loc = MIPSLoc::MEM;
mr[i].reg = INVALID_REG;
mr[i].imm = -1;
mr[i].spillLock = false;
mr[i].isStatic = false;
if (!initialReady_) {
SetupInitialRegs();
initialReady_ = true;
}
memcpy(ar, arInitial_, sizeof(ar));
memcpy(mr, mrInitial_, sizeof(mr));
int numStatics;
const StaticAllocation *statics = GetStaticAllocations(numStatics);
for (int i = 0; i < numStatics; i++) {
@ -61,24 +55,41 @@ void RiscVRegCache::Start() {
mr[statics[i].mr].isStatic = true;
mr[statics[i].mr].spillLock = true;
}
}
void RiscVRegCache::SetupInitialRegs() {
for (int i = 0; i < NUM_RVREG; i++) {
arInitial_[i].mipsReg = IRREG_INVALID;
arInitial_[i].isDirty = false;
arInitial_[i].pointerified = false;
arInitial_[i].tempLocked = false;
arInitial_[i].normalized32 = false;
}
for (int i = 0; i < NUM_MIPSREG; i++) {
mrInitial_[i].loc = MIPSLoc::MEM;
mrInitial_[i].reg = INVALID_REG;
mrInitial_[i].imm = -1;
mrInitial_[i].spillLock = false;
mrInitial_[i].isStatic = false;
}
// Treat R_ZERO a bit specially, but it's basically static alloc too.
ar[R_ZERO].mipsReg = MIPS_REG_ZERO;
ar[R_ZERO].normalized32 = true;
mr[MIPS_REG_ZERO].loc = MIPSLoc::RVREG_IMM;
mr[MIPS_REG_ZERO].reg = R_ZERO;
mr[MIPS_REG_ZERO].imm = 0;
mr[MIPS_REG_ZERO].isStatic = true;
arInitial_[R_ZERO].mipsReg = MIPS_REG_ZERO;
arInitial_[R_ZERO].normalized32 = true;
mrInitial_[MIPS_REG_ZERO].loc = MIPSLoc::RVREG_IMM;
mrInitial_[MIPS_REG_ZERO].reg = R_ZERO;
mrInitial_[MIPS_REG_ZERO].imm = 0;
mrInitial_[MIPS_REG_ZERO].isStatic = true;
}
const RiscVReg *RiscVRegCache::GetMIPSAllocationOrder(int &count) {
// X8 and X9 are the most ideal for static alloc because they can be used with compression.
// Otherwise we stick to saved regs - might not be necessary.
static const RiscVReg allocationOrder[] = {
X7, X8, X9, X12, X13, X14, X5, X6, X15, X16, X17, X18, X19, X20, X21, X22, X23, X28, X29, X30, X31,
X8, X9, X12, X13, X14, X15, X5, X6, X7, X16, X17, X18, X19, X20, X21, X22, X23, X28, X29, X30, X31,
};
static const RiscVReg allocationOrderStaticAlloc[] = {
X7, X12, X13, X14, X5, X6, X15, X16, X17, X21, X22, X23, X28, X29, X30, X31,
X12, X13, X14, X15, X5, X6, X7, X16, X17, X21, X22, X23, X28, X29, X30, X31,
};
if (jo_->useStaticAlloc) {
@ -432,6 +443,7 @@ RiscVReg RiscVRegCache::GetAndLockTempR() {
RiscVReg reg = AllocateReg();
if (reg != INVALID_REG) {
ar[reg].tempLocked = true;
pendingUnlock_ = true;
}
return reg;
}
@ -1008,9 +1020,13 @@ void RiscVRegCache::SpillLock(IRRegIndex r1, IRRegIndex r2, IRRegIndex r3, IRReg
if (r2 != IRREG_INVALID) mr[r2].spillLock = true;
if (r3 != IRREG_INVALID) mr[r3].spillLock = true;
if (r4 != IRREG_INVALID) mr[r4].spillLock = true;
pendingUnlock_ = true;
}
void RiscVRegCache::ReleaseSpillLocksAndDiscardTemps() {
if (!pendingUnlock_)
return;
for (int i = 0; i < NUM_MIPSREG; i++) {
if (!mr[i].isStatic)
mr[i].spillLock = false;
@ -1018,6 +1034,8 @@ void RiscVRegCache::ReleaseSpillLocksAndDiscardTemps() {
for (int i = 0; i < NUM_RVREG; i++) {
ar[i].tempLocked = false;
}
pendingUnlock_ = false;
}
void RiscVRegCache::ReleaseSpillLock(IRRegIndex r1, IRRegIndex r2, IRRegIndex r3, IRRegIndex r4) {

View file

@ -68,10 +68,6 @@ enum class MapType {
} // namespace RiscVJitConstants
namespace MIPSAnalyst {
struct AnalysisResults;
};
namespace MIPSComp {
struct JitOptions;
}
@ -175,6 +171,8 @@ private:
bool IsValidReg(IRRegIndex r) const;
bool IsValidRegNoZero(IRRegIndex r) const;
void SetupInitialRegs();
MIPSState *mips_;
RiscVGen::RiscVEmitter *emit_ = nullptr;
MIPSComp::JitOptions *jo_;
@ -186,4 +184,9 @@ private:
RegStatusRiscV ar[NUM_RVREG]{};
RegStatusMIPS mr[NUM_MIPSREG]{};
bool initialReady_ = false;
bool pendingUnlock_ = false;
RegStatusRiscV arInitial_[NUM_RVREG];
RegStatusMIPS mrInitial_[NUM_MIPSREG];
};

View file

@ -14,3 +14,375 @@
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#ifndef offsetof
#include <cstddef>
#endif
#include "Common/CPUDetect.h"
#include "Core/MIPS/RiscV/RiscVRegCacheFPU.h"
#include "Core/MIPS/JitCommon/JitState.h"
#include "Core/Reporting.h"
using namespace RiscVGen;
using namespace RiscVJitConstants;
using namespace RiscVGen;
using namespace RiscVJitConstants;
RiscVRegCacheFPU::RiscVRegCacheFPU(MIPSState *mipsState, MIPSComp::JitOptions *jo)
: mips_(mipsState), jo_(jo) {}
void RiscVRegCacheFPU::Init(RiscVEmitter *emitter) {
emit_ = emitter;
}
void RiscVRegCacheFPU::Start() {
if (!initialReady_) {
SetupInitialRegs();
initialReady_ = true;
}
memcpy(ar, arInitial_, sizeof(ar));
memcpy(mr, mrInitial_, sizeof(mr));
pendingFlush_ = false;
}
void RiscVRegCacheFPU::SetupInitialRegs() {
for (int i = 0; i < NUM_RVFPUREG; i++) {
ar[i].mipsReg = IRREG_INVALID;
ar[i].isDirty = false;
}
for (int i = 0; i < NUM_MIPSFPUREG; i++) {
mr[i].loc = MIPSLoc::MEM;
mr[i].reg = (int)INVALID_REG;
mr[i].spillLock = false;
}
}
const RiscVReg *RiscVRegCacheFPU::GetMIPSAllocationOrder(int &count) {
// F8 through F15 are used for compression, so they are great.
// TODO: Maybe we could remove some saved regs since we rarely need that many? Or maybe worth it?
static const RiscVReg allocationOrder[] = {
F8, F9, F10, F11, F12, F13, F14, F15,
F0, F1, F2, F3, F4, F5, F6, F7,
F16, F17, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27, F28, F29, F30, F31,
};
count = ARRAY_SIZE(allocationOrder);
return allocationOrder;
}
bool RiscVRegCacheFPU::IsInRAM(IRRegIndex reg) {
_dbg_assert_(IsValidReg(reg));
return mr[reg].loc == MIPSLoc::MEM;
}
bool RiscVRegCacheFPU::IsMapped(IRRegIndex mipsReg) {
_dbg_assert_(IsValidReg(mipsReg));
return mr[mipsReg].loc == MIPSLoc::RVREG;
}
RiscVReg RiscVRegCacheFPU::MapReg(IRRegIndex mipsReg, MIPSMap mapFlags) {
_dbg_assert_(IsValidReg(mipsReg));
_dbg_assert_(mr[mipsReg].loc == MIPSLoc::MEM || mr[mipsReg].loc == MIPSLoc::RVREG);
pendingFlush_ = true;
// Let's see if it's already mapped. If so we just need to update the dirty flag.
// We don't need to check for NOINIT because we assume that anyone who maps
// with that flag immediately writes a "known" value to the register.
if (mr[mipsReg].loc == MIPSLoc::RVREG) {
_assert_msg_(ar[mr[mipsReg].reg].mipsReg == mipsReg, "GPU mapping out of sync, IR=%i", mipsReg);
if ((mapFlags & MIPSMap::DIRTY) == MIPSMap::DIRTY) {
ar[mr[mipsReg].reg].isDirty = true;
}
return (RiscVReg)(mr[mipsReg].reg + F0);
}
// Okay, not mapped, so we need to allocate an RV register.
RiscVReg reg = AllocateReg();
if (reg != INVALID_REG) {
// That means it's free. Grab it, and load the value into it (if requested).
ar[reg - F0].isDirty = (mapFlags & MIPSMap::DIRTY) == MIPSMap::DIRTY;
if ((mapFlags & MIPSMap::NOINIT) != MIPSMap::NOINIT) {
if (mr[mipsReg].loc == MIPSLoc::MEM) {
emit_->FL(32, reg, CTXREG, GetMipsRegOffset(mipsReg));
}
}
ar[reg - F0].mipsReg = mipsReg;
mr[mipsReg].loc = MIPSLoc::RVREG;
mr[mipsReg].reg = reg - F0;
return reg;
}
return reg;
}
RiscVReg RiscVRegCacheFPU::AllocateReg() {
int allocCount = 0;
const RiscVReg *allocOrder = GetMIPSAllocationOrder(allocCount);
allocate:
for (int i = 0; i < allocCount; i++) {
RiscVReg reg = allocOrder[i];
if (ar[reg - F0].mipsReg == IRREG_INVALID) {
return reg;
}
}
// Still nothing. Let's spill a reg and goto 10.
// TODO: Use age or something to choose which register to spill?
// TODO: Spill dirty regs first? or opposite?
bool clobbered;
RiscVReg bestToSpill = FindBestToSpill(true, &clobbered);
if (bestToSpill == INVALID_REG) {
bestToSpill = FindBestToSpill(false, &clobbered);
}
if (bestToSpill != INVALID_REG) {
if (clobbered) {
DiscardR(ar[bestToSpill - F0].mipsReg);
} else {
FlushRiscVReg(bestToSpill);
}
// Now one must be free.
goto allocate;
}
// Uh oh, we have all of them spilllocked....
ERROR_LOG_REPORT(JIT, "Out of spillable registers near PC %08x", mips_->pc);
_assert_(bestToSpill != INVALID_REG);
return INVALID_REG;
}
RiscVReg RiscVRegCacheFPU::FindBestToSpill(bool unusedOnly, bool *clobbered) {
int allocCount = 0;
const RiscVReg *allocOrder = GetMIPSAllocationOrder(allocCount);
static const int UNUSED_LOOKAHEAD_OPS = 30;
*clobbered = false;
for (int i = 0; i < allocCount; i++) {
RiscVReg reg = allocOrder[i];
if (ar[reg - F0].mipsReg != IRREG_INVALID && mr[ar[reg - F0].mipsReg].spillLock)
continue;
// TODO: Look for clobbering in the IRInst array with index?
// Not awesome. A used reg. Let's try to avoid spilling.
// TODO: Actually check if we'd be spilling.
if (unusedOnly) {
continue;
}
return reg;
}
return INVALID_REG;
}
void RiscVRegCacheFPU::MapInIn(IRRegIndex rd, IRRegIndex rs) {
SpillLock(rd, rs);
MapReg(rd);
MapReg(rs);
ReleaseSpillLock(rd);
ReleaseSpillLock(rs);
}
void RiscVRegCacheFPU::MapDirtyIn(IRRegIndex rd, IRRegIndex rs, bool avoidLoad) {
SpillLock(rd, rs);
bool load = !avoidLoad || rd == rs;
MapReg(rd, load ? MIPSMap::DIRTY : MIPSMap::NOINIT);
MapReg(rs);
ReleaseSpillLock(rd);
ReleaseSpillLock(rs);
}
void RiscVRegCacheFPU::MapDirtyInIn(IRRegIndex rd, IRRegIndex rs, IRRegIndex rt, bool avoidLoad) {
SpillLock(rd, rs, rt);
bool load = !avoidLoad || (rd == rs || rd == rt);
MapReg(rd, load ? MIPSMap::DIRTY : MIPSMap::NOINIT);
MapReg(rt);
MapReg(rs);
ReleaseSpillLock(rd);
ReleaseSpillLock(rs);
ReleaseSpillLock(rt);
}
void RiscVRegCacheFPU::FlushRiscVReg(RiscVReg r) {
_dbg_assert_(r >= F0 && r <= F31);
int reg = r - F0;
if (ar[reg].mipsReg == IRREG_INVALID) {
// Nothing to do, reg not mapped.
return;
}
if (ar[reg].isDirty && mr[ar[reg].mipsReg].loc == MIPSLoc::RVREG) {
emit_->FS(32, r, CTXREG, GetMipsRegOffset(ar[reg].mipsReg));
}
mr[ar[reg].mipsReg].loc = MIPSLoc::MEM;
mr[ar[reg].mipsReg].reg = (int)INVALID_REG;
ar[reg].mipsReg = IRREG_INVALID;
ar[reg].isDirty = false;
}
void RiscVRegCacheFPU::FlushR(IRRegIndex r) {
_dbg_assert_(IsValidReg(r));
RiscVReg reg = RiscVRegForFlush(r);
if (reg != INVALID_REG)
FlushRiscVReg(reg);
}
RiscVReg RiscVRegCacheFPU::RiscVRegForFlush(IRRegIndex r) {
_dbg_assert_(IsValidReg(r));
switch (mr[r].loc) {
case MIPSLoc::RVREG:
_assert_msg_(mr[r].reg != INVALID_REG, "RiscVRegForFlush: IR %d had bad RiscVReg", r);
if (mr[r].reg == INVALID_REG) {
return INVALID_REG;
}
return (RiscVReg)(F0 + mr[r].reg);
case MIPSLoc::MEM:
return INVALID_REG;
default:
_assert_(false);
return INVALID_REG;
}
}
void RiscVRegCacheFPU::FlushAll() {
if (!pendingFlush_) {
// Nothing allocated. FPU regs are not nearly as common as GPR.
return;
}
int numRVRegs = 0;
const RiscVReg *order = GetMIPSAllocationOrder(numRVRegs);
for (int i = 0; i < numRVRegs; i++) {
int a = order[i] - F0;
int m = ar[a].mipsReg;
if (ar[a].isDirty) {
_assert_(m != MIPS_REG_INVALID);
emit_->FS(32, order[i], CTXREG, GetMipsRegOffset(m));
mr[m].loc = MIPSLoc::MEM;
mr[m].reg = (int)INVALID_REG;
ar[a].mipsReg = IRREG_INVALID;
ar[a].isDirty = false;
} else {
if (m != IRREG_INVALID) {
mr[m].loc = MIPSLoc::MEM;
mr[m].reg = (int)INVALID_REG;
}
ar[a].mipsReg = IRREG_INVALID;
}
}
pendingFlush_ = false;
}
void RiscVRegCacheFPU::DiscardR(IRRegIndex r) {
_dbg_assert_(IsValidReg(r));
switch (mr[r].loc) {
case MIPSLoc::RVREG:
_assert_(mr[r].reg != INVALID_REG);
if (mr[r].reg != INVALID_REG) {
// Note that we DO NOT write it back here. That's the whole point of Discard.
ar[mr[r].reg].isDirty = false;
ar[mr[r].reg].mipsReg = IRREG_INVALID;
}
break;
case MIPSLoc::MEM:
// Already there, nothing to do.
break;
default:
_assert_(false);
break;
}
mr[r].loc = MIPSLoc::MEM;
mr[r].reg = (int)INVALID_REG;
mr[r].spillLock = false;
}
int RiscVRegCacheFPU::GetMipsRegOffset(IRRegIndex r) {
_assert_(IsValidReg(r));
// These are offsets within the MIPSState structure.
// IR gives us an index that is already 32 after the state index (skipping GPRs.)
return (32 + r) * 4;
}
void RiscVRegCacheFPU::SpillLock(IRRegIndex r1, IRRegIndex r2, IRRegIndex r3, IRRegIndex r4) {
_dbg_assert_(IsValidReg(r1));
_dbg_assert_(r2 == IRREG_INVALID || IsValidReg(r2));
_dbg_assert_(r3 == IRREG_INVALID || IsValidReg(r3));
_dbg_assert_(r4 == IRREG_INVALID || IsValidReg(r4));
mr[r1].spillLock = true;
if (r2 != IRREG_INVALID)
mr[r2].spillLock = true;
if (r3 != IRREG_INVALID)
mr[r3].spillLock = true;
if (r4 != IRREG_INVALID)
mr[r4].spillLock = true;
pendingUnlock_ = true;
}
void RiscVRegCacheFPU::ReleaseSpillLocksAndDiscardTemps() {
if (!pendingUnlock_)
return;
for (int i = 0; i < NUM_MIPSFPUREG; i++) {
mr[i].spillLock = false;
}
pendingUnlock_ = false;
}
void RiscVRegCacheFPU::ReleaseSpillLock(IRRegIndex r1, IRRegIndex r2, IRRegIndex r3, IRRegIndex r4) {
_dbg_assert_(IsValidReg(r1));
_dbg_assert_(r2 == IRREG_INVALID || IsValidReg(r2));
_dbg_assert_(r3 == IRREG_INVALID || IsValidReg(r3));
_dbg_assert_(r4 == IRREG_INVALID || IsValidReg(r4));
mr[r1].spillLock = false;
if (r2 != IRREG_INVALID)
mr[r2].spillLock = false;
if (r3 != IRREG_INVALID)
mr[r3].spillLock = false;
if (r4 != IRREG_INVALID)
mr[r4].spillLock = false;
}
RiscVReg RiscVRegCacheFPU::R(IRRegIndex mipsReg) {
_dbg_assert_(IsValidReg(mipsReg));
_dbg_assert_(mr[mipsReg].loc == MIPSLoc::RVREG);
if (mr[mipsReg].loc == MIPSLoc::RVREG) {
return (RiscVReg)(mr[mipsReg].reg + F0);
} else {
ERROR_LOG_REPORT(JIT, "Reg %i not in riscv reg", mipsReg);
return INVALID_REG; // BAAAD
}
}
bool RiscVRegCacheFPU::IsValidReg(IRRegIndex r) const {
if (r < 0 || r >= NUM_MIPSFPUREG)
return false;
// See MIPSState for these offsets.
int index = r + 32;
// Allow FPU or VFPU regs here.
if (index >= 32 && index < 32 + 128)
return true;
// Also allow VFPU temps.
if (index >= 224 && index < 224 + 16)
return true;
// Nothing else is allowed for the FPU side cache.
return false;
}

View file

@ -16,3 +16,88 @@
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
#include "Common/RiscVEmitter.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/RiscV/RiscVRegCache.h"
struct FPURegStatusRiscV {
int mipsReg; // if -1, no mipsreg attached.
bool isDirty; // Should the register be written back?
};
struct FPURegStatusMIPS {
// Where is this MIPS register?
RiscVJitConstants::MIPSLoc loc;
// Index from F0.
int reg;
bool spillLock; // if true, this register cannot be spilled.
// If loc == ML_MEM, it's back in its location in the CPU context struct.
};
namespace MIPSComp {
struct JitOptions;
}
class RiscVRegCacheFPU {
public:
RiscVRegCacheFPU(MIPSState *mipsState, MIPSComp::JitOptions *jo);
~RiscVRegCacheFPU() {}
void Init(RiscVGen::RiscVEmitter *emitter);
// TODO: Maybe pass in IR block and start PC for logging/debugging?
void Start();
// Protect the RISC-V register containing a MIPS register from spilling, to ensure that
// it's being kept allocated.
void SpillLock(IRRegIndex reg, IRRegIndex reg2 = IRREG_INVALID, IRRegIndex reg3 = IRREG_INVALID, IRRegIndex reg4 = IRREG_INVALID);
void ReleaseSpillLock(IRRegIndex reg, IRRegIndex reg2 = IRREG_INVALID, IRRegIndex reg3 = IRREG_INVALID, IRRegIndex reg4 = IRREG_INVALID);
void ReleaseSpillLocksAndDiscardTemps();
// Returns a RISC-V register containing the requested MIPS register.
RiscVGen::RiscVReg MapReg(IRRegIndex reg, RiscVJitConstants::MIPSMap mapFlags = RiscVJitConstants::MIPSMap::INIT);
bool IsMapped(IRRegIndex r);
bool IsInRAM(IRRegIndex r);
void MapInIn(IRRegIndex rd, IRRegIndex rs);
void MapDirtyIn(IRRegIndex rd, IRRegIndex rs, bool avoidLoad = true);
void MapDirtyInIn(IRRegIndex rd, IRRegIndex rs, IRRegIndex rt, bool avoidLoad = true);
void FlushAll();
void FlushR(IRRegIndex r);
void DiscardR(IRRegIndex r);
RiscVGen::RiscVReg R(int preg); // Returns a cached register
private:
const RiscVGen::RiscVReg *GetMIPSAllocationOrder(int &count);
RiscVGen::RiscVReg AllocateReg();
RiscVGen::RiscVReg FindBestToSpill(bool unusedOnly, bool *clobbered);
RiscVGen::RiscVReg RiscVRegForFlush(IRRegIndex r);
void FlushRiscVReg(RiscVGen::RiscVReg r);
int GetMipsRegOffset(IRRegIndex r);
bool IsValidReg(IRRegIndex r) const;
void SetupInitialRegs();
MIPSState *mips_;
RiscVGen::RiscVEmitter *emit_ = nullptr;
MIPSComp::JitOptions *jo_;
enum {
// On RiscV, each of the 32 registers are full 128-bit. No sharing of components!
NUM_RVFPUREG = 32,
NUM_MIPSFPUREG = RiscVJitConstants::TOTAL_MAPPABLE_MIPSREGS - 32,
};
FPURegStatusRiscV ar[NUM_RVFPUREG];
FPURegStatusMIPS mr[NUM_MIPSFPUREG];
bool pendingFlush_ = false;
bool pendingUnlock_ = false;
bool initialReady_ = false;
FPURegStatusRiscV arInitial_[NUM_RVFPUREG];
FPURegStatusMIPS mrInitial_[NUM_MIPSFPUREG];
};