riscv: Spill registers more intelligently.

This commit is contained in:
Unknown W. Brackets 2023-07-30 13:29:13 -07:00
parent 020706f545
commit f870271011
18 changed files with 271 additions and 62 deletions

View File

@ -1539,6 +1539,8 @@ set(CoreExtra)
set(CoreExtraLibs)
set(CoreExtra ${CoreExtra}
Core/MIPS/IR/IRAnalysis.cpp
Core/MIPS/IR/IRAnalysis.h
Core/MIPS/IR/IRCompALU.cpp
Core/MIPS/IR/IRCompBranch.cpp
Core/MIPS/IR/IRCompFPU.cpp

View File

@ -580,6 +580,7 @@
<ClCompile Include="KeyMapDefaults.cpp" />
<ClCompile Include="MemFault.cpp" />
<ClCompile Include="MIPS\fake\FakeJit.cpp" />
<ClCompile Include="MIPS\IR\IRAnalysis.cpp" />
<ClCompile Include="MIPS\IR\IRAsm.cpp" />
<ClCompile Include="MIPS\IR\IRCompALU.cpp" />
<ClCompile Include="MIPS\IR\IRCompBranch.cpp" />
@ -1164,6 +1165,7 @@
<ClInclude Include="KeyMapDefaults.h" />
<ClInclude Include="MemFault.h" />
<ClInclude Include="MIPS\fake\FakeJit.h" />
<ClInclude Include="MIPS\IR\IRAnalysis.h" />
<ClInclude Include="MIPS\IR\IRFrontend.h" />
<ClInclude Include="MIPS\IR\IRInst.h" />
<ClInclude Include="MIPS\IR\IRInterpreter.h" />

View File

@ -1237,6 +1237,9 @@
<ClCompile Include="MIPS\RiscV\RiscVCompSystem.cpp">
<Filter>MIPS\RiscV</Filter>
</ClCompile>
<ClCompile Include="MIPS\IR\IRAnalysis.cpp">
<Filter>MIPS\IR</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="ELF\ElfReader.h">
@ -1992,6 +1995,9 @@
<ClInclude Include="MIPS\RiscV\RiscVRegCacheFPU.h">
<Filter>MIPS\RiscV</Filter>
</ClInclude>
<ClInclude Include="MIPS\IR\IRAnalysis.h">
<Filter>MIPS\IR</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="..\LICENSE.TXT" />

135
Core/MIPS/IR/IRAnalysis.cpp Normal file
View File

@ -0,0 +1,135 @@
// Copyright (c) 2016- 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/.
#include "Core/MIPS/IR/IRAnalysis.h"
static bool IRReadsFrom(const IRInst &inst, int reg, char type, bool directly = false) {
const IRMeta *m = GetIRMeta(inst.op);
if (m->types[1] == type && inst.src1 == reg) {
return true;
}
if (m->types[2] == type && inst.src2 == reg) {
return true;
}
if ((m->flags & (IRFLAG_SRC3 | IRFLAG_SRC3DST)) != 0 && m->types[0] == type && inst.src3 == reg) {
return true;
}
if (!directly) {
if (inst.op == IROp::Interpret || inst.op == IROp::CallReplacement || inst.op == IROp::Syscall || inst.op == IROp::Break)
return true;
if (inst.op == IROp::Breakpoint || inst.op == IROp::MemoryCheck)
return true;
}
return false;
}
bool IRReadsFromFPR(const IRInst &inst, int reg, bool directly) {
if (IRReadsFrom(inst, reg, 'F', directly))
return true;
const IRMeta *m = GetIRMeta(inst.op);
// We also need to check V and 2. Indirect reads already checked, don't check again.
if (m->types[1] == 'V' && reg >= inst.src1 && reg < inst.src1 + 4)
return true;
if (m->types[1] == '2' && reg >= inst.src1 && reg < inst.src1 + 2)
return true;
if (m->types[2] == 'V' && reg >= inst.src2 && reg < inst.src2 + 4)
return true;
if (m->types[2] == '2' && reg >= inst.src2 && reg < inst.src2 + 2)
return true;
if ((m->flags & (IRFLAG_SRC3 | IRFLAG_SRC3DST)) != 0) {
if (m->types[0] == 'V' && reg >= inst.src3 && reg <= inst.src3 + 4)
return true;
if (m->types[0] == '2' && reg >= inst.src3 && reg <= inst.src3 + 2)
return true;
}
return false;
}
bool IRReadsFromGPR(const IRInst &inst, int reg, bool directly) {
return IRReadsFrom(inst, reg, 'G', directly);
}
int IRDestGPR(const IRInst &inst) {
const IRMeta *m = GetIRMeta(inst.op);
if ((m->flags & IRFLAG_SRC3) == 0 && m->types[0] == 'G') {
return inst.dest;
}
return -1;
}
bool IRWritesToGPR(const IRInst &inst, int reg) {
return IRDestGPR(inst) == reg;
}
bool IRWritesToFPR(const IRInst &inst, int reg) {
const IRMeta *m = GetIRMeta(inst.op);
// Doesn't write to anything.
if ((m->flags & IRFLAG_SRC3) != 0)
return false;
if (m->types[0] == 'F' && reg == inst.dest)
return true;
if (m->types[0] == 'V' && reg >= inst.dest && reg < inst.dest + 4)
return true;
if (m->types[0] == '2' && reg >= inst.dest && reg < inst.dest + 2)
return true;
return false;
}
IRUsage IRNextGPRUsage(int gpr, const IRSituation &info) {
// Exclude any "special" regs from this logic for now.
if (gpr >= 32)
return IRUsage::UNKNOWN;
int count = std::min(info.numInstructions - info.currentIndex, info.lookaheadCount);
for (int i = 0; i < count; ++i) {
const IRInst inst = info.instructions[info.currentIndex + i];
if (IRReadsFromGPR(inst, gpr))
return IRUsage::READ;
// We say WRITE when the current instruction writes. It's not useful for spilling.
if (IRDestGPR(inst) == gpr)
return i == 0 ? IRUsage::WRITE : IRUsage::CLOBBERED;
}
return IRUsage::UNUSED;
}
IRUsage IRNextFPRUsage(int fpr, const IRSituation &info) {
// Let's only pay attention to standard FP regs and temps.
// See MIPS.h for these offsets.
if (fpr < 0 || (fpr >= 160 && fpr < 192) || fpr >= 208)
return IRUsage::UNKNOWN;
int count = std::min(info.numInstructions - info.currentIndex, info.lookaheadCount);
for (int i = 0; i < count; ++i) {
const IRInst inst = info.instructions[info.currentIndex + i];
if (IRReadsFromFPR(inst, fpr))
return IRUsage::READ;
// We say WRITE when the current instruction writes. It's not useful for spilling.
if (IRWritesToFPR(inst, fpr)) {
return i == 0 ? IRUsage::WRITE : IRUsage::CLOBBERED;
}
}
return IRUsage::UNUSED;
}

44
Core/MIPS/IR/IRAnalysis.h Normal file
View File

@ -0,0 +1,44 @@
// Copyright (c) 2016- 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
#include "Core/MIPS/IR/IRInst.h"
bool IRReadsFromFPR(const IRInst &inst, int reg, bool directly = false);
bool IRReadsFromGPR(const IRInst &inst, int reg, bool directly = false);
bool IRWritesToGPR(const IRInst &inst, int reg);
bool IRWritesToFPR(const IRInst &inst, int reg);
int IRDestGPR(const IRInst &inst);
struct IRSituation {
int lookaheadCount;
int currentIndex;
const IRInst *instructions;
int numInstructions;
};
enum class IRUsage {
UNKNOWN,
UNUSED,
READ,
WRITE,
CLOBBERED,
};
IRUsage IRNextGPRUsage(int gpr, const IRSituation &info);
IRUsage IRNextFPRUsage(int fpr, const IRSituation &info);

View File

@ -1,6 +1,5 @@
#include "Common/CommonFuncs.h"
#include "Core/MIPS/IR/IRInst.h"
#include "Core/MIPS/IR/IRPassSimplify.h"
#include "Core/MIPS/MIPSDebugInterface.h"
// Legend

View File

@ -36,7 +36,6 @@
#include "Core/MIPS/MIPSTables.h"
#include "Core/MIPS/IR/IRRegCache.h"
#include "Core/MIPS/IR/IRJit.h"
#include "Core/MIPS/IR/IRPassSimplify.h"
#include "Core/MIPS/IR/IRInterpreter.h"
#include "Core/MIPS/JitCommon/JitCommon.h"
#include "Core/Reporting.h"

View File

@ -6,6 +6,7 @@
#include "Common/Data/Convert/SmallDataConvert.h"
#include "Common/Log.h"
#include "Core/Config.h"
#include "Core/MIPS/IR/IRAnalysis.h"
#include "Core/MIPS/IR/IRInterpreter.h"
#include "Core/MIPS/IR/IRPassSimplify.h"
#include "Core/MIPS/IR/IRRegCache.h"
@ -803,27 +804,6 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts
return logBlocks;
}
bool IRReadsFromGPR(const IRInst &inst, int reg, bool directly = false) {
const IRMeta *m = GetIRMeta(inst.op);
if (m->types[1] == 'G' && inst.src1 == reg) {
return true;
}
if (m->types[2] == 'G' && inst.src2 == reg) {
return true;
}
if ((m->flags & (IRFLAG_SRC3 | IRFLAG_SRC3DST)) != 0 && m->types[0] == 'G' && inst.src3 == reg) {
return true;
}
if (!directly) {
if (inst.op == IROp::Interpret || inst.op == IROp::CallReplacement || inst.op == IROp::Syscall || inst.op == IROp::Break)
return true;
if (inst.op == IROp::Breakpoint || inst.op == IROp::MemoryCheck)
return true;
}
return false;
}
IRInst IRReplaceSrcGPR(const IRInst &inst, int fromReg, int toReg) {
IRInst newInst = inst;
const IRMeta *m = GetIRMeta(inst.op);
@ -840,15 +820,6 @@ IRInst IRReplaceSrcGPR(const IRInst &inst, int fromReg, int toReg) {
return newInst;
}
int IRDestGPR(const IRInst &inst) {
const IRMeta *m = GetIRMeta(inst.op);
if ((m->flags & IRFLAG_SRC3) == 0 && m->types[0] == 'G') {
return inst.dest;
}
return -1;
}
IRInst IRReplaceDestGPR(const IRInst &inst, int fromReg, int toReg) {
IRInst newInst = inst;
const IRMeta *m = GetIRMeta(inst.op);

View File

@ -813,7 +813,7 @@ namespace MIPSAnalyst {
break;
}
if (reg > 32) {
if (reg >= 32) {
return USAGE_UNKNOWN;
}

View File

@ -111,19 +111,20 @@ bool RiscVJit::CompileTargetBlock(IRBlock *block, int block_num, bool preload) {
// TODO: Block linking, checked entries and such.
gpr.Start();
fpr.Start();
gpr.Start(block);
fpr.Start(block);
for (int i = 0; i < block->GetNumInstructions(); ++i) {
const IRInst &inst = block->GetInstructions()[i];
gpr.SetIRIndex(i);
fpr.SetIRIndex(i);
CompileIRInst(inst);
if (jo.Disabled(JitDisable::REGALLOC_GPR)) {
if (jo.Disabled(JitDisable::REGALLOC_GPR))
gpr.FlushAll();
}
if (jo.Disabled(JitDisable::REGALLOC_FPR)) {
if (jo.Disabled(JitDisable::REGALLOC_FPR))
fpr.FlushAll();
}
// Safety check, in case we get a bunch of really large jit ops without a lot of branching.
if (GetSpaceLeft() < 0x800) {

View File

@ -21,6 +21,7 @@
#include "Common/CPUDetect.h"
#include "Core/MIPS/IR/IRInst.h"
#include "Core/MIPS/IR/IRAnalysis.h"
#include "Core/MIPS/RiscV/RiscVRegCache.h"
#include "Core/MIPS/JitCommon/JitState.h"
#include "Core/Reporting.h"
@ -36,7 +37,7 @@ void RiscVRegCache::Init(RiscVEmitter *emitter) {
emit_ = emitter;
}
void RiscVRegCache::Start() {
void RiscVRegCache::Start(MIPSComp::IRBlock *irBlock) {
if (!initialReady_) {
SetupInitialRegs();
initialReady_ = true;
@ -56,6 +57,9 @@ void RiscVRegCache::Start() {
mr[statics[i].mr].isStatic = true;
mr[statics[i].mr].spillLock = true;
}
irBlock_ = irBlock;
irIndex_ = 0;
}
void RiscVRegCache::SetupInitialRegs() {
@ -362,8 +366,6 @@ allocate:
}
// 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) {
@ -392,6 +394,12 @@ RiscVReg RiscVRegCache::FindBestToSpill(bool unusedOnly, bool *clobbered) {
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];
@ -401,16 +409,24 @@ RiscVReg RiscVRegCache::FindBestToSpill(bool unusedOnly, bool *clobbered) {
continue;
// As it's in alloc-order, we know it's not static so we don't need to check for that.
IRUsage usage = IRNextGPRUsage(ar[reg].mipsReg, info);
// 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;
// Awesome, a clobbered reg. Let's use it.
if (usage == IRUsage::CLOBBERED) {
// TODO: Check HI/LO clobber together if we combine.
bool canClobber = true;
if (canClobber) {
*clobbered = true;
return reg;
}
}
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;

View File

@ -19,6 +19,7 @@
#include "Common/RiscVEmitter.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/IR/IRJit.h"
namespace RiscVJitConstants {
@ -101,8 +102,10 @@ public:
~RiscVRegCache() {}
void Init(RiscVGen::RiscVEmitter *emitter);
// TODO: Maybe pass in IR block and start PC for logging/debugging?
void Start();
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.
@ -176,6 +179,8 @@ private:
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.

View File

@ -20,6 +20,8 @@
#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"
@ -34,7 +36,7 @@ void RiscVRegCacheFPU::Init(RiscVEmitter *emitter) {
emit_ = emitter;
}
void RiscVRegCacheFPU::Start() {
void RiscVRegCacheFPU::Start(MIPSComp::IRBlock *irBlock) {
if (!initialReady_) {
SetupInitialRegs();
initialReady_ = true;
@ -43,6 +45,9 @@ void RiscVRegCacheFPU::Start() {
memcpy(ar, arInitial_, sizeof(ar));
memcpy(mr, mrInitial_, sizeof(mr));
pendingFlush_ = false;
irBlock_ = irBlock;
irIndex_ = 0;
}
void RiscVRegCacheFPU::SetupInitialRegs() {
@ -130,8 +135,6 @@ allocate:
}
// 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) {
@ -160,21 +163,33 @@ RiscVReg RiscVRegCacheFPU::FindBestToSpill(bool unusedOnly, bool *clobbered) {
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;
// TODO: Look for clobbering in the IRInst array with index?
// 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);
// Not awesome. A used reg. Let's try to avoid spilling.
// TODO: Actually check if we'd be spilling.
if (unusedOnly) {
continue;
// Awesome, a clobbered reg. Let's use it.
if (usage == IRUsage::CLOBBERED) {
*clobbered = true;
return reg;
}
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;

View File

@ -46,8 +46,10 @@ public:
~RiscVRegCacheFPU() {}
void Init(RiscVGen::RiscVEmitter *emitter);
// TODO: Maybe pass in IR block and start PC for logging/debugging?
void Start();
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.
@ -89,6 +91,8 @@ private:
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!

View File

@ -302,6 +302,7 @@
<ClInclude Include="..\..\Core\MIPS\IR\IRInst.h" />
<ClInclude Include="..\..\Core\MIPS\IR\IRInterpreter.h" />
<ClInclude Include="..\..\Core\MIPS\IR\IRJit.h" />
<ClInclude Include="..\..\Core\MIPS\IR\IRAnalysis.h" />
<ClInclude Include="..\..\Core\MIPS\IR\IRPassSimplify.h" />
<ClInclude Include="..\..\Core\MIPS\IR\IRRegCache.h" />
<ClInclude Include="..\..\Core\MIPS\JitCommon\JitBlockCache.h" />
@ -561,6 +562,7 @@
<ClCompile Include="..\..\Core\MIPS\IR\IRInst.cpp" />
<ClCompile Include="..\..\Core\MIPS\IR\IRInterpreter.cpp" />
<ClCompile Include="..\..\Core\MIPS\IR\IRJit.cpp" />
<ClCompile Include="..\..\Core\MIPS\IR\IRAnalysis.cpp" />
<ClCompile Include="..\..\Core\MIPS\IR\IRPassSimplify.cpp" />
<ClCompile Include="..\..\Core\MIPS\IR\IRRegCache.cpp" />
<ClCompile Include="..\..\Core\MIPS\JitCommon\JitBlockCache.cpp" />

View File

@ -645,6 +645,9 @@
<ClCompile Include="..\..\Core\MIPS\IR\IRJit.cpp">
<Filter>MIPS\IR</Filter>
</ClCompile>
<ClCompile Include="..\..\Core\MIPS\IR\IRAnalysis.cpp">
<Filter>MIPS\IR</Filter>
</ClCompile>
<ClCompile Include="..\..\Core\MIPS\IR\IRPassSimplify.cpp">
<Filter>MIPS\IR</Filter>
</ClCompile>
@ -1667,6 +1670,9 @@
<ClInclude Include="..\..\Core\MIPS\IR\IRJit.h">
<Filter>MIPS\IR</Filter>
</ClInclude>
<ClInclude Include="..\..\Core\MIPS\IR\IRAnalysis.h">
<Filter>MIPS\IR</Filter>
</ClInclude>
<ClInclude Include="..\..\Core\MIPS\IR\IRPassSimplify.h">
<Filter>MIPS\IR</Filter>
</ClInclude>

View File

@ -390,6 +390,7 @@ EXEC_AND_LIB_FILES := \
$(SRC)/Core/MIPS/MIPSVFPUFallbacks.cpp.arm \
$(SRC)/Core/MIPS/MIPSCodeUtils.cpp.arm \
$(SRC)/Core/MIPS/MIPSDebugInterface.cpp \
$(SRC)/Core/MIPS/IR/IRAnalysis.cpp \
$(SRC)/Core/MIPS/IR/IRFrontend.cpp \
$(SRC)/Core/MIPS/IR/IRJit.cpp \
$(SRC)/Core/MIPS/IR/IRCompALU.cpp \

View File

@ -671,6 +671,7 @@ SOURCES_CXX += \
$(COREDIR)/MIPS/JitCommon/JitCommon.cpp \
$(COREDIR)/MIPS/JitCommon/JitState.cpp \
$(COREDIR)/MIPS/JitCommon/JitBlockCache.cpp \
$(COREDIR)/MIPS/IR/IRAnalysis.cpp \
$(COREDIR)/MIPS/IR/IRCompALU.cpp \
$(COREDIR)/MIPS/IR/IRCompBranch.cpp \
$(COREDIR)/MIPS/IR/IRCompFPU.cpp \