DolphinQt: JIT Widget Refresh

Fulfilling a certain six-year-old todo.
This commit is contained in:
mitaclaw 2024-04-15 21:52:40 -07:00
parent ca9222a16b
commit 9afd09598c
36 changed files with 1640 additions and 390 deletions

View File

@ -146,6 +146,14 @@ void Host_UpdateDisasmDialog()
{ {
} }
void Host_JitCacheCleared()
{
}
void Host_JitProfileDataWiped()
{
}
void Host_UpdateMainFrame() void Host_UpdateMainFrame()
{ {
} }

View File

@ -3,52 +3,43 @@
#include "Common/HostDisassembler.h" #include "Common/HostDisassembler.h"
#include <sstream> #include <span>
#include <fmt/format.h>
#include <fmt/ostream.h>
#include <fmt/ranges.h>
#if defined(HAVE_LLVM) #if defined(HAVE_LLVM)
#include <fmt/format.h>
#include <llvm-c/Disassembler.h> #include <llvm-c/Disassembler.h>
#include <llvm-c/Target.h> #include <llvm-c/Target.h>
#elif defined(_M_X86_64) #elif defined(_M_X86_64)
#include <disasm.h> // Bochs #include <disasm.h> // Bochs
#endif #endif
#include "Common/Assert.h"
#include "Common/VariantUtil.h"
#include "Core/PowerPC/JitInterface.h"
#include "Core/System.h"
#if defined(HAVE_LLVM) #if defined(HAVE_LLVM)
class HostDisassemblerLLVM : public HostDisassembler class HostDisassemblerLLVM final : public HostDisassembler
{ {
public: public:
HostDisassemblerLLVM(const std::string& host_disasm, int inst_size = -1, explicit HostDisassemblerLLVM(const char* host_disasm, const char* cpu = "",
const std::string& cpu = ""); std::size_t inst_size = 0);
~HostDisassemblerLLVM() ~HostDisassemblerLLVM();
{
if (m_can_disasm)
LLVMDisasmDispose(m_llvm_context);
}
private: private:
bool m_can_disasm;
LLVMDisasmContextRef m_llvm_context; LLVMDisasmContextRef m_llvm_context;
int m_instruction_size; std::size_t m_instruction_size;
std::string DisassembleHostBlock(const u8* code_start, const u32 code_size, std::size_t Disassemble(const u8* begin, const u8* end, std::ostream& stream) override;
u32* host_instructions_count, u64 starting_pc) override;
}; };
HostDisassemblerLLVM::HostDisassemblerLLVM(const std::string& host_disasm, int inst_size, HostDisassemblerLLVM::HostDisassemblerLLVM(const char* host_disasm, const char* cpu,
const std::string& cpu) std::size_t inst_size)
: m_can_disasm(false), m_instruction_size(inst_size) : m_instruction_size(inst_size)
{ {
LLVMInitializeAllTargetInfos(); LLVMInitializeAllTargetInfos();
LLVMInitializeAllTargetMCs(); LLVMInitializeAllTargetMCs();
LLVMInitializeAllDisassemblers(); LLVMInitializeAllDisassemblers();
m_llvm_context = m_llvm_context = LLVMCreateDisasmCPU(host_disasm, cpu, nullptr, 0, nullptr, nullptr);
LLVMCreateDisasmCPU(host_disasm.c_str(), cpu.c_str(), nullptr, 0, nullptr, nullptr);
// Couldn't create llvm context // Couldn't create llvm context
if (!m_llvm_context) if (!m_llvm_context)
@ -56,153 +47,112 @@ HostDisassemblerLLVM::HostDisassemblerLLVM(const std::string& host_disasm, int i
LLVMSetDisasmOptions(m_llvm_context, LLVMDisassembler_Option_AsmPrinterVariant | LLVMSetDisasmOptions(m_llvm_context, LLVMDisassembler_Option_AsmPrinterVariant |
LLVMDisassembler_Option_PrintLatency); LLVMDisassembler_Option_PrintLatency);
m_can_disasm = true;
} }
std::string HostDisassemblerLLVM::DisassembleHostBlock(const u8* code_start, const u32 code_size, HostDisassemblerLLVM::~HostDisassemblerLLVM()
u32* host_instructions_count,
u64 starting_pc)
{ {
if (!m_can_disasm) if (m_llvm_context)
return "(No LLVM context)"; LLVMDisasmDispose(m_llvm_context);
}
u8* disasmPtr = (u8*)code_start; std::size_t HostDisassemblerLLVM::Disassemble(const u8* begin, const u8* end, std::ostream& stream)
const u8* end = code_start + code_size; {
std::size_t instruction_count = 0;
if (!m_llvm_context)
return instruction_count;
std::ostringstream x86_disasm; while (begin < end)
while ((u8*)disasmPtr < end)
{ {
char inst_disasm[256]; char inst_disasm[256];
size_t inst_size = LLVMDisasmInstruction(m_llvm_context, disasmPtr, (u64)(end - disasmPtr),
starting_pc, inst_disasm, 256);
x86_disasm << "0x" << std::hex << starting_pc << "\t"; const auto inst_size =
if (!inst_size) LLVMDisasmInstruction(m_llvm_context, const_cast<u8*>(begin), static_cast<u64>(end - begin),
reinterpret_cast<u64>(begin), inst_disasm, sizeof(inst_disasm));
if (inst_size == 0)
{ {
x86_disasm << "Invalid inst:"; if (m_instruction_size != 0)
if (m_instruction_size != -1)
{ {
// If we are on an architecture that has a fixed instruction size // If we are on an architecture that has a fixed instruction
// We can continue onward past this bad instruction. // size, we can continue onward past this bad instruction.
std::string inst_str; fmt::println(stream, "{}\tInvalid inst: {:02x}", fmt::ptr(begin),
for (int i = 0; i < m_instruction_size; ++i) fmt::join(std::span{begin, m_instruction_size}, ""));
inst_str += fmt::format("{:02x}", disasmPtr[i]); begin += m_instruction_size;
x86_disasm << inst_str << std::endl;
disasmPtr += m_instruction_size;
} }
else else
{ {
// We can't continue if we are on an architecture that has flexible instruction sizes // We can't continue if we are on an architecture that has flexible
// Dump the rest of the block instead // instruction sizes. Dump the rest of the block instead.
std::string code_block; fmt::println(stream, "{}\tInvalid inst: {:02x}", fmt::ptr(begin),
for (int i = 0; (disasmPtr + i) < end; ++i) fmt::join(std::span{begin, end}, ""));
code_block += fmt::format("{:02x}", disasmPtr[i]);
x86_disasm << code_block << std::endl;
break; break;
} }
} }
else else
{ {
x86_disasm << inst_disasm << std::endl; fmt::println(stream, "{}{}", fmt::ptr(begin), inst_disasm);
disasmPtr += inst_size; begin += inst_size;
starting_pc += inst_size;
} }
++instruction_count;
(*host_instructions_count)++;
} }
return instruction_count;
return x86_disasm.str();
} }
#elif defined(_M_X86_64) #elif defined(_M_X86_64)
class HostDisassemblerX86 : public HostDisassembler class HostDisassemblerBochs final : public HostDisassembler
{ {
public: public:
HostDisassemblerX86(); explicit HostDisassemblerBochs();
~HostDisassemblerBochs() = default;
private: private:
disassembler m_disasm; disassembler m_disasm;
std::string DisassembleHostBlock(const u8* code_start, const u32 code_size, std::size_t Disassemble(const u8* begin, const u8* end, std::ostream& stream) override;
u32* host_instructions_count, u64 starting_pc) override;
}; };
HostDisassemblerX86::HostDisassemblerX86() HostDisassemblerBochs::HostDisassemblerBochs()
{ {
m_disasm.set_syntax_intel(); m_disasm.set_syntax_intel();
} }
std::string HostDisassemblerX86::DisassembleHostBlock(const u8* code_start, const u32 code_size, std::size_t HostDisassemblerBochs::Disassemble(const u8* begin, const u8* end, std::ostream& stream)
u32* host_instructions_count, u64 starting_pc)
{ {
u64 disasmPtr = (u64)code_start; std::size_t instruction_count = 0;
const u8* end = code_start + code_size; while (begin < end)
std::ostringstream x86_disasm;
while ((u8*)disasmPtr < end)
{ {
char inst_disasm[256]; char inst_disasm[256];
disasmPtr += m_disasm.disasm64(disasmPtr, disasmPtr, (u8*)disasmPtr, inst_disasm);
x86_disasm << inst_disasm << std::endl; const auto inst_size =
(*host_instructions_count)++; m_disasm.disasm64(0, reinterpret_cast<bx_address>(begin), begin, inst_disasm);
fmt::println(stream, "{}\t{}", fmt::ptr(begin), inst_disasm);
begin += inst_size;
++instruction_count;
} }
return instruction_count;
return x86_disasm.str();
} }
#endif #endif
std::unique_ptr<HostDisassembler> GetNewDisassembler(const std::string& arch) std::unique_ptr<HostDisassembler> HostDisassembler::Factory(Platform arch)
{ {
switch (arch)
{
#if defined(HAVE_LLVM) #if defined(HAVE_LLVM)
if (arch == "x86") case Platform::x86_64:
return std::make_unique<HostDisassemblerLLVM>("x86_64-none-unknown"); return std::make_unique<HostDisassemblerLLVM>("x86_64-none-unknown");
if (arch == "aarch64") case Platform::aarch64:
return std::make_unique<HostDisassemblerLLVM>("aarch64-none-unknown", 4, "cortex-a57"); return std::make_unique<HostDisassemblerLLVM>("aarch64-none-unknown", "cortex-a57", 4);
if (arch == "armv7")
return std::make_unique<HostDisassemblerLLVM>("armv7-none-unknown", 4, "cortex-a15");
#elif defined(_M_X86_64) #elif defined(_M_X86_64)
if (arch == "x86") case Platform::x86_64:
return std::make_unique<HostDisassemblerX86>(); return std::make_unique<HostDisassemblerBochs>();
#else
case Platform{}: // warning C4065: "switch statement contains 'default' but no 'case' labels"
#endif #endif
return std::make_unique<HostDisassembler>(); default:
return std::make_unique<HostDisassembler>();
}
} }
DisassembleResult DisassembleBlock(HostDisassembler* disasm, u32 address) std::size_t HostDisassembler::Disassemble(const u8* begin, const u8* end, std::ostream& stream)
{ {
auto res = Core::System::GetInstance().GetJitInterface().GetHostCode(address); fmt::println(stream, "{}\t{:02x}", fmt::ptr(begin), fmt::join(std::span{begin, end}, ""));
return 0;
return std::visit(overloaded{[&](JitInterface::GetHostCodeError error) {
DisassembleResult result;
switch (error)
{
case JitInterface::GetHostCodeError::NoJitActive:
result.text = "(No JIT active)";
break;
case JitInterface::GetHostCodeError::NoTranslation:
result.text = "(No translation)";
break;
default:
ASSERT(false);
break;
}
result.entry_address = address;
result.instruction_count = 0;
result.code_size = 0;
return result;
},
[&](JitInterface::GetHostCodeResult host_result) {
DisassembleResult new_result;
u32 instruction_count = 0;
new_result.text = disasm->DisassembleHostBlock(
host_result.code, host_result.code_size, &instruction_count,
(u64)host_result.code);
new_result.entry_address = host_result.entry_address;
new_result.code_size = host_result.code_size;
new_result.instruction_count = instruction_count;
return new_result;
}},
res);
} }

View File

@ -3,28 +3,24 @@
#pragma once #pragma once
#include <cstddef>
#include <iosfwd>
#include <memory> #include <memory>
#include <string>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
class HostDisassembler class HostDisassembler
{ {
public: public:
virtual ~HostDisassembler() {} enum class Platform
virtual std::string DisassembleHostBlock(const u8* code_start, const u32 code_size,
u32* host_instructions_count, u64 starting_pc)
{ {
return "(No disassembler)"; x86_64,
} aarch64,
}; };
struct DisassembleResult virtual ~HostDisassembler() = default;
{
std::string text;
u32 entry_address = 0;
u32 instruction_count = 0;
u32 code_size = 0;
};
std::unique_ptr<HostDisassembler> GetNewDisassembler(const std::string& arch); static std::unique_ptr<HostDisassembler> Factory(Platform arch);
DisassembleResult DisassembleBlock(HostDisassembler* disasm, u32 address);
virtual std::size_t Disassemble(const u8* begin, const u8* end, std::ostream& stream);
};

View File

@ -60,6 +60,8 @@ void Host_PPCSymbolsChanged();
void Host_RefreshDSPDebuggerWindow(); void Host_RefreshDSPDebuggerWindow();
void Host_RequestRenderWindowSize(int width, int height); void Host_RequestRenderWindowSize(int width, int height);
void Host_UpdateDisasmDialog(); void Host_UpdateDisasmDialog();
void Host_JitCacheCleared();
void Host_JitProfileDataWiped();
void Host_UpdateMainFrame(); void Host_UpdateMainFrame();
void Host_UpdateTitle(const std::string& title); void Host_UpdateTitle(const std::string& title);
void Host_YieldToUI(); void Host_YieldToUI();

View File

@ -10,6 +10,7 @@
#include "Core/CoreTiming.h" #include "Core/CoreTiming.h"
#include "Core/HLE/HLE.h" #include "Core/HLE/HLE.h"
#include "Core/HW/CPU.h" #include "Core/HW/CPU.h"
#include "Core/Host.h"
#include "Core/PowerPC/Gekko.h" #include "Core/PowerPC/Gekko.h"
#include "Core/PowerPC/Interpreter/Interpreter.h" #include "Core/PowerPC/Interpreter/Interpreter.h"
#include "Core/PowerPC/Jit64Common/Jit64Constants.h" #include "Core/PowerPC/Jit64Common/Jit64Constants.h"
@ -290,13 +291,12 @@ void CachedInterpreter::Jit(u32 em_address, bool clear_cache_and_retry_on_failur
b->far_begin = b->far_end = nullptr; b->far_begin = b->far_end = nullptr;
b->codeSize = static_cast<u32>(b->near_end - b->normalEntry); b->codeSize = static_cast<u32>(b->near_end - b->normalEntry);
b->originalSize = code_block.m_num_instructions;
// Mark the memory region that this code block uses in the RangeSizeSet. // Mark the memory region that this code block uses in the RangeSizeSet.
if (b->near_begin != b->near_end) if (b->near_begin != b->near_end)
m_free_ranges.erase(b->near_begin, b->near_end); m_free_ranges.erase(b->near_begin, b->near_end);
m_block_cache.FinalizeBlock(*b, jo.enableBlocklink, code_block.m_physical_addresses); m_block_cache.FinalizeBlock(*b, jo.enableBlocklink, code_block, m_code_buffer);
return; return;
} }
@ -413,6 +413,18 @@ std::vector<JitBase::MemoryStats> CachedInterpreter::GetMemoryStats() const
return {{"free", m_free_ranges.get_stats()}}; return {{"free", m_free_ranges.get_stats()}};
} }
std::size_t CachedInterpreter::DisassembleNearCode(const JitBlock& block,
std::ostream& stream) const
{
return Disassemble(block, stream);
}
std::size_t CachedInterpreter::DisassembleFarCode(const JitBlock& block, std::ostream& stream) const
{
stream << "N/A\n";
return 0;
}
void CachedInterpreter::ClearCache() void CachedInterpreter::ClearCache()
{ {
m_block_cache.Clear(); m_block_cache.Clear();
@ -420,4 +432,5 @@ void CachedInterpreter::ClearCache()
ClearCodeSpace(); ClearCodeSpace();
ResetFreeMemoryRanges(); ResetFreeMemoryRanges();
RefreshConfig(); RefreshConfig();
Host_JitCacheCleared();
} }

View File

@ -51,6 +51,9 @@ public:
static std::size_t Disassemble(const JitBlock& block, std::ostream& stream); static std::size_t Disassemble(const JitBlock& block, std::ostream& stream);
std::size_t DisassembleNearCode(const JitBlock& block, std::ostream& stream) const override;
std::size_t DisassembleFarCode(const JitBlock& block, std::ostream& stream) const override;
JitBaseBlockCache* GetBlockCache() override { return &m_block_cache; } JitBaseBlockCache* GetBlockCache() override { return &m_block_cache; }
const char* GetName() const override { return "Cached Interpreter"; } const char* GetName() const override { return "Cached Interpreter"; }
const CommonAsmRoutinesBase* GetAsmRoutines() override { return nullptr; } const CommonAsmRoutinesBase* GetAsmRoutines() override { return nullptr; }

View File

@ -9,6 +9,7 @@
#include <disasm.h> #include <disasm.h>
#include <fmt/format.h> #include <fmt/format.h>
#include <fmt/ostream.h>
// for the PROFILER stuff // for the PROFILER stuff
#ifdef _WIN32 #ifdef _WIN32
@ -18,6 +19,7 @@
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/EnumUtils.h" #include "Common/EnumUtils.h"
#include "Common/GekkoDisassembler.h" #include "Common/GekkoDisassembler.h"
#include "Common/HostDisassembler.h"
#include "Common/IOFile.h" #include "Common/IOFile.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Common/StringUtil.h" #include "Common/StringUtil.h"
@ -30,6 +32,7 @@
#include "Core/HW/GPFifo.h" #include "Core/HW/GPFifo.h"
#include "Core/HW/Memmap.h" #include "Core/HW/Memmap.h"
#include "Core/HW/ProcessorInterface.h" #include "Core/HW/ProcessorInterface.h"
#include "Core/Host.h"
#include "Core/MachineContext.h" #include "Core/MachineContext.h"
#include "Core/PatchEngine.h" #include "Core/PatchEngine.h"
#include "Core/PowerPC/Interpreter/Interpreter.h" #include "Core/PowerPC/Interpreter/Interpreter.h"
@ -116,7 +119,9 @@ using namespace PowerPC;
and such, but it's currently limited to integer ops only. This can definitely be made better. and such, but it's currently limited to integer ops only. This can definitely be made better.
*/ */
Jit64::Jit64(Core::System& system) : JitBase(system), QuantizedMemoryRoutines(*this) Jit64::Jit64(Core::System& system)
: JitBase(system), QuantizedMemoryRoutines(*this),
m_disassembler(HostDisassembler::Factory(HostDisassembler::Platform::x86_64))
{ {
} }
@ -308,6 +313,7 @@ void Jit64::ClearCache()
RefreshConfig(); RefreshConfig();
asm_routines.Regenerate(); asm_routines.Regenerate();
ResetFreeMemoryRanges(); ResetFreeMemoryRanges();
Host_JitCacheCleared();
} }
void Jit64::FreeRanges() void Jit64::FreeRanges()
@ -826,7 +832,7 @@ void Jit64::Jit(u32 em_address, bool clear_cache_and_retry_on_failure)
b->far_begin = far_start; b->far_begin = far_start;
b->far_end = far_end; b->far_end = far_end;
blocks.FinalizeBlock(*b, jo.enableBlocklink, code_block.m_physical_addresses); blocks.FinalizeBlock(*b, jo.enableBlocklink, code_block, m_code_buffer);
return; return;
} }
} }
@ -1193,7 +1199,6 @@ bool Jit64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
} }
b->codeSize = static_cast<u32>(GetCodePtr() - b->normalEntry); b->codeSize = static_cast<u32>(GetCodePtr() - b->normalEntry);
b->originalSize = code_block.m_num_instructions;
#ifdef JIT_LOG_GENERATED_CODE #ifdef JIT_LOG_GENERATED_CODE
LogGeneratedX86(code_block.m_num_instructions, m_code_buffer, start, b); LogGeneratedX86(code_block.m_num_instructions, m_code_buffer, start, b);
@ -1213,6 +1218,39 @@ std::vector<JitBase::MemoryStats> Jit64::GetMemoryStats() const
return {{"near", m_free_ranges_near.get_stats()}, {"far", m_free_ranges_far.get_stats()}}; return {{"near", m_free_ranges_near.get_stats()}, {"far", m_free_ranges_far.get_stats()}};
} }
std::size_t Jit64::DisassembleNearCode(const JitBlock& block, std::ostream& stream) const
{
// The last element of the JitBlock::linkData vector is not necessarily the furthest exit.
// See: Jit64::JustWriteExit
const auto iter = std::ranges::max_element(block.linkData, {}, &JitBlock::LinkData::exitPtrs);
// Link data is not guaranteed, e.g. Jit64::WriteRfiExitDestInRSCRATCH
if (iter == block.linkData.end())
return m_disassembler->Disassemble(block.normalEntry, block.near_end, stream);
// A JitBlock's near_end only records where the XEmitter was after DoJit concludes. However, a
// JitBlock's exits will be modified by block linking. If Block A wants to link its final exit
// to the entry_point of Block B, and Block B follows Block A in memory, then the final exit's
// JMP will not have its destination modified but will instead be overwritten by a multibyte NOP.
// Trickily, Block A's near_end does not necessarily equal Block B's entry_point because Block B's
// entry_point is aligned to the next multiple of 4! This means the multibyte NOP may need to
// extend past Block A's near_end, complicating host code disassembly. If the opcode of a JMP
// instruction is found at the final exit, the block will be disassembled like normal. If one
// is not, the exit is assumed to be overwritten, and special action is taken.
const u8* const furthest_exit = iter->exitPtrs;
if (*furthest_exit == 0xE9)
return m_disassembler->Disassemble(block.normalEntry, block.near_end, stream);
const auto inst_count = m_disassembler->Disassemble(block.normalEntry, furthest_exit, stream);
fmt::println(stream, "{}\tmultibyte nop", fmt::ptr(furthest_exit));
return inst_count + 1;
}
std::size_t Jit64::DisassembleFarCode(const JitBlock& block, std::ostream& stream) const
{
return m_disassembler->Disassemble(block.far_begin, block.far_end, stream);
}
BitSet8 Jit64::ComputeStaticGQRs(const PPCAnalyst::CodeBlock& cb) const BitSet8 Jit64::ComputeStaticGQRs(const PPCAnalyst::CodeBlock& cb) const
{ {
return cb.m_gqr_used & ~cb.m_gqr_modified; return cb.m_gqr_used & ~cb.m_gqr_modified;

View File

@ -34,6 +34,7 @@
#include "Core/PowerPC/JitCommon/JitBase.h" #include "Core/PowerPC/JitCommon/JitBase.h"
#include "Core/PowerPC/JitCommon/JitCache.h" #include "Core/PowerPC/JitCommon/JitCache.h"
class HostDisassembler;
namespace PPCAnalyst namespace PPCAnalyst
{ {
struct CodeBlock; struct CodeBlock;
@ -68,6 +69,9 @@ public:
void EraseSingleBlock(const JitBlock& block) override; void EraseSingleBlock(const JitBlock& block) override;
std::vector<MemoryStats> GetMemoryStats() const override; std::vector<MemoryStats> GetMemoryStats() const override;
std::size_t DisassembleNearCode(const JitBlock& block, std::ostream& stream) const override;
std::size_t DisassembleFarCode(const JitBlock& block, std::ostream& stream) const override;
// Finds a free memory region and sets the near and far code emitters to point at that region. // Finds a free memory region and sets the near and far code emitters to point at that region.
// Returns false if no free memory region can be found for either of the two. // Returns false if no free memory region can be found for either of the two.
bool SetEmitterStateToFreeCodeRegion(); bool SetEmitterStateToFreeCodeRegion();
@ -288,6 +292,7 @@ private:
const bool m_im_here_debug = false; const bool m_im_here_debug = false;
const bool m_im_here_log = false; const bool m_im_here_log = false;
std::map<u32, int> m_been_here; std::map<u32, int> m_been_here;
std::unique_ptr<HostDisassembler> m_disassembler;
}; };
void LogGeneratedX86(size_t size, const PPCAnalyst::CodeBuffer& code_buffer, const u8* normalEntry, void LogGeneratedX86(size_t size, const PPCAnalyst::CodeBuffer& code_buffer, const u8* normalEntry,

View File

@ -9,6 +9,7 @@
#include "Common/Arm64Emitter.h" #include "Common/Arm64Emitter.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/EnumUtils.h" #include "Common/EnumUtils.h"
#include "Common/HostDisassembler.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Common/MathUtil.h" #include "Common/MathUtil.h"
#include "Common/MsgHandler.h" #include "Common/MsgHandler.h"
@ -22,6 +23,7 @@
#include "Core/HW/GPFifo.h" #include "Core/HW/GPFifo.h"
#include "Core/HW/Memmap.h" #include "Core/HW/Memmap.h"
#include "Core/HW/ProcessorInterface.h" #include "Core/HW/ProcessorInterface.h"
#include "Core/Host.h"
#include "Core/PatchEngine.h" #include "Core/PatchEngine.h"
#include "Core/PowerPC/Interpreter/Interpreter.h" #include "Core/PowerPC/Interpreter/Interpreter.h"
#include "Core/PowerPC/JitArm64/JitArm64_RegCache.h" #include "Core/PowerPC/JitArm64/JitArm64_RegCache.h"
@ -39,7 +41,9 @@ constexpr size_t NEAR_CODE_SIZE = 1024 * 1024 * 64;
constexpr size_t FAR_CODE_SIZE = 1024 * 1024 * 64; constexpr size_t FAR_CODE_SIZE = 1024 * 1024 * 64;
constexpr size_t TOTAL_CODE_SIZE = NEAR_CODE_SIZE * 2 + FAR_CODE_SIZE * 2; constexpr size_t TOTAL_CODE_SIZE = NEAR_CODE_SIZE * 2 + FAR_CODE_SIZE * 2;
JitArm64::JitArm64(Core::System& system) : JitBase(system), m_float_emit(this) JitArm64::JitArm64(Core::System& system)
: JitBase(system), m_float_emit(this),
m_disassembler(HostDisassembler::Factory(HostDisassembler::Platform::aarch64))
{ {
} }
@ -186,6 +190,8 @@ void JitArm64::GenerateAsmAndResetFreeMemoryRanges()
ResetFreeMemoryRanges(routines_near_end - routines_near_start, ResetFreeMemoryRanges(routines_near_end - routines_near_start,
routines_far_end - routines_far_start); routines_far_end - routines_far_start);
Host_JitCacheCleared();
} }
void JitArm64::FreeRanges() void JitArm64::FreeRanges()
@ -1014,7 +1020,7 @@ void JitArm64::Jit(u32 em_address, bool clear_cache_and_retry_on_failure)
b->far_begin = far_start; b->far_begin = far_start;
b->far_end = far_end; b->far_end = far_end;
blocks.FinalizeBlock(*b, jo.enableBlocklink, code_block.m_physical_addresses); blocks.FinalizeBlock(*b, jo.enableBlocklink, code_block, m_code_buffer);
return; return;
} }
} }
@ -1048,6 +1054,16 @@ std::vector<JitBase::MemoryStats> JitArm64::GetMemoryStats() const
{"far_1", m_free_ranges_far_1.get_stats()}}; {"far_1", m_free_ranges_far_1.get_stats()}};
} }
std::size_t JitArm64::DisassembleNearCode(const JitBlock& block, std::ostream& stream) const
{
return m_disassembler->Disassemble(block.normalEntry, block.near_end, stream);
}
std::size_t JitArm64::DisassembleFarCode(const JitBlock& block, std::ostream& stream) const
{
return m_disassembler->Disassemble(block.far_begin, block.far_end, stream);
}
std::optional<size_t> JitArm64::SetEmitterStateToFreeCodeRegion() std::optional<size_t> JitArm64::SetEmitterStateToFreeCodeRegion()
{ {
// Find some large free memory blocks and set code emitters to point at them. If we can't find // Find some large free memory blocks and set code emitters to point at them. If we can't find
@ -1366,7 +1382,6 @@ bool JitArm64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
} }
b->codeSize = static_cast<u32>(GetCodePtr() - b->normalEntry); b->codeSize = static_cast<u32>(GetCodePtr() - b->normalEntry);
b->originalSize = code_block.m_num_instructions;
FlushIcache(); FlushIcache();
m_far_code.FlushIcache(); m_far_code.FlushIcache();

View File

@ -20,6 +20,8 @@
#include "Core/PowerPC/JitCommon/JitBase.h" #include "Core/PowerPC/JitCommon/JitBase.h"
#include "Core/PowerPC/PPCAnalyst.h" #include "Core/PowerPC/PPCAnalyst.h"
class HostDisassembler;
class JitArm64 : public JitBase, public Arm64Gen::ARM64CodeBlock, public CommonAsmRoutinesBase class JitArm64 : public JitBase, public Arm64Gen::ARM64CodeBlock, public CommonAsmRoutinesBase
{ {
public: public:
@ -51,6 +53,9 @@ public:
void EraseSingleBlock(const JitBlock& block) override; void EraseSingleBlock(const JitBlock& block) override;
std::vector<MemoryStats> GetMemoryStats() const override; std::vector<MemoryStats> GetMemoryStats() const override;
std::size_t DisassembleNearCode(const JitBlock& block, std::ostream& stream) const override;
std::size_t DisassembleFarCode(const JitBlock& block, std::ostream& stream) const override;
const char* GetName() const override { return "JITARM64"; } const char* GetName() const override { return "JITARM64"; }
// OPCODES // OPCODES
@ -413,4 +418,6 @@ protected:
HyoutaUtilities::RangeSizeSet<u8*> m_free_ranges_near_1; HyoutaUtilities::RangeSizeSet<u8*> m_free_ranges_near_1;
HyoutaUtilities::RangeSizeSet<u8*> m_free_ranges_far_0; HyoutaUtilities::RangeSizeSet<u8*> m_free_ranges_far_0;
HyoutaUtilities::RangeSizeSet<u8*> m_free_ranges_far_1; HyoutaUtilities::RangeSizeSet<u8*> m_free_ranges_far_1;
std::unique_ptr<HostDisassembler> m_disassembler;
}; };

View File

@ -5,6 +5,7 @@
#include <array> #include <array>
#include <cstddef> #include <cstddef>
#include <iosfwd>
#include <map> #include <map>
#include <string_view> #include <string_view>
#include <unordered_set> #include <unordered_set>
@ -202,6 +203,9 @@ public:
using MemoryStats = std::pair<std::string_view, std::pair<std::size_t, double>>; using MemoryStats = std::pair<std::string_view, std::pair<std::size_t, double>>;
virtual std::vector<MemoryStats> GetMemoryStats() const = 0; virtual std::vector<MemoryStats> GetMemoryStats() const = 0;
virtual std::size_t DisassembleNearCode(const JitBlock& block, std::ostream& stream) const = 0;
virtual std::size_t DisassembleFarCode(const JitBlock& block, std::ostream& stream) const = 0;
virtual const CommonAsmRoutinesBase* GetAsmRoutines() = 0; virtual const CommonAsmRoutinesBase* GetAsmRoutines() = 0;
virtual bool HandleFault(uintptr_t access_address, SContext* ctx) = 0; virtual bool HandleFault(uintptr_t access_address, SContext* ctx) = 0;

View File

@ -8,13 +8,16 @@
#include <cstring> #include <cstring>
#include <functional> #include <functional>
#include <map> #include <map>
#include <ranges>
#include <set> #include <set>
#include <span>
#include <utility> #include <utility>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/JitRegister.h" #include "Common/JitRegister.h"
#include "Core/Config/MainSettings.h" #include "Core/Config/MainSettings.h"
#include "Core/Core.h" #include "Core/Core.h"
#include "Core/Host.h"
#include "Core/PowerPC/JitCommon/JitBase.h" #include "Core/PowerPC/JitCommon/JitBase.h"
#include "Core/PowerPC/MMU.h" #include "Core/PowerPC/MMU.h"
#include "Core/PowerPC/PPCSymbolDB.h" #include "Core/PowerPC/PPCSymbolDB.h"
@ -124,6 +127,7 @@ void JitBaseBlockCache::WipeBlockProfilingData(const Core::CPUThreadGuard&)
if (JitBlock::ProfileData* const profile_data = kv.second.profile_data.get()) if (JitBlock::ProfileData* const profile_data = kv.second.profile_data.get())
*profile_data = {}; *profile_data = {};
} }
Host_JitProfileDataWiped();
} }
JitBlock* JitBaseBlockCache::AllocateBlock(u32 em_address) JitBlock* JitBaseBlockCache::AllocateBlock(u32 em_address)
@ -139,7 +143,8 @@ JitBlock* JitBaseBlockCache::AllocateBlock(u32 em_address)
} }
void JitBaseBlockCache::FinalizeBlock(JitBlock& block, bool block_link, void JitBaseBlockCache::FinalizeBlock(JitBlock& block, bool block_link,
const std::set<u32>& physical_addresses) const PPCAnalyst::CodeBlock& code_block,
const PPCAnalyst::CodeBuffer& code_buffer)
{ {
size_t index = FastLookupIndexForAddress(block.effectiveAddress, block.feature_flags); size_t index = FastLookupIndexForAddress(block.effectiveAddress, block.feature_flags);
if (m_entry_points_ptr) if (m_entry_points_ptr)
@ -153,7 +158,18 @@ void JitBaseBlockCache::FinalizeBlock(JitBlock& block, bool block_link,
} }
block.fast_block_map_index = index; block.fast_block_map_index = index;
block.physical_addresses = physical_addresses; block.physical_addresses = code_block.m_physical_addresses;
block.originalSize = code_block.m_num_instructions;
if (m_jit.IsDebuggingEnabled())
{
// TODO C++23: Can do this all in one statement with `std::vector::assign_range`.
const std::ranges::transform_view original_buffer_transform_view{
std::span{code_buffer.data(), block.originalSize},
[](const PPCAnalyst::CodeOp& op) { return std::make_pair(op.address, op.inst); }};
block.original_buffer.assign(original_buffer_transform_view.begin(),
original_buffer_transform_view.end());
}
for (u32 addr : block.physical_addresses) for (u32 addr : block.physical_addresses)
{ {
@ -175,13 +191,14 @@ void JitBaseBlockCache::FinalizeBlock(JitBlock& block, bool block_link,
if (Common::JitRegister::IsEnabled() && if (Common::JitRegister::IsEnabled() &&
(symbol = m_jit.m_ppc_symbol_db.GetSymbolFromAddr(block.effectiveAddress)) != nullptr) (symbol = m_jit.m_ppc_symbol_db.GetSymbolFromAddr(block.effectiveAddress)) != nullptr)
{ {
Common::JitRegister::Register(block.normalEntry, block.codeSize, "JIT_PPC_{}_{:08x}", Common::JitRegister::Register(block.normalEntry, block.near_end - block.normalEntry,
symbol->function_name.c_str(), block.physicalAddress); "JIT_PPC_{}_{:08x}", symbol->function_name,
block.physicalAddress);
} }
else else
{ {
Common::JitRegister::Register(block.normalEntry, block.codeSize, "JIT_PPC_{:08x}", Common::JitRegister::Register(block.normalEntry, block.near_end - block.normalEntry,
block.physicalAddress); "JIT_PPC_{:08x}", block.physicalAddress);
} }
} }

View File

@ -19,6 +19,7 @@
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Core/HW/Memmap.h" #include "Core/HW/Memmap.h"
#include "Core/PowerPC/Gekko.h" #include "Core/PowerPC/Gekko.h"
#include "Core/PowerPC/PPCAnalyst.h"
class JitBase; class JitBase;
@ -105,6 +106,10 @@ struct JitBlock : public JitBlockData
// This set stores all physical addresses of all occupied instructions. // This set stores all physical addresses of all occupied instructions.
std::set<u32> physical_addresses; std::set<u32> physical_addresses;
// This is only available when debugging is enabled. It is a trimmed-down copy of the
// PPCAnalyst::CodeBuffer used to recompile this block, including repeat instructions.
std::vector<std::pair<u32, UGeckoInstruction>> original_buffer;
std::unique_ptr<ProfileData> profile_data; std::unique_ptr<ProfileData> profile_data;
}; };
@ -162,9 +167,11 @@ public:
JitBlock** GetFastBlockMapFallback(); JitBlock** GetFastBlockMapFallback();
void RunOnBlocks(const Core::CPUThreadGuard& guard, std::function<void(const JitBlock&)> f) const; void RunOnBlocks(const Core::CPUThreadGuard& guard, std::function<void(const JitBlock&)> f) const;
void WipeBlockProfilingData(const Core::CPUThreadGuard& guard); void WipeBlockProfilingData(const Core::CPUThreadGuard& guard);
std::size_t GetBlockCount() const { return block_map.size(); }
JitBlock* AllocateBlock(u32 em_address); JitBlock* AllocateBlock(u32 em_address);
void FinalizeBlock(JitBlock& block, bool block_link, const std::set<u32>& physical_addresses); void FinalizeBlock(JitBlock& block, bool block_link, const PPCAnalyst::CodeBlock& code_block,
const PPCAnalyst::CodeBuffer& code_buffer);
// Look for the block in the slow but accurate way. // Look for the block in the slow but accurate way.
// This function shall be used if FastLookupIndexForAddress() failed. // This function shall be used if FastLookupIndexForAddress() failed.

View File

@ -188,46 +188,18 @@ void JitInterface::WipeBlockProfilingData(const Core::CPUThreadGuard& guard)
m_jit->GetBlockCache()->WipeBlockProfilingData(guard); m_jit->GetBlockCache()->WipeBlockProfilingData(guard);
} }
std::variant<JitInterface::GetHostCodeError, JitInterface::GetHostCodeResult> void JitInterface::RunOnBlocks(const Core::CPUThreadGuard& guard,
JitInterface::GetHostCode(u32 address) const std::function<void(const JitBlock&)> f) const
{ {
if (!m_jit) if (m_jit)
{ m_jit->GetBlockCache()->RunOnBlocks(guard, std::move(f));
return GetHostCodeError::NoJitActive; }
}
auto& ppc_state = m_system.GetPPCState(); std::size_t JitInterface::GetBlockCount() const
JitBlock* block = {
m_jit->GetBlockCache()->GetBlockFromStartAddress(address, ppc_state.feature_flags); if (m_jit)
if (!block) return m_jit->GetBlockCache()->GetBlockCount();
{ return 0;
for (int i = 0; i < 500; i++)
{
block = m_jit->GetBlockCache()->GetBlockFromStartAddress(address - 4 * i,
ppc_state.feature_flags);
if (block)
break;
}
if (block)
{
if (!(block->effectiveAddress <= address &&
block->originalSize + block->effectiveAddress >= address))
block = nullptr;
}
// Do not merge this "if" with the above - block changes inside it.
if (!block)
{
return GetHostCodeError::NoTranslation;
}
}
GetHostCodeResult result;
result.code = block->normalEntry;
result.code_size = block->codeSize;
result.entry_address = block->effectiveAddress;
return result;
} }
bool JitInterface::HandleFault(uintptr_t access_address, SContext* ctx) bool JitInterface::HandleFault(uintptr_t access_address, SContext* ctx)
@ -276,6 +248,20 @@ std::vector<JitBase::MemoryStats> JitInterface::GetMemoryStats() const
return {}; return {};
} }
std::size_t JitInterface::DisassembleNearCode(const JitBlock& block, std::ostream& stream) const
{
if (m_jit)
return m_jit->DisassembleNearCode(block, stream);
return 0;
}
std::size_t JitInterface::DisassembleFarCode(const JitBlock& block, std::ostream& stream) const
{
if (m_jit)
return m_jit->DisassembleFarCode(block, stream);
return 0;
}
void JitInterface::InvalidateICache(u32 address, u32 size, bool forced) void JitInterface::InvalidateICache(u32 address, u32 size, bool forced)
{ {
if (m_jit) if (m_jit)

View File

@ -6,11 +6,10 @@
#include <cstddef> #include <cstddef>
#include <cstdio> #include <cstdio>
#include <functional> #include <functional>
#include <iosfwd>
#include <memory> #include <memory>
#include <string>
#include <string_view> #include <string_view>
#include <utility> #include <utility>
#include <variant>
#include <vector> #include <vector>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
@ -46,23 +45,11 @@ public:
CPUCoreBase* InitJitCore(PowerPC::CPUCore core); CPUCoreBase* InitJitCore(PowerPC::CPUCore core);
CPUCoreBase* GetCore() const; CPUCoreBase* GetCore() const;
// Debugging
enum class GetHostCodeError
{
NoJitActive,
NoTranslation,
};
struct GetHostCodeResult
{
const u8* code;
u32 code_size;
u32 entry_address;
};
void UpdateMembase(); void UpdateMembase();
void JitBlockLogDump(const Core::CPUThreadGuard& guard, std::FILE* file) const; void JitBlockLogDump(const Core::CPUThreadGuard& guard, std::FILE* file) const;
void WipeBlockProfilingData(const Core::CPUThreadGuard& guard); void WipeBlockProfilingData(const Core::CPUThreadGuard& guard);
std::variant<GetHostCodeError, GetHostCodeResult> GetHostCode(u32 address) const; void RunOnBlocks(const Core::CPUThreadGuard& guard, std::function<void(const JitBlock&)> f) const;
std::size_t GetBlockCount() const;
// Memory Utilities // Memory Utilities
bool HandleFault(uintptr_t access_address, SContext* ctx); bool HandleFault(uintptr_t access_address, SContext* ctx);
@ -85,6 +72,10 @@ public:
using MemoryStats = std::pair<std::string_view, std::pair<std::size_t, double>>; using MemoryStats = std::pair<std::string_view, std::pair<std::size_t, double>>;
std::vector<MemoryStats> GetMemoryStats() const; std::vector<MemoryStats> GetMemoryStats() const;
// Disassemble the recompiled code from a JIT block. Returns the disassembled instruction count.
std::size_t DisassembleNearCode(const JitBlock& block, std::ostream& stream) const;
std::size_t DisassembleFarCode(const JitBlock& block, std::ostream& stream) const;
// If "forced" is true, a recompile is being requested on code that hasn't been modified. // If "forced" is true, a recompile is being requested on code that hasn't been modified.
void InvalidateICache(u32 address, u32 size, bool forced); void InvalidateICache(u32 address, u32 size, bool forced);
void InvalidateICacheLine(u32 address); void InvalidateICacheLine(u32 address);

View File

@ -86,6 +86,14 @@ void Host_UpdateDisasmDialog()
{ {
} }
void Host_JitCacheCleared()
{
}
void Host_JitProfileDataWiped()
{
}
void Host_UpdateMainFrame() void Host_UpdateMainFrame()
{ {
s_update_main_frame_event.Set(); s_update_main_frame_event.Set();

View File

@ -222,6 +222,8 @@ add_executable(dolphin-emu
Debugger/CodeWidget.h Debugger/CodeWidget.h
Debugger/GekkoSyntaxHighlight.cpp Debugger/GekkoSyntaxHighlight.cpp
Debugger/GekkoSyntaxHighlight.h Debugger/GekkoSyntaxHighlight.h
Debugger/JitBlockTableModel.cpp
Debugger/JitBlockTableModel.h
Debugger/JITWidget.cpp Debugger/JITWidget.cpp
Debugger/JITWidget.h Debugger/JITWidget.h
Debugger/MemoryViewWidget.cpp Debugger/MemoryViewWidget.cpp
@ -297,6 +299,7 @@ add_executable(dolphin-emu
QtUtils/BlockUserInputFilter.h QtUtils/BlockUserInputFilter.h
QtUtils/ClearLayoutRecursively.cpp QtUtils/ClearLayoutRecursively.cpp
QtUtils/ClearLayoutRecursively.h QtUtils/ClearLayoutRecursively.h
QtUtils/ClickableStatusBar.h
QtUtils/DolphinFileDialog.cpp QtUtils/DolphinFileDialog.cpp
QtUtils/DolphinFileDialog.h QtUtils/DolphinFileDialog.h
QtUtils/DoubleClickEventFilter.cpp QtUtils/DoubleClickEventFilter.cpp

View File

@ -881,7 +881,7 @@ void CodeViewWidget::OnPPCComparison()
{ {
const u32 addr = GetContextAddress(); const u32 addr = GetContextAddress();
emit RequestPPCComparison(addr); emit RequestPPCComparison(addr, m_system.GetPPCState().msr.IR);
} }
void CodeViewWidget::OnAddFunction() void CodeViewWidget::OnAddFunction()

View File

@ -55,7 +55,7 @@ public:
u32 AddressForRow(int row) const; u32 AddressForRow(int row) const;
signals: signals:
void RequestPPCComparison(u32 addr); void RequestPPCComparison(u32 address, bool translate_address);
void ShowMemory(u32 address); void ShowMemory(u32 address);
void UpdateCodeWidget(); void UpdateCodeWidget();

View File

@ -210,6 +210,11 @@ void CodeWidget::OnBranchWatchDialog()
m_branch_watch_dialog->activateWindow(); m_branch_watch_dialog->activateWindow();
} }
void CodeWidget::OnSetCodeAddress(u32 address)
{
SetAddress(address, CodeViewWidget::SetAddressUpdate::WithDetailedUpdate);
}
void CodeWidget::OnPPCSymbolsChanged() void CodeWidget::OnPPCSymbolsChanged()
{ {
UpdateSymbols(); UpdateSymbols();

View File

@ -43,6 +43,7 @@ public:
void SetPC(); void SetPC();
void OnBranchWatchDialog(); void OnBranchWatchDialog();
void OnSetCodeAddress(u32 address);
void ToggleBreakpoint(); void ToggleBreakpoint();
void AddBreakpoint(); void AddBreakpoint();
void SetAddress(u32 address, CodeViewWidget::SetAddressUpdate update); void SetAddress(u32 address, CodeViewWidget::SetAddressUpdate update);
@ -50,7 +51,7 @@ public:
void Update(); void Update();
void UpdateSymbols(); void UpdateSymbols();
signals: signals:
void RequestPPCComparison(u32 addr); void RequestPPCComparison(u32 address, bool translate_address);
void ShowMemory(u32 address); void ShowMemory(u32 address);
private: private:

View File

@ -3,216 +3,494 @@
#include "DolphinQt/Debugger/JITWidget.h" #include "DolphinQt/Debugger/JITWidget.h"
#include <algorithm>
#include <optional>
#include <ranges>
#include <utility>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLineEdit>
#include <QMenu>
#include <QPlainTextEdit>
#include <QPushButton> #include <QPushButton>
#include <QSignalBlocker>
#include <QSortFilterProxyModel>
#include <QSplitter> #include <QSplitter>
#include <QTableWidget> #include <QTableView>
#include <QTextBrowser>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <fmt/format.h> #include <fmt/format.h>
#include <fmt/ostream.h>
#include "Common/CommonFuncs.h"
#include "Common/GekkoDisassembler.h" #include "Common/GekkoDisassembler.h"
#include "Common/HostDisassembler.h"
#include "Core/Core.h" #include "Core/Core.h"
#include "Core/PowerPC/PPCAnalyst.h" #include "Core/PowerPC/JitCommon/JitCache.h"
#include "Core/PowerPC/JitInterface.h"
#include "Core/PowerPC/MMU.h"
#include "Core/PowerPC/PPCSymbolDB.h"
#include "Core/System.h" #include "Core/System.h"
#include "DolphinQt/Debugger/JitBlockTableModel.h"
#include "DolphinQt/Host.h" #include "DolphinQt/Host.h"
#include "DolphinQt/QtUtils/ClickableStatusBar.h"
#include "DolphinQt/QtUtils/FromStdString.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h"
#include "DolphinQt/Settings.h" #include "DolphinQt/Settings.h"
#include "UICommon/UICommon.h"
JITWidget::JITWidget(QWidget* parent) : QDockWidget(parent) class JitBlockProxyModel final : public QSortFilterProxyModel
{ {
setWindowTitle(tr("JIT Blocks")); friend JITWidget;
setObjectName(QStringLiteral("jitwidget"));
setHidden(!Settings::Instance().IsJITVisible() || !Settings::Instance().IsDebugModeEnabled()); public:
explicit JitBlockProxyModel(QObject* parent = nullptr);
~JitBlockProxyModel() override;
setAllowedAreas(Qt::AllDockWidgetAreas); JitBlockProxyModel(const JitBlockProxyModel&) = delete;
JitBlockProxyModel(JitBlockProxyModel&&) = delete;
JitBlockProxyModel& operator=(const JitBlockProxyModel&) = delete;
JitBlockProxyModel& operator=(JitBlockProxyModel&&) = delete;
auto& settings = Settings::GetQSettings(); bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override;
[[noreturn]] void setSourceModel(QAbstractItemModel* source_model) override;
void setSourceModel(JitBlockTableModel* source_model);
JitBlockTableModel* sourceModel() const;
CreateWidgets(); const JitBlock& GetJitBlock(const QModelIndex& index);
restoreGeometry(settings.value(QStringLiteral("jitwidget/geometry")).toByteArray()); // Always connected slots (external signals)
// macOS: setHidden() needs to be evaluated before setFloating() for proper window presentation void OnSymbolTextChanged(const QString& text);
// according to Settings template <std::optional<u32> JitBlockProxyModel::*member>
setFloating(settings.value(QStringLiteral("jitwidget/floating")).toBool()); void OnAddressTextChanged(const QString& text);
m_table_splitter->restoreState( private:
settings.value(QStringLiteral("jitwidget/tablesplitter")).toByteArray()); std::optional<u32> m_em_address_min, m_em_address_max, m_pm_address_covered;
m_asm_splitter->restoreState( QString m_symbol_name = {};
settings.value(QStringLiteral("jitwidget/asmsplitter")).toByteArray()); };
connect(&Settings::Instance(), &Settings::JITVisibilityChanged, this, const JitBlock& JitBlockProxyModel::GetJitBlock(const QModelIndex& index)
[this](bool visible) { setHidden(!visible); }); {
return sourceModel()->GetJitBlock(mapToSource(index));
connect(&Settings::Instance(), &Settings::DebugModeToggled, this,
[this](bool enabled) { setHidden(!enabled || !Settings::Instance().IsJITVisible()); });
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, &JITWidget::Update);
connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this, &JITWidget::Update);
ConnectWidgets();
#if defined(_M_X86_64)
m_disassembler = GetNewDisassembler("x86");
#elif defined(_M_ARM_64)
m_disassembler = GetNewDisassembler("aarch64");
#else
m_disassembler = GetNewDisassembler("UNK");
#endif
} }
JITWidget::~JITWidget() void JitBlockProxyModel::OnSymbolTextChanged(const QString& text)
{
m_symbol_name = text;
invalidateRowsFilter();
}
template <std::optional<u32> JitBlockProxyModel::*member>
void JitBlockProxyModel::OnAddressTextChanged(const QString& text)
{
bool ok = false;
if (const u32 value = text.toUInt(&ok, 16); ok)
this->*member = value;
else
this->*member = std::nullopt;
invalidateRowsFilter();
}
bool JitBlockProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
{
if (source_parent.isValid()) [[unlikely]]
return false;
if (!m_symbol_name.isEmpty())
{
if (const QVariant& symbol_name_v = *sourceModel()->GetSymbolList()[source_row];
!symbol_name_v.isValid() || !static_cast<const QString*>(symbol_name_v.data())
->contains(m_symbol_name, Qt::CaseInsensitive))
{
return false;
}
}
const JitBlock& block = sourceModel()->GetJitBlockRefs()[source_row];
if (m_em_address_min.has_value())
{
if (block.effectiveAddress < m_em_address_min.value())
return false;
}
if (m_em_address_max.has_value())
{
if (block.effectiveAddress > m_em_address_max.value())
return false;
}
if (m_pm_address_covered.has_value())
{
if (!block.physical_addresses.contains(m_pm_address_covered.value()))
return false;
}
return true;
}
// Virtual setSourceModel is forbidden for type-safety reasons.
void JitBlockProxyModel::setSourceModel(QAbstractItemModel* source_model)
{
Crash();
}
void JitBlockProxyModel::setSourceModel(JitBlockTableModel* source_model)
{
QSortFilterProxyModel::setSourceModel(source_model);
}
JitBlockTableModel* JitBlockProxyModel::sourceModel() const
{
return static_cast<JitBlockTableModel*>(QSortFilterProxyModel::sourceModel());
}
JitBlockProxyModel::JitBlockProxyModel(QObject* parent) : QSortFilterProxyModel(parent)
{
}
JitBlockProxyModel::~JitBlockProxyModel() = default;
void JITWidget::UpdateProfilingButton()
{
const QSignalBlocker blocker(m_toggle_profiling_button);
const bool enabled = Config::Get(Config::MAIN_DEBUG_JIT_ENABLE_PROFILING);
m_toggle_profiling_button->setText(enabled ? tr("Stop Profiling") : tr("Start Profiling"));
m_toggle_profiling_button->setChecked(enabled);
}
void JITWidget::UpdateOtherButtons(Core::State state)
{
const bool jit_exists = m_system.GetJitInterface().GetCore() != nullptr;
m_clear_cache_button->setEnabled(jit_exists);
m_wipe_profiling_button->setEnabled(jit_exists);
}
void JITWidget::UpdateDebugFont(const QFont& font)
{
m_table_view->setFont(font);
m_ppc_asm_widget->setFont(font);
m_host_near_asm_widget->setFont(font);
m_host_far_asm_widget->setFont(font);
}
void JITWidget::ClearDisassembly()
{
m_ppc_asm_widget->clear();
m_host_near_asm_widget->clear();
m_host_far_asm_widget->clear();
m_status_bar->clearMessage();
}
void JITWidget::ShowFreeMemoryStatus()
{
const std::vector memory_stats = m_system.GetJitInterface().GetMemoryStats();
QString message = tr("Free memory:");
for (const auto& [name, stats] : memory_stats)
{
const auto& [free_size, fragmentation_ratio] = stats;
// i18n: Of each memory region, %1 is its remaining size displayed in an appropriate scale
// of bytes (e.g. MiB), %2 is its untranslated name, and %3 is its fragmentation percentage.
message.append(tr(" %1 %2 (%3% fragmented)")
.arg(QString::fromStdString(UICommon::FormatSize(free_size, 2)))
.arg(QtUtils::FromStdString(name))
.arg(fragmentation_ratio * 100.0, 0, 'f', 2));
}
m_status_bar->showMessage(message);
}
void JITWidget::UpdateContent(Core::State state)
{
ClearDisassembly();
if (state == Core::State::Paused)
ShowFreeMemoryStatus();
}
static void DisassembleCodeBuffer(const JitBlock& block, PPCSymbolDB& ppc_symbol_db,
std::ostream& stream)
{
// Instructions are 4 byte aligned, so next_address = 1 will never produce a false-negative.
for (u32 next_address = 1; const auto& [address, inst] : block.original_buffer)
{
if (address != next_address)
{
stream << ppc_symbol_db.GetDescription(address) << '\n';
next_address = address;
}
fmt::print(stream, "0x{:08x}\t{}\n", address,
Common::GekkoDisassembler::Disassemble(inst.hex, address));
next_address += sizeof(UGeckoInstruction);
}
}
void JITWidget::CrossDisassemble(const JitBlock& block)
{
// TODO C++20: std::ostringstream::view() + QtUtils::FromStdString + std::ostream::seekp(0) would
// save a lot of wasted allocation here, but compiler support for the first thing isn't here yet.
std::ostringstream stream;
DisassembleCodeBuffer(block, m_system.GetPPCSymbolDB(), stream);
m_ppc_asm_widget->setPlainText(QString::fromStdString(std::move(stream).str()));
auto& jit_interface = m_system.GetJitInterface();
const auto host_near_instruction_count = jit_interface.DisassembleNearCode(block, stream);
m_host_near_asm_widget->setPlainText(QString::fromStdString(std::move(stream).str()));
const auto host_far_instruction_count = jit_interface.DisassembleFarCode(block, stream);
m_host_far_asm_widget->setPlainText(QString::fromStdString(std::move(stream).str()));
// i18n: "near" and "far" refer to the near code cache and far code cache of Dolphin's JITs.
// %1 and %2 are instruction counts from the near and far code caches, respectively. %3 is a
// percentage calculated from how inefficient (in other words, "blown-up") a given JIT block's
// recompilation was when considering the host instruction count vs the PPC instruction count.
m_status_bar->showMessage(tr("Host instruction count: %1 near %2 far (%3% blowup)")
.arg(host_near_instruction_count)
.arg(host_far_instruction_count)
.arg(static_cast<double>(100 * (host_near_instruction_count +
host_far_instruction_count)) /
block.originalSize -
100.0,
0, 'f', 2));
}
void JITWidget::CrossDisassemble(const QModelIndex& index)
{
if (index.isValid())
{
CrossDisassemble(m_table_proxy->GetJitBlock(index));
return;
}
UpdateContent(Core::GetState(m_system));
}
void JITWidget::CrossDisassemble()
{
CrossDisassemble(m_table_view->currentIndex());
}
void JITWidget::TableEraseBlocks()
{
auto* const selection_model = m_table_view->selectionModel();
QModelIndexList index_list = selection_model->selectedRows();
selection_model->clear(); // Side effect: currentChanged will be emitted (this is intended).
std::ranges::transform(index_list, index_list.begin(), [this](const QModelIndex& index) {
return m_table_proxy->mapToSource(index);
});
std::ranges::sort(index_list, std::less{}); // QModelIndex is incompatible with std::ranges::less
for (const QModelIndex& index : std::ranges::reverse_view{index_list})
{
if (!index.isValid())
continue;
m_table_model->removeRow(index.row());
}
}
void JITWidget::LoadQSettings()
{
auto& settings = Settings::GetQSettings();
restoreGeometry(settings.value(QStringLiteral("jitwidget/geometry")).toByteArray());
setHidden(!Settings::Instance().IsJITVisible() || !Settings::Instance().IsDebugModeEnabled());
// macOS: setFloating() needs to be after setHidden() for proper window presentation
// according to Settings
setFloating(settings.value(QStringLiteral("jitwidget/floating")).toBool());
m_table_view->horizontalHeader()->restoreState(
settings.value(QStringLiteral("jitwidget/tableheader/state")).toByteArray());
m_table_splitter->restoreState(
settings.value(QStringLiteral("jitwidget/tablesplitter")).toByteArray());
m_disasm_splitter->restoreState(
settings.value(QStringLiteral("jitwidget/disasmsplitter")).toByteArray());
}
void JITWidget::SaveQSettings() const
{ {
auto& settings = Settings::GetQSettings(); auto& settings = Settings::GetQSettings();
settings.setValue(QStringLiteral("jitwidget/geometry"), saveGeometry()); settings.setValue(QStringLiteral("jitwidget/geometry"), saveGeometry());
settings.setValue(QStringLiteral("jitwidget/floating"), isFloating()); settings.setValue(QStringLiteral("jitwidget/floating"), isFloating());
settings.setValue(QStringLiteral("jitwidget/tableheader/state"),
m_table_view->horizontalHeader()->saveState());
settings.setValue(QStringLiteral("jitwidget/tablesplitter"), m_table_splitter->saveState()); settings.setValue(QStringLiteral("jitwidget/tablesplitter"), m_table_splitter->saveState());
settings.setValue(QStringLiteral("jitwidget/asmsplitter"), m_asm_splitter->saveState()); settings.setValue(QStringLiteral("jitwidget/disasmsplitter"), m_disasm_splitter->saveState());
} }
void JITWidget::CreateWidgets() void JITWidget::ConnectSlots()
{ {
m_table_widget = new QTableWidget; auto* const host = Host::GetInstance();
connect(host, &Host::JitCacheCleared, this, &JITWidget::OnJitCacheCleared);
m_table_widget->setTabKeyNavigation(false); connect(host, &Host::UpdateDisasmDialog, this, &JITWidget::OnUpdateDisasmDialog);
m_table_widget->setColumnCount(7); connect(host, &Host::PPCSymbolsChanged, this, &JITWidget::OnPPCSymbolsUpdated);
m_table_widget->setHorizontalHeaderLabels( connect(host, &Host::PPCBreakpointsChanged, this, &JITWidget::OnPPCBreakpointsChanged);
{tr("Address"), tr("PPC Size"), tr("Host Size"), auto* const settings = &Settings::Instance();
// i18n: The symbolic name of a code block connect(settings, &Settings::ConfigChanged, this, &JITWidget::OnConfigChanged);
tr("Symbol"), connect(settings, &Settings::DebugFontChanged, this, &JITWidget::OnDebugFontChanged);
// i18n: These are the kinds of flags that a CPU uses (e.g. carry), connect(settings, &Settings::EmulationStateChanged, this, &JITWidget::OnEmulationStateChanged);
// not the kinds of flags that represent e.g. countries
tr("Flags"),
// i18n: The number of times a code block has been executed
tr("NumExec"),
// i18n: Performance cost, not monetary cost
tr("Cost")});
m_ppc_asm_widget = new QTextBrowser;
m_host_asm_widget = new QTextBrowser;
m_table_splitter = new QSplitter(Qt::Vertical);
m_asm_splitter = new QSplitter(Qt::Horizontal);
m_refresh_button = new QPushButton(tr("Refresh"));
m_table_splitter->addWidget(m_table_widget);
m_table_splitter->addWidget(m_asm_splitter);
m_asm_splitter->addWidget(m_ppc_asm_widget);
m_asm_splitter->addWidget(m_host_asm_widget);
QWidget* widget = new QWidget;
auto* layout = new QVBoxLayout;
layout->setContentsMargins(2, 2, 2, 2);
widget->setLayout(layout);
layout->addWidget(m_table_splitter);
layout->addWidget(m_refresh_button);
setWidget(widget);
} }
void JITWidget::ConnectWidgets() void JITWidget::DisconnectSlots()
{ {
connect(m_refresh_button, &QPushButton::clicked, this, &JITWidget::Update); auto* const host = Host::GetInstance();
disconnect(host, &Host::JitCacheCleared, this, &JITWidget::OnJitCacheCleared);
disconnect(host, &Host::UpdateDisasmDialog, this, &JITWidget::OnUpdateDisasmDialog);
disconnect(host, &Host::PPCSymbolsChanged, this, &JITWidget::OnPPCSymbolsUpdated);
disconnect(host, &Host::PPCBreakpointsChanged, this, &JITWidget::OnPPCBreakpointsChanged);
auto* const settings = &Settings::Instance();
disconnect(settings, &Settings::ConfigChanged, this, &JITWidget::OnConfigChanged);
disconnect(settings, &Settings::DebugFontChanged, this, &JITWidget::OnDebugFontChanged);
disconnect(settings, &Settings::EmulationStateChanged, this, &JITWidget::OnEmulationStateChanged);
} }
void JITWidget::Compare(u32 address) void JITWidget::Show()
{ {
m_address = address; ConnectSlots();
// Handle every slot that may have missed a signal while this widget was hidden.
// OnJitCacheCleared() can be skipped.
// OnUpdateDisasmDialog() can be skipped.
// OnPPCSymbolsUpdated() can be skipped.
// OnPPCBreakpointsChanged() can be skipped.
OnConfigChanged();
OnDebugFontChanged(Settings::Instance().GetDebugFont());
OnEmulationStateChanged(Core::GetState(m_system));
}
void JITWidget::Hide()
{
DisconnectSlots();
ClearDisassembly();
}
void JITWidget::OnRequestPPCComparison(u32 address, bool translate_address)
{
Settings::Instance().SetJITVisible(true); Settings::Instance().SetJITVisible(true);
raise(); raise();
m_host_asm_widget->setFocus();
Update(); if (translate_address)
{
const std::optional<u32> pm_address = m_system.GetMMU().GetTranslatedAddress(address);
if (!pm_address.has_value())
{
ModalMessageBox::warning(
this, tr("Error"),
tr("Effective address %1 has no physical address translation.").arg(address, 0, 16));
return;
}
address = pm_address.value();
}
m_pm_address_covered_line_edit->setText(QString::number(address, 16));
} }
void JITWidget::Update() void JITWidget::OnVisibilityToggled(bool visible)
{ {
if (!isVisible()) setHidden(!visible);
return; }
if (!m_address || (Core::GetState(Core::System::GetInstance()) != Core::State::Paused)) void JITWidget::OnDebugModeToggled(bool enabled)
{
setHidden(!enabled || !Settings::Instance().IsJITVisible());
}
void JITWidget::OnToggleProfiling(bool enabled)
{
Config::SetBaseOrCurrent(Config::MAIN_DEBUG_JIT_ENABLE_PROFILING, enabled);
}
void JITWidget::OnClearCache()
{
m_system.GetJitInterface().ClearCache(Core::CPUThreadGuard{m_system});
}
void JITWidget::OnWipeProfiling()
{
m_system.GetJitInterface().WipeBlockProfilingData(Core::CPUThreadGuard{m_system});
}
void JITWidget::OnTableCurrentChanged(const QModelIndex& current, const QModelIndex& previous)
{
CrossDisassemble(current);
}
void JITWidget::OnTableDoubleClicked(const QModelIndex& index)
{
emit SetCodeAddress(m_table_proxy->GetJitBlock(index).effectiveAddress);
}
void JITWidget::OnTableContextMenu(const QPoint& pos)
{
// There needs to be an option somewhere for a user to recover from hiding every column.
if (m_table_view->horizontalHeader()->hiddenSectionCount() == Column::NumberOfColumns)
{ {
m_ppc_asm_widget->setHtml(QStringLiteral("<i>%1</i>").arg(tr("(ppc)"))); m_column_visibility_menu->exec(m_table_view->viewport()->mapToGlobal(pos));
m_host_asm_widget->setHtml(QStringLiteral("<i>%1</i>").arg(tr("(host)")));
return; return;
} }
m_table_context_menu->exec(m_table_view->viewport()->mapToGlobal(pos));
}
// TODO: Actually do something with the table (Wx doesn't) void JITWidget::OnTableHeaderContextMenu(const QPoint& pos)
{
m_column_visibility_menu->exec(m_table_view->horizontalHeader()->mapToGlobal(pos));
}
// Get host side code disassembly void JITWidget::OnTableMenuViewCode()
auto host_instructions_disasm = DisassembleBlock(m_disassembler.get(), m_address); {
m_address = host_instructions_disasm.entry_address; // TODO: CodeWidget doesn't support it yet, but eventually signal if the address should be
// translated with ((block.feature_flags & CPUEmuFeatureFlags::FEATURE_FLAG_MSR_IR) != 0).
if (const QModelIndex& index = m_table_view->currentIndex(); index.isValid())
emit SetCodeAddress(m_table_proxy->GetJitBlock(index).effectiveAddress);
}
m_host_asm_widget->setHtml( void JITWidget::OnTableMenuEraseBlocks()
QStringLiteral("<pre>%1</pre>").arg(QString::fromStdString(host_instructions_disasm.text))); {
TableEraseBlocks(); // Side effect: currentChanged will be emitted (this is intended).
// Because currentChanged has been emitted, OnTableCurrentChanged has already handled the rest.
}
// == Fill in ppc box void JITWidget::OnStatusBarPressed()
u32 ppc_addr = m_address; {
PPCAnalyst::CodeBuffer code_buffer(32000); if (Core::GetState(m_system) == Core::State::Paused)
PPCAnalyst::BlockStats st; ShowFreeMemoryStatus();
PPCAnalyst::BlockRegStats gpa; }
PPCAnalyst::BlockRegStats fpa;
PPCAnalyst::CodeBlock code_block;
PPCAnalyst::PPCAnalyzer analyzer;
analyzer.SetDebuggingEnabled(Config::IsDebuggingEnabled());
analyzer.SetBranchFollowingEnabled(Config::Get(Config::MAIN_JIT_FOLLOW_BRANCH));
analyzer.SetFloatExceptionsEnabled(Config::Get(Config::MAIN_FLOAT_EXCEPTIONS));
analyzer.SetDivByZeroExceptionsEnabled(Config::Get(Config::MAIN_DIVIDE_BY_ZERO_EXCEPTIONS));
analyzer.SetOption(PPCAnalyst::PPCAnalyzer::OPTION_CONDITIONAL_CONTINUE);
analyzer.SetOption(PPCAnalyst::PPCAnalyzer::OPTION_BRANCH_FOLLOW);
code_block.m_stats = &st; void JITWidget::OnJitCacheCleared()
code_block.m_gpa = &gpa; {
code_block.m_fpa = &fpa; if (Core::GetState(m_system) != Core::State::Paused)
return;
ClearDisassembly();
ShowFreeMemoryStatus();
}
if (analyzer.Analyze(ppc_addr, &code_block, &code_buffer, code_buffer.size()) != 0xFFFFFFFF) void JITWidget::OnUpdateDisasmDialog()
{ {
std::string ppc_disasm_str; if (Core::GetState(m_system) != Core::State::Paused)
auto ppc_disasm = std::back_inserter(ppc_disasm_str); return;
for (u32 i = 0; i < code_block.m_num_instructions; i++) CrossDisassemble();
{ }
const PPCAnalyst::CodeOp& op = code_buffer[i];
const std::string opcode = Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address);
fmt::format_to(ppc_disasm, "{:08x} {}\n", op.address, opcode);
}
// Add stats to the end of the ppc box since it's generally the shortest. void JITWidget::OnPPCSymbolsUpdated()
fmt::format_to(ppc_disasm, "\n{} estimated cycles", st.numCycles); {
fmt::format_to(ppc_disasm, "\nNum instr: PPC: {} Host: {}", code_block.m_num_instructions, if (Core::GetState(m_system) != Core::State::Paused)
host_instructions_disasm.instruction_count); return;
if (code_block.m_num_instructions != 0 && host_instructions_disasm.instruction_count != 0) CrossDisassemble();
{ }
fmt::format_to(
ppc_disasm, " (blowup: {}%)",
100 * host_instructions_disasm.instruction_count / code_block.m_num_instructions - 100);
}
fmt::format_to(ppc_disasm, "\nNum bytes: PPC: {} Host: {}", code_block.m_num_instructions * 4, void JITWidget::OnPPCBreakpointsChanged()
host_instructions_disasm.code_size); {
if (code_block.m_num_instructions != 0 && host_instructions_disasm.code_size != 0) // Whatever row(s) might have been selected could no longer exist, because adding or removing
{ // breakpoints can invalidate JIT blocks. We must clear the selection to avoid stale indices.
fmt::format_to( auto* const selection_model = m_table_view->selectionModel();
ppc_disasm, " (blowup: {}%)", selection_model->clear(); // Side effect: currentChanged will be emitted (this is intended).
100 * host_instructions_disasm.code_size / (4 * code_block.m_num_instructions) - 100); // Because currentChanged has been emitted, OnTableCurrentChanged has already handled the rest.
} }
m_ppc_asm_widget->setHtml( void JITWidget::OnConfigChanged()
QStringLiteral("<pre>%1</pre>").arg(QString::fromStdString(ppc_disasm_str))); {
} UpdateProfilingButton();
else }
{
m_host_asm_widget->setHtml( void JITWidget::OnDebugFontChanged(const QFont& font)
QStringLiteral("<pre>%1</pre>") {
.arg(QString::fromStdString(fmt::format("(non-code address: {:08x})", m_address)))); UpdateDebugFont(font);
m_ppc_asm_widget->setHtml(QStringLiteral("<i>---</i>")); }
}
void JITWidget::OnEmulationStateChanged(Core::State state)
{
UpdateOtherButtons(state);
UpdateContent(state);
} }
void JITWidget::closeEvent(QCloseEvent*) void JITWidget::closeEvent(QCloseEvent*)
@ -220,7 +498,186 @@ void JITWidget::closeEvent(QCloseEvent*)
Settings::Instance().SetJITVisible(false); Settings::Instance().SetJITVisible(false);
} }
void JITWidget::showEvent(QShowEvent* event) void JITWidget::showEvent(QShowEvent*)
{ {
Update(); emit ShowSignal();
Show();
}
void JITWidget::hideEvent(QHideEvent*)
{
emit HideSignal();
Hide();
}
JITWidget::JITWidget(Core::System& system, QWidget* parent) : QDockWidget(parent), m_system(system)
{
setWindowTitle(tr("JIT Blocks"));
setObjectName(QStringLiteral("jitwidget"));
setAllowedAreas(Qt::AllDockWidgetAreas);
auto* const settings = &Settings::Instance();
connect(settings, &Settings::JITVisibilityChanged, this, &JITWidget::OnVisibilityToggled);
connect(settings, &Settings::DebugModeToggled, this, &JITWidget::OnDebugModeToggled);
m_table_view = new QTableView(nullptr);
m_table_proxy = new JitBlockProxyModel(m_table_view);
m_table_model = new JitBlockTableModel(m_system, m_system.GetJitInterface(),
m_system.GetPPCSymbolDB(), m_table_proxy);
connect(this, &JITWidget::HideSignal, m_table_model, &JitBlockTableModel::OnHideSignal);
connect(this, &JITWidget::ShowSignal, m_table_model, &JitBlockTableModel::OnShowSignal);
m_table_proxy->setSourceModel(m_table_model);
m_table_proxy->setSortRole(UserRole::SortRole);
m_table_proxy->setSortCaseSensitivity(Qt::CaseInsensitive);
m_table_view->setModel(m_table_proxy);
m_table_view->setSortingEnabled(true);
m_table_view->sortByColumn(Column::EffectiveAddress, Qt::AscendingOrder);
m_table_view->setSelectionMode(QAbstractItemView::ExtendedSelection);
m_table_view->setSelectionBehavior(QAbstractItemView::SelectRows);
m_table_view->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_table_view->setContextMenuPolicy(Qt::CustomContextMenu);
m_table_view->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_table_view->setCornerButtonEnabled(false);
m_table_view->verticalHeader()->hide();
connect(m_table_view, &QTableView::doubleClicked, this, &JITWidget::OnTableDoubleClicked);
connect(m_table_view, &QTableView::customContextMenuRequested, this,
&JITWidget::OnTableContextMenu);
auto* const horizontal_header = m_table_view->horizontalHeader();
horizontal_header->setContextMenuPolicy(Qt::CustomContextMenu);
horizontal_header->setStretchLastSection(true);
horizontal_header->setSectionsMovable(true);
horizontal_header->setFirstSectionMovable(true);
connect(horizontal_header, &QHeaderView::sortIndicatorChanged, m_table_model,
&JitBlockTableModel::OnSortIndicatorChanged);
connect(horizontal_header, &QHeaderView::customContextMenuRequested, this,
&JITWidget::OnTableHeaderContextMenu);
auto* const selection_model = m_table_view->selectionModel();
connect(selection_model, &QItemSelectionModel::currentChanged, this,
&JITWidget::OnTableCurrentChanged);
auto* const controls_layout = new QHBoxLayout(nullptr);
const auto address_filter_routine = [&](QLineEdit* line_edit, const QString& placeholder_text,
void (JitBlockProxyModel::*slot)(const QString&)) {
line_edit->setPlaceholderText(placeholder_text);
connect(line_edit, &QLineEdit::textChanged, m_table_proxy, slot);
controls_layout->addWidget(line_edit);
};
address_filter_routine(
new QLineEdit(nullptr), tr("Min Effective Address"),
&JitBlockProxyModel::OnAddressTextChanged<&JitBlockProxyModel::m_em_address_min>);
address_filter_routine(
new QLineEdit(nullptr), tr("Max Effective Address"),
&JitBlockProxyModel::OnAddressTextChanged<&JitBlockProxyModel::m_em_address_max>);
address_filter_routine(
m_pm_address_covered_line_edit = new QLineEdit(nullptr), tr("Recompiles Physical Address"),
&JitBlockProxyModel::OnAddressTextChanged<&JitBlockProxyModel::m_pm_address_covered>);
auto* const symbol_name_line_edit = new QLineEdit(nullptr);
symbol_name_line_edit->setPlaceholderText(tr("Symbol Name"));
connect(symbol_name_line_edit, &QLineEdit::textChanged, m_table_model,
&JitBlockTableModel::OnFilterSymbolTextChanged);
connect(symbol_name_line_edit, &QLineEdit::textChanged, m_table_proxy,
&JitBlockProxyModel::OnSymbolTextChanged);
controls_layout->addWidget(symbol_name_line_edit);
m_toggle_profiling_button = new QPushButton(nullptr);
m_toggle_profiling_button->setToolTip(
tr("Toggle software JIT block profiling (will clear the JIT cache)."));
m_toggle_profiling_button->setCheckable(true);
connect(m_toggle_profiling_button, &QPushButton::toggled, this, &JITWidget::OnToggleProfiling);
controls_layout->addWidget(m_toggle_profiling_button);
m_clear_cache_button = new QPushButton(tr("Clear Cache"), nullptr);
connect(m_clear_cache_button, &QPushButton::clicked, this, &JITWidget::OnClearCache);
controls_layout->addWidget(m_clear_cache_button);
m_wipe_profiling_button = new QPushButton(tr("Wipe Profiling"), nullptr);
m_wipe_profiling_button->setToolTip(tr("Re-initialize software JIT block profiling data."));
connect(m_wipe_profiling_button, &QPushButton::clicked, this, &JITWidget::OnWipeProfiling);
controls_layout->addWidget(m_wipe_profiling_button);
m_disasm_splitter = new QSplitter(Qt::Horizontal, nullptr);
const auto text_box_routine = [&](QPlainTextEdit* text_edit, const QString& placeholder_text) {
text_edit->setWordWrapMode(QTextOption::NoWrap);
text_edit->setPlaceholderText(placeholder_text);
text_edit->setReadOnly(true);
m_disasm_splitter->addWidget(text_edit);
};
text_box_routine(m_ppc_asm_widget = new QPlainTextEdit(nullptr), tr("PPC Instruction Coverage"));
text_box_routine(m_host_near_asm_widget = new QPlainTextEdit(nullptr),
tr("Host Near Code Cache"));
text_box_routine(m_host_far_asm_widget = new QPlainTextEdit(nullptr), tr("Host Far Code Cache"));
m_table_splitter = new QSplitter(Qt::Vertical, nullptr);
m_table_splitter->addWidget(m_table_view);
m_table_splitter->addWidget(m_disasm_splitter);
m_status_bar = new ClickableStatusBar(nullptr);
m_status_bar->setSizeGripEnabled(false);
connect(m_status_bar, &ClickableStatusBar::pressed, this, &JITWidget::OnStatusBarPressed);
m_table_context_menu = new QMenu(this);
m_table_context_menu->addAction(tr("View &Code"), this, &JITWidget::OnTableMenuViewCode);
m_table_context_menu->addAction(tr("&Erase Block(s)"), this, &JITWidget::OnTableMenuEraseBlocks);
LoadQSettings();
m_column_visibility_menu = new QMenu(this);
// These table header display names have abbreviated counterparts in JitBlockTableModel.cpp
static constexpr std::array<const char*, Column::NumberOfColumns> headers = {
QT_TR_NOOP("PPC Feature Flags"),
// i18n: "Effective" means this memory address might be translated within the MMU.
QT_TR_NOOP("Effective Address"),
QT_TR_NOOP("Code Buffer Size"),
// i18n: This means to say it is a count of PPC instructions recompiled more than once.
QT_TR_NOOP("Repeat Instructions"),
// i18n: "Near Code" refers to the near code cache of Dolphin's JITs.
QT_TR_NOOP("Host Near Code Size"),
// i18n: "Far Code" refers to the far code cache of Dolphin's JITs.
QT_TR_NOOP("Host Far Code Size"),
QT_TR_NOOP("Run Count"),
// i18n: "Cycles" means instruction cycles.
QT_TR_NOOP("Cycles Spent"),
// i18n: "Cycles" means instruction cycles.
QT_TR_NOOP("Cycles Average"),
// i18n: "Cycles" means instruction cycles.
QT_TR_NOOP("Cycles Percent"),
// i18n: "ns" is an abbreviation of nanoseconds.
QT_TR_NOOP("Time Spent (ns)"),
// i18n: "ns" is an abbreviation of nanoseconds.
QT_TR_NOOP("Time Average (ns)"),
QT_TR_NOOP("Time Percent"),
// i18n: "Symbol" means debugging symbol (its name in particular).
QT_TR_NOOP("Symbol"),
};
for (int column = 0; column < Column::NumberOfColumns; ++column)
{
auto* const action =
m_column_visibility_menu->addAction(tr(headers[column]), [this, column](bool enabled) {
m_table_view->setColumnHidden(column, !enabled);
});
action->setChecked(!m_table_view->isColumnHidden(column));
action->setCheckable(true);
}
auto* const main_layout = new QVBoxLayout(nullptr);
main_layout->setContentsMargins(2, 2, 2, 2);
main_layout->setSpacing(0);
main_layout->addLayout(controls_layout);
main_layout->addWidget(m_table_splitter);
main_layout->addWidget(m_status_bar);
auto* const main_widget = new QWidget(nullptr);
main_widget->setLayout(main_layout);
setWidget(main_widget);
}
JITWidget::~JITWidget()
{
SaveQSettings();
} }

View File

@ -4,42 +4,128 @@
#pragma once #pragma once
#include <QDockWidget> #include <QDockWidget>
#include <memory>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
class BreakpointWidget;
class ClickableStatusBar;
class CodeWidget;
namespace Core
{
enum class State;
class System;
} // namespace Core
struct JitBlock;
class JitBlockProxyModel;
class JitBlockTableModel;
namespace JitBlockTableModelColumn
{
enum EnumType : int;
}
namespace JitBlockTableModelUserRole
{
enum EnumType : int;
}
class MemoryWidget;
class QCloseEvent; class QCloseEvent;
class QFont;
class QLineEdit;
class QMenu;
class QPlainTextEdit;
class QPushButton;
class QShowEvent; class QShowEvent;
class QSplitter; class QSplitter;
class QTextBrowser; class QTableView;
class QTableWidget;
class QPushButton;
class HostDisassembler;
class JITWidget : public QDockWidget class JITWidget final : public QDockWidget
{ {
Q_OBJECT Q_OBJECT
public:
explicit JITWidget(QWidget* parent = nullptr);
~JITWidget();
void Compare(u32 address); using Column = JitBlockTableModelColumn::EnumType;
using UserRole = JitBlockTableModelUserRole::EnumType;
signals:
void HideSignal();
void ShowSignal();
void SetCodeAddress(u32 address);
public:
explicit JITWidget(Core::System& system, QWidget* parent = nullptr);
~JITWidget() override;
JITWidget(const JITWidget&) = delete;
JITWidget(JITWidget&&) = delete;
JITWidget& operator=(const JITWidget&) = delete;
JITWidget& operator=(JITWidget&&) = delete;
// Always connected slots (external signals)
void OnRequestPPCComparison(u32 address, bool translate_address);
private: private:
void Update(); void closeEvent(QCloseEvent* event) override;
void CreateWidgets();
void ConnectWidgets();
void closeEvent(QCloseEvent*) override;
void showEvent(QShowEvent* event) override; void showEvent(QShowEvent* event) override;
void hideEvent(QHideEvent* event) override;
QTableWidget* m_table_widget; void UpdateProfilingButton();
QTextBrowser* m_ppc_asm_widget; void UpdateOtherButtons(Core::State state);
QTextBrowser* m_host_asm_widget; void UpdateDebugFont(const QFont& font);
void ClearDisassembly();
void ShowFreeMemoryStatus();
void UpdateContent(Core::State state);
void CrossDisassemble(const JitBlock& block);
void CrossDisassemble(const QModelIndex& index);
void CrossDisassemble();
void TableEraseBlocks();
// Setup and teardown
void LoadQSettings();
void SaveQSettings() const;
void ConnectSlots();
void DisconnectSlots();
void Show();
void Hide();
// Always connected slots (external signals)
void OnVisibilityToggled(bool visible);
void OnDebugModeToggled(bool visible);
// Always connected slots (internal signals)
void OnToggleProfiling(bool enabled);
void OnClearCache();
void OnWipeProfiling();
void OnTableCurrentChanged(const QModelIndex& current, const QModelIndex& previous);
void OnTableDoubleClicked(const QModelIndex& index);
void OnTableContextMenu(const QPoint& pos);
void OnTableHeaderContextMenu(const QPoint& pos);
void OnTableMenuViewCode();
void OnTableMenuEraseBlocks();
void OnStatusBarPressed();
// Conditionally connected slots (external signals)
void OnJitCacheCleared();
void OnUpdateDisasmDialog();
void OnPPCSymbolsUpdated();
void OnPPCBreakpointsChanged();
void OnConfigChanged();
void OnDebugFontChanged(const QFont& font);
void OnEmulationStateChanged(Core::State state);
Core::System& m_system;
QLineEdit* m_pm_address_covered_line_edit;
QPushButton* m_clear_cache_button;
QPushButton* m_toggle_profiling_button;
QPushButton* m_wipe_profiling_button;
QTableView* m_table_view;
JitBlockProxyModel* m_table_proxy;
JitBlockTableModel* m_table_model;
QPlainTextEdit* m_ppc_asm_widget;
QPlainTextEdit* m_host_near_asm_widget;
QPlainTextEdit* m_host_far_asm_widget;
QSplitter* m_table_splitter; QSplitter* m_table_splitter;
QSplitter* m_asm_splitter; QSplitter* m_disasm_splitter;
QPushButton* m_refresh_button; ClickableStatusBar* m_status_bar;
std::unique_ptr<HostDisassembler> m_disassembler; QMenu* m_table_context_menu;
u32 m_address = 0; QMenu* m_column_visibility_menu;
}; };

View File

@ -0,0 +1,452 @@
// Copyright 2024 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/Debugger/JitBlockTableModel.h"
#include <array>
#include <span>
#include "Common/Assert.h"
#include "Common/Unreachable.h"
#include "Core/Core.h"
#include "Core/PowerPC/JitInterface.h"
#include "Core/PowerPC/PPCSymbolDB.h"
#include "DolphinQt/Host.h"
#include "DolphinQt/Settings.h"
const JitBlock& JitBlockTableModel::GetJitBlock(const QModelIndex& index) const
{
ASSERT(index.isValid());
return m_jit_blocks[index.row()];
}
void JitBlockTableModel::SumOverallCosts()
{
m_overall_cycles_spent = 0;
m_overall_time_spent = {};
for (const JitBlock& block : m_jit_blocks)
{
if (block.profile_data == nullptr)
continue;
m_overall_cycles_spent += block.profile_data->cycles_spent;
m_overall_time_spent += block.profile_data->time_spent;
};
}
static QVariant GetSymbolNameQVariant(const Common::Symbol* symbol)
{
return symbol ? QString::fromStdString(symbol->name) : QVariant{};
}
void JitBlockTableModel::PrefetchSymbols()
{
m_symbol_list.clear();
m_symbol_list.reserve(m_jit_blocks.size());
// If the table viewing this model will be accessing every element,
// it would be a waste of effort to lazy-initialize the symbol list.
if (m_sorting_by_symbols || m_filtering_by_symbols)
{
for (const JitBlock& block : m_jit_blocks)
{
m_symbol_list.emplace_back(
GetSymbolNameQVariant(m_ppc_symbol_db.GetSymbolFromAddr(block.effectiveAddress)));
}
}
else
{
for (const JitBlock& block : m_jit_blocks)
{
m_symbol_list.emplace_back([this, &block]() {
return GetSymbolNameQVariant(m_ppc_symbol_db.GetSymbolFromAddr(block.effectiveAddress));
});
}
}
}
void JitBlockTableModel::Clear()
{
emit layoutAboutToBeChanged();
m_jit_blocks.clear();
m_symbol_list.clear();
emit layoutChanged();
}
void JitBlockTableModel::Update(Core::State state)
{
emit layoutAboutToBeChanged();
m_jit_blocks.clear();
if (state == Core::State::Paused)
{
m_jit_blocks.reserve(m_jit_interface.GetBlockCount());
m_jit_interface.RunOnBlocks(Core::CPUThreadGuard{m_system}, [this](const JitBlock& block) {
m_jit_blocks.emplace_back(block);
});
SumOverallCosts();
}
PrefetchSymbols();
emit layoutChanged();
}
void JitBlockTableModel::UpdateProfileData()
{
const int row_count = rowCount();
if (row_count <= 0)
return;
SumOverallCosts();
static const QList<int> roles = {Qt::DisplayRole};
const int last = row_count - 1;
emit dataChanged(createIndex(0, Column::RunCount), createIndex(last, Column::TimePercent), roles);
}
void JitBlockTableModel::UpdateSymbols()
{
const int row_count = rowCount();
if (row_count <= 0)
return;
PrefetchSymbols();
static const QList<int> roles = {Qt::DisplayRole};
const int last = row_count - 1;
emit dataChanged(createIndex(0, Column::Symbol), createIndex(last, Column::Symbol), roles);
}
void JitBlockTableModel::ConnectSlots()
{
auto* const host = Host::GetInstance();
connect(host, &Host::JitCacheCleared, this, &JitBlockTableModel::OnJitCacheCleared);
connect(host, &Host::JitProfileDataWiped, this, &JitBlockTableModel::OnJitProfileDataWiped);
connect(host, &Host::UpdateDisasmDialog, this, &JitBlockTableModel::OnUpdateDisasmDialog);
connect(host, &Host::PPCSymbolsChanged, this, &JitBlockTableModel::OnPPCSymbolsUpdated);
connect(host, &Host::PPCBreakpointsChanged, this, &JitBlockTableModel::OnPPCBreakpointsChanged);
auto* const settings = &Settings::Instance();
connect(settings, &Settings::EmulationStateChanged, this,
&JitBlockTableModel::OnEmulationStateChanged);
}
void JitBlockTableModel::DisconnectSlots()
{
auto* const host = Host::GetInstance();
disconnect(host, &Host::JitCacheCleared, this, &JitBlockTableModel::OnJitCacheCleared);
disconnect(host, &Host::JitProfileDataWiped, this, &JitBlockTableModel::OnJitProfileDataWiped);
disconnect(host, &Host::UpdateDisasmDialog, this, &JitBlockTableModel::OnUpdateDisasmDialog);
disconnect(host, &Host::PPCSymbolsChanged, this, &JitBlockTableModel::OnPPCSymbolsUpdated);
disconnect(host, &Host::PPCBreakpointsChanged, this,
&JitBlockTableModel::OnPPCBreakpointsChanged);
auto* const settings = &Settings::Instance();
disconnect(settings, &Settings::EmulationStateChanged, this,
&JitBlockTableModel::OnEmulationStateChanged);
}
void JitBlockTableModel::Show()
{
ConnectSlots();
// Every slot that may have missed a signal while this model was hidden can be handled by:
Update(Core::GetState(m_system));
}
void JitBlockTableModel::Hide()
{
DisconnectSlots();
Clear();
}
void JitBlockTableModel::OnShowSignal()
{
Show();
}
void JitBlockTableModel::OnHideSignal()
{
Hide();
}
void JitBlockTableModel::OnSortIndicatorChanged(int logicalIndex, Qt::SortOrder)
{
m_sorting_by_symbols = logicalIndex == Column::Symbol;
}
void JitBlockTableModel::OnFilterSymbolTextChanged(const QString& string)
{
m_filtering_by_symbols = !string.isEmpty();
}
void JitBlockTableModel::OnJitCacheCleared()
{
Update(Core::GetState(m_system));
}
void JitBlockTableModel::OnJitProfileDataWiped()
{
UpdateProfileData();
}
void JitBlockTableModel::OnUpdateDisasmDialog()
{
// This should hopefully catch all the little things that lead to stale JitBlock references.
Update(Core::GetState(m_system));
}
void JitBlockTableModel::OnPPCSymbolsUpdated()
{
UpdateSymbols();
}
void JitBlockTableModel::OnPPCBreakpointsChanged()
{
Update(Core::GetState(m_system));
}
void JitBlockTableModel::OnEmulationStateChanged(Core::State state)
{
Update(state);
}
static QString GetQStringDescription(const CPUEmuFeatureFlags flags)
{
static const std::array<QString, (FEATURE_FLAG_END_OF_ENUMERATION - 1) << 1> descriptions = {
QStringLiteral(""), QStringLiteral("DR"),
QStringLiteral("IR"), QStringLiteral("DR|IR"),
QStringLiteral("PERFMON"), QStringLiteral("DR|PERFMON"),
QStringLiteral("IR|PERFMON"), QStringLiteral("DR|IR|PERFMON"),
};
return descriptions[flags];
}
static QVariant GetValidSymbolStringVariant(const QVariant& symbol_name_v)
{
if (symbol_name_v.isValid())
return symbol_name_v;
return QStringLiteral(" --- ");
}
QVariant JitBlockTableModel::DisplayRoleData(const QModelIndex& index) const
{
const int column = index.column();
if (column == Column::Symbol)
return GetValidSymbolStringVariant(*m_symbol_list[index.row()]);
const JitBlock& jit_block = m_jit_blocks[index.row()];
switch (column)
{
case Column::PPCFeatureFlags:
return GetQStringDescription(jit_block.feature_flags);
case Column::EffectiveAddress:
return QString::number(jit_block.effectiveAddress, 16);
case Column::CodeBufferSize:
return QString::number(jit_block.originalSize * sizeof(UGeckoInstruction));
case Column::RepeatInstructions:
return QString::number(jit_block.originalSize - jit_block.physical_addresses.size());
case Column::HostNearCodeSize:
return QString::number(jit_block.near_end - jit_block.near_begin);
case Column::HostFarCodeSize:
return QString::number(jit_block.far_end - jit_block.far_begin);
}
const JitBlock::ProfileData* const profile_data = jit_block.profile_data.get();
if (profile_data == nullptr)
return QStringLiteral(" --- ");
switch (column)
{
case Column::RunCount:
return QString::number(profile_data->run_count);
case Column::CyclesSpent:
return QString::number(profile_data->cycles_spent);
case Column::CyclesAverage:
if (profile_data->run_count == 0)
return QStringLiteral(" --- ");
return QString::number(
static_cast<double>(profile_data->cycles_spent) / profile_data->run_count, 'f', 6);
case Column::CyclesPercent:
if (m_overall_cycles_spent == 0)
return QStringLiteral(" --- ");
return QStringLiteral("%1%").arg(100.0 * profile_data->cycles_spent / m_overall_cycles_spent,
10, 'f', 6);
case Column::TimeSpent:
{
const auto cast_time =
std::chrono::duration_cast<std::chrono::nanoseconds>(profile_data->time_spent);
return QString::number(cast_time.count());
}
case Column::TimeAverage:
{
if (profile_data->run_count == 0)
return QStringLiteral(" --- ");
const auto cast_time = std::chrono::duration_cast<std::chrono::duration<double, std::nano>>(
profile_data->time_spent);
return QString::number(cast_time.count() / profile_data->run_count, 'f', 6);
}
case Column::TimePercent:
{
if (m_overall_time_spent == JitBlock::ProfileData::Clock::duration{})
return QStringLiteral(" --- ");
return QStringLiteral("%1%").arg(
100.0 * profile_data->time_spent.count() / m_overall_time_spent.count(), 10, 'f', 6);
}
}
static_assert(Column::NumberOfColumns == 14);
Common::Unreachable();
}
QVariant JitBlockTableModel::TextAlignmentRoleData(const QModelIndex& index) const
{
switch (index.column())
{
case Column::PPCFeatureFlags:
case Column::EffectiveAddress:
return Qt::AlignCenter;
case Column::CodeBufferSize:
case Column::RepeatInstructions:
case Column::HostNearCodeSize:
case Column::HostFarCodeSize:
case Column::RunCount:
case Column::CyclesSpent:
case Column::CyclesAverage:
case Column::CyclesPercent:
case Column::TimeSpent:
case Column::TimeAverage:
case Column::TimePercent:
return QVariant::fromValue(Qt::AlignRight | Qt::AlignVCenter);
case Column::Symbol:
return QVariant::fromValue(Qt::AlignLeft | Qt::AlignVCenter);
}
static_assert(Column::NumberOfColumns == 14);
Common::Unreachable();
}
QVariant JitBlockTableModel::SortRoleData(const QModelIndex& index) const
{
const int column = index.column();
if (column == Column::Symbol)
return *m_symbol_list[index.row()];
const JitBlock& jit_block = m_jit_blocks[index.row()];
switch (column)
{
case Column::PPCFeatureFlags:
return jit_block.feature_flags;
case Column::EffectiveAddress:
return jit_block.effectiveAddress;
case Column::CodeBufferSize:
return static_cast<qulonglong>(jit_block.originalSize);
case Column::RepeatInstructions:
return static_cast<qulonglong>(jit_block.originalSize - jit_block.physical_addresses.size());
case Column::HostNearCodeSize:
return static_cast<qulonglong>(jit_block.near_end - jit_block.near_begin);
case Column::HostFarCodeSize:
return static_cast<qulonglong>(jit_block.far_end - jit_block.far_begin);
}
const JitBlock::ProfileData* const profile_data = jit_block.profile_data.get();
if (profile_data == nullptr)
return QVariant();
switch (column)
{
case Column::RunCount:
return static_cast<qulonglong>(profile_data->run_count);
case Column::CyclesSpent:
case Column::CyclesPercent:
return static_cast<qulonglong>(profile_data->cycles_spent);
case Column::CyclesAverage:
if (profile_data->run_count == 0)
return QVariant();
return static_cast<double>(profile_data->cycles_spent) / profile_data->run_count;
case Column::TimeSpent:
case Column::TimePercent:
return static_cast<qulonglong>(profile_data->time_spent.count());
case Column::TimeAverage:
if (profile_data->run_count == 0)
return QVariant();
return static_cast<double>(profile_data->time_spent.count()) / profile_data->run_count;
}
static_assert(Column::NumberOfColumns == 14);
Common::Unreachable();
}
QVariant JitBlockTableModel::data(const QModelIndex& index, int role) const
{
switch (role)
{
case Qt::DisplayRole:
return DisplayRoleData(index);
case Qt::TextAlignmentRole:
return TextAlignmentRoleData(index);
case UserRole::SortRole:
return SortRoleData(index);
}
return QVariant();
}
QVariant JitBlockTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
return QVariant();
// These abbreviated table header display names have unabbreviated counterparts in JITWidget.cpp.
static constexpr std::array<const char*, Column::NumberOfColumns> headers = {
// i18n: PPC Feature Flags
QT_TR_NOOP("PPC Feat. Flags"),
// i18n: Effective Address
QT_TR_NOOP("Eff. Address"),
// i18n: Code Buffer Size
QT_TR_NOOP("Code Buff. Size"),
// i18n: Repeat Instructions
QT_TR_NOOP("Repeat Instr."),
// i18n: Host Near Code Size
QT_TR_NOOP("Host N. Size"),
// i18n: Host Far Code Size
QT_TR_NOOP("Host F. Size"),
QT_TR_NOOP("Run Count"),
QT_TR_NOOP("Cycles Spent"),
// i18n: Cycles Average
QT_TR_NOOP("Cycles Avg."),
// i18n: Cycles Percent
QT_TR_NOOP("Cycles %"),
QT_TR_NOOP("Time Spent (ns)"),
// i18n: Time Average (ns)
QT_TR_NOOP("Time Avg. (ns)"),
// i18n: Time Percent
QT_TR_NOOP("Time %"),
QT_TR_NOOP("Symbol"),
};
return tr(headers[section]);
}
int JitBlockTableModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid()) [[unlikely]]
return 0;
return m_jit_blocks.size();
}
int JitBlockTableModel::columnCount(const QModelIndex& parent) const
{
if (parent.isValid()) [[unlikely]]
return 0;
return Column::NumberOfColumns;
}
bool JitBlockTableModel::removeRows(int row, int count, const QModelIndex& parent)
{
if (parent.isValid() || row < 0) [[unlikely]]
return false;
if (count <= 0) [[unlikely]]
return true;
beginRemoveRows(parent, row, row + count - 1); // Last is inclusive in Qt!
for (const JitBlock& block :
std::span{m_jit_blocks.data() + row, static_cast<std::size_t>(count)})
{
m_jit_interface.EraseSingleBlock(block);
}
m_jit_blocks.remove(row, count);
m_symbol_list.remove(row, count);
endRemoveRows();
return true;
}
JitBlockTableModel::JitBlockTableModel(Core::System& system, JitInterface& jit_interface,
PPCSymbolDB& ppc_symbol_db, QObject* parent)
: QAbstractTableModel(parent), m_system(system), m_jit_interface(jit_interface),
m_ppc_symbol_db(ppc_symbol_db)
{
}
JitBlockTableModel::~JitBlockTableModel() = default;

View File

@ -0,0 +1,126 @@
// Copyright 2024 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QAbstractTableModel>
#include <QList>
#include <QVariant>
#include "Common/CommonTypes.h"
#include "Common/Lazy.h"
#include "Core/PowerPC/JitCommon/JitCache.h"
namespace Core
{
enum class State;
class System;
} // namespace Core
class JitInterface;
class PPCSymbolDB;
class QString;
namespace JitBlockTableModelColumn
{
enum EnumType : int
{
PPCFeatureFlags = 0,
EffectiveAddress,
CodeBufferSize,
RepeatInstructions,
HostNearCodeSize,
HostFarCodeSize,
RunCount,
CyclesSpent,
CyclesAverage,
CyclesPercent,
TimeSpent,
TimeAverage,
TimePercent,
Symbol,
NumberOfColumns,
};
}
namespace JitBlockTableModelUserRole
{
enum EnumType : int
{
SortRole = Qt::UserRole,
};
}
class JitBlockTableModel final : public QAbstractTableModel
{
Q_OBJECT
using Column = JitBlockTableModelColumn::EnumType;
using UserRole = JitBlockTableModelUserRole::EnumType;
using JitBlockRefs = QList<std::reference_wrapper<const JitBlock>>;
using SymbolListValueType = Common::Lazy<QVariant>;
using SymbolList = QList<SymbolListValueType>;
public:
explicit JitBlockTableModel(Core::System& system, JitInterface& jit_interface,
PPCSymbolDB& ppc_symbol_db, QObject* parent = nullptr);
~JitBlockTableModel() override;
JitBlockTableModel(const JitBlockTableModel&) = delete;
JitBlockTableModel(JitBlockTableModel&&) = delete;
JitBlockTableModel& operator=(const JitBlockTableModel&) = delete;
JitBlockTableModel& operator=(JitBlockTableModel&&) = delete;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex& parent = QModelIndex{}) const override;
int columnCount(const QModelIndex& parent = QModelIndex{}) const override;
bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex{}) override;
const JitBlock& GetJitBlock(const QModelIndex& index) const;
const JitBlockRefs& GetJitBlockRefs() const { return m_jit_blocks; }
const SymbolList& GetSymbolList() const { return m_symbol_list; }
// Always connected slots (external signals)
void OnShowSignal();
void OnHideSignal();
void OnSortIndicatorChanged(int logicalIndex, Qt::SortOrder order);
void OnFilterSymbolTextChanged(const QString& string);
private:
[[nodiscard]] QVariant DisplayRoleData(const QModelIndex& index) const;
[[nodiscard]] QVariant TextAlignmentRoleData(const QModelIndex& index) const;
[[nodiscard]] QVariant SortRoleData(const QModelIndex& index) const;
void SumOverallCosts();
void PrefetchSymbols();
void Clear();
void Update(Core::State state);
void UpdateProfileData();
void UpdateSymbols();
// Setup and teardown
void ConnectSlots();
void DisconnectSlots();
void Show();
void Hide();
// Conditionally connected slots (external signals)
void OnJitCacheCleared();
void OnJitProfileDataWiped();
void OnUpdateDisasmDialog();
void OnPPCSymbolsUpdated();
void OnPPCBreakpointsChanged();
void OnEmulationStateChanged(Core::State state);
Core::System& m_system;
JitInterface& m_jit_interface;
PPCSymbolDB& m_ppc_symbol_db;
JitBlockRefs m_jit_blocks;
SymbolList m_symbol_list;
u64 m_overall_cycles_spent;
JitBlock::ProfileData::Clock::duration m_overall_time_spent;
bool m_sorting_by_symbols = false;
bool m_filtering_by_symbols = false;
};

View File

@ -145,6 +145,7 @@
<ClCompile Include="Debugger\CodeViewWidget.cpp" /> <ClCompile Include="Debugger\CodeViewWidget.cpp" />
<ClCompile Include="Debugger\CodeWidget.cpp" /> <ClCompile Include="Debugger\CodeWidget.cpp" />
<ClCompile Include="Debugger\GekkoSyntaxHighlight.cpp" /> <ClCompile Include="Debugger\GekkoSyntaxHighlight.cpp" />
<ClCompile Include="Debugger\JitBlockTableModel.cpp" />
<ClCompile Include="Debugger\JITWidget.cpp" /> <ClCompile Include="Debugger\JITWidget.cpp" />
<ClCompile Include="Debugger\MemoryViewWidget.cpp" /> <ClCompile Include="Debugger\MemoryViewWidget.cpp" />
<ClCompile Include="Debugger\MemoryWidget.cpp" /> <ClCompile Include="Debugger\MemoryWidget.cpp" />
@ -249,6 +250,7 @@
<ClInclude Include="GBAHost.h" /> <ClInclude Include="GBAHost.h" />
<ClInclude Include="QtUtils\ActionHelper.h" /> <ClInclude Include="QtUtils\ActionHelper.h" />
<ClInclude Include="QtUtils\ClearLayoutRecursively.h" /> <ClInclude Include="QtUtils\ClearLayoutRecursively.h" />
<QtMoc Include="QtUtils\ClickableStatusBar.h" />
<ClInclude Include="QtUtils\DolphinFileDialog.h" /> <ClInclude Include="QtUtils\DolphinFileDialog.h" />
<ClInclude Include="QtUtils\ImageConverter.h" /> <ClInclude Include="QtUtils\ImageConverter.h" />
<ClInclude Include="QtUtils\ModalMessageBox.h" /> <ClInclude Include="QtUtils\ModalMessageBox.h" />
@ -359,6 +361,7 @@
<QtMoc Include="Debugger\CodeViewWidget.h" /> <QtMoc Include="Debugger\CodeViewWidget.h" />
<QtMoc Include="Debugger\CodeWidget.h" /> <QtMoc Include="Debugger\CodeWidget.h" />
<QtMoc Include="Debugger\GekkoSyntaxHighlight.h" /> <QtMoc Include="Debugger\GekkoSyntaxHighlight.h" />
<QtMoc Include="Debugger\JitBlockTableModel.h" />
<QtMoc Include="Debugger\JITWidget.h" /> <QtMoc Include="Debugger\JITWidget.h" />
<QtMoc Include="Debugger\MemoryViewWidget.h" /> <QtMoc Include="Debugger\MemoryViewWidget.h" />
<QtMoc Include="Debugger\MemoryWidget.h" /> <QtMoc Include="Debugger\MemoryWidget.h" />

View File

@ -256,6 +256,16 @@ void Host_UpdateDisasmDialog()
QueueOnObject(QApplication::instance(), [] { emit Host::GetInstance()->UpdateDisasmDialog(); }); QueueOnObject(QApplication::instance(), [] { emit Host::GetInstance()->UpdateDisasmDialog(); });
} }
void Host_JitCacheCleared()
{
QueueOnObject(QApplication::instance(), [] { emit Host::GetInstance()->JitCacheCleared(); });
}
void Host_JitProfileDataWiped()
{
QueueOnObject(QApplication::instance(), [] { emit Host::GetInstance()->JitProfileDataWiped(); });
}
void Host_PPCSymbolsChanged() void Host_PPCSymbolsChanged()
{ {
QueueOnObject(QApplication::instance(), [] { emit Host::GetInstance()->PPCSymbolsChanged(); }); QueueOnObject(QApplication::instance(), [] { emit Host::GetInstance()->PPCSymbolsChanged(); });

View File

@ -40,6 +40,8 @@ signals:
void RequestStop(); void RequestStop();
void RequestRenderSize(int w, int h); void RequestRenderSize(int w, int h);
void UpdateDisasmDialog(); void UpdateDisasmDialog();
void JitCacheCleared();
void JitProfileDataWiped();
void PPCSymbolsChanged(); void PPCSymbolsChanged();
void PPCBreakpointsChanged(); void PPCBreakpointsChanged();

View File

@ -463,7 +463,7 @@ void MainWindow::CreateComponents()
m_wii_tas_input_windows[i] = new WiiTASInputWindow(nullptr, i); m_wii_tas_input_windows[i] = new WiiTASInputWindow(nullptr, i);
} }
m_jit_widget = new JITWidget(this); m_jit_widget = new JITWidget(Core::System::GetInstance(), this);
m_log_widget = new LogWidget(this); m_log_widget = new LogWidget(this);
m_log_config_widget = new LogConfigWidget(this); m_log_config_widget = new LogConfigWidget(this);
m_memory_widget = new MemoryWidget(Core::System::GetInstance(), this); m_memory_widget = new MemoryWidget(Core::System::GetInstance(), this);
@ -488,6 +488,7 @@ void MainWindow::CreateComponents()
m_code_widget->SetAddress(addr, CodeViewWidget::SetAddressUpdate::WithDetailedUpdate); m_code_widget->SetAddress(addr, CodeViewWidget::SetAddressUpdate::WithDetailedUpdate);
}; };
connect(m_jit_widget, &JITWidget::SetCodeAddress, m_code_widget, &CodeWidget::OnSetCodeAddress);
connect(m_watch_widget, &WatchWidget::RequestMemoryBreakpoint, request_memory_breakpoint); connect(m_watch_widget, &WatchWidget::RequestMemoryBreakpoint, request_memory_breakpoint);
connect(m_watch_widget, &WatchWidget::ShowMemory, m_memory_widget, &MemoryWidget::SetAddress); connect(m_watch_widget, &WatchWidget::ShowMemory, m_memory_widget, &MemoryWidget::SetAddress);
connect(m_register_widget, &RegisterWidget::RequestMemoryBreakpoint, request_memory_breakpoint); connect(m_register_widget, &RegisterWidget::RequestMemoryBreakpoint, request_memory_breakpoint);
@ -500,7 +501,8 @@ void MainWindow::CreateComponents()
connect(m_thread_widget, &ThreadWidget::RequestViewInMemory, request_view_in_memory); connect(m_thread_widget, &ThreadWidget::RequestViewInMemory, request_view_in_memory);
connect(m_thread_widget, &ThreadWidget::RequestViewInCode, request_view_in_code); connect(m_thread_widget, &ThreadWidget::RequestViewInCode, request_view_in_code);
connect(m_code_widget, &CodeWidget::RequestPPCComparison, m_jit_widget, &JITWidget::Compare); connect(m_code_widget, &CodeWidget::RequestPPCComparison, m_jit_widget,
&JITWidget::OnRequestPPCComparison);
connect(m_code_widget, &CodeWidget::ShowMemory, m_memory_widget, &MemoryWidget::SetAddress); connect(m_code_widget, &CodeWidget::ShowMemory, m_memory_widget, &MemoryWidget::SetAddress);
connect(m_memory_widget, &MemoryWidget::ShowCode, m_code_widget, [this](u32 address) { connect(m_memory_widget, &MemoryWidget::ShowCode, m_code_widget, [this](u32 address) {
m_code_widget->SetAddress(address, CodeViewWidget::SetAddressUpdate::WithDetailedUpdate); m_code_widget->SetAddress(address, CodeViewWidget::SetAddressUpdate::WithDetailedUpdate);

View File

@ -13,6 +13,7 @@
#include <QFontDialog> #include <QFontDialog>
#include <QInputDialog> #include <QInputDialog>
#include <QMap> #include <QMap>
#include <QSignalBlocker>
#include <QUrl> #include <QUrl>
#include <fmt/format.h> #include <fmt/format.h>
@ -95,6 +96,7 @@ MenuBar::MenuBar(QWidget* parent) : QMenuBar(parent)
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
[=, this](Core::State state) { OnEmulationStateChanged(state); }); [=, this](Core::State state) { OnEmulationStateChanged(state); });
connect(&Settings::Instance(), &Settings::ConfigChanged, this, &MenuBar::OnConfigChanged);
connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this, connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this,
[this] { OnEmulationStateChanged(Core::GetState(Core::System::GetInstance())); }); [this] { OnEmulationStateChanged(Core::GetState(Core::System::GetInstance())); });
@ -167,6 +169,12 @@ void MenuBar::OnEmulationStateChanged(Core::State state)
OnDebugModeToggled(Settings::Instance().IsDebugModeEnabled()); OnDebugModeToggled(Settings::Instance().IsDebugModeEnabled());
} }
void MenuBar::OnConfigChanged()
{
const QSignalBlocker blocker(m_jit_profile_blocks);
m_jit_profile_blocks->setChecked(Config::Get(Config::MAIN_DEBUG_JIT_ENABLE_PROFILING));
}
void MenuBar::OnDebugModeToggled(bool enabled) void MenuBar::OnDebugModeToggled(bool enabled)
{ {
// Options // Options

View File

@ -127,6 +127,7 @@ signals:
private: private:
void OnEmulationStateChanged(Core::State state); void OnEmulationStateChanged(Core::State state);
void OnConfigChanged();
void AddFileMenu(); void AddFileMenu();

View File

@ -0,0 +1,22 @@
// Copyright 2024 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QStatusBar>
// I wanted a QStatusBar that emits a signal when clicked. Qt only provides event overrides.
class ClickableStatusBar final : public QStatusBar
{
Q_OBJECT
signals:
void pressed();
protected:
void mousePressEvent(QMouseEvent* event) override { emit pressed(); }
public:
explicit ClickableStatusBar(QWidget* parent) : QStatusBar(parent) {}
~ClickableStatusBar() override = default;
};

View File

@ -61,6 +61,14 @@ void Host_UpdateDisasmDialog()
{ {
} }
void Host_JitCacheCleared()
{
}
void Host_JitProfileDataWiped()
{
}
void Host_UpdateMainFrame() void Host_UpdateMainFrame()
{ {
} }

View File

@ -41,6 +41,12 @@ bool Host_UpdateDiscordPresenceRaw(const std::string& details, const std::string
void Host_UpdateDisasmDialog() void Host_UpdateDisasmDialog()
{ {
} }
void Host_JitCacheCleared()
{
}
void Host_JitProfileDataWiped()
{
}
void Host_UpdateMainFrame() void Host_UpdateMainFrame()
{ {
} }

View File

@ -42,6 +42,8 @@ public:
void Jit(u32 em_address) override {} void Jit(u32 em_address) override {}
void EraseSingleBlock(const JitBlock&) override {} void EraseSingleBlock(const JitBlock&) override {}
std::vector<MemoryStats> GetMemoryStats() const override { return {}; } std::vector<MemoryStats> GetMemoryStats() const override { return {}; }
std::size_t DisassembleNearCode(const JitBlock&, std::ostream&) const override { return 0; }
std::size_t DisassembleFarCode(const JitBlock&, std::ostream&) const override { return 0; }
const CommonAsmRoutinesBase* GetAsmRoutines() override { return nullptr; } const CommonAsmRoutinesBase* GetAsmRoutines() override { return nullptr; }
virtual bool HandleFault(uintptr_t access_address, SContext* ctx) override virtual bool HandleFault(uintptr_t access_address, SContext* ctx) override
{ {

View File

@ -41,6 +41,12 @@ bool Host_UpdateDiscordPresenceRaw(const std::string& details, const std::string
void Host_UpdateDisasmDialog() void Host_UpdateDisasmDialog()
{ {
} }
void Host_JitCacheCleared()
{
}
void Host_JitProfileDataWiped()
{
}
void Host_UpdateMainFrame() void Host_UpdateMainFrame()
{ {
} }