Files
ppsspp/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp
T
Unknown W. Brackets 4e41f83ecc riscv: Centralize IR reg cache metadata checks.
These are all largely the same between backends.
2023-08-17 23:03:31 -07:00

291 lines
8.7 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 "Common/CPUDetect.h"
#include "Core/MIPS/IR/IRInst.h"
#include "Core/MIPS/IR/IRAnalysis.h"
#include "Core/MIPS/RiscV/RiscVRegCacheFPU.h"
#include "Core/MIPS/JitCommon/JitState.h"
#include "Core/Reporting.h"
using namespace RiscVGen;
using namespace RiscVJitConstants;
RiscVRegCacheFPU::RiscVRegCacheFPU(MIPSComp::JitOptions *jo)
: IRNativeRegCacheBase(jo) {
totalNativeRegs_ = NUM_RVFPUREG;
}
void RiscVRegCacheFPU::Init(RiscVEmitter *emitter) {
emit_ = emitter;
}
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 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;
}
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);
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 + 32].loc == MIPSLoc::FREG) {
_assert_msg_(nr[mr[mipsReg + 32].nReg].mipsReg == mipsReg + 32, "FPR mapping out of sync, IR=%i", mipsReg);
if ((mapFlags & MIPSMap::DIRTY) == MIPSMap::DIRTY) {
nr[mr[mipsReg + 32].nReg].isDirty = true;
}
return (RiscVReg)(mr[mipsReg + 32].nReg + F0);
}
// Okay, not mapped, so we need to allocate an RV register.
IRNativeReg nreg = AllocateReg(MIPSLoc::FREG);
if (nreg != -1) {
// That means it's free. Grab it, and load the value into it (if requested).
nr[nreg].isDirty = (mapFlags & MIPSMap::DIRTY) == MIPSMap::DIRTY;
if ((mapFlags & MIPSMap::NOINIT) != MIPSMap::NOINIT) {
if (mr[mipsReg + 32].loc == MIPSLoc::MEM) {
emit_->FL(32, (RiscVReg)(F0 + nreg), CTXREG, GetMipsRegOffset(mipsReg + 32));
}
}
nr[nreg].mipsReg = mipsReg + 32;
mr[mipsReg + 32].loc = MIPSLoc::FREG;
mr[mipsReg + 32].nReg = nreg;
return (RiscVReg)(F0 + nreg);
}
return (RiscVReg)(F0 + nreg);
}
void RiscVRegCacheFPU::MapInIn(IRReg rd, IRReg rs) {
SpillLockFPR(rd, rs);
MapReg(rd);
MapReg(rs);
ReleaseSpillLockFPR(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);
ReleaseSpillLockFPR(rd, rs);
}
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);
ReleaseSpillLockFPR(rd, rs, rt);
}
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 = (RiscVReg)(F0 + AllocateReg(MIPSLoc::FREG));
ReleaseSpillLockFPR(rd, rs);
return temp;
}
void RiscVRegCacheFPU::Map4DirtyIn(IRReg rdbase, IRReg rsbase, bool avoidLoad) {
for (int i = 0; i < 4; ++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)
ReleaseSpillLockFPR(rdbase + i, rsbase + i);
}
void RiscVRegCacheFPU::Map4DirtyInIn(IRReg rdbase, IRReg rsbase, IRReg rtbase, bool avoidLoad) {
for (int i = 0; i < 4; ++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);
for (int i = 0; i < 4; ++i)
MapReg(rsbase + i);
for (int i = 0; i < 4; ++i)
MapReg(rtbase + i);
for (int i = 0; i < 4; ++i)
ReleaseSpillLockFPR(rdbase + i, rsbase + i, rtbase + i);
}
RiscVReg RiscVRegCacheFPU::Map4DirtyInTemp(IRReg rdbase, IRReg rsbase, bool avoidLoad) {
for (int i = 0; i < 4; ++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 = (RiscVReg)(F0 + AllocateReg(MIPSLoc::FREG));
for (int i = 0; i < 4; ++i)
ReleaseSpillLockFPR(rdbase + i, rsbase + i);
return temp;
}
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);
}
}
void RiscVRegCacheFPU::FlushR(IRReg r) {
_dbg_assert_(IsValidFPR(r));
switch (mr[r + 32].loc) {
case MIPSLoc::FREG:
case MIPSLoc::VREG:
_assert_msg_(mr[r + 32].nReg != INVALID_REG, "FlushR: IR %d had bad RiscVReg", r + 32);
FlushNativeReg(mr[r + 32].nReg);
break;
case MIPSLoc::MEM:
break;
default:
_assert_msg_(false, "Unexpected register location %d", (int)mr[r + 32].loc);
break;
}
}
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) {
FlushNativeReg(i);
}
for (int i = 10; i <= 17; ++i) {
FlushNativeReg(i);
}
for (int i = 28; i <= 31; ++i) {
FlushNativeReg(i);
}
}
void RiscVRegCacheFPU::FlushAll() {
if (!pendingFlush_) {
// Nothing allocated. FPU regs are not nearly as common as GPR.
return;
}
int numRVRegs = 0, baseIndex = 0;
const int *order = GetAllocationOrder(MIPSLoc::FREG, numRVRegs, baseIndex);
for (int i = 0; i < numRVRegs; i++) {
int a = order[i] - baseIndex;
int m = nr[a].mipsReg;
if (nr[a].isDirty) {
_assert_(m != MIPS_REG_INVALID);
emit_->FS(32, (RiscVReg)(F0 + a), CTXREG, GetMipsRegOffset(m));
mr[m].loc = MIPSLoc::MEM;
mr[m].nReg = (int)INVALID_REG;
nr[a].mipsReg = IRREG_INVALID;
nr[a].isDirty = false;
} else {
if (m != IRREG_INVALID) {
mr[m].loc = MIPSLoc::MEM;
mr[m].nReg = (int)INVALID_REG;
}
nr[a].mipsReg = IRREG_INVALID;
}
}
pendingFlush_ = false;
}
void RiscVRegCacheFPU::DiscardR(IRReg r) {
_dbg_assert_(IsValidFPR(r));
switch (mr[r + 32].loc) {
case MIPSLoc::FREG:
_assert_(mr[r + 32].nReg != INVALID_REG);
// Note that we DO NOT write it back here. That's the whole point of Discard.
DiscardNativeReg(mr[r + 32].nReg);
break;
case MIPSLoc::MEM:
// Already there, nothing to do.
break;
default:
_assert_(false);
break;
}
mr[r + 32].spillLockIRIndex = -1;
}
int RiscVRegCacheFPU::GetMipsRegOffset(IRReg r) {
_assert_(IsValidFPR(r - 32));
// These are offsets within the MIPSState structure.
// IR gives us an index that is already 32 after the state index (skipping GPRs.)
return (r) * 4;
}
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
}
}
void RiscVRegCacheFPU::SetupInitialRegs() {
IRNativeRegCacheBase::SetupInitialRegs();
// TODO: Move to a shared cache?
mrInitial_[0].loc = MIPSLoc::MEM;
}