Permalink
Browse files

Merge pull request #10514 from unknownbrackets/irjit

irjit: Add ini option to precompile functions
  • Loading branch information...
hrydgard committed Jan 8, 2018
2 parents a989b4a + 97674b8 commit 62242601ef28afee1eb489e6ca8725dc03cdc142
View
@@ -406,6 +406,7 @@ static ConfigSetting cpuSettings[] = {
ConfigSetting("FastMemoryAccess", &g_Config.bFastMemory, true, true, true),
ReportedConfigSetting("FuncReplacements", &g_Config.bFuncReplacements, true, true, true),
ConfigSetting("HideSlowWarnings", &g_Config.bHideSlowWarnings, false, true, false),
ConfigSetting("PreloadFunctions", &g_Config.bPreloadFunctions, false, true, true),
ReportedConfigSetting("CPUSpeed", &g_Config.iLockedCPUSpeed, 0, true, true),
ConfigSetting(false),
View
@@ -126,6 +126,7 @@ struct Config {
bool bForceLagSync;
bool bFuncReplacements;
bool bHideSlowWarnings;
bool bPreloadFunctions;
bool bSeparateSASThread;
bool bSeparateIOThread;
@@ -712,6 +712,7 @@ void ImportFuncSymbol(const FuncSymbolImport &func, bool reimporting) {
}
WriteSyscall(func.moduleName, func.nid, func.stubAddr);
currentMIPS->InvalidateICache(func.stubAddr, 8);
MIPSAnalyst::PrecompileFunction(func.stubAddr, 8);
return;
}
@@ -730,6 +731,7 @@ void ImportFuncSymbol(const FuncSymbolImport &func, bool reimporting) {
}
WriteFuncStub(func.stubAddr, it->symAddr);
currentMIPS->InvalidateICache(func.stubAddr, 8);
MIPSAnalyst::PrecompileFunction(func.stubAddr, 8);
return;
}
}
@@ -768,6 +770,7 @@ void ExportFuncSymbol(const FuncSymbolExport &func) {
INFO_LOG(LOADER, "Resolving function %s/%08x", func.moduleName, func.nid);
WriteFuncStub(it->stubAddr, func.symAddr);
currentMIPS->InvalidateICache(it->stubAddr, 8);
MIPSAnalyst::PrecompileFunction(it->stubAddr, 8);
}
}
}
@@ -1450,6 +1453,9 @@ static Module *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 loadAdd
// use module_start_func instead of entry_addr if entry_addr is 0
if (module->nm.entry_addr == 0)
module->nm.entry_addr = module->nm.module_start_func;
MIPSAnalyst::PrecompileFunctions();
} else {
module->nm.entry_addr = -1;
}
@@ -26,7 +26,7 @@
#include "Common/CommonTypes.h"
#include "Core/HLE/HLE.h"
#include "Core/HLE/HLETables.h"
#include "Core/MIPS/MIPSInt.h"
#include "Core/MIPS/MIPSAnalyst.h"
#include "Core/MIPS/MIPSCodeUtils.h"
#include "Core/MIPS/MIPS.h"
#include "Core/CoreTiming.h"
@@ -887,6 +887,7 @@ static void __KernelWriteFakeSysCall(u32 nid, u32 *ptr, u32 &pos)
*ptr = pos;
pos += 8;
WriteSyscall("FakeSysCalls", nid, *ptr);
MIPSAnalyst::PrecompileFunction(*ptr, 8);
}
void __KernelThreadingInit()
@@ -295,11 +295,12 @@ void IRFrontend::Comp_Jump(MIPSOpcode op) {
// Might be a stubbed address or something?
if (!Memory::IsValidAddress(targetAddr)) {
if (js.nextExit == 0) {
// If preloading, flush - this block will likely be fixed later.
if (js.preloading)
js.cancel = true;
else
ERROR_LOG_REPORT(JIT, "Jump to invalid address: %08x", targetAddr);
} else {
js.compiling = false;
}
js.compiling = false;
// TODO: Mark this block dirty or something? May be indication it will be changed by imports.
return;
}
@@ -219,8 +219,9 @@ MIPSOpcode IRFrontend::GetOffsetInstruction(int offset) {
return Memory::Read_Instruction(GetCompilerPC() + 4 * offset);
}
void IRFrontend::DoJit(u32 em_address, std::vector<IRInst> &instructions, u32 &mipsBytes) {
void IRFrontend::DoJit(u32 em_address, std::vector<IRInst> &instructions, u32 &mipsBytes, bool preload) {
js.cancel = false;
js.preloading = preload;
js.blockStart = em_address;
js.compilerPC = em_address;
js.lastContinuedPC = 0;
@@ -246,6 +247,11 @@ void IRFrontend::DoJit(u32 em_address, std::vector<IRInst> &instructions, u32 &m
js.numInstructions++;
}
if (js.cancel) {
// Clear the instructions to signal this was not compiled.
ir.Clear();
}
mipsBytes = js.compilerPC - em_address;
IRWriter simplified;
@@ -88,7 +88,7 @@ class IRFrontend : public MIPSFrontendInterface {
void DoState(PointerWrap &p);
bool CheckRounding(u32 blockAddress); // returns true if we need a do-over
void DoJit(u32 em_address, std::vector<IRInst> &instructions, u32 &mipsBytes);
void DoJit(u32 em_address, std::vector<IRInst> &instructions, u32 &mipsBytes, bool preload);
void EatPrefix() override {
js.EatPrefix();
View
@@ -16,6 +16,7 @@
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include "base/logging.h"
#include "ext/xxhash.h"
#include "profiler/profiler.h"
#include "Common/ChunkFile.h"
#include "Common/StringUtils.h"
@@ -66,27 +67,136 @@ void IRJit::InvalidateCacheAt(u32 em_address, int length) {
void IRJit::Compile(u32 em_address) {
PROFILE_THIS_SCOPE("jitc");
int block_num = blocks_.AllocateBlock(em_address);
if ((block_num & ~MIPS_EMUHACK_VALUE_MASK) != 0) {
// Ran out of block numbers - need to reset.
ERROR_LOG(JIT, "Ran out of block numbers, clearing cache");
ClearCache();
block_num = blocks_.AllocateBlock(em_address);
if (g_Config.bPreloadFunctions) {
// Look to see if we've preloaded this block.
int block_num = blocks_.FindPreloadBlock(em_address);
if (block_num != -1) {
IRBlock *b = blocks_.GetBlock(block_num);
// Okay, let's link and finalize the block now.
b->Finalize(block_num);
if (b->IsValid()) {
// Success, we're done.
return;
}
}
}
IRBlock *b = blocks_.GetBlock(block_num);
std::vector<IRInst> instructions;
u32 mipsBytes;
frontend_.DoJit(em_address, instructions, mipsBytes);
b->SetInstructions(instructions);
b->SetOriginalSize(mipsBytes);
// Overwrites the first instruction, and also updates stats.
blocks_.FinalizeBlock(block_num);
if (!CompileBlock(em_address, instructions, mipsBytes, false)) {
// Ran out of block numbers - need to reset.
ERROR_LOG(JIT, "Ran out of block numbers, clearing cache");
ClearCache();
CompileBlock(em_address, instructions, mipsBytes, false);
}
if (frontend_.CheckRounding(em_address)) {
// Our assumptions are all wrong so it's clean-slate time.
ClearCache();
Compile(em_address);
CompileBlock(em_address, instructions, mipsBytes, false);
}
}
bool IRJit::CompileBlock(u32 em_address, std::vector<IRInst> &instructions, u32 &mipsBytes, bool preload) {
frontend_.DoJit(em_address, instructions, mipsBytes, preload);
if (instructions.empty()) {
_dbg_assert_(JIT, preload);
// We return true when preloading so it doesn't abort.
return preload;
}
int block_num = blocks_.AllocateBlock(em_address);
if ((block_num & ~MIPS_EMUHACK_VALUE_MASK) != 0) {
// Out of block numbers. Caller will handle.
return false;
}
IRBlock *b = blocks_.GetBlock(block_num);
b->SetInstructions(instructions);
b->SetOriginalSize(mipsBytes);
if (preload) {
// Hash, then only update page stats, don't link yet.
b->UpdateHash();
blocks_.FinalizeBlock(block_num, true);
} else {
// Overwrites the first instruction, and also updates stats.
// TODO: Should we always hash? Then we can reuse blocks.
blocks_.FinalizeBlock(block_num);
}
return true;
}
void IRJit::CompileFunction(u32 start_address, u32 length) {
PROFILE_THIS_SCOPE("jitc");
// Note: we don't actually write emuhacks yet, so we can validate hashes.
// This way, if the game changes the code afterward, we'll catch even without icache invalidation.
// We may go up and down from branches, so track all block starts done here.
std::set<u32> doneAddresses;
std::vector<u32> pendingAddresses;
pendingAddresses.push_back(start_address);
while (!pendingAddresses.empty()) {
u32 em_address = pendingAddresses.back();
pendingAddresses.pop_back();
// To be safe, also check if a real block is there. This can be a runtime module load.
u32 inst = Memory::ReadUnchecked_U32(em_address);
if (MIPS_IS_RUNBLOCK(inst) || doneAddresses.find(em_address) != doneAddresses.end()) {
// Already compiled this address.
continue;
}
std::vector<IRInst> instructions;
u32 mipsBytes;
if (!CompileBlock(em_address, instructions, mipsBytes, true)) {
// Ran out of block numbers - let's hope there's no more code it needs to run.
// Will flush when actually compiling.
ERROR_LOG(JIT, "Ran out of block numbers while compiling function");
return;
}
doneAddresses.insert(em_address);
for (const IRInst &inst : instructions) {
u32 exit = 0;
switch (inst.op) {
case IROp::ExitToConst:
case IROp::ExitToConstIfEq:
case IROp::ExitToConstIfNeq:
case IROp::ExitToConstIfGtZ:
case IROp::ExitToConstIfGeZ:
case IROp::ExitToConstIfLtZ:
case IROp::ExitToConstIfLeZ:
case IROp::ExitToConstIfFpTrue:
case IROp::ExitToConstIfFpFalse:
exit = inst.constant;
break;
case IROp::ExitToPC:
case IROp::Break:
// Don't add any, we'll do block end anyway (for jal, etc.)
exit = 0;
break;
default:
exit = 0;
break;
}
// Only follow jumps internal to the function.
if (exit != 0 && exit >= start_address && exit < start_address + length) {
// Even if it's a duplicate, we check at loop start.
pendingAddresses.push_back(exit);
}
}
// Also include after the block for jal returns.
if (em_address + mipsBytes < start_address + length) {
pendingAddresses.push_back(em_address + mipsBytes);
}
}
}
@@ -166,8 +276,10 @@ void IRBlockCache::InvalidateICache(u32 address, u32 length) {
}
}
void IRBlockCache::FinalizeBlock(int i) {
blocks_[i].Finalize(i);
void IRBlockCache::FinalizeBlock(int i, bool preload) {
if (!preload) {
blocks_[i].Finalize(i);
}
u32 startAddr, size;
blocks_[i].GetRange(startAddr, size);
@@ -185,6 +297,27 @@ u32 IRBlockCache::AddressToPage(u32 addr) const {
return (addr & 0x3FFFFFFF) >> 10;
}
int IRBlockCache::FindPreloadBlock(u32 em_address) {
u32 page = AddressToPage(em_address);
auto iter = byPage_.find(page);
if (iter == byPage_.end())
return -1;
const std::vector<int> &blocksInPage = iter->second;
for (int i : blocksInPage) {
u32 start, mipsBytes;
blocks_[i].GetRange(start, mipsBytes);
if (start == em_address) {
if (blocks_[i].HashMatches()) {
return i;
}
}
}
return -1;
}
std::vector<u32> IRBlockCache::SaveAndClearEmuHackOps() {
std::vector<u32> result;
result.resize(blocks_.size());
@@ -277,14 +410,18 @@ int IRBlockCache::GetBlockNumberFromStartAddress(u32 em_address, bool realBlocks
return -1;
const std::vector<int> &blocksInPage = iter->second;
int best = -1;
for (int i : blocksInPage) {
uint32_t start, size;
blocks_[i].GetRange(start, size);
if (start == em_address) {
return i;
best = i;
if (blocks_[i].IsValid()) {
return i;
}
}
}
return -1;
return best;
}
bool IRBlock::HasOriginalFirstOp() const {
@@ -301,9 +438,13 @@ bool IRBlock::RestoreOriginalFirstOp(int number) {
}
void IRBlock::Finalize(int number) {
origFirstOpcode_ = Memory::Read_Opcode_JIT(origAddr_);
MIPSOpcode opcode = MIPSOpcode(MIPS_EMUHACK_OPCODE | number);
Memory::Write_Opcode_JIT(origAddr_, opcode);
// Check it wasn't invalidated, in case this is after preload.
// TODO: Allow reusing blocks when the code matches hash_ again, instead.
if (origAddr_) {
origFirstOpcode_ = Memory::Read_Opcode_JIT(origAddr_);
MIPSOpcode opcode = MIPSOpcode(MIPS_EMUHACK_OPCODE | number);
Memory::Write_Opcode_JIT(origAddr_, opcode);
}
}
void IRBlock::Destroy(int number) {
@@ -317,6 +458,24 @@ void IRBlock::Destroy(int number) {
}
}
u64 IRBlock::CalculateHash() const {
if (origAddr_) {
// This is unfortunate. In case of emuhacks, we have to make a copy.
std::vector<u32> buffer;
buffer.resize(origSize_ / 4);
size_t pos = 0;
for (u32 off = 0; off < origSize_; off += 4) {
// Let's actually hash the replacement, if any.
MIPSOpcode instr = Memory::ReadUnchecked_Instruction(origAddr_ + off, false);
buffer[pos++] = instr.encoding;
}
return XXH64(&buffer[0], origSize_, 0x9A5C33B8);
}
return 0;
}
bool IRBlock::OverlapsRange(u32 addr, u32 size) const {
addr &= 0x3FFFFFFF;
u32 origAddr = origAddr_ & 0x3FFFFFFF;
Oops, something went wrong.

0 comments on commit 6224260

Please sign in to comment.