riscv: Centralize reg allocation.

This commit is contained in:
Unknown W. Brackets 2023-08-17 18:50:33 -07:00
parent b30daa5760
commit ebab0e1591
6 changed files with 421 additions and 435 deletions

View File

@ -21,6 +21,8 @@
#include <cstring>
#include "Common/Log.h"
#include "Common/LogReporting.h"
#include "Core/MIPS/IR/IRAnalysis.h"
#include "Core/MIPS/IR/IRRegCache.h"
#include "Core/MIPS/IR/IRInst.h"
#include "Core/MIPS/IR/IRJit.h"
@ -90,10 +92,10 @@ void IRImmRegCache::MapDirtyInIn(IRReg rd, IRReg rs, IRReg rt) {
Flush(rt);
}
IRNativeRegCache::IRNativeRegCache(MIPSComp::JitOptions *jo)
IRNativeRegCacheBase::IRNativeRegCacheBase(MIPSComp::JitOptions *jo)
: jo_(jo) {}
void IRNativeRegCache::Start(MIPSComp::IRBlock *irBlock) {
void IRNativeRegCacheBase::Start(MIPSComp::IRBlock *irBlock) {
if (!initialReady_) {
SetupInitialRegs();
initialReady_ = true;
@ -109,7 +111,7 @@ void IRNativeRegCache::Start(MIPSComp::IRBlock *irBlock) {
nr[statics[i].nr].mipsReg = statics[i].mr;
nr[statics[i].nr].pointerified = statics[i].pointerified && jo_->enablePointerify;
nr[statics[i].nr].normalized32 = statics[i].normalized32;
mr[statics[i].mr].loc = MIPSLoc::REG;
mr[statics[i].mr].loc = statics[i].loc;
mr[statics[i].mr].nReg = statics[i].nr;
mr[statics[i].mr].isStatic = true;
// Lock it until the very end.
@ -120,10 +122,242 @@ void IRNativeRegCache::Start(MIPSComp::IRBlock *irBlock) {
irIndex_ = 0;
}
void IRNativeRegCache::SetupInitialRegs() {
void IRNativeRegCacheBase::SetupInitialRegs() {
_assert_msg_(totalNativeRegs_ > 0, "totalNativeRegs_ was never set by backend");
// Everything else is initialized in the struct.
mrInitial_[MIPS_REG_ZERO].loc = MIPSLoc::IMM;
mrInitial_[MIPS_REG_ZERO].imm = 0;
}
IRNativeReg IRNativeRegCacheBase::AllocateReg(MIPSLoc type) {
_dbg_assert_(type == MIPSLoc::REG || type == MIPSLoc::FREG || type == MIPSLoc::VREG);
IRNativeReg nreg = FindFreeReg(type);
if (nreg != -1)
return nreg;
// Still nothing. Let's spill a reg and goto 10.
bool clobbered;
IRNativeReg bestToSpill = FindBestToSpill(type, true, &clobbered);
if (bestToSpill == -1) {
bestToSpill = FindBestToSpill(type, false, &clobbered);
}
if (bestToSpill != -1) {
if (clobbered) {
DiscardNativeReg(bestToSpill);
} else {
FlushNativeReg(bestToSpill);
}
// Now one must be free.
return FindFreeReg(type);
}
// Uh oh, we have all of them spilllocked....
ERROR_LOG_REPORT(JIT, "Out of spillable registers in block PC %08x, index %d", irBlock_->GetOriginalStart(), irIndex_);
_assert_(bestToSpill != -1);
return -1;
}
IRNativeReg IRNativeRegCacheBase::FindFreeReg(MIPSLoc type) const {
int allocCount = 0, base = 0;
const int *allocOrder = GetAllocationOrder(type, allocCount, base);
for (int i = 0; i < allocCount; i++) {
IRNativeReg nreg = IRNativeReg(allocOrder[i] - base);
if (nr[nreg].mipsReg == IRREG_INVALID) {
return nreg;
}
}
return -1;
}
IRNativeReg IRNativeRegCacheBase::FindBestToSpill(MIPSLoc type, bool unusedOnly, bool *clobbered) const {
int allocCount = 0, base = 0;
const int *allocOrder = GetAllocationOrder(type, allocCount, base);
static const int UNUSED_LOOKAHEAD_OPS = 30;
IRSituation info;
info.lookaheadCount = UNUSED_LOOKAHEAD_OPS;
info.currentIndex = irIndex_;
info.instructions = irBlock_->GetInstructions();
info.numInstructions = irBlock_->GetNumInstructions();
auto getUsage = [type, &info](IRReg mipsReg) {
if (type == MIPSLoc::REG)
return IRNextGPRUsage(mipsReg, info);
else if (type == MIPSLoc::FREG || type == MIPSLoc::VREG)
return IRNextFPRUsage(mipsReg - 32, info);
_assert_msg_(false, "Unknown spill allocation type");
return IRUsage::UNKNOWN;
};
*clobbered = false;
for (int i = 0; i < allocCount; i++) {
IRNativeReg nreg = IRNativeReg(allocOrder[i] - base);
if (nr[nreg].mipsReg != IRREG_INVALID && mr[nr[nreg].mipsReg].spillLockIRIndex >= irIndex_)
continue;
if (nr[nreg].tempLockIRIndex >= irIndex_)
continue;
// As it's in alloc-order, we know it's not static so we don't need to check for that.
IRReg mipsReg = nr[nreg].mipsReg;
IRUsage usage = getUsage(mipsReg);
// Awesome, a clobbered reg. Let's use it?
if (usage == IRUsage::CLOBBERED) {
// If multiple mips regs use this native reg (i.e. vector, HI/LO), check each.
// Note: mipsReg points to the lowest numbered IRReg.
bool canClobber = true;
for (IRReg m = mipsReg + 1; mr[m].nReg == nreg && m < IRREG_INVALID && canClobber; ++m)
canClobber = getUsage(mipsReg) == IRUsage::CLOBBERED;
// Okay, if all can be clobbered, we're good to go.
if (canClobber) {
*clobbered = true;
return nreg;
}
}
// Not awesome. A used reg. Let's try to avoid spilling.
if (!unusedOnly || usage == IRUsage::UNUSED) {
// TODO: Use age or something to choose which register to spill?
// TODO: Spill dirty regs first? or opposite?
return nreg;
}
}
return -1;
}
void IRNativeRegCacheBase::DiscardNativeReg(IRNativeReg nreg) {
_assert_msg_(nreg >= 0 && nreg < totalNativeRegs_, "DiscardNativeReg on invalid register %d", nreg);
if (nr[nreg].mipsReg != IRREG_INVALID) {
_assert_(nr[nreg].mipsReg != MIPS_REG_ZERO);
int8_t lanes = 0;
for (IRReg m = nr[nreg].mipsReg; mr[m].nReg == nreg && m < IRREG_INVALID; ++m)
lanes++;
if (mr[nr[nreg].mipsReg].isStatic) {
int numStatics;
const StaticAllocation *statics = GetStaticAllocations(numStatics);
// If it's not currently marked as in a reg, throw it away.
for (IRReg m = nr[nreg].mipsReg; m < nr[nreg].mipsReg + lanes; ++m) {
_assert_msg_(!mr[m].isStatic, "Reg in lane %d mismatched static status", m - nr[nreg].mipsReg);
for (int i = 0; i < numStatics; i++) {
if (m == statics[i].mr)
mr[m].loc = statics[i].loc;
}
}
} else {
for (IRReg m = nr[nreg].mipsReg; m < nr[nreg].mipsReg + lanes; ++m) {
mr[m].loc = MIPSLoc::MEM;
mr[m].nReg = -1;
mr[m].imm = 0;
_assert_msg_(!mr[m].isStatic, "Reg in lane %d mismatched static status", m - nr[nreg].mipsReg);
}
nr[nreg].mipsReg = IRREG_INVALID;
}
}
// Even for a static reg, we assume this means it's not pointerified anymore.
nr[nreg].pointerified = false;
nr[nreg].isDirty = false;
nr[nreg].normalized32 = false;
}
void IRNativeRegCacheBase::FlushNativeReg(IRNativeReg nreg) {
_assert_msg_(nreg >= 0 && nreg < totalNativeRegs_, "FlushNativeReg on invalid register %d", nreg);
if (nr[nreg].mipsReg == IRREG_INVALID || nr[nreg].mipsReg == MIPS_REG_ZERO) {
// Nothing to do, reg not mapped or mapped to fixed zero.
_dbg_assert_(!nr[nreg].isDirty);
return;
}
_dbg_assert_(!mr[nr[nreg].mipsReg].isStatic);
if (mr[nr[nreg].mipsReg].isStatic) {
ERROR_LOG(JIT, "Cannot FlushNativeReg a statically mapped register");
return;
}
// Multiple mipsRegs may match this if a vector or HI/LO, etc.
bool isDirty = nr[nreg].isDirty;
int8_t lanes = 0;
for (IRReg m = nr[nreg].mipsReg; mr[m].nReg == nreg && m < IRREG_INVALID; ++m) {
_dbg_assert_(!mr[m].isStatic);
lanes++;
}
if (isDirty) {
IRReg first = nr[nreg].mipsReg;
if (mr[first].loc == MIPSLoc::REG_AS_PTR) {
// We assume this can't be multiple lanes. Maybe some gather craziness?
_assert_(lanes == 1);
AdjustNativeRegAsPtr(nreg, false);
mr[first].loc = MIPSLoc::REG;
}
StoreNativeReg(nreg, first, lanes);
}
for (int8_t i = 0; i < lanes; ++i) {
auto &mreg = mr[nr[nreg].mipsReg + i];
mreg.nReg = -1;
// Note that it loses its imm status, because imms are always dirty.
mreg.loc = MIPSLoc::MEM;
mreg.imm = 0;
}
nr[nreg].mipsReg = IRREG_INVALID;
nr[nreg].isDirty = false;
nr[nreg].pointerified = false;
nr[nreg].normalized32 = false;
}
void IRNativeRegCacheBase::AdjustNativeRegAsPtr(IRNativeReg nreg, bool state) {
// This isn't necessary to implement if REG_AS_PTR is unsupported entirely.
_assert_msg_(false, "AdjustNativeRegAsPtr unimplemented");
}
bool IRNativeRegCacheBase::IsValidGPR(IRReg r) const {
// See MIPSState for these offsets.
// Don't allow FPU regs, VFPU regs, or VFPU temps here.
if (r >= 32 && IsValidFPR(r - 32))
return false;
// Don't allow nextPC, etc. since it's probably a mistake.
if (r > IRREG_FPCOND && r != IRREG_LLBIT)
return false;
// Don't allow PC either.
if (r == 241)
return false;
return true;
}
bool IRNativeRegCacheBase::IsValidGPRNoZero(IRReg r) const {
return IsValidGPR(r) && r != MIPS_REG_ZERO;
}
bool IRNativeRegCacheBase::IsValidFPR(IRReg r) const {
// FPR parameters are off by 32 within the MIPSState object.
if (r >= TOTAL_MAPPABLE_IRREGS - 32)
return false;
// See MIPSState for these offsets.
int index = r + 32;
// Allow FPU or VFPU regs here.
if (index >= 32 && index < 32 + 32 + 128)
return true;
// Also allow VFPU temps.
if (index >= 224 && index < 224 + 16)
return true;
// Nothing else is allowed for the FPU side.
return false;
}

View File

@ -30,6 +30,8 @@ constexpr int TOTAL_MAPPABLE_IRREGS = 256;
// Arbitrary - increase if your backend has more.
constexpr int TOTAL_POSSIBLE_NATIVEREGS = 128;
typedef int8_t IRNativeReg;
constexpr IRReg IRREG_INVALID = 255;
class IRWriter;
@ -75,7 +77,7 @@ private:
IRWriter *ir_;
};
class IRNativeRegCache {
class IRNativeRegCacheBase {
protected:
enum class MIPSLoc {
// Known immediate value (only in regcache.)
@ -95,10 +97,10 @@ protected:
};
struct RegStatusMIPS {
// Where is this IR/MIPS register?
// Where is this IR/MIPS register? Note: base reg if vector.
MIPSLoc loc = MIPSLoc::MEM;
// If in a register, what index (into nr array)?
int8_t nReg = -1;
IRNativeReg nReg = -1;
// If a known immediate value, what value?
uint64_t imm = 0;
// Locked from spilling (i.e. used by current instruction) as of what IR instruction?
@ -125,7 +127,9 @@ protected:
struct StaticAllocation {
IRReg mr;
int8_t nr;
IRNativeReg nr;
// Register type.
MIPSLoc loc;
// Whether the reg should be marked pointerified by default.
bool pointerified = false;
// Whether the reg should be considered always normalized at the start of a block.
@ -133,8 +137,8 @@ protected:
};
public:
IRNativeRegCache(MIPSComp::JitOptions *jo);
virtual ~IRNativeRegCache() {}
IRNativeRegCacheBase(MIPSComp::JitOptions *jo);
virtual ~IRNativeRegCacheBase() {}
virtual void Start(MIPSComp::IRBlock *irBlock);
void SetIRIndex(int index) {
@ -143,13 +147,26 @@ public:
protected:
virtual void SetupInitialRegs();
virtual const StaticAllocation *GetStaticAllocations(int &count) {
virtual const int *GetAllocationOrder(MIPSLoc type, int &count, int &base) const = 0;
virtual const StaticAllocation *GetStaticAllocations(int &count) const {
count = 0;
return nullptr;
}
IRNativeReg AllocateReg(MIPSLoc type);
IRNativeReg FindFreeReg(MIPSLoc type) const;
IRNativeReg FindBestToSpill(MIPSLoc type, bool unusedOnly, bool *clobbered) const;
virtual void DiscardNativeReg(IRNativeReg nreg);
virtual void FlushNativeReg(IRNativeReg nreg);
virtual void AdjustNativeRegAsPtr(IRNativeReg nreg, bool state);
virtual void StoreNativeReg(IRNativeReg nreg, IRReg first, int lanes) = 0;
bool IsValidGPR(IRReg r) const;
bool IsValidGPRNoZero(IRReg r) const;
bool IsValidFPR(IRReg r) const;
MIPSComp::JitOptions *jo_;
MIPSComp::IRBlock *irBlock_ = nullptr;
const MIPSComp::IRBlock *irBlock_ = nullptr;
int irIndex_ = 0;
int totalNativeRegs_ = 0;

View File

@ -30,7 +30,7 @@ using namespace RiscVGen;
using namespace RiscVJitConstants;
RiscVRegCache::RiscVRegCache(MIPSComp::JitOptions *jo)
: IRNativeRegCache(jo) {
: IRNativeRegCacheBase(jo) {
// TODO: Move to using for FPRs and VPRs too?
totalNativeRegs_ = NUM_RVREG;
}
@ -40,7 +40,7 @@ void RiscVRegCache::Init(RiscVEmitter *emitter) {
}
void RiscVRegCache::SetupInitialRegs() {
IRNativeRegCache::SetupInitialRegs();
IRNativeRegCacheBase::SetupInitialRegs();
// Treat R_ZERO a bit specially, but it's basically static alloc too.
nrInitial_[R_ZERO].mipsReg = MIPS_REG_ZERO;
@ -53,16 +53,18 @@ void RiscVRegCache::SetupInitialRegs() {
mrInitial_[MIPS_REG_ZERO].isStatic = true;
}
const RiscVReg *RiscVRegCache::GetMIPSAllocationOrder(int &count) {
const int *RiscVRegCache::GetAllocationOrder(MIPSLoc type, int &count, int &base) const {
_assert_(type == MIPSLoc::REG);
// 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[] = {
static const int allocationOrder[] = {
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[] = {
static const int allocationOrderStaticAlloc[] = {
X12, X13, X14, X15, X5, X6, X7, X16, X17, X21, X22, X23, X28, X29, X30, X31,
};
base = X0;
if (jo_->useStaticAlloc) {
count = ARRAY_SIZE(allocationOrderStaticAlloc);
return allocationOrderStaticAlloc;
@ -72,20 +74,20 @@ const RiscVReg *RiscVRegCache::GetMIPSAllocationOrder(int &count) {
}
}
const RiscVRegCache::StaticAllocation *RiscVRegCache::GetStaticAllocations(int &count) {
const RiscVRegCache::StaticAllocation *RiscVRegCache::GetStaticAllocations(int &count) const {
static const StaticAllocation allocs[] = {
{ MIPS_REG_SP, X8, true },
{ MIPS_REG_V0, X9 },
{ MIPS_REG_V1, X18 },
{ MIPS_REG_A0, X19 },
{ MIPS_REG_RA, X20 },
{ MIPS_REG_SP, X8, MIPSLoc::REG, true },
{ MIPS_REG_V0, X9, MIPSLoc::REG },
{ MIPS_REG_V1, X18, MIPSLoc::REG },
{ MIPS_REG_A0, X19, MIPSLoc::REG },
{ MIPS_REG_RA, X20, MIPSLoc::REG },
};
if (jo_->useStaticAlloc) {
count = ARRAY_SIZE(allocs);
return allocs;
}
return IRNativeRegCache::GetStaticAllocations(count);
return IRNativeRegCacheBase::GetStaticAllocations(count);
}
void RiscVRegCache::EmitLoadStaticRegisters() {
@ -115,28 +117,28 @@ void RiscVRegCache::EmitSaveStaticRegisters() {
void RiscVRegCache::FlushBeforeCall() {
// These registers are not preserved by function calls.
for (int i = 5; i <= 7; ++i) {
FlushRiscVReg(RiscVReg(X0 + i));
FlushNativeReg(i);
}
for (int i = 10; i <= 17; ++i) {
FlushRiscVReg(RiscVReg(X0 + i));
FlushNativeReg(i);
}
for (int i = 28; i <= 31; ++i) {
FlushRiscVReg(RiscVReg(X0 + i));
FlushNativeReg(i);
}
}
bool RiscVRegCache::IsInRAM(IRReg reg) {
_dbg_assert_(IsValidReg(reg));
_dbg_assert_(IsValidGPR(reg));
return mr[reg].loc == MIPSLoc::MEM;
}
bool RiscVRegCache::IsMapped(IRReg mipsReg) {
_dbg_assert_(IsValidReg(mipsReg));
_dbg_assert_(IsValidGPR(mipsReg));
return mr[mipsReg].loc == MIPSLoc::REG || mr[mipsReg].loc == MIPSLoc::REG_IMM;
}
bool RiscVRegCache::IsMappedAsPointer(IRReg mipsReg) {
_dbg_assert_(IsValidReg(mipsReg));
_dbg_assert_(IsValidGPR(mipsReg));
if (mr[mipsReg].loc == MIPSLoc::REG) {
return nr[mr[mipsReg].nReg].pointerified;
} else if (mr[mipsReg].loc == MIPSLoc::REG_IMM) {
@ -157,7 +159,7 @@ bool RiscVRegCache::IsMappedAsStaticPointer(IRReg reg) {
}
bool RiscVRegCache::IsNormalized32(IRReg mipsReg) {
_dbg_assert_(IsValidReg(mipsReg));
_dbg_assert_(IsValidGPR(mipsReg));
if (XLEN == 32)
return true;
if (mr[mipsReg].loc == MIPSLoc::REG || mr[mipsReg].loc == MIPSLoc::REG_IMM) {
@ -196,7 +198,7 @@ void RiscVRegCache::MarkPtrDirty(RiscVReg reg) {
}
RiscVGen::RiscVReg RiscVRegCache::Normalize32(IRReg mipsReg, RiscVGen::RiscVReg destReg) {
_dbg_assert_(IsValidReg(mipsReg));
_dbg_assert_(IsValidGPR(mipsReg));
_dbg_assert_(destReg == INVALID_REG || (destReg > X0 && destReg <= X31));
RiscVReg reg = (RiscVReg)mr[mipsReg].nReg;
@ -257,7 +259,7 @@ void RiscVRegCache::SetRegImm(RiscVReg reg, u64 imm) {
void RiscVRegCache::MapRegTo(RiscVReg reg, IRReg mipsReg, MIPSMap mapFlags) {
_dbg_assert_(reg > X0 && reg <= X31);
_dbg_assert_(IsValidReg(mipsReg));
_dbg_assert_(IsValidGPR(mipsReg));
_dbg_assert_(!mr[mipsReg].isStatic);
if (mr[mipsReg].isStatic) {
ERROR_LOG(JIT, "Cannot MapRegTo static register %d", mipsReg);
@ -316,88 +318,8 @@ void RiscVRegCache::MapRegTo(RiscVReg reg, IRReg mipsReg, MIPSMap mapFlags) {
mr[mipsReg].nReg = reg;
}
RiscVReg RiscVRegCache::AllocateReg() {
int allocCount;
const RiscVReg *allocOrder = GetMIPSAllocationOrder(allocCount);
allocate:
for (int i = 0; i < allocCount; i++) {
RiscVReg reg = allocOrder[i];
if (nr[reg].mipsReg == IRREG_INVALID && nr[reg].tempLockIRIndex < irIndex_) {
return reg;
}
}
// Still nothing. Let's spill a reg and goto 10.
bool clobbered;
RiscVReg bestToSpill = FindBestToSpill(true, &clobbered);
if (bestToSpill == INVALID_REG) {
bestToSpill = FindBestToSpill(false, &clobbered);
}
if (bestToSpill != INVALID_REG) {
if (clobbered) {
DiscardR(nr[bestToSpill].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 in block PC %08x, index %d", irBlock_->GetOriginalStart(), irIndex_);
_assert_(bestToSpill != INVALID_REG);
return INVALID_REG;
}
RiscVReg RiscVRegCache::FindBestToSpill(bool unusedOnly, bool *clobbered) {
int allocCount;
const RiscVReg *allocOrder = GetMIPSAllocationOrder(allocCount);
static const int UNUSED_LOOKAHEAD_OPS = 30;
IRSituation info;
info.lookaheadCount = UNUSED_LOOKAHEAD_OPS;
info.currentIndex = irIndex_;
info.instructions = irBlock_->GetInstructions();
info.numInstructions = irBlock_->GetNumInstructions();
*clobbered = false;
for (int i = 0; i < allocCount; i++) {
RiscVReg reg = allocOrder[i];
if (nr[reg].mipsReg != IRREG_INVALID && mr[nr[reg].mipsReg].spillLockIRIndex >= irIndex_)
continue;
if (nr[reg].tempLockIRIndex >= irIndex_)
continue;
// As it's in alloc-order, we know it's not static so we don't need to check for that.
IRUsage usage = IRNextGPRUsage(nr[reg].mipsReg, info);
// Awesome, a clobbered reg. Let's use it.
if (usage == IRUsage::CLOBBERED) {
// TODO: Check HI/LO clobber together if we combine.
bool canClobber = true;
if (canClobber) {
*clobbered = true;
return reg;
}
}
// Not awesome. A used reg. Let's try to avoid spilling.
if (!unusedOnly || usage == IRUsage::UNUSED) {
// TODO: Use age or something to choose which register to spill?
// TODO: Spill dirty regs first? or opposite?
return reg;
}
}
return INVALID_REG;
}
RiscVReg RiscVRegCache::TryMapTempImm(IRReg r) {
_dbg_assert_(IsValidReg(r));
_dbg_assert_(IsValidGPR(r));
// If already mapped, no need for a temporary.
if (IsMapped(r)) {
return R(r);
@ -421,7 +343,7 @@ RiscVReg RiscVRegCache::TryMapTempImm(IRReg r) {
}
RiscVReg RiscVRegCache::GetAndLockTempR() {
RiscVReg reg = AllocateReg();
RiscVReg reg = (RiscVReg)AllocateReg(MIPSLoc::REG);
if (reg != INVALID_REG) {
nr[reg].tempLockIRIndex = irIndex_;
}
@ -429,7 +351,7 @@ RiscVReg RiscVRegCache::GetAndLockTempR() {
}
RiscVReg RiscVRegCache::MapReg(IRReg mipsReg, MIPSMap mapFlags) {
_dbg_assert_(IsValidReg(mipsReg));
_dbg_assert_(IsValidGPR(mipsReg));
// TODO: Optimization to force HI/LO to be combined?
@ -521,7 +443,7 @@ RiscVReg RiscVRegCache::MapReg(IRReg mipsReg, MIPSMap mapFlags) {
}
// Okay, not mapped, so we need to allocate an RV register.
RiscVReg reg = AllocateReg();
RiscVReg reg = (RiscVReg)AllocateReg(MIPSLoc::REG);
if (reg != INVALID_REG) {
// Grab it, and load the value into it (if requested).
MapRegTo(reg, mipsReg, mapFlags);
@ -531,7 +453,7 @@ RiscVReg RiscVRegCache::MapReg(IRReg mipsReg, MIPSMap mapFlags) {
}
RiscVReg RiscVRegCache::MapRegAsPointer(IRReg reg) {
_dbg_assert_(IsValidRegNoZero(reg));
_dbg_assert_(IsValidGPRNoZero(reg));
// Already mapped.
if (mr[reg].loc == MIPSLoc::REG_AS_PTR) {
@ -639,83 +561,38 @@ void RiscVRegCache::MapDirtyDirtyInIn(IRReg rd1, IRReg rd2, IRReg rs, IRReg rt,
ReleaseSpillLock(rd1, rd2, rs, rt);
}
void RiscVRegCache::FlushRiscVReg(RiscVReg r) {
_dbg_assert_(r > X0 && r <= X31);
_dbg_assert_(nr[r].mipsReg != MIPS_REG_ZERO);
if (r == INVALID_REG) {
ERROR_LOG(JIT, "FlushRiscVReg called on invalid register %d", r);
return;
}
if (nr[r].mipsReg == IRREG_INVALID) {
// Nothing to do, reg not mapped.
_dbg_assert_(!nr[r].isDirty);
return;
}
_dbg_assert_(!mr[nr[r].mipsReg].isStatic);
if (mr[nr[r].mipsReg].isStatic) {
ERROR_LOG(JIT, "Cannot FlushRiscVReg a statically mapped register");
return;
}
auto &mreg = mr[nr[r].mipsReg];
if (mreg.loc == MIPSLoc::REG_IMM || nr[r].mipsReg == MIPS_REG_ZERO) {
// We know its immediate value, no need to STR now.
mreg.loc = MIPSLoc::IMM;
mreg.nReg = (int)INVALID_REG;
void RiscVRegCache::AdjustNativeRegAsPtr(IRNativeReg nreg, bool state) {
RiscVReg r = (RiscVReg)(X0 + nreg);
if (state) {
AddMemBase(r);
} else {
if (mreg.loc == MIPSLoc::IMM || nr[r].isDirty) {
if (mreg.loc == MIPSLoc::REG_AS_PTR) {
// Unpointerify, in case dirty.
#ifdef MASKED_PSP_MEMORY
_dbg_assert_(!nr[r].isDirty);
_dbg_assert_(!nr[nreg].isDirty);
#endif
emit_->SUB(r, r, MEMBASEREG);
mreg.loc = MIPSLoc::REG;
nr[r].normalized32 = false;
}
RiscVReg storeReg = RiscVRegForFlush(nr[r].mipsReg);
if (storeReg != INVALID_REG)
emit_->SW(storeReg, CTXREG, GetMipsRegOffset(nr[r].mipsReg));
}
mreg.loc = MIPSLoc::MEM;
mreg.nReg = (int)INVALID_REG;
mreg.imm = -1;
emit_->SUB(r, r, MEMBASEREG);
nr[nreg].normalized32 = false;
}
nr[r].isDirty = false;
nr[r].mipsReg = IRREG_INVALID;
nr[r].pointerified = false;
}
void RiscVRegCache::StoreNativeReg(IRNativeReg nreg, IRReg first, int lanes) {
RiscVReg r = (RiscVReg)(X0 + nreg);
_dbg_assert_(r > X0 && r <= X31);
_dbg_assert_(first != MIPS_REG_ZERO);
// Multilane not yet supported.
_assert_(lanes == 1);
_assert_(mr[first].loc == MIPSLoc::REG || mr[first].loc == MIPSLoc::REG_IMM);
emit_->SW(r, CTXREG, GetMipsRegOffset(first));
}
void RiscVRegCache::DiscardR(IRReg mipsReg) {
_dbg_assert_(IsValidRegNoZero(mipsReg));
_dbg_assert_(IsValidGPRNoZero(mipsReg));
if (mr[mipsReg].isStatic) {
// Simply do nothing unless it's an IMM/RVREG_IMM/RVREG_AS_PTR, in case we just switch it over to RVREG, losing the value.
RiscVReg riscvReg = (RiscVReg)mr[mipsReg].nReg;
_dbg_assert_(riscvReg != INVALID_REG);
if (mipsReg == MIPS_REG_ZERO) {
// Shouldn't happen, but in case it does.
mr[mipsReg].loc = MIPSLoc::REG_IMM;
mr[mipsReg].nReg = R_ZERO;
mr[mipsReg].imm = 0;
} else if (mr[mipsReg].loc == MIPSLoc::REG_IMM || mr[mipsReg].loc == MIPSLoc::IMM || mr[mipsReg].loc == MIPSLoc::REG_AS_PTR) {
// Ignore the imm value, restore sanity
mr[mipsReg].loc = MIPSLoc::REG;
nr[riscvReg].pointerified = false;
nr[riscvReg].isDirty = false;
nr[riscvReg].normalized32 = false;
}
DiscardNativeReg(mr[mipsReg].nReg);
return;
}
const MIPSLoc prevLoc = mr[mipsReg].loc;
if (prevLoc == MIPSLoc::REG || prevLoc == MIPSLoc::REG_IMM || prevLoc == MIPSLoc::REG_AS_PTR) {
RiscVReg riscvReg = (RiscVReg)mr[mipsReg].nReg;
_dbg_assert_(riscvReg != INVALID_REG);
nr[riscvReg].mipsReg = IRREG_INVALID;
nr[riscvReg].pointerified = false;
nr[riscvReg].isDirty = false;
nr[riscvReg].normalized32 = false;
mr[mipsReg].nReg = (int)INVALID_REG;
mr[mipsReg].loc = MIPSLoc::MEM;
mr[mipsReg].imm = -1;
DiscardNativeReg(mr[mipsReg].nReg);
}
if (prevLoc == MIPSLoc::IMM && mipsReg != MIPS_REG_ZERO) {
mr[mipsReg].loc = MIPSLoc::MEM;
@ -724,7 +601,7 @@ void RiscVRegCache::DiscardR(IRReg mipsReg) {
}
RiscVReg RiscVRegCache::RiscVRegForFlush(IRReg r) {
_dbg_assert_(IsValidReg(r));
_dbg_assert_(IsValidGPR(r));
if (mr[r].isStatic)
return INVALID_REG; // No flushing needed
@ -772,7 +649,7 @@ RiscVReg RiscVRegCache::RiscVRegForFlush(IRReg r) {
}
void RiscVRegCache::FlushR(IRReg r) {
_dbg_assert_(IsValidRegNoZero(r));
_dbg_assert_(IsValidGPRNoZero(r));
if (mr[r].isStatic) {
ERROR_LOG(JIT, "Cannot flush static reg %d", r);
return;
@ -796,10 +673,7 @@ void RiscVRegCache::FlushR(IRReg r) {
case MIPSLoc::REG:
case MIPSLoc::REG_IMM:
if (nr[mr[r].nReg].isDirty) {
RiscVReg storeReg = RiscVRegForFlush(r);
if (storeReg != INVALID_REG) {
emit_->SW(storeReg, CTXREG, GetMipsRegOffset(r));
}
StoreNativeReg(mr[r].nReg, r, 1);
nr[mr[r].nReg].isDirty = false;
}
nr[mr[r].nReg].mipsReg = IRREG_INVALID;
@ -808,17 +682,10 @@ void RiscVRegCache::FlushR(IRReg r) {
case MIPSLoc::REG_AS_PTR:
if (nr[mr[r].nReg].isDirty) {
#ifdef MASKED_PSP_MEMORY
// This is kinda bad, because we've cleared bits in it.
_dbg_assert_(!nr[mr[r].nReg].isDirty);
#endif
emit_->SUB((RiscVReg)mr[r].nReg, (RiscVReg)mr[r].nReg, MEMBASEREG);
// We set this so RiscVRegForFlush knows it's no longer a pointer.
AdjustNativeRegAsPtr(mr[r].nReg, false);
// We set this so StoreNativeReg knows it's no longer a pointer.
mr[r].loc = MIPSLoc::REG;
RiscVReg storeReg = RiscVRegForFlush(r);
if (storeReg != INVALID_REG) {
emit_->SW(storeReg, CTXREG, GetMipsRegOffset(r));
}
StoreNativeReg(mr[r].nReg, r, 1);
nr[mr[r].nReg].isDirty = false;
}
nr[mr[r].nReg].mipsReg = IRREG_INVALID;
@ -845,11 +712,10 @@ void RiscVRegCache::FlushR(IRReg r) {
void RiscVRegCache::FlushAll() {
// Note: make sure not to change the registers when flushing:
// Branching code expects the armreg to retain its value.
// Branching code expects the native reg to retain its value.
// TODO: HI/LO optimization?
// Final pass to grab any that were left behind.
for (int i = 1; i < TOTAL_MAPPABLE_IRREGS; i++) {
IRReg mipsReg = IRReg(i);
if (mr[i].isStatic) {
@ -878,7 +744,7 @@ void RiscVRegCache::FlushAll() {
ERROR_LOG(JIT, "RV reg of static %i is invalid", i);
continue;
}
} else if (IsValidRegNoZero(mipsReg)) {
} else if (IsValidGPRNoZero(mipsReg)) {
FlushR(mipsReg);
}
}
@ -907,7 +773,7 @@ void RiscVRegCache::FlushAll() {
}
void RiscVRegCache::SetImm(IRReg r, u64 immVal) {
_dbg_assert_(IsValidReg(r));
_dbg_assert_(IsValidGPR(r));
if (r == MIPS_REG_ZERO && immVal != 0) {
ERROR_LOG_REPORT(JIT, "Trying to set immediate %08x to r0", (u32)immVal);
return;
@ -943,7 +809,7 @@ void RiscVRegCache::SetImm(IRReg r, u64 immVal) {
}
bool RiscVRegCache::IsImm(IRReg r) const {
_dbg_assert_(IsValidReg(r));
_dbg_assert_(IsValidGPR(r));
if (r == MIPS_REG_ZERO)
return true;
else
@ -951,7 +817,7 @@ bool RiscVRegCache::IsImm(IRReg r) const {
}
u64 RiscVRegCache::GetImm(IRReg r) const {
_dbg_assert_(IsValidReg(r));
_dbg_assert_(IsValidGPR(r));
if (r == MIPS_REG_ZERO)
return 0;
if (mr[r].loc != MIPSLoc::IMM && mr[r].loc != MIPSLoc::REG_IMM) {
@ -961,41 +827,15 @@ u64 RiscVRegCache::GetImm(IRReg r) const {
}
int RiscVRegCache::GetMipsRegOffset(IRReg r) {
_dbg_assert_(IsValidReg(r));
_dbg_assert_(IsValidGPR(r));
return r * 4;
}
bool RiscVRegCache::IsValidReg(IRReg r) const {
if (r < 0 || r >= TOTAL_MAPPABLE_IRREGS)
return false;
// See MIPSState for these offsets.
// Don't allow FPU or VFPU regs here.
if (r >= 32 && r < 32 + 32 + 128)
return false;
// Also disallow VFPU temps.
if (r >= 224 && r < 224 + 16)
return false;
// Don't allow nextPC, etc. since it's probably a mistake.
if (r > IRREG_FPCOND && r != IRREG_LLBIT)
return false;
// Don't allow PC either.
if (r == 241)
return false;
return true;
}
bool RiscVRegCache::IsValidRegNoZero(IRReg r) const {
return IsValidReg(r) && r != MIPS_REG_ZERO;
}
void RiscVRegCache::SpillLock(IRReg r1, IRReg r2, IRReg r3, IRReg 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));
_dbg_assert_(IsValidGPR(r1));
_dbg_assert_(r2 == IRREG_INVALID || IsValidGPR(r2));
_dbg_assert_(r3 == IRREG_INVALID || IsValidGPR(r3));
_dbg_assert_(r4 == IRREG_INVALID || IsValidGPR(r4));
mr[r1].spillLockIRIndex = irIndex_;
if (r2 != IRREG_INVALID) mr[r2].spillLockIRIndex = irIndex_;
if (r3 != IRREG_INVALID) mr[r3].spillLockIRIndex = irIndex_;
@ -1003,10 +843,10 @@ void RiscVRegCache::SpillLock(IRReg r1, IRReg r2, IRReg r3, IRReg r4) {
}
void RiscVRegCache::ReleaseSpillLock(IRReg r1, IRReg r2, IRReg r3, IRReg 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));
_dbg_assert_(IsValidGPR(r1));
_dbg_assert_(r2 == IRREG_INVALID || IsValidGPR(r2));
_dbg_assert_(r3 == IRREG_INVALID || IsValidGPR(r3));
_dbg_assert_(r4 == IRREG_INVALID || IsValidGPR(r4));
if (!mr[r1].isStatic)
mr[r1].spillLockIRIndex = -1;
if (r2 != IRREG_INVALID && !mr[r2].isStatic)
@ -1018,7 +858,7 @@ void RiscVRegCache::ReleaseSpillLock(IRReg r1, IRReg r2, IRReg r3, IRReg r4) {
}
RiscVReg RiscVRegCache::R(IRReg mipsReg) {
_dbg_assert_(IsValidReg(mipsReg));
_dbg_assert_(IsValidGPR(mipsReg));
_dbg_assert_(mr[mipsReg].loc == MIPSLoc::REG || mr[mipsReg].loc == MIPSLoc::REG_IMM);
if (mr[mipsReg].loc == MIPSLoc::REG || mr[mipsReg].loc == MIPSLoc::REG_IMM) {
return (RiscVReg)mr[mipsReg].nReg;
@ -1029,7 +869,7 @@ RiscVReg RiscVRegCache::R(IRReg mipsReg) {
}
RiscVReg RiscVRegCache::RPtr(IRReg mipsReg) {
_dbg_assert_(IsValidReg(mipsReg));
_dbg_assert_(IsValidGPR(mipsReg));
_dbg_assert_(mr[mipsReg].loc == MIPSLoc::REG || mr[mipsReg].loc == MIPSLoc::REG_IMM || mr[mipsReg].loc == MIPSLoc::REG_AS_PTR);
if (mr[mipsReg].loc == MIPSLoc::REG_AS_PTR) {
return (RiscVReg)mr[mipsReg].nReg;

View File

@ -57,7 +57,7 @@ enum class MapType {
} // namespace RiscVJitConstants
class RiscVRegCache : public IRNativeRegCache {
class RiscVRegCache : public IRNativeRegCacheBase {
public:
RiscVRegCache(MIPSComp::JitOptions *jo);
@ -98,7 +98,6 @@ public:
void FlushBeforeCall();
void FlushAll();
void FlushR(IRReg r);
void FlushRiscVReg(RiscVGen::RiscVReg r);
void DiscardR(IRReg r);
RiscVGen::RiscVReg GetAndLockTempR();
@ -112,21 +111,18 @@ public:
protected:
void SetupInitialRegs() override;
const StaticAllocation *GetStaticAllocations(int &count) override;
const StaticAllocation *GetStaticAllocations(int &count) const override;
const int *GetAllocationOrder(MIPSLoc type, int &count, int &base) const override;
void AdjustNativeRegAsPtr(IRNativeReg nreg, bool state) override;
void StoreNativeReg(IRNativeReg nreg, IRReg first, int lanes) override;
private:
const RiscVGen::RiscVReg *GetMIPSAllocationOrder(int &count);
void MapRegTo(RiscVGen::RiscVReg reg, IRReg mipsReg, RiscVJitConstants::MIPSMap mapFlags);
RiscVGen::RiscVReg AllocateReg();
RiscVGen::RiscVReg FindBestToSpill(bool unusedOnly, bool *clobbered);
RiscVGen::RiscVReg RiscVRegForFlush(IRReg r);
void SetRegImm(RiscVGen::RiscVReg reg, u64 imm);
void AddMemBase(RiscVGen::RiscVReg reg);
int GetMipsRegOffset(IRReg r);
bool IsValidReg(IRReg r) const;
bool IsValidRegNoZero(IRReg r) const;
RiscVGen::RiscVEmitter *emit_ = nullptr;
enum {

View File

@ -30,7 +30,7 @@ using namespace RiscVGen;
using namespace RiscVJitConstants;
RiscVRegCacheFPU::RiscVRegCacheFPU(MIPSComp::JitOptions *jo)
: IRNativeRegCache(jo) {
: IRNativeRegCacheBase(jo) {
totalNativeRegs_ = NUM_RVFPUREG;
}
@ -38,137 +38,65 @@ void RiscVRegCacheFPU::Init(RiscVEmitter *emitter) {
emit_ = emitter;
}
const RiscVReg *RiscVRegCacheFPU::GetMIPSAllocationOrder(int &count) {
const int *RiscVRegCacheFPU::GetAllocationOrder(MIPSLoc type, int &count, int &base) const {
_assert_(type == MIPSLoc::FREG);
// 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[] = {
static const int 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);
base = F0;
return allocationOrder;
}
bool RiscVRegCacheFPU::IsInRAM(IRReg reg) {
_dbg_assert_(IsValidReg(reg));
return mr[reg].loc == MIPSLoc::MEM;
_dbg_assert_(IsValidFPR(reg));
return mr[reg + 32].loc == MIPSLoc::MEM;
}
bool RiscVRegCacheFPU::IsMapped(IRReg mipsReg) {
_dbg_assert_(IsValidReg(mipsReg));
return mr[mipsReg].loc == MIPSLoc::FREG;
_dbg_assert_(IsValidFPR(mipsReg));
return mr[mipsReg + 32].loc == MIPSLoc::FREG;
}
RiscVReg RiscVRegCacheFPU::MapReg(IRReg mipsReg, MIPSMap mapFlags) {
_dbg_assert_(IsValidReg(mipsReg));
_dbg_assert_(mr[mipsReg].loc == MIPSLoc::MEM || mr[mipsReg].loc == MIPSLoc::FREG);
_dbg_assert_(IsValidFPR(mipsReg));
_dbg_assert_(mr[mipsReg + 32].loc == MIPSLoc::MEM || mr[mipsReg + 32].loc == MIPSLoc::FREG);
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::FREG) {
_assert_msg_(nr[mr[mipsReg].nReg].mipsReg == mipsReg, "GPU mapping out of sync, IR=%i", mipsReg);
if (mr[mipsReg + 32].loc == MIPSLoc::FREG) {
_assert_msg_(nr[mr[mipsReg + 32].nReg].mipsReg == mipsReg + 32, "FPR mapping out of sync, IR=%i", mipsReg);
if ((mapFlags & MIPSMap::DIRTY) == MIPSMap::DIRTY) {
nr[mr[mipsReg].nReg].isDirty = true;
nr[mr[mipsReg + 32].nReg].isDirty = true;
}
return (RiscVReg)(mr[mipsReg].nReg + F0);
return (RiscVReg)(mr[mipsReg + 32].nReg + F0);
}
// Okay, not mapped, so we need to allocate an RV register.
RiscVReg reg = AllocateReg();
if (reg != INVALID_REG) {
IRNativeReg nreg = AllocateReg(MIPSLoc::FREG);
if (nreg != -1) {
// That means it's free. Grab it, and load the value into it (if requested).
nr[reg - F0].isDirty = (mapFlags & MIPSMap::DIRTY) == MIPSMap::DIRTY;
nr[nreg].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));
if (mr[mipsReg + 32].loc == MIPSLoc::MEM) {
emit_->FL(32, (RiscVReg)(F0 + nreg), CTXREG, GetMipsRegOffset(mipsReg + 32));
}
}
nr[reg - F0].mipsReg = mipsReg;
mr[mipsReg].loc = MIPSLoc::FREG;
mr[mipsReg].nReg = reg - F0;
return reg;
nr[nreg].mipsReg = mipsReg + 32;
mr[mipsReg + 32].loc = MIPSLoc::FREG;
mr[mipsReg + 32].nReg = nreg;
return (RiscVReg)(F0 + nreg);
}
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 (nr[reg - F0].mipsReg == IRREG_INVALID) {
return reg;
}
}
// Still nothing. Let's spill a reg and goto 10.
bool clobbered;
RiscVReg bestToSpill = FindBestToSpill(true, &clobbered);
if (bestToSpill == INVALID_REG) {
bestToSpill = FindBestToSpill(false, &clobbered);
}
if (bestToSpill != INVALID_REG) {
if (clobbered) {
DiscardR(nr[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 in block PC %08x, index %d", irBlock_->GetOriginalStart(), irIndex_);
_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;
IRSituation info;
info.lookaheadCount = UNUSED_LOOKAHEAD_OPS;
info.currentIndex = irIndex_;
info.instructions = irBlock_->GetInstructions();
info.numInstructions = irBlock_->GetNumInstructions();
*clobbered = false;
for (int i = 0; i < allocCount; i++) {
RiscVReg reg = allocOrder[i];
if (nr[reg - F0].mipsReg != IRREG_INVALID && mr[nr[reg - F0].mipsReg].spillLockIRIndex >= irIndex_)
continue;
// As it's in alloc-order, we know it's not static so we don't need to check for that.
IRUsage usage = IRNextFPRUsage(nr[reg - F0].mipsReg, info);
// Awesome, a clobbered reg. Let's use it.
if (usage == IRUsage::CLOBBERED) {
*clobbered = true;
return reg;
}
// Not awesome. A used reg. Let's try to avoid spilling.
if (!unusedOnly || usage == IRUsage::UNUSED) {
// TODO: Use age or something to choose which register to spill?
// TODO: Spill dirty regs first? or opposite?
return reg;
}
}
return INVALID_REG;
return (RiscVReg)(F0 + nreg);
}
void RiscVRegCacheFPU::MapInIn(IRReg rd, IRReg rs) {
@ -200,7 +128,7 @@ RiscVReg RiscVRegCacheFPU::MapDirtyInTemp(IRReg rd, IRReg rs, bool avoidLoad) {
bool load = !avoidLoad || rd == rs;
MapReg(rd, load ? MIPSMap::DIRTY : MIPSMap::NOINIT);
MapReg(rs);
RiscVReg temp = AllocateReg();
RiscVReg temp = (RiscVReg)(F0 + AllocateReg(MIPSLoc::FREG));
ReleaseSpillLock(rd, rs);
return temp;
}
@ -239,44 +167,40 @@ RiscVReg RiscVRegCacheFPU::Map4DirtyInTemp(IRReg rdbase, IRReg rsbase, bool avoi
MapReg(rdbase + i, load ? MIPSMap::DIRTY : MIPSMap::NOINIT);
for (int i = 0; i < 4; ++i)
MapReg(rsbase + i);
RiscVReg temp = AllocateReg();
RiscVReg temp = (RiscVReg)(F0 + AllocateReg(MIPSLoc::FREG));
for (int i = 0; i < 4; ++i)
ReleaseSpillLock(rdbase + i, rsbase + i);
return temp;
}
void RiscVRegCacheFPU::FlushRiscVReg(RiscVReg r) {
void RiscVRegCacheFPU::StoreNativeReg(IRNativeReg nreg, IRReg first, int lanes) {
RiscVReg r = (RiscVReg)(F0 + nreg);
_dbg_assert_(r >= F0 && r <= F31);
int reg = r - F0;
if (nr[reg].mipsReg == IRREG_INVALID) {
// Nothing to do, reg not mapped.
return;
// Multilane not yet supported.
_assert_(lanes == 1);
if (mr[first].loc == MIPSLoc::FREG) {
emit_->FS(32, r, CTXREG, GetMipsRegOffset(first));
} else {
_assert_msg_(mr[first].loc == MIPSLoc::FREG, "Cannot store this type: %d", (int)mr[first].loc);
}
if (nr[reg].isDirty && mr[nr[reg].mipsReg].loc == MIPSLoc::FREG) {
emit_->FS(32, r, CTXREG, GetMipsRegOffset(nr[reg].mipsReg));
}
mr[nr[reg].mipsReg].loc = MIPSLoc::MEM;
mr[nr[reg].mipsReg].nReg = (int)INVALID_REG;
nr[reg].mipsReg = IRREG_INVALID;
nr[reg].isDirty = false;
}
void RiscVRegCacheFPU::FlushR(IRReg r) {
_dbg_assert_(IsValidReg(r));
_dbg_assert_(IsValidFPR(r));
RiscVReg reg = RiscVRegForFlush(r);
if (reg != INVALID_REG)
FlushRiscVReg(reg);
FlushNativeReg((IRNativeReg)(reg - F0));
}
RiscVReg RiscVRegCacheFPU::RiscVRegForFlush(IRReg r) {
_dbg_assert_(IsValidReg(r));
switch (mr[r].loc) {
_dbg_assert_(IsValidFPR(r));
switch (mr[r + 32].loc) {
case MIPSLoc::FREG:
_assert_msg_(mr[r].nReg != INVALID_REG, "RiscVRegForFlush: IR %d had bad RiscVReg", r);
if (mr[r].nReg == INVALID_REG) {
_assert_msg_(mr[r + 32].nReg != INVALID_REG, "RiscVRegForFlush: IR %d had bad RiscVReg", r + 32);
if (mr[r + 32].nReg == INVALID_REG) {
return INVALID_REG;
}
return (RiscVReg)(F0 + mr[r].nReg);
return (RiscVReg)(F0 + mr[r + 32].nReg);
case MIPSLoc::MEM:
return INVALID_REG;
@ -295,13 +219,13 @@ void RiscVRegCacheFPU::FlushBeforeCall() {
// These registers are not preserved by function calls.
for (int i = 0; i <= 7; ++i) {
FlushRiscVReg(RiscVReg(F0 + i));
FlushNativeReg(i);
}
for (int i = 10; i <= 17; ++i) {
FlushRiscVReg(RiscVReg(F0 + i));
FlushNativeReg(i);
}
for (int i = 28; i <= 31; ++i) {
FlushRiscVReg(RiscVReg(F0 + i));
FlushNativeReg(i);
}
}
@ -311,16 +235,16 @@ void RiscVRegCacheFPU::FlushAll() {
return;
}
int numRVRegs = 0;
const RiscVReg *order = GetMIPSAllocationOrder(numRVRegs);
int numRVRegs = 0, baseIndex = 0;
const int *order = GetAllocationOrder(MIPSLoc::FREG, numRVRegs, baseIndex);
for (int i = 0; i < numRVRegs; i++) {
int a = order[i] - F0;
int a = order[i] - baseIndex;
int m = nr[a].mipsReg;
if (nr[a].isDirty) {
_assert_(m != MIPS_REG_INVALID);
emit_->FS(32, order[i], CTXREG, GetMipsRegOffset(m));
emit_->FS(32, (RiscVReg)(F0 + a), CTXREG, GetMipsRegOffset(m));
mr[m].loc = MIPSLoc::MEM;
mr[m].nReg = (int)INVALID_REG;
@ -339,15 +263,12 @@ void RiscVRegCacheFPU::FlushAll() {
}
void RiscVRegCacheFPU::DiscardR(IRReg r) {
_dbg_assert_(IsValidReg(r));
switch (mr[r].loc) {
_dbg_assert_(IsValidFPR(r));
switch (mr[r + 32].loc) {
case MIPSLoc::FREG:
_assert_(mr[r].nReg != INVALID_REG);
if (mr[r].nReg != INVALID_REG) {
// Note that we DO NOT write it back here. That's the whole point of Discard.
nr[mr[r].nReg].isDirty = false;
nr[mr[r].nReg].mipsReg = IRREG_INVALID;
}
_assert_(mr[r + 32].nReg != INVALID_REG);
// Note that we DO NOT write it back here. That's the whole point of Discard.
DiscardNativeReg(mr[r + 32].nReg);
break;
case MIPSLoc::MEM:
@ -358,77 +279,59 @@ void RiscVRegCacheFPU::DiscardR(IRReg r) {
_assert_(false);
break;
}
mr[r].loc = MIPSLoc::MEM;
mr[r].nReg = (int)INVALID_REG;
mr[r].spillLockIRIndex = -1;
mr[r + 32].loc = MIPSLoc::MEM;
mr[r + 32].nReg = -1;
mr[r + 32].spillLockIRIndex = -1;
}
int RiscVRegCacheFPU::GetMipsRegOffset(IRReg r) {
_assert_(IsValidReg(r));
_assert_(IsValidFPR(r - 32));
// 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;
return (r) * 4;
}
void RiscVRegCacheFPU::SpillLock(IRReg r1, IRReg r2, IRReg r3, IRReg 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].spillLockIRIndex = irIndex_;
_dbg_assert_(IsValidFPR(r1));
_dbg_assert_(r2 == IRREG_INVALID || IsValidFPR(r2));
_dbg_assert_(r3 == IRREG_INVALID || IsValidFPR(r3));
_dbg_assert_(r4 == IRREG_INVALID || IsValidFPR(r4));
mr[r1 + 32].spillLockIRIndex = irIndex_;
if (r2 != IRREG_INVALID)
mr[r2].spillLockIRIndex = irIndex_;
mr[r2 + 32].spillLockIRIndex = irIndex_;
if (r3 != IRREG_INVALID)
mr[r3].spillLockIRIndex = irIndex_;
mr[r3 + 32].spillLockIRIndex = irIndex_;
if (r4 != IRREG_INVALID)
mr[r4].spillLockIRIndex = irIndex_;
mr[r4 + 32].spillLockIRIndex = irIndex_;
}
void RiscVRegCacheFPU::ReleaseSpillLock(IRReg r1, IRReg r2, IRReg r3, IRReg 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].spillLockIRIndex = -1;
_dbg_assert_(IsValidFPR(r1));
_dbg_assert_(r2 == IRREG_INVALID || IsValidFPR(r2));
_dbg_assert_(r3 == IRREG_INVALID || IsValidFPR(r3));
_dbg_assert_(r4 == IRREG_INVALID || IsValidFPR(r4));
mr[r1 + 32].spillLockIRIndex = -1;
if (r2 != IRREG_INVALID)
mr[r2].spillLockIRIndex = -1;
mr[r2 + 32].spillLockIRIndex = -1;
if (r3 != IRREG_INVALID)
mr[r3].spillLockIRIndex = -1;
mr[r3 + 32].spillLockIRIndex = -1;
if (r4 != IRREG_INVALID)
mr[r4].spillLockIRIndex = -1;
mr[r4 + 32].spillLockIRIndex = -1;
}
RiscVReg RiscVRegCacheFPU::R(IRReg mipsReg) {
_dbg_assert_(IsValidReg(mipsReg));
_dbg_assert_(mr[mipsReg].loc == MIPSLoc::FREG);
if (mr[mipsReg].loc == MIPSLoc::FREG) {
return (RiscVReg)(mr[mipsReg].nReg + F0);
_dbg_assert_(IsValidFPR(mipsReg));
_dbg_assert_(mr[mipsReg + 32].loc == MIPSLoc::FREG);
if (mr[mipsReg + 32].loc == MIPSLoc::FREG) {
return (RiscVReg)(mr[mipsReg + 32].nReg + F0);
} else {
ERROR_LOG_REPORT(JIT, "Reg %i not in riscv reg", mipsReg);
return INVALID_REG; // BAAAD
}
}
bool RiscVRegCacheFPU::IsValidReg(IRReg 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 + 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;
}
void RiscVRegCacheFPU::SetupInitialRegs() {
IRNativeRegCache::SetupInitialRegs();
IRNativeRegCacheBase::SetupInitialRegs();
// TODO: Move to a shared cache?
mrInitial_[0].loc = MIPSLoc::MEM;

View File

@ -25,7 +25,7 @@ namespace MIPSComp {
struct JitOptions;
}
class RiscVRegCacheFPU : public IRNativeRegCache {
class RiscVRegCacheFPU : public IRNativeRegCacheBase {
public:
RiscVRegCacheFPU(MIPSComp::JitOptions *jo);
@ -52,23 +52,19 @@ public:
void FlushBeforeCall();
void FlushAll();
void FlushR(IRReg r);
void FlushRiscVReg(RiscVGen::RiscVReg r);
void DiscardR(IRReg r);
RiscVGen::RiscVReg R(IRReg preg); // Returns a cached register
protected:
void SetupInitialRegs() override;
const int *GetAllocationOrder(MIPSLoc type, int &count, int &base) const override;
void StoreNativeReg(IRNativeReg nreg, IRReg first, int lanes) override;
private:
const RiscVGen::RiscVReg *GetMIPSAllocationOrder(int &count);
RiscVGen::RiscVReg AllocateReg();
RiscVGen::RiscVReg FindBestToSpill(bool unusedOnly, bool *clobbered);
RiscVGen::RiscVReg RiscVRegForFlush(IRReg r);
int GetMipsRegOffset(IRReg r);
bool IsValidReg(IRReg r) const;
RiscVGen::RiscVEmitter *emit_ = nullptr;
enum {