mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-30 19:24:23 +01:00
Merge pull request #17934 from unknownbrackets/riscv-centralize
RISC-V: Centralize IR regcaches
This commit is contained in:
14 files changed
+1421
-1503
No files matched your search
@@ -523,9 +523,9 @@ void KernelObjectPool::List() {
|
||||
char buffer[256];
|
||||
if (pool[i]) {
|
||||
pool[i]->GetQuickInfo(buffer, sizeof(buffer));
|
||||
INFO_LOG(SCEKERNEL, "KO %i: %s \"%s\": %s", i + handleOffset, pool[i]->GetTypeName(), pool[i]->GetName(), buffer);
|
||||
DEBUG_LOG(SCEKERNEL, "KO %i: %s \"%s\": %s", i + handleOffset, pool[i]->GetTypeName(), pool[i]->GetName(), buffer);
|
||||
} else {
|
||||
strcpy(buffer, "WTF? Zero Pointer");
|
||||
ERROR_LOG(SCEKERNEL, "KO %i: bad object", i + handleOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+826
-1
@@ -1,7 +1,33 @@
|
||||
// Copyright (c) 2023- 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/.
|
||||
|
||||
#ifndef offsetof
|
||||
#include <cstddef>
|
||||
#endif
|
||||
|
||||
#include <cstring>
|
||||
#include "Common/Log.h"
|
||||
#include "Common/LogReporting.h"
|
||||
#include "Core/MemMap.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"
|
||||
#include "Core/MIPS/JitCommon/JitState.h"
|
||||
|
||||
void IRImmRegCache::Flush(IRReg rd) {
|
||||
if (rd == 0) {
|
||||
@@ -28,7 +54,7 @@ IRImmRegCache::IRImmRegCache(IRWriter *ir) : ir_(ir) {
|
||||
}
|
||||
|
||||
void IRImmRegCache::FlushAll() {
|
||||
for (int i = 0; i < TOTAL_MAPPABLE_MIPSREGS; i++) {
|
||||
for (int i = 0; i < TOTAL_MAPPABLE_IRREGS; i++) {
|
||||
Flush(i);
|
||||
}
|
||||
}
|
||||
@@ -67,3 +93,802 @@ void IRImmRegCache::MapDirtyInIn(IRReg rd, IRReg rs, IRReg rt) {
|
||||
Flush(rt);
|
||||
}
|
||||
|
||||
IRNativeRegCacheBase::IRNativeRegCacheBase(MIPSComp::JitOptions *jo)
|
||||
: jo_(jo) {}
|
||||
|
||||
void IRNativeRegCacheBase::Start(MIPSComp::IRBlock *irBlock) {
|
||||
if (!initialReady_) {
|
||||
SetupInitialRegs();
|
||||
initialReady_ = true;
|
||||
}
|
||||
|
||||
memcpy(nr, nrInitial_, sizeof(nr[0]) * totalNativeRegs_);
|
||||
memcpy(mr, mrInitial_, sizeof(mr));
|
||||
|
||||
int numStatics;
|
||||
const StaticAllocation *statics = GetStaticAllocations(numStatics);
|
||||
for (int i = 0; i < numStatics; i++) {
|
||||
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 = statics[i].loc;
|
||||
mr[statics[i].mr].nReg = statics[i].nr;
|
||||
mr[statics[i].mr].isStatic = true;
|
||||
// Lock it until the very end.
|
||||
mr[statics[i].mr].spillLockIRIndex = irBlock->GetNumInstructions();
|
||||
}
|
||||
|
||||
irBlock_ = irBlock;
|
||||
irIndex_ = 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool IRNativeRegCacheBase::IsGPRInRAM(IRReg gpr) {
|
||||
_dbg_assert_(IsValidGPR(gpr));
|
||||
return mr[gpr].loc == MIPSLoc::MEM;
|
||||
}
|
||||
|
||||
bool IRNativeRegCacheBase::IsFPRInRAM(IRReg fpr) {
|
||||
_dbg_assert_(IsValidFPR(fpr));
|
||||
return mr[fpr + 32].loc == MIPSLoc::MEM;
|
||||
}
|
||||
|
||||
bool IRNativeRegCacheBase::IsGPRMapped(IRReg gpr) {
|
||||
_dbg_assert_(IsValidGPR(gpr));
|
||||
return mr[gpr].loc == MIPSLoc::REG || mr[gpr].loc == MIPSLoc::REG_IMM;
|
||||
}
|
||||
|
||||
bool IRNativeRegCacheBase::IsFPRMapped(IRReg fpr) {
|
||||
_dbg_assert_(IsValidFPR(fpr));
|
||||
return mr[fpr + 32].loc == MIPSLoc::FREG || mr[fpr + 32].loc == MIPSLoc::VREG;
|
||||
}
|
||||
|
||||
bool IRNativeRegCacheBase::IsGPRMappedAsPointer(IRReg gpr) {
|
||||
_dbg_assert_(IsValidGPR(gpr));
|
||||
if (mr[gpr].loc == MIPSLoc::REG) {
|
||||
return nr[mr[gpr].nReg].pointerified;
|
||||
} else if (mr[gpr].loc == MIPSLoc::REG_IMM) {
|
||||
_assert_msg_(!nr[mr[gpr].nReg].pointerified, "Really shouldn't be pointerified here");
|
||||
} else if (mr[gpr].loc == MIPSLoc::REG_AS_PTR) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IRNativeRegCacheBase::IsGPRMappedAsStaticPointer(IRReg gpr) {
|
||||
if (IsGPRMappedAsPointer(gpr)) {
|
||||
return mr[gpr].isStatic;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IRNativeRegCacheBase::IsGPRImm(IRReg gpr) {
|
||||
_dbg_assert_(IsValidGPR(gpr));
|
||||
if (gpr == MIPS_REG_ZERO)
|
||||
return true;
|
||||
return mr[gpr].loc == MIPSLoc::IMM || mr[gpr].loc == MIPSLoc::REG_IMM;
|
||||
}
|
||||
|
||||
bool IRNativeRegCacheBase::IsGPR2Imm(IRReg base) {
|
||||
return IsGPRImm(base) && IsGPRImm(base + 1);
|
||||
}
|
||||
|
||||
uint32_t IRNativeRegCacheBase::GetGPRImm(IRReg gpr) {
|
||||
_dbg_assert_(IsValidGPR(gpr));
|
||||
if (gpr == MIPS_REG_ZERO)
|
||||
return 0;
|
||||
if (mr[gpr].loc != MIPSLoc::IMM && mr[gpr].loc != MIPSLoc::REG_IMM) {
|
||||
_assert_msg_(mr[gpr].loc == MIPSLoc::IMM || mr[gpr].loc == MIPSLoc::REG_IMM, "GPR %d not in an imm", gpr);
|
||||
}
|
||||
return mr[gpr].imm;
|
||||
}
|
||||
|
||||
uint64_t IRNativeRegCacheBase::GetGPR2Imm(IRReg base) {
|
||||
return (uint64_t)GetGPRImm(base) | ((uint64_t)GetGPRImm(base + 1) << 32);
|
||||
}
|
||||
|
||||
void IRNativeRegCacheBase::SetGPRImm(IRReg gpr, uint32_t immVal) {
|
||||
_dbg_assert_(IsValidGPR(gpr));
|
||||
if (gpr == MIPS_REG_ZERO && immVal != 0) {
|
||||
ERROR_LOG_REPORT(JIT, "Trying to set immediate %08x to r0", immVal);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mr[gpr].loc == MIPSLoc::REG_IMM && mr[gpr].imm == immVal) {
|
||||
// Already have that value, let's keep it in the reg.
|
||||
return;
|
||||
}
|
||||
|
||||
if (mr[gpr].nReg != -1) {
|
||||
// Zap existing value if cached in a reg.
|
||||
_assert_msg_(mr[gpr].lane == -1, "Should not be a multilane reg");
|
||||
DiscardNativeReg(mr[gpr].nReg);
|
||||
}
|
||||
|
||||
mr[gpr].loc = MIPSLoc::IMM;
|
||||
mr[gpr].imm = immVal;
|
||||
}
|
||||
|
||||
void IRNativeRegCacheBase::SetGPR2Imm(IRReg base, uint64_t immVal) {
|
||||
_dbg_assert_(IsValidGPRNoZero(base));
|
||||
uint32_t imm0 = (uint32_t)(immVal & 0xFFFFFFFF);
|
||||
uint32_t imm1 = (uint32_t)(immVal >> 32);
|
||||
|
||||
if (IsGPRImm(base) && IsGPRImm(base + 1) && GetGPRImm(base) == imm0 && GetGPRImm(base + 1) == imm1) {
|
||||
// Already set to this, don't bother.
|
||||
return;
|
||||
}
|
||||
|
||||
if (mr[base].nReg != -1) {
|
||||
// Zap existing value if cached in a reg.
|
||||
DiscardNativeReg(mr[base].nReg);
|
||||
if (mr[base + 1].nReg != -1)
|
||||
DiscardNativeReg(mr[base + 1].nReg);
|
||||
}
|
||||
|
||||
mr[base].loc = MIPSLoc::IMM;
|
||||
mr[base].imm = imm0;
|
||||
mr[base + 1].loc = MIPSLoc::IMM;
|
||||
mr[base + 1].imm = imm1;
|
||||
}
|
||||
|
||||
void IRNativeRegCacheBase::SpillLockGPR(IRReg r1, IRReg r2, IRReg r3, IRReg 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));
|
||||
SetSpillLockIRIndex(r1, r2, r3, r4, 0, irIndex_);
|
||||
}
|
||||
|
||||
void IRNativeRegCacheBase::SpillLockFPR(IRReg r1, IRReg r2, IRReg r3, IRReg r4) {
|
||||
_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));
|
||||
SetSpillLockIRIndex(r1, r2, r3, r4, 32, irIndex_);
|
||||
}
|
||||
|
||||
void IRNativeRegCacheBase::ReleaseSpillLockGPR(IRReg r1, IRReg r2, IRReg r3, IRReg 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));
|
||||
SetSpillLockIRIndex(r1, r2, r3, r4, 0, -1);
|
||||
}
|
||||
|
||||
void IRNativeRegCacheBase::ReleaseSpillLockFPR(IRReg r1, IRReg r2, IRReg r3, IRReg r4) {
|
||||
_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));
|
||||
SetSpillLockIRIndex(r1, r2, r3, r4, 32, -1);
|
||||
}
|
||||
|
||||
void IRNativeRegCacheBase::SetSpillLockIRIndex(IRReg r1, IRReg r2, IRReg r3, IRReg r4, int offset, int index) {
|
||||
if (!mr[r1 + offset].isStatic)
|
||||
mr[r1 + offset].spillLockIRIndex = index;
|
||||
if (r2 != IRREG_INVALID && !mr[r2 + offset].isStatic)
|
||||
mr[r2 + offset].spillLockIRIndex = index;
|
||||
if (r3 != IRREG_INVALID && !mr[r3 + offset].isStatic)
|
||||
mr[r3 + offset].spillLockIRIndex = index;
|
||||
if (r4 != IRREG_INVALID && !mr[r4 + offset].isStatic)
|
||||
mr[r4 + offset].spillLockIRIndex = index;
|
||||
}
|
||||
|
||||
void IRNativeRegCacheBase::MarkGPRDirty(IRReg gpr, bool andNormalized32) {
|
||||
_assert_(IsGPRMapped(gpr));
|
||||
if (!IsGPRMapped(gpr))
|
||||
return;
|
||||
|
||||
IRNativeReg nreg = mr[gpr].nReg;
|
||||
nr[nreg].isDirty = true;
|
||||
nr[nreg].normalized32 = andNormalized32;
|
||||
// If reg is written to, pointerification is assumed lost.
|
||||
nr[nreg].pointerified = false;
|
||||
if (mr[gpr].loc == MIPSLoc::REG_AS_PTR || mr[gpr].loc == MIPSLoc::REG_IMM) {
|
||||
mr[gpr].loc = MIPSLoc::REG;
|
||||
mr[gpr].imm = -1;
|
||||
}
|
||||
_dbg_assert_(mr[gpr].loc == MIPSLoc::REG);
|
||||
}
|
||||
|
||||
void IRNativeRegCacheBase::MarkGPRAsPointerDirty(IRReg gpr) {
|
||||
_assert_(IsGPRMappedAsPointer(gpr));
|
||||
if (!IsGPRMappedAsPointer(gpr))
|
||||
return;
|
||||
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
if (mr[gpr].loc == MIPSLoc::REG_AS_PTR) {
|
||||
_assert_msg_(false, "MarkGPRAsPointerDirty is not possible when using MASKED_PSP_MEMORY");
|
||||
}
|
||||
#endif
|
||||
|
||||
IRNativeReg nreg = mr[gpr].nReg;
|
||||
_dbg_assert_(!nr[nreg].normalized32);
|
||||
nr[nreg].isDirty = true;
|
||||
// Stays pointerified or REG_AS_PTR.
|
||||
}
|
||||
|
||||
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;
|
||||
mr[m].lane = -1;
|
||||
_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) {
|
||||
_assert_(!mr[m].isStatic);
|
||||
// If we're flushing a native reg, better not be partially in mem or an imm.
|
||||
_assert_(mr[m].loc != MIPSLoc::MEM && mr[m].loc != MIPSLoc::IMM);
|
||||
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;
|
||||
mreg.lane = -1;
|
||||
}
|
||||
|
||||
nr[nreg].mipsReg = IRREG_INVALID;
|
||||
nr[nreg].isDirty = false;
|
||||
nr[nreg].pointerified = false;
|
||||
nr[nreg].normalized32 = false;
|
||||
}
|
||||
|
||||
void IRNativeRegCacheBase::DiscardReg(IRReg mreg) {
|
||||
if (mr[mreg].isStatic) {
|
||||
DiscardNativeReg(mr[mreg].nReg);
|
||||
return;
|
||||
}
|
||||
switch (mr[mreg].loc) {
|
||||
case MIPSLoc::IMM:
|
||||
if (mreg != MIPS_REG_ZERO) {
|
||||
mr[mreg].loc = MIPSLoc::MEM;
|
||||
mr[mreg].imm = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case MIPSLoc::REG:
|
||||
case MIPSLoc::REG_AS_PTR:
|
||||
case MIPSLoc::REG_IMM:
|
||||
case MIPSLoc::FREG:
|
||||
case MIPSLoc::VREG:
|
||||
DiscardNativeReg(mr[mreg].nReg);
|
||||
break;
|
||||
|
||||
case MIPSLoc::MEM:
|
||||
// Already discarded.
|
||||
break;
|
||||
}
|
||||
mr[mreg].spillLockIRIndex = -1;
|
||||
}
|
||||
|
||||
void IRNativeRegCacheBase::FlushReg(IRReg mreg) {
|
||||
_assert_msg_(!mr[mreg].isStatic, "Cannot flush static reg %d", mreg);
|
||||
|
||||
switch (mr[mreg].loc) {
|
||||
case MIPSLoc::IMM:
|
||||
// IMM is always "dirty".
|
||||
StoreRegValue(mreg, mr[mreg].imm);
|
||||
mr[mreg].loc = MIPSLoc::MEM;
|
||||
mr[mreg].nReg = -1;
|
||||
mr[mreg].imm = 0;
|
||||
break;
|
||||
|
||||
case MIPSLoc::REG:
|
||||
case MIPSLoc::REG_IMM:
|
||||
case MIPSLoc::REG_AS_PTR:
|
||||
case MIPSLoc::FREG:
|
||||
case MIPSLoc::VREG:
|
||||
// Might be in a native reg with multiple IR regs, flush together.
|
||||
FlushNativeReg(mr[mreg].nReg);
|
||||
break;
|
||||
|
||||
case MIPSLoc::MEM:
|
||||
// Already there, nothing to do.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void IRNativeRegCacheBase::FlushAll() {
|
||||
// Note: make sure not to change the registers when flushing.
|
||||
// Branching code may expect the native reg to retain its value.
|
||||
|
||||
for (int i = 1; i < TOTAL_MAPPABLE_IRREGS; i++) {
|
||||
IRReg mipsReg = (IRReg)i;
|
||||
if (mr[i].isStatic) {
|
||||
IRNativeReg nreg = mr[i].nReg;
|
||||
// Cannot leave any IMMs in registers, not even MIPSLoc::REG_IMM.
|
||||
// Can confuse the regalloc later if this flush is mid-block
|
||||
// due to an interpreter fallback that changes the register.
|
||||
if (mr[i].loc == MIPSLoc::IMM) {
|
||||
SetNativeRegValue(mr[i].nReg, mr[i].imm);
|
||||
_assert_(IsValidGPR(mipsReg));
|
||||
mr[i].loc = MIPSLoc::REG;
|
||||
nr[nreg].pointerified = false;
|
||||
} else if (mr[i].loc == MIPSLoc::REG_IMM) {
|
||||
// The register already contains the immediate.
|
||||
if (nr[nreg].pointerified) {
|
||||
ERROR_LOG(JIT, "RVREG_IMM but pointerified. Wrong.");
|
||||
nr[nreg].pointerified = false;
|
||||
}
|
||||
mr[i].loc = MIPSLoc::REG;
|
||||
} else if (mr[i].loc == MIPSLoc::REG_AS_PTR) {
|
||||
AdjustNativeRegAsPtr(mr[i].nReg, false);
|
||||
mr[i].loc = MIPSLoc::REG;
|
||||
}
|
||||
_assert_(mr[i].nReg != -1);
|
||||
} else if (mr[i].loc != MIPSLoc::MEM) {
|
||||
FlushReg(mipsReg);
|
||||
}
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
const StaticAllocation *allocs = GetStaticAllocations(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (allocs[i].pointerified && !nr[allocs[i].nr].pointerified && jo_->enablePointerify) {
|
||||
// Re-pointerify
|
||||
if (mr[allocs[i].mr].loc == MIPSLoc::REG_IMM)
|
||||
mr[allocs[i].mr].loc = MIPSLoc::REG;
|
||||
_dbg_assert_(mr[allocs[i].mr].loc == MIPSLoc::REG);
|
||||
AdjustNativeRegAsPtr(allocs[i].nr, true);
|
||||
nr[allocs[i].nr].pointerified = true;
|
||||
} else if (!allocs[i].pointerified) {
|
||||
// If this register got pointerified on the way, mark it as not.
|
||||
// This is so that after save/reload (like in an interpreter fallback),
|
||||
// it won't be regarded as such, as it may no longer be.
|
||||
nr[allocs[i].nr].pointerified = false;
|
||||
}
|
||||
}
|
||||
// Sanity check
|
||||
for (int i = 0; i < totalNativeRegs_; i++) {
|
||||
if (nr[i].mipsReg != IRREG_INVALID && !mr[nr[i].mipsReg].isStatic) {
|
||||
ERROR_LOG_REPORT(JIT, "Flush fail: nr[%i].mipsReg=%i", i, nr[i].mipsReg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IRNativeReg IRNativeRegCacheBase::MapNativeReg(MIPSLoc type, IRReg first, int lanes, MIPSMap flags) {
|
||||
_assert_msg_(first != IRREG_INVALID, "Cannot map invalid register");
|
||||
_assert_msg_(lanes >= 1 && lanes <= 4, "Cannot map %d lanes", lanes);
|
||||
if (first == IRREG_INVALID || lanes < 0)
|
||||
return -1;
|
||||
|
||||
// Let's see if it's already mapped or we need a new reg.
|
||||
IRNativeReg nreg = mr[first].nReg;
|
||||
if (mr[first].isStatic) {
|
||||
_assert_msg_(nreg != -1, "MapIRReg on static without an nReg?");
|
||||
} else {
|
||||
switch (mr[first].loc) {
|
||||
case MIPSLoc::REG_IMM:
|
||||
case MIPSLoc::REG_AS_PTR:
|
||||
case MIPSLoc::REG:
|
||||
if (type != MIPSLoc::REG)
|
||||
nreg = AllocateReg(type);
|
||||
break;
|
||||
|
||||
case MIPSLoc::FREG:
|
||||
if (type != MIPSLoc::FREG)
|
||||
nreg = AllocateReg(type);
|
||||
break;
|
||||
|
||||
case MIPSLoc::VREG:
|
||||
if (type != MIPSLoc::VREG)
|
||||
nreg = AllocateReg(type);
|
||||
break;
|
||||
|
||||
case MIPSLoc::IMM:
|
||||
case MIPSLoc::MEM:
|
||||
nreg = AllocateReg(type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (nreg != -1) {
|
||||
// This will handle already mapped and new mappings.
|
||||
MapNativeReg(type, nreg, first, lanes, flags);
|
||||
}
|
||||
|
||||
return nreg;
|
||||
}
|
||||
|
||||
void IRNativeRegCacheBase::MapNativeReg(MIPSLoc type, IRNativeReg nreg, IRReg first, int lanes, MIPSMap flags) {
|
||||
// First, try to clean up any lane mismatches.
|
||||
// It must either be in the same nreg and lane count, or not in an nreg.
|
||||
for (int i = 0; i < lanes; ++i) {
|
||||
auto &mreg = mr[first + i];
|
||||
if (mreg.nReg != -1) {
|
||||
// How many lanes is it currently in?
|
||||
int oldlanes = 0;
|
||||
for (IRReg m = nr[mreg.nReg].mipsReg; mr[m].nReg == mreg.nReg && m < IRREG_INVALID; ++m)
|
||||
oldlanes++;
|
||||
|
||||
// We may need to flush if it goes outside or we're initing.
|
||||
bool mismatch = oldlanes != lanes || mreg.lane != (lanes == 1 ? -1 : i);
|
||||
if (mismatch) {
|
||||
_assert_msg_(!mreg.isStatic, "Cannot MapNativeReg a static reg mismatch");
|
||||
if ((flags & MIPSMap::NOINIT) != MIPSMap::NOINIT) {
|
||||
// If we need init, we have to flush mismatches.
|
||||
// TODO: Do a shuffle if interior only?
|
||||
// TODO: We may also be motivated to have multiple read-only "views" or an IRReg.
|
||||
// For example Vec4Scale v0..v3, v0..v3, v3
|
||||
FlushNativeReg(mreg.nReg);
|
||||
} else if (oldlanes != 1) {
|
||||
// Even if we don't care about the current contents, we can't discard outside.
|
||||
bool extendsBefore = mreg.lane > i;
|
||||
bool extendsAfter = i + oldlanes - mreg.lane > lanes;
|
||||
if (extendsBefore || extendsAfter)
|
||||
FlushNativeReg(mreg.nReg);
|
||||
}
|
||||
}
|
||||
|
||||
// If it's still in a different reg, either discard or possibly transfer.
|
||||
if (mreg.nReg != -1 && mreg.nReg != nreg) {
|
||||
_assert_msg_(!mreg.isStatic, "Cannot MapNativeReg a static reg to a new reg");
|
||||
if ((flags & MIPSMap::NOINIT) != MIPSMap::NOINIT) {
|
||||
// We better not be trying to map to a different nreg if it's in one now.
|
||||
// This might happen on some sort of transfer...
|
||||
// TODO: Make a direct transfer, i.e. FREG -> VREG?
|
||||
FlushNativeReg(mreg.nReg);
|
||||
} else {
|
||||
DiscardNativeReg(mreg.nReg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If somehow this is an imm and mapping to a multilane native reg (HI/LO?), we store it.
|
||||
// TODO: Could check the others are imm and be smarter, but seems an unlikely case.
|
||||
if (mreg.loc == MIPSLoc::IMM && lanes > 1) {
|
||||
if ((flags & MIPSMap::NOINIT) != MIPSMap::NOINIT)
|
||||
StoreRegValue(first + i, mreg.imm);
|
||||
mreg.loc = MIPSLoc::MEM;
|
||||
if (!mreg.isStatic)
|
||||
mreg.nReg = -1;
|
||||
mreg.imm = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Double check: everything should be in the same loc for multilane now.
|
||||
for (int i = 1; i < lanes; ++i) {
|
||||
_assert_(mr[first + i].loc == mr[first].loc);
|
||||
}
|
||||
|
||||
bool markDirty = (flags & MIPSMap::DIRTY) == MIPSMap::DIRTY;
|
||||
if (mr[first].nReg != nreg) {
|
||||
nr[nreg].isDirty = markDirty;
|
||||
nr[nreg].pointerified = false;
|
||||
nr[nreg].normalized32 = (flags & MIPSMap::MARK_NORM32) == MIPSMap::MARK_NORM32;
|
||||
}
|
||||
|
||||
// Alright, now to actually map.
|
||||
if ((flags & MIPSMap::NOINIT) != MIPSMap::NOINIT) {
|
||||
if (first == MIPS_REG_ZERO) {
|
||||
_assert_msg_(lanes == 1, "Cannot use MIPS_REG_ZERO in multilane");
|
||||
SetNativeRegValue(nreg, 0);
|
||||
mr[first].loc = MIPSLoc::REG_IMM;
|
||||
mr[first].imm = 0;
|
||||
} else {
|
||||
// Note: we checked above, everything is in the same loc if multilane.
|
||||
switch (mr[first].loc) {
|
||||
case MIPSLoc::IMM:
|
||||
_assert_msg_(lanes == 1, "Not handling multilane imm here");
|
||||
SetNativeRegValue(nreg, mr[first].imm);
|
||||
// IMM is always dirty unless static.
|
||||
if (!mr[first].isStatic)
|
||||
nr[nreg].isDirty = true;
|
||||
|
||||
// If we are mapping dirty, it means we're gonna overwrite.
|
||||
// So the imm value is no longer valid.
|
||||
if ((flags & MIPSMap::DIRTY) == MIPSMap::DIRTY)
|
||||
mr[first].loc = MIPSLoc::REG;
|
||||
else
|
||||
mr[first].loc = MIPSLoc::REG_IMM;
|
||||
break;
|
||||
|
||||
case MIPSLoc::REG_IMM:
|
||||
// If it's not dirty, we can keep it.
|
||||
_assert_msg_(type == MIPSLoc::REG, "Should have flushed this reg already");
|
||||
if ((flags & MIPSMap::DIRTY) == MIPSMap::DIRTY || lanes != 1)
|
||||
mr[first].loc = MIPSLoc::REG;
|
||||
for (int i = 1; i < lanes; ++i)
|
||||
mr[first + i].loc = type;
|
||||
break;
|
||||
|
||||
case MIPSLoc::REG_AS_PTR:
|
||||
_assert_msg_(lanes == 1, "Should have flushed before getting here");
|
||||
_assert_msg_(type == MIPSLoc::REG, "Should have flushed this reg already");
|
||||
AdjustNativeRegAsPtr(nreg, false);
|
||||
for (int i = 0; i < lanes; ++i)
|
||||
mr[first + i].loc = type;
|
||||
break;
|
||||
|
||||
case MIPSLoc::REG:
|
||||
case MIPSLoc::FREG:
|
||||
case MIPSLoc::VREG:
|
||||
// Might be flipping from FREG -> VREG or something.
|
||||
_assert_msg_(type == mr[first].loc, "Should have flushed this reg already");
|
||||
for (int i = 0; i < lanes; ++i)
|
||||
mr[first + i].loc = type;
|
||||
break;
|
||||
|
||||
case MIPSLoc::MEM:
|
||||
for (int i = 0; i < lanes; ++i)
|
||||
mr[first + i].loc = type;
|
||||
LoadNativeReg(nreg, first, lanes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < lanes; ++i)
|
||||
mr[first + i].loc = type;
|
||||
}
|
||||
|
||||
for (int i = 0; i < lanes; ++i) {
|
||||
mr[first + i].nReg = nreg;
|
||||
mr[first + i].lane = lanes == 1 ? -1 : i;
|
||||
}
|
||||
|
||||
nr[nreg].mipsReg = first;
|
||||
|
||||
if (markDirty) {
|
||||
nr[nreg].isDirty = true;
|
||||
nr[nreg].pointerified = false;
|
||||
nr[nreg].normalized32 = (flags & MIPSMap::MARK_NORM32) == MIPSMap::MARK_NORM32;
|
||||
_assert_(first != MIPS_REG_ZERO);
|
||||
} else if ((flags & MIPSMap::MARK_NORM32) == MIPSMap::MARK_NORM32) {
|
||||
nr[nreg].normalized32 = true;
|
||||
}
|
||||
}
|
||||
|
||||
IRNativeReg IRNativeRegCacheBase::MapNativeRegAsPointer(IRReg gpr) {
|
||||
_dbg_assert_(IsValidGPRNoZero(gpr));
|
||||
|
||||
// Already mapped.
|
||||
if (mr[gpr].loc == MIPSLoc::REG_AS_PTR) {
|
||||
return mr[gpr].nReg;
|
||||
}
|
||||
|
||||
IRNativeReg nreg = mr[gpr].nReg;
|
||||
if (mr[gpr].loc != MIPSLoc::REG && mr[gpr].loc != MIPSLoc::REG_IMM) {
|
||||
nreg = MapNativeReg(MIPSLoc::REG, gpr, 1, MIPSMap::INIT);
|
||||
}
|
||||
|
||||
if (mr[gpr].loc == MIPSLoc::REG || mr[gpr].loc == MIPSLoc::REG_IMM) {
|
||||
// If there was an imm attached, discard it.
|
||||
mr[gpr].loc = MIPSLoc::REG;
|
||||
mr[gpr].imm = 0;
|
||||
|
||||
if (!jo_->enablePointerify) {
|
||||
AdjustNativeRegAsPtr(nreg, true);
|
||||
mr[gpr].loc = MIPSLoc::REG_AS_PTR;
|
||||
} else if (!nr[nreg].pointerified) {
|
||||
AdjustNativeRegAsPtr(nreg, true);
|
||||
nr[nreg].pointerified = true;
|
||||
}
|
||||
} else {
|
||||
ERROR_LOG(JIT, "MapNativeRegAsPointer: MapNativeReg failed to allocate a register?");
|
||||
}
|
||||
return nreg;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
int IRNativeRegCacheBase::GetMipsRegOffset(IRReg r) {
|
||||
_dbg_assert_(IsValidGPR(r) || (r >= 32 && IsValidFPR(r - 32)));
|
||||
return r * 4;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
+195
-8
@@ -1,3 +1,20 @@
|
||||
// Copyright (c) 2023- 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/.
|
||||
|
||||
#pragma once
|
||||
|
||||
// IRImmRegCache is only to perform pre-constant folding. This is worth it to get cleaner
|
||||
@@ -7,16 +24,23 @@
|
||||
#include "Core/MIPS/MIPS.h"
|
||||
#include "Core/MIPS/IR/IRInst.h"
|
||||
|
||||
enum {
|
||||
TOTAL_MAPPABLE_MIPSREGS = 256,
|
||||
};
|
||||
|
||||
struct RegIR {
|
||||
bool isImm;
|
||||
u32 immVal;
|
||||
};
|
||||
// Have to account for all of them due to temps, etc.
|
||||
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;
|
||||
class MIPSState;
|
||||
|
||||
namespace MIPSComp {
|
||||
class IRBlock;
|
||||
struct JitOptions;
|
||||
}
|
||||
|
||||
// Transient
|
||||
class IRImmRegCache {
|
||||
@@ -43,6 +67,169 @@ public:
|
||||
private:
|
||||
void Flush(IRReg rd);
|
||||
void Discard(IRReg rd);
|
||||
RegIR reg_[TOTAL_MAPPABLE_MIPSREGS];
|
||||
|
||||
struct RegIR {
|
||||
bool isImm;
|
||||
u32 immVal;
|
||||
};
|
||||
|
||||
RegIR reg_[TOTAL_MAPPABLE_IRREGS];
|
||||
IRWriter *ir_;
|
||||
};
|
||||
|
||||
// Initing is the default so the flag is reversed.
|
||||
enum class MIPSMap {
|
||||
INIT = 0,
|
||||
DIRTY = 1,
|
||||
NOINIT = 2 | DIRTY,
|
||||
MARK_NORM32 = 4,
|
||||
};
|
||||
static inline MIPSMap operator |(const MIPSMap &lhs, const MIPSMap &rhs) {
|
||||
return MIPSMap((int)lhs | (int)rhs);
|
||||
}
|
||||
static inline MIPSMap operator &(const MIPSMap &lhs, const MIPSMap &rhs) {
|
||||
return MIPSMap((int)lhs & (int)rhs);
|
||||
}
|
||||
|
||||
class IRNativeRegCacheBase {
|
||||
protected:
|
||||
enum class MIPSLoc {
|
||||
// Known immediate value (only in regcache.)
|
||||
IMM,
|
||||
// In a general reg.
|
||||
REG,
|
||||
// In a general reg, but an adjusted pointer (not pointerified - unaligned.)
|
||||
REG_AS_PTR,
|
||||
// In a general reg, but also has a known immediate value.
|
||||
REG_IMM,
|
||||
// In a native floating-point reg.
|
||||
FREG,
|
||||
// In a native vector reg. Note: if FREGs and VREGS overlap, just use FREG.
|
||||
VREG,
|
||||
// Away in memory (in the mips context struct.)
|
||||
MEM,
|
||||
};
|
||||
|
||||
struct RegStatusMIPS {
|
||||
// Where is this IR/MIPS register? Note: base reg if vector.
|
||||
MIPSLoc loc = MIPSLoc::MEM;
|
||||
// If in a register, what index (into nr array)?
|
||||
IRNativeReg nReg = -1;
|
||||
// If a known immediate value, what value?
|
||||
uint32_t imm = 0;
|
||||
// Locked from spilling (i.e. used by current instruction) as of what IR instruction?
|
||||
int spillLockIRIndex = -1;
|
||||
// If in a multipart reg (vector or HI/LO), which lane?
|
||||
int lane = -1;
|
||||
// Whether this reg is statically allocated.
|
||||
bool isStatic = false;
|
||||
};
|
||||
struct RegStatusNative {
|
||||
// Which IR/MIPS reg is this currently holding?
|
||||
IRReg mipsReg = IRREG_INVALID;
|
||||
// Locked either as temp or direct reg as of what IR instruction?
|
||||
int tempLockIRIndex = -1;
|
||||
// Should the register be written back?
|
||||
bool isDirty = false;
|
||||
// Upper part of the register is used for "pointerification".
|
||||
// Depending on backend, this may not be used or some/all operations may work on the lower 32 bits.
|
||||
bool pointerified = false;
|
||||
// Upper part of the register has a normalized form (i.e. zero or sign extend.)
|
||||
// Which this means or if it matters depends on the backend.
|
||||
bool normalized32 = false;
|
||||
};
|
||||
|
||||
struct StaticAllocation {
|
||||
IRReg mr;
|
||||
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.
|
||||
bool normalized32 = false;
|
||||
};
|
||||
|
||||
public:
|
||||
IRNativeRegCacheBase(MIPSComp::JitOptions *jo);
|
||||
virtual ~IRNativeRegCacheBase() {}
|
||||
|
||||
virtual void Start(MIPSComp::IRBlock *irBlock);
|
||||
void SetIRIndex(int index) {
|
||||
irIndex_ = index;
|
||||
}
|
||||
|
||||
bool IsGPRInRAM(IRReg gpr);
|
||||
bool IsFPRInRAM(IRReg fpr);
|
||||
bool IsGPRMapped(IRReg gpr);
|
||||
bool IsFPRMapped(IRReg fpr);
|
||||
bool IsGPRMappedAsPointer(IRReg gpr);
|
||||
bool IsGPRMappedAsStaticPointer(IRReg gpr);
|
||||
|
||||
bool IsGPRImm(IRReg gpr);
|
||||
bool IsGPR2Imm(IRReg base);
|
||||
uint32_t GetGPRImm(IRReg gpr);
|
||||
uint64_t GetGPR2Imm(IRReg first);
|
||||
void SetGPRImm(IRReg gpr, uint32_t immval);
|
||||
void SetGPR2Imm(IRReg first, uint64_t immval);
|
||||
|
||||
// Protect the native registers containing register froms spilling, to ensure that
|
||||
// it's being kept allocated.
|
||||
void SpillLockGPR(IRReg reg, IRReg reg2 = IRREG_INVALID, IRReg reg3 = IRREG_INVALID, IRReg reg4 = IRREG_INVALID);
|
||||
void SpillLockFPR(IRReg reg, IRReg reg2 = IRREG_INVALID, IRReg reg3 = IRREG_INVALID, IRReg reg4 = IRREG_INVALID);
|
||||
void ReleaseSpillLockGPR(IRReg reg, IRReg reg2 = IRREG_INVALID, IRReg reg3 = IRREG_INVALID, IRReg reg4 = IRREG_INVALID);
|
||||
void ReleaseSpillLockFPR(IRReg reg, IRReg reg2 = IRREG_INVALID, IRReg reg3 = IRREG_INVALID, IRReg reg4 = IRREG_INVALID);
|
||||
|
||||
void MarkGPRDirty(IRReg gpr, bool andNormalized32 = false);
|
||||
void MarkGPRAsPointerDirty(IRReg gpr);
|
||||
|
||||
virtual void FlushAll();
|
||||
|
||||
protected:
|
||||
virtual void SetupInitialRegs();
|
||||
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 DiscardReg(IRReg mreg);
|
||||
virtual void FlushReg(IRReg mreg);
|
||||
virtual void AdjustNativeRegAsPtr(IRNativeReg nreg, bool state);
|
||||
virtual void MapNativeReg(MIPSLoc type, IRNativeReg nreg, IRReg first, int lanes, MIPSMap flags);
|
||||
virtual IRNativeReg MapNativeReg(MIPSLoc type, IRReg first, int lanes, MIPSMap flags);
|
||||
IRNativeReg MapNativeRegAsPointer(IRReg gpr);
|
||||
|
||||
// Load data from memory (possibly multiple lanes) into a native reg.
|
||||
virtual void LoadNativeReg(IRNativeReg nreg, IRReg first, int lanes) = 0;
|
||||
// Store data in a native reg back into memory.
|
||||
virtual void StoreNativeReg(IRNativeReg nreg, IRReg first, int lanes) = 0;
|
||||
// Set a native reg to a specific integer value.
|
||||
virtual void SetNativeRegValue(IRNativeReg nreg, uint32_t imm) = 0;
|
||||
// Store the imm value for a reg to memory (not currently in a native reg.)
|
||||
virtual void StoreRegValue(IRReg mreg, uint32_t imm) = 0;
|
||||
|
||||
void SetSpillLockIRIndex(IRReg reg, IRReg reg2, IRReg reg3, IRReg reg4, int offset, int index);
|
||||
int GetMipsRegOffset(IRReg r);
|
||||
|
||||
bool IsValidGPR(IRReg r) const;
|
||||
bool IsValidGPRNoZero(IRReg r) const;
|
||||
bool IsValidFPR(IRReg r) const;
|
||||
|
||||
MIPSComp::JitOptions *jo_;
|
||||
const MIPSComp::IRBlock *irBlock_ = nullptr;
|
||||
int irIndex_ = 0;
|
||||
int totalNativeRegs_ = 0;
|
||||
|
||||
RegStatusNative nr[TOTAL_POSSIBLE_NATIVEREGS];
|
||||
RegStatusMIPS mr[TOTAL_MAPPABLE_IRREGS];
|
||||
RegStatusNative nrInitial_[TOTAL_POSSIBLE_NATIVEREGS];
|
||||
RegStatusMIPS mrInitial_[TOTAL_MAPPABLE_IRREGS];
|
||||
|
||||
bool initialReady_ = false;
|
||||
};
|
||||
@@ -65,8 +65,8 @@ void RiscVJitBackend::CompIR_Arith(IRInst inst) {
|
||||
case IROp::AddConst:
|
||||
if ((int32_t)inst.constant >= -2048 && (int32_t)inst.constant <= 2047) {
|
||||
// Typical of stack pointer updates.
|
||||
if (gpr.IsMappedAsPointer(inst.src1) && inst.dest == inst.src1 && allowPtrMath) {
|
||||
gpr.MarkPtrDirty(gpr.RPtr(inst.dest));
|
||||
if (gpr.IsGPRMappedAsPointer(inst.dest) && inst.dest == inst.src1 && allowPtrMath) {
|
||||
gpr.MarkGPRAsPointerDirty(inst.dest);
|
||||
ADDI(gpr.RPtr(inst.dest), gpr.RPtr(inst.dest), inst.constant);
|
||||
} else {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
@@ -107,7 +107,7 @@ void RiscVJitBackend::CompIR_Logic(IRInst inst) {
|
||||
} else if (inst.src1 != inst.dest) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
gpr.MarkGPRDirty(inst.dest, gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -117,17 +117,17 @@ void RiscVJitBackend::CompIR_Logic(IRInst inst) {
|
||||
OR(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2));
|
||||
// If both were normalized before, the result is normalized.
|
||||
if (gpr.IsNormalized32(inst.src1) && gpr.IsNormalized32(inst.src2))
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
gpr.MarkGPRDirty(inst.dest, true);
|
||||
} else if (inst.src1 != inst.dest) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
gpr.MarkGPRDirty(inst.dest, gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Xor:
|
||||
if (inst.src1 == inst.src2) {
|
||||
gpr.SetImm(inst.dest, 0);
|
||||
gpr.SetGPRImm(inst.dest, 0);
|
||||
} else {
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2);
|
||||
XOR(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2));
|
||||
@@ -145,10 +145,10 @@ void RiscVJitBackend::CompIR_Logic(IRInst inst) {
|
||||
}
|
||||
// If the sign bits aren't cleared, and it was normalized before - it still is.
|
||||
if ((inst.constant & 0x80000000) != 0 && gpr.IsNormalized32(inst.src1))
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
gpr.MarkGPRDirty(inst.dest, true);
|
||||
// Otherwise, if we cleared the sign bits, it's naturally normalized.
|
||||
else if ((inst.constant & 0x80000000) == 0)
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
gpr.MarkGPRDirty(inst.dest, true);
|
||||
break;
|
||||
|
||||
case IROp::OrConst:
|
||||
@@ -162,7 +162,7 @@ void RiscVJitBackend::CompIR_Logic(IRInst inst) {
|
||||
}
|
||||
// Since our constant is normalized, oring its bits in won't hurt normalization.
|
||||
if (gpr.IsNormalized32(inst.src1))
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
gpr.MarkGPRDirty(inst.dest, true);
|
||||
break;
|
||||
|
||||
case IROp::XorConst:
|
||||
@@ -195,7 +195,7 @@ void RiscVJitBackend::CompIR_Assign(IRInst inst) {
|
||||
if (inst.dest != inst.src1) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
gpr.MarkGPRDirty(inst.dest, gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -280,7 +280,7 @@ void RiscVJitBackend::CompIR_Bits(IRInst inst) {
|
||||
if (XLEN >= 64) {
|
||||
// REV8 swaps the entire register, so get the 32 highest bits.
|
||||
SRAI(gpr.R(inst.dest), gpr.R(inst.dest), XLEN - 32);
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
gpr.MarkGPRDirty(inst.dest, true);
|
||||
}
|
||||
} else {
|
||||
CompIR_Generic(inst);
|
||||
@@ -334,12 +334,12 @@ void RiscVJitBackend::CompIR_Shift(IRInst inst) {
|
||||
case IROp::ShlImm:
|
||||
// Shouldn't happen, but let's be safe of any passes that modify the ops.
|
||||
if (inst.src2 >= 32) {
|
||||
gpr.SetImm(inst.dest, 0);
|
||||
gpr.SetGPRImm(inst.dest, 0);
|
||||
} else if (inst.src2 == 0) {
|
||||
if (inst.dest != inst.src1) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
gpr.MarkGPRDirty(inst.dest, gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
} else {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
@@ -350,12 +350,12 @@ void RiscVJitBackend::CompIR_Shift(IRInst inst) {
|
||||
case IROp::ShrImm:
|
||||
// Shouldn't happen, but let's be safe of any passes that modify the ops.
|
||||
if (inst.src2 >= 32) {
|
||||
gpr.SetImm(inst.dest, 0);
|
||||
gpr.SetGPRImm(inst.dest, 0);
|
||||
} else if (inst.src2 == 0) {
|
||||
if (inst.dest != inst.src1) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
gpr.MarkGPRDirty(inst.dest, gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
} else {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
@@ -372,7 +372,7 @@ void RiscVJitBackend::CompIR_Shift(IRInst inst) {
|
||||
if (inst.dest != inst.src1) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
gpr.MarkGPRDirty(inst.dest, gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
} else {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
@@ -385,7 +385,7 @@ void RiscVJitBackend::CompIR_Shift(IRInst inst) {
|
||||
if (inst.dest != inst.src1) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
gpr.MarkGPRDirty(inst.dest, gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
} else if (cpu_info.RiscV_Zbb) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
@@ -408,12 +408,11 @@ void RiscVJitBackend::CompIR_Compare(IRInst inst) {
|
||||
RiscVReg rhs = INVALID_REG;
|
||||
switch (inst.op) {
|
||||
case IROp::Slt:
|
||||
gpr.SpillLock(inst.dest, inst.src1, inst.src2);
|
||||
gpr.SpillLockGPR(inst.dest, inst.src1, inst.src2);
|
||||
gpr.MapReg(inst.src1);
|
||||
gpr.MapReg(inst.src2);
|
||||
NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true);
|
||||
gpr.MapReg(inst.dest, MIPSMap::NOINIT | MIPSMap::MARK_NORM32);
|
||||
gpr.ReleaseSpillLock(inst.dest, inst.src1, inst.src2);
|
||||
|
||||
SLT(gpr.R(inst.dest), lhs, rhs);
|
||||
break;
|
||||
@@ -424,11 +423,10 @@ void RiscVJitBackend::CompIR_Compare(IRInst inst) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SRLIW(gpr.R(inst.dest), gpr.R(inst.src1), 31);
|
||||
} else {
|
||||
gpr.SpillLock(inst.dest, inst.src1);
|
||||
gpr.SpillLockGPR(inst.dest, inst.src1);
|
||||
gpr.MapReg(inst.src1);
|
||||
NormalizeSrc1(inst, &lhs, SCRATCH1, false);
|
||||
gpr.MapReg(inst.dest, MIPSMap::NOINIT | MIPSMap::MARK_NORM32);
|
||||
gpr.ReleaseSpillLock(inst.dest, inst.src1);
|
||||
|
||||
if ((int32_t)inst.constant >= -2048 && (int32_t)inst.constant <= 2047) {
|
||||
SLTI(gpr.R(inst.dest), lhs, (int32_t)inst.constant);
|
||||
@@ -436,31 +434,29 @@ void RiscVJitBackend::CompIR_Compare(IRInst inst) {
|
||||
LI(SCRATCH2, (int32_t)inst.constant);
|
||||
SLT(gpr.R(inst.dest), lhs, SCRATCH2);
|
||||
}
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
gpr.MarkGPRDirty(inst.dest, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::SltU:
|
||||
gpr.SpillLock(inst.dest, inst.src1, inst.src2);
|
||||
gpr.SpillLockGPR(inst.dest, inst.src1, inst.src2);
|
||||
gpr.MapReg(inst.src1);
|
||||
gpr.MapReg(inst.src2);
|
||||
// It's still fine to sign extend, the biggest just get even bigger.
|
||||
NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true);
|
||||
gpr.MapReg(inst.dest, MIPSMap::NOINIT | MIPSMap::MARK_NORM32);
|
||||
gpr.ReleaseSpillLock(inst.dest, inst.src1, inst.src2);
|
||||
|
||||
SLTU(gpr.R(inst.dest), lhs, rhs);
|
||||
break;
|
||||
|
||||
case IROp::SltUConst:
|
||||
if (inst.constant == 0) {
|
||||
gpr.SetImm(inst.dest, 0);
|
||||
gpr.SetGPRImm(inst.dest, 0);
|
||||
} else {
|
||||
gpr.SpillLock(inst.dest, inst.src1);
|
||||
gpr.SpillLockGPR(inst.dest, inst.src1);
|
||||
gpr.MapReg(inst.src1);
|
||||
NormalizeSrc1(inst, &lhs, SCRATCH1, false);
|
||||
gpr.MapReg(inst.dest, MIPSMap::NOINIT | MIPSMap::MARK_NORM32);
|
||||
gpr.ReleaseSpillLock(inst.dest, inst.src1);
|
||||
|
||||
// We sign extend because we're comparing against something normalized.
|
||||
// It's also the most efficient to set.
|
||||
@@ -518,14 +514,14 @@ void RiscVJitBackend::CompIR_CondAssign(IRInst inst) {
|
||||
NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true);
|
||||
MAX(gpr.R(inst.dest), lhs, rhs);
|
||||
// Because we had to normalize the inputs, the output is normalized.
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
gpr.MarkGPRDirty(inst.dest, true);
|
||||
} else {
|
||||
CompIR_Generic(inst);
|
||||
}
|
||||
} else if (inst.dest != inst.src1) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
gpr.MarkGPRDirty(inst.dest, gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -536,14 +532,14 @@ void RiscVJitBackend::CompIR_CondAssign(IRInst inst) {
|
||||
NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true);
|
||||
MIN(gpr.R(inst.dest), lhs, rhs);
|
||||
// Because we had to normalize the inputs, the output is normalized.
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
gpr.MarkGPRDirty(inst.dest, true);
|
||||
} else {
|
||||
CompIR_Generic(inst);
|
||||
}
|
||||
} else if (inst.dest != inst.src1) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
gpr.MarkGPRDirty(inst.dest, gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -560,25 +556,25 @@ void RiscVJitBackend::CompIR_HiLo(IRInst inst) {
|
||||
case IROp::MtLo:
|
||||
gpr.MapDirtyIn(IRREG_LO, inst.src1);
|
||||
MV(gpr.R(IRREG_LO), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(IRREG_LO), gpr.IsNormalized32(inst.src1));
|
||||
gpr.MarkGPRDirty(IRREG_LO, gpr.IsNormalized32(inst.src1));
|
||||
break;
|
||||
|
||||
case IROp::MtHi:
|
||||
gpr.MapDirtyIn(IRREG_HI, inst.src1);
|
||||
MV(gpr.R(IRREG_HI), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(IRREG_HI), gpr.IsNormalized32(inst.src1));
|
||||
gpr.MarkGPRDirty(IRREG_HI, gpr.IsNormalized32(inst.src1));
|
||||
break;
|
||||
|
||||
case IROp::MfLo:
|
||||
gpr.MapDirtyIn(inst.dest, IRREG_LO);
|
||||
MV(gpr.R(inst.dest), gpr.R(IRREG_LO));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(IRREG_LO));
|
||||
gpr.MarkGPRDirty(inst.dest, gpr.IsNormalized32(IRREG_LO));
|
||||
break;
|
||||
|
||||
case IROp::MfHi:
|
||||
gpr.MapDirtyIn(inst.dest, IRREG_HI);
|
||||
MV(gpr.R(inst.dest), gpr.R(IRREG_HI));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(IRREG_HI));
|
||||
gpr.MarkGPRDirty(inst.dest, gpr.IsNormalized32(IRREG_HI));
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -616,7 +612,7 @@ void RiscVJitBackend::CompIR_Mult(IRInst inst) {
|
||||
};
|
||||
auto splitMulResult = [&] {
|
||||
SRAI(gpr.R(IRREG_HI), gpr.R(IRREG_LO), 32);
|
||||
gpr.MarkDirty(gpr.R(IRREG_HI), true);
|
||||
gpr.MarkGPRDirty(IRREG_HI, true);
|
||||
};
|
||||
|
||||
RiscVReg lhs = INVALID_REG;
|
||||
|
||||
@@ -117,8 +117,6 @@ void RiscVJitBackend::CompIR_FCondAssign(IRInst inst) {
|
||||
SetJumpTarget(skipSwapCompare);
|
||||
} else {
|
||||
RiscVReg isSrc1LowerReg = gpr.GetAndLockTempR();
|
||||
gpr.ReleaseSpillLocksAndDiscardTemps();
|
||||
|
||||
SLT(isSrc1LowerReg, SCRATCH1, SCRATCH2);
|
||||
// Flip the flag (to reverse the min/max) based on if both were negative.
|
||||
XOR(isSrc1LowerReg, isSrc1LowerReg, R_RA);
|
||||
@@ -342,13 +340,13 @@ void RiscVJitBackend::CompIR_FSat(IRInst inst) {
|
||||
void RiscVJitBackend::CompIR_FCompare(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
constexpr IRRegIndex IRREG_VFPUL_CC = IRREG_VFPU_CTRL_BASE + VFPU_CTRL_CC;
|
||||
constexpr IRReg IRREG_VFPUL_CC = IRREG_VFPU_CTRL_BASE + VFPU_CTRL_CC;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::FCmp:
|
||||
switch (inst.dest) {
|
||||
case IRFpCompareMode::False:
|
||||
gpr.SetImm(IRREG_FPCOND, 0);
|
||||
gpr.SetGPRImm(IRREG_FPCOND, 0);
|
||||
break;
|
||||
|
||||
case IRFpCompareMode::EitherUnordered:
|
||||
@@ -586,7 +584,7 @@ void RiscVJitBackend::CompIR_FSpecial(IRInst inst) {
|
||||
gpr.FlushBeforeCall();
|
||||
fpr.FlushBeforeCall();
|
||||
// It might be in a non-volatile register.
|
||||
if (fpr.IsMapped(inst.src1)) {
|
||||
if (fpr.IsFPRMapped(inst.src1)) {
|
||||
FMV(32, F10, fpr.R(inst.src1));
|
||||
} else {
|
||||
int offset = offsetof(MIPSState, f) + inst.src1 * 4;
|
||||
|
||||
@@ -66,7 +66,7 @@ int32_t RiscVJitBackend::AdjustForAddressOffset(RiscVGen::RiscVReg *reg, int32_t
|
||||
void RiscVJitBackend::CompIR_Load(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
gpr.SpillLock(inst.dest, inst.src1);
|
||||
gpr.SpillLockGPR(inst.dest, inst.src1);
|
||||
RiscVReg addrReg = INVALID_REG;
|
||||
if (inst.src1 == MIPS_REG_ZERO) {
|
||||
// This will get changed by AdjustForAddressOffset.
|
||||
@@ -74,7 +74,7 @@ void RiscVJitBackend::CompIR_Load(IRInst inst) {
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
inst.constant &= Memory::MEMVIEW32_MASK;
|
||||
#endif
|
||||
} else if (jo.cachePointers || gpr.IsMappedAsPointer(inst.src1)) {
|
||||
} else if (jo.cachePointers || gpr.IsGPRMappedAsPointer(inst.src1)) {
|
||||
addrReg = gpr.MapRegAsPointer(inst.src1);
|
||||
} else {
|
||||
SetScratch1ToSrc1Address(inst.src1);
|
||||
@@ -82,7 +82,6 @@ void RiscVJitBackend::CompIR_Load(IRInst inst) {
|
||||
}
|
||||
// With NOINIT, MapReg won't subtract MEMBASEREG even if dest == src1.
|
||||
gpr.MapReg(inst.dest, MIPSMap::NOINIT | MIPSMap::MARK_NORM32);
|
||||
gpr.ReleaseSpillLock(inst.dest, inst.src1);
|
||||
|
||||
s32 imm = AdjustForAddressOffset(&addrReg, inst.constant);
|
||||
|
||||
@@ -112,7 +111,7 @@ void RiscVJitBackend::CompIR_Load(IRInst inst) {
|
||||
case IROp::Load32Linked:
|
||||
if (inst.dest != MIPS_REG_ZERO)
|
||||
LW(gpr.R(inst.dest), addrReg, imm);
|
||||
gpr.SetImm(IRREG_LLBIT, 1);
|
||||
gpr.SetGPRImm(IRREG_LLBIT, 1);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -147,7 +146,7 @@ void RiscVJitBackend::CompIR_FLoad(IRInst inst) {
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
inst.constant &= Memory::MEMVIEW32_MASK;
|
||||
#endif
|
||||
} else if (jo.cachePointers || gpr.IsMappedAsPointer(inst.src1)) {
|
||||
} else if (jo.cachePointers || gpr.IsGPRMappedAsPointer(inst.src1)) {
|
||||
addrReg = gpr.MapRegAsPointer(inst.src1);
|
||||
} else {
|
||||
SetScratch1ToSrc1Address(inst.src1);
|
||||
@@ -180,7 +179,7 @@ void RiscVJitBackend::CompIR_VecLoad(IRInst inst) {
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
inst.constant &= Memory::MEMVIEW32_MASK;
|
||||
#endif
|
||||
} else if (jo.cachePointers || gpr.IsMappedAsPointer(inst.src1)) {
|
||||
} else if (jo.cachePointers || gpr.IsGPRMappedAsPointer(inst.src1)) {
|
||||
addrReg = gpr.MapRegAsPointer(inst.src1);
|
||||
} else {
|
||||
SetScratch1ToSrc1Address(inst.src1);
|
||||
@@ -210,7 +209,7 @@ void RiscVJitBackend::CompIR_VecLoad(IRInst inst) {
|
||||
void RiscVJitBackend::CompIR_Store(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
gpr.SpillLock(inst.src3, inst.src1);
|
||||
gpr.SpillLockGPR(inst.src3, inst.src1);
|
||||
RiscVReg addrReg = INVALID_REG;
|
||||
if (inst.src1 == MIPS_REG_ZERO) {
|
||||
// This will get changed by AdjustForAddressOffset.
|
||||
@@ -218,7 +217,7 @@ void RiscVJitBackend::CompIR_Store(IRInst inst) {
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
inst.constant &= Memory::MEMVIEW32_MASK;
|
||||
#endif
|
||||
} else if ((jo.cachePointers || gpr.IsMappedAsPointer(inst.src1)) && inst.src3 != inst.src1) {
|
||||
} else if ((jo.cachePointers || gpr.IsGPRMappedAsPointer(inst.src1)) && inst.src3 != inst.src1) {
|
||||
addrReg = gpr.MapRegAsPointer(inst.src1);
|
||||
} else {
|
||||
SetScratch1ToSrc1Address(inst.src1);
|
||||
@@ -227,7 +226,6 @@ void RiscVJitBackend::CompIR_Store(IRInst inst) {
|
||||
RiscVReg valueReg = gpr.TryMapTempImm(inst.src3);
|
||||
if (valueReg == INVALID_REG)
|
||||
valueReg = gpr.MapReg(inst.src3);
|
||||
gpr.ReleaseSpillLock(inst.src3, inst.src1);
|
||||
|
||||
s32 imm = AdjustForAddressOffset(&addrReg, inst.constant);
|
||||
|
||||
@@ -257,7 +255,7 @@ void RiscVJitBackend::CompIR_CondStore(IRInst inst) {
|
||||
if (inst.op != IROp::Store32Conditional)
|
||||
INVALIDOP;
|
||||
|
||||
gpr.SpillLock(IRREG_LLBIT, inst.src3, inst.src1);
|
||||
gpr.SpillLockGPR(IRREG_LLBIT, inst.src3, inst.src1);
|
||||
RiscVReg addrReg = INVALID_REG;
|
||||
if (inst.src1 == MIPS_REG_ZERO) {
|
||||
// This will get changed by AdjustForAddressOffset.
|
||||
@@ -265,7 +263,7 @@ void RiscVJitBackend::CompIR_CondStore(IRInst inst) {
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
inst.constant &= Memory::MEMVIEW32_MASK;
|
||||
#endif
|
||||
} else if ((jo.cachePointers || gpr.IsMappedAsPointer(inst.src1)) && inst.src3 != inst.src1) {
|
||||
} else if ((jo.cachePointers || gpr.IsGPRMappedAsPointer(inst.src1)) && inst.src3 != inst.src1) {
|
||||
addrReg = gpr.MapRegAsPointer(inst.src1);
|
||||
} else {
|
||||
SetScratch1ToSrc1Address(inst.src1);
|
||||
@@ -273,7 +271,6 @@ void RiscVJitBackend::CompIR_CondStore(IRInst inst) {
|
||||
}
|
||||
gpr.MapReg(inst.src3, inst.dest == MIPS_REG_ZERO ? MIPSMap::INIT : MIPSMap::DIRTY);
|
||||
gpr.MapReg(IRREG_LLBIT);
|
||||
gpr.ReleaseSpillLock(IRREG_LLBIT, inst.src3, inst.src1);
|
||||
|
||||
s32 imm = AdjustForAddressOffset(&addrReg, inst.constant);
|
||||
|
||||
@@ -320,7 +317,7 @@ void RiscVJitBackend::CompIR_FStore(IRInst inst) {
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
inst.constant &= Memory::MEMVIEW32_MASK;
|
||||
#endif
|
||||
} else if (jo.cachePointers || gpr.IsMappedAsPointer(inst.src1)) {
|
||||
} else if (jo.cachePointers || gpr.IsGPRMappedAsPointer(inst.src1)) {
|
||||
addrReg = gpr.MapRegAsPointer(inst.src1);
|
||||
} else {
|
||||
SetScratch1ToSrc1Address(inst.src1);
|
||||
@@ -353,7 +350,7 @@ void RiscVJitBackend::CompIR_VecStore(IRInst inst) {
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
inst.constant &= Memory::MEMVIEW32_MASK;
|
||||
#endif
|
||||
} else if (jo.cachePointers || gpr.IsMappedAsPointer(inst.src1)) {
|
||||
} else if (jo.cachePointers || gpr.IsGPRMappedAsPointer(inst.src1)) {
|
||||
addrReg = gpr.MapRegAsPointer(inst.src1);
|
||||
} else {
|
||||
SetScratch1ToSrc1Address(inst.src1);
|
||||
|
||||
@@ -45,7 +45,7 @@ void RiscVJitBackend::CompIR_Basic(IRInst inst) {
|
||||
case IROp::SetConst:
|
||||
// Sign extend all constants. We get 0xFFFFFFFF sometimes, and it's more work to truncate.
|
||||
// The register only holds 32 bits in the end anyway.
|
||||
gpr.SetImm(inst.dest, (int32_t)inst.constant);
|
||||
gpr.SetGPRImm(inst.dest, (int32_t)inst.constant);
|
||||
break;
|
||||
|
||||
case IROp::SetConstF:
|
||||
@@ -90,13 +90,13 @@ void RiscVJitBackend::CompIR_Transfer(IRInst inst) {
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::SetCtrlVFPU:
|
||||
gpr.SetImm(IRREG_VFPU_CTRL_BASE + inst.dest, (int32_t)inst.constant);
|
||||
gpr.SetGPRImm(IRREG_VFPU_CTRL_BASE + inst.dest, (int32_t)inst.constant);
|
||||
break;
|
||||
|
||||
case IROp::SetCtrlVFPUReg:
|
||||
gpr.MapDirtyIn(IRREG_VFPU_CTRL_BASE + inst.dest, inst.src1);
|
||||
MV(gpr.R(IRREG_VFPU_CTRL_BASE + inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(IRREG_VFPU_CTRL_BASE + inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
gpr.MarkGPRDirty(IRREG_VFPU_CTRL_BASE + inst.dest, gpr.IsNormalized32(inst.src1));
|
||||
break;
|
||||
|
||||
case IROp::SetCtrlVFPUFReg:
|
||||
@@ -113,7 +113,7 @@ void RiscVJitBackend::CompIR_Transfer(IRInst inst) {
|
||||
case IROp::FpCondToReg:
|
||||
gpr.MapDirtyIn(inst.dest, IRREG_FPCOND);
|
||||
MV(gpr.R(inst.dest), gpr.R(IRREG_FPCOND));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(IRREG_FPCOND));
|
||||
gpr.MarkGPRDirty(inst.dest, gpr.IsNormalized32(IRREG_FPCOND));
|
||||
break;
|
||||
|
||||
case IROp::FpCtrlFromReg:
|
||||
@@ -153,12 +153,12 @@ void RiscVJitBackend::CompIR_Transfer(IRInst inst) {
|
||||
case IROp::VfpuCtrlToReg:
|
||||
gpr.MapDirtyIn(inst.dest, IRREG_VFPU_CTRL_BASE + inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(IRREG_VFPU_CTRL_BASE + inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(IRREG_VFPU_CTRL_BASE + inst.src1));
|
||||
gpr.MarkGPRDirty(inst.dest, gpr.IsNormalized32(IRREG_VFPU_CTRL_BASE + inst.src1));
|
||||
break;
|
||||
|
||||
case IROp::FMovFromGPR:
|
||||
fpr.MapReg(inst.dest, MIPSMap::NOINIT);
|
||||
if (gpr.IsImm(inst.src1) && gpr.GetImm(inst.src1) == 0) {
|
||||
if (gpr.IsGPRImm(inst.src1) && gpr.GetGPRImm(inst.src1) == 0) {
|
||||
FCVT(FConv::S, FConv::W, fpr.R(inst.dest), R_ZERO);
|
||||
} else {
|
||||
gpr.MapReg(inst.src1);
|
||||
|
||||
@@ -41,11 +41,9 @@ void RiscVJitBackend::CompIR_VecAssign(IRInst inst) {
|
||||
switch (inst.op) {
|
||||
case IROp::Vec4Init:
|
||||
for (int i = 0; i < 4; ++i)
|
||||
fpr.SpillLock(inst.dest + i);
|
||||
fpr.SpillLockFPR(inst.dest + i);
|
||||
for (int i = 0; i < 4; ++i)
|
||||
fpr.MapReg(inst.dest + i, MIPSMap::NOINIT);
|
||||
for (int i = 0; i < 4; ++i)
|
||||
fpr.ReleaseSpillLock(inst.dest + i);
|
||||
|
||||
// TODO: Check if FCVT/FMV/FL is better.
|
||||
switch ((Vec4Init)inst.src1) {
|
||||
@@ -220,10 +218,9 @@ void RiscVJitBackend::CompIR_VecArith(IRInst inst) {
|
||||
break;
|
||||
|
||||
case IROp::Vec4Scale:
|
||||
fpr.SpillLock(inst.src2);
|
||||
fpr.SpillLockFPR(inst.src2);
|
||||
fpr.MapReg(inst.src2);
|
||||
fpr.Map4DirtyIn(inst.dest, inst.src1);
|
||||
fpr.ReleaseSpillLock(inst.src2);
|
||||
for (int i = 0; i < 4; ++i)
|
||||
FMUL(32, fpr.R(inst.dest + i), fpr.R(inst.src1 + i), fpr.R(inst.src2));
|
||||
break;
|
||||
@@ -252,21 +249,16 @@ void RiscVJitBackend::CompIR_VecHoriz(IRInst inst) {
|
||||
switch (inst.op) {
|
||||
case IROp::Vec4Dot:
|
||||
// TODO: Maybe some option to call the slow accurate mode?
|
||||
fpr.SpillLock(inst.dest);
|
||||
fpr.SpillLockFPR(inst.dest);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
fpr.SpillLock(inst.src1 + i);
|
||||
fpr.SpillLock(inst.src2 + i);
|
||||
fpr.SpillLockFPR(inst.src1 + i);
|
||||
fpr.SpillLockFPR(inst.src2 + i);
|
||||
}
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
fpr.MapReg(inst.src1 + i);
|
||||
fpr.MapReg(inst.src2 + i);
|
||||
}
|
||||
fpr.MapReg(inst.dest, MIPSMap::NOINIT);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
fpr.ReleaseSpillLock(inst.src1 + i);
|
||||
fpr.ReleaseSpillLock(inst.src2 + i);
|
||||
}
|
||||
fpr.ReleaseSpillLock(inst.dest);
|
||||
|
||||
if ((inst.dest < inst.src1 + 4 && inst.dest >= inst.src1) || (inst.dest < inst.src2 + 4 && inst.dest >= inst.src2)) {
|
||||
// This means inst.dest overlaps one of src1 or src2. We have to do that one first.
|
||||
@@ -303,13 +295,12 @@ void RiscVJitBackend::CompIR_VecPack(IRInst inst) {
|
||||
break;
|
||||
|
||||
case IROp::Vec4Unpack8To32:
|
||||
fpr.SpillLock(inst.src1);
|
||||
fpr.SpillLockFPR(inst.src1);
|
||||
for (int i = 0; i < 4; ++i)
|
||||
fpr.SpillLock(inst.dest + i);
|
||||
fpr.SpillLockFPR(inst.dest + i);
|
||||
fpr.MapReg(inst.src1);
|
||||
for (int i = 0; i < 4; ++i)
|
||||
fpr.MapReg(inst.dest + i, MIPSMap::NOINIT);
|
||||
fpr.ReleaseSpillLocksAndDiscardTemps();
|
||||
|
||||
FMV(FMv::X, FMv::W, SCRATCH2, fpr.R(inst.src1));
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
@@ -325,13 +316,12 @@ void RiscVJitBackend::CompIR_VecPack(IRInst inst) {
|
||||
break;
|
||||
|
||||
case IROp::Vec2Unpack16To32:
|
||||
fpr.SpillLock(inst.src1);
|
||||
fpr.SpillLockFPR(inst.src1);
|
||||
for (int i = 0; i < 2; ++i)
|
||||
fpr.SpillLock(inst.dest + i);
|
||||
fpr.SpillLockFPR(inst.dest + i);
|
||||
fpr.MapReg(inst.src1);
|
||||
for (int i = 0; i < 2; ++i)
|
||||
fpr.MapReg(inst.dest + i, MIPSMap::NOINIT);
|
||||
fpr.ReleaseSpillLocksAndDiscardTemps();
|
||||
|
||||
FMV(FMv::X, FMv::W, SCRATCH2, fpr.R(inst.src1));
|
||||
SLLI(SCRATCH1, SCRATCH2, 16);
|
||||
@@ -355,13 +345,12 @@ void RiscVJitBackend::CompIR_VecPack(IRInst inst) {
|
||||
break;
|
||||
|
||||
case IROp::Vec4Pack31To8:
|
||||
fpr.SpillLock(inst.dest);
|
||||
fpr.SpillLockFPR(inst.dest);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
fpr.SpillLock(inst.src1 + i);
|
||||
fpr.SpillLockFPR(inst.src1 + i);
|
||||
fpr.MapReg(inst.src1 + i);
|
||||
}
|
||||
fpr.MapReg(inst.dest, MIPSMap::NOINIT);
|
||||
fpr.ReleaseSpillLocksAndDiscardTemps();
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
FMV(FMv::X, FMv::W, SCRATCH1, fpr.R(inst.src1 + i));
|
||||
|
||||
@@ -31,8 +31,8 @@ using namespace RiscVJitConstants;
|
||||
static constexpr int MIN_BLOCK_NORMAL_LEN = 16;
|
||||
static constexpr int MIN_BLOCK_EXIT_LEN = 8;
|
||||
|
||||
RiscVJitBackend::RiscVJitBackend(MIPSState *mipsState, JitOptions &jitopt, IRBlockCache &blocks)
|
||||
: IRNativeBackend(blocks), jo(jitopt), gpr(mipsState, &jo), fpr(mipsState, &jo) {
|
||||
RiscVJitBackend::RiscVJitBackend(JitOptions &jitopt, IRBlockCache &blocks)
|
||||
: IRNativeBackend(blocks), jo(jitopt), gpr(&jo), fpr(&jo) {
|
||||
// Automatically disable incompatible options.
|
||||
if (((intptr_t)Memory::base & 0x00000000FFFFFFFFUL) != 0) {
|
||||
jo.enablePointerify = false;
|
||||
@@ -352,12 +352,12 @@ void RiscVJitBackend::NormalizeSrc12(IRInst inst, RiscVReg *lhs, RiscVReg *rhs,
|
||||
*rhs = NormalizeR(inst.src2, allowOverlap ? 0 : inst.dest, rhsTempReg);
|
||||
}
|
||||
|
||||
RiscVReg RiscVJitBackend::NormalizeR(IRRegIndex rs, IRRegIndex rd, RiscVReg tempReg) {
|
||||
RiscVReg RiscVJitBackend::NormalizeR(IRReg rs, IRReg rd, RiscVReg tempReg) {
|
||||
// For proper compare, we must sign extend so they both match or don't match.
|
||||
// But don't change pointers, in case one is SP (happens in LittleBigPlanet.)
|
||||
if (gpr.IsImm(rs) && gpr.GetImm(rs) == 0) {
|
||||
if (gpr.IsGPRImm(rs) && gpr.GetGPRImm(rs) == 0) {
|
||||
return R_ZERO;
|
||||
} else if (gpr.IsMappedAsPointer(rs) || rs == rd) {
|
||||
} else if (gpr.IsGPRMappedAsPointer(rs) || rs == rd) {
|
||||
return gpr.Normalize32(rs, tempReg);
|
||||
} else {
|
||||
return gpr.Normalize32(rs);
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace MIPSComp {
|
||||
|
||||
class RiscVJitBackend : public RiscVGen::RiscVCodeBlock, public IRNativeBackend {
|
||||
public:
|
||||
RiscVJitBackend(MIPSState *mipsState, JitOptions &jo, IRBlockCache &blocks);
|
||||
RiscVJitBackend(JitOptions &jo, IRBlockCache &blocks);
|
||||
~RiscVJitBackend();
|
||||
|
||||
bool DescribeCodePtr(const u8 *ptr, std::string &name) const override;
|
||||
@@ -109,7 +109,7 @@ private:
|
||||
int32_t AdjustForAddressOffset(RiscVGen::RiscVReg *reg, int32_t constant, int32_t range = 0);
|
||||
void NormalizeSrc1(IRInst inst, RiscVGen::RiscVReg *reg, RiscVGen::RiscVReg tempReg, bool allowOverlap);
|
||||
void NormalizeSrc12(IRInst inst, RiscVGen::RiscVReg *lhs, RiscVGen::RiscVReg *rhs, RiscVGen::RiscVReg lhsTempReg, RiscVGen::RiscVReg rhsTempReg, bool allowOverlap);
|
||||
RiscVGen::RiscVReg NormalizeR(IRRegIndex rs, IRRegIndex rd, RiscVGen::RiscVReg tempReg);
|
||||
RiscVGen::RiscVReg NormalizeR(IRReg rs, IRReg rd, RiscVGen::RiscVReg tempReg);
|
||||
|
||||
JitOptions &jo;
|
||||
RiscVRegCache gpr;
|
||||
@@ -133,7 +133,7 @@ private:
|
||||
class RiscVJit : public IRNativeJit {
|
||||
public:
|
||||
RiscVJit(MIPSState *mipsState)
|
||||
: IRNativeJit(mipsState), rvBackend_(mipsState, jo, blocks_) {
|
||||
: IRNativeJit(mipsState), rvBackend_(jo, blocks_) {
|
||||
Init(rvBackend_);
|
||||
}
|
||||
|
||||
|
||||
+196
-872
File diff suppressed because it is too large.
Load diff
+30
-128
@@ -20,6 +20,7 @@
|
||||
#include "Common/RiscVEmitter.h"
|
||||
#include "Core/MIPS/MIPS.h"
|
||||
#include "Core/MIPS/IR/IRJit.h"
|
||||
#include "Core/MIPS/IR/IRRegCache.h"
|
||||
|
||||
namespace RiscVJitConstants {
|
||||
|
||||
@@ -34,33 +35,6 @@ const RiscVGen::RiscVReg MEMBASEREG = RiscVGen::X27;
|
||||
const RiscVGen::RiscVReg SCRATCH1 = RiscVGen::X10;
|
||||
const RiscVGen::RiscVReg SCRATCH2 = RiscVGen::X11;
|
||||
|
||||
// Have to account for all of them due to temps, etc.
|
||||
constexpr int TOTAL_MAPPABLE_MIPSREGS = 256;
|
||||
|
||||
enum class MIPSLoc {
|
||||
IMM,
|
||||
RVREG,
|
||||
// In a native reg, but an adjusted pointer (not pointerified - unaligned.)
|
||||
RVREG_AS_PTR,
|
||||
// In a native reg, but also has a known immediate value.
|
||||
RVREG_IMM,
|
||||
MEM,
|
||||
};
|
||||
|
||||
// Initing is the default so the flag is reversed.
|
||||
enum class MIPSMap {
|
||||
INIT = 0,
|
||||
DIRTY = 1,
|
||||
NOINIT = 2 | DIRTY,
|
||||
MARK_NORM32 = 4,
|
||||
};
|
||||
static inline MIPSMap operator |(const MIPSMap &lhs, const MIPSMap &rhs) {
|
||||
return MIPSMap((int)lhs | (int)rhs);
|
||||
}
|
||||
static inline MIPSMap operator &(const MIPSMap &lhs, const MIPSMap &rhs) {
|
||||
return MIPSMap((int)lhs & (int)rhs);
|
||||
}
|
||||
|
||||
enum class MapType {
|
||||
AVOID_LOAD,
|
||||
AVOID_LOAD_MARK_NORM32,
|
||||
@@ -69,129 +43,57 @@ enum class MapType {
|
||||
|
||||
} // namespace RiscVJitConstants
|
||||
|
||||
namespace MIPSComp {
|
||||
struct JitOptions;
|
||||
}
|
||||
|
||||
// Not using IRReg since this can be -1.
|
||||
typedef int IRRegIndex;
|
||||
constexpr IRRegIndex IRREG_INVALID = -1;
|
||||
|
||||
struct RegStatusRiscV {
|
||||
IRRegIndex mipsReg; // if -1, no mipsreg attached.
|
||||
bool isDirty; // Should the register be written back?
|
||||
bool pointerified; // Has added the memory base into the top part of the reg. Note - still usable as 32-bit reg (in only some cases.)
|
||||
bool tempLocked; // Reserved for a temp register.
|
||||
bool normalized32; // 32 bits sign extended to XLEN. RISC-V can't always address just the low 32-bits, so this matters.
|
||||
};
|
||||
|
||||
struct RegStatusMIPS {
|
||||
// Where is this MIPS register?
|
||||
RiscVJitConstants::MIPSLoc loc;
|
||||
// Data (both or only one may be used, depending on loc.)
|
||||
u64 imm;
|
||||
RiscVGen::RiscVReg reg; // reg index
|
||||
bool spillLock; // if true, this register cannot be spilled.
|
||||
bool isStatic; // if true, this register will not be written back to ram by the regcache
|
||||
// If loc == ML_MEM, it's back in its location in the CPU context struct.
|
||||
};
|
||||
|
||||
class RiscVRegCache {
|
||||
class RiscVRegCache : public IRNativeRegCacheBase {
|
||||
public:
|
||||
RiscVRegCache(MIPSState *mipsState, MIPSComp::JitOptions *jo);
|
||||
~RiscVRegCache() {}
|
||||
RiscVRegCache(MIPSComp::JitOptions *jo);
|
||||
|
||||
void Init(RiscVGen::RiscVEmitter *emitter);
|
||||
void Start(MIPSComp::IRBlock *irBlock);
|
||||
void SetIRIndex(int index) {
|
||||
irIndex_ = index;
|
||||
}
|
||||
|
||||
// Protect the arm 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();
|
||||
|
||||
void SetImm(IRRegIndex reg, u64 immVal);
|
||||
bool IsImm(IRRegIndex reg) const;
|
||||
u64 GetImm(IRRegIndex reg) const;
|
||||
|
||||
// May fail and return INVALID_REG if it needs flushing.
|
||||
RiscVGen::RiscVReg TryMapTempImm(IRRegIndex);
|
||||
RiscVGen::RiscVReg TryMapTempImm(IRReg);
|
||||
|
||||
// Returns an ARM register containing the requested MIPS register.
|
||||
RiscVGen::RiscVReg MapReg(IRRegIndex reg, RiscVJitConstants::MIPSMap mapFlags = RiscVJitConstants::MIPSMap::INIT);
|
||||
RiscVGen::RiscVReg MapRegAsPointer(IRRegIndex reg);
|
||||
RiscVGen::RiscVReg MapReg(IRReg reg, MIPSMap mapFlags = MIPSMap::INIT);
|
||||
RiscVGen::RiscVReg MapRegAsPointer(IRReg reg);
|
||||
|
||||
bool IsMapped(IRRegIndex reg);
|
||||
bool IsMappedAsPointer(IRRegIndex reg);
|
||||
bool IsMappedAsStaticPointer(IRRegIndex reg);
|
||||
bool IsInRAM(IRRegIndex reg);
|
||||
bool IsNormalized32(IRRegIndex reg);
|
||||
bool IsNormalized32(IRReg reg);
|
||||
|
||||
void MarkDirty(RiscVGen::RiscVReg reg, bool andNormalized32 = false);
|
||||
void MarkPtrDirty(RiscVGen::RiscVReg reg);
|
||||
// Copies to another reg if specified, otherwise same reg.
|
||||
RiscVGen::RiscVReg Normalize32(IRRegIndex reg, RiscVGen::RiscVReg destReg = RiscVGen::INVALID_REG);
|
||||
void MapIn(IRRegIndex rs);
|
||||
void MapInIn(IRRegIndex rd, IRRegIndex rs);
|
||||
void MapDirtyIn(IRRegIndex rd, IRRegIndex rs, RiscVJitConstants::MapType type = RiscVJitConstants::MapType::AVOID_LOAD);
|
||||
void MapDirtyInIn(IRRegIndex rd, IRRegIndex rs, IRRegIndex rt, RiscVJitConstants::MapType type = RiscVJitConstants::MapType::AVOID_LOAD);
|
||||
void MapDirtyDirtyIn(IRRegIndex rd1, IRRegIndex rd2, IRRegIndex rs, RiscVJitConstants::MapType type = RiscVJitConstants::MapType::AVOID_LOAD);
|
||||
void MapDirtyDirtyInIn(IRRegIndex rd1, IRRegIndex rd2, IRRegIndex rs, IRRegIndex rt, RiscVJitConstants::MapType type = RiscVJitConstants::MapType::AVOID_LOAD);
|
||||
RiscVGen::RiscVReg Normalize32(IRReg reg, RiscVGen::RiscVReg destReg = RiscVGen::INVALID_REG);
|
||||
void MapIn(IRReg rs);
|
||||
void MapInIn(IRReg rd, IRReg rs);
|
||||
void MapDirtyIn(IRReg rd, IRReg rs, RiscVJitConstants::MapType type = RiscVJitConstants::MapType::AVOID_LOAD);
|
||||
void MapDirtyInIn(IRReg rd, IRReg rs, IRReg rt, RiscVJitConstants::MapType type = RiscVJitConstants::MapType::AVOID_LOAD);
|
||||
void MapDirtyDirtyIn(IRReg rd1, IRReg rd2, IRReg rs, RiscVJitConstants::MapType type = RiscVJitConstants::MapType::AVOID_LOAD);
|
||||
void MapDirtyDirtyInIn(IRReg rd1, IRReg rd2, IRReg rs, IRReg rt, RiscVJitConstants::MapType type = RiscVJitConstants::MapType::AVOID_LOAD);
|
||||
void FlushBeforeCall();
|
||||
void FlushAll();
|
||||
void FlushR(IRRegIndex r);
|
||||
void FlushRiscVReg(RiscVGen::RiscVReg r);
|
||||
void DiscardR(IRRegIndex r);
|
||||
void FlushR(IRReg r);
|
||||
void DiscardR(IRReg r);
|
||||
|
||||
RiscVGen::RiscVReg GetAndLockTempR();
|
||||
|
||||
RiscVGen::RiscVReg R(IRRegIndex preg); // Returns a cached register, while checking that it's NOT mapped as a pointer
|
||||
RiscVGen::RiscVReg RPtr(IRRegIndex preg); // Returns a cached register, if it has been mapped as a pointer
|
||||
RiscVGen::RiscVReg R(IRReg preg); // Returns a cached register, while checking that it's NOT mapped as a pointer
|
||||
RiscVGen::RiscVReg RPtr(IRReg preg); // Returns a cached register, if it has been mapped as a pointer
|
||||
|
||||
// These are called once on startup to generate functions, that you should then call.
|
||||
void EmitLoadStaticRegisters();
|
||||
void EmitSaveStaticRegisters();
|
||||
|
||||
protected:
|
||||
void SetupInitialRegs() 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 LoadNativeReg(IRNativeReg nreg, IRReg first, int lanes) override;
|
||||
void StoreNativeReg(IRNativeReg nreg, IRReg first, int lanes) override;
|
||||
void SetNativeRegValue(IRNativeReg nreg, uint32_t imm) override;
|
||||
void StoreRegValue(IRReg mreg, uint32_t imm) override;
|
||||
|
||||
private:
|
||||
struct StaticAllocation {
|
||||
IRRegIndex mr;
|
||||
RiscVGen::RiscVReg ar;
|
||||
bool pointerified;
|
||||
};
|
||||
const StaticAllocation *GetStaticAllocations(int &count);
|
||||
const RiscVGen::RiscVReg *GetMIPSAllocationOrder(int &count);
|
||||
void MapRegTo(RiscVGen::RiscVReg reg, IRRegIndex mipsReg, RiscVJitConstants::MIPSMap mapFlags);
|
||||
RiscVGen::RiscVReg AllocateReg();
|
||||
RiscVGen::RiscVReg FindBestToSpill(bool unusedOnly, bool *clobbered);
|
||||
RiscVGen::RiscVReg RiscVRegForFlush(IRRegIndex r);
|
||||
void SetRegImm(RiscVGen::RiscVReg reg, u64 imm);
|
||||
void AddMemBase(RiscVGen::RiscVReg reg);
|
||||
int GetMipsRegOffset(IRRegIndex r);
|
||||
|
||||
bool IsValidReg(IRRegIndex r) const;
|
||||
bool IsValidRegNoZero(IRRegIndex r) const;
|
||||
|
||||
void SetupInitialRegs();
|
||||
|
||||
MIPSState *mips_;
|
||||
RiscVGen::RiscVEmitter *emit_ = nullptr;
|
||||
MIPSComp::JitOptions *jo_;
|
||||
MIPSComp::IRBlock *irBlock_ = nullptr;
|
||||
int irIndex_ = 0;
|
||||
|
||||
enum {
|
||||
NUM_RVREG = 32, // 31 actual registers, plus the zero/sp register which is not mappable.
|
||||
NUM_MIPSREG = RiscVJitConstants::TOTAL_MAPPABLE_MIPSREGS,
|
||||
NUM_RVREG = 32,
|
||||
};
|
||||
|
||||
RegStatusRiscV ar[NUM_RVREG]{};
|
||||
RegStatusMIPS mr[NUM_MIPSREG]{};
|
||||
|
||||
bool initialReady_ = false;
|
||||
bool pendingUnlock_ = false;
|
||||
RegStatusRiscV arInitial_[NUM_RVREG];
|
||||
RegStatusMIPS mrInitial_[NUM_MIPSREG];
|
||||
};
|
||||
@@ -29,221 +29,89 @@
|
||||
using namespace RiscVGen;
|
||||
using namespace RiscVJitConstants;
|
||||
|
||||
RiscVRegCacheFPU::RiscVRegCacheFPU(MIPSState *mipsState, MIPSComp::JitOptions *jo)
|
||||
: mips_(mipsState), jo_(jo) {}
|
||||
RiscVRegCacheFPU::RiscVRegCacheFPU(MIPSComp::JitOptions *jo)
|
||||
: IRNativeRegCacheBase(jo) {
|
||||
totalNativeRegs_ = NUM_RVFPUREG;
|
||||
}
|
||||
|
||||
void RiscVRegCacheFPU::Init(RiscVEmitter *emitter) {
|
||||
emit_ = emitter;
|
||||
}
|
||||
|
||||
void RiscVRegCacheFPU::Start(MIPSComp::IRBlock *irBlock) {
|
||||
if (!initialReady_) {
|
||||
SetupInitialRegs();
|
||||
initialReady_ = true;
|
||||
}
|
||||
|
||||
memcpy(ar, arInitial_, sizeof(ar));
|
||||
memcpy(mr, mrInitial_, sizeof(mr));
|
||||
pendingFlush_ = false;
|
||||
|
||||
irBlock_ = irBlock;
|
||||
irIndex_ = 0;
|
||||
}
|
||||
|
||||
void RiscVRegCacheFPU::SetupInitialRegs() {
|
||||
for (int i = 0; i < NUM_RVFPUREG; i++) {
|
||||
arInitial_[i].mipsReg = IRREG_INVALID;
|
||||
arInitial_[i].isDirty = false;
|
||||
}
|
||||
for (int i = 0; i < NUM_MIPSFPUREG; i++) {
|
||||
mrInitial_[i].loc = MIPSLoc::MEM;
|
||||
mrInitial_[i].reg = (int)INVALID_REG;
|
||||
mrInitial_[i].spillLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
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(IRRegIndex reg) {
|
||||
_dbg_assert_(IsValidReg(reg));
|
||||
return mr[reg].loc == MIPSLoc::MEM;
|
||||
}
|
||||
RiscVReg RiscVRegCacheFPU::MapReg(IRReg mipsReg, MIPSMap mapFlags) {
|
||||
_dbg_assert_(IsValidFPR(mipsReg));
|
||||
_dbg_assert_(mr[mipsReg + 32].loc == MIPSLoc::MEM || mr[mipsReg + 32].loc == MIPSLoc::FREG);
|
||||
|
||||
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.
|
||||
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);
|
||||
IRNativeReg nreg = MapNativeReg(MIPSLoc::FREG, mipsReg + 32, 1, mapFlags);
|
||||
if (nreg != -1)
|
||||
return (RiscVReg)(F0 + nreg);
|
||||
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 (ar[reg - F0].mipsReg != IRREG_INVALID && mr[ar[reg - F0].mipsReg].spillLock)
|
||||
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(ar[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;
|
||||
}
|
||||
|
||||
void RiscVRegCacheFPU::MapInIn(IRRegIndex rd, IRRegIndex rs) {
|
||||
SpillLock(rd, rs);
|
||||
void RiscVRegCacheFPU::MapInIn(IRReg rd, IRReg rs) {
|
||||
SpillLockFPR(rd, rs);
|
||||
MapReg(rd);
|
||||
MapReg(rs);
|
||||
ReleaseSpillLock(rd, rs);
|
||||
ReleaseSpillLockFPR(rd, rs);
|
||||
}
|
||||
|
||||
void RiscVRegCacheFPU::MapDirtyIn(IRRegIndex rd, IRRegIndex rs, bool avoidLoad) {
|
||||
SpillLock(rd, rs);
|
||||
void RiscVRegCacheFPU::MapDirtyIn(IRReg rd, IRReg rs, bool avoidLoad) {
|
||||
SpillLockFPR(rd, rs);
|
||||
bool load = !avoidLoad || rd == rs;
|
||||
MapReg(rd, load ? MIPSMap::DIRTY : MIPSMap::NOINIT);
|
||||
MapReg(rs);
|
||||
ReleaseSpillLock(rd, rs);
|
||||
ReleaseSpillLockFPR(rd, rs);
|
||||
}
|
||||
|
||||
void RiscVRegCacheFPU::MapDirtyInIn(IRRegIndex rd, IRRegIndex rs, IRRegIndex rt, bool avoidLoad) {
|
||||
SpillLock(rd, rs, rt);
|
||||
void RiscVRegCacheFPU::MapDirtyInIn(IRReg rd, IRReg rs, IRReg rt, bool avoidLoad) {
|
||||
SpillLockFPR(rd, rs, rt);
|
||||
bool load = !avoidLoad || (rd == rs || rd == rt);
|
||||
MapReg(rd, load ? MIPSMap::DIRTY : MIPSMap::NOINIT);
|
||||
MapReg(rt);
|
||||
MapReg(rs);
|
||||
ReleaseSpillLock(rd, rs, rt);
|
||||
ReleaseSpillLockFPR(rd, rs, rt);
|
||||
}
|
||||
|
||||
RiscVReg RiscVRegCacheFPU::MapDirtyInTemp(IRRegIndex rd, IRRegIndex rs, bool avoidLoad) {
|
||||
SpillLock(rd, rs);
|
||||
RiscVReg RiscVRegCacheFPU::MapDirtyInTemp(IRReg rd, IRReg rs, bool avoidLoad) {
|
||||
SpillLockFPR(rd, rs);
|
||||
bool load = !avoidLoad || rd == rs;
|
||||
MapReg(rd, load ? MIPSMap::DIRTY : MIPSMap::NOINIT);
|
||||
MapReg(rs);
|
||||
RiscVReg temp = AllocateReg();
|
||||
ReleaseSpillLock(rd, rs);
|
||||
RiscVReg temp = (RiscVReg)(F0 + AllocateReg(MIPSLoc::FREG));
|
||||
ReleaseSpillLockFPR(rd, rs);
|
||||
return temp;
|
||||
}
|
||||
|
||||
void RiscVRegCacheFPU::Map4DirtyIn(IRRegIndex rdbase, IRRegIndex rsbase, bool avoidLoad) {
|
||||
void RiscVRegCacheFPU::Map4DirtyIn(IRReg rdbase, IRReg rsbase, bool avoidLoad) {
|
||||
for (int i = 0; i < 4; ++i)
|
||||
SpillLock(rdbase + i, rsbase + i);
|
||||
SpillLockFPR(rdbase + i, rsbase + i);
|
||||
bool load = !avoidLoad || (rdbase < rsbase + 4 && rdbase + 4 > rsbase);
|
||||
for (int i = 0; i < 4; ++i)
|
||||
MapReg(rdbase + i, load ? MIPSMap::DIRTY : MIPSMap::NOINIT);
|
||||
for (int i = 0; i < 4; ++i)
|
||||
MapReg(rsbase + i);
|
||||
for (int i = 0; i < 4; ++i)
|
||||
ReleaseSpillLock(rdbase + i, rsbase + i);
|
||||
ReleaseSpillLockFPR(rdbase + i, rsbase + i);
|
||||
}
|
||||
|
||||
void RiscVRegCacheFPU::Map4DirtyInIn(IRRegIndex rdbase, IRRegIndex rsbase, IRRegIndex rtbase, bool avoidLoad) {
|
||||
void RiscVRegCacheFPU::Map4DirtyInIn(IRReg rdbase, IRReg rsbase, IRReg rtbase, bool avoidLoad) {
|
||||
for (int i = 0; i < 4; ++i)
|
||||
SpillLock(rdbase + i, rsbase + i, rtbase + i);
|
||||
SpillLockFPR(rdbase + i, rsbase + i, rtbase + i);
|
||||
bool load = !avoidLoad || (rdbase < rsbase + 4 && rdbase + 4 > rsbase) || (rdbase < rtbase + 4 && rdbase + 4 > rtbase);
|
||||
for (int i = 0; i < 4; ++i)
|
||||
MapReg(rdbase + i, load ? MIPSMap::DIRTY : MIPSMap::NOINIT);
|
||||
@@ -252,213 +120,92 @@ void RiscVRegCacheFPU::Map4DirtyInIn(IRRegIndex rdbase, IRRegIndex rsbase, IRReg
|
||||
for (int i = 0; i < 4; ++i)
|
||||
MapReg(rtbase + i);
|
||||
for (int i = 0; i < 4; ++i)
|
||||
ReleaseSpillLock(rdbase + i, rsbase + i, rtbase + i);
|
||||
ReleaseSpillLockFPR(rdbase + i, rsbase + i, rtbase + i);
|
||||
}
|
||||
|
||||
RiscVReg RiscVRegCacheFPU::Map4DirtyInTemp(IRRegIndex rdbase, IRRegIndex rsbase, bool avoidLoad) {
|
||||
RiscVReg RiscVRegCacheFPU::Map4DirtyInTemp(IRReg rdbase, IRReg rsbase, bool avoidLoad) {
|
||||
for (int i = 0; i < 4; ++i)
|
||||
SpillLock(rdbase + i, rsbase + i);
|
||||
SpillLockFPR(rdbase + i, rsbase + i);
|
||||
bool load = !avoidLoad || (rdbase < rsbase + 4 && rdbase + 4 > rsbase);
|
||||
for (int i = 0; i < 4; ++i)
|
||||
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);
|
||||
ReleaseSpillLockFPR(rdbase + i, rsbase + i);
|
||||
return temp;
|
||||
}
|
||||
|
||||
void RiscVRegCacheFPU::FlushRiscVReg(RiscVReg r) {
|
||||
void RiscVRegCacheFPU::LoadNativeReg(IRNativeReg nreg, IRReg first, int lanes) {
|
||||
RiscVReg r = (RiscVReg)(F0 + nreg);
|
||||
_dbg_assert_(r >= F0 && r <= F31);
|
||||
int reg = r - F0;
|
||||
if (ar[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_->FL(32, r, CTXREG, GetMipsRegOffset(first));
|
||||
} else {
|
||||
_assert_msg_(mr[first].loc == MIPSLoc::FREG, "Cannot store this type: %d", (int)mr[first].loc);
|
||||
}
|
||||
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);
|
||||
void RiscVRegCacheFPU::StoreNativeReg(IRNativeReg nreg, IRReg first, int lanes) {
|
||||
RiscVReg r = (RiscVReg)(F0 + nreg);
|
||||
_dbg_assert_(r >= F0 && r <= F31);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
void RiscVRegCacheFPU::SetNativeRegValue(IRNativeReg nreg, uint32_t imm) {
|
||||
_assert_msg_(false, "Set float to imm is unsupported");
|
||||
}
|
||||
|
||||
case MIPSLoc::MEM:
|
||||
return INVALID_REG;
|
||||
void RiscVRegCacheFPU::StoreRegValue(IRReg mreg, uint32_t imm) {
|
||||
_assert_msg_(false, "Storing imms to floats is unsupported");
|
||||
}
|
||||
|
||||
default:
|
||||
_assert_(false);
|
||||
return INVALID_REG;
|
||||
}
|
||||
void RiscVRegCacheFPU::FlushR(IRReg r) {
|
||||
_dbg_assert_(IsValidFPR(r));
|
||||
FlushReg(r + 32);
|
||||
}
|
||||
|
||||
void RiscVRegCacheFPU::FlushBeforeCall() {
|
||||
// Note: don't set this false at the end, since we don't flush everything.
|
||||
if (!pendingFlush_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
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(IRReg r) {
|
||||
_dbg_assert_(IsValidFPR(r));
|
||||
DiscardReg(r + 32);
|
||||
}
|
||||
|
||||
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);
|
||||
RiscVReg RiscVRegCacheFPU::R(IRReg mipsReg) {
|
||||
_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(IRRegIndex r) const {
|
||||
if (r < 0 || r >= NUM_MIPSFPUREG)
|
||||
return false;
|
||||
void RiscVRegCacheFPU::SetupInitialRegs() {
|
||||
IRNativeRegCacheBase::SetupInitialRegs();
|
||||
|
||||
// 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;
|
||||
// TODO: Move to a shared cache?
|
||||
mrInitial_[0].loc = MIPSLoc::MEM;
|
||||
}
|
||||
@@ -21,92 +21,45 @@
|
||||
#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 {
|
||||
class RiscVRegCacheFPU : public IRNativeRegCacheBase {
|
||||
public:
|
||||
RiscVRegCacheFPU(MIPSState *mipsState, MIPSComp::JitOptions *jo);
|
||||
~RiscVRegCacheFPU() {}
|
||||
RiscVRegCacheFPU(MIPSComp::JitOptions *jo);
|
||||
|
||||
void Init(RiscVGen::RiscVEmitter *emitter);
|
||||
void Start(MIPSComp::IRBlock *irBlock);
|
||||
void SetIRIndex(int index) {
|
||||
irIndex_ = index;
|
||||
}
|
||||
|
||||
// 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);
|
||||
RiscVGen::RiscVReg MapReg(IRReg reg, MIPSMap mapFlags = 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);
|
||||
RiscVGen::RiscVReg MapDirtyInTemp(IRRegIndex rd, IRRegIndex rs, bool avoidLoad = true);
|
||||
void Map4DirtyIn(IRRegIndex rdbase, IRRegIndex rsbase, bool avoidLoad = true);
|
||||
void Map4DirtyInIn(IRRegIndex rdbase, IRRegIndex rsbase, IRRegIndex rtbase, bool avoidLoad = true);
|
||||
RiscVGen::RiscVReg Map4DirtyInTemp(IRRegIndex rdbase, IRRegIndex rsbase, bool avoidLoad = true);
|
||||
void MapInIn(IRReg rd, IRReg rs);
|
||||
void MapDirtyIn(IRReg rd, IRReg rs, bool avoidLoad = true);
|
||||
void MapDirtyInIn(IRReg rd, IRReg rs, IRReg rt, bool avoidLoad = true);
|
||||
RiscVGen::RiscVReg MapDirtyInTemp(IRReg rd, IRReg rs, bool avoidLoad = true);
|
||||
void Map4DirtyIn(IRReg rdbase, IRReg rsbase, bool avoidLoad = true);
|
||||
void Map4DirtyInIn(IRReg rdbase, IRReg rsbase, IRReg rtbase, bool avoidLoad = true);
|
||||
RiscVGen::RiscVReg Map4DirtyInTemp(IRReg rdbase, IRReg rsbase, bool avoidLoad = true);
|
||||
void FlushBeforeCall();
|
||||
void FlushAll();
|
||||
void FlushR(IRRegIndex r);
|
||||
void FlushRiscVReg(RiscVGen::RiscVReg r);
|
||||
void DiscardR(IRRegIndex r);
|
||||
void FlushR(IRReg r);
|
||||
void DiscardR(IRReg r);
|
||||
|
||||
RiscVGen::RiscVReg R(int preg); // Returns a cached register
|
||||
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 LoadNativeReg(IRNativeReg nreg, IRReg first, int lanes) override;
|
||||
void StoreNativeReg(IRNativeReg nreg, IRReg first, int lanes) override;
|
||||
void SetNativeRegValue(IRNativeReg nreg, uint32_t imm) override;
|
||||
void StoreRegValue(IRReg mreg, uint32_t imm) override;
|
||||
|
||||
private:
|
||||
const RiscVGen::RiscVReg *GetMIPSAllocationOrder(int &count);
|
||||
RiscVGen::RiscVReg AllocateReg();
|
||||
RiscVGen::RiscVReg FindBestToSpill(bool unusedOnly, bool *clobbered);
|
||||
RiscVGen::RiscVReg RiscVRegForFlush(IRRegIndex r);
|
||||
int GetMipsRegOffset(IRRegIndex r);
|
||||
|
||||
bool IsValidReg(IRRegIndex r) const;
|
||||
|
||||
void SetupInitialRegs();
|
||||
|
||||
MIPSState *mips_;
|
||||
RiscVGen::RiscVEmitter *emit_ = nullptr;
|
||||
MIPSComp::JitOptions *jo_;
|
||||
MIPSComp::IRBlock *irBlock_ = nullptr;
|
||||
int irIndex_ = 0;
|
||||
|
||||
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];
|
||||
};
|
||||
Reference in new issue
Block a user