Merge pull request #12714 from mitaclaw/jit-widget-refresh

DolphinQt: JIT Widget Refresh
This commit is contained in:
JMC47 2024-10-21 12:03:50 -04:00 committed by GitHub
commit f412e2488c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 2279 additions and 611 deletions

View File

@ -3,6 +3,7 @@
#include <cassert>
#include <cstddef>
#include <map>
#include <utility>
namespace HyoutaUtilities {
template <typename T> class RangeSet {
@ -254,7 +255,31 @@ public:
return !(*this == other);
}
// Get free size and fragmentation ratio
std::pair<std::size_t, double> get_stats() const {
std::size_t free_total = 0;
if (begin() == end())
return {free_total, 1.0};
std::size_t largest_size = 0;
for (auto iter = begin(); iter != end(); ++iter) {
const std::size_t size = calc_size(iter.from(), iter.to());
if (size > largest_size)
largest_size = size;
free_total += size;
}
return {free_total, static_cast<double>(free_total - largest_size) / free_total};
}
private:
static std::size_t calc_size(T from, T to) {
if constexpr (std::is_pointer_v<T>) {
// For pointers we don't want pointer arithmetic here, else void* breaks.
return reinterpret_cast<std::size_t>(to) - reinterpret_cast<std::size_t>(from);
} else {
return static_cast<std::size_t>(to - from);
}
}
// Assumptions that can be made about the data:
// - Range are stored in the form [from, to[
// That is, the starting value is inclusive, and the end value is exclusive.

View File

@ -4,6 +4,7 @@
#include <cstddef>
#include <map>
#include <type_traits>
#include <utility>
namespace HyoutaUtilities {
// Like RangeSet, but additionally stores a map of the ranges sorted by their size, for quickly finding the largest or
@ -398,6 +399,16 @@ public:
return !(*this == other);
}
// Get free size and fragmentation ratio
std::pair<std::size_t, double> get_stats() const {
std::size_t free_total = 0;
if (begin() == end())
return {free_total, 1.0};
for (auto iter = begin(); iter != end(); ++iter)
free_total += calc_size(iter.from(), iter.to());
return {free_total, static_cast<double>(free_total - Sizes.begin()->first) / free_total};
}
private:
static SizeT calc_size(T from, T to) {
if constexpr (std::is_pointer_v<T>) {

View File

@ -391,6 +391,11 @@ public final class NativeLibrary
*/
public static native boolean IsUninitialized();
/**
* Re-initialize software JitBlock profiling data
*/
public static native void WipeJitBlockProfilingData();
/**
* Writes out the JitBlock Cache log dump
*/

View File

@ -2013,6 +2013,16 @@ class SettingsFragmentPresenter(
0
)
)
sl.add(
RunRunnable(
context,
R.string.debug_jit_wipe_block_profiling_data,
0,
R.string.debug_jit_wipe_block_profiling_data_alert,
0,
true
) { NativeLibrary.WipeJitBlockProfilingData() }
)
sl.add(
RunRunnable(
context,

View File

@ -412,6 +412,8 @@
<string name="debug_large_entry_points_map">Disable Large Entry Points Map</string>
<string name="debug_jit_profiling_header">Jit Profiling</string>
<string name="debug_jit_enable_block_profiling">Enable Jit Block Profiling</string>
<string name="debug_jit_wipe_block_profiling_data">Wipe Jit Block Profiling Data</string>
<string name="debug_jit_wipe_block_profiling_data_alert">Re-initialize JIT block profiling data?</string>
<string name="debug_jit_write_block_log_dump">Write Jit Block Log Dump</string>
<string name="debug_jit_header">Jit</string>
<string name="debug_jitoff">Jit Disabled</string>

View File

@ -146,6 +146,14 @@ void Host_UpdateDisasmDialog()
{
}
void Host_JitCacheCleared()
{
}
void Host_JitProfileDataWiped()
{
}
void Host_UpdateMainFrame()
{
}
@ -410,6 +418,22 @@ JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetMaxLogLev
return static_cast<jint>(Common::Log::MAX_LOGLEVEL);
}
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_WipeJitBlockProfilingData(
JNIEnv* env, jclass native_library_class)
{
HostThreadLock guard;
auto& system = Core::System::GetInstance();
auto& jit_interface = system.GetJitInterface();
const Core::CPUThreadGuard cpu_guard(system);
if (jit_interface.GetCore() == nullptr)
{
env->CallStaticVoidMethod(native_library_class, IDCache::GetDisplayToastMsg(),
ToJString(env, Common::GetStringT("JIT is not active")), JNI_FALSE);
return;
}
jit_interface.WipeBlockProfilingData(cpu_guard);
}
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_WriteJitBlockLogDump(
JNIEnv* env, jclass native_library_class)
{

View File

@ -75,6 +75,8 @@ add_library(common
Hash.cpp
Hash.h
HookableEvent.h
HostDisassembler.cpp
HostDisassembler.h
HttpRequest.cpp
HttpRequest.h
Image.cpp
@ -180,6 +182,11 @@ PRIVATE
${VTUNE_LIBRARIES}
)
if ((DEFINED CMAKE_ANDROID_ARCH_ABI AND CMAKE_ANDROID_ARCH_ABI MATCHES "x86|x86_64") OR
(NOT DEFINED CMAKE_ANDROID_ARCH_ABI AND _M_X86_64))
target_link_libraries(common PRIVATE bdisasm)
endif()
if (APPLE)
target_link_libraries(common
PRIVATE
@ -330,6 +337,28 @@ if(OPROFILE_FOUND)
target_link_libraries(common PRIVATE OProfile::OProfile)
endif()
if(ENABLE_LLVM)
find_package(LLVM CONFIG)
if(LLVM_FOUND)
message(STATUS "LLVM found, enabling LLVM support in disassembler")
target_compile_definitions(common PRIVATE HAVE_LLVM)
# Minimal documentation about LLVM's CMake functions is available here:
# https://releases.llvm.org/16.0.0/docs/CMake.html#embedding-llvm-in-your-project
# https://groups.google.com/g/llvm-dev/c/YeEVe7HTasQ?pli=1
#
# However, you have to read the source code in any case.
# Look for LLVM-Config.cmake in your (Unix) system:
# $ find /usr -name LLVM-Config\\.cmake 2>/dev/null
llvm_expand_pseudo_components(LLVM_EXPAND_COMPONENTS
AllTargetsInfos AllTargetsDisassemblers AllTargetsCodeGens
)
llvm_config(common USE_SHARED
mcdisassembler target ${LLVM_EXPAND_COMPONENTS}
)
target_include_directories(common PRIVATE ${LLVM_INCLUDE_DIRS})
endif()
endif()
if(UNIX)
# Posix networking code needs to be fixed for Windows
add_executable(traversal_server TraversalServer.cpp)

View File

@ -0,0 +1,158 @@
// Copyright 2008 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Common/HostDisassembler.h"
#include <span>
#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
#if defined(HAVE_LLVM)
class HostDisassemblerLLVM final : public HostDisassembler
{
public:
explicit HostDisassemblerLLVM(const char* host_disasm, const char* cpu = "",
std::size_t inst_size = 0);
~HostDisassemblerLLVM();
private:
LLVMDisasmContextRef m_llvm_context;
std::size_t m_instruction_size;
std::size_t Disassemble(const u8* begin, const u8* end, std::ostream& stream) override;
};
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, cpu, nullptr, 0, nullptr, nullptr);
// Couldn't create llvm context
if (!m_llvm_context)
return;
LLVMSetDisasmOptions(m_llvm_context, LLVMDisassembler_Option_AsmPrinterVariant |
LLVMDisassembler_Option_PrintLatency);
}
HostDisassemblerLLVM::~HostDisassemblerLLVM()
{
if (m_llvm_context)
LLVMDisasmDispose(m_llvm_context);
}
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;
while (begin < end)
{
char inst_disasm[256];
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)
{
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.
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.
fmt::println(stream, "{}\tInvalid inst: {:02x}", fmt::ptr(begin),
fmt::join(std::span{begin, end}, ""));
break;
}
}
else
{
fmt::println(stream, "{}{}", fmt::ptr(begin), inst_disasm);
begin += inst_size;
}
++instruction_count;
}
return instruction_count;
}
#elif defined(_M_X86_64)
class HostDisassemblerBochs final : public HostDisassembler
{
public:
explicit HostDisassemblerBochs();
~HostDisassemblerBochs() = default;
private:
disassembler m_disasm;
std::size_t Disassemble(const u8* begin, const u8* end, std::ostream& stream) override;
};
HostDisassemblerBochs::HostDisassemblerBochs()
{
m_disasm.set_syntax_intel();
}
std::size_t HostDisassemblerBochs::Disassemble(const u8* begin, const u8* end, std::ostream& stream)
{
std::size_t instruction_count = 0;
while (begin < end)
{
char inst_disasm[256];
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> HostDisassembler::Factory(Platform arch)
{
switch (arch)
{
#if defined(HAVE_LLVM)
case Platform::x86_64:
return std::make_unique<HostDisassemblerLLVM>("x86_64-none-unknown");
case Platform::aarch64:
return std::make_unique<HostDisassemblerLLVM>("aarch64-none-unknown", "cortex-a57", 4);
#elif defined(_M_X86_64)
case Platform::x86_64:
return std::make_unique<HostDisassemblerBochs>();
#else
case Platform{}: // warning C4065: "switch statement contains 'default' but no 'case' labels"
#endif
default:
return std::make_unique<HostDisassembler>();
}
}
std::size_t HostDisassembler::Disassemble(const u8* begin, const u8* end, std::ostream& stream)
{
fmt::println(stream, "{}\t{:02x}", fmt::ptr(begin), fmt::join(std::span{begin, end}, ""));
return 0;
}

View File

@ -0,0 +1,26 @@
// Copyright 2008 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstddef>
#include <iosfwd>
#include <memory>
#include "Common/CommonTypes.h"
class HostDisassembler
{
public:
enum class Platform
{
x86_64,
aarch64,
};
virtual ~HostDisassembler() = default;
static std::unique_ptr<HostDisassembler> Factory(Platform arch);
virtual std::size_t Disassemble(const u8* begin, const u8* end, std::ostream& stream);
};

View File

@ -479,6 +479,7 @@ add_library(core
PatchEngine.h
PowerPC/BreakPoints.cpp
PowerPC/BreakPoints.h
PowerPC/CachedInterpreter/CachedInterpreter_Disassembler.cpp
PowerPC/CachedInterpreter/CachedInterpreter.cpp
PowerPC/CachedInterpreter/CachedInterpreter.h
PowerPC/CachedInterpreter/CachedInterpreterBlockCache.cpp
@ -652,11 +653,6 @@ PRIVATE
ZLIB::ZLIB
)
if ((DEFINED CMAKE_ANDROID_ARCH_ABI AND CMAKE_ANDROID_ARCH_ABI MATCHES "x86|x86_64") OR
(NOT DEFINED CMAKE_ANDROID_ARCH_ABI AND _M_X86_64))
target_link_libraries(core PRIVATE bdisasm)
endif()
if (APPLE)
target_link_libraries(core
PRIVATE

View File

@ -195,6 +195,11 @@ u32 GetHookByFunctionAddress(PPCSymbolDB& ppc_symbol_db, u32 address)
return (symbol && symbol->address == address) ? index : 0;
}
const char* GetHookNameByIndex(u32 index)
{
return os_patches[index].name;
}
HookType GetHookTypeByIndex(u32 index)
{
return os_patches[index].type;

View File

@ -69,6 +69,7 @@ void ExecuteFromJIT(u32 current_pc, u32 hook_index, Core::System& system);
u32 GetHookByAddress(u32 address);
// Returns the HLE hook index if the address matches the function start
u32 GetHookByFunctionAddress(PPCSymbolDB& ppc_symbol_db, u32 address);
const char* GetHookNameByIndex(u32 index);
HookType GetHookTypeByIndex(u32 index);
HookFlag GetHookFlagsByIndex(u32 index);

View File

@ -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();

View File

@ -3,13 +3,22 @@
#include "Core/PowerPC/CachedInterpreter/CachedInterpreter.h"
#include <span>
#include <sstream>
#include <utility>
#include <fmt/format.h>
#include <fmt/ostream.h>
#include "Common/CommonTypes.h"
#include "Common/GekkoDisassembler.h"
#include "Common/Logging/Log.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#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"
@ -289,14 +298,15 @@ void CachedInterpreter::Jit(u32 em_address, bool clear_cache_and_retry_on_failur
b->near_end = GetWritableCodePtr();
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);
#ifdef JIT_LOG_GENERATED_CODE
LogGeneratedCode();
#endif
return;
}
@ -370,15 +380,16 @@ bool CachedInterpreter::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
{interpreter, Interpreter::GetInterpreterOp(op.inst), js.compilerPC, op.inst},
power_pc,
js.downcountAmount};
Write(op.canEndBlock ? InterpretAndCheckExceptions<true> :
InterpretAndCheckExceptions<false>,
Write(op.canEndBlock ? CallbackCast(InterpretAndCheckExceptions<true>) :
CallbackCast(InterpretAndCheckExceptions<false>),
operands);
}
else
{
const InterpretOperands operands = {interpreter, Interpreter::GetInterpreterOp(op.inst),
js.compilerPC, op.inst};
Write(op.canEndBlock ? Interpret<true> : Interpret<false>, operands);
Write(op.canEndBlock ? CallbackCast(Interpret<true>) : CallbackCast(Interpret<false>),
operands);
}
if (op.branchIsIdleLoop)
@ -401,6 +412,29 @@ bool CachedInterpreter::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
return true;
}
void CachedInterpreter::EraseSingleBlock(const JitBlock& block)
{
m_block_cache.EraseSingleBlock(block);
FreeRanges();
}
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();
@ -408,4 +442,24 @@ void CachedInterpreter::ClearCache()
ClearCodeSpace();
ResetFreeMemoryRanges();
RefreshConfig();
Host_JitCacheCleared();
}
void CachedInterpreter::LogGeneratedCode() const
{
std::ostringstream stream;
stream << "\nPPC Code Buffer:\n";
for (const PPCAnalyst::CodeOp& op :
std::span{m_code_buffer.data(), code_block.m_num_instructions})
{
fmt::print(stream, "0x{:08x}\t\t{}\n", op.address,
Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address));
}
stream << "\nHost Code:\n";
Disassemble(*js.curBlock, stream);
// TODO C++20: std::ostringstream::view()
DEBUG_LOG_FMT(DYNA_REC, "{}", std::move(stream).str());
}

View File

@ -46,6 +46,14 @@ public:
void Jit(u32 address, bool clear_cache_and_retry_on_failure);
bool DoJit(u32 address, JitBlock* b, u32 nextPC);
void EraseSingleBlock(const JitBlock& block) override;
std::vector<MemoryStats> GetMemoryStats() const override;
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; }
@ -63,6 +71,8 @@ private:
void FreeRanges();
void ResetFreeMemoryRanges();
void LogGeneratedCode() const;
struct StartProfiledBlockOperands;
template <bool profiled>
struct EndBlockOperands;
@ -74,20 +84,33 @@ private:
struct CheckIdleOperands;
static s32 StartProfiledBlock(PowerPC::PowerPCState& ppc_state,
const StartProfiledBlockOperands& profile_data);
const StartProfiledBlockOperands& operands);
static s32 StartProfiledBlock(std::ostream& stream, const StartProfiledBlockOperands& operands);
template <bool profiled>
static s32 EndBlock(PowerPC::PowerPCState& ppc_state, const EndBlockOperands<profiled>& operands);
template <bool profiled>
static s32 EndBlock(std::ostream& stream, const EndBlockOperands<profiled>& operands);
template <bool write_pc>
static s32 Interpret(PowerPC::PowerPCState& ppc_state, const InterpretOperands& operands);
template <bool write_pc>
static s32 Interpret(std::ostream& stream, const InterpretOperands& operands);
template <bool write_pc>
static s32 InterpretAndCheckExceptions(PowerPC::PowerPCState& ppc_state,
const InterpretAndCheckExceptionsOperands& operands);
template <bool write_pc>
static s32 InterpretAndCheckExceptions(std::ostream& stream,
const InterpretAndCheckExceptionsOperands& operands);
static s32 HLEFunction(PowerPC::PowerPCState& ppc_state, const HLEFunctionOperands& operands);
static s32 HLEFunction(std::ostream& stream, const HLEFunctionOperands& operands);
static s32 WriteBrokenBlockNPC(PowerPC::PowerPCState& ppc_state,
const WriteBrokenBlockNPCOperands& operands);
static s32 WriteBrokenBlockNPC(std::ostream& stream, const WriteBrokenBlockNPCOperands& operands);
static s32 CheckFPU(PowerPC::PowerPCState& ppc_state, const CheckHaltOperands& operands);
static s32 CheckFPU(std::ostream& stream, const CheckHaltOperands& operands);
static s32 CheckBreakpoint(PowerPC::PowerPCState& ppc_state, const CheckHaltOperands& operands);
static s32 CheckBreakpoint(std::ostream& stream, const CheckHaltOperands& operands);
static s32 CheckIdle(PowerPC::PowerPCState& ppc_state, const CheckIdleOperands& operands);
static s32 CheckIdle(std::ostream& stream, const CheckIdleOperands& operands);
HyoutaUtilities::RangeSizeSet<u8*> m_free_ranges;
CachedInterpreterBlockCache m_block_cache;

View File

@ -4,6 +4,7 @@
#pragma once
#include <cstddef>
#include <iosfwd>
#include <type_traits>
#include "Common/CodeBlock.h"
@ -24,6 +25,11 @@ protected:
using Callback = s32 (*)(PowerPC::PowerPCState& ppc_state, const Operands& operands);
using AnyCallback = s32 (*)(PowerPC::PowerPCState& ppc_state, const void* operands);
template <class Operands>
static consteval Callback<Operands> CallbackCast(Callback<Operands> callback)
{
return callback;
}
template <class Operands>
static AnyCallback AnyCallbackCast(Callback<Operands> callback)
{
@ -31,6 +37,21 @@ protected:
}
static consteval AnyCallback AnyCallbackCast(AnyCallback callback) { return callback; }
// Disassemble callbacks will always return the distance to the next callback.
template <class Operands>
using Disassemble = s32 (*)(std::ostream& stream, const Operands& operands);
using AnyDisassemble = s32 (*)(std::ostream& stream, const void* operands);
template <class Operands>
static AnyDisassemble AnyDisassembleCast(Disassemble<Operands> disassemble)
{
return reinterpret_cast<AnyDisassemble>(disassemble);
}
static consteval AnyDisassemble AnyDisassembleCast(AnyDisassemble disassemble)
{
return disassemble;
}
public:
CachedInterpreterEmitter() = default;
explicit CachedInterpreterEmitter(u8* begin, u8* end) : m_code(begin), m_code_end(end) {}
@ -63,6 +84,7 @@ public:
}
static s32 PoisonCallback(PowerPC::PowerPCState& ppc_state, const void* operands);
static s32 PoisonCallback(std::ostream& stream, const void* operands);
private:
void Write(AnyCallback callback, const void* operands, std::size_t size);

View File

@ -0,0 +1,143 @@
// Copyright 2024 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Core/PowerPC/CachedInterpreter/CachedInterpreter.h"
#include <algorithm>
#include <array>
#include <mutex>
#include <utility>
#include <fmt/format.h>
#include <fmt/ostream.h>
#include "Core/HLE/HLE.h"
s32 CachedInterpreterEmitter::PoisonCallback(std::ostream& stream, const void* operands)
{
stream << "PoisonCallback()\n";
return sizeof(AnyCallback);
}
s32 CachedInterpreter::StartProfiledBlock(std::ostream& stream,
const StartProfiledBlockOperands& operands)
{
stream << "StartProfiledBlock()\n";
return sizeof(AnyCallback) + sizeof(operands);
}
template <bool profiled>
s32 CachedInterpreter::EndBlock(std::ostream& stream, const EndBlockOperands<profiled>& operands)
{
fmt::println(stream, "EndBlock<profiled={}>(downcount={}, num_load_stores={}, num_fp_inst={})",
profiled, operands.downcount, operands.num_load_stores, operands.num_fp_inst);
return sizeof(AnyCallback) + sizeof(operands);
}
template <bool write_pc>
s32 CachedInterpreter::Interpret(std::ostream& stream, const InterpretOperands& operands)
{
fmt::println(stream, "Interpret<write_pc={:5}>(current_pc=0x{:08x}, inst=0x{:08x})", write_pc,
operands.current_pc, operands.inst.hex);
return sizeof(AnyCallback) + sizeof(operands);
}
template <bool write_pc>
s32 CachedInterpreter::InterpretAndCheckExceptions(
std::ostream& stream, const InterpretAndCheckExceptionsOperands& operands)
{
fmt::println(stream,
"InterpretAndCheckExceptions<write_pc={:5}>(current_pc=0x{:08x}, inst=0x{:08x}, "
"downcount={})",
write_pc, operands.current_pc, operands.inst.hex, operands.downcount);
return sizeof(AnyCallback) + sizeof(operands);
}
s32 CachedInterpreter::HLEFunction(std::ostream& stream, const HLEFunctionOperands& operands)
{
const auto& [system, current_pc, hook_index] = operands;
fmt::println(stream, "HLEFunction(current_pc=0x{:08x}, hook_index={}) [\"{}\"]", current_pc,
hook_index, HLE::GetHookNameByIndex(hook_index));
return sizeof(AnyCallback) + sizeof(operands);
}
s32 CachedInterpreter::WriteBrokenBlockNPC(std::ostream& stream,
const WriteBrokenBlockNPCOperands& operands)
{
const auto& [current_pc] = operands;
fmt::println(stream, "WriteBrokenBlockNPC(current_pc=0x{:08x})", current_pc);
return sizeof(AnyCallback) + sizeof(operands);
}
s32 CachedInterpreter::CheckFPU(std::ostream& stream, const CheckHaltOperands& operands)
{
const auto& [power_pc, current_pc, downcount] = operands;
fmt::println(stream, "CheckFPU(current_pc=0x{:08x}, downcount={})", current_pc, downcount);
return sizeof(AnyCallback) + sizeof(operands);
}
s32 CachedInterpreter::CheckBreakpoint(std::ostream& stream, const CheckHaltOperands& operands)
{
const auto& [power_pc, current_pc, downcount] = operands;
fmt::println(stream, "CheckBreakpoint(current_pc=0x{:08x}, downcount={})", current_pc, downcount);
return sizeof(AnyCallback) + sizeof(operands);
}
s32 CachedInterpreter::CheckIdle(std::ostream& stream, const CheckIdleOperands& operands)
{
const auto& [core_timing, idle_pc] = operands;
fmt::println(stream, "CheckIdle(idle_pc=0x{:08x})", idle_pc);
return sizeof(AnyCallback) + sizeof(operands);
}
static std::once_flag s_sorted_lookup_flag;
std::size_t CachedInterpreter::Disassemble(const JitBlock& block, std::ostream& stream)
{
using LookupKV = std::pair<AnyCallback, AnyDisassemble>;
// clang-format off
#define LOOKUP_KV(...) {AnyCallbackCast(__VA_ARGS__), AnyDisassembleCast(__VA_ARGS__)}
// clang-format on
// Function addresses aren't known at compile-time, so this array is sorted at run-time.
static auto sorted_lookup = std::to_array<LookupKV>({
LOOKUP_KV(CachedInterpreter::PoisonCallback),
LOOKUP_KV(CachedInterpreter::StartProfiledBlock),
LOOKUP_KV(CachedInterpreter::EndBlock<false>),
LOOKUP_KV(CachedInterpreter::EndBlock<true>),
LOOKUP_KV(CachedInterpreter::Interpret<false>),
LOOKUP_KV(CachedInterpreter::Interpret<true>),
LOOKUP_KV(CachedInterpreter::InterpretAndCheckExceptions<false>),
LOOKUP_KV(CachedInterpreter::InterpretAndCheckExceptions<true>),
LOOKUP_KV(CachedInterpreter::HLEFunction),
LOOKUP_KV(CachedInterpreter::WriteBrokenBlockNPC),
LOOKUP_KV(CachedInterpreter::CheckFPU),
LOOKUP_KV(CachedInterpreter::CheckBreakpoint),
LOOKUP_KV(CachedInterpreter::CheckIdle),
});
#undef LOOKUP_KV
std::call_once(s_sorted_lookup_flag, []() {
const auto end = std::ranges::sort(sorted_lookup, {}, &LookupKV::first);
ASSERT_MSG(DYNA_REC, std::ranges::adjacent_find(sorted_lookup, {}, &LookupKV::first) == end,
"Sorted lookup should not contain duplicate keys.");
});
std::size_t instruction_count = 0;
for (const u8* normal_entry = block.normalEntry; normal_entry != block.near_end;
++instruction_count)
{
const auto callback = *reinterpret_cast<const AnyCallback*>(normal_entry);
const auto kv = std::ranges::lower_bound(sorted_lookup, callback, {}, &LookupKV::first);
if (kv != sorted_lookup.end() && kv->first == callback)
{
normal_entry += kv->second(stream, normal_entry + sizeof(AnyCallback));
continue;
}
stream << "UNKNOWN OR ILLEGAL CALLBACK\n";
break;
}
return instruction_count;
}

View File

@ -4,11 +4,12 @@
#include "Core/PowerPC/Jit64/Jit.h"
#include <map>
#include <span>
#include <sstream>
#include <string>
#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,18 @@ void Jit64::ClearCache()
RefreshConfig();
asm_routines.Regenerate();
ResetFreeMemoryRanges();
Host_JitCacheCleared();
}
void Jit64::FreeRanges()
{
// Check if any code blocks have been freed in the block cache and transfer this information to
// the local rangesets to allow overwriting them with new code.
for (const auto& [from, to] : blocks.GetRangesToFreeNear())
m_free_ranges_near.insert(from, to);
for (const auto& [from, to] : blocks.GetRangesToFreeFar())
m_free_ranges_far.insert(from, to);
blocks.ClearRangesToFree();
}
void Jit64::ResetFreeMemoryRanges()
@ -746,14 +763,7 @@ void Jit64::Jit(u32 em_address, bool clear_cache_and_retry_on_failure)
}
ClearCache();
}
// Check if any code blocks have been freed in the block cache and transfer this information to
// the local rangesets to allow overwriting them with new code.
for (auto range : blocks.GetRangesToFreeNear())
m_free_ranges_near.insert(range.first, range.second);
for (auto range : blocks.GetRangesToFreeFar())
m_free_ranges_far.insert(range.first, range.second);
blocks.ClearRangesToFree();
FreeRanges();
std::size_t block_size = m_code_buffer.size();
@ -822,7 +832,11 @@ 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);
#ifdef JIT_LOG_GENERATED_CODE
LogGeneratedCode();
#endif
return;
}
}
@ -1178,6 +1192,12 @@ bool Jit64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
WriteExit(nextPC);
}
// When linking to an entry point immediately following it in memory, a JIT block's furthest
// exit can, as a micro-optimization, overwrite the JMP instruction with a multibyte NOP.
// See: 'JitBlockCache::WriteLinkBlock'
// In order to do this in a non-sketchy way, a JIT block must own the alignment padding bytes.
AlignCode4(); // TODO: Test if this or AlignCode16 make a difference from GetCodePtr
if (HasWriteFailed() || m_far_code.HasWriteFailed())
{
if (HasWriteFailed())
@ -1188,16 +1208,30 @@ bool Jit64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
return false;
}
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);
#endif
return true;
}
void Jit64::EraseSingleBlock(const JitBlock& block)
{
blocks.EraseSingleBlock(block);
FreeRanges();
}
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
{
return m_disassembler->Disassemble(block.normalEntry, block.near_end, stream);
}
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;
@ -1277,39 +1311,24 @@ bool Jit64::HandleFunctionHooking(u32 address)
return true;
}
void LogGeneratedX86(size_t size, const PPCAnalyst::CodeBuffer& code_buffer, const u8* normalEntry,
const JitBlock* b)
void Jit64::LogGeneratedCode() const
{
for (size_t i = 0; i < size; i++)
std::ostringstream stream;
stream << "\nPPC Code Buffer:\n";
for (const PPCAnalyst::CodeOp& op :
std::span{m_code_buffer.data(), code_block.m_num_instructions})
{
const PPCAnalyst::CodeOp& op = code_buffer[i];
const std::string disasm = Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address);
DEBUG_LOG_FMT(DYNA_REC, "IR_X86 PPC: {:08x} {}\n", op.address, disasm);
fmt::print(stream, "0x{:08x}\t\t{}\n", op.address,
Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address));
}
disassembler x64disasm;
x64disasm.set_syntax_intel();
const JitBlock* const block = js.curBlock;
stream << "\nHost Near Code:\n";
m_disassembler->Disassemble(block->normalEntry, block->near_end, stream);
stream << "\nHost Far Code:\n";
m_disassembler->Disassemble(block->far_begin, block->far_end, stream);
u64 disasmPtr = reinterpret_cast<u64>(normalEntry);
const u8* end = normalEntry + b->codeSize;
while (reinterpret_cast<u8*>(disasmPtr) < end)
{
char sptr[1000] = "";
disasmPtr += x64disasm.disasm64(disasmPtr, disasmPtr, reinterpret_cast<u8*>(disasmPtr), sptr);
DEBUG_LOG_FMT(DYNA_REC, "IR_X86 x86: {}", sptr);
}
if (b->codeSize <= 250)
{
std::ostringstream ss;
ss << std::hex;
for (u8 i = 0; i <= b->codeSize; i++)
{
ss.width(2);
ss.fill('0');
ss << static_cast<u32>(*(normalEntry + i));
}
DEBUG_LOG_FMT(DYNA_REC, "IR_X86 bin: {}\n\n\n", ss.str());
}
// TODO C++20: std::ostringstream::view()
DEBUG_LOG_FMT(DYNA_REC, "{}", std::move(stream).str());
}

View File

@ -34,6 +34,7 @@
#include "Core/PowerPC/JitCommon/JitBase.h"
#include "Core/PowerPC/JitCommon/JitCache.h"
class HostDisassembler;
namespace PPCAnalyst
{
struct CodeBlock;
@ -65,6 +66,12 @@ public:
void Jit(u32 em_address, bool clear_cache_and_retry_on_failure);
bool DoJit(u32 em_address, JitBlock* b, u32 nextPC);
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();
@ -266,8 +273,11 @@ private:
bool HandleFunctionHooking(u32 address);
void FreeRanges();
void ResetFreeMemoryRanges();
void LogGeneratedCode() const;
static void ImHere(Jit64& jit);
JitBlockCache blocks{*this};
@ -284,7 +294,5 @@ 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,
const JitBlock* b);

View File

@ -5,10 +5,17 @@
#include <cstdio>
#include <optional>
#include <span>
#include <sstream>
#include <fmt/format.h>
#include <fmt/ostream.h>
#include "Common/Arm64Emitter.h"
#include "Common/CommonTypes.h"
#include "Common/EnumUtils.h"
#include "Common/GekkoDisassembler.h"
#include "Common/HostDisassembler.h"
#include "Common/Logging/Log.h"
#include "Common/MathUtil.h"
#include "Common/MsgHandler.h"
@ -22,6 +29,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 +47,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 +196,36 @@ void JitArm64::GenerateAsmAndResetFreeMemoryRanges()
ResetFreeMemoryRanges(routines_near_end - routines_near_start,
routines_far_end - routines_far_start);
Host_JitCacheCleared();
}
void JitArm64::FreeRanges()
{
// Check if any code blocks have been freed in the block cache and transfer this information to
// the local rangesets to allow overwriting them with new code.
for (const auto& [from, to] : blocks.GetRangesToFreeNear())
{
const auto first_fastmem_area = m_fault_to_handler.upper_bound(from);
auto last_fastmem_area = first_fastmem_area;
const auto end = m_fault_to_handler.end();
while (last_fastmem_area != end && last_fastmem_area->first <= to)
++last_fastmem_area;
m_fault_to_handler.erase(first_fastmem_area, last_fastmem_area);
if (from < m_near_code_0.GetCodeEnd())
m_free_ranges_near_0.insert(from, to);
else
m_free_ranges_near_1.insert(from, to);
}
for (const auto& [from, to] : blocks.GetRangesToFreeFar())
{
if (from < m_far_code_0.GetCodeEnd())
m_free_ranges_far_0.insert(from, to);
else
m_free_ranges_far_1.insert(from, to);
}
blocks.ClearRangesToFree();
}
void JitArm64::ResetFreeMemoryRanges(size_t routines_near_size, size_t routines_far_size)
@ -911,31 +951,7 @@ void JitArm64::Jit(u32 em_address, bool clear_cache_and_retry_on_failure)
if (SConfig::GetInstance().bJITNoBlockCache)
ClearCache();
// Check if any code blocks have been freed in the block cache and transfer this information to
// the local rangesets to allow overwriting them with new code.
for (auto range : blocks.GetRangesToFreeNear())
{
auto first_fastmem_area = m_fault_to_handler.upper_bound(range.first);
auto last_fastmem_area = first_fastmem_area;
auto end = m_fault_to_handler.end();
while (last_fastmem_area != end && last_fastmem_area->first <= range.second)
++last_fastmem_area;
m_fault_to_handler.erase(first_fastmem_area, last_fastmem_area);
if (range.first < m_near_code_0.GetCodeEnd())
m_free_ranges_near_0.insert(range.first, range.second);
else
m_free_ranges_near_1.insert(range.first, range.second);
}
for (auto range : blocks.GetRangesToFreeFar())
{
if (range.first < m_far_code_0.GetCodeEnd())
m_free_ranges_far_0.insert(range.first, range.second);
else
m_free_ranges_far_1.insert(range.first, range.second);
}
blocks.ClearRangesToFree();
FreeRanges();
const Common::ScopedJITPageWriteAndNoExecute enable_jit_page_writes;
@ -1010,7 +1026,11 @@ 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);
#ifdef JIT_LOG_GENERATED_CODE
LogGeneratedCode();
#endif
return;
}
}
@ -1030,6 +1050,30 @@ void JitArm64::Jit(u32 em_address, bool clear_cache_and_retry_on_failure)
exit(-1);
}
void JitArm64::EraseSingleBlock(const JitBlock& block)
{
blocks.EraseSingleBlock(block);
FreeRanges();
}
std::vector<JitBase::MemoryStats> JitArm64::GetMemoryStats() const
{
return {{"near_0", m_free_ranges_near_0.get_stats()},
{"near_1", m_free_ranges_near_1.get_stats()},
{"far_0", m_free_ranges_far_0.get_stats()},
{"far_1", m_free_ranges_far_1.get_stats()}};
}
std::size_t JitArm64::DisassembleNearCode(const JitBlock& block, std::ostream& stream) const
{
return m_disassembler->Disassemble(block.normalEntry, block.near_end, stream);
}
std::size_t JitArm64::DisassembleFarCode(const JitBlock& block, std::ostream& stream) const
{
return m_disassembler->Disassemble(block.far_begin, block.far_end, stream);
}
std::optional<size_t> JitArm64::SetEmitterStateToFreeCodeRegion()
{
// Find some large free memory blocks and set code emitters to point at them. If we can't find
@ -1347,11 +1391,30 @@ bool JitArm64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
return false;
}
b->codeSize = static_cast<u32>(GetCodePtr() - b->normalEntry);
b->originalSize = code_block.m_num_instructions;
FlushIcache();
m_far_code.FlushIcache();
return true;
}
void JitArm64::LogGeneratedCode() const
{
std::ostringstream stream;
stream << "\nPPC Code Buffer:\n";
for (const PPCAnalyst::CodeOp& op :
std::span{m_code_buffer.data(), code_block.m_num_instructions})
{
fmt::print(stream, "0x{:08x}\t\t{}\n", op.address,
Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address));
}
const JitBlock* const block = js.curBlock;
stream << "\nHost Near Code:\n";
m_disassembler->Disassemble(block->normalEntry, block->near_end, stream);
stream << "\nHost Far Code:\n";
m_disassembler->Disassemble(block->far_begin, block->far_end, stream);
// TODO C++20: std::ostringstream::view()
DEBUG_LOG_FMT(DYNA_REC, "{}", std::move(stream).str());
}

View File

@ -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:
@ -48,6 +50,12 @@ public:
void Jit(u32 em_address) override;
void Jit(u32 em_address, bool clear_cache_and_retry_on_failure);
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
@ -294,11 +302,14 @@ protected:
void Cleanup();
void ResetStack();
void FreeRanges();
void GenerateAsmAndResetFreeMemoryRanges();
void ResetFreeMemoryRanges(size_t routines_near_size, size_t routines_far_size);
void IntializeSpeculativeConstants();
void LogGeneratedCode() const;
// AsmRoutines
void GenerateAsm();
void GenerateCommonAsm();
@ -409,4 +420,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;
};

View File

@ -5,9 +5,12 @@
#include <array>
#include <cstddef>
#include <iosfwd>
#include <map>
#include <string_view>
#include <unordered_set>
#include <utility>
#include <vector>
#include "Common/BitSet.h"
#include "Common/CommonTypes.h"
@ -194,6 +197,15 @@ public:
virtual void Jit(u32 em_address) = 0;
virtual void EraseSingleBlock(const JitBlock& block) = 0;
// Memory region name, free size, and fragmentation ratio
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;

View File

@ -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"
@ -117,6 +120,16 @@ void JitBaseBlockCache::RunOnBlocks(const Core::CPUThreadGuard&,
f(e.second);
}
void JitBaseBlockCache::WipeBlockProfilingData(const Core::CPUThreadGuard&)
{
for (const auto& kv : block_map)
{
if (JitBlock::ProfileData* const profile_data = kv.second.profile_data.get())
*profile_data = {};
}
Host_JitProfileDataWiped();
}
JitBlock* JitBaseBlockCache::AllocateBlock(u32 em_address)
{
const u32 physical_address = m_jit.m_mmu.JitCache_TranslateAddress(em_address).address;
@ -130,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)
@ -144,13 +158,23 @@ 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;
u32 range_mask = ~(BLOCK_RANGE_MAP_ELEMENTS - 1);
for (u32 addr : 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)
{
valid_block.Set(addr / 32);
block_range_map[addr & range_mask].insert(&block);
block_range_map[addr & BLOCK_RANGE_MAP_MASK].insert(&block);
}
if (block_link)
@ -167,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);
}
}
@ -324,8 +349,7 @@ void JitBaseBlockCache::InvalidateICacheInternal(u32 physical_address, u32 addre
void JitBaseBlockCache::ErasePhysicalRange(u32 address, u32 length)
{
// Iterate over all macro blocks which overlap the given range.
u32 range_mask = ~(BLOCK_RANGE_MAP_ELEMENTS - 1);
auto start = block_range_map.lower_bound(address & range_mask);
auto start = block_range_map.lower_bound(address & BLOCK_RANGE_MAP_MASK);
auto end = block_range_map.lower_bound(address + length);
while (start != end)
{
@ -339,8 +363,8 @@ void JitBaseBlockCache::ErasePhysicalRange(u32 address, u32 length)
// If the block overlaps, also remove all other occupied slots in the other macro blocks.
// This will leak empty macro blocks, but they may be reused or cleared later on.
for (u32 addr : block->physical_addresses)
if ((addr & range_mask) != start->first)
block_range_map[addr & range_mask].erase(block);
if ((addr & BLOCK_RANGE_MAP_MASK) != start->first)
block_range_map[addr & BLOCK_RANGE_MAP_MASK].erase(block);
// And remove the block.
DestroyBlock(*block);
@ -370,6 +394,23 @@ void JitBaseBlockCache::ErasePhysicalRange(u32 address, u32 length)
}
}
void JitBaseBlockCache::EraseSingleBlock(const JitBlock& block)
{
const auto equal_range = block_map.equal_range(block.physicalAddress);
const auto block_map_iter = std::ranges::find(equal_range.first, equal_range.second, &block,
[](const auto& kv) { return &kv.second; });
if (block_map_iter == equal_range.second) [[unlikely]]
return;
JitBlock& mutable_block = block_map_iter->second;
for (const u32 addr : mutable_block.physical_addresses)
block_range_map[addr & BLOCK_RANGE_MAP_MASK].erase(&mutable_block);
DestroyBlock(mutable_block);
block_map.erase(block_map_iter); // The original JitBlock reference is now dangling.
}
u32* JitBaseBlockCache::GetBlockBitSet() const
{
return valid_block.m_valid_block.get();

View File

@ -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;
@ -44,9 +45,6 @@ struct JitBlockData
// and valid_block in particular). This is useful because of
// of the way the instruction cache works on PowerPC.
u32 physicalAddress;
// The number of bytes of JIT'ed code contained in this block. Mostly
// useful for logging.
u32 codeSize;
// The number of PPC instructions represented by this block. Mostly
// useful for logging.
u32 originalSize;
@ -105,6 +103,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;
};
@ -161,9 +163,12 @@ public:
u8** GetEntryPoints();
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.
@ -179,6 +184,7 @@ public:
void InvalidateICache(u32 address, u32 length, bool forced);
void InvalidateICacheLine(u32 address);
void ErasePhysicalRange(u32 address, u32 length);
void EraseSingleBlock(const JitBlock& block);
u32* GetBlockBitSet() const;
@ -212,7 +218,7 @@ private:
// Range of overlapping code indexed by a masked physical address.
// This is used for invalidation of memory regions. The range is grouped
// in macro blocks of each 0x100 bytes.
static constexpr u32 BLOCK_RANGE_MAP_ELEMENTS = 0x100;
static constexpr u32 BLOCK_RANGE_MAP_MASK = ~(0x100 - 1);
std::map<u32, std::unordered_set<JitBlock*>> block_range_map;
// This bitsets shows which cachelines overlap with any blocks.

View File

@ -182,46 +182,24 @@ void JitInterface::JitBlockLogDump(const Core::CPUThreadGuard& guard, std::FILE*
}
}
std::variant<JitInterface::GetHostCodeError, JitInterface::GetHostCodeResult>
JitInterface::GetHostCode(u32 address) const
void JitInterface::WipeBlockProfilingData(const Core::CPUThreadGuard& guard)
{
if (!m_jit)
{
return GetHostCodeError::NoJitActive;
}
if (m_jit)
m_jit->GetBlockCache()->WipeBlockProfilingData(guard);
}
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;
}
void JitInterface::RunOnBlocks(const Core::CPUThreadGuard& guard,
std::function<void(const JitBlock&)> f) const
{
if (m_jit)
m_jit->GetBlockCache()->RunOnBlocks(guard, std::move(f));
}
if (block)
{
if (!(block->effectiveAddress <= address &&
block->originalSize + block->effectiveAddress >= address))
block = nullptr;
}
// Do not merge this "if" with the above - block changes inside it.
if (!block)
{
return GetHostCodeError::NoTranslation;
}
}
GetHostCodeResult result;
result.code = block->normalEntry;
result.code_size = block->codeSize;
result.entry_address = block->effectiveAddress;
return result;
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)
@ -257,6 +235,33 @@ void JitInterface::ClearSafe()
m_jit->GetBlockCache()->Clear();
}
void JitInterface::EraseSingleBlock(const JitBlock& block)
{
if (m_jit)
m_jit->EraseSingleBlock(block);
}
std::vector<JitBase::MemoryStats> JitInterface::GetMemoryStats() const
{
if (m_jit)
return m_jit->GetMemoryStats();
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)

View File

@ -6,9 +6,11 @@
#include <cstddef>
#include <cstdio>
#include <functional>
#include <iosfwd>
#include <memory>
#include <string>
#include <variant>
#include <string_view>
#include <utility>
#include <vector>
#include "Common/CommonTypes.h"
#include "Core/MachineContext.h"
@ -16,6 +18,7 @@
class CPUCoreBase;
class PointerWrap;
class JitBase;
struct JitBlock;
namespace Core
{
@ -42,22 +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;
std::variant<GetHostCodeError, GetHostCodeResult> GetHostCode(u32 address) const;
void WipeBlockProfilingData(const Core::CPUThreadGuard& guard);
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);
@ -71,6 +63,19 @@ public:
// the JIT'ed code.
void ClearSafe();
// DolphinQt's JITWidget needs EraseSingleBlock. Nothing else (from outside of the Core) should
// use it, or else JitBlockTableModel will contain a dangling reference. If something else from
// outside of the Core *must* use this, consider reworking the logic in JITWidget.
void EraseSingleBlock(const JitBlock& block);
// Memory region name, free size, and fragmentation ratio
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);

View File

@ -117,6 +117,7 @@
<ClInclude Include="Common\GL\GLX11Window.h" />
<ClInclude Include="Common\Hash.h" />
<ClInclude Include="Common\HookableEvent.h" />
<ClInclude Include="Common\HostDisassembler.h" />
<ClInclude Include="Common\HRWrap.h" />
<ClInclude Include="Common\HttpRequest.h" />
<ClInclude Include="Common\Image.h" />
@ -549,7 +550,6 @@
<ClInclude Include="InputCommon\KeyboardStatus.h" />
<ClInclude Include="UICommon\AutoUpdate.h" />
<ClInclude Include="UICommon\CommandLineParse.h" />
<ClInclude Include="UICommon\Disassembler.h" />
<ClInclude Include="UICommon\DiscordPresence.h" />
<ClInclude Include="UICommon\GameFile.h" />
<ClInclude Include="UICommon\GameFileCache.h" />
@ -809,6 +809,7 @@
<ClCompile Include="Common\GL\GLInterface\WGL.cpp" />
<ClCompile Include="Common\GL\GLUtil.cpp" />
<ClCompile Include="Common\Hash.cpp" />
<ClCompile Include="Common\HostDisassembler.cpp" />
<ClCompile Include="Common\HRWrap.cpp" />
<ClCompile Include="Common\HttpRequest.cpp" />
<ClCompile Include="Common\Image.cpp" />
@ -1089,6 +1090,7 @@
<ClCompile Include="Core\NetworkCaptureLogger.cpp" />
<ClCompile Include="Core\PatchEngine.cpp" />
<ClCompile Include="Core\PowerPC\BreakPoints.cpp" />
<ClCompile Include="Core\PowerPC\CachedInterpreter\CachedInterpreter_Disassembler.cpp" />
<ClCompile Include="Core\PowerPC\CachedInterpreter\CachedInterpreter.cpp" />
<ClCompile Include="Core\PowerPC\CachedInterpreter\CachedInterpreterBlockCache.cpp" />
<ClCompile Include="Core\PowerPC\CachedInterpreter\CachedInterpreterEmitter.cpp" />
@ -1206,7 +1208,6 @@
<ClCompile Include="InputCommon\InputProfile.cpp" />
<ClCompile Include="UICommon\AutoUpdate.cpp" />
<ClCompile Include="UICommon\CommandLineParse.cpp" />
<ClCompile Include="UICommon\Disassembler.cpp" />
<ClCompile Include="UICommon\DiscordPresence.cpp" />
<ClCompile Include="UICommon\GameFile.cpp" />
<ClCompile Include="UICommon\GameFileCache.cpp" />

View File

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

View File

@ -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

View File

@ -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()

View File

@ -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();

View File

@ -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();

View File

@ -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:

View File

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

View File

@ -4,42 +4,128 @@
#pragma once
#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
public:
explicit JITWidget(QWidget* parent = nullptr);
~JITWidget();
void Compare(u32 address);
using Column = JitBlockTableModelColumn::EnumType;
using UserRole = JitBlockTableModelUserRole::EnumType;
signals:
void HideSignal();
void ShowSignal();
void SetCodeAddress(u32 address);
public:
explicit JITWidget(Core::System& system, QWidget* parent = nullptr);
~JITWidget() override;
JITWidget(const JITWidget&) = delete;
JITWidget(JITWidget&&) = delete;
JITWidget& operator=(const JITWidget&) = delete;
JITWidget& operator=(JITWidget&&) = delete;
// Always connected slots (external signals)
void OnRequestPPCComparison(u32 address, bool translate_address);
private:
void Update();
void CreateWidgets();
void ConnectWidgets();
void closeEvent(QCloseEvent*) override;
void closeEvent(QCloseEvent* event) override;
void showEvent(QShowEvent* event) override;
void hideEvent(QHideEvent* event) override;
QTableWidget* m_table_widget;
QTextBrowser* m_ppc_asm_widget;
QTextBrowser* m_host_asm_widget;
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;
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;
};

View File

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

View File

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

View File

@ -145,6 +145,7 @@
<ClCompile Include="Debugger\CodeViewWidget.cpp" />
<ClCompile Include="Debugger\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" />

View File

@ -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(); });

View File

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

View File

@ -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);

View File

@ -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())); });
@ -155,7 +157,8 @@ void MenuBar::OnEmulationStateChanged(Core::State state)
m_jit_clear_cache->setEnabled(running);
m_jit_log_coverage->setEnabled(!running);
m_jit_search_instruction->setEnabled(running);
m_jit_write_cache_log_dump->setEnabled(running && jit_exists);
m_jit_wipe_profiling_data->setEnabled(jit_exists);
m_jit_write_cache_log_dump->setEnabled(jit_exists);
// Symbols
m_symbols->setEnabled(running);
@ -166,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
@ -196,6 +205,12 @@ void MenuBar::OnDebugModeToggled(bool enabled)
}
}
void MenuBar::OnWipeJitBlockProfilingData()
{
auto& system = Core::System::GetInstance();
system.GetJitInterface().WipeBlockProfilingData(Core::CPUThreadGuard{system});
}
void MenuBar::OnWriteJitBlockLogDump()
{
const std::string filename = fmt::format("{}{}.txt", File::GetUserPath(D_DUMPDEBUG_JITBLOCKS_IDX),
@ -922,6 +937,8 @@ void MenuBar::AddJITMenu()
connect(m_jit_profile_blocks, &QAction::toggled, [](bool enabled) {
Config::SetBaseOrCurrent(Config::MAIN_DEBUG_JIT_ENABLE_PROFILING, enabled);
});
m_jit_wipe_profiling_data = m_jit->addAction(tr("Wipe JIT Block Profiling Data"), this,
&MenuBar::OnWipeJitBlockProfilingData);
m_jit_write_cache_log_dump =
m_jit->addAction(tr("Write JIT Block Log Dump"), this, &MenuBar::OnWriteJitBlockLogDump);

View File

@ -127,6 +127,7 @@ signals:
private:
void OnEmulationStateChanged(Core::State state);
void OnConfigChanged();
void AddFileMenu();
@ -185,6 +186,7 @@ private:
void OnRecordingStatusChanged(bool recording);
void OnReadOnlyModeChanged(bool read_only);
void OnDebugModeToggled(bool enabled);
void OnWipeJitBlockProfilingData();
void OnWriteJitBlockLogDump();
QString GetSignatureSelector() const;
@ -270,6 +272,7 @@ private:
QAction* m_jit_log_coverage;
QAction* m_jit_search_instruction;
QAction* m_jit_profile_blocks;
QAction* m_jit_wipe_profiling_data;
QAction* m_jit_write_cache_log_dump;
QAction* m_jit_off;
QAction* m_jit_loadstore_off;

View File

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

View File

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

View File

@ -3,8 +3,6 @@ add_library(uicommon
AutoUpdate.h
CommandLineParse.cpp
CommandLineParse.h
Disassembler.cpp
Disassembler.h
DiscordPresence.cpp
DiscordPresence.h
GameFile.cpp
@ -59,28 +57,6 @@ if(TARGET LibUSB::LibUSB)
target_link_libraries(uicommon PRIVATE LibUSB::LibUSB)
endif()
if(ENABLE_LLVM)
find_package(LLVM CONFIG)
if(LLVM_FOUND)
message(STATUS "LLVM found, enabling LLVM support in disassembler")
target_compile_definitions(uicommon PRIVATE HAVE_LLVM)
# Minimal documentation about LLVM's CMake functions is available here:
# https://releases.llvm.org/16.0.0/docs/CMake.html#embedding-llvm-in-your-project
# https://groups.google.com/g/llvm-dev/c/YeEVe7HTasQ?pli=1
#
# However, you have to read the source code in any case.
# Look for LLVM-Config.cmake in your (Unix) system:
# $ find /usr -name LLVM-Config\\.cmake 2>/dev/null
llvm_expand_pseudo_components(LLVM_EXPAND_COMPONENTS
AllTargetsInfos AllTargetsDisassemblers AllTargetsCodeGens
)
llvm_config(uicommon USE_SHARED
mcdisassembler target ${LLVM_EXPAND_COMPONENTS}
)
target_include_directories(uicommon PRIVATE ${LLVM_INCLUDE_DIRS})
endif()
endif()
if(USE_DISCORD_PRESENCE)
target_compile_definitions(uicommon PRIVATE -DUSE_DISCORD_PRESENCE)
target_link_libraries(uicommon PRIVATE discord-rpc)

View File

@ -1,208 +0,0 @@
// Copyright 2008 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "UICommon/Disassembler.h"
#include <sstream>
#if defined(HAVE_LLVM)
#include <fmt/format.h>
#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
{
public:
HostDisassemblerLLVM(const std::string& host_disasm, int inst_size = -1,
const std::string& cpu = "");
~HostDisassemblerLLVM()
{
if (m_can_disasm)
LLVMDisasmDispose(m_llvm_context);
}
private:
bool m_can_disasm;
LLVMDisasmContextRef m_llvm_context;
int m_instruction_size;
std::string DisassembleHostBlock(const u8* code_start, const u32 code_size,
u32* host_instructions_count, u64 starting_pc) override;
};
HostDisassemblerLLVM::HostDisassemblerLLVM(const std::string& host_disasm, int inst_size,
const std::string& cpu)
: m_can_disasm(false), m_instruction_size(inst_size)
{
LLVMInitializeAllTargetInfos();
LLVMInitializeAllTargetMCs();
LLVMInitializeAllDisassemblers();
m_llvm_context =
LLVMCreateDisasmCPU(host_disasm.c_str(), cpu.c_str(), nullptr, 0, nullptr, nullptr);
// Couldn't create llvm context
if (!m_llvm_context)
return;
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)
{
if (!m_can_disasm)
return "(No LLVM context)";
u8* disasmPtr = (u8*)code_start;
const u8* end = code_start + code_size;
std::ostringstream x86_disasm;
while ((u8*)disasmPtr < 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)
{
x86_disasm << "Invalid inst:";
if (m_instruction_size != -1)
{
// 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;
}
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;
break;
}
}
else
{
x86_disasm << inst_disasm << std::endl;
disasmPtr += inst_size;
starting_pc += inst_size;
}
(*host_instructions_count)++;
}
return x86_disasm.str();
}
#elif defined(_M_X86_64)
class HostDisassemblerX86 : public HostDisassembler
{
public:
HostDisassemblerX86();
private:
disassembler m_disasm;
std::string DisassembleHostBlock(const u8* code_start, const u32 code_size,
u32* host_instructions_count, u64 starting_pc) override;
};
HostDisassemblerX86::HostDisassemblerX86()
{
m_disasm.set_syntax_intel();
}
std::string HostDisassemblerX86::DisassembleHostBlock(const u8* code_start, const u32 code_size,
u32* host_instructions_count, u64 starting_pc)
{
u64 disasmPtr = (u64)code_start;
const u8* end = code_start + code_size;
std::ostringstream x86_disasm;
while ((u8*)disasmPtr < 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();
}
#endif
std::unique_ptr<HostDisassembler> GetNewDisassembler(const std::string& arch)
{
#if defined(HAVE_LLVM)
if (arch == "x86")
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");
#elif defined(_M_X86_64)
if (arch == "x86")
return std::make_unique<HostDisassemblerX86>();
#endif
return std::make_unique<HostDisassembler>();
}
DisassembleResult DisassembleBlock(HostDisassembler* disasm, u32 address)
{
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);
}

View File

@ -1,30 +0,0 @@
// Copyright 2008 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#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)
{
return "(No disassembler)";
}
};
struct DisassembleResult
{
std::string text;
u32 entry_address = 0;
u32 instruction_count = 0;
u32 code_size = 0;
};
std::unique_ptr<HostDisassembler> GetNewDisassembler(const std::string& arch);
DisassembleResult DisassembleBlock(HostDisassembler* disasm, u32 address);

View File

@ -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()
{
}

View File

@ -40,6 +40,10 @@ public:
// JitBase methods
JitBaseBlockCache* GetBlockCache() override { return nullptr; }
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
{

View File

@ -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()
{
}