Merge pull request #17934 from unknownbrackets/riscv-centralize

RISC-V: Centralize IR regcaches
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2023-08-20 14:49:18 +02:00
commit cd1c5beb60
14 files changed
+1421 -1503

No files matched your search

+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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;
};
+32 -36
View File
@@ -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;
+3 -5
View File
@@ -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;
+11 -14
View File
@@ -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);
+6 -6
View File
@@ -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);
+11 -22
View File
@@ -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));
+5 -5
View File
@@ -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);
+3 -3
View File
@@ -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_);
}
File diff suppressed because it is too large. Load diff
+30 -128
View File
@@ -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];
};
+79 -332
View File
@@ -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;
}
+22 -69
View File
@@ -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];
};