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 <cassert>
#include <cstddef> #include <cstddef>
#include <map> #include <map>
#include <utility>
namespace HyoutaUtilities { namespace HyoutaUtilities {
template <typename T> class RangeSet { template <typename T> class RangeSet {
@ -254,7 +255,31 @@ public:
return !(*this == other); 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: 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: // Assumptions that can be made about the data:
// - Range are stored in the form [from, to[ // - Range are stored in the form [from, to[
// That is, the starting value is inclusive, and the end value is exclusive. // That is, the starting value is inclusive, and the end value is exclusive.

View File

@ -4,6 +4,7 @@
#include <cstddef> #include <cstddef>
#include <map> #include <map>
#include <type_traits> #include <type_traits>
#include <utility>
namespace HyoutaUtilities { namespace HyoutaUtilities {
// Like RangeSet, but additionally stores a map of the ranges sorted by their size, for quickly finding the largest or // 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); 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: private:
static SizeT calc_size(T from, T to) { static SizeT calc_size(T from, T to) {
if constexpr (std::is_pointer_v<T>) { if constexpr (std::is_pointer_v<T>) {

View File

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

View File

@ -2013,6 +2013,16 @@ class SettingsFragmentPresenter(
0 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( sl.add(
RunRunnable( RunRunnable(
context, context,

View File

@ -412,6 +412,8 @@
<string name="debug_large_entry_points_map">Disable Large Entry Points Map</string> <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_profiling_header">Jit Profiling</string>
<string name="debug_jit_enable_block_profiling">Enable Jit Block 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_write_block_log_dump">Write Jit Block Log Dump</string>
<string name="debug_jit_header">Jit</string> <string name="debug_jit_header">Jit</string>
<string name="debug_jitoff">Jit Disabled</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() void Host_UpdateMainFrame()
{ {
} }
@ -410,6 +418,22 @@ JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetMaxLogLev
return static_cast<jint>(Common::Log::MAX_LOGLEVEL); 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( JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_WriteJitBlockLogDump(
JNIEnv* env, jclass native_library_class) JNIEnv* env, jclass native_library_class)
{ {

View File

@ -75,6 +75,8 @@ add_library(common
Hash.cpp Hash.cpp
Hash.h Hash.h
HookableEvent.h HookableEvent.h
HostDisassembler.cpp
HostDisassembler.h
HttpRequest.cpp HttpRequest.cpp
HttpRequest.h HttpRequest.h
Image.cpp Image.cpp
@ -180,6 +182,11 @@ PRIVATE
${VTUNE_LIBRARIES} ${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) if (APPLE)
target_link_libraries(common target_link_libraries(common
PRIVATE PRIVATE
@ -330,6 +337,28 @@ if(OPROFILE_FOUND)
target_link_libraries(common PRIVATE OProfile::OProfile) target_link_libraries(common PRIVATE OProfile::OProfile)
endif() 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) if(UNIX)
# Posix networking code needs to be fixed for Windows # Posix networking code needs to be fixed for Windows
add_executable(traversal_server TraversalServer.cpp) 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 PatchEngine.h
PowerPC/BreakPoints.cpp PowerPC/BreakPoints.cpp
PowerPC/BreakPoints.h PowerPC/BreakPoints.h
PowerPC/CachedInterpreter/CachedInterpreter_Disassembler.cpp
PowerPC/CachedInterpreter/CachedInterpreter.cpp PowerPC/CachedInterpreter/CachedInterpreter.cpp
PowerPC/CachedInterpreter/CachedInterpreter.h PowerPC/CachedInterpreter/CachedInterpreter.h
PowerPC/CachedInterpreter/CachedInterpreterBlockCache.cpp PowerPC/CachedInterpreter/CachedInterpreterBlockCache.cpp
@ -652,11 +653,6 @@ PRIVATE
ZLIB::ZLIB 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) if (APPLE)
target_link_libraries(core target_link_libraries(core
PRIVATE PRIVATE

View File

@ -195,6 +195,11 @@ u32 GetHookByFunctionAddress(PPCSymbolDB& ppc_symbol_db, u32 address)
return (symbol && symbol->address == address) ? index : 0; return (symbol && symbol->address == address) ? index : 0;
} }
const char* GetHookNameByIndex(u32 index)
{
return os_patches[index].name;
}
HookType GetHookTypeByIndex(u32 index) HookType GetHookTypeByIndex(u32 index)
{ {
return os_patches[index].type; 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); u32 GetHookByAddress(u32 address);
// Returns the HLE hook index if the address matches the function start // Returns the HLE hook index if the address matches the function start
u32 GetHookByFunctionAddress(PPCSymbolDB& ppc_symbol_db, u32 address); u32 GetHookByFunctionAddress(PPCSymbolDB& ppc_symbol_db, u32 address);
const char* GetHookNameByIndex(u32 index);
HookType GetHookTypeByIndex(u32 index); HookType GetHookTypeByIndex(u32 index);
HookFlag GetHookFlagsByIndex(u32 index); HookFlag GetHookFlagsByIndex(u32 index);

View File

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

View File

@ -3,13 +3,22 @@
#include "Core/PowerPC/CachedInterpreter/CachedInterpreter.h" #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/CommonTypes.h"
#include "Common/GekkoDisassembler.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
#include "Core/Core.h" #include "Core/Core.h"
#include "Core/CoreTiming.h" #include "Core/CoreTiming.h"
#include "Core/HLE/HLE.h" #include "Core/HLE/HLE.h"
#include "Core/HW/CPU.h" #include "Core/HW/CPU.h"
#include "Core/Host.h"
#include "Core/PowerPC/Gekko.h" #include "Core/PowerPC/Gekko.h"
#include "Core/PowerPC/Interpreter/Interpreter.h" #include "Core/PowerPC/Interpreter/Interpreter.h"
#include "Core/PowerPC/Jit64Common/Jit64Constants.h" #include "Core/PowerPC/Jit64Common/Jit64Constants.h"
@ -289,14 +298,15 @@ void CachedInterpreter::Jit(u32 em_address, bool clear_cache_and_retry_on_failur
b->near_end = GetWritableCodePtr(); b->near_end = GetWritableCodePtr();
b->far_begin = b->far_end = nullptr; b->far_begin = b->far_end = nullptr;
b->codeSize = static_cast<u32>(b->near_end - b->normalEntry);
b->originalSize = code_block.m_num_instructions;
// Mark the memory region that this code block uses in the RangeSizeSet. // Mark the memory region that this code block uses in the RangeSizeSet.
if (b->near_begin != b->near_end) if (b->near_begin != b->near_end)
m_free_ranges.erase(b->near_begin, b->near_end); m_free_ranges.erase(b->near_begin, b->near_end);
m_block_cache.FinalizeBlock(*b, jo.enableBlocklink, code_block.m_physical_addresses); m_block_cache.FinalizeBlock(*b, jo.enableBlocklink, code_block, m_code_buffer);
#ifdef JIT_LOG_GENERATED_CODE
LogGeneratedCode();
#endif
return; return;
} }
@ -370,15 +380,16 @@ bool CachedInterpreter::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
{interpreter, Interpreter::GetInterpreterOp(op.inst), js.compilerPC, op.inst}, {interpreter, Interpreter::GetInterpreterOp(op.inst), js.compilerPC, op.inst},
power_pc, power_pc,
js.downcountAmount}; js.downcountAmount};
Write(op.canEndBlock ? InterpretAndCheckExceptions<true> : Write(op.canEndBlock ? CallbackCast(InterpretAndCheckExceptions<true>) :
InterpretAndCheckExceptions<false>, CallbackCast(InterpretAndCheckExceptions<false>),
operands); operands);
} }
else else
{ {
const InterpretOperands operands = {interpreter, Interpreter::GetInterpreterOp(op.inst), const InterpretOperands operands = {interpreter, Interpreter::GetInterpreterOp(op.inst),
js.compilerPC, 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) if (op.branchIsIdleLoop)
@ -401,6 +412,29 @@ bool CachedInterpreter::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
return true; 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() void CachedInterpreter::ClearCache()
{ {
m_block_cache.Clear(); m_block_cache.Clear();
@ -408,4 +442,24 @@ void CachedInterpreter::ClearCache()
ClearCodeSpace(); ClearCodeSpace();
ResetFreeMemoryRanges(); ResetFreeMemoryRanges();
RefreshConfig(); 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); void Jit(u32 address, bool clear_cache_and_retry_on_failure);
bool DoJit(u32 address, JitBlock* b, u32 nextPC); 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; } JitBaseBlockCache* GetBlockCache() override { return &m_block_cache; }
const char* GetName() const override { return "Cached Interpreter"; } const char* GetName() const override { return "Cached Interpreter"; }
const CommonAsmRoutinesBase* GetAsmRoutines() override { return nullptr; } const CommonAsmRoutinesBase* GetAsmRoutines() override { return nullptr; }
@ -63,6 +71,8 @@ private:
void FreeRanges(); void FreeRanges();
void ResetFreeMemoryRanges(); void ResetFreeMemoryRanges();
void LogGeneratedCode() const;
struct StartProfiledBlockOperands; struct StartProfiledBlockOperands;
template <bool profiled> template <bool profiled>
struct EndBlockOperands; struct EndBlockOperands;
@ -74,20 +84,33 @@ private:
struct CheckIdleOperands; struct CheckIdleOperands;
static s32 StartProfiledBlock(PowerPC::PowerPCState& ppc_state, 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> template <bool profiled>
static s32 EndBlock(PowerPC::PowerPCState& ppc_state, const EndBlockOperands<profiled>& operands); 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> template <bool write_pc>
static s32 Interpret(PowerPC::PowerPCState& ppc_state, const InterpretOperands& operands); static s32 Interpret(PowerPC::PowerPCState& ppc_state, const InterpretOperands& operands);
template <bool write_pc> template <bool write_pc>
static s32 Interpret(std::ostream& stream, const InterpretOperands& operands);
template <bool write_pc>
static s32 InterpretAndCheckExceptions(PowerPC::PowerPCState& ppc_state, static s32 InterpretAndCheckExceptions(PowerPC::PowerPCState& ppc_state,
const InterpretAndCheckExceptionsOperands& operands); 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(PowerPC::PowerPCState& ppc_state, const HLEFunctionOperands& operands);
static s32 HLEFunction(std::ostream& stream, const HLEFunctionOperands& operands);
static s32 WriteBrokenBlockNPC(PowerPC::PowerPCState& ppc_state, static s32 WriteBrokenBlockNPC(PowerPC::PowerPCState& ppc_state,
const WriteBrokenBlockNPCOperands& operands); 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(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(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(PowerPC::PowerPCState& ppc_state, const CheckIdleOperands& operands);
static s32 CheckIdle(std::ostream& stream, const CheckIdleOperands& operands);
HyoutaUtilities::RangeSizeSet<u8*> m_free_ranges; HyoutaUtilities::RangeSizeSet<u8*> m_free_ranges;
CachedInterpreterBlockCache m_block_cache; CachedInterpreterBlockCache m_block_cache;

View File

@ -4,6 +4,7 @@
#pragma once #pragma once
#include <cstddef> #include <cstddef>
#include <iosfwd>
#include <type_traits> #include <type_traits>
#include "Common/CodeBlock.h" #include "Common/CodeBlock.h"
@ -24,6 +25,11 @@ protected:
using Callback = s32 (*)(PowerPC::PowerPCState& ppc_state, const Operands& operands); using Callback = s32 (*)(PowerPC::PowerPCState& ppc_state, const Operands& operands);
using AnyCallback = s32 (*)(PowerPC::PowerPCState& ppc_state, const void* 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> template <class Operands>
static AnyCallback AnyCallbackCast(Callback<Operands> callback) static AnyCallback AnyCallbackCast(Callback<Operands> callback)
{ {
@ -31,6 +37,21 @@ protected:
} }
static consteval AnyCallback AnyCallbackCast(AnyCallback callback) { return callback; } 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: public:
CachedInterpreterEmitter() = default; CachedInterpreterEmitter() = default;
explicit CachedInterpreterEmitter(u8* begin, u8* end) : m_code(begin), m_code_end(end) {} 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(PowerPC::PowerPCState& ppc_state, const void* operands);
static s32 PoisonCallback(std::ostream& stream, const void* operands);
private: private:
void Write(AnyCallback callback, const void* operands, std::size_t size); 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 "Core/PowerPC/Jit64/Jit.h"
#include <map> #include <map>
#include <span>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <disasm.h>
#include <fmt/format.h> #include <fmt/format.h>
#include <fmt/ostream.h>
// for the PROFILER stuff // for the PROFILER stuff
#ifdef _WIN32 #ifdef _WIN32
@ -18,6 +19,7 @@
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/EnumUtils.h" #include "Common/EnumUtils.h"
#include "Common/GekkoDisassembler.h" #include "Common/GekkoDisassembler.h"
#include "Common/HostDisassembler.h"
#include "Common/IOFile.h" #include "Common/IOFile.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Common/StringUtil.h" #include "Common/StringUtil.h"
@ -30,6 +32,7 @@
#include "Core/HW/GPFifo.h" #include "Core/HW/GPFifo.h"
#include "Core/HW/Memmap.h" #include "Core/HW/Memmap.h"
#include "Core/HW/ProcessorInterface.h" #include "Core/HW/ProcessorInterface.h"
#include "Core/Host.h"
#include "Core/MachineContext.h" #include "Core/MachineContext.h"
#include "Core/PatchEngine.h" #include "Core/PatchEngine.h"
#include "Core/PowerPC/Interpreter/Interpreter.h" #include "Core/PowerPC/Interpreter/Interpreter.h"
@ -116,7 +119,9 @@ using namespace PowerPC;
and such, but it's currently limited to integer ops only. This can definitely be made better. and such, but it's currently limited to integer ops only. This can definitely be made better.
*/ */
Jit64::Jit64(Core::System& system) : JitBase(system), QuantizedMemoryRoutines(*this) Jit64::Jit64(Core::System& system)
: JitBase(system), QuantizedMemoryRoutines(*this),
m_disassembler(HostDisassembler::Factory(HostDisassembler::Platform::x86_64))
{ {
} }
@ -308,6 +313,18 @@ void Jit64::ClearCache()
RefreshConfig(); RefreshConfig();
asm_routines.Regenerate(); asm_routines.Regenerate();
ResetFreeMemoryRanges(); 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() void Jit64::ResetFreeMemoryRanges()
@ -746,14 +763,7 @@ void Jit64::Jit(u32 em_address, bool clear_cache_and_retry_on_failure)
} }
ClearCache(); ClearCache();
} }
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 (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();
std::size_t block_size = m_code_buffer.size(); 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_begin = far_start;
b->far_end = far_end; b->far_end = far_end;
blocks.FinalizeBlock(*b, jo.enableBlocklink, code_block.m_physical_addresses); blocks.FinalizeBlock(*b, jo.enableBlocklink, code_block, m_code_buffer);
#ifdef JIT_LOG_GENERATED_CODE
LogGeneratedCode();
#endif
return; return;
} }
} }
@ -1178,6 +1192,12 @@ bool Jit64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
WriteExit(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() || m_far_code.HasWriteFailed())
{ {
if (HasWriteFailed()) if (HasWriteFailed())
@ -1188,16 +1208,30 @@ bool Jit64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
return false; 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; 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 BitSet8 Jit64::ComputeStaticGQRs(const PPCAnalyst::CodeBlock& cb) const
{ {
return cb.m_gqr_used & ~cb.m_gqr_modified; return cb.m_gqr_used & ~cb.m_gqr_modified;
@ -1277,39 +1311,24 @@ bool Jit64::HandleFunctionHooking(u32 address)
return true; return true;
} }
void LogGeneratedX86(size_t size, const PPCAnalyst::CodeBuffer& code_buffer, const u8* normalEntry, void Jit64::LogGeneratedCode() const
const JitBlock* b)
{ {
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]; fmt::print(stream, "0x{:08x}\t\t{}\n", op.address,
const std::string disasm = Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address); Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address));
DEBUG_LOG_FMT(DYNA_REC, "IR_X86 PPC: {:08x} {}\n", op.address, disasm);
} }
disassembler x64disasm; const JitBlock* const block = js.curBlock;
x64disasm.set_syntax_intel(); 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); // TODO C++20: std::ostringstream::view()
const u8* end = normalEntry + b->codeSize; DEBUG_LOG_FMT(DYNA_REC, "{}", std::move(stream).str());
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());
}
} }

View File

@ -34,6 +34,7 @@
#include "Core/PowerPC/JitCommon/JitBase.h" #include "Core/PowerPC/JitCommon/JitBase.h"
#include "Core/PowerPC/JitCommon/JitCache.h" #include "Core/PowerPC/JitCommon/JitCache.h"
class HostDisassembler;
namespace PPCAnalyst namespace PPCAnalyst
{ {
struct CodeBlock; struct CodeBlock;
@ -65,6 +66,12 @@ public:
void Jit(u32 em_address, bool clear_cache_and_retry_on_failure); void Jit(u32 em_address, bool clear_cache_and_retry_on_failure);
bool DoJit(u32 em_address, JitBlock* b, u32 nextPC); 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. // Finds a free memory region and sets the near and far code emitters to point at that region.
// Returns false if no free memory region can be found for either of the two. // Returns false if no free memory region can be found for either of the two.
bool SetEmitterStateToFreeCodeRegion(); bool SetEmitterStateToFreeCodeRegion();
@ -266,8 +273,11 @@ private:
bool HandleFunctionHooking(u32 address); bool HandleFunctionHooking(u32 address);
void FreeRanges();
void ResetFreeMemoryRanges(); void ResetFreeMemoryRanges();
void LogGeneratedCode() const;
static void ImHere(Jit64& jit); static void ImHere(Jit64& jit);
JitBlockCache blocks{*this}; JitBlockCache blocks{*this};
@ -284,7 +294,5 @@ private:
const bool m_im_here_debug = false; const bool m_im_here_debug = false;
const bool m_im_here_log = false; const bool m_im_here_log = false;
std::map<u32, int> m_been_here; std::map<u32, int> m_been_here;
std::unique_ptr<HostDisassembler> m_disassembler;
}; };
void LogGeneratedX86(size_t size, const PPCAnalyst::CodeBuffer& code_buffer, const u8* normalEntry,
const JitBlock* b);

View File

@ -5,10 +5,17 @@
#include <cstdio> #include <cstdio>
#include <optional> #include <optional>
#include <span>
#include <sstream>
#include <fmt/format.h>
#include <fmt/ostream.h>
#include "Common/Arm64Emitter.h" #include "Common/Arm64Emitter.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/EnumUtils.h" #include "Common/EnumUtils.h"
#include "Common/GekkoDisassembler.h"
#include "Common/HostDisassembler.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Common/MathUtil.h" #include "Common/MathUtil.h"
#include "Common/MsgHandler.h" #include "Common/MsgHandler.h"
@ -22,6 +29,7 @@
#include "Core/HW/GPFifo.h" #include "Core/HW/GPFifo.h"
#include "Core/HW/Memmap.h" #include "Core/HW/Memmap.h"
#include "Core/HW/ProcessorInterface.h" #include "Core/HW/ProcessorInterface.h"
#include "Core/Host.h"
#include "Core/PatchEngine.h" #include "Core/PatchEngine.h"
#include "Core/PowerPC/Interpreter/Interpreter.h" #include "Core/PowerPC/Interpreter/Interpreter.h"
#include "Core/PowerPC/JitArm64/JitArm64_RegCache.h" #include "Core/PowerPC/JitArm64/JitArm64_RegCache.h"
@ -39,7 +47,9 @@ constexpr size_t NEAR_CODE_SIZE = 1024 * 1024 * 64;
constexpr size_t FAR_CODE_SIZE = 1024 * 1024 * 64; constexpr size_t FAR_CODE_SIZE = 1024 * 1024 * 64;
constexpr size_t TOTAL_CODE_SIZE = NEAR_CODE_SIZE * 2 + FAR_CODE_SIZE * 2; constexpr size_t TOTAL_CODE_SIZE = NEAR_CODE_SIZE * 2 + FAR_CODE_SIZE * 2;
JitArm64::JitArm64(Core::System& system) : JitBase(system), m_float_emit(this) JitArm64::JitArm64(Core::System& system)
: JitBase(system), m_float_emit(this),
m_disassembler(HostDisassembler::Factory(HostDisassembler::Platform::aarch64))
{ {
} }
@ -186,6 +196,36 @@ void JitArm64::GenerateAsmAndResetFreeMemoryRanges()
ResetFreeMemoryRanges(routines_near_end - routines_near_start, ResetFreeMemoryRanges(routines_near_end - routines_near_start,
routines_far_end - routines_far_start); routines_far_end - routines_far_start);
Host_JitCacheCleared();
}
void JitArm64::FreeRanges()
{
// 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) 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) if (SConfig::GetInstance().bJITNoBlockCache)
ClearCache(); ClearCache();
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 (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();
const Common::ScopedJITPageWriteAndNoExecute enable_jit_page_writes; 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_begin = far_start;
b->far_end = far_end; b->far_end = far_end;
blocks.FinalizeBlock(*b, jo.enableBlocklink, code_block.m_physical_addresses); blocks.FinalizeBlock(*b, jo.enableBlocklink, code_block, m_code_buffer);
#ifdef JIT_LOG_GENERATED_CODE
LogGeneratedCode();
#endif
return; return;
} }
} }
@ -1030,6 +1050,30 @@ void JitArm64::Jit(u32 em_address, bool clear_cache_and_retry_on_failure)
exit(-1); 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() std::optional<size_t> JitArm64::SetEmitterStateToFreeCodeRegion()
{ {
// Find some large free memory blocks and set code emitters to point at them. If we can't find // Find some large free memory blocks and set code emitters to point at them. If we can't find
@ -1347,11 +1391,30 @@ bool JitArm64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
return false; return false;
} }
b->codeSize = static_cast<u32>(GetCodePtr() - b->normalEntry);
b->originalSize = code_block.m_num_instructions;
FlushIcache(); FlushIcache();
m_far_code.FlushIcache(); m_far_code.FlushIcache();
return true; 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/JitCommon/JitBase.h"
#include "Core/PowerPC/PPCAnalyst.h" #include "Core/PowerPC/PPCAnalyst.h"
class HostDisassembler;
class JitArm64 : public JitBase, public Arm64Gen::ARM64CodeBlock, public CommonAsmRoutinesBase class JitArm64 : public JitBase, public Arm64Gen::ARM64CodeBlock, public CommonAsmRoutinesBase
{ {
public: public:
@ -48,6 +50,12 @@ public:
void Jit(u32 em_address) override; void Jit(u32 em_address) override;
void Jit(u32 em_address, bool clear_cache_and_retry_on_failure); 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"; } const char* GetName() const override { return "JITARM64"; }
// OPCODES // OPCODES
@ -294,11 +302,14 @@ protected:
void Cleanup(); void Cleanup();
void ResetStack(); void ResetStack();
void FreeRanges();
void GenerateAsmAndResetFreeMemoryRanges(); void GenerateAsmAndResetFreeMemoryRanges();
void ResetFreeMemoryRanges(size_t routines_near_size, size_t routines_far_size); void ResetFreeMemoryRanges(size_t routines_near_size, size_t routines_far_size);
void IntializeSpeculativeConstants(); void IntializeSpeculativeConstants();
void LogGeneratedCode() const;
// AsmRoutines // AsmRoutines
void GenerateAsm(); void GenerateAsm();
void GenerateCommonAsm(); void GenerateCommonAsm();
@ -409,4 +420,6 @@ protected:
HyoutaUtilities::RangeSizeSet<u8*> m_free_ranges_near_1; HyoutaUtilities::RangeSizeSet<u8*> m_free_ranges_near_1;
HyoutaUtilities::RangeSizeSet<u8*> m_free_ranges_far_0; HyoutaUtilities::RangeSizeSet<u8*> m_free_ranges_far_0;
HyoutaUtilities::RangeSizeSet<u8*> m_free_ranges_far_1; HyoutaUtilities::RangeSizeSet<u8*> m_free_ranges_far_1;
std::unique_ptr<HostDisassembler> m_disassembler;
}; };

View File

@ -5,9 +5,12 @@
#include <array> #include <array>
#include <cstddef> #include <cstddef>
#include <iosfwd>
#include <map> #include <map>
#include <string_view>
#include <unordered_set> #include <unordered_set>
#include <utility> #include <utility>
#include <vector>
#include "Common/BitSet.h" #include "Common/BitSet.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
@ -194,6 +197,15 @@ public:
virtual void Jit(u32 em_address) = 0; 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 const CommonAsmRoutinesBase* GetAsmRoutines() = 0;
virtual bool HandleFault(uintptr_t access_address, SContext* ctx) = 0; virtual bool HandleFault(uintptr_t access_address, SContext* ctx) = 0;

View File

@ -8,13 +8,16 @@
#include <cstring> #include <cstring>
#include <functional> #include <functional>
#include <map> #include <map>
#include <ranges>
#include <set> #include <set>
#include <span>
#include <utility> #include <utility>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/JitRegister.h" #include "Common/JitRegister.h"
#include "Core/Config/MainSettings.h" #include "Core/Config/MainSettings.h"
#include "Core/Core.h" #include "Core/Core.h"
#include "Core/Host.h"
#include "Core/PowerPC/JitCommon/JitBase.h" #include "Core/PowerPC/JitCommon/JitBase.h"
#include "Core/PowerPC/MMU.h" #include "Core/PowerPC/MMU.h"
#include "Core/PowerPC/PPCSymbolDB.h" #include "Core/PowerPC/PPCSymbolDB.h"
@ -117,6 +120,16 @@ void JitBaseBlockCache::RunOnBlocks(const Core::CPUThreadGuard&,
f(e.second); 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) JitBlock* JitBaseBlockCache::AllocateBlock(u32 em_address)
{ {
const u32 physical_address = m_jit.m_mmu.JitCache_TranslateAddress(em_address).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, void JitBaseBlockCache::FinalizeBlock(JitBlock& block, bool block_link,
const std::set<u32>& physical_addresses) const PPCAnalyst::CodeBlock& code_block,
const PPCAnalyst::CodeBuffer& code_buffer)
{ {
size_t index = FastLookupIndexForAddress(block.effectiveAddress, block.feature_flags); size_t index = FastLookupIndexForAddress(block.effectiveAddress, block.feature_flags);
if (m_entry_points_ptr) if (m_entry_points_ptr)
@ -144,13 +158,23 @@ void JitBaseBlockCache::FinalizeBlock(JitBlock& block, bool block_link,
} }
block.fast_block_map_index = index; block.fast_block_map_index = index;
block.physical_addresses = physical_addresses; block.physical_addresses = code_block.m_physical_addresses;
u32 range_mask = ~(BLOCK_RANGE_MAP_ELEMENTS - 1); block.originalSize = code_block.m_num_instructions;
for (u32 addr : physical_addresses) 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); 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) if (block_link)
@ -167,13 +191,14 @@ void JitBaseBlockCache::FinalizeBlock(JitBlock& block, bool block_link,
if (Common::JitRegister::IsEnabled() && if (Common::JitRegister::IsEnabled() &&
(symbol = m_jit.m_ppc_symbol_db.GetSymbolFromAddr(block.effectiveAddress)) != nullptr) (symbol = m_jit.m_ppc_symbol_db.GetSymbolFromAddr(block.effectiveAddress)) != nullptr)
{ {
Common::JitRegister::Register(block.normalEntry, block.codeSize, "JIT_PPC_{}_{:08x}", Common::JitRegister::Register(block.normalEntry, block.near_end - block.normalEntry,
symbol->function_name.c_str(), block.physicalAddress); "JIT_PPC_{}_{:08x}", symbol->function_name,
block.physicalAddress);
} }
else else
{ {
Common::JitRegister::Register(block.normalEntry, block.codeSize, "JIT_PPC_{:08x}", Common::JitRegister::Register(block.normalEntry, block.near_end - block.normalEntry,
block.physicalAddress); "JIT_PPC_{:08x}", block.physicalAddress);
} }
} }
@ -324,8 +349,7 @@ void JitBaseBlockCache::InvalidateICacheInternal(u32 physical_address, u32 addre
void JitBaseBlockCache::ErasePhysicalRange(u32 address, u32 length) void JitBaseBlockCache::ErasePhysicalRange(u32 address, u32 length)
{ {
// Iterate over all macro blocks which overlap the given range. // 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 & BLOCK_RANGE_MAP_MASK);
auto start = block_range_map.lower_bound(address & range_mask);
auto end = block_range_map.lower_bound(address + length); auto end = block_range_map.lower_bound(address + length);
while (start != end) 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. // 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. // This will leak empty macro blocks, but they may be reused or cleared later on.
for (u32 addr : block->physical_addresses) for (u32 addr : block->physical_addresses)
if ((addr & range_mask) != start->first) if ((addr & BLOCK_RANGE_MAP_MASK) != start->first)
block_range_map[addr & range_mask].erase(block); block_range_map[addr & BLOCK_RANGE_MAP_MASK].erase(block);
// And remove the block. // And remove the block.
DestroyBlock(*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 u32* JitBaseBlockCache::GetBlockBitSet() const
{ {
return valid_block.m_valid_block.get(); return valid_block.m_valid_block.get();

View File

@ -19,6 +19,7 @@
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Core/HW/Memmap.h" #include "Core/HW/Memmap.h"
#include "Core/PowerPC/Gekko.h" #include "Core/PowerPC/Gekko.h"
#include "Core/PowerPC/PPCAnalyst.h"
class JitBase; class JitBase;
@ -44,9 +45,6 @@ struct JitBlockData
// and valid_block in particular). This is useful because of // and valid_block in particular). This is useful because of
// of the way the instruction cache works on PowerPC. // of the way the instruction cache works on PowerPC.
u32 physicalAddress; 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 // The number of PPC instructions represented by this block. Mostly
// useful for logging. // useful for logging.
u32 originalSize; u32 originalSize;
@ -105,6 +103,10 @@ struct JitBlock : public JitBlockData
// This set stores all physical addresses of all occupied instructions. // This set stores all physical addresses of all occupied instructions.
std::set<u32> physical_addresses; std::set<u32> physical_addresses;
// This is only available when debugging is enabled. It is a trimmed-down copy of the
// PPCAnalyst::CodeBuffer used to recompile this block, including repeat instructions.
std::vector<std::pair<u32, UGeckoInstruction>> original_buffer;
std::unique_ptr<ProfileData> profile_data; std::unique_ptr<ProfileData> profile_data;
}; };
@ -161,9 +163,12 @@ public:
u8** GetEntryPoints(); u8** GetEntryPoints();
JitBlock** GetFastBlockMapFallback(); JitBlock** GetFastBlockMapFallback();
void RunOnBlocks(const Core::CPUThreadGuard& guard, std::function<void(const JitBlock&)> f) const; void RunOnBlocks(const Core::CPUThreadGuard& guard, std::function<void(const JitBlock&)> f) const;
void WipeBlockProfilingData(const Core::CPUThreadGuard& guard);
std::size_t GetBlockCount() const { return block_map.size(); }
JitBlock* AllocateBlock(u32 em_address); JitBlock* AllocateBlock(u32 em_address);
void FinalizeBlock(JitBlock& block, bool block_link, const std::set<u32>& physical_addresses); void FinalizeBlock(JitBlock& block, bool block_link, const PPCAnalyst::CodeBlock& code_block,
const PPCAnalyst::CodeBuffer& code_buffer);
// Look for the block in the slow but accurate way. // Look for the block in the slow but accurate way.
// This function shall be used if FastLookupIndexForAddress() failed. // This function shall be used if FastLookupIndexForAddress() failed.
@ -179,6 +184,7 @@ public:
void InvalidateICache(u32 address, u32 length, bool forced); void InvalidateICache(u32 address, u32 length, bool forced);
void InvalidateICacheLine(u32 address); void InvalidateICacheLine(u32 address);
void ErasePhysicalRange(u32 address, u32 length); void ErasePhysicalRange(u32 address, u32 length);
void EraseSingleBlock(const JitBlock& block);
u32* GetBlockBitSet() const; u32* GetBlockBitSet() const;
@ -212,7 +218,7 @@ private:
// Range of overlapping code indexed by a masked physical address. // Range of overlapping code indexed by a masked physical address.
// This is used for invalidation of memory regions. The range is grouped // This is used for invalidation of memory regions. The range is grouped
// in macro blocks of each 0x100 bytes. // 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; std::map<u32, std::unordered_set<JitBlock*>> block_range_map;
// This bitsets shows which cachelines overlap with any blocks. // 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> void JitInterface::WipeBlockProfilingData(const Core::CPUThreadGuard& guard)
JitInterface::GetHostCode(u32 address) const
{ {
if (!m_jit) if (m_jit)
{ m_jit->GetBlockCache()->WipeBlockProfilingData(guard);
return GetHostCodeError::NoJitActive;
} }
auto& ppc_state = m_system.GetPPCState(); void JitInterface::RunOnBlocks(const Core::CPUThreadGuard& guard,
JitBlock* block = std::function<void(const JitBlock&)> f) const
m_jit->GetBlockCache()->GetBlockFromStartAddress(address, ppc_state.feature_flags);
if (!block)
{ {
for (int i = 0; i < 500; i++) if (m_jit)
{ m_jit->GetBlockCache()->RunOnBlocks(guard, std::move(f));
block = m_jit->GetBlockCache()->GetBlockFromStartAddress(address - 4 * i,
ppc_state.feature_flags);
if (block)
break;
} }
if (block) std::size_t JitInterface::GetBlockCount() const
{ {
if (!(block->effectiveAddress <= address && if (m_jit)
block->originalSize + block->effectiveAddress >= address)) return m_jit->GetBlockCache()->GetBlockCount();
block = nullptr; return 0;
}
// Do not merge this "if" with the above - block changes inside it.
if (!block)
{
return GetHostCodeError::NoTranslation;
}
}
GetHostCodeResult result;
result.code = block->normalEntry;
result.code_size = block->codeSize;
result.entry_address = block->effectiveAddress;
return result;
} }
bool JitInterface::HandleFault(uintptr_t access_address, SContext* ctx) bool JitInterface::HandleFault(uintptr_t access_address, SContext* ctx)
@ -257,6 +235,33 @@ void JitInterface::ClearSafe()
m_jit->GetBlockCache()->Clear(); 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) void JitInterface::InvalidateICache(u32 address, u32 size, bool forced)
{ {
if (m_jit) if (m_jit)

View File

@ -6,9 +6,11 @@
#include <cstddef> #include <cstddef>
#include <cstdio> #include <cstdio>
#include <functional> #include <functional>
#include <iosfwd>
#include <memory> #include <memory>
#include <string> #include <string_view>
#include <variant> #include <utility>
#include <vector>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Core/MachineContext.h" #include "Core/MachineContext.h"
@ -16,6 +18,7 @@
class CPUCoreBase; class CPUCoreBase;
class PointerWrap; class PointerWrap;
class JitBase; class JitBase;
struct JitBlock;
namespace Core namespace Core
{ {
@ -42,22 +45,11 @@ public:
CPUCoreBase* InitJitCore(PowerPC::CPUCore core); CPUCoreBase* InitJitCore(PowerPC::CPUCore core);
CPUCoreBase* GetCore() const; CPUCoreBase* GetCore() const;
// Debugging
enum class GetHostCodeError
{
NoJitActive,
NoTranslation,
};
struct GetHostCodeResult
{
const u8* code;
u32 code_size;
u32 entry_address;
};
void UpdateMembase(); void UpdateMembase();
void JitBlockLogDump(const Core::CPUThreadGuard& guard, std::FILE* file) const; void JitBlockLogDump(const Core::CPUThreadGuard& guard, std::FILE* file) const;
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 // Memory Utilities
bool HandleFault(uintptr_t access_address, SContext* ctx); bool HandleFault(uintptr_t access_address, SContext* ctx);
@ -71,6 +63,19 @@ public:
// the JIT'ed code. // the JIT'ed code.
void ClearSafe(); 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. // If "forced" is true, a recompile is being requested on code that hasn't been modified.
void InvalidateICache(u32 address, u32 size, bool forced); void InvalidateICache(u32 address, u32 size, bool forced);
void InvalidateICacheLine(u32 address); void InvalidateICacheLine(u32 address);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,6 +13,7 @@
#include <QFontDialog> #include <QFontDialog>
#include <QInputDialog> #include <QInputDialog>
#include <QMap> #include <QMap>
#include <QSignalBlocker>
#include <QUrl> #include <QUrl>
#include <fmt/format.h> #include <fmt/format.h>
@ -95,6 +96,7 @@ MenuBar::MenuBar(QWidget* parent) : QMenuBar(parent)
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
[=, this](Core::State state) { OnEmulationStateChanged(state); }); [=, this](Core::State state) { OnEmulationStateChanged(state); });
connect(&Settings::Instance(), &Settings::ConfigChanged, this, &MenuBar::OnConfigChanged);
connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this, connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this,
[this] { OnEmulationStateChanged(Core::GetState(Core::System::GetInstance())); }); [this] { OnEmulationStateChanged(Core::GetState(Core::System::GetInstance())); });
@ -155,7 +157,8 @@ void MenuBar::OnEmulationStateChanged(Core::State state)
m_jit_clear_cache->setEnabled(running); m_jit_clear_cache->setEnabled(running);
m_jit_log_coverage->setEnabled(!running); m_jit_log_coverage->setEnabled(!running);
m_jit_search_instruction->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 // Symbols
m_symbols->setEnabled(running); m_symbols->setEnabled(running);
@ -166,6 +169,12 @@ void MenuBar::OnEmulationStateChanged(Core::State state)
OnDebugModeToggled(Settings::Instance().IsDebugModeEnabled()); OnDebugModeToggled(Settings::Instance().IsDebugModeEnabled());
} }
void MenuBar::OnConfigChanged()
{
const QSignalBlocker blocker(m_jit_profile_blocks);
m_jit_profile_blocks->setChecked(Config::Get(Config::MAIN_DEBUG_JIT_ENABLE_PROFILING));
}
void MenuBar::OnDebugModeToggled(bool enabled) void MenuBar::OnDebugModeToggled(bool enabled)
{ {
// Options // Options
@ -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() void MenuBar::OnWriteJitBlockLogDump()
{ {
const std::string filename = fmt::format("{}{}.txt", File::GetUserPath(D_DUMPDEBUG_JITBLOCKS_IDX), 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) { connect(m_jit_profile_blocks, &QAction::toggled, [](bool enabled) {
Config::SetBaseOrCurrent(Config::MAIN_DEBUG_JIT_ENABLE_PROFILING, 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_write_cache_log_dump =
m_jit->addAction(tr("Write JIT Block Log Dump"), this, &MenuBar::OnWriteJitBlockLogDump); m_jit->addAction(tr("Write JIT Block Log Dump"), this, &MenuBar::OnWriteJitBlockLogDump);

View File

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

View File

@ -3,8 +3,6 @@ add_library(uicommon
AutoUpdate.h AutoUpdate.h
CommandLineParse.cpp CommandLineParse.cpp
CommandLineParse.h CommandLineParse.h
Disassembler.cpp
Disassembler.h
DiscordPresence.cpp DiscordPresence.cpp
DiscordPresence.h DiscordPresence.h
GameFile.cpp GameFile.cpp
@ -59,28 +57,6 @@ if(TARGET LibUSB::LibUSB)
target_link_libraries(uicommon PRIVATE LibUSB::LibUSB) target_link_libraries(uicommon PRIVATE LibUSB::LibUSB)
endif() 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) if(USE_DISCORD_PRESENCE)
target_compile_definitions(uicommon PRIVATE -DUSE_DISCORD_PRESENCE) target_compile_definitions(uicommon PRIVATE -DUSE_DISCORD_PRESENCE)
target_link_libraries(uicommon PRIVATE discord-rpc) 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_UpdateDisasmDialog()
{ {
} }
void Host_JitCacheCleared()
{
}
void Host_JitProfileDataWiped()
{
}
void Host_UpdateMainFrame() void Host_UpdateMainFrame()
{ {
} }

View File

@ -40,6 +40,10 @@ public:
// JitBase methods // JitBase methods
JitBaseBlockCache* GetBlockCache() override { return nullptr; } JitBaseBlockCache* GetBlockCache() override { return nullptr; }
void Jit(u32 em_address) override {} 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; } const CommonAsmRoutinesBase* GetAsmRoutines() override { return nullptr; }
virtual bool HandleFault(uintptr_t access_address, SContext* ctx) override virtual bool HandleFault(uintptr_t access_address, SContext* ctx) override
{ {

View File

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