mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 15:00:30 +02:00
The JITs used to find a block by overwriting its first instruction with an "emuhack" holding the offset into the code space, and replacement functions installed their own emuhack the same way. Anything that read code (savestates, the debugger, memory dumps, cheats, the function hasher, games reading their own code) had to translate them back. Blocks now live in a block shadow: one u32 per guest instruction, indexed by the PC masked with BLOCK_SHADOW_MASK (1GB on 64-bit, folding the kernel and uncached mirrors; 256MB on 32-bit, iOS and Switch, where address space is short), with the low bits cleared so a misaligned PC can't read across two entries. Only the ranges under memory views are committed, so a bad PC still faults in the dispatcher, and the shadow is reserved before the memory map so it can't land in a gap of the 4GB window. Entries hold full 32-bit offsets, lifting the 16MB limit. The dispatchers on every backend, and the IR interpreter's loop, look blocks up there. A hooked instruction without a compiled block gets the JIT's hook value in its entry: the offset of the dispatcher's compile path (1 for the interpreters, the IR arena keeping that offset empty), so dispatching there compiles a block, and the frontends substitute the hook. Compiling replaces the entry with the block's, and destroying the block puts the hook value back. The hook value is rewritten when the JIT changes or regenerates its dispatcher. Hooks live in a map keyed by the masked address, and are dropped when the game overwrites the hooked instruction, or when an icache invalidation finds the function's hash (immediates masked, as when matching it) changed. Those checks run when queued invalidations are processed, since cheats and the debugger queue them before writing. Installing and removing hooks invalidates deferred, as it happens in HLE calls that can run inside a block. The cache instruction goes through MIPSState, so it drops changed hooks too. With clean memory, the save/clear/restore around savestates, memory reads and dumps goes away, as do the source-range invalidations in the memcpy replacements, sceDmac and the cheats. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
541 lines
17 KiB
C++
541 lines
17 KiB
C++
// Copyright (c) 2012- PPSSPP Project / Dolphin 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 "ppsspp_config.h"
|
|
#include <cstddef>
|
|
#include <algorithm>
|
|
|
|
#include "ext/xxhash.h"
|
|
#include "Common/CommonTypes.h"
|
|
#include "Common/Profiler/Profiler.h"
|
|
|
|
#ifdef _WIN32
|
|
#include "Common/CommonWindows.h"
|
|
#endif
|
|
|
|
#include "Core/Core.h"
|
|
#include "Core/MemMap.h"
|
|
#include "Core/CoreTiming.h"
|
|
#include "Core/Reporting.h"
|
|
#include "Core/Config.h"
|
|
|
|
#include "Core/MIPS/MIPS.h"
|
|
#include "Core/MIPS/MIPSTables.h"
|
|
#include "Core/MIPS/MIPSAnalyst.h"
|
|
|
|
#include "Core/MIPS/JitCommon/JitBlockCache.h"
|
|
#include "Core/MIPS/JitCommon/JitCommon.h"
|
|
|
|
constexpr u32 INVALID_EXIT = 0xFFFFFFFF;
|
|
constexpr u32 SENTINEL_VAL = 0xc0ffeefe;
|
|
|
|
static uint64_t HashJitBlock(const JitBlock &b) {
|
|
PROFILE_THIS_SCOPE("jithash");
|
|
if (JIT_USE_COMPILEDHASH) {
|
|
if (Memory::IsValidRange(b.originalAddress, b.originalSize * 4)) {
|
|
return XXH3_64bits(Memory::GetPointerUnchecked(b.originalAddress), b.originalSize * 4);
|
|
} else {
|
|
// Hm, this would be bad.
|
|
return 0;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool JitBlock::ContainsAddress(u32 address) const {
|
|
// WARNING - THIS DOES NOT WORK WITH JIT INLINING ENABLED.
|
|
// However, that doesn't exist yet so meh.
|
|
return address >= originalAddress && address < originalAddress + 4 * originalSize;
|
|
}
|
|
|
|
void JitBlock::DoIntegrityCheck(u32 emAddress, int num, const char *reason) const {
|
|
_assert_msg_(emAddress == originalAddress, "%s: Bad orig %08x (expected: %08x, snt: %08x) in block %d (b.num: %d) sz: %d", reason, originalAddress, emAddress, sentinel, num, blockNum, codeSize);
|
|
// Also alert just if the sentinel got corrupted.
|
|
_assert_msg_(sentinel == SENTINEL_VAL, "%s: Block %d(%d) sentinel got corrupted: %08x != %08x (origAddr: %08x)", reason, num, blockNum, sentinel, SENTINEL_VAL, originalAddress);
|
|
}
|
|
|
|
JitBlockCache::JitBlockCache(MIPSState *mipsState, CodeBlockCommon *codeBlock) : codeBlock_(codeBlock) {}
|
|
|
|
JitBlockCache::~JitBlockCache() {
|
|
Shutdown();
|
|
}
|
|
|
|
bool JitBlockCache::IsFull() const {
|
|
// 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() {
|
|
blocks_ = new JitBlock[MAX_NUM_BLOCKS];
|
|
Clear();
|
|
}
|
|
|
|
void JitBlockCache::Shutdown() {
|
|
Clear(); // Make sure proxy block links are deleted
|
|
delete [] blocks_;
|
|
blocks_ = 0;
|
|
num_blocks_ = 0;
|
|
}
|
|
|
|
// This clears the JIT cache. It's called from JitCache.cpp when the JIT cache
|
|
// is full and when saving and loading states.
|
|
void JitBlockCache::Clear() {
|
|
// Note: We intentionally clear the block_map_ first to avoid O(N^2) behavior in RemoveBlockMap
|
|
block_map_.clear();
|
|
for (int i = 0; i < num_blocks_; i++) {
|
|
DestroyBlock(i, DestroyType::CLEAR);
|
|
}
|
|
links_to_.clear();
|
|
num_blocks_ = 0;
|
|
|
|
blockMemRanges_[JITBLOCK_RANGE_SCRATCH] = std::make_pair(0xFFFFFFFF, 0x00000000);
|
|
blockMemRanges_[JITBLOCK_RANGE_RAMBOTTOM] = std::make_pair(0xFFFFFFFF, 0x00000000);
|
|
blockMemRanges_[JITBLOCK_RANGE_RAMTOP] = std::make_pair(0xFFFFFFFF, 0x00000000);
|
|
}
|
|
|
|
void JitBlockCache::Reset() {
|
|
Shutdown();
|
|
Init();
|
|
}
|
|
|
|
int JitBlockCache::AllocateBlock(u32 startAddress) {
|
|
_assert_(num_blocks_ < MAX_NUM_BLOCKS);
|
|
|
|
const int numBlocks = num_blocks_;
|
|
|
|
JitBlock &b = blocks_[numBlocks];
|
|
|
|
b.invalid = false;
|
|
b.originalAddress = startAddress;
|
|
for (int i = 0; i < MAX_JIT_BLOCK_EXITS; ++i) {
|
|
b.exitAddress[i] = INVALID_EXIT;
|
|
b.exitPtrs[i] = 0;
|
|
b.linkStatus[i] = false;
|
|
}
|
|
b.blockNum = numBlocks;
|
|
b.sentinel = SENTINEL_VAL;
|
|
|
|
num_blocks_ = numBlocks + 1; //commit the current block
|
|
return numBlocks;
|
|
}
|
|
|
|
void JitBlockCache::AddBlockMap(int block_num) {
|
|
const JitBlock &b = blocks_[block_num];
|
|
// Convert the logical address to a physical address for the block map
|
|
u32 pAddr = b.originalAddress & 0x1FFFFFFF;
|
|
block_map_[std::make_pair(pAddr + 4 * b.originalSize, pAddr)] = block_num;
|
|
}
|
|
|
|
void JitBlockCache::RemoveBlockMap(int block_num) {
|
|
const JitBlock &b = blocks_[block_num];
|
|
if (b.invalid) {
|
|
return;
|
|
}
|
|
|
|
const u32 pAddr = b.originalAddress & 0x1FFFFFFF;
|
|
auto it = block_map_.find(std::make_pair(pAddr + 4 * b.originalSize, pAddr));
|
|
if (it != block_map_.end() && it->second == (u32)block_num) {
|
|
block_map_.erase(it);
|
|
} else {
|
|
// It wasn't in there, or it has the wrong key. Let's search...
|
|
// TODO: This is O(n), so O(n^2) when called for every block.
|
|
for (auto it = block_map_.begin(); it != block_map_.end(); ++it) {
|
|
if (it->second == (u32)block_num) {
|
|
_dbg_assert_(false);
|
|
block_map_.erase(it);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ExpandRange(std::pair<u32, u32> &range, u32 newStart, u32 newEnd) {
|
|
range.first = std::min(range.first, newStart);
|
|
range.second = std::max(range.second, newEnd);
|
|
}
|
|
|
|
void JitBlockCache::FinalizeBlock(int block_num, bool block_link) {
|
|
JitBlock &b = blocks_[block_num];
|
|
|
|
b.originalFirstOpcode = Memory::ReadUnchecked_Instruction(b.originalAddress);
|
|
Memory::WriteBlockShadow(b.originalAddress, GetShadowValueForBlock(block_num));
|
|
b.compiledHash = HashJitBlock(b);
|
|
|
|
AddBlockMap(block_num);
|
|
|
|
if (block_link) {
|
|
for (int i = 0; i < MAX_JIT_BLOCK_EXITS; i++) {
|
|
if (b.exitAddress[i] != INVALID_EXIT) {
|
|
links_to_.emplace(b.exitAddress[i], block_num);
|
|
}
|
|
}
|
|
|
|
LinkBlock(block_num);
|
|
LinkBlockExits(block_num);
|
|
}
|
|
|
|
const u32 blockEnd = b.originalAddress + b.originalSize * 4 - 4;
|
|
if (Memory::IsScratchpadAddress(b.originalAddress)) {
|
|
ExpandRange(blockMemRanges_[JITBLOCK_RANGE_SCRATCH], b.originalAddress, blockEnd);
|
|
}
|
|
const u32 halfUserMemory = (PSP_GetUserMemoryEnd() - PSP_GetUserMemoryBase()) / 2;
|
|
if (b.originalAddress < PSP_GetUserMemoryBase() + halfUserMemory) {
|
|
ExpandRange(blockMemRanges_[JITBLOCK_RANGE_RAMBOTTOM], b.originalAddress, blockEnd);
|
|
}
|
|
// >=, not >: the bottom check above is strict, so a block sitting exactly on the midpoint
|
|
// would otherwise land in neither range.
|
|
if (blockEnd >= PSP_GetUserMemoryBase() + halfUserMemory) {
|
|
ExpandRange(blockMemRanges_[JITBLOCK_RANGE_RAMTOP], b.originalAddress, blockEnd);
|
|
}
|
|
}
|
|
|
|
bool JitBlockCache::RangeMayHaveBlocks(u32 start, u32 end) const {
|
|
for (int i = 0; i < JITBLOCK_RANGE_COUNT; ++i) {
|
|
if (end >= blockMemRanges_[i].first && start <= blockMemRanges_[i].second) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static int binary_search(const JitBlock blocks_[], const u8 *baseoff, int imin, int imax) {
|
|
while (imin < imax) {
|
|
int imid = (imin + imax) >> 1;
|
|
if (blocks_[imid].normalEntry < baseoff)
|
|
imin = imid + 1;
|
|
else
|
|
imax = imid;
|
|
}
|
|
if ((imax == imin) && (blocks_[imin].normalEntry == baseoff))
|
|
return imin;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
int JitBlockCache::GetBlockNumberFromShadowValue(u32 value) const {
|
|
if (!num_blocks_ || value == 0)
|
|
return -1;
|
|
|
|
const u8 *baseoff = codeBlock_->GetBasePtr() + value;
|
|
if (baseoff >= codeBlock_->GetCodePtr()) {
|
|
ERROR_LOG(Log::JIT, "JitBlockCache: Invalid block shadow value %08x", value);
|
|
return -1;
|
|
}
|
|
|
|
int bl = binary_search(blocks_, baseoff, 0, num_blocks_ - 1);
|
|
if (bl >= 0 && blocks_[bl].invalid) {
|
|
return -1;
|
|
} else {
|
|
return bl;
|
|
}
|
|
}
|
|
|
|
u32 JitBlockCache::GetShadowValueForBlock(int blockNum) const {
|
|
// Never 0, the dispatcher is at the start of the code space.
|
|
const u32 off = (u32)(blocks_[blockNum].normalEntry - codeBlock_->GetBasePtr());
|
|
_dbg_assert_(off != 0);
|
|
return off;
|
|
}
|
|
|
|
int JitBlockCache::GetBlockNumberFromStartAddress(u32 addr) const {
|
|
if (!blocks_ || !Memory::IsValid4AlignedAddress(addr))
|
|
return -1;
|
|
|
|
// A replacement hook's value is the dispatcher's compile path, which isn't any block's entry.
|
|
int bl = GetBlockNumberFromShadowValue(Memory::ReadBlockShadow(addr));
|
|
if (bl < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (blocks_[bl].originalAddress != addr)
|
|
return -1;
|
|
|
|
return bl;
|
|
}
|
|
|
|
void JitBlockCache::GetBlockNumbersFromAddress(u32 em_address, std::vector<int> *block_numbers) const {
|
|
for (int i = 0; i < num_blocks_; i++)
|
|
if (blocks_[i].ContainsAddress(em_address))
|
|
block_numbers->push_back(i);
|
|
}
|
|
|
|
int JitBlockCache::GetBlockNumberFromAddress(u32 em_address) const {
|
|
for (int i = 0; i < num_blocks_; i++) {
|
|
if (blocks_[i].ContainsAddress(em_address))
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
u32 JitBlockCache::GetAddressFromBlockPtr(const u8 *ptr) const {
|
|
if (!codeBlock_->IsInSpace(ptr))
|
|
return (u32)-1;
|
|
|
|
for (int i = 0; i < num_blocks_; ++i) {
|
|
const auto &b = blocks_[i];
|
|
if (!b.invalid && ptr >= b.checkedEntry && ptr < b.normalEntry + b.codeSize) {
|
|
return b.originalAddress;
|
|
}
|
|
}
|
|
|
|
// It's in jit somewhere, but we must have deleted it.
|
|
return 0;
|
|
}
|
|
|
|
MIPSOpcode JitBlockCache::GetOriginalFirstOp(int block_num) const {
|
|
if (block_num >= num_blocks_ || block_num < 0) {
|
|
return MIPSOpcode(block_num);
|
|
}
|
|
return blocks_[block_num].originalFirstOpcode;
|
|
}
|
|
|
|
void JitBlockCache::LinkBlockExits(int i) {
|
|
JitBlock &b = blocks_[i];
|
|
if (b.invalid) {
|
|
// This block is dead. Don't relink it.
|
|
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]);
|
|
if (destinationBlock == -1) {
|
|
continue;
|
|
}
|
|
|
|
JitBlock &eb = blocks_[destinationBlock];
|
|
// Make sure the destination is not invalid.
|
|
if (!eb.invalid) {
|
|
MIPSComp::jit->LinkBlock(b.exitPtrs[e], eb.checkedEntry);
|
|
b.linkStatus[e] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void JitBlockCache::LinkBlock(int i) {
|
|
LinkBlockExits(i);
|
|
JitBlock &b = blocks_[i];
|
|
// equal_range(b) returns pair<iterator,iterator> representing the range
|
|
// of element with key b
|
|
auto ppp = links_to_.equal_range(b.originalAddress);
|
|
if (ppp.first == ppp.second)
|
|
return;
|
|
for (auto iter = ppp.first; iter != ppp.second; ++iter) {
|
|
// INFO_LOG(Log::JIT, "Linking block %i to block %i", iter->second, i);
|
|
LinkBlockExits(iter->second);
|
|
}
|
|
}
|
|
|
|
void JitBlockCache::UnlinkBlock(int i) {
|
|
JitBlock &b = blocks_[i];
|
|
auto ppp = links_to_.equal_range(b.originalAddress);
|
|
if (ppp.first == ppp.second)
|
|
return;
|
|
for (auto iter = ppp.first; iter != ppp.second; ++iter) {
|
|
if ((size_t)iter->second >= num_blocks_) {
|
|
// Something probably went very wrong. Try to stumble along nevertheless.
|
|
ERROR_LOG(Log::JIT, "UnlinkBlock: Invalid block number %d", iter->second);
|
|
continue;
|
|
}
|
|
JitBlock &sourceBlock = blocks_[iter->second];
|
|
for (int e = 0; e < MAX_JIT_BLOCK_EXITS; e++) {
|
|
if (sourceBlock.exitAddress[e] == b.originalAddress)
|
|
sourceBlock.linkStatus[e] = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void JitBlockCache::DestroyBlock(int block_num, DestroyType type) {
|
|
if (block_num < 0 || block_num >= num_blocks_) {
|
|
ERROR_LOG_REPORT(Log::JIT, "DestroyBlock: Invalid block number %d", block_num);
|
|
return;
|
|
}
|
|
JitBlock *b = &blocks_[block_num];
|
|
// No point it being in there anymore.
|
|
RemoveBlockMap(block_num);
|
|
|
|
// 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).
|
|
|
|
if (b->invalid) {
|
|
if (type == DestroyType::INVALIDATE)
|
|
ERROR_LOG(Log::JIT, "Invalidating invalid block %d", block_num);
|
|
return;
|
|
}
|
|
|
|
b->invalid = true;
|
|
|
|
// Only clearing the shadow entry needs a valid address. The unlinking below has to
|
|
// happen either way - otherwise other blocks go on jumping straight into this one, which
|
|
// we've just marked invalid.
|
|
if (Memory::IsValid4AlignedAddress(b->originalAddress)) {
|
|
// A mirror may have replaced the entry with its own block, leave that alone.
|
|
Memory::ClearBlockShadow(b->originalAddress, GetShadowValueForBlock(block_num));
|
|
} else {
|
|
_dbg_assert_msg_(false, "Destroying block with invalid original address: %08x (block num: %d)", b->originalAddress, block_num);
|
|
}
|
|
|
|
// It's not safe to set normalEntry to 0 here, since we use a binary search
|
|
// that looks at that later to find blocks. Marking it invalid is enough.
|
|
|
|
UnlinkBlock(block_num);
|
|
|
|
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) {
|
|
u8 *writableEntry = codeBlock_->GetWritablePtrFromCodePtr(b->checkedEntry);
|
|
MIPSComp::jit->UnlinkBlock(writableEntry, b->originalAddress);
|
|
}
|
|
} else {
|
|
ERROR_LOG(Log::JIT, "Unlinking block with no entry: %08x (%d)", b->originalAddress, block_num);
|
|
}
|
|
}
|
|
|
|
void JitBlockCache::InvalidateICache(u32 address, const u32 length) {
|
|
// Convert the logical address to a physical address for the block map
|
|
const u32 pAddr = address & 0x1FFFFFFF;
|
|
const u32 pEnd = pAddr + length;
|
|
|
|
if (pEnd < pAddr) {
|
|
ERROR_LOG(Log::JIT, "Bad InvalidateICache: %08x with len=%d", address, length);
|
|
return;
|
|
}
|
|
|
|
if (pAddr == 0 && pEnd >= 0x1FFFFFFF) {
|
|
InvalidateChangedBlocks();
|
|
return;
|
|
}
|
|
|
|
// Blocks may start and end in overlapping ways, and destroying one invalidates iterators.
|
|
// So after destroying one, we start over.
|
|
do {
|
|
restart:
|
|
auto next = block_map_.lower_bound(std::make_pair(pAddr, 0));
|
|
// block_map_ is keyed by byte addresses, so the margin has to cover the longest
|
|
// possible block in bytes, not instructions.
|
|
auto last = block_map_.upper_bound(std::make_pair(pEnd + 4 * MAX_BLOCK_INSTRUCTIONS, 0));
|
|
// Note that if next is end(), last will be end() too (equal.)
|
|
for (; next != last; ++next) {
|
|
const u32 blockStart = next->first.second;
|
|
const u32 blockEnd = next->first.first;
|
|
if (blockStart < pEnd && blockEnd > pAddr) {
|
|
DestroyBlock(next->second, DestroyType::INVALIDATE);
|
|
// Our iterator is now invalid. Break and search again.
|
|
// Most of the time there shouldn't be a bunch of matching blocks.
|
|
goto restart;
|
|
}
|
|
}
|
|
// We got here - it wasn't in the map at all (or anymore.)
|
|
} while (false);
|
|
}
|
|
|
|
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)
|
|
continue;
|
|
|
|
bool changed = false;
|
|
if (JIT_USE_COMPILEDHASH) {
|
|
changed = b.compiledHash != HashJitBlock(b);
|
|
} else {
|
|
changed = Memory::ReadUnchecked_U32(b.originalAddress) != b.originalFirstOpcode.encoding;
|
|
}
|
|
|
|
if (changed) {
|
|
DEBUG_LOG(Log::JIT, "Invalidating changed block at %08x", b.originalAddress);
|
|
DestroyBlock(block_num, DestroyType::INVALIDATE);
|
|
}
|
|
}
|
|
}
|
|
|
|
int JitBlockCache::GetBlockExitSize() {
|
|
#if PPSSPP_ARCH(ARM)
|
|
// Will depend on the sequence found to encode the destination address.
|
|
return 0;
|
|
#elif PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
|
|
return 15;
|
|
#elif PPSSPP_ARCH(ARM64)
|
|
// Will depend on the sequence found to encode the destination address.
|
|
return 0;
|
|
#elif PPSSPP_ARCH(RISCV64)
|
|
// Will depend on the sequence found to encode the destination address.
|
|
return 0;
|
|
#elif PPSSPP_ARCH(LOONGARCH64)
|
|
// Will depend on the sequence found to encode the destination address.
|
|
return 0;
|
|
#else
|
|
#warning GetBlockExitSize unimplemented
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
void JitBlockCache::ComputeStats(BlockCacheStats &bcStats) const {
|
|
double totalBloat = 0.0;
|
|
double maxBloat = 0.0;
|
|
double minBloat = 1000000000.0;
|
|
for (int i = 0; i < num_blocks_; i++) {
|
|
const JitBlock *b = GetBlock(i);
|
|
double codeSize = (double)b->codeSize;
|
|
if (codeSize == 0)
|
|
continue;
|
|
double origSize = (double)(4 * b->originalSize);
|
|
double bloat = codeSize / origSize;
|
|
if (bloat < minBloat) {
|
|
minBloat = bloat;
|
|
bcStats.minBloatBlock = b->originalAddress;
|
|
}
|
|
if (bloat > maxBloat) {
|
|
maxBloat = bloat;
|
|
bcStats.maxBloatBlock = b->originalAddress;
|
|
}
|
|
totalBloat += bloat;
|
|
}
|
|
bcStats.numBlocks = num_blocks_;
|
|
bcStats.minBloat = (float)minBloat;
|
|
bcStats.maxBloat = (float)maxBloat;
|
|
bcStats.avgBloat = (float)(totalBloat / (double)num_blocks_);
|
|
}
|
|
|
|
JitBlockDebugInfo JitBlockCache::GetBlockDebugInfo(int blockNum) const {
|
|
JitBlockDebugInfo debugInfo{};
|
|
const JitBlock *block = GetBlock(blockNum);
|
|
debugInfo.originalAddress = block->originalAddress;
|
|
debugInfo.origDisasm.reserve(((block->originalAddress + block->originalSize * 4) - block->originalAddress) / 4);
|
|
for (u32 addr = block->originalAddress; addr < block->originalAddress + block->originalSize * 4; addr += 4) {
|
|
char temp[256];
|
|
MIPSDisAsm(Memory::Read_Instruction(addr), addr, temp, sizeof(temp), true);
|
|
std::string mipsDis = temp;
|
|
debugInfo.origDisasm.push_back(mipsDis);
|
|
}
|
|
|
|
#if PPSSPP_ARCH(ARM)
|
|
debugInfo.targetDisasm = DisassembleArm2(block->normalEntry, block->codeSize);
|
|
#elif PPSSPP_ARCH(ARM64)
|
|
debugInfo.targetDisasm = DisassembleArm64(block->normalEntry, block->codeSize);
|
|
#elif PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
|
|
debugInfo.targetDisasm = DisassembleX86(block->normalEntry, block->codeSize);
|
|
#elif PPSSPP_ARCH(RISCV64)
|
|
debugInfo.targetDisasm = DisassembleRV64(block->normalEntry, block->codeSize);
|
|
#elif PPSSPP_ARCH(LOONGARCH64)
|
|
debugInfo.targetDisasm = DisassembleLA64(block->normalEntry, block->codeSize);
|
|
#endif
|
|
return debugInfo;
|
|
}
|