Merge pull request #21265 from hrydgard/delete-proxy-blocks

JIT: Remove the proxy block concept
This commit is contained in:
Henrik Rydgård
2026-02-17 10:11:09 +01:00
committed by GitHub
22 changed files with 20 additions and 614 deletions
+1 -33
View File
@@ -1611,7 +1611,7 @@ static const ReplacementTableEntry entries[] = {
{ "strncpy", &Replace_strncpy, 0, REPFLAG_DISABLED },
{ "strcmp", &Replace_strcmp, 0, REPFLAG_DISABLED },
{ "strncmp", &Replace_strncmp, 0, REPFLAG_DISABLED },
{ "fabsf", &Replace_fabsf, JITFUNC(Replace_fabsf), REPFLAG_ALLOWINLINE | REPFLAG_DISABLED },
{ "fabsf", &Replace_fabsf, JITFUNC(Replace_fabsf), REPFLAG_DISABLED },
{ "dl_write_matrix", &Replace_dl_write_matrix, 0, REPFLAG_DISABLED }, // &MIPSComp::Jit::Replace_dl_write_matrix, REPFLAG_DISABLED },
{ "dl_write_matrix_2", &Replace_dl_write_matrix, 0, REPFLAG_DISABLED },
{ "gta_dl_write_matrix", &Replace_gta_dl_write_matrix, 0, REPFLAG_DISABLED },
@@ -1873,35 +1873,3 @@ bool GetReplacedOpAt(u32 address, u32 *op) {
}
return false;
}
bool CanReplaceJalTo(u32 dest, const ReplacementTableEntry **entry, u32 *funcSize) {
MIPSOpcode op(Memory::Read_Opcode_JIT(dest));
if (!MIPS_IS_REPLACEMENT(op.encoding))
return false;
// Make sure we don't replace if there are any breakpoints inside.
*funcSize = g_symbolMap->GetFunctionSize(dest);
if (*funcSize == SymbolMap::INVALID_ADDRESS) {
if (g_breakpoints.IsAddressBreakPoint(dest)) {
return false;
}
*funcSize = (u32)sizeof(u32);
} else {
if (g_breakpoints.RangeContainsBreakPoint(dest, *funcSize)) {
return false;
}
}
int index = op.encoding & MIPS_EMUHACK_VALUE_MASK;
*entry = GetReplacementFunc(index);
if (!*entry) {
ERROR_LOG(Log::HLE, "ReplaceJalTo: Invalid replacement op %08x at %08x", op.encoding, dest);
return false;
}
if ((*entry)->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT | REPFLAG_DISABLED | REPFLAG_SLICED)) {
// If it's a hook, we can't replace the jal, we have to go inside the func.
return false;
}
return true;
}
-2
View File
@@ -46,7 +46,6 @@
typedef int (* ReplaceFunc)();
enum {
REPFLAG_ALLOWINLINE = 0x01,
// Used to keep things around but disable them.
REPFLAG_DISABLED = 0x02,
// Note that this will re-execute in a function that loops at start.
@@ -77,7 +76,6 @@ void WriteReplaceInstructions(u32 address, u64 hash, int size);
void RestoreReplacedInstruction(u32 address);
void RestoreReplacedInstructions(u32 startAddr, u32 endAddr);
bool GetReplacedOpAt(u32 address, u32 *op);
bool CanReplaceJalTo(u32 dest, const ReplacementTableEntry **entry, u32 *funcSize);
// For savestates. If you call SaveAndClearReplacements(), you must call RestoreSavedReplacements().
std::map<u32, u32> SaveAndClearReplacements();
-69
View File
@@ -98,24 +98,6 @@ void ArmJit::BranchRSRTComp(MIPSOpcode op, CCFlags cc, bool likely)
immBranchTaken = !immBranchNotTaken;
}
if (jo.immBranches && immBranch && js.numInstructions < jo.continueMaxInstructions) {
if (!immBranchTaken) {
// Skip the delay slot if likely, otherwise it'll be the next instruction.
if (likely)
js.compilerPC += 4;
return;
}
// Branch taken. Always compile the delay slot, and then go to dest.
CompileDelaySlot(DELAYSLOT_NICE);
AddContinuedBlock(targetAddr);
// Account for the increment in the loop.
js.compilerPC = targetAddr - 4;
// In case the delay slot was a break or something.
js.compiling = true;
return;
}
js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
u32 notTakenTarget = ResolveNotTakenTarget(branchInfo);
@@ -225,29 +207,6 @@ void ArmJit::BranchRSZeroComp(MIPSOpcode op, CCFlags cc, bool andLink, bool like
immBranchTaken = !immBranchNotTaken;
}
if (jo.immBranches && immBranch && js.numInstructions < jo.continueMaxInstructions) {
if (!immBranchTaken) {
// Skip the delay slot if likely, otherwise it'll be the next instruction.
if (andLink)
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 8);
if (likely)
js.compilerPC += 4;
return;
}
// Branch taken. Always compile the delay slot, and then go to dest.
if (andLink)
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 8);
CompileDelaySlot(DELAYSLOT_NICE);
AddContinuedBlock(targetAddr);
// Account for the increment in the loop.
js.compilerPC = targetAddr - 4;
// In case the delay slot was a break or something.
js.compiling = true;
return;
}
js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
u32 notTakenTarget = ResolveNotTakenTarget(branchInfo);
@@ -530,32 +489,13 @@ void ArmJit::Comp_Jump(MIPSOpcode op) {
switch (op >> 26) {
case 2: //j
CompileDelaySlot(DELAYSLOT_NICE);
if (jo.continueJumps && js.numInstructions < jo.continueMaxInstructions) {
AddContinuedBlock(targetAddr);
// Account for the increment in the loop.
js.compilerPC = targetAddr - 4;
// In case the delay slot was a break or something.
js.compiling = true;
return;
}
FlushAll();
WriteExit(targetAddr, js.nextExit++);
break;
case 3: //jal
if (ReplaceJalTo(targetAddr))
return;
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 8);
CompileDelaySlot(DELAYSLOT_NICE);
if (jo.continueJumps && js.numInstructions < jo.continueMaxInstructions) {
AddContinuedBlock(targetAddr);
// Account for the increment in the loop.
js.compilerPC = targetAddr - 4;
// In case the delay slot was a break or something.
js.compiling = true;
return;
}
FlushAll();
WriteExit(targetAddr, js.nextExit++);
break;
@@ -609,15 +549,6 @@ void ArmJit::Comp_JumpReg(MIPSOpcode op)
gpr.DiscardR(MIPS_REG_T9);
}
if (jo.continueJumps && gpr.IsImm(rs) && js.numInstructions < jo.continueMaxInstructions) {
AddContinuedBlock(gpr.GetImm(rs));
// Account for the increment in the loop.
js.compilerPC = gpr.GetImm(rs) - 4;
// In case the delay slot was a break or something.
js.compiling = true;
return;
}
gpr.MapReg(rs);
destReg = gpr.R(rs); // Safe because FlushAll doesn't change any regs
FlushAll();
+1 -70
View File
@@ -405,24 +405,7 @@ void ArmJit::DoJit(u32 em_address, JitBlock *b) {
// Don't forget to zap the newly written instructions in the instruction cache!
FlushIcache();
if (js.lastContinuedPC == 0)
b->originalSize = js.numInstructions;
else
{
// We continued at least once. Add the last proxy and set the originalSize correctly.
blocks.CreateProxyBlock(js.blockStart, js.lastContinuedPC, (GetCompilerPC() - js.lastContinuedPC) / sizeof(u32), GetCodePtr());
b->originalSize = js.initialBlockSize;
}
}
void ArmJit::AddContinuedBlock(u32 dest)
{
// The first block is the root block. When we continue, we create proxy blocks after that.
if (js.lastContinuedPC == 0)
js.initialBlockSize = js.numInstructions;
else
blocks.CreateProxyBlock(js.blockStart, js.lastContinuedPC, (GetCompilerPC() - js.lastContinuedPC) / sizeof(u32), GetCodePtr());
js.lastContinuedPC = dest;
b->originalSize = js.numInstructions;
}
bool ArmJit::DescribeCodePtr(const u8 *ptr, std::string &name)
@@ -481,58 +464,6 @@ void ArmJit::UnlinkBlock(u8 *checkedEntry, u32 originalAddress) {
}
}
bool ArmJit::ReplaceJalTo(u32 dest) {
const ReplacementTableEntry *entry = nullptr;
u32 funcSize = 0;
if (!CanReplaceJalTo(dest, &entry, &funcSize)) {
return false;
}
// Warning - this might be bad if the code at the destination changes...
if (entry->flags & REPFLAG_ALLOWINLINE) {
// Jackpot! Just do it, no flushing. The code will be entirely inlined.
// First, compile the delay slot. It's unconditional so no issues.
CompileDelaySlot(DELAYSLOT_NICE);
// Technically, we should write the unused return address to RA, but meh.
MIPSReplaceFunc repl = entry->jitReplaceFunc;
int cycles = (this->*repl)();
js.downcountAmount += cycles;
} else {
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 8);
CompileDelaySlot(DELAYSLOT_NICE);
FlushAll();
SaveDowncount();
RestoreRoundingMode();
if (BLInRange((const void *)(entry->replaceFunc))) {
BL((const void *)(entry->replaceFunc));
} else {
MOVI2R(R0, (uintptr_t)entry->replaceFunc);
BL(R0);
}
ApplyRoundingMode();
RestoreDowncount();
WriteDownCountR(R0);
}
js.compilerPC += 4;
// No writing exits, keep going!
if (g_breakpoints.HasMemChecks()) {
// We could modify coreState, so we need to write PC and check.
// Otherwise, PC may end up on the jal. We add 4 to skip the delay slot.
FlushAll();
WriteExit(GetCompilerPC() + 4, js.nextExit++);
js.compiling = false;
}
// Add a trigger so that if the inlined code changes, we invalidate this block.
blocks.CreateProxyBlock(js.blockStart, dest, funcSize / sizeof(u32), GetCodePtr());
return true;
}
void ArmJit::Comp_ReplacementFunc(MIPSOpcode op)
{
// We get here if we execute the first instruction of a replaced function. This means
-3
View File
@@ -202,7 +202,6 @@ private:
u32 GetCompilerPC();
void CompileDelaySlot(int flags);
void EatInstruction(MIPSOpcode op);
void AddContinuedBlock(u32 dest);
MIPSOpcode GetOffsetInstruction(int offset);
void WriteDownCount(int offset = 0);
@@ -213,8 +212,6 @@ private:
void MovFromPC(ArmGen::ARMReg r);
void MovToPC(ArmGen::ARMReg r);
bool ReplaceJalTo(u32 dest);
void SaveDowncount();
void RestoreDowncount();
-69
View File
@@ -98,24 +98,6 @@ void Arm64Jit::BranchRSRTComp(MIPSOpcode op, CCFlags cc, bool likely)
immBranchTaken = !immBranchNotTaken;
}
if (jo.immBranches && immBranch && js.numInstructions < jo.continueMaxInstructions) {
if (!immBranchTaken) {
// Skip the delay slot if likely, otherwise it'll be the next instruction.
if (likely)
js.compilerPC += 4;
return;
}
// Branch taken. Always compile the delay slot, and then go to dest.
CompileDelaySlot(DELAYSLOT_NICE);
AddContinuedBlock(targetAddr);
// Account for the increment in the loop.
js.compilerPC = targetAddr - 4;
// In case the delay slot was a break or something.
js.compiling = true;
return;
}
js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
u32 notTakenTarget = ResolveNotTakenTarget(branchInfo);
@@ -242,29 +224,6 @@ void Arm64Jit::BranchRSZeroComp(MIPSOpcode op, CCFlags cc, bool andLink, bool li
immBranchTaken = !immBranchNotTaken;
}
if (jo.immBranches && immBranch && js.numInstructions < jo.continueMaxInstructions) {
if (!immBranchTaken) {
// Skip the delay slot if likely, otherwise it'll be the next instruction.
if (andLink)
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 8);
if (likely)
js.compilerPC += 4;
return;
}
// Branch taken. Always compile the delay slot, and then go to dest.
if (andLink)
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 8);
CompileDelaySlot(DELAYSLOT_NICE);
AddContinuedBlock(targetAddr);
// Account for the increment in the loop.
js.compilerPC = targetAddr - 4;
// In case the delay slot was a break or something.
js.compiling = true;
return;
}
js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
u32 notTakenTarget = ResolveNotTakenTarget(branchInfo);
@@ -545,32 +504,13 @@ void Arm64Jit::Comp_Jump(MIPSOpcode op) {
switch (op >> 26) {
case 2: //j
CompileDelaySlot(DELAYSLOT_NICE);
if (jo.continueJumps && js.numInstructions < jo.continueMaxInstructions) {
AddContinuedBlock(targetAddr);
// Account for the increment in the loop.
js.compilerPC = targetAddr - 4;
// In case the delay slot was a break or something.
js.compiling = true;
return;
}
FlushAll();
WriteExit(targetAddr, js.nextExit++);
break;
case 3: //jal
if (ReplaceJalTo(targetAddr))
return;
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 8);
CompileDelaySlot(DELAYSLOT_NICE);
if (jo.continueJumps && js.numInstructions < jo.continueMaxInstructions) {
AddContinuedBlock(targetAddr);
// Account for the increment in the loop.
js.compilerPC = targetAddr - 4;
// In case the delay slot was a break or something.
js.compiling = true;
return;
}
FlushAll();
WriteExit(targetAddr, js.nextExit++);
break;
@@ -624,15 +564,6 @@ void Arm64Jit::Comp_JumpReg(MIPSOpcode op)
gpr.DiscardR(MIPS_REG_T9);
}
if (jo.continueJumps && gpr.IsImm(rs) && js.numInstructions < jo.continueMaxInstructions) {
AddContinuedBlock(gpr.GetImm(rs));
// Account for the increment in the loop.
js.compilerPC = gpr.GetImm(rs) - 4;
// In case the delay slot was a break or something.
js.compiling = true;
return;
}
gpr.MapReg(rs);
destReg = gpr.R(rs); // Safe because FlushAll doesn't change any regs
FlushAll();
+1 -62
View File
@@ -394,22 +394,7 @@ void Arm64Jit::DoJit(u32 em_address, JitBlock *b) {
if (dontLogBlocks > 0)
dontLogBlocks--;
if (js.lastContinuedPC == 0) {
b->originalSize = js.numInstructions;
} else {
// We continued at least once. Add the last proxy and set the originalSize correctly.
blocks.CreateProxyBlock(js.blockStart, js.lastContinuedPC, (GetCompilerPC() - js.lastContinuedPC) / sizeof(u32), GetCodePtr());
b->originalSize = js.initialBlockSize;
}
}
void Arm64Jit::AddContinuedBlock(u32 dest) {
// The first block is the root block. When we continue, we create proxy blocks after that.
if (js.lastContinuedPC == 0)
js.initialBlockSize = js.numInstructions;
else
blocks.CreateProxyBlock(js.blockStart, js.lastContinuedPC, (GetCompilerPC() - js.lastContinuedPC) / sizeof(u32), GetCodePtr());
js.lastContinuedPC = dest;
b->originalSize = js.numInstructions;
}
bool Arm64Jit::DescribeCodePtr(const u8 *ptr, std::string &name) {
@@ -493,52 +478,6 @@ void Arm64Jit::UnlinkBlock(u8 *checkedEntry, u32 originalAddress) {
}
}
bool Arm64Jit::ReplaceJalTo(u32 dest) {
#if PPSSPP_ARCH(ARM64)
const ReplacementTableEntry *entry = nullptr;
u32 funcSize = 0;
if (!CanReplaceJalTo(dest, &entry, &funcSize)) {
return false;
}
// Warning - this might be bad if the code at the destination changes...
if (entry->flags & REPFLAG_ALLOWINLINE) {
// Jackpot! Just do it, no flushing. The code will be entirely inlined.
// First, compile the delay slot. It's unconditional so no issues.
CompileDelaySlot(DELAYSLOT_NICE);
// Technically, we should write the unused return address to RA, but meh.
MIPSReplaceFunc repl = entry->jitReplaceFunc;
int cycles = (this->*repl)();
js.downcountAmount += cycles;
} else {
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 8);
CompileDelaySlot(DELAYSLOT_NICE);
FlushAll();
SaveStaticRegisters();
RestoreRoundingMode();
QuickCallFunction(SCRATCH1_64, (const void *)(entry->replaceFunc));
ApplyRoundingMode();
LoadStaticRegisters();
WriteDownCountR(W0); // W0 is the return value from entry->replaceFunc. Neither LoadStaticRegisters nor ApplyRoundingMode can trash it.
}
js.compilerPC += 4;
// No writing exits, keep going!
if (g_breakpoints.HasMemChecks()) {
// We could modify coreState, so we need to write PC and check.
// Otherwise, PC may end up on the jal. We add 4 to skip the delay slot.
FlushAll();
WriteExit(GetCompilerPC() + 4, js.nextExit++);
js.compiling = false;
}
// Add a trigger so that if the inlined code changes, we invalidate this block.
blocks.CreateProxyBlock(js.blockStart, dest, funcSize / sizeof(u32), GetCodePtr());
#endif
return true;
}
void Arm64Jit::Comp_ReplacementFunc(MIPSOpcode op)
{
// We get here if we execute the first instruction of a replaced function. This means
-3
View File
@@ -201,7 +201,6 @@ private:
u32 GetCompilerPC();
void CompileDelaySlot(int flags);
void EatInstruction(MIPSOpcode op);
void AddContinuedBlock(u32 dest);
MIPSOpcode GetOffsetInstruction(int offset);
void WriteDownCount(int offset = 0, bool updateFlags = true);
@@ -212,8 +211,6 @@ private:
void MovFromPC(Arm64Gen::ARM64Reg r);
void MovToPC(Arm64Gen::ARM64Reg r);
bool ReplaceJalTo(u32 dest);
// Clobbers SCRATCH2.
void SaveStaticRegisters();
// Clobbers SCRATCH2.
-1
View File
@@ -243,7 +243,6 @@ void IRFrontend::DoJit(u32 em_address, std::vector<IRInst> &instructions, u32 &m
js.cancel = false;
js.blockStart = em_address;
js.compilerPC = em_address;
js.lastContinuedPC = 0;
js.initialBlockSize = 0;
js.nextExit = 0;
js.downcountAmount = 0;
+1 -1
View File
@@ -511,7 +511,7 @@ void IRBlockCache::ComputeStats(BlockCacheStats &bcStats) const {
bcStats.avgBloat = totalBloat / (double)blocks_.size();
}
int IRBlockCache::GetBlockNumberFromStartAddress(u32 em_address, bool realBlocksOnly) const {
int IRBlockCache::GetBlockNumberFromStartAddress(u32 em_address) const {
u32 page = AddressToPage(em_address);
const auto iter = byPage_.find(page);
+1 -1
View File
@@ -185,7 +185,7 @@ public:
#endif
}
void ComputeStats(BlockCacheStats &bcStats) const override;
int GetBlockNumberFromStartAddress(u32 em_address, bool realBlocksOnly = true) const override;
int GetBlockNumberFromStartAddress(u32 em_address) const override;
bool SupportsProfiling() const override {
#ifdef IR_PROFILING
+2 -2
View File
@@ -719,8 +719,8 @@ int IRNativeBlockCacheDebugInterface::GetNumBlocks() const {
return irBlocks_.GetNumBlocks();
}
int IRNativeBlockCacheDebugInterface::GetBlockNumberFromStartAddress(u32 em_address, bool realBlocksOnly) const {
return irBlocks_.GetBlockNumberFromStartAddress(em_address, realBlocksOnly);
int IRNativeBlockCacheDebugInterface::GetBlockNumberFromStartAddress(u32 em_address) const {
return irBlocks_.GetBlockNumberFromStartAddress(em_address);
}
JitBlockProfileStats IRNativeBlockCacheDebugInterface::GetBlockProfileStats(int blockNum) const {
+1 -1
View File
@@ -163,7 +163,7 @@ public:
IRNativeBlockCacheDebugInterface(const MIPSComp::IRBlockCache &irBlocks);
void Init(const IRNativeBackend *backend);
int GetNumBlocks() const override;
int GetBlockNumberFromStartAddress(u32 em_address, bool realBlocksOnly = true) const override;
int GetBlockNumberFromStartAddress(u32 em_address) const override;
JitBlockDebugInfo GetBlockDebugInfo(int blockNum) const override;
JitBlockMeta GetBlockMeta(int blockNum) const override;
JitBlockProfileStats GetBlockProfileStats(int blockNum) const override;
+8 -107
View File
@@ -77,8 +77,8 @@ JitBlockCache::~JitBlockCache() {
}
bool JitBlockCache::IsFull() const {
// Subtract some amount to safely leave space for some proxy blocks, which we don't check before we allocate (not ideal, but should be enough).
return num_blocks_ >= MAX_NUM_BLOCKS - 512;
// Subtract some amount to safely leave space for some proxy blocks (now obsolete, but let's still have some extra margin).
return num_blocks_ >= MAX_NUM_BLOCKS - 64;
}
void JitBlockCache::Init() {
@@ -101,7 +101,6 @@ void JitBlockCache::Clear() {
for (int i = 0; i < num_blocks_; i++) {
DestroyBlock(i, DestroyType::CLEAR);
}
proxyBlockMap_.clear();
links_to_.clear();
num_blocks_ = 0;
@@ -120,22 +119,6 @@ int JitBlockCache::AllocateBlock(u32 startAddress) {
JitBlock &b = blocks_[num_blocks_];
b.proxyFor = 0;
// If there's an existing pure proxy block at the address, we need to ditch it and create a new one,
// taking over the proxied blocks.
int num = GetBlockNumberFromStartAddress(startAddress, false);
if (num >= 0) {
if (blocks_[num].IsPureProxy()) {
RemoveBlockMap(num);
blocks_[num].invalid = true;
b.proxyFor = new std::vector<u32>();
*b.proxyFor = *blocks_[num].proxyFor;
blocks_[num].proxyFor->clear();
delete blocks_[num].proxyFor;
blocks_[num].proxyFor = 0;
}
}
b.invalid = false;
b.originalAddress = startAddress;
for (int i = 0; i < MAX_JIT_BLOCK_EXITS; ++i) {
@@ -149,43 +132,6 @@ int JitBlockCache::AllocateBlock(u32 startAddress) {
return num_blocks_ - 1;
}
void JitBlockCache::CreateProxyBlock(u32 rootAddress, u32 startAddress, u32 size, const u8 *codePtr) {
_assert_(num_blocks_ < MAX_NUM_BLOCKS);
// If there's an existing block at the startAddress, add rootAddress as a proxy root of that block
// instead of creating a new block.
int num = GetBlockNumberFromStartAddress(startAddress, false);
if (num != -1) {
DEBUG_LOG(Log::HLE, "Adding proxy root %08x to block at %08x", rootAddress, startAddress);
if (!blocks_[num].proxyFor) {
blocks_[num].proxyFor = new std::vector<u32>();
}
blocks_[num].proxyFor->push_back(rootAddress);
}
JitBlock &b = blocks_[num_blocks_];
b.invalid = false;
b.originalAddress = startAddress;
b.originalSize = size;
for (int i = 0; i < MAX_JIT_BLOCK_EXITS; ++i) {
b.exitAddress[i] = INVALID_EXIT;
b.exitPtrs[i] = 0;
b.linkStatus[i] = false;
}
b.exitAddress[0] = rootAddress;
b.blockNum = num_blocks_;
b.proxyFor = new std::vector<u32>();
b.SetPureProxy(); // flag as pure proxy block.
// Make binary searches and stuff work ok
b.normalEntry = codePtr;
b.checkedEntry = codePtr;
proxyBlockMap_.emplace(startAddress, num_blocks_);
AddBlockMap(num_blocks_);
num_blocks_++; //commit the current block
}
void JitBlockCache::AddBlockMap(int block_num) {
const JitBlock &b = blocks_[block_num];
// Convert the logical address to a physical address for the block map
@@ -223,7 +169,7 @@ static void ExpandRange(std::pair<u32, u32> &range, u32 newStart, u32 newEnd) {
void JitBlockCache::FinalizeBlock(int block_num, bool block_link) {
JitBlock &b = blocks_[block_num];
_assert_msg_(Memory::IsValidAddress(b.originalAddress), "FinalizeBlock: Bad originalAddress %08x in block %d (b.num: %d) proxy: %s sz: %d", b.originalAddress, block_num, b.blockNum, b.proxyFor ? "y" : "n", b.codeSize);
_assert_msg_(Memory::IsValidAddress(b.originalAddress), "FinalizeBlock: Bad originalAddress %08x in block %d (b.num: %d) sz: %d", b.originalAddress, block_num, b.blockNum, b.codeSize);
b.originalFirstOpcode = Memory::Read_Opcode_JIT(b.originalAddress);
MIPSOpcode opcode = GetEmuHackOpForBlock(block_num);
@@ -307,22 +253,13 @@ MIPSOpcode JitBlockCache::GetEmuHackOpForBlock(int blockNum) const {
return MIPSOpcode(MIPS_EMUHACK_OPCODE | off);
}
int JitBlockCache::GetBlockNumberFromStartAddress(u32 addr, bool realBlocksOnly) const {
int JitBlockCache::GetBlockNumberFromStartAddress(u32 addr) const {
if (!blocks_ || !Memory::IsValidAddress(addr))
return -1;
MIPSOpcode inst = MIPSOpcode(Memory::Read_U32(addr));
int bl = GetBlockNumberFromEmuHackOp(inst);
if (bl < 0) {
if (!realBlocksOnly) {
// Wasn't an emu hack op, look through proxyBlockMap_.
auto range = proxyBlockMap_.equal_range(addr);
for (auto it = range.first; it != range.second; ++it) {
const int blockIndex = it->second;
if (blocks_[blockIndex].originalAddress == addr && !blocks_[blockIndex].proxyFor && !blocks_[blockIndex].invalid)
return blockIndex;
}
}
return -1;
}
@@ -374,14 +311,10 @@ void JitBlockCache::LinkBlockExits(int i) {
// This block is dead. Don't relink it.
return;
}
if (b.IsPureProxy()) {
// Pure proxies can't link, since they don't have code.
return;
}
for (int e = 0; e < MAX_JIT_BLOCK_EXITS; e++) {
if (b.exitAddress[e] != INVALID_EXIT && !b.linkStatus[e]) {
int destinationBlock = GetBlockNumberFromStartAddress(b.exitAddress[e], true);
int destinationBlock = GetBlockNumberFromStartAddress(b.exitAddress[e]);
if (destinationBlock == -1) {
continue;
}
@@ -477,32 +410,6 @@ void JitBlockCache::DestroyBlock(int block_num, DestroyType type) {
// No point it being in there anymore.
RemoveBlockMap(block_num);
// Pure proxy blocks always point directly to a real block, there should be no chains of
// proxy-only blocks pointing to proxy-only blocks.
// Follow a block proxy chain.
// Destroy the block that transitively has this as a proxy. Likely the root block once inlined
// this block or its 'parent', so now that this block has changed, the root block must be destroyed.
if (b->proxyFor) {
for (size_t i = 0; i < b->proxyFor->size(); i++) {
int proxied_blocknum = GetBlockNumberFromStartAddress((*b->proxyFor)[i], false);
// If it was already cleared, we don't know which to destroy.
if (proxied_blocknum != -1) {
DestroyBlock(proxied_blocknum, type);
}
}
b->proxyFor->clear();
delete b->proxyFor;
b->proxyFor = 0;
}
auto range = proxyBlockMap_.equal_range(b->originalAddress);
for (auto it = range.first; it != range.second; ++it) {
if (it->second == block_num) {
// Found it. Delete and bail.
proxyBlockMap_.erase(it);
break;
}
}
// TODO: Handle the case when there's a proxy block and a regular JIT block at the same location.
// In this case we probably "leak" the proxy block currently (no memory leak but it'll stay enabled).
@@ -513,9 +420,8 @@ void JitBlockCache::DestroyBlock(int block_num, DestroyType type) {
}
b->invalid = true;
if (!b->IsPureProxy()) {
if (Memory::ReadUnchecked_U32(b->originalAddress) == GetEmuHackOpForBlock(block_num).encoding)
Memory::Write_Opcode_JIT(b->originalAddress, b->originalFirstOpcode);
if (Memory::ReadUnchecked_U32(b->originalAddress) == GetEmuHackOpForBlock(block_num).encoding) {
Memory::Write_Opcode_JIT(b->originalAddress, b->originalFirstOpcode);
}
// It's not safe to set normalEntry to 0 here, since we use a binary search
@@ -523,11 +429,6 @@ void JitBlockCache::DestroyBlock(int block_num, DestroyType type) {
UnlinkBlock(block_num);
// Don't change the jit code when invalidating a pure proxy block.
if (b->IsPureProxy()) {
return;
}
if (b->checkedEntry) {
// We can skip this if we're clearing anyway, which cuts down on protect back and forth on WX exclusive.
if (type != DestroyType::CLEAR) {
@@ -579,7 +480,7 @@ void JitBlockCache::InvalidateChangedBlocks() {
// The primary goal of this is to make sure block linking is cleared up.
for (int block_num = 0; block_num < num_blocks_; ++block_num) {
JitBlock &b = blocks_[block_num];
if (b.invalid || b.IsPureProxy())
if (b.invalid)
continue;
bool changed = false;
+3 -18
View File
@@ -76,18 +76,6 @@ struct JitBlock {
bool invalid;
bool linkStatus[MAX_JIT_BLOCK_EXITS];
// By having a pointer, we avoid a constructor/destructor call being generated if unused,
// thus avoiding dog slow performance in debug build.
std::vector<u32> *proxyFor;
bool IsPureProxy() const {
return originalFirstOpcode.encoding == 0x68FF0000;
}
void SetPureProxy() {
// Magic number that won't be a real opcode.
originalFirstOpcode.encoding = 0x68FF0000;
}
};
typedef void (*CompiledCode)();
@@ -114,7 +102,7 @@ struct JitBlockMeta {
class JitBlockCacheDebugInterface {
public:
virtual int GetNumBlocks() const = 0;
virtual int GetBlockNumberFromStartAddress(u32 em_address, bool realBlocksOnly = true) const = 0;
virtual int GetBlockNumberFromStartAddress(u32 em_address) const = 0;
virtual JitBlockDebugInfo GetBlockDebugInfo(int blockNum) const = 0; // Expensive
virtual JitBlockMeta GetBlockMeta(int blockNum) const = 0;
virtual JitBlockProfileStats GetBlockProfileStats(int blockNum) const = 0;
@@ -131,8 +119,6 @@ public:
~JitBlockCache();
int AllocateBlock(u32 em_address);
// When a proxy block is invalidated, the block located at the rootAddress is invalidated too.
void CreateProxyBlock(u32 rootAddress, u32 startAddress, u32 size, const u8 *codePtr);
void FinalizeBlock(int block_num, bool block_link);
void Clear();
@@ -148,7 +134,7 @@ public:
const JitBlock *GetBlock(int no) const { return &blocks_[no]; }
// Fast way to get a block. Only works on the first source-cpu instruction of a block.
int GetBlockNumberFromStartAddress(u32 em_address, bool realBlocksOnly = true) const override;
int GetBlockNumberFromStartAddress(u32 em_address) const override;
// slower, but can get numbers from within blocks, not just the first instruction.
// WARNING! WILL NOT WORK WITH JIT INLINING ENABLED (not yet a feature but will be soon)
@@ -210,7 +196,6 @@ private:
CodeBlockCommon *codeBlock_;
JitBlock *blocks_ = nullptr;
std::unordered_multimap<u32, int> proxyBlockMap_;
int num_blocks_ = 0;
std::unordered_multimap<u32, int> links_to_;
@@ -222,7 +207,7 @@ private:
JITBLOCK_RANGE_RAMTOP = 2,
JITBLOCK_RANGE_COUNT = 3,
};
std::pair<u32, u32> blockMemRanges_[3];
std::pair<u32, u32> blockMemRanges_[JITBLOCK_RANGE_COUNT];
enum {
// Where does this number come from?
-4
View File
@@ -28,7 +28,6 @@ void JitState::Begin(JitBlock *block) {
cancel = false;
blockStart = block->originalAddress;
compilerPC = block->originalAddress;
lastContinuedPC = 0;
initialBlockSize = 0;
nextExit = 0;
downcountAmount = 0;
@@ -65,9 +64,6 @@ void JitState::Begin(JitBlock *block) {
#else
enableBlocklink = !Disabled(JitDisable::BLOCKLINK);
#endif
immBranches = false;
continueJumps = false;
continueMaxInstructions = 300;
useStaticAlloc = false;
enablePointerify = false;
-4
View File
@@ -54,7 +54,6 @@ namespace MIPSComp {
u32 compilerPC;
u32 blockStart;
u32 lastContinuedPC;
u32 initialBlockSize;
int nextExit;
bool cancel;
@@ -240,8 +239,5 @@ namespace MIPSComp {
// Common
bool enableBlocklink;
bool immBranches;
bool continueJumps;
int continueMaxInstructions;
};
}
-4
View File
@@ -136,10 +136,6 @@ void FakeJit::DoJit(u32 em_address, JitBlock *b) {
_assert_(false);
}
void FakeJit::AddContinuedBlock(u32 dest)
{
}
bool FakeJit::DescribeCodePtr(const u8 *ptr, std::string &name)
{
// TODO: Not used by anything yet.
-1
View File
@@ -53,7 +53,6 @@ public:
void CompileDelaySlot(int flags);
void EatInstruction(MIPSOpcode op);
void AddContinuedBlock(u32 dest);
void Comp_RunBlock(MIPSOpcode op) override;
void Comp_ReplacementFunc(MIPSOpcode op) override;
-80
View File
@@ -299,26 +299,6 @@ void Jit::BranchRSRTComp(MIPSOpcode op, Gen::CCFlags cc, bool likely)
immBranchTaken = !immBranchNotTaken;
}
if (jo.immBranches && immBranch && js.numInstructions < jo.continueMaxInstructions)
{
if (!immBranchTaken)
{
// Skip the delay slot if likely, otherwise it'll be the next instruction.
if (likely)
js.compilerPC += 4;
return;
}
// Branch taken. Always compile the delay slot, and then go to dest.
CompileDelaySlot(DELAYSLOT_NICE);
AddContinuedBlock(targetAddr);
// Account for the increment in the loop.
js.compilerPC = targetAddr - 4;
// In case the delay slot was a break or something.
js.compiling = true;
return;
}
js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
u32 notTakenTarget = ResolveNotTakenTarget(branchInfo);
@@ -379,31 +359,6 @@ void Jit::BranchRSZeroComp(MIPSOpcode op, Gen::CCFlags cc, bool andLink, bool li
immBranchTaken = !immBranchNotTaken;
}
if (jo.immBranches && immBranch && js.numInstructions < jo.continueMaxInstructions)
{
if (!immBranchTaken)
{
// Skip the delay slot if likely, otherwise it'll be the next instruction.
if (andLink)
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 8);
if (likely)
js.compilerPC += 4;
return;
}
// Branch taken. Always compile the delay slot, and then go to dest.
if (andLink)
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 8);
CompileDelaySlot(DELAYSLOT_NICE);
AddContinuedBlock(targetAddr);
// Account for the increment in the loop.
js.compilerPC = targetAddr - 4;
// In case the delay slot was a break or something.
js.compiling = true;
return;
}
js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
u32 notTakenTarget = ResolveNotTakenTarget(branchInfo);
@@ -586,40 +541,15 @@ void Jit::Comp_Jump(MIPSOpcode op) {
switch (op >> 26) {
case 2: //j
CompileDelaySlot(DELAYSLOT_NICE);
if (CanContinueJump(targetAddr))
{
AddContinuedBlock(targetAddr);
// Account for the increment in the loop.
js.compilerPC = targetAddr - 4;
// In case the delay slot was a break or something.
js.compiling = true;
return;
}
FlushAll();
CONDITIONAL_LOG_EXIT(targetAddr);
WriteExit(targetAddr, js.nextExit++);
break;
case 3: //jal
// Special case for branches to "replace functions":
if (ReplaceJalTo(targetAddr))
return;
// Check for small function inlining (future)
// Save return address - might be overwritten by delay slot.
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 8);
CompileDelaySlot(DELAYSLOT_NICE);
if (CanContinueJump(targetAddr))
{
AddContinuedBlock(targetAddr);
// Account for the increment in the loop.
js.compilerPC = targetAddr - 4;
// In case the delay slot was a break or something.
js.compiling = true;
return;
}
FlushAll();
CONDITIONAL_LOG_EXIT(targetAddr);
WriteExit(targetAddr, js.nextExit++);
@@ -682,16 +612,6 @@ void Jit::Comp_JumpReg(MIPSOpcode op)
gpr.DiscardRegContentsIfCached(MIPS_REG_T9);
}
if (gpr.IsImm(rs) && CanContinueJump(gpr.GetImm(rs)))
{
AddContinuedBlock(gpr.GetImm(rs));
// Account for the increment in the loop.
js.compilerPC = gpr.GetImm(rs) - 4;
// In case the delay slot was a break or something.
js.compiling = true;
return;
}
if (gpr.R(rs).IsSimpleReg()) {
destReg = gpr.R(rs).GetSimpleReg();
} else {
+1 -61
View File
@@ -429,22 +429,7 @@ void Jit::DoJit(u32 em_address, JitBlock *b) {
NOP();
AlignCode4();
if (js.lastContinuedPC == 0) {
b->originalSize = js.numInstructions;
} else {
// We continued at least once. Add the last proxy and set the originalSize correctly.
blocks.CreateProxyBlock(js.blockStart, js.lastContinuedPC, (GetCompilerPC() - js.lastContinuedPC) / sizeof(u32), GetCodePtr());
b->originalSize = js.initialBlockSize;
}
}
void Jit::AddContinuedBlock(u32 dest) {
// The first block is the root block. When we continue, we create proxy blocks after that.
if (js.lastContinuedPC == 0)
js.initialBlockSize = js.numInstructions;
else
blocks.CreateProxyBlock(js.blockStart, js.lastContinuedPC, (GetCompilerPC() - js.lastContinuedPC) / sizeof(u32), GetCodePtr());
js.lastContinuedPC = dest;
b->originalSize = js.numInstructions;
}
bool Jit::DescribeCodePtr(const u8 *ptr, std::string &name) {
@@ -538,51 +523,6 @@ void Jit::UnlinkBlock(u8 *checkedEntry, u32 originalAddress) {
}
}
bool Jit::ReplaceJalTo(u32 dest) {
const ReplacementTableEntry *entry = nullptr;
u32 funcSize = 0;
if (!CanReplaceJalTo(dest, &entry, &funcSize)) {
return false;
}
// Warning - this might be bad if the code at the destination changes...
if (entry->flags & REPFLAG_ALLOWINLINE) {
// Jackpot! Just do it, no flushing. The code will be entirely inlined.
// First, compile the delay slot. It's unconditional so no issues.
CompileDelaySlot(DELAYSLOT_NICE);
// Technically, we should write the unused return address to RA, but meh.
MIPSReplaceFunc repl = entry->jitReplaceFunc;
int cycles = (this->*repl)();
js.downcountAmount += cycles;
} else {
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 8);
CompileDelaySlot(DELAYSLOT_NICE);
FlushAll();
MOV(32, MIPSSTATE_VAR(pc), Imm32(GetCompilerPC()));
RestoreRoundingMode();
ABI_CallFunction(entry->replaceFunc);
SUB(32, MIPSSTATE_VAR(downcount), R(EAX));
ApplyRoundingMode();
}
js.compilerPC += 4;
// No writing exits, keep going!
if (g_breakpoints.HasMemChecks()) {
// We could modify coreState, so we need to write PC and check.
// Otherwise, PC may end up on the jal. We add 4 to skip the delay slot.
MOV(32, MIPSSTATE_VAR(pc), Imm32(GetCompilerPC() + 4));
js.afterOp |= JitState::AFTER_CORE_STATE;
}
// Add a trigger so that if the inlined code changes, we invalidate this block.
blocks.CreateProxyBlock(js.blockStart, dest, funcSize / sizeof(u32), GetCodePtr());
return true;
}
void Jit::Comp_ReplacementFunc(MIPSOpcode op) {
// We get here if we execute the first instruction of a replaced function. This means
// that we do need to return to RA.
-18
View File
@@ -187,7 +187,6 @@ private:
void FlushAll();
void FlushPrefixV();
void WriteDowncount(int offset = 0);
bool ReplaceJalTo(u32 dest);
u32 GetCompilerPC();
// See CompileDelaySlotFlags for flags.
@@ -196,7 +195,6 @@ private:
CompileDelaySlot(flags, &state);
}
void EatInstruction(MIPSOpcode op);
void AddContinuedBlock(u32 dest);
MIPSOpcode GetOffsetInstruction(int offset);
void WriteExit(u32 destination, int exit_num);
@@ -268,22 +266,6 @@ private:
}
bool PredictTakeBranch(u32 targetAddr, bool likely);
bool CanContinueJump(u32 targetAddr) {
if (!jo.continueJumps || js.numInstructions >= jo.continueMaxInstructions) {
return false;
}
if (!targetAddr) {
return false;
}
return true;
}
bool CanContinueImmBranch(u32 targetAddr) {
if (!jo.immBranches || js.numInstructions >= jo.continueMaxInstructions) {
return false;
}
return true;
}
bool IsAtDispatchFetch(const u8 *codePtr) const override {
return codePtr == dispatcherFetch;
}