mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-16 07:50:28 +02:00
The core and the headless tools no longer use mutexes, condition variables, spinlocks, std::atomic, std::thread or volatile flags. Threads hand off through libretro-common's retro_atomic and rthreads plus a few small pieces on top of them in Common/Thread: Thread, MpscQueue, EventCounter, ParkingLot waits and ReaderGate. Every wait blocks until it's woken; nothing polls on a timer. State that only one thread touches just loses its lock. The PSP filesystem, sceIo and the savedata dialog now belong to the emulation thread: async reads and writes run when they're queued and complete later in emulated time, and the IO and savedata threads are gone. The friend finder only talks to the adhoc server and hands what it gets to the emulation thread. GPU init always runs on the thread that starts the game. The Vulkan render and compile queues, pipeline and render pass caches, delete list and SPIR-V cache, the GL render queue, the software renderer's jit caches, the file loader caches, the web debugger, UPnP, input, savestates, logging and the GE debugger all hand off through queues and atomics. The IR jit's spinning profiler thread goes. A CPU thread with nothing to run now sleeps until work is queued instead of spinning. Headless with --debugger waits for a client while stopped, and ends the run once every client that connected has left. Web server shutdown keeps servicing the CPU queue until the server has finished.
756 lines
19 KiB
C++
756 lines
19 KiB
C++
// Copyright (c) 2023- PPSSPP 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 <climits>
|
|
#include "Common/Profiler/Profiler.h"
|
|
#include "Common/StringUtils.h"
|
|
#include "Common/TimeUtil.h"
|
|
#include "Core/Core.h"
|
|
#include "Core/Debugger/SymbolMap.h"
|
|
#include "Core/MemMap.h"
|
|
#include "Core/MIPS/MIPSTables.h"
|
|
#include "Core/MIPS/IR/IRNativeCommon.h"
|
|
|
|
using namespace MIPSComp;
|
|
|
|
namespace MIPSComp {
|
|
|
|
// Compile time flag to enable debug stats for not compiled ops.
|
|
static constexpr bool enableDebugStats = false;
|
|
// Compile time flag for the jit's profiler hooks (the current PC and status, kept in memory for a
|
|
// sampling profiler to read).
|
|
static constexpr bool enableDebugProfiler = false;
|
|
|
|
// Used only for debugging when enableDebug is true above.
|
|
static std::map<uint8_t, int> debugSeenNotCompiledIR;
|
|
static std::map<const char *, int> debugSeenNotCompiled;
|
|
static double lastDebugStatsLog = 0.0;
|
|
static constexpr double debugStatsFrequency = 5.0;
|
|
|
|
const char *IRProfilerStatusToString(IRProfilerStatus s) {
|
|
switch (s) {
|
|
case IRProfilerStatus::NOT_RUNNING: return "NOT_RUNNING";
|
|
case IRProfilerStatus::IN_JIT: return "IN_JIT";
|
|
case IRProfilerStatus::TIMER_ADVANCE: return "TIMER_ADVANCE";
|
|
case IRProfilerStatus::COMPILING: return "COMPILING";
|
|
case IRProfilerStatus::MATH_HELPER: return "MATH_HELPER";
|
|
case IRProfilerStatus::REPLACEMENT: return "REPLACEMENT";
|
|
case IRProfilerStatus::SYSCALL: return "SYSCALL";
|
|
case IRProfilerStatus::INTERPRET: return "INTERPRET";
|
|
case IRProfilerStatus::IR_INTERPRET: return "IR_INTERPRET";
|
|
}
|
|
return "INVALID";
|
|
}
|
|
|
|
static void LogDebugStats() {
|
|
if (!enableDebugStats)
|
|
return;
|
|
|
|
double now = time_now_d();
|
|
if (now < lastDebugStatsLog + debugStatsFrequency)
|
|
return;
|
|
lastDebugStatsLog = now;
|
|
|
|
int worstIROp = -1;
|
|
int worstIRVal = 0;
|
|
for (auto it : debugSeenNotCompiledIR) {
|
|
if (it.second > worstIRVal) {
|
|
worstIRVal = it.second;
|
|
worstIROp = it.first;
|
|
}
|
|
}
|
|
debugSeenNotCompiledIR.clear();
|
|
|
|
const char *worstName = nullptr;
|
|
int worstVal = 0;
|
|
for (auto it : debugSeenNotCompiled) {
|
|
if (it.second > worstVal) {
|
|
worstVal = it.second;
|
|
worstName = it.first;
|
|
}
|
|
}
|
|
debugSeenNotCompiled.clear();
|
|
|
|
if (worstIROp != -1)
|
|
WARN_LOG(Log::JIT, "Most not compiled IR op: %s (%d)", GetIRMeta((IROp)worstIROp)->name, worstIRVal);
|
|
if (worstName != nullptr)
|
|
WARN_LOG(Log::JIT, "Most not compiled op: %s (%d)", worstName, worstVal);
|
|
}
|
|
|
|
bool IRNativeBackend::DebugStatsEnabled() const {
|
|
return enableDebugStats;
|
|
}
|
|
|
|
bool IRNativeBackend::DebugProfilerEnabled() const {
|
|
return enableDebugProfiler;
|
|
}
|
|
|
|
void IRNativeBackend::NotifyMIPSInterpret(const char *name) {
|
|
_assert_(enableDebugStats);
|
|
debugSeenNotCompiled[name]++;
|
|
}
|
|
|
|
void IRNativeBackend::DoMIPSInst(uint32_t value) {
|
|
MIPSOpcode op;
|
|
memcpy(&op, &value, sizeof(op));
|
|
|
|
if constexpr (enableDebugStats)
|
|
debugSeenNotCompiled[MIPSGetName(op)]++;
|
|
|
|
MIPSInterpret(currentMIPS, op);
|
|
}
|
|
|
|
// This is called from IR->JIT implementation to fall back to the IR interpreter for missing ops.
|
|
// Not fast.
|
|
uint32_t IRNativeBackend::DoIRInst(uint64_t value) {
|
|
IRInst inst[2]{};
|
|
memcpy(&inst[0], &value, sizeof(value));
|
|
if constexpr (enableDebugStats)
|
|
debugSeenNotCompiledIR[(uint8_t)inst[0].op]++;
|
|
// Doesn't really matter what value it returns as PC.
|
|
inst[1].op = IROp::ExitToPC;
|
|
return IRInterpret(currentMIPS, &inst[0]);
|
|
}
|
|
|
|
int IRNativeBackend::ReportBadAddress(uint32_t addr, uint32_t alignment, uint32_t isWrite) {
|
|
const auto toss = [&](MemoryExceptionType t) {
|
|
Core_MemoryException(addr, alignment, currentMIPS->pc, t);
|
|
return coreState != CORE_RUNNING_CPU ? 1 : 0;
|
|
};
|
|
|
|
if (!Memory::IsValidRange(addr, alignment)) {
|
|
MemoryExceptionType t = isWrite == 1 ? MemoryExceptionType::WRITE_WORD : MemoryExceptionType::READ_WORD;
|
|
if (alignment > 4)
|
|
t = isWrite ? MemoryExceptionType::WRITE_BLOCK : MemoryExceptionType::READ_BLOCK;
|
|
return toss(t);
|
|
} else if (alignment > 1 && (addr & (alignment - 1)) != 0) {
|
|
return toss(MemoryExceptionType::ALIGNMENT);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
IRNativeBackend::IRNativeBackend(IRBlockCache &blocks) : blocks_(blocks) {}
|
|
|
|
IRNativeBackend::~IRNativeBackend() {
|
|
}
|
|
|
|
void IRNativeBackend::CompileIRInst(IRInst inst) {
|
|
switch (inst.op) {
|
|
case IROp::Nop:
|
|
break;
|
|
|
|
case IROp::SetConst:
|
|
case IROp::SetConstF:
|
|
case IROp::Downcount:
|
|
case IROp::SetPC:
|
|
case IROp::SetPCConst:
|
|
CompIR_Basic(inst);
|
|
break;
|
|
|
|
case IROp::Add:
|
|
case IROp::Sub:
|
|
case IROp::AddConst:
|
|
case IROp::SubConst:
|
|
case IROp::Neg:
|
|
CompIR_Arith(inst);
|
|
break;
|
|
|
|
case IROp::And:
|
|
case IROp::Or:
|
|
case IROp::Xor:
|
|
case IROp::AndConst:
|
|
case IROp::OrConst:
|
|
case IROp::XorConst:
|
|
case IROp::Not:
|
|
CompIR_Logic(inst);
|
|
break;
|
|
|
|
case IROp::Mov:
|
|
case IROp::Ext8to32:
|
|
case IROp::Ext16to32:
|
|
CompIR_Assign(inst);
|
|
break;
|
|
|
|
case IROp::ReverseBits:
|
|
case IROp::BSwap16:
|
|
case IROp::BSwap32:
|
|
case IROp::Clz:
|
|
CompIR_Bits(inst);
|
|
break;
|
|
|
|
case IROp::Shl:
|
|
case IROp::Shr:
|
|
case IROp::Sar:
|
|
case IROp::Ror:
|
|
case IROp::ShlImm:
|
|
case IROp::ShrImm:
|
|
case IROp::SarImm:
|
|
case IROp::RorImm:
|
|
CompIR_Shift(inst);
|
|
break;
|
|
|
|
case IROp::Slt:
|
|
case IROp::SltConst:
|
|
case IROp::SltU:
|
|
case IROp::SltUConst:
|
|
CompIR_Compare(inst);
|
|
break;
|
|
|
|
case IROp::MovZ:
|
|
case IROp::MovNZ:
|
|
case IROp::Max:
|
|
case IROp::Min:
|
|
CompIR_CondAssign(inst);
|
|
break;
|
|
|
|
case IROp::MtLo:
|
|
case IROp::MtHi:
|
|
case IROp::MfLo:
|
|
case IROp::MfHi:
|
|
CompIR_HiLo(inst);
|
|
break;
|
|
|
|
case IROp::Mult:
|
|
case IROp::MultU:
|
|
case IROp::Madd:
|
|
case IROp::MaddU:
|
|
case IROp::Msub:
|
|
case IROp::MsubU:
|
|
CompIR_Mult(inst);
|
|
break;
|
|
|
|
case IROp::Div:
|
|
case IROp::DivU:
|
|
CompIR_Div(inst);
|
|
break;
|
|
|
|
case IROp::Load8:
|
|
case IROp::Load8Ext:
|
|
case IROp::Load16:
|
|
case IROp::Load16Ext:
|
|
case IROp::Load32:
|
|
case IROp::Load32Linked:
|
|
CompIR_Load(inst);
|
|
break;
|
|
|
|
case IROp::Load32Left:
|
|
case IROp::Load32Right:
|
|
CompIR_LoadShift(inst);
|
|
break;
|
|
|
|
case IROp::LoadFloat:
|
|
CompIR_FLoad(inst);
|
|
break;
|
|
|
|
case IROp::LoadVec4:
|
|
CompIR_VecLoad(inst);
|
|
break;
|
|
|
|
case IROp::Store8:
|
|
case IROp::Store16:
|
|
case IROp::Store32:
|
|
CompIR_Store(inst);
|
|
break;
|
|
|
|
case IROp::Store32Conditional:
|
|
CompIR_CondStore(inst);
|
|
break;
|
|
|
|
case IROp::Store32Left:
|
|
case IROp::Store32Right:
|
|
CompIR_StoreShift(inst);
|
|
break;
|
|
|
|
case IROp::StoreFloat:
|
|
CompIR_FStore(inst);
|
|
break;
|
|
|
|
case IROp::StoreVec4:
|
|
CompIR_VecStore(inst);
|
|
break;
|
|
|
|
case IROp::FAdd:
|
|
case IROp::FSub:
|
|
case IROp::FMul:
|
|
case IROp::FDiv:
|
|
case IROp::FSqrt:
|
|
case IROp::FNeg:
|
|
CompIR_FArith(inst);
|
|
break;
|
|
|
|
case IROp::FMin:
|
|
case IROp::FMax:
|
|
CompIR_FCondAssign(inst);
|
|
break;
|
|
|
|
case IROp::FMov:
|
|
case IROp::FAbs:
|
|
case IROp::FSign:
|
|
CompIR_FAssign(inst);
|
|
break;
|
|
|
|
case IROp::FRound:
|
|
case IROp::FTrunc:
|
|
case IROp::FCeil:
|
|
case IROp::FFloor:
|
|
CompIR_FRound(inst);
|
|
break;
|
|
|
|
case IROp::FCvtWS:
|
|
case IROp::FCvtSW:
|
|
case IROp::FCvtScaledWS:
|
|
case IROp::FCvtScaledSW:
|
|
CompIR_FCvt(inst);
|
|
break;
|
|
|
|
case IROp::FSat0_1:
|
|
case IROp::FSatMinus1_1:
|
|
CompIR_FSat(inst);
|
|
break;
|
|
|
|
case IROp::FCmp:
|
|
case IROp::FCmovVfpuCC:
|
|
case IROp::FCmpVfpuBit:
|
|
case IROp::FCmpVfpuAggregate:
|
|
CompIR_FCompare(inst);
|
|
break;
|
|
|
|
case IROp::RestoreRoundingMode:
|
|
case IROp::ApplyRoundingMode:
|
|
case IROp::UpdateRoundingMode:
|
|
CompIR_RoundingMode(inst);
|
|
break;
|
|
|
|
case IROp::SetCtrlVFPU:
|
|
case IROp::SetCtrlVFPUReg:
|
|
case IROp::SetCtrlVFPUFReg:
|
|
case IROp::FpCondFromReg:
|
|
case IROp::FpCondToReg:
|
|
case IROp::FpCtrlFromReg:
|
|
case IROp::FpCtrlToReg:
|
|
case IROp::VfpuCtrlToReg:
|
|
case IROp::FMovFromGPR:
|
|
case IROp::FMovToGPR:
|
|
CompIR_Transfer(inst);
|
|
break;
|
|
|
|
case IROp::Vec4Init:
|
|
case IROp::Vec4Shuffle:
|
|
case IROp::Vec4Blend:
|
|
case IROp::Vec4Mov:
|
|
CompIR_VecAssign(inst);
|
|
break;
|
|
|
|
case IROp::Vec4Add:
|
|
case IROp::Vec4Sub:
|
|
case IROp::Vec4Mul:
|
|
case IROp::Vec4Div:
|
|
case IROp::Vec4Scale:
|
|
case IROp::Vec4Neg:
|
|
case IROp::Vec4Abs:
|
|
CompIR_VecArith(inst);
|
|
break;
|
|
|
|
case IROp::Vec4Dot:
|
|
CompIR_VecHoriz(inst);
|
|
break;
|
|
|
|
case IROp::Vec2Unpack16To31:
|
|
case IROp::Vec2Unpack16To32:
|
|
case IROp::Vec4Unpack8To32:
|
|
case IROp::Vec4DuplicateUpperBitsAndShift1:
|
|
case IROp::Vec4Pack31To8:
|
|
case IROp::Vec4Pack32To8:
|
|
case IROp::Vec2Pack31To16:
|
|
case IROp::Vec2Pack32To16:
|
|
CompIR_VecPack(inst);
|
|
break;
|
|
|
|
case IROp::FSin:
|
|
case IROp::FCos:
|
|
case IROp::FRSqrt:
|
|
case IROp::FRecip:
|
|
case IROp::FAsin:
|
|
case IROp::FVSqrt:
|
|
case IROp::FExp2:
|
|
case IROp::FLog2:
|
|
case IROp::FHalfToFloat:
|
|
case IROp::FSinCos:
|
|
CompIR_FSpecial(inst);
|
|
break;
|
|
|
|
case IROp::Interpret:
|
|
CompIR_Interpret(inst);
|
|
break;
|
|
|
|
case IROp::Syscall:
|
|
case IROp::SyscallUnresolved:
|
|
case IROp::CallReplacement:
|
|
case IROp::Break:
|
|
CompIR_System(inst);
|
|
break;
|
|
|
|
case IROp::Breakpoint:
|
|
case IROp::MemoryCheck:
|
|
CompIR_Breakpoint(inst);
|
|
break;
|
|
|
|
case IROp::ValidateAddress8:
|
|
case IROp::ValidateAddress16:
|
|
case IROp::ValidateAddress32:
|
|
case IROp::ValidateAddress128:
|
|
CompIR_ValidateAddress(inst);
|
|
break;
|
|
|
|
case IROp::ExitToConst:
|
|
case IROp::ExitToReg:
|
|
case IROp::ExitToPC:
|
|
CompIR_Exit(inst);
|
|
break;
|
|
|
|
case IROp::ExitToConstIfEq:
|
|
case IROp::ExitToConstIfNeq:
|
|
case IROp::ExitToConstIfGtZ:
|
|
case IROp::ExitToConstIfGeZ:
|
|
case IROp::ExitToConstIfLtZ:
|
|
case IROp::ExitToConstIfLeZ:
|
|
case IROp::ExitToConstIfFpTrue:
|
|
case IROp::ExitToConstIfFpFalse:
|
|
CompIR_ExitIf(inst);
|
|
break;
|
|
|
|
default:
|
|
_assert_msg_(false, "Unexpected IR op %d", (int)inst.op);
|
|
CompIR_Generic(inst);
|
|
break;
|
|
}
|
|
}
|
|
|
|
IRNativeJit::IRNativeJit(MIPSState *mipsState)
|
|
: IRJit(mipsState, true), debugInterface_(blocks_) {}
|
|
|
|
void IRNativeJit::Init(IRNativeBackend &backend) {
|
|
backend_ = &backend;
|
|
debugInterface_.Init(backend_);
|
|
backend_->GenerateFixedCode(mips_);
|
|
|
|
// Wanted this to be a reference, but vtbls get in the way. Shouldn't change.
|
|
hooks_ = backend.GetNativeHooks();
|
|
}
|
|
|
|
bool IRNativeJit::CompileNativeBlock(IRBlockCache *irblockCache, int block_num) {
|
|
return backend_->CompileBlock(irblockCache, block_num);
|
|
}
|
|
|
|
void IRNativeJit::FinalizeNativeBlock(IRBlockCache *irblockCache, int block_num) {
|
|
backend_->FinalizeBlock(irblockCache, block_num, jo);
|
|
}
|
|
|
|
void IRNativeJit::RunLoopUntil(u64 globalticks) {
|
|
if constexpr (enableDebugStats) {
|
|
LogDebugStats();
|
|
}
|
|
|
|
PROFILE_THIS_SCOPE("jit");
|
|
hooks_.enterDispatcher();
|
|
}
|
|
|
|
void IRNativeJit::ClearCache() {
|
|
IRJit::ClearCache();
|
|
backend_->ClearAllBlocks();
|
|
}
|
|
|
|
bool IRNativeJit::DescribeCodePtr(const u8 *ptr, std::string &name) {
|
|
if (ptr != nullptr && backend_->DescribeCodePtr(ptr, name))
|
|
return true;
|
|
|
|
int offset = backend_->OffsetFromCodePtr(ptr);
|
|
if (offset == -1)
|
|
return false;
|
|
|
|
int block_num = -1;
|
|
int block_offset = INT_MAX;
|
|
for (int i = 0; i < blocks_.GetNumBlocks(); ++i) {
|
|
const auto &b = blocks_.GetBlock(i);
|
|
int b_start = b->GetNativeOffset();
|
|
if (b_start > offset)
|
|
continue;
|
|
|
|
int b_end = backend_->GetNativeBlock(i)->checkedOffset;
|
|
int b_offset = offset - b_start;
|
|
if (b_end > b_start && b_end >= offset) {
|
|
// For sure within the block.
|
|
block_num = i;
|
|
block_offset = b_offset;
|
|
break;
|
|
}
|
|
|
|
if (b_offset < block_offset) {
|
|
// Possibly within the block, unless in some other block...
|
|
block_num = i;
|
|
block_offset = b_offset;
|
|
}
|
|
}
|
|
|
|
// Used by profiling tools that don't like spaces.
|
|
if (block_num == -1) {
|
|
name = "unknownOrDeletedBlock";
|
|
return true;
|
|
}
|
|
|
|
const IRBlock *block = blocks_.GetBlock(block_num);
|
|
if (block) {
|
|
u32 start = 0, size = 0;
|
|
block->GetRange(&start, &size);
|
|
|
|
// It helps to know which func this block is inside.
|
|
const std::string label = g_symbolMap ? g_symbolMap->GetDescription(start) : "";
|
|
if (!label.empty())
|
|
name = StringFromFormat("block%d_%08x_%s_0x%x", block_num, start, label.c_str(), block_offset);
|
|
else
|
|
name = StringFromFormat("block%d_%08x_0x%x", block_num, start, block_offset);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool IRNativeJit::CodeInRange(const u8 *ptr) const {
|
|
return backend_->CodeInRange(ptr);
|
|
}
|
|
|
|
const u8 *IRNativeJit::GetCodeBase() const {
|
|
return backend_->CodeBlock().GetBasePtr();
|
|
}
|
|
|
|
bool IRNativeJit::IsAtDispatchFetch(const u8 *ptr) const {
|
|
return ptr == backend_->GetNativeHooks().dispatchFetch;
|
|
}
|
|
|
|
const u8 *IRNativeJit::GetDispatcher() const {
|
|
return backend_->GetNativeHooks().dispatcher;
|
|
}
|
|
|
|
const u8 *IRNativeJit::GetCrashHandler() const {
|
|
return backend_->GetNativeHooks().crashHandler;
|
|
}
|
|
|
|
void IRNativeJit::UpdateFCR31() {
|
|
backend_->UpdateFCR31(mips_);
|
|
}
|
|
|
|
JitBlockCacheDebugInterface *IRNativeJit::GetBlockCacheDebugInterface() {
|
|
return &debugInterface_;
|
|
}
|
|
|
|
bool IRNativeBackend::CodeInRange(const u8 *ptr) const {
|
|
return CodeBlock().IsInSpace(ptr);
|
|
}
|
|
|
|
bool IRNativeBackend::DescribeCodePtr(const u8 *ptr, std::string &name) const {
|
|
if (!CodeBlock().IsInSpace(ptr))
|
|
return false;
|
|
|
|
// Used in disassembly viewer.
|
|
if (ptr == (const uint8_t *)hooks_.enterDispatcher) {
|
|
name = "enterDispatcher";
|
|
} else if (ptr == hooks_.dispatcher) {
|
|
name = "dispatcher";
|
|
} else if (ptr == hooks_.dispatchFetch) {
|
|
name = "dispatchFetch";
|
|
} else if (ptr == hooks_.crashHandler) {
|
|
name = "crashHandler";
|
|
} else {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int IRNativeBackend::OffsetFromCodePtr(const u8 *ptr) {
|
|
auto &codeBlock = CodeBlock();
|
|
if (!codeBlock.IsInSpace(ptr))
|
|
return -1;
|
|
return (int)codeBlock.GetOffset(ptr);
|
|
}
|
|
|
|
void IRNativeBackend::FinalizeBlock(IRBlockCache *irBlockCache, int block_num, const JitOptions &jo) {
|
|
IRBlock *block = irBlockCache->GetBlock(block_num);
|
|
if (jo.enableBlocklink) {
|
|
uint32_t pc = block->GetOriginalStart();
|
|
|
|
// First, link other blocks to this one now that it's finalized.
|
|
auto incoming = linksTo_.equal_range(pc);
|
|
for (auto it = incoming.first; it != incoming.second; ++it) {
|
|
auto &exits = nativeBlocks_[it->second].exits;
|
|
for (auto &blockExit : exits) {
|
|
if (blockExit.dest == pc)
|
|
OverwriteExit(blockExit.offset, blockExit.len, block_num);
|
|
}
|
|
}
|
|
|
|
// And also any blocks from this one, in case we're finalizing it later.
|
|
auto &outgoing = nativeBlocks_[block_num].exits;
|
|
for (auto &blockExit : outgoing) {
|
|
int dstBlockNum = blocks_.GetBlockNumberFromStartAddress(blockExit.dest);
|
|
const IRNativeBlock *nativeBlock = GetNativeBlock(dstBlockNum);
|
|
if (nativeBlock)
|
|
OverwriteExit(blockExit.offset, blockExit.len, dstBlockNum);
|
|
}
|
|
}
|
|
}
|
|
|
|
const IRNativeBlock *IRNativeBackend::GetNativeBlock(int block_num) const {
|
|
if (block_num < 0 || block_num >= (int)nativeBlocks_.size())
|
|
return nullptr;
|
|
return &nativeBlocks_[block_num];
|
|
}
|
|
|
|
void IRNativeBackend::SetBlockCheckedOffset(int block_num, int offset) {
|
|
if (block_num >= (int)nativeBlocks_.size())
|
|
nativeBlocks_.resize(block_num + 1);
|
|
|
|
nativeBlocks_[block_num].checkedOffset = offset;
|
|
}
|
|
|
|
void IRNativeBackend::AddLinkableExit(int block_num, uint32_t pc, int exitStartOffset, int exitLen) {
|
|
linksTo_.emplace(pc, block_num);
|
|
|
|
if (block_num >= (int)nativeBlocks_.size())
|
|
nativeBlocks_.resize(block_num + 1);
|
|
IRNativeBlockExit blockExit;
|
|
blockExit.offset = exitStartOffset;
|
|
blockExit.len = exitLen;
|
|
blockExit.dest = pc;
|
|
nativeBlocks_[block_num].exits.push_back(blockExit);
|
|
}
|
|
|
|
void IRNativeBackend::EraseAllLinks(int block_num) {
|
|
if (block_num == -1) {
|
|
linksTo_.clear();
|
|
nativeBlocks_.clear();
|
|
} else {
|
|
linksTo_.erase(block_num);
|
|
if (block_num < (int)nativeBlocks_.size())
|
|
nativeBlocks_[block_num].exits.clear();
|
|
}
|
|
}
|
|
|
|
IRNativeBlockCacheDebugInterface::IRNativeBlockCacheDebugInterface(const IRBlockCache &irBlocks)
|
|
: irBlocks_(irBlocks) {}
|
|
|
|
void IRNativeBlockCacheDebugInterface::Init(const IRNativeBackend *backend) {
|
|
codeBlock_ = &backend->CodeBlock();
|
|
backend_ = backend;
|
|
}
|
|
|
|
bool IRNativeBlockCacheDebugInterface::IsValidBlock(int blockNum) const {
|
|
return irBlocks_.IsValidBlock(blockNum);
|
|
}
|
|
|
|
JitBlockMeta IRNativeBlockCacheDebugInterface::GetBlockMeta(int blockNum) const {
|
|
return irBlocks_.GetBlockMeta(blockNum);
|
|
}
|
|
|
|
int IRNativeBlockCacheDebugInterface::GetNumBlocks() const {
|
|
return irBlocks_.GetNumBlocks();
|
|
}
|
|
|
|
int IRNativeBlockCacheDebugInterface::GetBlockNumberFromStartAddress(u32 em_address) const {
|
|
return irBlocks_.GetBlockNumberFromStartAddress(em_address);
|
|
}
|
|
|
|
JitBlockProfileStats IRNativeBlockCacheDebugInterface::GetBlockProfileStats(int blockNum) const {
|
|
return irBlocks_.GetBlockProfileStats(blockNum);
|
|
}
|
|
|
|
void IRNativeBlockCacheDebugInterface::GetBlockCodeRange(int blockNum, int *startOffset, int *size) const {
|
|
int blockOffset = irBlocks_.GetBlock(blockNum)->GetNativeOffset();
|
|
int endOffset = backend_->GetNativeBlock(blockNum)->checkedOffset;
|
|
|
|
// If endOffset is before, the checked entry is before the block start.
|
|
if (endOffset < blockOffset) {
|
|
// We assume linear allocation. Maybe a bit dangerous, should always be right.
|
|
if (blockNum + 1 >= GetNumBlocks()) {
|
|
// Last block, get from current code pointer.
|
|
endOffset = (int)codeBlock_->GetOffset(codeBlock_->GetCodePtr());
|
|
} else {
|
|
endOffset = irBlocks_.GetBlock(blockNum + 1)->GetNativeOffset();
|
|
_assert_msg_(endOffset >= blockOffset, "Next block not sequential, block=%d/%08x, next=%d/%08x", blockNum, blockOffset, blockNum + 1, endOffset);
|
|
}
|
|
}
|
|
|
|
*startOffset = blockOffset;
|
|
*size = endOffset - blockOffset;
|
|
}
|
|
|
|
JitBlockDebugInfo IRNativeBlockCacheDebugInterface::GetBlockDebugInfo(int blockNum) const {
|
|
JitBlockDebugInfo debugInfo = irBlocks_.GetBlockDebugInfo(blockNum);
|
|
|
|
int blockOffset, codeSize;
|
|
GetBlockCodeRange(blockNum, &blockOffset, &codeSize);
|
|
|
|
const u8 *blockStart = codeBlock_->GetBasePtr() + blockOffset;
|
|
#if PPSSPP_ARCH(ARM)
|
|
debugInfo.targetDisasm = DisassembleArm2(blockStart, codeSize);
|
|
#elif PPSSPP_ARCH(ARM64)
|
|
debugInfo.targetDisasm = DisassembleArm64(blockStart, codeSize);
|
|
#elif PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
|
|
debugInfo.targetDisasm = DisassembleX86(blockStart, codeSize);
|
|
#elif PPSSPP_ARCH(RISCV64)
|
|
debugInfo.targetDisasm = DisassembleRV64(blockStart, codeSize);
|
|
#elif PPSSPP_ARCH(LOONGARCH64)
|
|
debugInfo.targetDisasm = DisassembleLA64(blockStart, codeSize);
|
|
#endif
|
|
return debugInfo;
|
|
}
|
|
|
|
void IRNativeBlockCacheDebugInterface::ComputeStats(BlockCacheStats &bcStats) const {
|
|
double totalBloat = 0.0;
|
|
double maxBloat = 0.0;
|
|
double minBloat = 1000000000.0;
|
|
int numBlocks = GetNumBlocks();
|
|
for (int i = 0; i < numBlocks; ++i) {
|
|
const IRBlock &b = *irBlocks_.GetBlock(i);
|
|
|
|
// Native size, not IR size.
|
|
int blockOffset, codeSize;
|
|
GetBlockCodeRange(i, &blockOffset, &codeSize);
|
|
if (codeSize == 0)
|
|
continue;
|
|
|
|
// MIPS (PSP) size.
|
|
u32 origAddr, origSize;
|
|
b.GetRange(&origAddr, &origSize);
|
|
|
|
double bloat = (double)codeSize / (double)origSize;
|
|
if (bloat < minBloat) {
|
|
minBloat = bloat;
|
|
bcStats.minBloatBlock = origAddr;
|
|
}
|
|
if (bloat > maxBloat) {
|
|
maxBloat = bloat;
|
|
bcStats.maxBloatBlock = origAddr;
|
|
}
|
|
totalBloat += bloat;
|
|
}
|
|
bcStats.numBlocks = numBlocks;
|
|
bcStats.minBloat = (float)minBloat;
|
|
bcStats.maxBloat = (float)maxBloat;
|
|
bcStats.avgBloat = (float)(totalBloat / (double)numBlocks);
|
|
}
|
|
|
|
} // namespace MIPSComp
|