From 067a033dc0f3d19095a69f0ddf71fc01b372d499 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 24 Jul 2023 20:48:17 -0700 Subject: [PATCH] riscv: Add FPU regcache. --- Core/MIPS/ARM64/Arm64RegCache.cpp | 2 +- Core/MIPS/ARM64/Arm64RegCacheFPU.cpp | 3 +- Core/MIPS/ARM64/Arm64RegCacheFPU.h | 3 - Core/MIPS/RiscV/RiscVJit.cpp | 11 +- Core/MIPS/RiscV/RiscVJit.h | 2 + Core/MIPS/RiscV/RiscVRegCache.cpp | 68 +++-- Core/MIPS/RiscV/RiscVRegCache.h | 11 +- Core/MIPS/RiscV/RiscVRegCacheFPU.cpp | 372 +++++++++++++++++++++++++++ Core/MIPS/RiscV/RiscVRegCacheFPU.h | 85 ++++++ 9 files changed, 516 insertions(+), 41 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64RegCache.cpp b/Core/MIPS/ARM64/Arm64RegCache.cpp index 3bfede4dbb..7dc2ba1ddf 100644 --- a/Core/MIPS/ARM64/Arm64RegCache.cpp +++ b/Core/MIPS/ARM64/Arm64RegCache.cpp @@ -180,7 +180,7 @@ void Arm64RegCache::MapRegTo(ARM64Reg reg, MIPSGPReg mipsReg, int mapFlags) { ar[reg].isDirty = (mapFlags & MAP_DIRTY) ? true : false; if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) { if (mipsReg == MIPS_REG_ZERO) { - // If we get a request to load the zero register, at least we won't spend + // If we get a request to map the zero register, at least we won't spend // time on a memory access... emit_->MOVI2R(reg, 0); diff --git a/Core/MIPS/ARM64/Arm64RegCacheFPU.cpp b/Core/MIPS/ARM64/Arm64RegCacheFPU.cpp index 6a7b7ada56..6771713652 100644 --- a/Core/MIPS/ARM64/Arm64RegCacheFPU.cpp +++ b/Core/MIPS/ARM64/Arm64RegCacheFPU.cpp @@ -319,6 +319,7 @@ void Arm64RegCacheFPU::FlushR(MIPSReg r) { if (mr[r].reg == INVALID_REG) { ERROR_LOG(JIT, "FlushR: MipsReg had bad ArmReg"); } + FlushArmReg((ARM64Reg)(S0 + mr[r].reg)); break; case ML_MEM: @@ -329,8 +330,6 @@ void Arm64RegCacheFPU::FlushR(MIPSReg r) { //BAD break; } - mr[r].loc = ML_MEM; - mr[r].reg = (int)INVALID_REG; } Arm64Gen::ARM64Reg Arm64RegCacheFPU::ARM64RegForFlush(int r) { diff --git a/Core/MIPS/ARM64/Arm64RegCacheFPU.h b/Core/MIPS/ARM64/Arm64RegCacheFPU.h index 657d218611..40b7647448 100644 --- a/Core/MIPS/ARM64/Arm64RegCacheFPU.h +++ b/Core/MIPS/ARM64/Arm64RegCacheFPU.h @@ -17,8 +17,6 @@ #pragma once -#pragma once - #include "Core/MIPS/MIPS.h" #include "Core/MIPS/ARM64/Arm64RegCache.h" #include "Core/MIPS/MIPSVFPUUtils.h" @@ -165,7 +163,6 @@ private: MIPSComp::JitOptions *jo_; int numARMFpuReg_; - int qTime_; enum { // On ARM64, each of the 32 registers are full 128-bit. No sharing of components! diff --git a/Core/MIPS/RiscV/RiscVJit.cpp b/Core/MIPS/RiscV/RiscVJit.cpp index 3fcc91cef3..11e98d9f59 100644 --- a/Core/MIPS/RiscV/RiscVJit.cpp +++ b/Core/MIPS/RiscV/RiscVJit.cpp @@ -26,7 +26,7 @@ namespace MIPSComp { using namespace RiscVGen; using namespace RiscVJitConstants; -RiscVJit::RiscVJit(MIPSState *mipsState) : IRJit(mipsState), gpr(mipsState, &jo) { +RiscVJit::RiscVJit(MIPSState *mipsState) : IRJit(mipsState), gpr(mipsState, &jo), fpr(mipsState, &jo) { // Automatically disable incompatible options. if (((intptr_t)Memory::base & 0x00000000FFFFFFFFUL) != 0) { jo.enablePointerify = false; @@ -40,7 +40,7 @@ RiscVJit::RiscVJit(MIPSState *mipsState) : IRJit(mipsState), gpr(mipsState, &jo) memset(blockStartAddrs_, 0, sizeof(blockStartAddrs_[0]) * MAX_ALLOWED_JIT_BLOCKS); gpr.Init(this); - // TODO: fpr + fpr.Init(this); GenerateFixedCode(jo); } @@ -79,7 +79,7 @@ bool RiscVJit::CompileBlock(u32 em_address, std::vector &instructions, u blockStartAddrs_[block_num] = GetCodePointer(); gpr.Start(); - // TODO: fpr. + fpr.Start(); for (const IRInst &inst : instructions) { CompileIRInst(inst); @@ -87,9 +87,8 @@ bool RiscVJit::CompileBlock(u32 em_address, std::vector &instructions, u if (jo.Disabled(JitDisable::REGALLOC_GPR)) { gpr.FlushAll(); } - // TODO if (jo.Disabled(JitDisable::REGALLOC_FPR)) { - //fpr.FlushAll(); + fpr.FlushAll(); } // Safety check, in case we get a bunch of really large jit ops without a lot of branching. @@ -419,7 +418,7 @@ void RiscVJit::CompIR_Generic(IRInst inst) { void RiscVJit::FlushAll() { gpr.FlushAll(); - // TODO: fpr. + fpr.FlushAll(); } bool RiscVJit::DescribeCodePtr(const u8 *ptr, std::string &name) { diff --git a/Core/MIPS/RiscV/RiscVJit.h b/Core/MIPS/RiscV/RiscVJit.h index 5f32aee999..497bb175c5 100644 --- a/Core/MIPS/RiscV/RiscVJit.h +++ b/Core/MIPS/RiscV/RiscVJit.h @@ -24,6 +24,7 @@ #include "Core/MIPS/JitCommon/JitState.h" #include "Core/MIPS/JitCommon/JitCommon.h" #include "Core/MIPS/RiscV/RiscVRegCache.h" +#include "Core/MIPS/RiscV/RiscVRegCacheFPU.h" namespace MIPSComp { @@ -113,6 +114,7 @@ private: RiscVGen::RiscVReg NormalizeR(IRRegIndex rs, IRRegIndex rd, RiscVGen::RiscVReg tempReg); RiscVRegCache gpr; + RiscVRegCacheFPU fpr; static constexpr int MAX_ALLOWED_JIT_BLOCKS = 262144; diff --git a/Core/MIPS/RiscV/RiscVRegCache.cpp b/Core/MIPS/RiscV/RiscVRegCache.cpp index 6c966dabe7..639c11843a 100644 --- a/Core/MIPS/RiscV/RiscVRegCache.cpp +++ b/Core/MIPS/RiscV/RiscVRegCache.cpp @@ -15,15 +15,15 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#ifndef offsetof +#include +#endif + #include "Common/CPUDetect.h" #include "Core/MIPS/RiscV/RiscVRegCache.h" #include "Core/MIPS/JitCommon/JitState.h" #include "Core/Reporting.h" -#ifndef offsetof -#include "stddef.h" -#endif - using namespace RiscVGen; using namespace RiscVJitConstants; @@ -36,20 +36,14 @@ void RiscVRegCache::Init(RiscVEmitter *emitter) { } void RiscVRegCache::Start() { - for (int i = 0; i < NUM_RVREG; i++) { - ar[i].mipsReg = IRREG_INVALID; - ar[i].isDirty = false; - ar[i].pointerified = false; - ar[i].tempLocked = false; - ar[i].normalized32 = false; - } - for (int i = 0; i < NUM_MIPSREG; i++) { - mr[i].loc = MIPSLoc::MEM; - mr[i].reg = INVALID_REG; - mr[i].imm = -1; - mr[i].spillLock = false; - mr[i].isStatic = false; + if (!initialReady_) { + SetupInitialRegs(); + initialReady_ = true; } + + memcpy(ar, arInitial_, sizeof(ar)); + memcpy(mr, mrInitial_, sizeof(mr)); + int numStatics; const StaticAllocation *statics = GetStaticAllocations(numStatics); for (int i = 0; i < numStatics; i++) { @@ -61,24 +55,41 @@ void RiscVRegCache::Start() { mr[statics[i].mr].isStatic = true; mr[statics[i].mr].spillLock = true; } +} + +void RiscVRegCache::SetupInitialRegs() { + for (int i = 0; i < NUM_RVREG; i++) { + arInitial_[i].mipsReg = IRREG_INVALID; + arInitial_[i].isDirty = false; + arInitial_[i].pointerified = false; + arInitial_[i].tempLocked = false; + arInitial_[i].normalized32 = false; + } + for (int i = 0; i < NUM_MIPSREG; i++) { + mrInitial_[i].loc = MIPSLoc::MEM; + mrInitial_[i].reg = INVALID_REG; + mrInitial_[i].imm = -1; + mrInitial_[i].spillLock = false; + mrInitial_[i].isStatic = false; + } // Treat R_ZERO a bit specially, but it's basically static alloc too. - ar[R_ZERO].mipsReg = MIPS_REG_ZERO; - ar[R_ZERO].normalized32 = true; - mr[MIPS_REG_ZERO].loc = MIPSLoc::RVREG_IMM; - mr[MIPS_REG_ZERO].reg = R_ZERO; - mr[MIPS_REG_ZERO].imm = 0; - mr[MIPS_REG_ZERO].isStatic = true; + arInitial_[R_ZERO].mipsReg = MIPS_REG_ZERO; + arInitial_[R_ZERO].normalized32 = true; + mrInitial_[MIPS_REG_ZERO].loc = MIPSLoc::RVREG_IMM; + mrInitial_[MIPS_REG_ZERO].reg = R_ZERO; + mrInitial_[MIPS_REG_ZERO].imm = 0; + mrInitial_[MIPS_REG_ZERO].isStatic = true; } const RiscVReg *RiscVRegCache::GetMIPSAllocationOrder(int &count) { // X8 and X9 are the most ideal for static alloc because they can be used with compression. // Otherwise we stick to saved regs - might not be necessary. static const RiscVReg allocationOrder[] = { - X7, X8, X9, X12, X13, X14, X5, X6, X15, X16, X17, X18, X19, X20, X21, X22, X23, X28, X29, X30, X31, + X8, X9, X12, X13, X14, X15, X5, X6, X7, X16, X17, X18, X19, X20, X21, X22, X23, X28, X29, X30, X31, }; static const RiscVReg allocationOrderStaticAlloc[] = { - X7, X12, X13, X14, X5, X6, X15, X16, X17, X21, X22, X23, X28, X29, X30, X31, + X12, X13, X14, X15, X5, X6, X7, X16, X17, X21, X22, X23, X28, X29, X30, X31, }; if (jo_->useStaticAlloc) { @@ -432,6 +443,7 @@ RiscVReg RiscVRegCache::GetAndLockTempR() { RiscVReg reg = AllocateReg(); if (reg != INVALID_REG) { ar[reg].tempLocked = true; + pendingUnlock_ = true; } return reg; } @@ -1008,9 +1020,13 @@ void RiscVRegCache::SpillLock(IRRegIndex r1, IRRegIndex r2, IRRegIndex r3, IRReg 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 RiscVRegCache::ReleaseSpillLocksAndDiscardTemps() { + if (!pendingUnlock_) + return; + for (int i = 0; i < NUM_MIPSREG; i++) { if (!mr[i].isStatic) mr[i].spillLock = false; @@ -1018,6 +1034,8 @@ void RiscVRegCache::ReleaseSpillLocksAndDiscardTemps() { for (int i = 0; i < NUM_RVREG; i++) { ar[i].tempLocked = false; } + + pendingUnlock_ = false; } void RiscVRegCache::ReleaseSpillLock(IRRegIndex r1, IRRegIndex r2, IRRegIndex r3, IRRegIndex r4) { diff --git a/Core/MIPS/RiscV/RiscVRegCache.h b/Core/MIPS/RiscV/RiscVRegCache.h index b3a8835152..72d680dc9b 100644 --- a/Core/MIPS/RiscV/RiscVRegCache.h +++ b/Core/MIPS/RiscV/RiscVRegCache.h @@ -68,10 +68,6 @@ enum class MapType { } // namespace RiscVJitConstants -namespace MIPSAnalyst { -struct AnalysisResults; -}; - namespace MIPSComp { struct JitOptions; } @@ -175,6 +171,8 @@ private: bool IsValidReg(IRRegIndex r) const; bool IsValidRegNoZero(IRRegIndex r) const; + void SetupInitialRegs(); + MIPSState *mips_; RiscVGen::RiscVEmitter *emit_ = nullptr; MIPSComp::JitOptions *jo_; @@ -186,4 +184,9 @@ private: RegStatusRiscV ar[NUM_RVREG]{}; RegStatusMIPS mr[NUM_MIPSREG]{}; + + bool initialReady_ = false; + bool pendingUnlock_ = false; + RegStatusRiscV arInitial_[NUM_RVREG]; + RegStatusMIPS mrInitial_[NUM_MIPSREG]; }; diff --git a/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp b/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp index 7287ea10b7..8a4e0f5f30 100644 --- a/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp +++ b/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp @@ -14,3 +14,375 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#ifndef offsetof +#include +#endif + +#include "Common/CPUDetect.h" +#include "Core/MIPS/RiscV/RiscVRegCacheFPU.h" +#include "Core/MIPS/JitCommon/JitState.h" +#include "Core/Reporting.h" + +using namespace RiscVGen; +using namespace RiscVJitConstants; + +using namespace RiscVGen; +using namespace RiscVJitConstants; + +RiscVRegCacheFPU::RiscVRegCacheFPU(MIPSState *mipsState, MIPSComp::JitOptions *jo) + : mips_(mipsState), jo_(jo) {} + +void RiscVRegCacheFPU::Init(RiscVEmitter *emitter) { + emit_ = emitter; +} + +void RiscVRegCacheFPU::Start() { + if (!initialReady_) { + SetupInitialRegs(); + initialReady_ = true; + } + + memcpy(ar, arInitial_, sizeof(ar)); + memcpy(mr, mrInitial_, sizeof(mr)); + pendingFlush_ = false; +} + +void RiscVRegCacheFPU::SetupInitialRegs() { + for (int i = 0; i < NUM_RVFPUREG; i++) { + ar[i].mipsReg = IRREG_INVALID; + ar[i].isDirty = false; + } + for (int i = 0; i < NUM_MIPSFPUREG; i++) { + mr[i].loc = MIPSLoc::MEM; + mr[i].reg = (int)INVALID_REG; + mr[i].spillLock = false; + } +} + +const RiscVReg *RiscVRegCacheFPU::GetMIPSAllocationOrder(int &count) { + // 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[] = { + 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); + return allocationOrder; +} + +bool RiscVRegCacheFPU::IsInRAM(IRRegIndex reg) { + _dbg_assert_(IsValidReg(reg)); + return mr[reg].loc == MIPSLoc::MEM; +} + +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. + // TODO: Use age or something to choose which register to spill? + // TODO: Spill dirty regs first? or opposite? + 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); + 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; + + *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; + + // TODO: Look for clobbering in the IRInst array with index? + + // Not awesome. A used reg. Let's try to avoid spilling. + // TODO: Actually check if we'd be spilling. + if (unusedOnly) { + continue; + } + + return reg; + } + + return INVALID_REG; +} + +void RiscVRegCacheFPU::MapInIn(IRRegIndex rd, IRRegIndex rs) { + SpillLock(rd, rs); + MapReg(rd); + MapReg(rs); + ReleaseSpillLock(rd); + ReleaseSpillLock(rs); +} + +void RiscVRegCacheFPU::MapDirtyIn(IRRegIndex rd, IRRegIndex rs, bool avoidLoad) { + SpillLock(rd, rs); + bool load = !avoidLoad || rd == rs; + MapReg(rd, load ? MIPSMap::DIRTY : MIPSMap::NOINIT); + MapReg(rs); + ReleaseSpillLock(rd); + ReleaseSpillLock(rs); +} + +void RiscVRegCacheFPU::MapDirtyInIn(IRRegIndex rd, IRRegIndex rs, IRRegIndex rt, bool avoidLoad) { + SpillLock(rd, rs, rt); + bool load = !avoidLoad || (rd == rs || rd == rt); + MapReg(rd, load ? MIPSMap::DIRTY : MIPSMap::NOINIT); + MapReg(rt); + MapReg(rs); + ReleaseSpillLock(rd); + ReleaseSpillLock(rs); + ReleaseSpillLock(rt); +} + +void RiscVRegCacheFPU::FlushRiscVReg(RiscVReg r) { + _dbg_assert_(r >= F0 && r <= F31); + int reg = r - F0; + if (ar[reg].mipsReg == IRREG_INVALID) { + // Nothing to do, reg not mapped. + return; + } + 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); +} + +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); + + case MIPSLoc::MEM: + return INVALID_REG; + + default: + _assert_(false); + return INVALID_REG; + } +} + +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(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); + } 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; + + // See MIPSState for these offsets. + int index = r + 32; + + // Allow FPU or VFPU regs here. + if (index >= 32 && index < 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; +} diff --git a/Core/MIPS/RiscV/RiscVRegCacheFPU.h b/Core/MIPS/RiscV/RiscVRegCacheFPU.h index 48b5ab53e0..c861c6599b 100644 --- a/Core/MIPS/RiscV/RiscVRegCacheFPU.h +++ b/Core/MIPS/RiscV/RiscVRegCacheFPU.h @@ -16,3 +16,88 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #pragma once + +#include "Common/RiscVEmitter.h" +#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 { +public: + RiscVRegCacheFPU(MIPSState *mipsState, MIPSComp::JitOptions *jo); + ~RiscVRegCacheFPU() {} + + void Init(RiscVGen::RiscVEmitter *emitter); + // TODO: Maybe pass in IR block and start PC for logging/debugging? + void Start(); + + // 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); + + 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); + void FlushAll(); + void FlushR(IRRegIndex r); + void DiscardR(IRRegIndex r); + + RiscVGen::RiscVReg R(int preg); // Returns a cached register + +private: + const RiscVGen::RiscVReg *GetMIPSAllocationOrder(int &count); + RiscVGen::RiscVReg AllocateReg(); + RiscVGen::RiscVReg FindBestToSpill(bool unusedOnly, bool *clobbered); + RiscVGen::RiscVReg RiscVRegForFlush(IRRegIndex r); + void FlushRiscVReg(RiscVGen::RiscVReg r); + int GetMipsRegOffset(IRRegIndex r); + + bool IsValidReg(IRRegIndex r) const; + + void SetupInitialRegs(); + + MIPSState *mips_; + RiscVGen::RiscVEmitter *emit_ = nullptr; + MIPSComp::JitOptions *jo_; + + 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]; +};