diff --git a/Core/MIPS/IR/IRJit.cpp b/Core/MIPS/IR/IRJit.cpp index 4f11fde897..511a5fe755 100644 --- a/Core/MIPS/IR/IRJit.cpp +++ b/Core/MIPS/IR/IRJit.cpp @@ -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,131 @@ 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 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 &instructions, u32 &mipsBytes, bool 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; + } + + frontend_.DoJit(em_address, instructions, mipsBytes); + + 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 doneAddresses; + std::vector 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 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 +271,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 +292,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 &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 IRBlockCache::SaveAndClearEmuHackOps() { std::vector result; result.resize(blocks_.size()); @@ -301,9 +429,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 +449,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 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; diff --git a/Core/MIPS/IR/IRJit.h b/Core/MIPS/IR/IRJit.h index 7e3055c253..beefcf9bcb 100644 --- a/Core/MIPS/IR/IRJit.h +++ b/Core/MIPS/IR/IRJit.h @@ -46,6 +46,7 @@ public: origAddr_ = b.origAddr_; origSize_ = b.origSize_; origFirstOpcode_ = b.origFirstOpcode_; + hash_ = b.hash_; b.instr_ = nullptr; } @@ -66,10 +67,16 @@ public: MIPSOpcode GetOriginalFirstOp() const { return origFirstOpcode_; } bool HasOriginalFirstOp() const; bool RestoreOriginalFirstOp(int number); - bool IsValid() const { return origAddr_ != 0; } + bool IsValid() const { return origAddr_ != 0 && origFirstOpcode_.encoding != 0x68FFFFFF; } void SetOriginalSize(u32 size) { origSize_ = size; } + void UpdateHash() { + hash_ = CalculateHash(); + } + bool HashMatches() const { + return origAddr_ && hash_ == CalculateHash(); + } bool OverlapsRange(u32 addr, u32 size) const; void GetRange(u32 &start, u32 &size) const { @@ -81,11 +88,14 @@ public: void Destroy(int number); private: + u64 CalculateHash() const; + IRInst *instr_; u16 numInstructions_; u32 origAddr_; u32 origSize_; - MIPSOpcode origFirstOpcode_; + u64 hash_ = 0; + MIPSOpcode origFirstOpcode_ = MIPSOpcode(0x68FFFFFF); }; class IRBlockCache : public JitBlockCacheDebugInterface { @@ -93,7 +103,7 @@ public: IRBlockCache() {} void Clear(); void InvalidateICache(u32 address, u32 length); - void FinalizeBlock(int i); + void FinalizeBlock(int i, bool preload = false); int GetNumBlocks() const override { return (int)blocks_.size(); } int AllocateBlock(int emAddr) { blocks_.push_back(IRBlock(emAddr)); @@ -107,6 +117,8 @@ public: } } + int FindPreloadBlock(u32 em_address); + std::vector SaveAndClearEmuHackOps(); void RestoreSavedEmuHackOps(std::vector saved); @@ -133,6 +145,7 @@ public: void RunLoopUntil(u64 globalticks) override; void Compile(u32 em_address) override; // Compiles a block at current MIPS PC + void CompileFunction(u32 start_address, u32 length) override; bool DescribeCodePtr(const u8 *ptr, std::string &name) override; // Not using a regular block cache. @@ -152,6 +165,7 @@ public: void UnlinkBlock(u8 *checkedEntry, u32 originalAddress) override; private: + bool CompileBlock(u32 em_address, std::vector &instructions, u32 &mipsBytes, bool preload); bool ReplaceJalTo(u32 dest); JitOptions jo; diff --git a/Core/MIPS/MIPSAnalyst.cpp b/Core/MIPS/MIPSAnalyst.cpp index 036af24154..98d07490ab 100644 --- a/Core/MIPS/MIPSAnalyst.cpp +++ b/Core/MIPS/MIPSAnalyst.cpp @@ -918,6 +918,8 @@ skip: } std::lock_guard guard(functions_lock); + // TODO: Load from cache file if available instead. + double st = real_time_now(); for (auto iter = functions.begin(), end = functions.end(); iter != end; iter++) { const AnalyzedFunction &f = *iter;