Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

irjit: Add ini option to precompile functions #10514

Merged
merged 4 commits into from
Jan 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions Core/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions Core/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ struct Config {
bool bForceLagSync;
bool bFuncReplacements;
bool bHideSlowWarnings;
bool bPreloadFunctions;

bool bSeparateSASThread;
bool bSeparateIOThread;
Expand Down
6 changes: 6 additions & 0 deletions Core/HLE/sceKernelModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -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;
}
Expand Down
3 changes: 2 additions & 1 deletion Core/HLE/sceKernelThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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()
Expand Down
9 changes: 5 additions & 4 deletions Core/MIPS/IR/IRCompBranch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
8 changes: 7 additions & 1 deletion Core/MIPS/IR/IRFrontend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion Core/MIPS/IR/IRFrontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
199 changes: 179 additions & 20 deletions Core/MIPS/IR/IRJit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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);
}
}
}

Expand Down Expand Up @@ -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);
Expand All @@ -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());
Expand Down Expand Up @@ -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 {
Expand All @@ -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) {
Expand All @@ -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;
Expand Down