1268 lines
37 KiB
C++
1268 lines
37 KiB
C++
// 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 (isImm_[rd]) {
|
|
if (rd == 0) {
|
|
return;
|
|
}
|
|
_assert_((rd > 0 && rd < 32) || (rd >= IRTEMP_0 && rd < IRREG_VFPU_CTRL_BASE));
|
|
ir_->WriteSetConstant(rd, immVal_[rd]);
|
|
isImm_[rd] = false;
|
|
}
|
|
}
|
|
|
|
void IRImmRegCache::Discard(IRReg rd) {
|
|
if (rd == 0) {
|
|
return;
|
|
}
|
|
isImm_[rd] = false;
|
|
}
|
|
|
|
IRImmRegCache::IRImmRegCache(IRWriter *ir) : ir_(ir) {
|
|
memset(&isImm_, 0, sizeof(isImm_));
|
|
memset(&immVal_, 0, sizeof(immVal_));
|
|
isImm_[0] = true;
|
|
ir_ = ir;
|
|
}
|
|
|
|
void IRImmRegCache::FlushAll() {
|
|
for (int i = 1; i < TOTAL_MAPPABLE_IRREGS; ) {
|
|
if (isImm_[i]) {
|
|
Flush(i);
|
|
}
|
|
|
|
// Most of the time, lots are not. This speeds it up a lot.
|
|
bool *next = (bool *)memchr(&isImm_[i], 1, TOTAL_MAPPABLE_IRREGS - i);
|
|
if (!next)
|
|
break;
|
|
i = (int)(next - &isImm_[0]);
|
|
}
|
|
}
|
|
|
|
void IRImmRegCache::MapIn(IRReg rd) {
|
|
Flush(rd);
|
|
}
|
|
|
|
void IRImmRegCache::MapDirty(IRReg rd) {
|
|
Discard(rd);
|
|
}
|
|
|
|
void IRImmRegCache::MapInIn(IRReg rs, IRReg rt) {
|
|
Flush(rs);
|
|
Flush(rt);
|
|
}
|
|
|
|
void IRImmRegCache::MapInInIn(IRReg rd, IRReg rs, IRReg rt) {
|
|
Flush(rd);
|
|
Flush(rs);
|
|
Flush(rt);
|
|
}
|
|
|
|
void IRImmRegCache::MapDirtyIn(IRReg rd, IRReg rs) {
|
|
if (rs != rd) {
|
|
Discard(rd);
|
|
}
|
|
Flush(rs);
|
|
}
|
|
|
|
void IRImmRegCache::MapDirtyInIn(IRReg rd, IRReg rs, IRReg rt) {
|
|
if (rs != rd && rt != rd) {
|
|
Discard(rd);
|
|
}
|
|
Flush(rs);
|
|
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]) * config_.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_(config_.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;
|
|
}
|
|
|
|
int IRNativeRegCacheBase::GetFPRLaneCount(IRReg fpr) {
|
|
if (!IsFPRMapped(fpr))
|
|
return 0;
|
|
if (mr[fpr + 32].lane == -1)
|
|
return 1;
|
|
|
|
IRReg base = fpr + 32 - mr[fpr + 32].lane;
|
|
int c = 1;
|
|
for (int i = 1; i < 4; ++i) {
|
|
if (mr[base + i].nReg != mr[base].nReg || mr[base + i].loc != mr[base].loc)
|
|
return c;
|
|
if (mr[base + i].lane != i)
|
|
return c;
|
|
|
|
c++;
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
int IRNativeRegCacheBase::GetFPRLane(IRReg fpr) {
|
|
_dbg_assert_(IsValidFPR(fpr));
|
|
if (mr[fpr + 32].loc == MIPSLoc::FREG || mr[fpr + 32].loc == MIPSLoc::VREG) {
|
|
int l = mr[fpr + 32].lane;
|
|
return l == -1 ? 0 : l;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
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::SetSpillLockIRIndex(IRReg r1, int index) {
|
|
if (!mr[r1].isStatic)
|
|
mr[r1].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, MIPSMap flags) {
|
|
_dbg_assert_(type == MIPSLoc::REG || type == MIPSLoc::FREG || type == MIPSLoc::VREG);
|
|
|
|
IRNativeReg nreg = FindFreeReg(type, flags);
|
|
if (nreg != -1)
|
|
return nreg;
|
|
|
|
// Still nothing. Let's spill a reg and goto 10.
|
|
bool clobbered;
|
|
IRNativeReg bestToSpill = FindBestToSpill(type, flags, true, &clobbered);
|
|
if (bestToSpill == -1) {
|
|
bestToSpill = FindBestToSpill(type, flags, false, &clobbered);
|
|
}
|
|
|
|
if (bestToSpill != -1) {
|
|
if (clobbered) {
|
|
DiscardNativeReg(bestToSpill);
|
|
} else {
|
|
FlushNativeReg(bestToSpill);
|
|
}
|
|
// Now one must be free.
|
|
return FindFreeReg(type, flags);
|
|
}
|
|
|
|
// 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, MIPSMap flags) const {
|
|
int allocCount = 0, base = 0;
|
|
const int *allocOrder = GetAllocationOrder(type, flags, allocCount, base);
|
|
|
|
for (int i = 0; i < allocCount; i++) {
|
|
IRNativeReg nreg = IRNativeReg(allocOrder[i] - base);
|
|
|
|
if (nr[nreg].mipsReg == IRREG_INVALID && nr[nreg].tempLockIRIndex < irIndex_) {
|
|
return nreg;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
bool IRNativeRegCacheBase::IsGPRClobbered(IRReg gpr) const {
|
|
_dbg_assert_(IsValidGPR(gpr));
|
|
return IsRegClobbered(MIPSLoc::REG, gpr);
|
|
}
|
|
|
|
bool IRNativeRegCacheBase::IsFPRClobbered(IRReg fpr) const {
|
|
_dbg_assert_(IsValidFPR(fpr));
|
|
return IsRegClobbered(MIPSLoc::FREG, fpr + 32);
|
|
}
|
|
|
|
IRUsage IRNativeRegCacheBase::GetNextRegUsage(const IRSituation &info, MIPSLoc type, IRReg r) const {
|
|
if (type == MIPSLoc::REG)
|
|
return IRNextGPRUsage(r, info);
|
|
else if (type == MIPSLoc::FREG || type == MIPSLoc::VREG)
|
|
return IRNextFPRUsage(r - 32, info);
|
|
_assert_msg_(false, "Unknown spill allocation type");
|
|
return IRUsage::UNKNOWN;
|
|
}
|
|
|
|
bool IRNativeRegCacheBase::IsRegClobbered(MIPSLoc type, IRReg r) const {
|
|
static const int UNUSED_LOOKAHEAD_OPS = 30;
|
|
|
|
IRSituation info;
|
|
info.lookaheadCount = UNUSED_LOOKAHEAD_OPS;
|
|
// We look starting one ahead, unlike spilling. We want to know if it clobbers later.
|
|
info.currentIndex = irIndex_ + 1;
|
|
info.instructions = irBlock_->GetInstructions();
|
|
info.numInstructions = irBlock_->GetNumInstructions();
|
|
|
|
// Make sure we're on the first one if this is multi-lane.
|
|
IRReg first = r;
|
|
if (mr[r].lane != -1)
|
|
first -= mr[r].lane;
|
|
|
|
IRUsage usage = GetNextRegUsage(info, type, first);
|
|
if (usage == IRUsage::CLOBBERED) {
|
|
// If multiple mips regs use this native reg (i.e. vector, HI/LO), check each.
|
|
bool canClobber = true;
|
|
for (IRReg m = first + 1; mr[m].nReg == mr[first].nReg && m < IRREG_INVALID && canClobber; ++m)
|
|
canClobber = GetNextRegUsage(info, type, m) == IRUsage::CLOBBERED;
|
|
|
|
return canClobber;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool IRNativeRegCacheBase::IsRegRead(MIPSLoc type, IRReg first) const {
|
|
static const int UNUSED_LOOKAHEAD_OPS = 30;
|
|
|
|
IRSituation info;
|
|
info.lookaheadCount = UNUSED_LOOKAHEAD_OPS;
|
|
// We look starting one ahead, unlike spilling.
|
|
info.currentIndex = irIndex_ + 1;
|
|
info.instructions = irBlock_->GetInstructions();
|
|
info.numInstructions = irBlock_->GetNumInstructions();
|
|
|
|
// Note: this intentionally doesn't look at the full reg, only the lane.
|
|
IRUsage usage = GetNextRegUsage(info, type, first);
|
|
return usage == IRUsage::READ;
|
|
}
|
|
|
|
IRNativeReg IRNativeRegCacheBase::FindBestToSpill(MIPSLoc type, MIPSMap flags, bool unusedOnly, bool *clobbered) const {
|
|
int allocCount = 0, base = 0;
|
|
const int *allocOrder = GetAllocationOrder(type, flags, 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();
|
|
|
|
*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 = GetNextRegUsage(info, type, 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 = GetNextRegUsage(info, type, m) == 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?
|
|
*clobbered = mipsReg == MIPS_REG_ZERO;
|
|
return nreg;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
bool IRNativeRegCacheBase::IsNativeRegCompatible(IRNativeReg nreg, MIPSLoc type, MIPSMap flags, int lanes) {
|
|
int allocCount = 0, base = 0;
|
|
const int *allocOrder = GetAllocationOrder(type, flags, allocCount, base);
|
|
|
|
for (int i = 0; i < allocCount; i++) {
|
|
IRNativeReg allocReg = IRNativeReg(allocOrder[i] - base);
|
|
if (allocReg == nreg)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool IRNativeRegCacheBase::TransferNativeReg(IRNativeReg nreg, IRNativeReg dest, MIPSLoc type, IRReg first, int lanes, MIPSMap flags) {
|
|
// To be overridden if the backend supports transfers.
|
|
return false;
|
|
}
|
|
|
|
void IRNativeRegCacheBase::DiscardNativeReg(IRNativeReg nreg) {
|
|
_assert_msg_(nreg >= 0 && nreg < config_.totalNativeRegs, "DiscardNativeReg on invalid register %d", nreg);
|
|
if (nr[nreg].mipsReg != IRREG_INVALID) {
|
|
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) {
|
|
_assert_(nr[nreg].mipsReg != MIPS_REG_ZERO);
|
|
|
|
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 < config_.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(bool gprs, bool fprs) {
|
|
// Note: make sure not to change the registers when flushing.
|
|
// Branching code may expect the native reg to retain its value.
|
|
|
|
if (!mr[MIPS_REG_ZERO].isStatic && mr[MIPS_REG_ZERO].nReg != -1)
|
|
DiscardNativeReg(mr[MIPS_REG_ZERO].nReg);
|
|
|
|
for (int i = 1; i < TOTAL_MAPPABLE_IRREGS; i++) {
|
|
IRReg mipsReg = (IRReg)i;
|
|
if (!fprs && i >= 32 && IsValidFPR(mipsReg))
|
|
continue;
|
|
if (!gprs && IsValidGPR(mipsReg))
|
|
continue;
|
|
|
|
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 (!fprs && allocs[i].loc != MIPSLoc::FREG && allocs[i].loc != MIPSLoc::VREG)
|
|
continue;
|
|
if (!gprs && allocs[i].loc != MIPSLoc::REG)
|
|
continue;
|
|
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 < config_.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);
|
|
}
|
|
}
|
|
}
|
|
|
|
void IRNativeRegCacheBase::Map(const IRInst &inst) {
|
|
Mapping mapping[3];
|
|
MappingFromInst(inst, mapping);
|
|
|
|
ApplyMapping(mapping, 3);
|
|
CleanupMapping(mapping, 3);
|
|
}
|
|
|
|
void IRNativeRegCacheBase::MapWithExtra(const IRInst &inst, std::vector<Mapping> extra) {
|
|
extra.resize(extra.size() + 3);
|
|
MappingFromInst(inst, &extra[extra.size() - 3]);
|
|
|
|
ApplyMapping(extra.data(), (int)extra.size());
|
|
CleanupMapping(extra.data(), (int)extra.size());
|
|
}
|
|
|
|
IRNativeReg IRNativeRegCacheBase::MapWithTemp(const IRInst &inst, MIPSLoc type) {
|
|
Mapping mapping[3];
|
|
MappingFromInst(inst, mapping);
|
|
|
|
ApplyMapping(mapping, 3);
|
|
// Grab a temp while things are spill locked.
|
|
IRNativeReg temp = AllocateReg(type, MIPSMap::INIT);
|
|
CleanupMapping(mapping, 3);
|
|
return temp;
|
|
}
|
|
|
|
void IRNativeRegCacheBase::ApplyMapping(const Mapping *mapping, int count) {
|
|
for (int i = 0; i < count; ++i) {
|
|
SetSpillLockIRIndex(mapping[i].reg, irIndex_);
|
|
if (!config_.mapFPUSIMD && mapping[i].type != 'G') {
|
|
for (int j = 1; j < mapping[i].lanes; ++j)
|
|
SetSpillLockIRIndex(mapping[i].reg + j, irIndex_);
|
|
}
|
|
}
|
|
|
|
auto isNoinit = [](MIPSMap f) {
|
|
return (f & MIPSMap::NOINIT) == MIPSMap::NOINIT;
|
|
};
|
|
|
|
auto mapRegs = [&](int i) {
|
|
MIPSLoc type = MIPSLoc::MEM;
|
|
switch (mapping[i].type) {
|
|
case 'G': type = MIPSLoc::REG; break;
|
|
case 'F': type = MIPSLoc::FREG; break;
|
|
case 'V': type = MIPSLoc::VREG; break;
|
|
|
|
case '_':
|
|
// Ignored intentionally.
|
|
return;
|
|
|
|
default:
|
|
_assert_msg_(false, "Unexpected type: %c", mapping[i].type);
|
|
return;
|
|
}
|
|
|
|
bool mapSIMD = config_.mapFPUSIMD || mapping[i].type == 'G';
|
|
MIPSMap flags = mapping[i].flags;
|
|
for (int j = 0; j < count; ++j) {
|
|
if (mapping[j].type == mapping[i].type && mapping[j].reg == mapping[i].reg && i != j) {
|
|
_assert_msg_(!mapSIMD || mapping[j].lanes == mapping[i].lanes, "Lane aliasing not supported yet");
|
|
|
|
if (!isNoinit(mapping[j].flags) && isNoinit(flags)) {
|
|
flags = (flags & MIPSMap::BACKEND_MASK) | MIPSMap::DIRTY;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mapSIMD) {
|
|
MapNativeReg(type, mapping[i].reg, mapping[i].lanes, flags);
|
|
return;
|
|
}
|
|
|
|
for (int j = 0; j < mapping[i].lanes; ++j)
|
|
MapNativeReg(type, mapping[i].reg + j, 1, flags);
|
|
};
|
|
auto mapFilteredRegs = [&](auto pred) {
|
|
for (int i = 0; i < count; ++i) {
|
|
if (pred(mapping[i].flags))
|
|
mapRegs(i);
|
|
}
|
|
};
|
|
|
|
// Do two passes: with backend special flags, and without.
|
|
mapFilteredRegs([](MIPSMap flags) {
|
|
return (flags & MIPSMap::BACKEND_MASK) != MIPSMap::INIT;
|
|
});
|
|
mapFilteredRegs([](MIPSMap flags) {
|
|
return (flags & MIPSMap::BACKEND_MASK) == MIPSMap::INIT;
|
|
});
|
|
}
|
|
|
|
void IRNativeRegCacheBase::CleanupMapping(const Mapping *mapping, int count) {
|
|
for (int i = 0; i < count; ++i) {
|
|
SetSpillLockIRIndex(mapping[i].reg, -1);
|
|
if (!config_.mapFPUSIMD && mapping[i].type != 'G') {
|
|
for (int j = 1; j < mapping[i].lanes; ++j)
|
|
SetSpillLockIRIndex(mapping[i].reg + j, -1);
|
|
}
|
|
}
|
|
|
|
// Sanity check. If these don't pass, we may have Vec overlap issues or etc.
|
|
for (int i = 0; i < count; ++i) {
|
|
if (mapping[i].reg != IRREG_INVALID) {
|
|
auto &mreg = mr[mapping[i].reg];
|
|
_dbg_assert_(mreg.nReg != -1);
|
|
if (mapping[i].type == 'G') {
|
|
_dbg_assert_(mreg.loc == MIPSLoc::REG || mreg.loc == MIPSLoc::REG_AS_PTR || mreg.loc == MIPSLoc::REG_IMM);
|
|
} else if (mapping[i].type == 'F') {
|
|
_dbg_assert_(mreg.loc == MIPSLoc::FREG);
|
|
} else if (mapping[i].type == 'V') {
|
|
_dbg_assert_(mreg.loc == MIPSLoc::VREG);
|
|
}
|
|
if (mapping[i].lanes != 1 && (config_.mapFPUSIMD || mapping[i].type == 'G')) {
|
|
_dbg_assert_(mreg.lane == 0);
|
|
_dbg_assert_(mr[mapping[i].reg + mapping[i].lanes - 1].lane == mapping[i].lanes - 1);
|
|
_dbg_assert_(mreg.nReg == mr[mapping[i].reg + mapping[i].lanes - 1].nReg);
|
|
} else {
|
|
_dbg_assert_(mreg.lane == -1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void IRNativeRegCacheBase::MappingFromInst(const IRInst &inst, Mapping mapping[3]) {
|
|
mapping[0].reg = inst.dest;
|
|
mapping[1].reg = inst.src1;
|
|
mapping[2].reg = inst.src2;
|
|
|
|
const IRMeta *m = GetIRMeta(inst.op);
|
|
for (int i = 0; i < 3; ++i) {
|
|
switch (m->types[i]) {
|
|
case 'G':
|
|
mapping[i].type = 'G';
|
|
_assert_msg_(IsValidGPR(mapping[i].reg), "G was not valid GPR?");
|
|
break;
|
|
|
|
case 'F':
|
|
mapping[i].reg += 32;
|
|
mapping[i].type = 'F';
|
|
_assert_msg_(IsValidFPR(mapping[i].reg - 32), "F was not valid FPR?");
|
|
break;
|
|
|
|
case 'V':
|
|
case '2':
|
|
mapping[i].reg += 32;
|
|
mapping[i].type = config_.mapUseVRegs ? 'V' : 'F';
|
|
mapping[i].lanes = m->types[i] == 'V' ? 4 : (m->types[i] == '2' ? 2 : 1);
|
|
_assert_msg_(IsValidFPR(mapping[i].reg - 32), "%c was not valid FPR?", m->types[i]);
|
|
break;
|
|
|
|
case 'T':
|
|
mapping[i].type = 'G';
|
|
_assert_msg_(mapping[i].reg < VFPU_CTRL_MAX, "T was not valid VFPU CTRL?");
|
|
mapping[i].reg += IRREG_VFPU_CTRL_BASE;
|
|
break;
|
|
|
|
case '\0':
|
|
case '_':
|
|
case 'C':
|
|
case 'I':
|
|
case 'v':
|
|
case 's':
|
|
case 'm':
|
|
mapping[i].type = '_';
|
|
mapping[i].reg = IRREG_INVALID;
|
|
mapping[i].lanes = 0;
|
|
break;
|
|
|
|
default:
|
|
_assert_msg_(mapping[i].reg == IRREG_INVALID, "Unexpected register type %c", m->types[i]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (mapping[0].type != '_') {
|
|
if ((m->flags & IRFLAG_SRC3DST) != 0)
|
|
mapping[0].flags = MIPSMap::DIRTY;
|
|
else if ((m->flags & IRFLAG_SRC3) != 0)
|
|
mapping[0].flags = MIPSMap::INIT;
|
|
else
|
|
mapping[0].flags = MIPSMap::NOINIT;
|
|
}
|
|
}
|
|
|
|
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, flags);
|
|
} else if (!IsNativeRegCompatible(nreg, type, flags, lanes)) {
|
|
// If it's not compatible, we'll need to reallocate.
|
|
if (TransferNativeReg(nreg, -1, type, first, lanes, flags)) {
|
|
nreg = mr[first].nReg;
|
|
} else {
|
|
FlushNativeReg(nreg);
|
|
nreg = AllocateReg(type, flags);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MIPSLoc::FREG:
|
|
case MIPSLoc::VREG:
|
|
if (type != mr[first].loc) {
|
|
nreg = AllocateReg(type, flags);
|
|
} else if (!IsNativeRegCompatible(nreg, type, flags, lanes)) {
|
|
if (TransferNativeReg(nreg, -1, type, first, lanes, flags)) {
|
|
nreg = mr[first].nReg;
|
|
} else {
|
|
FlushNativeReg(nreg);
|
|
nreg = AllocateReg(type, flags);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MIPSLoc::IMM:
|
|
case MIPSLoc::MEM:
|
|
nreg = AllocateReg(type, flags);
|
|
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.
|
|
int oldlane = mreg.lane == -1 ? 0 : mreg.lane;
|
|
bool mismatch = oldlanes != lanes || oldlane != 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.
|
|
if (!TransferNativeReg(mreg.nReg, nreg, type, first, lanes, flags)) {
|
|
// 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);
|
|
}
|
|
// The mismatch has been "resolved" now.
|
|
mismatch = false;
|
|
} else if (oldlanes != 1) {
|
|
// Even if we don't care about the current contents, we can't discard outside.
|
|
bool extendsBefore = oldlane > i;
|
|
bool extendsAfter = i + oldlanes - oldlane > lanes;
|
|
if (extendsBefore || extendsAfter) {
|
|
// Usually, this is 4->1. Check for clobber.
|
|
bool clobbered = false;
|
|
if (lanes == 1) {
|
|
IRSituation info;
|
|
info.lookaheadCount = 16;
|
|
info.currentIndex = irIndex_;
|
|
info.instructions = irBlock_->GetInstructions();
|
|
info.numInstructions = irBlock_->GetNumInstructions();
|
|
|
|
IRReg basefpr = first - oldlane - 32;
|
|
clobbered = true;
|
|
for (int l = 0; l < oldlanes; ++l) {
|
|
// Ignore the one we're modifying.
|
|
if (l == oldlane)
|
|
continue;
|
|
|
|
if (IRNextFPRUsage(basefpr + l, info) != IRUsage::CLOBBERED) {
|
|
clobbered = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (clobbered)
|
|
DiscardNativeReg(mreg.nReg);
|
|
else
|
|
FlushNativeReg(mreg.nReg);
|
|
|
|
// That took care of the mismatch, either by clobber or flush.
|
|
mismatch = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If it's still in a different reg, either discard or possibly transfer.
|
|
if (mreg.nReg != -1 && (mreg.nReg != nreg || mismatch)) {
|
|
_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...
|
|
if (!TransferNativeReg(mreg.nReg, nreg, type, first, lanes, flags))
|
|
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 = false;
|
|
}
|
|
|
|
// 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");
|
|
#ifndef MASKED_PSP_MEMORY
|
|
AdjustNativeRegAsPtr(nreg, false);
|
|
#endif
|
|
for (int i = 0; i < lanes; ++i)
|
|
mr[first + i].loc = type;
|
|
#ifdef MASKED_PSP_MEMORY
|
|
LoadNativeReg(nreg, first, lanes);
|
|
#endif
|
|
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 = false;
|
|
_assert_(first != MIPS_REG_ZERO);
|
|
}
|
|
}
|
|
|
|
IRNativeReg IRNativeRegCacheBase::MapNativeRegAsPointer(IRReg gpr) {
|
|
_dbg_assert_(IsValidGPRNoZero(gpr));
|
|
|
|
// Already mapped.
|
|
if (mr[gpr].loc == MIPSLoc::REG_AS_PTR) {
|
|
return mr[gpr].nReg;
|
|
}
|
|
|
|
// Cannot use if somehow multilane.
|
|
if (mr[gpr].nReg != -1 && mr[gpr].lane != -1) {
|
|
FlushNativeReg(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;
|
|
|
|
#ifdef MASKED_PSP_MEMORY
|
|
if (nr[mr[gpr].nReg].isDirty) {
|
|
StoreNativeReg(mr[gpr].nReg, gpr, 1);
|
|
nr[mr[gpr].nReg].isDirty = false;
|
|
}
|
|
#endif
|
|
|
|
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;
|
|
}
|