mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-13 14:30:27 +02:00
291 lines
8.7 KiB
C++
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;
|
|
}
|