summaryrefslogtreecommitdiff
path: root/Source
diff options
context:
space:
mode:
authormitaclaw <140017135+mitaclaw@users.noreply.github.com>2024-04-15 21:52:40 -0700
committermitaclaw <140017135+mitaclaw@users.noreply.github.com>2024-10-19 02:30:44 -0700
commit9afd09598cc81eabd3fcfe296c25ace2a71aca26 (patch)
treedb39aeaef1107294cb65e70c20e59ab2c023298d /Source
parentca9222a16b477869616b3938c374ed7a497802b4 (diff)
DolphinQt: JIT Widget Refresh
Fulfilling a certain six-year-old todo.
Diffstat (limited to 'Source')
-rw-r--r--Source/Android/jni/MainAndroid.cpp8
-rw-r--r--Source/Core/Common/HostDisassembler.cpp198
-rw-r--r--Source/Core/Common/HostDisassembler.h28
-rw-r--r--Source/Core/Core/Host.h2
-rw-r--r--Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.cpp17
-rw-r--r--Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.h3
-rw-r--r--Source/Core/Core/PowerPC/Jit64/Jit.cpp44
-rw-r--r--Source/Core/Core/PowerPC/Jit64/Jit.h5
-rw-r--r--Source/Core/Core/PowerPC/JitArm64/Jit.cpp21
-rw-r--r--Source/Core/Core/PowerPC/JitArm64/Jit.h7
-rw-r--r--Source/Core/Core/PowerPC/JitCommon/JitBase.h4
-rw-r--r--Source/Core/Core/PowerPC/JitCommon/JitCache.cpp29
-rw-r--r--Source/Core/Core/PowerPC/JitCommon/JitCache.h9
-rw-r--r--Source/Core/Core/PowerPC/JitInterface.cpp62
-rw-r--r--Source/Core/Core/PowerPC/JitInterface.h23
-rw-r--r--Source/Core/DolphinNoGUI/MainNoGUI.cpp8
-rw-r--r--Source/Core/DolphinQt/CMakeLists.txt3
-rw-r--r--Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp2
-rw-r--r--Source/Core/DolphinQt/Debugger/CodeViewWidget.h2
-rw-r--r--Source/Core/DolphinQt/Debugger/CodeWidget.cpp5
-rw-r--r--Source/Core/DolphinQt/Debugger/CodeWidget.h3
-rw-r--r--Source/Core/DolphinQt/Debugger/JITWidget.cpp755
-rw-r--r--Source/Core/DolphinQt/Debugger/JITWidget.h128
-rw-r--r--Source/Core/DolphinQt/Debugger/JitBlockTableModel.cpp452
-rw-r--r--Source/Core/DolphinQt/Debugger/JitBlockTableModel.h126
-rw-r--r--Source/Core/DolphinQt/DolphinQt.vcxproj3
-rw-r--r--Source/Core/DolphinQt/Host.cpp10
-rw-r--r--Source/Core/DolphinQt/Host.h2
-rw-r--r--Source/Core/DolphinQt/MainWindow.cpp6
-rw-r--r--Source/Core/DolphinQt/MenuBar.cpp8
-rw-r--r--Source/Core/DolphinQt/MenuBar.h1
-rw-r--r--Source/Core/DolphinQt/QtUtils/ClickableStatusBar.h22
-rw-r--r--Source/Core/DolphinTool/ToolHeadlessPlatform.cpp8
-rw-r--r--Source/DSPTool/StubHost.cpp6
-rw-r--r--Source/UnitTests/Core/PageFaultTest.cpp2
-rw-r--r--Source/UnitTests/StubHost.cpp6
36 files changed, 1634 insertions, 384 deletions
diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp
index f0a74986f6..1d26f1d895 100644
--- a/Source/Android/jni/MainAndroid.cpp
+++ b/Source/Android/jni/MainAndroid.cpp
@@ -146,6 +146,14 @@ void Host_UpdateDisasmDialog()
{
}
+void Host_JitCacheCleared()
+{
+}
+
+void Host_JitProfileDataWiped()
+{
+}
+
void Host_UpdateMainFrame()
{
}
diff --git a/Source/Core/Common/HostDisassembler.cpp b/Source/Core/Common/HostDisassembler.cpp
index d21536ecc4..14dd3b5b46 100644
--- a/Source/Core/Common/HostDisassembler.cpp
+++ b/Source/Core/Common/HostDisassembler.cpp
@@ -3,52 +3,43 @@
#include "Common/HostDisassembler.h"
-#include <sstream>
+#include <span>
-#if defined(HAVE_LLVM)
#include <fmt/format.h>
+#include <fmt/ostream.h>
+#include <fmt/ranges.h>
+
+#if defined(HAVE_LLVM)
#include <llvm-c/Disassembler.h>
#include <llvm-c/Target.h>
#elif defined(_M_X86_64)
#include <disasm.h> // Bochs
#endif
-#include "Common/Assert.h"
-#include "Common/VariantUtil.h"
-#include "Core/PowerPC/JitInterface.h"
-#include "Core/System.h"
-
#if defined(HAVE_LLVM)
-class HostDisassemblerLLVM : public HostDisassembler
+class HostDisassemblerLLVM final : public HostDisassembler
{
public:
- HostDisassemblerLLVM(const std::string& host_disasm, int inst_size = -1,
- const std::string& cpu = "");
- ~HostDisassemblerLLVM()
- {
- if (m_can_disasm)
- LLVMDisasmDispose(m_llvm_context);
- }
+ explicit HostDisassemblerLLVM(const char* host_disasm, const char* cpu = "",
+ std::size_t inst_size = 0);
+ ~HostDisassemblerLLVM();
private:
- bool m_can_disasm;
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,
- u32* host_instructions_count, u64 starting_pc) override;
+ std::size_t Disassemble(const u8* begin, const u8* end, std::ostream& stream) override;
};
-HostDisassemblerLLVM::HostDisassemblerLLVM(const std::string& host_disasm, int inst_size,
- const std::string& cpu)
- : m_can_disasm(false), m_instruction_size(inst_size)
+HostDisassemblerLLVM::HostDisassemblerLLVM(const char* host_disasm, const char* cpu,
+ std::size_t inst_size)
+ : m_instruction_size(inst_size)
{
LLVMInitializeAllTargetInfos();
LLVMInitializeAllTargetMCs();
LLVMInitializeAllDisassemblers();
- m_llvm_context =
- LLVMCreateDisasmCPU(host_disasm.c_str(), cpu.c_str(), nullptr, 0, nullptr, nullptr);
+ m_llvm_context = LLVMCreateDisasmCPU(host_disasm, cpu, nullptr, 0, nullptr, nullptr);
// Couldn't create 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 |
LLVMDisassembler_Option_PrintLatency);
-
- m_can_disasm = true;
}
-std::string HostDisassemblerLLVM::DisassembleHostBlock(const u8* code_start, const u32 code_size,
- u32* host_instructions_count,
- u64 starting_pc)
+HostDisassemblerLLVM::~HostDisassemblerLLVM()
{
- if (!m_can_disasm)
- return "(No LLVM context)";
+ if (m_llvm_context)
+ LLVMDisasmDispose(m_llvm_context);
+}
- u8* disasmPtr = (u8*)code_start;
- const u8* end = code_start + code_size;
+std::size_t HostDisassemblerLLVM::Disassemble(const u8* begin, const u8* end, std::ostream& stream)
+{
+ std::size_t instruction_count = 0;
+ if (!m_llvm_context)
+ return instruction_count;
- std::ostringstream x86_disasm;
- while ((u8*)disasmPtr < end)
+ while (begin < end)
{
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";
- if (!inst_size)
+ const auto 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 != -1)
+ if (m_instruction_size != 0)
{
- // If we are on an architecture that has a fixed instruction size
- // We can continue onward past this bad instruction.
- std::string inst_str;
- for (int i = 0; i < m_instruction_size; ++i)
- inst_str += fmt::format("{:02x}", disasmPtr[i]);
-
- x86_disasm << inst_str << std::endl;
- disasmPtr += m_instruction_size;
+ // If we are on an architecture that has a fixed instruction
+ // size, we can continue onward past this bad instruction.
+ fmt::println(stream, "{}\tInvalid inst: {:02x}", fmt::ptr(begin),
+ fmt::join(std::span{begin, m_instruction_size}, ""));
+ begin += m_instruction_size;
}
else
{
- // We can't continue if we are on an architecture that has flexible instruction sizes
- // Dump the rest of the block instead
- std::string code_block;
- for (int i = 0; (disasmPtr + i) < end; ++i)
- code_block += fmt::format("{:02x}", disasmPtr[i]);
-
- x86_disasm << code_block << std::endl;
+ // We can't continue if we are on an architecture that has flexible
+ // instruction sizes. Dump the rest of the block instead.
+ fmt::println(stream, "{}\tInvalid inst: {:02x}", fmt::ptr(begin),
+ fmt::join(std::span{begin, end}, ""));
break;
}
}
else
{
- x86_disasm << inst_disasm << std::endl;
- disasmPtr += inst_size;
- starting_pc += inst_size;
+ fmt::println(stream, "{}{}", fmt::ptr(begin), inst_disasm);
+ begin += inst_size;
}
-
- (*host_instructions_count)++;
+ ++instruction_count;
}
-
- return x86_disasm.str();
+ return instruction_count;
}
#elif defined(_M_X86_64)
-class HostDisassemblerX86 : public HostDisassembler
+class HostDisassemblerBochs final : public HostDisassembler
{
public:
- HostDisassemblerX86();
+ explicit HostDisassemblerBochs();
+ ~HostDisassemblerBochs() = default;
private:
disassembler m_disasm;
- std::string DisassembleHostBlock(const u8* code_start, const u32 code_size,
- u32* host_instructions_count, u64 starting_pc) override;
+ std::size_t Disassemble(const u8* begin, const u8* end, std::ostream& stream) override;
};
-HostDisassemblerX86::HostDisassemblerX86()
+HostDisassemblerBochs::HostDisassemblerBochs()
{
m_disasm.set_syntax_intel();
}
-std::string HostDisassemblerX86::DisassembleHostBlock(const u8* code_start, const u32 code_size,
- u32* host_instructions_count, u64 starting_pc)
+std::size_t HostDisassemblerBochs::Disassemble(const u8* begin, const u8* end, std::ostream& stream)
{
- u64 disasmPtr = (u64)code_start;
- const u8* end = code_start + code_size;
-
- std::ostringstream x86_disasm;
- while ((u8*)disasmPtr < end)
+ std::size_t instruction_count = 0;
+ while (begin < end)
{
char inst_disasm[256];
- disasmPtr += m_disasm.disasm64(disasmPtr, disasmPtr, (u8*)disasmPtr, inst_disasm);
- x86_disasm << inst_disasm << std::endl;
- (*host_instructions_count)++;
- }
- return x86_disasm.str();
+ const auto inst_size =
+ 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;
}
#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 (arch == "x86")
+ case Platform::x86_64:
return std::make_unique<HostDisassemblerLLVM>("x86_64-none-unknown");
- if (arch == "aarch64")
- return std::make_unique<HostDisassemblerLLVM>("aarch64-none-unknown", 4, "cortex-a57");
- if (arch == "armv7")
- return std::make_unique<HostDisassemblerLLVM>("armv7-none-unknown", 4, "cortex-a15");
+ case Platform::aarch64:
+ return std::make_unique<HostDisassemblerLLVM>("aarch64-none-unknown", "cortex-a57", 4);
#elif defined(_M_X86_64)
- if (arch == "x86")
- return std::make_unique<HostDisassemblerX86>();
+ case Platform::x86_64:
+ return std::make_unique<HostDisassemblerBochs>();
+#else
+ case Platform{}: // warning C4065: "switch statement contains 'default' but no 'case' labels"
#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);
-
- 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);
+ fmt::println(stream, "{}\t{:02x}", fmt::ptr(begin), fmt::join(std::span{begin, end}, ""));
+ return 0;
}
diff --git a/Source/Core/Common/HostDisassembler.h b/Source/Core/Common/HostDisassembler.h
index 6c193c49c0..3761f52937 100644
--- a/Source/Core/Common/HostDisassembler.h
+++ b/Source/Core/Common/HostDisassembler.h
@@ -3,28 +3,24 @@
#pragma once
+#include <cstddef>
+#include <iosfwd>
#include <memory>
-#include <string>
+
#include "Common/CommonTypes.h"
class HostDisassembler
{
public:
- virtual ~HostDisassembler() {}
- virtual std::string DisassembleHostBlock(const u8* code_start, const u32 code_size,
- u32* host_instructions_count, u64 starting_pc)
+ enum class Platform
{
- return "(No disassembler)";
- }
-};
+ x86_64,
+ aarch64,
+ };
-struct DisassembleResult
-{
- std::string text;
- u32 entry_address = 0;
- u32 instruction_count = 0;
- u32 code_size = 0;
-};
+ virtual ~HostDisassembler() = default;
+
+ static std::unique_ptr<HostDisassembler> Factory(Platform arch);
-std::unique_ptr<HostDisassembler> GetNewDisassembler(const std::string& arch);
-DisassembleResult DisassembleBlock(HostDisassembler* disasm, u32 address);
+ virtual std::size_t Disassemble(const u8* begin, const u8* end, std::ostream& stream);
+};
diff --git a/Source/Core/Core/Host.h b/Source/Core/Core/Host.h
index 66a5e2d78e..6d217f2650 100644
--- a/Source/Core/Core/Host.h
+++ b/Source/Core/Core/Host.h
@@ -60,6 +60,8 @@ void Host_PPCSymbolsChanged();
void Host_RefreshDSPDebuggerWindow();
void Host_RequestRenderWindowSize(int width, int height);
void Host_UpdateDisasmDialog();
+void Host_JitCacheCleared();
+void Host_JitProfileDataWiped();
void Host_UpdateMainFrame();
void Host_UpdateTitle(const std::string& title);
void Host_YieldToUI();
diff --git a/Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.cpp b/Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.cpp
index 99b9e30163..a8a9f99ed3 100644
--- a/Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.cpp
+++ b/Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.cpp
@@ -10,6 +10,7 @@
#include "Core/CoreTiming.h"
#include "Core/HLE/HLE.h"
#include "Core/HW/CPU.h"
+#include "Core/Host.h"
#include "Core/PowerPC/Gekko.h"
#include "Core/PowerPC/Interpreter/Interpreter.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->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.
if (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;
}
@@ -413,6 +413,18 @@ std::vector<JitBase::MemoryStats> CachedInterpreter::GetMemoryStats() const
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()
{
m_block_cache.Clear();
@@ -420,4 +432,5 @@ void CachedInterpreter::ClearCache()
ClearCodeSpace();
ResetFreeMemoryRanges();
RefreshConfig();
+ Host_JitCacheCleared();
}
diff --git a/Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.h b/Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.h
index 9f84f5d4ec..4096c7b3a8 100644
--- a/Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.h
+++ b/Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.h
@@ -51,6 +51,9 @@ public:
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; }
const char* GetName() const override { return "Cached Interpreter"; }
const CommonAsmRoutinesBase* GetAsmRoutines() override { return nullptr; }
diff --git a/Source/Core/Core/PowerPC/Jit64/Jit.cpp b/Source/Core/Core/PowerPC/Jit64/Jit.cpp
index 0f408f8bb7..7d26ab76af 100644
--- a/Source/Core/Core/PowerPC/Jit64/Jit.cpp
+++ b/Source/Core/Core/PowerPC/Jit64/Jit.cpp
@@ -9,6 +9,7 @@
#include <disasm.h>
#include <fmt/format.h>
+#include <fmt/ostream.h>
// for the PROFILER stuff
#ifdef _WIN32
@@ -18,6 +19,7 @@
#include "Common/CommonTypes.h"
#include "Common/EnumUtils.h"
#include "Common/GekkoDisassembler.h"
+#include "Common/HostDisassembler.h"
#include "Common/IOFile.h"
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
@@ -30,6 +32,7 @@
#include "Core/HW/GPFifo.h"
#include "Core/HW/Memmap.h"
#include "Core/HW/ProcessorInterface.h"
+#include "Core/Host.h"
#include "Core/MachineContext.h"
#include "Core/PatchEngine.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.
*/
-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();
asm_routines.Regenerate();
ResetFreeMemoryRanges();
+ Host_JitCacheCleared();
}
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_end = far_end;
- blocks.FinalizeBlock(*b, jo.enableBlocklink, code_block.m_physical_addresses);
+ blocks.FinalizeBlock(*b, jo.enableBlocklink, code_block, m_code_buffer);
return;
}
}
@@ -1193,7 +1199,6 @@ bool Jit64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
}
b->codeSize = static_cast<u32>(GetCodePtr() - b->normalEntry);
- b->originalSize = code_block.m_num_instructions;
#ifdef JIT_LOG_GENERATED_CODE
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()}};
}
+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
{
return cb.m_gqr_used & ~cb.m_gqr_modified;
diff --git a/Source/Core/Core/PowerPC/Jit64/Jit.h b/Source/Core/Core/PowerPC/Jit64/Jit.h
index 75fd8a118b..b1969704b3 100644
--- a/Source/Core/Core/PowerPC/Jit64/Jit.h
+++ b/Source/Core/Core/PowerPC/Jit64/Jit.h
@@ -34,6 +34,7 @@
#include "Core/PowerPC/JitCommon/JitBase.h"
#include "Core/PowerPC/JitCommon/JitCache.h"
+class HostDisassembler;
namespace PPCAnalyst
{
struct CodeBlock;
@@ -68,6 +69,9 @@ public:
void EraseSingleBlock(const JitBlock& block) 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.
// Returns false if no free memory region can be found for either of the two.
bool SetEmitterStateToFreeCodeRegion();
@@ -288,6 +292,7 @@ private:
const bool m_im_here_debug = false;
const bool m_im_here_log = false;
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,
diff --git a/Source/Core/Core/PowerPC/JitArm64/Jit.cpp b/Source/Core/Core/PowerPC/JitArm64/Jit.cpp
index ad129ad98f..d2dfbc605b 100644
--- a/Source/Core/Core/PowerPC/JitArm64/Jit.cpp
+++ b/Source/Core/Core/PowerPC/JitArm64/Jit.cpp
@@ -9,6 +9,7 @@
#include "Common/Arm64Emitter.h"
#include "Common/CommonTypes.h"
#include "Common/EnumUtils.h"
+#include "Common/HostDisassembler.h"
#include "Common/Logging/Log.h"
#include "Common/MathUtil.h"
#include "Common/MsgHandler.h"
@@ -22,6 +23,7 @@
#include "Core/HW/GPFifo.h"
#include "Core/HW/Memmap.h"
#include "Core/HW/ProcessorInterface.h"
+#include "Core/Host.h"
#include "Core/PatchEngine.h"
#include "Core/PowerPC/Interpreter/Interpreter.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 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,
routines_far_end - routines_far_start);
+
+ Host_JitCacheCleared();
}
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_end = far_end;
- blocks.FinalizeBlock(*b, jo.enableBlocklink, code_block.m_physical_addresses);
+ blocks.FinalizeBlock(*b, jo.enableBlocklink, code_block, m_code_buffer);
return;
}
}
@@ -1048,6 +1054,16 @@ std::vector<JitBase::MemoryStats> JitArm64::GetMemoryStats() const
{"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()
{
// 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->originalSize = code_block.m_num_instructions;
FlushIcache();
m_far_code.FlushIcache();
diff --git a/Source/Core/Core/PowerPC/JitArm64/Jit.h b/Source/Core/Core/PowerPC/JitArm64/Jit.h
index f73c5f3252..e27392b3e5 100644
--- a/Source/Core/Core/PowerPC/JitArm64/Jit.h
+++ b/Source/Core/Core/PowerPC/JitArm64/Jit.h
@@ -20,6 +20,8 @@
#include "Core/PowerPC/JitCommon/JitBase.h"
#include "Core/PowerPC/PPCAnalyst.h"
+class HostDisassembler;
+
class JitArm64 : public JitBase, public Arm64Gen::ARM64CodeBlock, public CommonAsmRoutinesBase
{
public:
@@ -51,6 +53,9 @@ public:
void EraseSingleBlock(const JitBlock& block) 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"; }
// OPCODES
@@ -413,4 +418,6 @@ protected:
HyoutaUtilities::RangeSizeSet<u8*> m_free_ranges_near_1;
HyoutaUtilities::RangeSizeSet<u8*> m_free_ranges_far_0;
HyoutaUtilities::RangeSizeSet<u8*> m_free_ranges_far_1;
+
+ std::unique_ptr<HostDisassembler> m_disassembler;
};
diff --git a/Source/Core/Core/PowerPC/JitCommon/JitBase.h b/Source/Core/Core/PowerPC/JitCommon/JitBase.h
index cad4e6d44e..fa2fdd167f 100644
--- a/Source/Core/Core/PowerPC/JitCommon/JitBase.h
+++ b/Source/Core/Core/PowerPC/JitCommon/JitBase.h
@@ -5,6 +5,7 @@
#include <array>
#include <cstddef>
+#include <iosfwd>
#include <map>
#include <string_view>
#include <unordered_set>
@@ -202,6 +203,9 @@ public:
using MemoryStats = std::pair<std::string_view, std::pair<std::size_t, double>>;
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 bool HandleFault(uintptr_t access_address, SContext* ctx) = 0;
diff --git a/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp b/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp
index 220c3783db..687d40417b 100644
--- a/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp
+++ b/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp
@@ -8,13 +8,16 @@
#include <cstring>
#include <functional>
#include <map>
+#include <ranges>
#include <set>
+#include <span>
#include <utility>
#include "Common/CommonTypes.h"
#include "Common/JitRegister.h"
#include "Core/Config/MainSettings.h"
#include "Core/Core.h"
+#include "Core/Host.h"
#include "Core/PowerPC/JitCommon/JitBase.h"
#include "Core/PowerPC/MMU.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())
*profile_data = {};
}
+ Host_JitProfileDataWiped();
}
JitBlock* JitBaseBlockCache::AllocateBlock(u32 em_address)
@@ -139,7 +143,8 @@ JitBlock* JitBaseBlockCache::AllocateBlock(u32 em_address)
}
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);
if (m_entry_points_ptr)
@@ -153,7 +158,18 @@ void JitBaseBlockCache::FinalizeBlock(JitBlock& block, bool block_link,
}
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)
{
@@ -175,13 +191,14 @@ void JitBaseBlockCache::FinalizeBlock(JitBlock& block, bool block_link,
if (Common::JitRegister::IsEnabled() &&
(symbol = m_jit.m_ppc_symbol_db.GetSymbolFromAddr(block.effectiveAddress)) != nullptr)
{
- Common::JitRegister::Register(block.normalEntry, block.codeSize, "JIT_PPC_{}_{:08x}",
- symbol->function_name.c_str(), block.physicalAddress);
+ Common::JitRegister::Register(block.normalEntry, block.near_end - block.normalEntry,
+ "JIT_PPC_{}_{:08x}", symbol->function_name,
+ block.physicalAddress);
}
else
{
- Common::JitRegister::Register(block.normalEntry, block.codeSize, "JIT_PPC_{:08x}",
- block.physicalAddress);
+ Common::JitRegister::Register(block.normalEntry, block.near_end - block.normalEntry,
+ "JIT_PPC_{:08x}", block.physicalAddress);
}
}
diff --git a/Source/Core/Core/PowerPC/JitCommon/JitCache.h b/Source/Core/Core/PowerPC/JitCommon/JitCache.h
index 9f4a127ffb..fb9b330097 100644
--- a/Source/Core/Core/PowerPC/JitCommon/JitCache.h
+++ b/Source/Core/Core/PowerPC/JitCommon/JitCache.h
@@ -19,6 +19,7 @@
#include "Common/CommonTypes.h"
#include "Core/HW/Memmap.h"
#include "Core/PowerPC/Gekko.h"
+#include "Core/PowerPC/PPCAnalyst.h"
class JitBase;
@@ -105,6 +106,10 @@ struct JitBlock : public JitBlockData
// This set stores all physical addresses of all occupied instructions.
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;
};
@@ -162,9 +167,11 @@ public:
JitBlock** GetFastBlockMapFallback();
void RunOnBlocks(const Core::CPUThreadGuard& guard, std::function<void(const JitBlock&)> f) const;
void WipeBlockProfilingData(const Core::CPUThreadGuard& guard);
+ std::size_t GetBlockCount() const { return block_map.size(); }
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.
// This function shall be used if FastLookupIndexForAddress() failed.
diff --git a/Source/Core/Core/PowerPC/JitInterface.cpp b/Source/Core/Core/PowerPC/JitInterface.cpp
index f142fcee60..be0f21f2e9 100644
--- a/Source/Core/Core/PowerPC/JitInterface.cpp
+++ b/Source/Core/Core/PowerPC/JitInterface.cpp
@@ -188,46 +188,18 @@ void JitInterface::WipeBlockProfilingData(const Core::CPUThreadGuard& guard)
m_jit->GetBlockCache()->WipeBlockProfilingData(guard);
}
-std::variant<JitInterface::GetHostCodeError, JitInterface::GetHostCodeResult>
-JitInterface::GetHostCode(u32 address) const
+void JitInterface::RunOnBlocks(const Core::CPUThreadGuard& guard,
+ std::function<void(const JitBlock&)> f) const
{
- if (!m_jit)
- {
- return GetHostCodeError::NoJitActive;
- }
-
- auto& ppc_state = m_system.GetPPCState();
- JitBlock* block =
- m_jit->GetBlockCache()->GetBlockFromStartAddress(address, ppc_state.feature_flags);
- if (!block)
- {
- 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;
- }
- }
+ if (m_jit)
+ m_jit->GetBlockCache()->RunOnBlocks(guard, std::move(f));
+}
- GetHostCodeResult result;
- result.code = block->normalEntry;
- result.code_size = block->codeSize;
- result.entry_address = block->effectiveAddress;
- return result;
+std::size_t JitInterface::GetBlockCount() const
+{
+ if (m_jit)
+ return m_jit->GetBlockCache()->GetBlockCount();
+ return 0;
}
bool JitInterface::HandleFault(uintptr_t access_address, SContext* ctx)
@@ -276,6 +248,20 @@ std::vector<JitBase::MemoryStats> JitInterface::GetMemoryStats() const
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)
{
if (m_jit)
diff --git a/Source/Core/Core/PowerPC/JitInterface.h b/Source/Core/Core/PowerPC/JitInterface.h
index 09e3d49f8b..a382fb1ba4 100644
--- a/Source/Core/Core/PowerPC/JitInterface.h
+++ b/Source/Core/Core/PowerPC/JitInterface.h
@@ -6,11 +6,10 @@
#include <cstddef>
#include <cstdio>
#include <functional>
+#include <iosfwd>
#include <memory>
-#include <string>
#include <string_view>
#include <utility>
-#include <variant>
#include <vector>
#include "Common/CommonTypes.h"
@@ -46,23 +45,11 @@ public:
CPUCoreBase* InitJitCore(PowerPC::CPUCore core);
CPUCoreBase* GetCore() const;
- // Debugging
- enum class GetHostCodeError
- {
- NoJitActive,
- NoTranslation,
- };
- struct GetHostCodeResult
- {
- const u8* code;
- u32 code_size;
- u32 entry_address;
- };
-
void UpdateMembase();
void JitBlockLogDump(const Core::CPUThreadGuard& guard, std::FILE* file) const;
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
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>>;
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.
void InvalidateICache(u32 address, u32 size, bool forced);
void InvalidateICacheLine(u32 address);
diff --git a/Source/Core/DolphinNoGUI/MainNoGUI.cpp b/Source/Core/DolphinNoGUI/MainNoGUI.cpp
index 40145e6e4c..f5aaae01ca 100644
--- a/Source/Core/DolphinNoGUI/MainNoGUI.cpp
+++ b/Source/Core/DolphinNoGUI/MainNoGUI.cpp
@@ -86,6 +86,14 @@ void Host_UpdateDisasmDialog()
{
}
+void Host_JitCacheCleared()
+{
+}
+
+void Host_JitProfileDataWiped()
+{
+}
+
void Host_UpdateMainFrame()
{
s_update_main_frame_event.Set();
diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt
index 79e5cce207..1daceff217 100644
--- a/Source/Core/DolphinQt/CMakeLists.txt
+++ b/Source/Core/DolphinQt/CMakeLists.txt
@@ -222,6 +222,8 @@ add_executable(dolphin-emu
Debugger/CodeWidget.h
Debugger/GekkoSyntaxHighlight.cpp
Debugger/GekkoSyntaxHighlight.h
+ Debugger/JitBlockTableModel.cpp
+ Debugger/JitBlockTableModel.h
Debugger/JITWidget.cpp
Debugger/JITWidget.h
Debugger/MemoryViewWidget.cpp
@@ -297,6 +299,7 @@ add_executable(dolphin-emu
QtUtils/BlockUserInputFilter.h
QtUtils/ClearLayoutRecursively.cpp
QtUtils/ClearLayoutRecursively.h
+ QtUtils/ClickableStatusBar.h
QtUtils/DolphinFileDialog.cpp
QtUtils/DolphinFileDialog.h
QtUtils/DoubleClickEventFilter.cpp
diff --git a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp
index 12cb8b30c5..792d78d9b0 100644
--- a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp
+++ b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp
@@ -881,7 +881,7 @@ void CodeViewWidget::OnPPCComparison()
{
const u32 addr = GetContextAddress();
- emit RequestPPCComparison(addr);
+ emit RequestPPCComparison(addr, m_system.GetPPCState().msr.IR);
}
void CodeViewWidget::OnAddFunction()
diff --git a/Source/Core/DolphinQt/Debugger/CodeViewWidget.h b/Source/Core/DolphinQt/Debugger/CodeViewWidget.h
index 4dec82b676..3fc78489ba 100644
--- a/Source/Core/DolphinQt/Debugger/CodeViewWidget.h
+++ b/Source/Core/DolphinQt/Debugger/CodeViewWidget.h
@@ -55,7 +55,7 @@ public:
u32 AddressForRow(int row) const;
signals:
- void RequestPPCComparison(u32 addr);
+ void RequestPPCComparison(u32 address, bool translate_address);
void ShowMemory(u32 address);
void UpdateCodeWidget();
diff --git a/Source/Core/DolphinQt/Debugger/CodeWidget.cpp b/Source/Core/DolphinQt/Debugger/CodeWidget.cpp
index 681f14286c..c36f8c31d7 100644
--- a/Source/Core/DolphinQt/Debugger/CodeWidget.cpp
+++ b/Source/Core/DolphinQt/Debugger/CodeWidget.cpp
@@ -210,6 +210,11 @@ void CodeWidget::OnBranchWatchDialog()
m_branch_watch_dialog->activateWindow();
}
+void CodeWidget::OnSetCodeAddress(u32 address)
+{
+ SetAddress(address, CodeViewWidget::SetAddressUpdate::WithDetailedUpdate);
+}
+
void CodeWidget::OnPPCSymbolsChanged()
{
UpdateSymbols();
diff --git a/Source/Core/DolphinQt/Debugger/CodeWidget.h b/Source/Core/DolphinQt/Debugger/CodeWidget.h
index e0eb0bb09f..bf51db9d59 100644
--- a/Source/Core/DolphinQt/Debugger/CodeWidget.h
+++ b/Source/Core/DolphinQt/Debugger/CodeWidget.h
@@ -43,6 +43,7 @@ public:
void SetPC();
void OnBranchWatchDialog();
+ void OnSetCodeAddress(u32 address);
void ToggleBreakpoint();
void AddBreakpoint();
void SetAddress(u32 address, CodeViewWidget::SetAddressUpdate update);
@@ -50,7 +51,7 @@ public:
void Update();
void UpdateSymbols();
signals:
- void RequestPPCComparison(u32 addr);
+ void RequestPPCComparison(u32 address, bool translate_address);
void ShowMemory(u32 address);
private:
diff --git a/Source/Core/DolphinQt/Debugger/JITWidget.cpp b/Source/Core/DolphinQt/Debugger/JITWidget.cpp
index ae9bf661b9..f83b3a6c9c 100644
--- a/Source/Core/DolphinQt/Debugger/JITWidget.cpp
+++ b/Source/Core/DolphinQt/Debugger/JITWidget.cpp
@@ -3,216 +3,494 @@
#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 <QSignalBlocker>
+#include <QSortFilterProxyModel>
#include <QSplitter>
-#include <QTableWidget>
-#include <QTextBrowser>
+#include <QTableView>
#include <QVBoxLayout>
#include <fmt/format.h>
+#include <fmt/ostream.h>
+#include "Common/CommonFuncs.h"
#include "Common/GekkoDisassembler.h"
-#include "Common/HostDisassembler.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 "DolphinQt/Debugger/JitBlockTableModel.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 "UICommon/UICommon.h"
-JITWidget::JITWidget(QWidget* parent) : QDockWidget(parent)
+class JitBlockProxyModel final : public QSortFilterProxyModel
{
- setWindowTitle(tr("JIT Blocks"));
- setObjectName(QStringLiteral("jitwidget"));
+ friend 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());
- // macOS: setHidden() needs to be evaluated before setFloating() for proper window presentation
- // according to Settings
- setFloating(settings.value(QStringLiteral("jitwidget/floating")).toBool());
+ // Always connected slots (external signals)
+ void OnSymbolTextChanged(const QString& text);
+ template <std::optional<u32> JitBlockProxyModel::*member>
+ void OnAddressTextChanged(const QString& text);
- m_table_splitter->restoreState(
- settings.value(QStringLiteral("jitwidget/tablesplitter")).toByteArray());
- m_asm_splitter->restoreState(
- settings.value(QStringLiteral("jitwidget/asmsplitter")).toByteArray());
+private:
+ std::optional<u32> m_em_address_min, m_em_address_max, m_pm_address_covered;
+ QString m_symbol_name = {};
+};
+
+const JitBlock& JitBlockProxyModel::GetJitBlock(const QModelIndex& index)
+{
+ return sourceModel()->GetJitBlock(mapToSource(index));
+}
+
+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;
+}
- connect(&Settings::Instance(), &Settings::JITVisibilityChanged, this,
- [this](bool visible) { setHidden(!visible); });
+// Virtual setSourceModel is forbidden for type-safety reasons.
+void JitBlockProxyModel::setSourceModel(QAbstractItemModel* source_model)
+{
+ Crash();
+}
- connect(&Settings::Instance(), &Settings::DebugModeToggled, this,
- [this](bool enabled) { setHidden(!enabled || !Settings::Instance().IsJITVisible()); });
+void JitBlockProxyModel::setSourceModel(JitBlockTableModel* source_model)
+{
+ QSortFilterProxyModel::setSourceModel(source_model);
+}
- connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, &JITWidget::Update);
- connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this, &JITWidget::Update);
+JitBlockTableModel* JitBlockProxyModel::sourceModel() const
+{
+ return static_cast<JitBlockTableModel*>(QSortFilterProxyModel::sourceModel());
+}
- ConnectWidgets();
+JitBlockProxyModel::JitBlockProxyModel(QObject* parent) : QSortFilterProxyModel(parent)
+{
+}
-#if defined(_M_X86_64)
- m_disassembler = GetNewDisassembler("x86");
-#elif defined(_M_ARM_64)
- m_disassembler = GetNewDisassembler("aarch64");
-#else
- m_disassembler = GetNewDisassembler("UNK");
-#endif
+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);
}
-JITWidget::~JITWidget()
+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();
settings.setValue(QStringLiteral("jitwidget/geometry"), saveGeometry());
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/asmsplitter"), m_asm_splitter->saveState());
+ settings.setValue(QStringLiteral("jitwidget/disasmsplitter"), m_disasm_splitter->saveState());
+}
+
+void JITWidget::ConnectSlots()
+{
+ auto* const host = Host::GetInstance();
+ connect(host, &Host::JitCacheCleared, this, &JITWidget::OnJitCacheCleared);
+ connect(host, &Host::UpdateDisasmDialog, this, &JITWidget::OnUpdateDisasmDialog);
+ connect(host, &Host::PPCSymbolsChanged, this, &JITWidget::OnPPCSymbolsUpdated);
+ connect(host, &Host::PPCBreakpointsChanged, this, &JITWidget::OnPPCBreakpointsChanged);
+ auto* const settings = &Settings::Instance();
+ connect(settings, &Settings::ConfigChanged, this, &JITWidget::OnConfigChanged);
+ connect(settings, &Settings::DebugFontChanged, this, &JITWidget::OnDebugFontChanged);
+ connect(settings, &Settings::EmulationStateChanged, this, &JITWidget::OnEmulationStateChanged);
+}
+
+void JITWidget::DisconnectSlots()
+{
+ 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::CreateWidgets()
+void JITWidget::Show()
{
- m_table_widget = new QTableWidget;
+ 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));
+}
- m_table_widget->setTabKeyNavigation(false);
- m_table_widget->setColumnCount(7);
- m_table_widget->setHorizontalHeaderLabels(
- {tr("Address"), tr("PPC Size"), tr("Host Size"),
- // i18n: The symbolic name of a code block
- tr("Symbol"),
- // i18n: These are the kinds of flags that a CPU uses (e.g. carry),
- // 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")});
+void JITWidget::Hide()
+{
+ DisconnectSlots();
+ ClearDisassembly();
+}
+
+void JITWidget::OnRequestPPCComparison(u32 address, bool translate_address)
+{
+ Settings::Instance().SetJITVisible(true);
+ raise();
+
+ 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));
+}
- m_ppc_asm_widget = new QTextBrowser;
- m_host_asm_widget = new QTextBrowser;
+void JITWidget::OnVisibilityToggled(bool visible)
+{
+ setHidden(!visible);
+}
- m_table_splitter = new QSplitter(Qt::Vertical);
- m_asm_splitter = new QSplitter(Qt::Horizontal);
+void JITWidget::OnDebugModeToggled(bool enabled)
+{
+ setHidden(!enabled || !Settings::Instance().IsJITVisible());
+}
- m_refresh_button = new QPushButton(tr("Refresh"));
+void JITWidget::OnToggleProfiling(bool enabled)
+{
+ Config::SetBaseOrCurrent(Config::MAIN_DEBUG_JIT_ENABLE_PROFILING, enabled);
+}
- m_table_splitter->addWidget(m_table_widget);
- m_table_splitter->addWidget(m_asm_splitter);
+void JITWidget::OnClearCache()
+{
+ m_system.GetJitInterface().ClearCache(Core::CPUThreadGuard{m_system});
+}
- m_asm_splitter->addWidget(m_ppc_asm_widget);
- m_asm_splitter->addWidget(m_host_asm_widget);
+void JITWidget::OnWipeProfiling()
+{
+ m_system.GetJitInterface().WipeBlockProfilingData(Core::CPUThreadGuard{m_system});
+}
- QWidget* widget = new QWidget;
- auto* layout = new QVBoxLayout;
- layout->setContentsMargins(2, 2, 2, 2);
- widget->setLayout(layout);
+void JITWidget::OnTableCurrentChanged(const QModelIndex& current, const QModelIndex& previous)
+{
+ CrossDisassemble(current);
+}
- layout->addWidget(m_table_splitter);
- layout->addWidget(m_refresh_button);
+void JITWidget::OnTableDoubleClicked(const QModelIndex& index)
+{
+ emit SetCodeAddress(m_table_proxy->GetJitBlock(index).effectiveAddress);
+}
- setWidget(widget);
+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_column_visibility_menu->exec(m_table_view->viewport()->mapToGlobal(pos));
+ return;
+ }
+ m_table_context_menu->exec(m_table_view->viewport()->mapToGlobal(pos));
}
-void JITWidget::ConnectWidgets()
+void JITWidget::OnTableHeaderContextMenu(const QPoint& pos)
{
- connect(m_refresh_button, &QPushButton::clicked, this, &JITWidget::Update);
+ m_column_visibility_menu->exec(m_table_view->horizontalHeader()->mapToGlobal(pos));
}
-void JITWidget::Compare(u32 address)
+void JITWidget::OnTableMenuViewCode()
{
- m_address = 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);
+}
- Settings::Instance().SetJITVisible(true);
- raise();
- m_host_asm_widget->setFocus();
+void JITWidget::OnTableMenuEraseBlocks()
+{
+ TableEraseBlocks(); // Side effect: currentChanged will be emitted (this is intended).
+ // Because currentChanged has been emitted, OnTableCurrentChanged has already handled the rest.
+}
- Update();
+void JITWidget::OnStatusBarPressed()
+{
+ if (Core::GetState(m_system) == Core::State::Paused)
+ ShowFreeMemoryStatus();
}
-void JITWidget::Update()
+void JITWidget::OnJitCacheCleared()
{
- if (!isVisible())
+ if (Core::GetState(m_system) != Core::State::Paused)
return;
+ ClearDisassembly();
+ ShowFreeMemoryStatus();
+}
- if (!m_address || (Core::GetState(Core::System::GetInstance()) != Core::State::Paused))
- {
- m_ppc_asm_widget->setHtml(QStringLiteral("<i>%1</i>").arg(tr("(ppc)")));
- m_host_asm_widget->setHtml(QStringLiteral("<i>%1</i>").arg(tr("(host)")));
+void JITWidget::OnUpdateDisasmDialog()
+{
+ if (Core::GetState(m_system) != Core::State::Paused)
return;
- }
+ CrossDisassemble();
+}
- // TODO: Actually do something with the table (Wx doesn't)
-
- // Get host side code disassembly
- auto host_instructions_disasm = DisassembleBlock(m_disassembler.get(), m_address);
- m_address = host_instructions_disasm.entry_address;
-
- m_host_asm_widget->setHtml(
- QStringLiteral("<pre>%1</pre>").arg(QString::fromStdString(host_instructions_disasm.text)));
-
- // == Fill in ppc box
- u32 ppc_addr = m_address;
- PPCAnalyst::CodeBuffer code_buffer(32000);
- PPCAnalyst::BlockStats st;
- 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;
- code_block.m_gpa = &gpa;
- code_block.m_fpa = &fpa;
-
- if (analyzer.Analyze(ppc_addr, &code_block, &code_buffer, code_buffer.size()) != 0xFFFFFFFF)
- {
- std::string ppc_disasm_str;
- auto ppc_disasm = std::back_inserter(ppc_disasm_str);
- for (u32 i = 0; i < code_block.m_num_instructions; i++)
- {
- 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);
- }
+void JITWidget::OnPPCSymbolsUpdated()
+{
+ if (Core::GetState(m_system) != Core::State::Paused)
+ return;
+ CrossDisassemble();
+}
- // Add stats to the end of the ppc box since it's generally the shortest.
- fmt::format_to(ppc_disasm, "\n{} estimated cycles", st.numCycles);
- fmt::format_to(ppc_disasm, "\nNum instr: PPC: {} Host: {}", code_block.m_num_instructions,
- host_instructions_disasm.instruction_count);
- if (code_block.m_num_instructions != 0 && host_instructions_disasm.instruction_count != 0)
- {
- fmt::format_to(
- ppc_disasm, " (blowup: {}%)",
- 100 * host_instructions_disasm.instruction_count / code_block.m_num_instructions - 100);
- }
+void JITWidget::OnPPCBreakpointsChanged()
+{
+ // 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.
+ auto* const selection_model = m_table_view->selectionModel();
+ selection_model->clear(); // Side effect: currentChanged will be emitted (this is intended).
+ // Because currentChanged has been emitted, OnTableCurrentChanged has already handled the rest.
+}
- fmt::format_to(ppc_disasm, "\nNum bytes: PPC: {} Host: {}", code_block.m_num_instructions * 4,
- host_instructions_disasm.code_size);
- if (code_block.m_num_instructions != 0 && host_instructions_disasm.code_size != 0)
- {
- fmt::format_to(
- ppc_disasm, " (blowup: {}%)",
- 100 * host_instructions_disasm.code_size / (4 * code_block.m_num_instructions) - 100);
- }
+void JITWidget::OnConfigChanged()
+{
+ UpdateProfilingButton();
+}
- m_ppc_asm_widget->setHtml(
- QStringLiteral("<pre>%1</pre>").arg(QString::fromStdString(ppc_disasm_str)));
- }
- else
- {
- m_host_asm_widget->setHtml(
- QStringLiteral("<pre>%1</pre>")
- .arg(QString::fromStdString(fmt::format("(non-code address: {:08x})", m_address))));
- m_ppc_asm_widget->setHtml(QStringLiteral("<i>---</i>"));
- }
+void JITWidget::OnDebugFontChanged(const QFont& font)
+{
+ UpdateDebugFont(font);
+}
+
+void JITWidget::OnEmulationStateChanged(Core::State state)
+{
+ UpdateOtherButtons(state);
+ UpdateContent(state);
}
void JITWidget::closeEvent(QCloseEvent*)
@@ -220,7 +498,186 @@ void JITWidget::closeEvent(QCloseEvent*)
Settings::Instance().SetJITVisible(false);
}
-void JITWidget::showEvent(QShowEvent* event)
+void JITWidget::showEvent(QShowEvent*)
+{
+ 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()
{
- Update();
+ SaveQSettings();
}
diff --git a/Source/Core/DolphinQt/Debugger/JITWidget.h b/Source/Core/DolphinQt/Debugger/JITWidget.h
index 3b2f80701d..d192a8ee0a 100644
--- a/Source/Core/DolphinQt/Debugger/JITWidget.h
+++ b/Source/Core/DolphinQt/Debugger/JITWidget.h
@@ -4,42 +4,128 @@
#pragma once
#include <QDockWidget>
-#include <memory>
#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 QFont;
+class QLineEdit;
+class QMenu;
+class QPlainTextEdit;
+class QPushButton;
class QShowEvent;
class QSplitter;
-class QTextBrowser;
-class QTableWidget;
-class QPushButton;
-class HostDisassembler;
+class QTableView;
-class JITWidget : public QDockWidget
+class JITWidget final : public QDockWidget
{
Q_OBJECT
+
+ using Column = JitBlockTableModelColumn::EnumType;
+ using UserRole = JitBlockTableModelUserRole::EnumType;
+
+signals:
+ void HideSignal();
+ void ShowSignal();
+ void SetCodeAddress(u32 address);
+
public:
- explicit JITWidget(QWidget* parent = nullptr);
- ~JITWidget();
+ explicit JITWidget(Core::System& system, QWidget* parent = nullptr);
+ ~JITWidget() override;
- void Compare(u32 address);
+ JITWidget(const JITWidget&) = delete;
+ JITWidget(JITWidget&&) = delete;
+ JITWidget& operator=(const JITWidget&) = delete;
+ JITWidget& operator=(JITWidget&&) = delete;
-private:
- void Update();
- void CreateWidgets();
- void ConnectWidgets();
+ // Always connected slots (external signals)
+ void OnRequestPPCComparison(u32 address, bool translate_address);
- void closeEvent(QCloseEvent*) override;
+private:
+ void closeEvent(QCloseEvent* event) override;
void showEvent(QShowEvent* event) override;
+ void hideEvent(QHideEvent* event) override;
+
+ void UpdateProfilingButton();
+ void UpdateOtherButtons(Core::State state);
+ 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;
- QTableWidget* m_table_widget;
- QTextBrowser* m_ppc_asm_widget;
- QTextBrowser* m_host_asm_widget;
+ 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_asm_splitter;
- QPushButton* m_refresh_button;
+ QSplitter* m_disasm_splitter;
+ ClickableStatusBar* m_status_bar;
- std::unique_ptr<HostDisassembler> m_disassembler;
- u32 m_address = 0;
+ QMenu* m_table_context_menu;
+ QMenu* m_column_visibility_menu;
};
diff --git a/Source/Core/DolphinQt/Debugger/JitBlockTableModel.cpp b/Source/Core/DolphinQt/Debugger/JitBlockTableModel.cpp
new file mode 100644
index 0000000000..27bece81cd
--- /dev/null
+++ b/Source/Core/DolphinQt/Debugger/JitBlockTableModel.cpp
@@ -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;
diff --git a/Source/Core/DolphinQt/Debugger/JitBlockTableModel.h b/Source/Core/DolphinQt/Debugger/JitBlockTableModel.h
new file mode 100644
index 0000000000..eeaf2273d5
--- /dev/null
+++ b/Source/Core/DolphinQt/Debugger/JitBlockTableModel.h
@@ -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;
+};
diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj
index 7ee8cac6a5..f275175de6 100644
--- a/Source/Core/DolphinQt/DolphinQt.vcxproj
+++ b/Source/Core/DolphinQt/DolphinQt.vcxproj
@@ -145,6 +145,7 @@
<ClCompile Include="Debugger\CodeViewWidget.cpp" />
<ClCompile Include="Debugger\CodeWidget.cpp" />
<ClCompile Include="Debugger\GekkoSyntaxHighlight.cpp" />
+ <ClCompile Include="Debugger\JitBlockTableModel.cpp" />
<ClCompile Include="Debugger\JITWidget.cpp" />
<ClCompile Include="Debugger\MemoryViewWidget.cpp" />
<ClCompile Include="Debugger\MemoryWidget.cpp" />
@@ -249,6 +250,7 @@
<ClInclude Include="GBAHost.h" />
<ClInclude Include="QtUtils\ActionHelper.h" />
<ClInclude Include="QtUtils\ClearLayoutRecursively.h" />
+ <QtMoc Include="QtUtils\ClickableStatusBar.h" />
<ClInclude Include="QtUtils\DolphinFileDialog.h" />
<ClInclude Include="QtUtils\ImageConverter.h" />
<ClInclude Include="QtUtils\ModalMessageBox.h" />
@@ -359,6 +361,7 @@
<QtMoc Include="Debugger\CodeViewWidget.h" />
<QtMoc Include="Debugger\CodeWidget.h" />
<QtMoc Include="Debugger\GekkoSyntaxHighlight.h" />
+ <QtMoc Include="Debugger\JitBlockTableModel.h" />
<QtMoc Include="Debugger\JITWidget.h" />
<QtMoc Include="Debugger\MemoryViewWidget.h" />
<QtMoc Include="Debugger\MemoryWidget.h" />
diff --git a/Source/Core/DolphinQt/Host.cpp b/Source/Core/DolphinQt/Host.cpp
index 80a4247a2b..3dc6f2a12b 100644
--- a/Source/Core/DolphinQt/Host.cpp
+++ b/Source/Core/DolphinQt/Host.cpp
@@ -256,6 +256,16 @@ void Host_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()
{
QueueOnObject(QApplication::instance(), [] { emit Host::GetInstance()->PPCSymbolsChanged(); });
diff --git a/Source/Core/DolphinQt/Host.h b/Source/Core/DolphinQt/Host.h
index ce076d1a91..4f3be255e8 100644
--- a/Source/Core/DolphinQt/Host.h
+++ b/Source/Core/DolphinQt/Host.h
@@ -40,6 +40,8 @@ signals:
void RequestStop();
void RequestRenderSize(int w, int h);
void UpdateDisasmDialog();
+ void JitCacheCleared();
+ void JitProfileDataWiped();
void PPCSymbolsChanged();
void PPCBreakpointsChanged();
diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp
index 5e733b1c71..8ccf384506 100644
--- a/Source/Core/DolphinQt/MainWindow.cpp
+++ b/Source/Core/DolphinQt/MainWindow.cpp
@@ -463,7 +463,7 @@ void MainWindow::CreateComponents()
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_config_widget = new LogConfigWidget(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);
};
+ 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::ShowMemory, m_memory_widget, &MemoryWidget::SetAddress);
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::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_memory_widget, &MemoryWidget::ShowCode, m_code_widget, [this](u32 address) {
m_code_widget->SetAddress(address, CodeViewWidget::SetAddressUpdate::WithDetailedUpdate);
diff --git a/Source/Core/DolphinQt/MenuBar.cpp b/Source/Core/DolphinQt/MenuBar.cpp
index 6170fa9ada..fef441c062 100644
--- a/Source/Core/DolphinQt/MenuBar.cpp
+++ b/Source/Core/DolphinQt/MenuBar.cpp
@@ -13,6 +13,7 @@
#include <QFontDialog>
#include <QInputDialog>
#include <QMap>
+#include <QSignalBlocker>
#include <QUrl>
#include <fmt/format.h>
@@ -95,6 +96,7 @@ MenuBar::MenuBar(QWidget* parent) : QMenuBar(parent)
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
[=, this](Core::State state) { OnEmulationStateChanged(state); });
+ connect(&Settings::Instance(), &Settings::ConfigChanged, this, &MenuBar::OnConfigChanged);
connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this,
[this] { OnEmulationStateChanged(Core::GetState(Core::System::GetInstance())); });
@@ -167,6 +169,12 @@ void MenuBar::OnEmulationStateChanged(Core::State state)
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)
{
// Options
diff --git a/Source/Core/DolphinQt/MenuBar.h b/Source/Core/DolphinQt/MenuBar.h
index 4ccc8df819..29457c15f7 100644
--- a/Source/Core/DolphinQt/MenuBar.h
+++ b/Source/Core/DolphinQt/MenuBar.h
@@ -127,6 +127,7 @@ signals:
private:
void OnEmulationStateChanged(Core::State state);
+ void OnConfigChanged();
void AddFileMenu();
diff --git a/Source/Core/DolphinQt/QtUtils/ClickableStatusBar.h b/Source/Core/DolphinQt/QtUtils/ClickableStatusBar.h
new file mode 100644
index 0000000000..86ab9e6b1a
--- /dev/null
+++ b/Source/Core/DolphinQt/QtUtils/ClickableStatusBar.h
@@ -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;
+};
diff --git a/Source/Core/DolphinTool/ToolHeadlessPlatform.cpp b/Source/Core/DolphinTool/ToolHeadlessPlatform.cpp
index 0b04cbfa19..4f445623b9 100644
--- a/Source/Core/DolphinTool/ToolHeadlessPlatform.cpp
+++ b/Source/Core/DolphinTool/ToolHeadlessPlatform.cpp
@@ -61,6 +61,14 @@ void Host_UpdateDisasmDialog()
{
}
+void Host_JitCacheCleared()
+{
+}
+
+void Host_JitProfileDataWiped()
+{
+}
+
void Host_UpdateMainFrame()
{
}
diff --git a/Source/DSPTool/StubHost.cpp b/Source/DSPTool/StubHost.cpp
index e270acb23a..d54d005674 100644
--- a/Source/DSPTool/StubHost.cpp
+++ b/Source/DSPTool/StubHost.cpp
@@ -41,6 +41,12 @@ bool Host_UpdateDiscordPresenceRaw(const std::string& details, const std::string
void Host_UpdateDisasmDialog()
{
}
+void Host_JitCacheCleared()
+{
+}
+void Host_JitProfileDataWiped()
+{
+}
void Host_UpdateMainFrame()
{
}
diff --git a/Source/UnitTests/Core/PageFaultTest.cpp b/Source/UnitTests/Core/PageFaultTest.cpp
index 21ef289159..a4c5525da2 100644
--- a/Source/UnitTests/Core/PageFaultTest.cpp
+++ b/Source/UnitTests/Core/PageFaultTest.cpp
@@ -42,6 +42,8 @@ public:
void Jit(u32 em_address) override {}
void EraseSingleBlock(const JitBlock&) override {}
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; }
virtual bool HandleFault(uintptr_t access_address, SContext* ctx) override
{
diff --git a/Source/UnitTests/StubHost.cpp b/Source/UnitTests/StubHost.cpp
index dc7dfe2277..96fe3e71d9 100644
--- a/Source/UnitTests/StubHost.cpp
+++ b/Source/UnitTests/StubHost.cpp
@@ -41,6 +41,12 @@ bool Host_UpdateDiscordPresenceRaw(const std::string& details, const std::string
void Host_UpdateDisasmDialog()
{
}
+void Host_JitCacheCleared()
+{
+}
+void Host_JitProfileDataWiped()
+{
+}
void Host_UpdateMainFrame()
{
}