diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java index 43880bd0bab4..e3b05261113e 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java @@ -385,6 +385,11 @@ public static native void Run(String[] path, boolean riivolution, String savesta public static native boolean IsRunningAndUnpaused(); + /** + * Re-initialize software JitBlock profiling data + */ + public static native void WipeJitBlockProfilingData(); + /** * Writes out the JitBlock Cache log dump */ diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt index 6de39ed1e909..1bd613fcfa38 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt @@ -1987,6 +1987,16 @@ class SettingsFragmentPresenter( 0 ) ) + sl.add( + RunRunnable( + context, + R.string.debug_jit_wipe_block_profiling_data, + 0, + R.string.debug_jit_wipe_block_profiling_data_alert, + 0, + true + ) { NativeLibrary.WipeJitBlockProfilingData() } + ) sl.add( RunRunnable( context, diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index 360e5006a03a..1d6df244255b 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -408,6 +408,8 @@ Disable Large Entry Points Map Jit Profiling Enable Jit Block Profiling + Wipe Jit Block Profiling Data + Re-initialize JIT block profiling data? Write Jit Block Log Dump Jit Jit Disabled diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index 1034406d7a85..67ab51ca668b 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -147,6 +147,14 @@ void Host_UpdateDisasmDialog() { } +void Host_JitCacheCleared() +{ +} + +void Host_JitProfileDataWiped() +{ +} + void Host_UpdateMainFrame() { } @@ -405,6 +413,22 @@ JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetMaxLogLev return static_cast(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(); + if (jit_interface.GetCore() == nullptr) + { + env->CallStaticVoidMethod(native_library_class, IDCache::GetDisplayToastMsg(), + ToJString(env, Common::GetStringT("JIT is not active")), + static_cast(false)); + return; + } + jit_interface.WipeBlockProfilingData(Core::CPUThreadGuard{system}); +} + JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_WriteJitBlockLogDump( JNIEnv* env, jclass native_library_class) { diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index 7b81cd2c6d0f..9b2263bfcba6 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -75,6 +75,8 @@ add_library(common Hash.cpp Hash.h HookableEvent.h + HostDisassembler.cpp + HostDisassembler.h HttpRequest.cpp HttpRequest.h Image.cpp @@ -142,6 +144,7 @@ add_library(common TraversalClient.h TraversalProto.h TypeUtils.h + Unreachable.h UPnP.cpp UPnP.h VariantUtil.h @@ -177,6 +180,11 @@ PRIVATE ${VTUNE_LIBRARIES} ) +if ((DEFINED CMAKE_ANDROID_ARCH_ABI AND CMAKE_ANDROID_ARCH_ABI MATCHES "x86|x86_64") OR + (NOT DEFINED CMAKE_ANDROID_ARCH_ABI AND _M_X86_64)) + target_link_libraries(common PRIVATE bdisasm) +endif() + if (APPLE) target_link_libraries(common PRIVATE @@ -327,6 +335,16 @@ if(OPROFILE_FOUND) target_link_libraries(common PRIVATE OProfile::OProfile) endif() +if(ENABLE_LLVM) + find_package(LLVM CONFIG QUIET) + if(LLVM_FOUND AND TARGET LLVM) + message(STATUS "LLVM found, enabling LLVM support in disassembler") + target_compile_definitions(common PRIVATE HAVE_LLVM) + target_link_libraries(common PRIVATE LLVM) + target_include_directories(common PRIVATE ${LLVM_INCLUDE_DIRS}) + endif() +endif() + if(UNIX) # Posix networking code needs to be fixed for Windows add_executable(traversal_server TraversalServer.cpp) diff --git a/Source/Core/Common/HostDisassembler.cpp b/Source/Core/Common/HostDisassembler.cpp new file mode 100644 index 000000000000..c92c7c7113bd --- /dev/null +++ b/Source/Core/Common/HostDisassembler.cpp @@ -0,0 +1,175 @@ +// Copyright 2008 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Common/HostDisassembler.h" + +#include +#include +#include + +#include +#include + +#if defined(HAVE_LLVM) +#include +#include +#elif defined(_M_X86_64) +#include // 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; + + void Disassemble(const u8* begin, const u8* end, std::ostream& stream, + std::size_t& instruction_count) 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); +} + +void HostDisassemblerLLVM::Disassemble(const u8* begin, const u8* end, std::ostream& stream, + std::size_t& instruction_count) +{ + instruction_count = 0; + if (!m_llvm_context) + return; + + while (begin < end) + { + char inst_disasm[256]; + + const auto inst_size = LLVMDisasmInstruction( + m_llvm_context, const_cast(begin), static_cast(end - begin), + reinterpret_cast(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; + } +} +#elif defined(_M_X86_64) +class HostDisassemblerBochs final : public HostDisassembler +{ +public: + explicit HostDisassemblerBochs(); + ~HostDisassemblerBochs() = default; + +private: + disassembler m_disasm; + + void Disassemble(const u8* begin, const u8* end, std::ostream& stream, + std::size_t& instruction_count) override; +}; + +HostDisassemblerBochs::HostDisassemblerBochs() +{ + m_disasm.set_syntax_intel(); +} + +void HostDisassemblerBochs::Disassemble(const u8* begin, const u8* end, std::ostream& stream, + std::size_t& instruction_count) +{ + instruction_count = 0; + + while (begin < end) + { + char inst_disasm[256]; + const auto inst_size = m_disasm.disasm64(reinterpret_cast(begin), + reinterpret_cast(begin), + const_cast(begin), inst_disasm); + fmt::println(stream, "{}\t{}", fmt::ptr(begin), inst_disasm); + begin += inst_size; + ++instruction_count; + } +} +#endif + +std::unique_ptr HostDisassembler::Factory(Platform arch) +{ + switch (arch) + { +#if defined(HAVE_LLVM) + case Platform::x86_64: + return std::make_unique("x86_64-none-unknown"); + case Platform::aarch64: + return std::make_unique("aarch64-none-unknown", "cortex-a57", 4); +#elif defined(_M_X86_64) + case Platform::x86_64: + return std::make_unique(); +#endif + default: + return std::make_unique(); + } +} + +void HostDisassembler::Disassemble(const u8* begin, const u8* end, std::ostream& stream, + std::size_t& instruction_count) +{ + instruction_count = 0; + return fmt::println(stream, "{}\t{:02x}", fmt::ptr(begin), fmt::join(std::span{begin, end}, "")); +} + +void HostDisassembler::Disassemble(const u8* begin, const u8* end, std::ostream& stream) +{ + std::size_t instruction_count; + Disassemble(begin, end, stream, instruction_count); +} + +std::string HostDisassembler::Disassemble(const u8* begin, const u8* end) +{ + std::ostringstream stream; + Disassemble(begin, end, stream); + return std::move(stream).str(); +} diff --git a/Source/Core/Common/HostDisassembler.h b/Source/Core/Common/HostDisassembler.h new file mode 100644 index 000000000000..de0ecf72e0ad --- /dev/null +++ b/Source/Core/Common/HostDisassembler.h @@ -0,0 +1,30 @@ +// Copyright 2008 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "Common/CommonTypes.h" + +class HostDisassembler +{ +public: + enum class Platform + { + x86_64, + aarch64, + }; + + virtual ~HostDisassembler() = default; + + static std::unique_ptr Factory(Platform arch); + + virtual void Disassemble(const u8* begin, const u8* end, std::ostream& stream, + std::size_t& instruction_count); + void Disassemble(const u8* begin, const u8* end, std::ostream& stream); + std::string Disassemble(const u8* begin, const u8* end); +}; diff --git a/Source/Core/Common/Unreachable.h b/Source/Core/Common/Unreachable.h new file mode 100644 index 000000000000..a01810a2396b --- /dev/null +++ b/Source/Core/Common/Unreachable.h @@ -0,0 +1,21 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Common/CommonFuncs.h" + +namespace Common +{ +// TODO C++23: Replace with std::unreachable. +[[noreturn]] inline void Unreachable() +{ +#ifdef _DEBUG + Crash(); +#elif defined(_MSC_VER) && !defined(__clang__) + __assume(false); +#else + __builtin_unreachable(); +#endif +} +} // namespace Common diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index 9d477b50a500..7e387d2f875b 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -650,11 +650,6 @@ PRIVATE ZLIB::ZLIB ) -if ((DEFINED CMAKE_ANDROID_ARCH_ABI AND CMAKE_ANDROID_ARCH_ABI MATCHES "x86|x86_64") OR - (NOT DEFINED CMAKE_ANDROID_ARCH_ABI AND _M_X86_64)) - target_link_libraries(core PRIVATE bdisasm) -endif() - if (APPLE) target_link_libraries(core PRIVATE diff --git a/Source/Core/Core/HLE/HLE.cpp b/Source/Core/Core/HLE/HLE.cpp index ff9db8fc7587..0fafedfaa836 100644 --- a/Source/Core/Core/HLE/HLE.cpp +++ b/Source/Core/Core/HLE/HLE.cpp @@ -195,6 +195,11 @@ u32 GetHookByFunctionAddress(PPCSymbolDB& ppc_symbol_db, u32 address) return (symbol && symbol->address == address) ? index : 0; } +const char* GetHookNameByIndex(u32 index) +{ + return os_patches[index].name; +} + HookType GetHookTypeByIndex(u32 index) { return os_patches[index].type; diff --git a/Source/Core/Core/HLE/HLE.h b/Source/Core/Core/HLE/HLE.h index e273e8b4d7ea..e3ffc3d03c05 100644 --- a/Source/Core/Core/HLE/HLE.h +++ b/Source/Core/Core/HLE/HLE.h @@ -69,6 +69,7 @@ void ExecuteFromJIT(u32 current_pc, u32 hook_index, Core::System& system); u32 GetHookByAddress(u32 address); // Returns the HLE hook index if the address matches the function start u32 GetHookByFunctionAddress(PPCSymbolDB& ppc_symbol_db, u32 address); +const char* GetHookNameByIndex(u32 index); HookType GetHookTypeByIndex(u32 index); HookFlag GetHookFlagsByIndex(u32 index); diff --git a/Source/Core/Core/Host.h b/Source/Core/Core/Host.h index 8d22e9d59a09..9fc465a6926d 100644 --- a/Source/Core/Core/Host.h +++ b/Source/Core/Core/Host.h @@ -59,6 +59,8 @@ void Host_PPCSymbolsChanged(); void Host_RefreshDSPDebuggerWindow(); void Host_RequestRenderWindowSize(int width, int height); void Host_UpdateDisasmDialog(); +void Host_JitCacheCleared(); +void Host_JitProfileDataWiped(); void Host_UpdateMainFrame(); void Host_UpdateTitle(const std::string& title); void Host_YieldToUI(); diff --git a/Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.cpp b/Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.cpp index d2296641c2e8..df8a0fbd3141 100644 --- a/Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.cpp +++ b/Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.cpp @@ -3,7 +3,15 @@ #include "Core/PowerPC/CachedInterpreter/CachedInterpreter.h" +#include +#include +#include + +#include +#include + #include "Common/CommonTypes.h" +#include "Common/GekkoDisassembler.h" #include "Common/Logging/Log.h" #include "Core/ConfigManager.h" #include "Core/CoreTiming.h" @@ -386,10 +394,100 @@ void CachedInterpreter::Jit(u32 address) b->far_begin = nullptr; b->far_end = nullptr; - b->codeSize = static_cast(GetCodePtr() - b->normalEntry); - b->originalSize = code_block.m_num_instructions; + m_block_cache.FinalizeBlock(*b, jo.enableBlocklink, code_block, m_code_buffer); + +#ifdef JIT_LOG_GENERATED_CODE + LogGeneratedCode(); +#endif +} - m_block_cache.FinalizeBlock(*b, jo.enableBlocklink, code_block.m_physical_addresses); +void CachedInterpreter::EraseSingleBlock(const JitBlock& block) +{ + m_block_cache.EraseSingleBlock(block); +} + +void CachedInterpreter::DisasmNearCode(const JitBlock& block, std::ostream& stream, + std::size_t& instruction_count) const +{ + u32 em_address = block.effectiveAddress; + for (const Instruction* code = reinterpret_cast(block.normalEntry);; ++code) + { + switch (code->type) + { + case Instruction::Type::Abort: + instruction_count = code - reinterpret_cast(block.normalEntry); + return; + case Instruction::Type::Common: + case Instruction::Type::Conditional: + ASSERT_MSG(DYNA_REC, false, "Previously unused code types are not accounted for"); + continue; + case Instruction::Type::Interpreter: + { + if (code->interpreter_callback == Interpreter::HLEFunction) + { + fmt::println(stream, "Interpreter::HLEFunction(\"{}\")", + HLE::GetHookNameByIndex(code->data)); + continue; + } + fmt::println(stream, "0x{:08x} {}", em_address, + Common::GekkoDisassembler::Disassemble(code->data, em_address)); + em_address += 4; + continue; + } + case Instruction::Type::CachedInterpreter: + { + const auto& callback = code->cached_interpreter_callback; + if (callback == WritePC) + fmt::println(stream, "WritePC(0x{:08x})", code->data); + else if (callback == WriteBrokenBlockNPC) + fmt::println(stream, "WriteBrokenBlockNPC(0x{:08x})", code->data); + else if (callback == UpdateNumLoadStoreInstructions) + fmt::println(stream, "UpdateNumLoadStoreInstructions({})", code->data); + else if (callback == UpdateNumFloatingPointInstructions) + fmt::println(stream, "UpdateNumFloatingPointInstructions({})", code->data); + else if (callback == EndBlock) + fmt::println(stream, "EndBlock(downcount={})", code->data); + else + ASSERT_MSG(DYNA_REC, false, "Unknown callback"); + continue; + } + case Instruction::Type::ConditionalCachedInterpreter: + { + const auto& callback = code->conditional_cached_interpreter_callback; + if (callback == CheckIdle) + fmt::println(stream, "CheckIdle(0x{:08x})", code->data); + else if (callback == CheckBreakpoint) + fmt::println(stream, "CheckBreakpoint(downcount={})", code->data); + else if (callback == CheckFPU) + fmt::println(stream, "CheckFPU(downcount={})", code->data); + else if (callback == CheckDSI) + fmt::println(stream, "CheckDSI(downcount={})", code->data); + else if (callback == CheckProgramException) + fmt::println(stream, "CheckProgramException(downcount={})", code->data); + else + ASSERT_MSG(DYNA_REC, false, "Unknown callback"); + continue; + } + } + ASSERT_MSG(DYNA_REC, false, "Invalid code type"); + } +} + +void CachedInterpreter::DisasmFarCode(const JitBlock& block, std::ostream& stream, + std::size_t& instruction_count) const +{ + instruction_count = 0; + stream << "N/A\n"; +} + +std::pair CachedInterpreter::GetNearMemoryInfo() const +{ + return {m_code.capacity() - m_code.size(), 0.0}; +} + +std::pair CachedInterpreter::GetFarMemoryInfo() const +{ + return {0, 0.0}; } void CachedInterpreter::ClearCache() @@ -397,4 +495,24 @@ void CachedInterpreter::ClearCache() m_code.clear(); m_block_cache.Clear(); RefreshConfig(); + JitBase::ClearCache(); +} + +void CachedInterpreter::LogGeneratedCode() const +{ + std::ostringstream stream; + + stream << "\nPPC Code Buffer:\n"; + for (const auto& op : std::span{m_code_buffer.begin(), code_block.m_num_instructions}) + { + fmt::print(stream, "0x{:08x}\t\t{}\n", op.address, + Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address)); + } + + std::size_t dummy; + stream << "\nHost Code:\n"; + DisasmNearCode(*js.curBlock, stream, dummy); + + // TODO C++20: std::ostringstream::view() + DEBUG_LOG_FMT(DYNA_REC, "{}", std::move(stream).str()); } diff --git a/Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.h b/Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.h index 513ad7dbc911..eccf2538c581 100644 --- a/Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.h +++ b/Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.h @@ -31,6 +31,14 @@ class CachedInterpreter : public JitBase void Jit(u32 address) override; + void EraseSingleBlock(const JitBlock& block) override; + void DisasmNearCode(const JitBlock& block, std::ostream& stream, + std::size_t& instruction_count) const override; + void DisasmFarCode(const JitBlock& block, std::ostream& stream, + std::size_t& instruction_count) const override; + std::pair GetNearMemoryInfo() const override; + std::pair GetFarMemoryInfo() const override; + JitBaseBlockCache* GetBlockCache() override { return &m_block_cache; } const char* GetName() const override { return "Cached Interpreter"; } const CommonAsmRoutinesBase* GetAsmRoutines() override { return nullptr; } @@ -43,6 +51,8 @@ class CachedInterpreter : public JitBase bool HandleFunctionHooking(u32 address); + void LogGeneratedCode() const; + static void EndBlock(CachedInterpreter& cached_interpreter, UGeckoInstruction data); static void UpdateNumLoadStoreInstructions(CachedInterpreter& cached_interpreter, UGeckoInstruction data); diff --git a/Source/Core/Core/PowerPC/Jit64/Jit.cpp b/Source/Core/Core/PowerPC/Jit64/Jit.cpp index 867190e28487..67edcda403af 100644 --- a/Source/Core/Core/PowerPC/Jit64/Jit.cpp +++ b/Source/Core/Core/PowerPC/Jit64/Jit.cpp @@ -4,11 +4,12 @@ #include "Core/PowerPC/Jit64/Jit.h" #include +#include #include #include -#include #include +#include // for the PROFILER stuff #ifdef _WIN32 @@ -17,6 +18,7 @@ #include "Common/CommonTypes.h" #include "Common/GekkoDisassembler.h" +#include "Common/HostDisassembler.h" #include "Common/IOFile.h" #include "Common/Logging/Log.h" #include "Common/StringUtil.h" @@ -115,7 +117,9 @@ using namespace PowerPC; and such, but it's currently limited to integer ops only. This can definitely be made better. */ -Jit64::Jit64(Core::System& system) : JitBase(system), QuantizedMemoryRoutines(*this) +Jit64::Jit64(Core::System& system) + : JitBase(system), QuantizedMemoryRoutines(*this), + m_disassembler(HostDisassembler::Factory(HostDisassembler::Platform::x86_64)) { } @@ -307,6 +311,18 @@ void Jit64::ClearCache() RefreshConfig(); asm_routines.Regenerate(); ResetFreeMemoryRanges(); + JitBase::ClearCache(); +} + +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 (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(); } void Jit64::ResetFreeMemoryRanges() @@ -745,14 +761,7 @@ void Jit64::Jit(u32 em_address, bool clear_cache_and_retry_on_failure) } ClearCache(); } - - // Check if any code blocks have been freed in the block cache and transfer this information to - // the local rangesets to allow overwriting them with new code. - for (auto range : blocks.GetRangesToFreeNear()) - m_free_ranges_near.insert(range.first, range.second); - for (auto range : blocks.GetRangesToFreeFar()) - m_free_ranges_far.insert(range.first, range.second); - blocks.ClearRangesToFree(); + FreeRanges(); std::size_t block_size = m_code_buffer.size(); @@ -821,7 +830,11 @@ void Jit64::Jit(u32 em_address, bool clear_cache_and_retry_on_failure) b->far_begin = far_start; b->far_end = far_end; - blocks.FinalizeBlock(*b, jo.enableBlocklink, code_block.m_physical_addresses); + blocks.FinalizeBlock(*b, jo.enableBlocklink, code_block, m_code_buffer); + +#ifdef JIT_LOG_GENERATED_CODE + LogGeneratedCode(); +#endif return; } } @@ -1194,14 +1207,49 @@ bool Jit64::DoJit(u32 em_address, JitBlock* b, u32 nextPC) return false; } - b->codeSize = static_cast(GetCodePtr() - b->normalEntry); - b->originalSize = code_block.m_num_instructions; + return true; +} -#ifdef JIT_LOG_GENERATED_CODE - LogGeneratedX86(code_block.m_num_instructions, m_code_buffer, start, b); -#endif +void Jit64::EraseSingleBlock(const JitBlock& block) +{ + blocks.EraseSingleBlock(block); + FreeRanges(); +} - return true; +void Jit64::DisasmNearCode(const JitBlock& block, std::ostream& stream, + std::size_t& instruction_count) const +{ + return m_disassembler->Disassemble(block.normalEntry, block.near_end, stream, instruction_count); +} + +void Jit64::DisasmFarCode(const JitBlock& block, std::ostream& stream, + std::size_t& instruction_count) const +{ + return m_disassembler->Disassemble(block.far_begin, block.far_end, stream, instruction_count); +} + +static std::pair +GetFreeMemoryInfo(const HyoutaUtilities::RangeSizeSet::by_size_const_iterator begin, + const HyoutaUtilities::RangeSizeSet::by_size_const_iterator end) +{ + if (begin == end) + return {0, 1.0}; + + std::size_t free_total = 0; + for (auto iter = begin; iter != end; ++iter) + free_total += iter.to() - iter.from(); + const std::size_t free_max = begin.to() - begin.from(); + return {free_total, static_cast(free_total - free_max) / free_total}; +} + +std::pair Jit64::GetNearMemoryInfo() const +{ + return GetFreeMemoryInfo(m_free_ranges_near.by_size_begin(), m_free_ranges_near.by_size_end()); +} + +std::pair Jit64::GetFarMemoryInfo() const +{ + return GetFreeMemoryInfo(m_free_ranges_far.by_size_begin(), m_free_ranges_far.by_size_end()); } BitSet8 Jit64::ComputeStaticGQRs(const PPCAnalyst::CodeBlock& cb) const @@ -1283,39 +1331,23 @@ bool Jit64::HandleFunctionHooking(u32 address) return true; } -void LogGeneratedX86(size_t size, const PPCAnalyst::CodeBuffer& code_buffer, const u8* normalEntry, - const JitBlock* b) +void Jit64::LogGeneratedCode() const { - for (size_t i = 0; i < size; i++) - { - const PPCAnalyst::CodeOp& op = code_buffer[i]; - const std::string disasm = Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address); - DEBUG_LOG_FMT(DYNA_REC, "IR_X86 PPC: {:08x} {}\n", op.address, disasm); - } - - disassembler x64disasm; - x64disasm.set_syntax_intel(); + std::ostringstream stream; - u64 disasmPtr = reinterpret_cast(normalEntry); - const u8* end = normalEntry + b->codeSize; - - while (reinterpret_cast(disasmPtr) < end) + stream << "\nPPC Code Buffer:\n"; + for (const auto& op : std::span{m_code_buffer.begin(), code_block.m_num_instructions}) { - char sptr[1000] = ""; - disasmPtr += x64disasm.disasm64(disasmPtr, disasmPtr, reinterpret_cast(disasmPtr), sptr); - DEBUG_LOG_FMT(DYNA_REC, "IR_X86 x86: {}", sptr); + fmt::print(stream, "0x{:08x}\t\t{}\n", op.address, + Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address)); } - 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(*(normalEntry + i)); - } - DEBUG_LOG_FMT(DYNA_REC, "IR_X86 bin: {}\n\n\n", ss.str()); - } + 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()); } diff --git a/Source/Core/Core/PowerPC/Jit64/Jit.h b/Source/Core/Core/PowerPC/Jit64/Jit.h index 0794dc34a33f..fc7c10d222bd 100644 --- a/Source/Core/Core/PowerPC/Jit64/Jit.h +++ b/Source/Core/Core/PowerPC/Jit64/Jit.h @@ -34,6 +34,7 @@ #include "Core/PowerPC/JitCommon/JitBase.h" #include "Core/PowerPC/JitCommon/JitCache.h" +class HostDisassembler; namespace PPCAnalyst { struct CodeBlock; @@ -65,6 +66,14 @@ class Jit64 : public JitBase, public QuantizedMemoryRoutines void Jit(u32 em_address, bool clear_cache_and_retry_on_failure); bool DoJit(u32 em_address, JitBlock* b, u32 nextPC); + void EraseSingleBlock(const JitBlock& block) override; + void DisasmNearCode(const JitBlock& block, std::ostream& stream, + std::size_t& instruction_count) const override; + void DisasmFarCode(const JitBlock& block, std::ostream& stream, + std::size_t& instruction_count) const override; + std::pair GetNearMemoryInfo() const override; + std::pair GetFarMemoryInfo() const override; + // Finds a free memory region and sets the near and far code emitters to point at that region. // Returns false if no free memory region can be found for either of the two. bool SetEmitterStateToFreeCodeRegion(); @@ -266,8 +275,11 @@ class Jit64 : public JitBase, public QuantizedMemoryRoutines bool HandleFunctionHooking(u32 address); + void FreeRanges(); void ResetFreeMemoryRanges(); + void LogGeneratedCode() const; + static void ImHere(Jit64& jit); JitBlockCache blocks{*this}; @@ -284,7 +296,5 @@ class Jit64 : public JitBase, public QuantizedMemoryRoutines const bool m_im_here_debug = false; const bool m_im_here_log = false; std::map m_been_here; + std::unique_ptr m_disassembler; }; - -void LogGeneratedX86(size_t size, const PPCAnalyst::CodeBuffer& code_buffer, const u8* normalEntry, - const JitBlock* b); diff --git a/Source/Core/Core/PowerPC/JitArm64/Jit.cpp b/Source/Core/Core/PowerPC/JitArm64/Jit.cpp index f596128ef918..7160f9be6350 100644 --- a/Source/Core/Core/PowerPC/JitArm64/Jit.cpp +++ b/Source/Core/Core/PowerPC/JitArm64/Jit.cpp @@ -4,9 +4,16 @@ #include "Core/PowerPC/JitArm64/Jit.h" #include +#include +#include + +#include +#include #include "Common/Arm64Emitter.h" #include "Common/CommonTypes.h" +#include "Common/GekkoDisassembler.h" +#include "Common/HostDisassembler.h" #include "Common/Logging/Log.h" #include "Common/MathUtil.h" #include "Common/MsgHandler.h" @@ -37,7 +44,9 @@ constexpr size_t CODE_SIZE = 1024 * 1024 * 32; constexpr size_t FARCODE_SIZE = 1024 * 1024 * 64; constexpr size_t FARCODE_SIZE_MMU = 1024 * 1024 * 64; -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)) { } @@ -160,6 +169,30 @@ void JitArm64::ClearCache() GenerateAsm(); ResetFreeMemoryRanges(); + + JitBase::ClearCache(); +} + +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 (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); + + 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(); } void JitArm64::ResetFreeMemoryRanges() @@ -877,25 +910,7 @@ void JitArm64::Jit(u32 em_address, bool clear_cache_and_retry_on_failure) if (SConfig::GetInstance().bJITNoBlockCache) ClearCache(); - - // Check if any code blocks have been freed in the block cache and transfer this information to - // the local rangesets to allow overwriting them with new code. - for (auto range : blocks.GetRangesToFreeNear()) - { - auto first_fastmem_area = m_fault_to_handler.upper_bound(range.first); - auto last_fastmem_area = first_fastmem_area; - auto end = m_fault_to_handler.end(); - while (last_fastmem_area != end && last_fastmem_area->first <= range.second) - ++last_fastmem_area; - m_fault_to_handler.erase(first_fastmem_area, last_fastmem_area); - - m_free_ranges_near.insert(range.first, range.second); - } - for (auto range : blocks.GetRangesToFreeFar()) - { - m_free_ranges_far.insert(range.first, range.second); - } - blocks.ClearRangesToFree(); + FreeRanges(); const Common::ScopedJITPageWriteAndNoExecute enable_jit_page_writes; @@ -964,7 +979,11 @@ void JitArm64::Jit(u32 em_address, bool clear_cache_and_retry_on_failure) b->far_begin = far_start; b->far_end = far_end; - blocks.FinalizeBlock(*b, jo.enableBlocklink, code_block.m_physical_addresses); + blocks.FinalizeBlock(*b, jo.enableBlocklink, code_block, m_code_buffer); + +#ifdef JIT_LOG_GENERATED_CODE + LogGeneratedCode(); +#endif return; } } @@ -985,6 +1004,48 @@ void JitArm64::Jit(u32 em_address, bool clear_cache_and_retry_on_failure) exit(-1); } +void JitArm64::EraseSingleBlock(const JitBlock& block) +{ + blocks.EraseSingleBlock(block); + FreeRanges(); +} + +void JitArm64::DisasmNearCode(const JitBlock& block, std::ostream& stream, + std::size_t& instruction_count) const +{ + return m_disassembler->Disassemble(block.normalEntry, block.near_end, stream, instruction_count); +} + +void JitArm64::DisasmFarCode(const JitBlock& block, std::ostream& stream, + std::size_t& instruction_count) const +{ + return m_disassembler->Disassemble(block.far_begin, block.far_end, stream, instruction_count); +} + +static std::pair +GetFreeMemoryInfo(const HyoutaUtilities::RangeSizeSet::by_size_const_iterator begin, + const HyoutaUtilities::RangeSizeSet::by_size_const_iterator end) +{ + if (begin == end) + return {0, 1.0}; + + std::size_t free_total = 0; + for (auto iter = begin; iter != end; ++iter) + free_total += iter.to() - iter.from(); + const std::size_t free_max = begin.to() - begin.from(); + return {free_total, static_cast(free_total - free_max) / free_total}; +} + +std::pair JitArm64::GetNearMemoryInfo() const +{ + return GetFreeMemoryInfo(m_free_ranges_near.by_size_begin(), m_free_ranges_near.by_size_end()); +} + +std::pair JitArm64::GetFarMemoryInfo() const +{ + return GetFreeMemoryInfo(m_free_ranges_far.by_size_begin(), m_free_ranges_far.by_size_end()); +} + bool JitArm64::SetEmitterStateToFreeCodeRegion() { // Find the largest free memory blocks and set code emitters to point at them. @@ -1283,11 +1344,29 @@ bool JitArm64::DoJit(u32 em_address, JitBlock* b, u32 nextPC) return false; } - b->codeSize = static_cast(GetCodePtr() - b->normalEntry); - b->originalSize = code_block.m_num_instructions; - FlushIcache(); m_far_code.FlushIcache(); return true; } + +void JitArm64::LogGeneratedCode() const +{ + std::ostringstream stream; + + stream << "\nPPC Code Buffer:\n"; + for (const auto& op : std::span{m_code_buffer.begin(), 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()); +} diff --git a/Source/Core/Core/PowerPC/JitArm64/Jit.h b/Source/Core/Core/PowerPC/JitArm64/Jit.h index a572b2e38fe4..0148b022347d 100644 --- a/Source/Core/Core/PowerPC/JitArm64/Jit.h +++ b/Source/Core/Core/PowerPC/JitArm64/Jit.h @@ -19,6 +19,8 @@ #include "Core/PowerPC/JitCommon/JitBase.h" #include "Core/PowerPC/PPCAnalyst.h" +class HostDisassembler; + class JitArm64 : public JitBase, public Arm64Gen::ARM64CodeBlock, public CommonAsmRoutinesBase { public: @@ -47,6 +49,14 @@ class JitArm64 : public JitBase, public Arm64Gen::ARM64CodeBlock, public CommonA void Jit(u32 em_address) override; void Jit(u32 em_address, bool clear_cache_and_retry_on_failure); + void EraseSingleBlock(const JitBlock& block) override; + void DisasmNearCode(const JitBlock& block, std::ostream& stream, + std::size_t& instruction_count) const override; + void DisasmFarCode(const JitBlock& block, std::ostream& stream, + std::size_t& instruction_count) const override; + std::pair GetNearMemoryInfo() const override; + std::pair GetFarMemoryInfo() const override; + const char* GetName() const override { return "JITARM64"; } // OPCODES @@ -292,10 +302,13 @@ class JitArm64 : public JitBase, public Arm64Gen::ARM64CodeBlock, public CommonA void Cleanup(); void ResetStack(); + void FreeRanges(); void ResetFreeMemoryRanges(); void IntializeSpeculativeConstants(); + void LogGeneratedCode() const; + // AsmRoutines void GenerateAsm(); void GenerateCommonAsm(); @@ -382,4 +395,6 @@ class JitArm64 : public JitBase, public Arm64Gen::ARM64CodeBlock, public CommonA HyoutaUtilities::RangeSizeSet m_free_ranges_near; HyoutaUtilities::RangeSizeSet m_free_ranges_far; + + std::unique_ptr m_disassembler; }; diff --git a/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp b/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp index 5ec9af396751..6a2d816f04d8 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp +++ b/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp @@ -18,6 +18,7 @@ #include "Core/Core.h" #include "Core/CoreTiming.h" #include "Core/HW/CPU.h" +#include "Core/Host.h" #include "Core/MemTools.h" #include "Core/PowerPC/PPCAnalyst.h" #include "Core/PowerPC/PowerPC.h" @@ -110,6 +111,11 @@ JitBase::~JitBase() CPUThreadConfigCallback::RemoveConfigChangedCallback(m_registered_config_callback_id); } +void JitBase::ClearCache() +{ + Host_JitCacheCleared(); +} + bool JitBase::DoesConfigNeedRefresh() { return std::any_of(JIT_SETTINGS.begin(), JIT_SETTINGS.end(), [this](const auto& pair) { diff --git a/Source/Core/Core/PowerPC/JitCommon/JitBase.h b/Source/Core/Core/PowerPC/JitCommon/JitBase.h index cf0ce78dbc6c..53e87a6b45f8 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitBase.h +++ b/Source/Core/Core/PowerPC/JitCommon/JitBase.h @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -187,6 +188,8 @@ class JitBase : public CPUCoreBase JitBase& operator=(JitBase&&) = delete; ~JitBase() override; + void ClearCache() override; + bool IsProfilingEnabled() const { return m_enable_profiling; } bool IsDebuggingEnabled() const { return m_enable_debugging; } @@ -195,6 +198,15 @@ class JitBase : public CPUCoreBase virtual void Jit(u32 em_address) = 0; + virtual void EraseSingleBlock(const JitBlock& block) = 0; + virtual void DisasmNearCode(const JitBlock& block, std::ostream& stream, + std::size_t& instruction_count) const = 0; + virtual void DisasmFarCode(const JitBlock& block, std::ostream& stream, + std::size_t& instruction_count) const = 0; + // Pair contains free size + fragmentation ratio + virtual std::pair GetNearMemoryInfo() const = 0; + virtual std::pair GetFarMemoryInfo() const = 0; + virtual const CommonAsmRoutinesBase* GetAsmRoutines() = 0; virtual bool HandleFault(uintptr_t access_address, SContext* ctx) = 0; diff --git a/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp b/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp index 8029d06c2d36..bda367c501b1 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp +++ b/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp @@ -9,12 +9,14 @@ #include #include #include +#include #include #include "Common/CommonTypes.h" #include "Common/JitRegister.h" #include "Core/Config/MainSettings.h" #include "Core/Core.h" +#include "Core/Host.h" #include "Core/PowerPC/JitCommon/JitBase.h" #include "Core/PowerPC/MMU.h" #include "Core/PowerPC/PPCSymbolDB.h" @@ -117,6 +119,16 @@ void JitBaseBlockCache::RunOnBlocks(const Core::CPUThreadGuard&, f(e.second); } +void JitBaseBlockCache::WipeBlockProfilingData(const Core::CPUThreadGuard&) +{ + for (const auto& kv : block_map) + { + if (JitBlock::ProfileData* const profile_data = kv.second.profile_data.get()) + *profile_data = {}; + } + Host_JitProfileDataWiped(); +} + JitBlock* JitBaseBlockCache::AllocateBlock(u32 em_address) { const u32 physical_address = m_jit.m_mmu.JitCache_TranslateAddress(em_address).address; @@ -130,7 +142,8 @@ JitBlock* JitBaseBlockCache::AllocateBlock(u32 em_address) } void JitBaseBlockCache::FinalizeBlock(JitBlock& block, bool block_link, - const std::set& physical_addresses) + PPCAnalyst::CodeBlock& code_block, + const PPCAnalyst::CodeBuffer& code_buffer) { size_t index = FastLookupIndexForAddress(block.effectiveAddress, block.feature_flags); if (m_entry_points_ptr) @@ -144,10 +157,18 @@ void JitBaseBlockCache::FinalizeBlock(JitBlock& block, bool block_link, } block.fast_block_map_index = index; - block.physical_addresses = physical_addresses; + block.physical_addresses = std::move(code_block.m_physical_addresses); + + block.originalSize = code_block.m_num_instructions; + if (m_jit.IsDebuggingEnabled()) + { + block.original_buffer.reserve(block.originalSize); + for (const auto& op : std::span{code_buffer.begin(), block.originalSize}) + block.original_buffer.emplace_back(op.address, op.inst); + } u32 range_mask = ~(BLOCK_RANGE_MAP_ELEMENTS - 1); - for (u32 addr : physical_addresses) + for (const u32 addr : block.physical_addresses) { valid_block.Set(addr / 32); block_range_map[addr & range_mask].insert(&block); @@ -167,13 +188,14 @@ void JitBaseBlockCache::FinalizeBlock(JitBlock& block, bool block_link, if (Common::JitRegister::IsEnabled() && (symbol = m_jit.m_ppc_symbol_db.GetSymbolFromAddr(block.effectiveAddress)) != nullptr) { - Common::JitRegister::Register(block.normalEntry, block.codeSize, "JIT_PPC_{}_{:08x}", - symbol->function_name.c_str(), block.physicalAddress); + Common::JitRegister::Register(block.normalEntry, block.near_end - block.normalEntry, + "JIT_PPC_{}_{:08x}", symbol->function_name, + block.physicalAddress); } else { - Common::JitRegister::Register(block.normalEntry, block.codeSize, "JIT_PPC_{:08x}", - block.physicalAddress); + Common::JitRegister::Register(block.normalEntry, block.near_end - block.normalEntry, + "JIT_PPC_{:08x}", block.physicalAddress); } } @@ -370,6 +392,24 @@ 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::find_if(equal_range.first, equal_range.second, + [&](const auto& kv) { return &block == &kv.second; }); + if (block_map_iter == equal_range.second) [[unlikely]] + return; + + JitBlock& mutable_block = block_map_iter->second; + + constexpr u32 range_mask = ~(BLOCK_RANGE_MAP_ELEMENTS - 1); + for (const u32 addr : mutable_block.physical_addresses) + block_range_map[addr & range_mask].erase(&mutable_block); + + DestroyBlock(mutable_block); + block_map.erase(block_map_iter); // The original JitBlock reference is now dangling. +} + u32* JitBaseBlockCache::GetBlockBitSet() const { return valid_block.m_valid_block.get(); diff --git a/Source/Core/Core/PowerPC/JitCommon/JitCache.h b/Source/Core/Core/PowerPC/JitCommon/JitCache.h index d44cc097d3eb..e20ed395221b 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitCache.h +++ b/Source/Core/Core/PowerPC/JitCommon/JitCache.h @@ -19,6 +19,7 @@ #include "Common/CommonTypes.h" #include "Core/HW/Memmap.h" #include "Core/PowerPC/Gekko.h" +#include "Core/PowerPC/PPCAnalyst.h" class JitBase; @@ -44,9 +45,6 @@ struct JitBlockData // and valid_block in particular). This is useful because of // of the way the instruction cache works on PowerPC. u32 physicalAddress; - // The number of bytes of JIT'ed code contained in this block. Mostly - // useful for logging. - u32 codeSize; // The number of PPC instructions represented by this block. Mostly // useful for logging. u32 originalSize; @@ -105,6 +103,10 @@ struct JitBlock : public JitBlockData // This set stores all physical addresses of all occupied instructions. std::set 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> original_buffer; + std::unique_ptr profile_data; }; @@ -161,9 +163,12 @@ class JitBaseBlockCache u8** GetEntryPoints(); JitBlock** GetFastBlockMapFallback(); void RunOnBlocks(const Core::CPUThreadGuard& guard, std::function f) const; + void WipeBlockProfilingData(const Core::CPUThreadGuard& guard); + std::size_t GetBlockCount() const { return block_map.size(); } JitBlock* AllocateBlock(u32 em_address); - void FinalizeBlock(JitBlock& block, bool block_link, const std::set& physical_addresses); + void FinalizeBlock(JitBlock& block, bool block_link, PPCAnalyst::CodeBlock& code_block, + const PPCAnalyst::CodeBuffer& code_buffer); // Look for the block in the slow but accurate way. // This function shall be used if FastLookupIndexForAddress() failed. @@ -179,6 +184,7 @@ class JitBaseBlockCache void InvalidateICache(u32 address, u32 length, bool forced); void InvalidateICacheLine(u32 address); void ErasePhysicalRange(u32 address, u32 length); + void EraseSingleBlock(const JitBlock& block); u32* GetBlockBitSet() const; diff --git a/Source/Core/Core/PowerPC/JitInterface.cpp b/Source/Core/Core/PowerPC/JitInterface.cpp index bd3189303594..d729d15631b1 100644 --- a/Source/Core/Core/PowerPC/JitInterface.cpp +++ b/Source/Core/Core/PowerPC/JitInterface.cpp @@ -106,6 +106,26 @@ void JitInterface::UpdateMembase() } } +void JitInterface::RunOnBlocks(const Core::CPUThreadGuard& guard, + std::function f) const +{ + if (m_jit) + m_jit->GetBlockCache()->RunOnBlocks(guard, std::move(f)); +} + +void JitInterface::WipeBlockProfilingData(const Core::CPUThreadGuard& guard) +{ + if (m_jit) + m_jit->GetBlockCache()->WipeBlockProfilingData(guard); +} + +std::size_t JitInterface::GetBlockCount() const +{ + if (m_jit) + return m_jit->GetBlockCache()->GetBlockCount(); + return 0; +} + static std::string_view GetDescription(const CPUEmuFeatureFlags flags) { static constexpr std::array @@ -182,48 +202,6 @@ void JitInterface::JitBlockLogDump(const Core::CPUThreadGuard& guard, std::FILE* } } -std::variant -JitInterface::GetHostCode(u32 address) const -{ - if (!m_jit) - { - return GetHostCodeError::NoJitActive; - } - - auto& ppc_state = m_system.GetPPCState(); - JitBlock* block = - m_jit->GetBlockCache()->GetBlockFromStartAddress(address, ppc_state.feature_flags); - if (!block) - { - for (int i = 0; i < 500; i++) - { - block = m_jit->GetBlockCache()->GetBlockFromStartAddress(address - 4 * i, - ppc_state.feature_flags); - if (block) - break; - } - - if (block) - { - if (!(block->effectiveAddress <= address && - block->originalSize + block->effectiveAddress >= address)) - block = nullptr; - } - - // Do not merge this "if" with the above - block changes inside it. - if (!block) - { - return GetHostCodeError::NoTranslation; - } - } - - 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) { // Prevent nullptr dereference on a crash with no JIT present @@ -257,6 +235,40 @@ void JitInterface::ClearSafe() m_jit->GetBlockCache()->Clear(); } +void JitInterface::EraseSingleBlock(const JitBlock& block) +{ + if (m_jit) + m_jit->EraseSingleBlock(block); +} + +void JitInterface::DisasmNearCode(const JitBlock& block, std::ostream& stream, + std::size_t& instruction_count) const +{ + if (m_jit) + m_jit->DisasmNearCode(block, stream, instruction_count); +} + +void JitInterface::DisasmFarCode(const JitBlock& block, std::ostream& stream, + std::size_t& instruction_count) const +{ + if (m_jit) + m_jit->DisasmFarCode(block, stream, instruction_count); +} + +std::pair JitInterface::GetNearMemoryInfo() const +{ + if (m_jit) + return m_jit->GetNearMemoryInfo(); + return {0, 1.0}; +} + +std::pair JitInterface::GetFarMemoryInfo() const +{ + if (m_jit) + return m_jit->GetFarMemoryInfo(); + return {0, 1.0}; +} + void JitInterface::InvalidateICache(u32 address, u32 size, bool forced) { if (m_jit) diff --git a/Source/Core/Core/PowerPC/JitInterface.h b/Source/Core/Core/PowerPC/JitInterface.h index 17d0796bfddc..181e8f3c3cdc 100644 --- a/Source/Core/Core/PowerPC/JitInterface.h +++ b/Source/Core/Core/PowerPC/JitInterface.h @@ -6,9 +6,10 @@ #include #include #include +#include #include #include -#include +#include #include "Common/CommonTypes.h" #include "Core/MachineContext.h" @@ -16,6 +17,7 @@ class CPUCoreBase; class PointerWrap; class JitBase; +struct JitBlock; namespace Core { @@ -57,7 +59,9 @@ class JitInterface void UpdateMembase(); void JitBlockLogDump(const Core::CPUThreadGuard& guard, std::FILE* file) const; - std::variant GetHostCode(u32 address) const; + void RunOnBlocks(const Core::CPUThreadGuard& guard, std::function f) const; + void WipeBlockProfilingData(const Core::CPUThreadGuard& guard); + std::size_t GetBlockCount() const; // Memory Utilities bool HandleFault(uintptr_t access_address, SContext* ctx); @@ -71,6 +75,19 @@ class JitInterface // the JIT'ed code. void ClearSafe(); + // DolphinQt's JITWidget needs this. 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); + // Disassemble the recompiled code from a JIT block. + void DisasmNearCode(const JitBlock& block, std::ostream& stream, + std::size_t& instruction_count) const; + void DisasmFarCode(const JitBlock& block, std::ostream& stream, + std::size_t& instruction_count) const; + // Pair contains free size + fragmentation ratio + std::pair GetNearMemoryInfo() const; + std::pair GetFarMemoryInfo() const; + // If "forced" is true, a recompile is being requested on code that hasn't been modified. void InvalidateICache(u32 address, u32 size, bool forced); void InvalidateICacheLine(u32 address); diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index cd6fcfc16136..e6a233fd119a 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -117,6 +117,7 @@ + @@ -164,6 +165,7 @@ + @@ -546,7 +548,6 @@ - @@ -806,6 +807,7 @@ + @@ -1201,7 +1203,6 @@ - diff --git a/Source/Core/DolphinNoGUI/MainNoGUI.cpp b/Source/Core/DolphinNoGUI/MainNoGUI.cpp index 6cbb2750d213..18597399acc9 100644 --- a/Source/Core/DolphinNoGUI/MainNoGUI.cpp +++ b/Source/Core/DolphinNoGUI/MainNoGUI.cpp @@ -86,6 +86,14 @@ void Host_UpdateDisasmDialog() { } +void Host_JitCacheCleared() +{ +} + +void Host_JitProfileDataWiped() +{ +} + void Host_UpdateMainFrame() { s_update_main_frame_event.Set(); diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index 87bd9883d526..a14229297bcb 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -220,6 +220,8 @@ add_executable(dolphin-emu Debugger/CodeWidget.h Debugger/GekkoSyntaxHighlight.cpp Debugger/GekkoSyntaxHighlight.h + Debugger/JitBlockTableModel.cpp + Debugger/JitBlockTableModel.h Debugger/JITWidget.cpp Debugger/JITWidget.h Debugger/MemoryViewWidget.cpp @@ -295,6 +297,7 @@ add_executable(dolphin-emu QtUtils/BlockUserInputFilter.h QtUtils/ClearLayoutRecursively.cpp QtUtils/ClearLayoutRecursively.h + QtUtils/ClickableStatusBar.h QtUtils/DolphinFileDialog.cpp QtUtils/DolphinFileDialog.h QtUtils/DoubleClickEventFilter.cpp diff --git a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp index e33fbde2fa48..dac1e6b1de54 100644 --- a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp @@ -877,7 +877,7 @@ void CodeViewWidget::OnPPCComparison() { const u32 addr = GetContextAddress(); - emit RequestPPCComparison(addr); + emit RequestPPCComparison(addr, m_system.GetPPCState().msr.IR); } void CodeViewWidget::OnAddFunction() diff --git a/Source/Core/DolphinQt/Debugger/CodeViewWidget.h b/Source/Core/DolphinQt/Debugger/CodeViewWidget.h index 2984140b6280..79024b00d167 100644 --- a/Source/Core/DolphinQt/Debugger/CodeViewWidget.h +++ b/Source/Core/DolphinQt/Debugger/CodeViewWidget.h @@ -55,7 +55,7 @@ class CodeViewWidget : public QTableWidget u32 AddressForRow(int row) const; signals: - void RequestPPCComparison(u32 addr); + void RequestPPCComparison(u32 address, bool effective); void ShowMemory(u32 address); void BreakpointsChanged(); void UpdateCodeWidget(); diff --git a/Source/Core/DolphinQt/Debugger/CodeWidget.cpp b/Source/Core/DolphinQt/Debugger/CodeWidget.cpp index 15683c973dcc..cb600dcf5340 100644 --- a/Source/Core/DolphinQt/Debugger/CodeWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/CodeWidget.cpp @@ -222,6 +222,11 @@ void CodeWidget::OnPPCSymbolsChanged() } } +void CodeWidget::OnSetCodeAddress(u32 address) +{ + SetAddress(address, CodeViewWidget::SetAddressUpdate::WithDetailedUpdate); +} + void CodeWidget::OnSearchAddress() { bool good = true; diff --git a/Source/Core/DolphinQt/Debugger/CodeWidget.h b/Source/Core/DolphinQt/Debugger/CodeWidget.h index e9a981f8e558..645af7653992 100644 --- a/Source/Core/DolphinQt/Debugger/CodeWidget.h +++ b/Source/Core/DolphinQt/Debugger/CodeWidget.h @@ -51,7 +51,7 @@ class CodeWidget : public QDockWidget void UpdateSymbols(); signals: void BreakpointsChanged(); - void RequestPPCComparison(u32 addr); + void RequestPPCComparison(u32 address, bool effective); void ShowMemory(u32 address); private: @@ -61,6 +61,10 @@ class CodeWidget : public QDockWidget void UpdateFunctionCalls(const Common::Symbol* symbol); void UpdateFunctionCallers(const Common::Symbol* symbol); +public slots: + void OnSetCodeAddress(u32 address); + +private: void OnPPCSymbolsChanged(); void OnSearchAddress(); void OnSearchSymbols(); diff --git a/Source/Core/DolphinQt/Debugger/JITWidget.cpp b/Source/Core/DolphinQt/Debugger/JITWidget.cpp index a6c84dbe0800..e0fd9871b28f 100644 --- a/Source/Core/DolphinQt/Debugger/JITWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/JITWidget.cpp @@ -3,216 +3,646 @@ #include "DolphinQt/Debugger/JITWidget.h" +#include +#include +#include + +#include +#include +#include +#include +#include #include +#include +#include #include -#include -#include +#include #include #include +#include +#include "Common/CommonFuncs.h" #include "Common/GekkoDisassembler.h" #include "Core/Core.h" -#include "Core/PowerPC/PPCAnalyst.h" +#include "Core/PowerPC/JitCommon/JitCache.h" +#include "Core/PowerPC/JitInterface.h" +#include "Core/PowerPC/MMU.h" +#include "Core/PowerPC/PPCSymbolDB.h" #include "Core/System.h" -#include "UICommon/Disassembler.h" +#include "DolphinQt/Debugger/JitBlockTableModel.h" #include "DolphinQt/Host.h" +#include "DolphinQt/QtUtils/ClickableStatusBar.h" +#include "DolphinQt/QtUtils/ModalMessageBox.h" #include "DolphinQt/Settings.h" +#include "UICommon/UICommon.h" -JITWidget::JITWidget(QWidget* parent) : QDockWidget(parent) +class JitBlockProxyModel final : public QSortFilterProxyModel { - setWindowTitle(tr("JIT Blocks")); - setObjectName(QStringLiteral("jitwidget")); + friend JITWidget; - setHidden(!Settings::Instance().IsJITVisible() || !Settings::Instance().IsDebugModeEnabled()); +public: + const JitBlock& GetJitBlock(const QModelIndex& index); - setAllowedAreas(Qt::AllDockWidgetAreas); +public: // Qt slots + void OnSymbolTextChanged(const QString& text); + template JitBlockProxyModel::*member> + void OnAddressTextChanged(const QString& text); - auto& settings = Settings::GetQSettings(); +public: // Qt overrides + explicit JitBlockProxyModel(QObject* parent = nullptr); + ~JitBlockProxyModel() = default; - CreateWidgets(); + JitBlockProxyModel(const JitBlockProxyModel&) = delete; + JitBlockProxyModel(JitBlockProxyModel&&) = delete; + JitBlockProxyModel& operator=(const JitBlockProxyModel&) = delete; + JitBlockProxyModel& operator=(JitBlockProxyModel&&) = delete; - restoreGeometry(settings.value(QStringLiteral("jitwidget/geometry")).toByteArray()); - // macOS: setHidden() needs to be evaluated before setFloating() for proper window presentation - // according to Settings - setFloating(settings.value(QStringLiteral("jitwidget/floating")).toBool()); + 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; - m_table_splitter->restoreState( - settings.value(QStringLiteral("jitwidget/tablesplitter")).toByteArray()); - m_asm_splitter->restoreState( - settings.value(QStringLiteral("jitwidget/asmsplitter")).toByteArray()); +private: + std::optional m_em_address_min, m_em_address_max, m_pm_address_covered; + QString m_symbol_name = {}; +}; - connect(&Settings::Instance(), &Settings::JITVisibilityChanged, this, - [this](bool visible) { setHidden(!visible); }); +const JitBlock& JitBlockProxyModel::GetJitBlock(const QModelIndex& index) +{ + return sourceModel()->GetJitBlock(mapToSource(index)); +} - connect(&Settings::Instance(), &Settings::DebugModeToggled, this, - [this](bool enabled) { setHidden(!enabled || !Settings::Instance().IsJITVisible()); }); +void JitBlockProxyModel::OnSymbolTextChanged(const QString& text) +{ + m_symbol_name = text; + invalidateRowsFilter(); +} - connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, &JITWidget::Update); - connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this, &JITWidget::Update); +template 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(); +} - ConnectWidgets(); +JitBlockProxyModel::JitBlockProxyModel(QObject* parent) : QSortFilterProxyModel(parent) +{ +} -#if defined(_M_X86_64) - m_disassembler = GetNewDisassembler("x86"); -#elif defined(_M_ARM_64) - m_disassembler = GetNewDisassembler("aarch64"); -#else - m_disassembler = GetNewDisassembler("UNK"); -#endif +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() || !reinterpret_cast(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; } -JITWidget::~JITWidget() +// Virtual setSourceModel is forbidden for type-safety reasons. +void JitBlockProxyModel::setSourceModel(QAbstractItemModel* source_model) { - auto& settings = Settings::GetQSettings(); + Crash(); +} - settings.setValue(QStringLiteral("jitwidget/geometry"), saveGeometry()); - settings.setValue(QStringLiteral("jitwidget/floating"), isFloating()); - settings.setValue(QStringLiteral("jitwidget/tablesplitter"), m_table_splitter->saveState()); - settings.setValue(QStringLiteral("jitwidget/asmsplitter"), m_asm_splitter->saveState()); +void JitBlockProxyModel::setSourceModel(JitBlockTableModel* source_model) +{ + QSortFilterProxyModel::setSourceModel(source_model); } -void JITWidget::CreateWidgets() +JitBlockTableModel* JitBlockProxyModel::sourceModel() const { - m_table_widget = new QTableWidget; + return static_cast(QSortFilterProxyModel::sourceModel()); +} - m_table_widget->setTabKeyNavigation(false); - m_table_widget->setColumnCount(7); - m_table_widget->setHorizontalHeaderLabels( - {tr("Address"), tr("PPC Size"), tr("Host Size"), - // i18n: The symbolic name of a code block - tr("Symbol"), - // i18n: These are the kinds of flags that a CPU uses (e.g. carry), - // not the kinds of flags that represent e.g. countries - tr("Flags"), - // i18n: The number of times a code block has been executed - tr("NumExec"), - // i18n: Performance cost, not monetary cost - tr("Cost")}); +void JITWidget::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); +} - m_ppc_asm_widget = new QTextBrowser; - m_host_asm_widget = new QTextBrowser; +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); +} - m_table_splitter = new QSplitter(Qt::Vertical); - m_asm_splitter = new QSplitter(Qt::Horizontal); +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); +} - m_refresh_button = new QPushButton(tr("Refresh")); +void JITWidget::ClearDisassembly() +{ + m_ppc_asm_widget->clear(); + m_host_near_asm_widget->clear(); + m_host_far_asm_widget->clear(); + m_status_bar->clearMessage(); +} - m_table_splitter->addWidget(m_table_widget); - m_table_splitter->addWidget(m_asm_splitter); +void JITWidget::ShowFreeMemoryStatus() +{ + const auto [near_free, near_frag] = m_system.GetJitInterface().GetNearMemoryInfo(); + const auto [far_free, far_frag] = m_system.GetJitInterface().GetFarMemoryInfo(); + m_status_bar->showMessage( + QStringLiteral("Free memory: %1 near (%2% fragmented) %3 far (%4% fragmented)") + .arg(QString::fromStdString(UICommon::FormatSize(near_free, 2))) + .arg(near_frag * 100.0, 3, 'f', 2) + .arg(QString::fromStdString(UICommon::FormatSize(far_free, 2))) + .arg(far_frag * 100.0, 3, 'f', 2)); +} - m_asm_splitter->addWidget(m_ppc_asm_widget); - m_asm_splitter->addWidget(m_host_asm_widget); +void JITWidget::UpdateContent(Core::State state) +{ + ClearDisassembly(); + if (state == Core::State::Paused) + ShowFreeMemoryStatus(); +} - QWidget* widget = new QWidget; - auto* layout = new QVBoxLayout; - layout->setContentsMargins(2, 2, 2, 2); - widget->setLayout(layout); +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); + } +} - layout->addWidget(m_table_splitter); - layout->addWidget(m_refresh_button); +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(); + + std::size_t host_near_instruction_count; + jit_interface.DisasmNearCode(block, stream, host_near_instruction_count); + m_host_near_asm_widget->setPlainText(QString::fromStdString(std::move(stream).str())); + + std::size_t host_far_instruction_count; + jit_interface.DisasmFarCode(block, stream, host_far_instruction_count); + m_host_far_asm_widget->setPlainText(QString::fromStdString(std::move(stream).str())); + + const std::size_t host_instruction_count = + host_near_instruction_count + host_far_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(100 * host_instruction_count / block.originalSize - 100)); +} - setWidget(widget); +void JITWidget::CrossDisassemble(const QModelIndex& index) +{ + if (index.isValid()) + { + CrossDisassemble(m_table_proxy->GetJitBlock(index)); + return; + } + UpdateContent(Core::GetState(m_system)); } -void JITWidget::ConnectWidgets() +void JITWidget::CrossDisassemble() { - connect(m_refresh_button, &QPushButton::clicked, this, &JITWidget::Update); + CrossDisassemble(m_table_view->currentIndex()); } -void JITWidget::Compare(u32 address) +void JITWidget::TableEraseBlocks() { - m_address = address; + auto* const selection_model = m_table_view->selectionModel(); + // Disconnect to avoid the slot being called for every single erasure. + disconnect(selection_model, &QItemSelectionModel::currentChanged, this, + &JITWidget::OnTableCurrentChanged); + + QModelIndexList index_list = selection_model->selectedRows(); + std::transform(index_list.begin(), index_list.end(), index_list.begin(), + [this](const QModelIndex& index) { return m_table_proxy->mapToSource(index); }); + // std::greater doesn't work. Do a backwards compare instead. + std::sort(index_list.begin(), index_list.end(), + [](const QModelIndex& lhs, const QModelIndex& rhs) { return rhs < lhs; }); + for (const QModelIndex& index : index_list) + { + if (!index.isValid()) + continue; + m_table_model->removeRow(index.row()); + } - Settings::Instance().SetJITVisible(true); - raise(); - m_host_asm_widget->setFocus(); + connect(selection_model, &QItemSelectionModel::currentChanged, this, + &JITWidget::OnTableCurrentChanged); + selection_model->clear(); +} + +void JITWidget::LoadQSettings() +{ + auto& settings = Settings::GetQSettings(); - Update(); + restoreGeometry(settings.value(QStringLiteral("jitwidget/geometry")).toByteArray()); + // macOS: setFloating() needs to be after setHidden() for proper window presentation. + 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::Update() +void JITWidget::SaveQSettings() const { - if (!isVisible()) - return; + auto& settings = Settings::GetQSettings(); + + settings.setValue(QStringLiteral("jitwidget/geometry"), saveGeometry()); + settings.setValue(QStringLiteral("jitwidget/floating"), isFloating()); + settings.setValue(QStringLiteral("jitwidget/tableheader/state"), + m_table_view->horizontalHeader()->saveState()); + settings.setValue(QStringLiteral("jitwidget/tablesplitter"), m_table_splitter->saveState()); + settings.setValue(QStringLiteral("jitwidget/disasmsplitter"), m_disasm_splitter->saveState()); +} + +void JITWidget::ConnectSlots() +{ + auto* const host = Host::GetInstance(); + connect(host, &Host::JitCacheCleared, this, &JITWidget::OnJitCacheCleared); + connect(host, &Host::UpdateDisasmDialog, this, &JITWidget::OnUpdateDisasmDialog); + connect(host, &Host::PPCSymbolsChanged, this, &JITWidget::OnPPCSymbolsUpdated); + auto* const settings = &Settings::Instance(); + connect(settings, &Settings::ConfigChanged, this, &JITWidget::OnConfigChanged); + connect(settings, &Settings::DebugFontChanged, this, &JITWidget::OnDebugFontChanged); + connect(settings, &Settings::EmulationStateChanged, this, &JITWidget::OnEmulationStateChanged); +} + +void JITWidget::DisconnectSlots() +{ + auto* const host = Host::GetInstance(); + disconnect(host, &Host::JitCacheCleared, this, &JITWidget::OnJitCacheCleared); + disconnect(host, &Host::UpdateDisasmDialog, this, &JITWidget::OnUpdateDisasmDialog); + disconnect(host, &Host::PPCSymbolsChanged, this, &JITWidget::OnPPCSymbolsUpdated); + 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::Hide() +{ + DisconnectSlots(); + ClearDisassembly(); +} + +void JITWidget::Show() +{ + const Core::State state = Core::GetState(m_system); + ConnectSlots(); + UpdateProfilingButton(); + UpdateOtherButtons(state); + UpdateDebugFont(Settings::Instance().GetDebugFont()); + if (state == Core::State::Paused) + ShowFreeMemoryStatus(); +} - if (!m_address || (Core::GetState(Core::System::GetInstance()) != Core::State::Paused)) +QMenu* JITWidget::GetTableContextMenu() +{ + if (m_table_context_menu == nullptr) { - m_ppc_asm_widget->setHtml(QStringLiteral("%1").arg(tr("(ppc)"))); - m_host_asm_widget->setHtml(QStringLiteral("%1").arg(tr("(host)"))); - return; + 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); } + return m_table_context_menu; +} - // TODO: Actually do something with the table (Wx doesn't) - - // Get host side code disassembly - auto host_instructions_disasm = DisassembleBlock(m_disassembler.get(), m_address); - m_address = host_instructions_disasm.entry_address; - - m_host_asm_widget->setHtml( - QStringLiteral("
%1
").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) +QMenu* JITWidget::GetColumnVisibilityMenu() +{ + if (m_column_visibility_menu == nullptr) { - 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++) + m_column_visibility_menu = new QMenu(this); + static constexpr std::array headers = { + QT_TR_NOOP("PPC Feature Flags"), QT_TR_NOOP("Effective Address"), + QT_TR_NOOP("Code Buffer Size"), QT_TR_NOOP("Repeat Instructions"), + QT_TR_NOOP("Host Near Code Size"), QT_TR_NOOP("Host Far Code Size"), + QT_TR_NOOP("Run Count"), QT_TR_NOOP("Cycles Spent"), + QT_TR_NOOP("Cycles Average"), QT_TR_NOOP("Cycles Percent"), + QT_TR_NOOP("Time Spent (ns)"), QT_TR_NOOP("Time Average (ns)"), + QT_TR_NOOP("Time Percent"), QT_TR_NOOP("Symbol"), + }; + for (int column = 0; column < Column::NumberOfColumns; ++column) { - 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); + 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); } + } + return m_column_visibility_menu; +} - // Add stats to the end of the ppc box since it's generally the shortest. - fmt::format_to(ppc_disasm, "\n{} estimated cycles", st.numCycles); - fmt::format_to(ppc_disasm, "\nNum instr: PPC: {} Host: {}", code_block.m_num_instructions, - host_instructions_disasm.instruction_count); - if (code_block.m_num_instructions != 0 && host_instructions_disasm.instruction_count != 0) - { - fmt::format_to( - ppc_disasm, " (blowup: {}%)", - 100 * host_instructions_disasm.instruction_count / code_block.m_num_instructions - 100); - } +void JITWidget::OnRequestPPCComparison(u32 address, bool effective) +{ + Settings::Instance().SetJITVisible(true); + raise(); - fmt::format_to(ppc_disasm, "\nNum bytes: PPC: {} Host: {}", code_block.m_num_instructions * 4, - host_instructions_disasm.code_size); - if (code_block.m_num_instructions != 0 && host_instructions_disasm.code_size != 0) + if (effective) + { + const std::optional pm_address = m_system.GetMMU().GetTranslatedAddress(address); + if (!pm_address.has_value()) { - fmt::format_to( - ppc_disasm, " (blowup: {}%)", - 100 * host_instructions_disasm.code_size / (4 * code_block.m_num_instructions) - 100); + ModalMessageBox::warning( + this, tr("Error"), + tr("Effective address %1 has no physical address translation.").arg(address, 0, 16)); + return; } - - m_ppc_asm_widget->setHtml( - QStringLiteral("
%1
").arg(QString::fromStdString(ppc_disasm_str))); + address = pm_address.value(); } - else + m_pm_address_covered_line_edit->setText(QString::number(address, 16)); +} + +void JITWidget::OnVisibilityToggled(bool visible) +{ + setHidden(!visible); +} + +void JITWidget::OnDebugModeToggled(bool enabled) +{ + setHidden(!enabled || !Settings::Instance().IsJITVisible()); +} + +void JITWidget::OnToggleProfiling(bool enabled) +{ + Config::SetBaseOrCurrent(Config::MAIN_DEBUG_JIT_ENABLE_PROFILING, enabled); +} + +void JITWidget::OnClearCache() +{ + m_system.GetJitInterface().ClearCache(Core::CPUThreadGuard{m_system}); +} + +void JITWidget::OnWipeProfiling() +{ + m_system.GetJitInterface().WipeBlockProfilingData(Core::CPUThreadGuard{m_system}); +} + +void JITWidget::OnTableCurrentChanged(const QModelIndex& current, const QModelIndex& previous) +{ + CrossDisassemble(current); +} + +void JITWidget::OnTableDoubleClicked(const QModelIndex& index) +{ + emit SetCodeAddress(m_table_proxy->GetJitBlock(index).effectiveAddress); +} + +void JITWidget::OnTableContextMenu(const QPoint& pos) +{ + // There needs to be an option somewhere for a user to recover from hiding every column. + if (m_table_view->horizontalHeader()->hiddenSectionCount() == Column::NumberOfColumns) { - m_host_asm_widget->setHtml( - QStringLiteral("
%1
") - .arg(QString::fromStdString(fmt::format("(non-code address: {:08x})", m_address)))); - m_ppc_asm_widget->setHtml(QStringLiteral("---")); + GetColumnVisibilityMenu()->exec(m_table_view->viewport()->mapToGlobal(pos)); + return; } + GetTableContextMenu()->exec(m_table_view->viewport()->mapToGlobal(pos)); +} + +void JITWidget::OnTableHeaderContextMenu(const QPoint& pos) +{ + GetColumnVisibilityMenu()->exec(m_table_view->horizontalHeader()->mapToGlobal(pos)); +} + +void JITWidget::OnTableMenuViewCode() +{ + // TODO: CodeWidget doesn't support it yet, but eventually signal if the address is + // effective 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(); + if (Core::GetState(m_system) == Core::State::Paused) + ShowFreeMemoryStatus(); +} + +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::OnConfigChanged() +{ + UpdateProfilingButton(); +} + +void JITWidget::OnDebugFontChanged(const QFont& font) +{ + UpdateDebugFont(font); +} + +void JITWidget::OnEmulationStateChanged(Core::State state) +{ + UpdateOtherButtons(state); + UpdateContent(state); +} + +JITWidget::JITWidget(Core::System& system, QWidget* parent) : QDockWidget(parent), m_system(system) +{ + setWindowTitle(tr("JIT Blocks")); + setObjectName(QStringLiteral("jitwidget")); + setAllowedAreas(Qt::AllDockWidgetAreas); + + auto* const widget = new QWidget(this); + auto* const layout = new QVBoxLayout; + layout->setContentsMargins(2, 2, 2, 2); + layout->setSpacing(0); + + m_table_view = new QTableView(widget); + 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); + + 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); + + 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(); + + auto* const selection_model = m_table_view->selectionModel(); + connect(selection_model, &QItemSelectionModel::currentChanged, this, + &JITWidget::OnTableCurrentChanged); + + connect(m_table_view, &QTableView::doubleClicked, this, &JITWidget::OnTableDoubleClicked); + connect(m_table_view, &QTableView::customContextMenuRequested, this, + &JITWidget::OnTableContextMenu); + + auto* const controls_layout = new QHBoxLayout; + + 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(widget), tr("Min Effective Address"), + &JitBlockProxyModel::OnAddressTextChanged<&JitBlockProxyModel::m_em_address_min>); + address_filter_routine( + new QLineEdit(widget), tr("Max Effective Address"), + &JitBlockProxyModel::OnAddressTextChanged<&JitBlockProxyModel::m_em_address_max>); + address_filter_routine( + m_pm_address_covered_line_edit = new QLineEdit(widget), tr("Recompiles Physical Address"), + &JitBlockProxyModel::OnAddressTextChanged<&JitBlockProxyModel::m_pm_address_covered>); + + auto* const symbol_name_line_edit = new QLineEdit(widget); + 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(widget); + 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"), widget); + connect(m_clear_cache_button, &QPushButton::pressed, this, &JITWidget::OnClearCache); + controls_layout->addWidget(m_clear_cache_button); + + m_wipe_profiling_button = new QPushButton(tr("Wipe Profiling"), widget); + m_wipe_profiling_button->setToolTip(tr("Re-initialize software JIT block profiling data.")); + connect(m_wipe_profiling_button, &QPushButton::pressed, this, &JITWidget::OnWipeProfiling); + controls_layout->addWidget(m_wipe_profiling_button); + + m_disasm_splitter = new QSplitter(Qt::Horizontal, widget); + + 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(widget), tr("PPC Instruction Coverage")); + text_box_routine(m_host_near_asm_widget = new QPlainTextEdit(widget), tr("Host Near Code Cache")); + text_box_routine(m_host_far_asm_widget = new QPlainTextEdit(widget), tr("Host Far Code Cache")); + + m_table_splitter = new QSplitter(Qt::Vertical, widget); + m_table_splitter->addWidget(m_table_view); + m_table_splitter->addWidget(m_disasm_splitter); + + m_status_bar = new ClickableStatusBar(widget); + m_status_bar->setSizeGripEnabled(false); + connect(m_status_bar, &ClickableStatusBar::pressed, this, &JITWidget::OnStatusBarPressed); + + layout->addLayout(controls_layout); + layout->addWidget(m_table_splitter); + layout->addWidget(m_status_bar); + + auto& settings = Settings::Instance(); + connect(&settings, &Settings::JITVisibilityChanged, this, &JITWidget::OnVisibilityToggled); + connect(&settings, &Settings::DebugModeToggled, this, &JITWidget::OnDebugModeToggled); + + widget->setLayout(layout); + setWidget(widget); + setHidden(!settings.IsJITVisible() || !settings.IsDebugModeEnabled()); + + LoadQSettings(); +} + +JITWidget::~JITWidget() +{ + SaveQSettings(); } void JITWidget::closeEvent(QCloseEvent*) @@ -220,7 +650,14 @@ void JITWidget::closeEvent(QCloseEvent*) Settings::Instance().SetJITVisible(false); } -void JITWidget::showEvent(QShowEvent* event) +void JITWidget::hideEvent(QHideEvent*) +{ + emit HideSignal(); + Hide(); +} + +void JITWidget::showEvent(QShowEvent*) { - Update(); + emit ShowSignal(); + Show(); } diff --git a/Source/Core/DolphinQt/Debugger/JITWidget.h b/Source/Core/DolphinQt/Debugger/JITWidget.h index 3b2f80701d31..6139b4eca6f6 100644 --- a/Source/Core/DolphinQt/Debugger/JITWidget.h +++ b/Source/Core/DolphinQt/Debugger/JITWidget.h @@ -4,42 +4,127 @@ #pragma once #include -#include #include "Common/CommonTypes.h" +class ClickableStatusBar; +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 QCloseEvent; +class QFont; +class QLineEdit; class QShowEvent; class QSplitter; -class QTextBrowser; -class QTableWidget; +class QPlainTextEdit; class QPushButton; -class HostDisassembler; +class QTableView; -class JITWidget : public QDockWidget +class JITWidget final : public QDockWidget { Q_OBJECT -public: - explicit JITWidget(QWidget* parent = nullptr); - ~JITWidget(); - void Compare(u32 address); +private: + using Column = JitBlockTableModelColumn::EnumType; + using UserRole = JitBlockTableModelUserRole::EnumType; private: - void Update(); - void CreateWidgets(); - void ConnectWidgets(); + void UpdateProfilingButton(); + void UpdateOtherButtons(Core::State state); + void UpdateDebugFont(const QFont& font); + void ClearDisassembly(); + void ShowFreeMemoryStatus(); + void UpdateContent(Core::State state); + void CrossDisassemble(const JitBlock& block); + void CrossDisassemble(const QModelIndex& index); + void CrossDisassemble(); + void TableEraseBlocks(); + + void LoadQSettings(); + void SaveQSettings() const; + void ConnectSlots(); + void DisconnectSlots(); + void Hide(); + void Show(); + + [[nodiscard]] QMenu* GetTableContextMenu(); + [[nodiscard]] QMenu* GetColumnVisibilityMenu(); + +signals: + void HideSignal(); + void ShowSignal(); + void SetCodeAddress(u32 address); - void closeEvent(QCloseEvent*) override; +public: // Qt slots + void OnRequestPPCComparison(u32 address, bool effective); + +private: // Qt slots + // Always connected (external signals) + void OnVisibilityToggled(bool visible); + void OnDebugModeToggled(bool visible); + // Always connected (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 (external signals) + void OnJitCacheCleared(); + void OnUpdateDisasmDialog(); + void OnPPCSymbolsUpdated(); + void OnConfigChanged(); + void OnDebugFontChanged(const QFont& font); + void OnEmulationStateChanged(Core::State state); + +public: // Qt overrides + 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; + +private: // Qt overrides + void closeEvent(QCloseEvent* event) override; + void hideEvent(QHideEvent* event) override; void showEvent(QShowEvent* event) override; - QTableWidget* m_table_widget; - QTextBrowser* m_ppc_asm_widget; - QTextBrowser* m_host_asm_widget; +private: + Core::System& m_system; + + QLineEdit* m_pm_address_covered_line_edit; + QPushButton* m_clear_cache_button; + QPushButton* m_toggle_profiling_button; + QPushButton* m_wipe_profiling_button; + QTableView* m_table_view; + JitBlockProxyModel* m_table_proxy; + JitBlockTableModel* m_table_model; + QPlainTextEdit* m_ppc_asm_widget; + QPlainTextEdit* m_host_near_asm_widget; + QPlainTextEdit* m_host_far_asm_widget; QSplitter* m_table_splitter; - QSplitter* m_asm_splitter; - QPushButton* m_refresh_button; + QSplitter* m_disasm_splitter; + ClickableStatusBar* m_status_bar; - std::unique_ptr m_disassembler; - u32 m_address = 0; + QMenu* m_table_context_menu = nullptr; + QMenu* m_column_visibility_menu = nullptr; }; diff --git a/Source/Core/DolphinQt/Debugger/JitBlockTableModel.cpp b/Source/Core/DolphinQt/Debugger/JitBlockTableModel.cpp new file mode 100644 index 000000000000..baf99cf743cb --- /dev/null +++ b/Source/Core/DolphinQt/Debugger/JitBlockTableModel.cpp @@ -0,0 +1,421 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "DolphinQt/Debugger/JitBlockTableModel.h" + +#include +#include + +#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 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 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); + 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); + auto* const settings = &Settings::Instance(); + disconnect(settings, &Settings::EmulationStateChanged, this, + &JitBlockTableModel::OnEmulationStateChanged); +} + +void JitBlockTableModel::Hide() +{ + DisconnectSlots(); + Clear(); +} + +void JitBlockTableModel::Show() +{ + ConnectSlots(); + Update(Core::GetState(m_system)); +} + +static QString GetQStringDescription(const CPUEmuFeatureFlags flags) +{ + static const std::array 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(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(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>( + 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(jit_block.originalSize); + case Column::RepeatInstructions: + return static_cast(jit_block.originalSize - jit_block.physical_addresses.size()); + case Column::HostNearCodeSize: + return static_cast(jit_block.near_end - jit_block.near_begin); + case Column::HostFarCodeSize: + return static_cast(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(profile_data->run_count); + case Column::CyclesSpent: + case Column::CyclesPercent: + return static_cast(profile_data->cycles_spent); + case Column::CyclesAverage: + if (profile_data->run_count == 0) + return QVariant(); + return static_cast(profile_data->cycles_spent) / profile_data->run_count; + case Column::TimeSpent: + case Column::TimePercent: + return static_cast(profile_data->time_spent.count()); + case Column::TimeAverage: + if (profile_data->run_count == 0) + return QVariant(); + return static_cast(profile_data->time_spent.count()) / profile_data->run_count; + } + static_assert(Column::NumberOfColumns == 14); + Common::Unreachable(); +} + +void JitBlockTableModel::OnHideSignal() +{ + Hide(); +} + +void JitBlockTableModel::OnShowSignal() +{ + Show(); +} + +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::OnEmulationStateChanged(Core::State state) +{ + Update(state); +} + +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; + +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(); + + static constexpr std::array headers = { + QT_TR_NOOP("PPC Feat. Flags"), QT_TR_NOOP("Eff. Address"), QT_TR_NOOP("Code Buff. Size"), + QT_TR_NOOP("Repeat Instr."), QT_TR_NOOP("Host N. Size"), QT_TR_NOOP("Host F. Size"), + QT_TR_NOOP("Run Count"), QT_TR_NOOP("Cycles Spent"), QT_TR_NOOP("Cycles Avg."), + QT_TR_NOOP("Cycles %"), QT_TR_NOOP("Time Spent (ns)"), QT_TR_NOOP("Time Avg. (ns)"), + 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.begin() + row, static_cast(count)}) + m_jit_interface.EraseSingleBlock(block); + m_jit_blocks.remove(row, count); + m_symbol_list.remove(row, count); + endRemoveRows(); + return true; +} diff --git a/Source/Core/DolphinQt/Debugger/JitBlockTableModel.h b/Source/Core/DolphinQt/Debugger/JitBlockTableModel.h new file mode 100644 index 000000000000..f0942955fec8 --- /dev/null +++ b/Source/Core/DolphinQt/Debugger/JitBlockTableModel.h @@ -0,0 +1,129 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#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 + +public: + using Column = JitBlockTableModelColumn::EnumType; + using UserRole = JitBlockTableModelUserRole::EnumType; + using JitBlockRefs = QList>; + using SymbolListValueType = Common::Lazy; + using SymbolList = QList; + +public: + const JitBlock& GetJitBlock(const QModelIndex& index) const; + const JitBlockRefs& GetJitBlockRefs() const { return m_jit_blocks; } + const SymbolList& GetSymbolList() const { return m_symbol_list; } + +private: + void SumOverallCosts(); + void PrefetchSymbols(); + void Clear(); + void Update(Core::State state); + void UpdateProfileData(); + void UpdateSymbols(); + + void ConnectSlots(); + void DisconnectSlots(); + void Hide(); + void Show(); + + [[nodiscard]] QVariant DisplayRoleData(const QModelIndex& index) const; + [[nodiscard]] QVariant TextAlignmentRoleData(const QModelIndex& index) const; + [[nodiscard]] QVariant SortRoleData(const QModelIndex& index) const; + +public: // Qt slots + // Always connected (external signals) + void OnHideSignal(); + void OnShowSignal(); + void OnSortIndicatorChanged(int logicalIndex, Qt::SortOrder order); + void OnFilterSymbolTextChanged(const QString& string); + +private: // Qt slots + // Conditionally connected (external signals) + void OnJitCacheCleared(); + void OnJitProfileDataWiped(); + void OnUpdateDisasmDialog(); + void OnPPCSymbolsUpdated(); + void OnEmulationStateChanged(Core::State state); + +public: // Qt overrides + 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; + +private: + Core::System& m_system; + JitInterface& m_jit_interface; + PPCSymbolDB& m_ppc_symbol_db; + + JitBlockRefs m_jit_blocks; + SymbolList m_symbol_list; + u64 m_overall_cycles_spent; + JitBlock::ProfileData::Clock::duration m_overall_time_spent; + bool m_sorting_by_symbols = false; + bool m_filtering_by_symbols = false; +}; diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index cbcd07bfad0d..3140873983bc 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -144,6 +144,7 @@ + @@ -248,6 +249,7 @@ + @@ -357,6 +359,7 @@ + diff --git a/Source/Core/DolphinQt/Host.cpp b/Source/Core/DolphinQt/Host.cpp index fe339364ab79..d0b86ba820c6 100644 --- a/Source/Core/DolphinQt/Host.cpp +++ b/Source/Core/DolphinQt/Host.cpp @@ -238,6 +238,16 @@ void Host_UpdateDisasmDialog() QueueOnObject(QApplication::instance(), [] { emit Host::GetInstance()->UpdateDisasmDialog(); }); } +void Host_JitCacheCleared() +{ + QueueOnObject(QApplication::instance(), [] { emit Host::GetInstance()->JitCacheCleared(); }); +} + +void Host_JitProfileDataWiped() +{ + QueueOnObject(QApplication::instance(), [] { emit Host::GetInstance()->JitProfileDataWiped(); }); +} + void Host_PPCSymbolsChanged() { QueueOnObject(QApplication::instance(), [] { emit Host::GetInstance()->PPCSymbolsChanged(); }); diff --git a/Source/Core/DolphinQt/Host.h b/Source/Core/DolphinQt/Host.h index 645c4bd54d24..6b886ee81076 100644 --- a/Source/Core/DolphinQt/Host.h +++ b/Source/Core/DolphinQt/Host.h @@ -38,6 +38,8 @@ class Host final : public QObject void RequestStop(); void RequestRenderSize(int w, int h); void UpdateDisasmDialog(); + void JitCacheCleared(); + void JitProfileDataWiped(); void PPCSymbolsChanged(); private: diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index aacf4f933bac..ea145a891efc 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -446,7 +446,7 @@ void MainWindow::CreateComponents() m_wii_tas_input_windows[i] = new WiiTASInputWindow(nullptr, i); } - m_jit_widget = new JITWidget(this); + m_jit_widget = new JITWidget(Core::System::GetInstance(), this); m_log_widget = new LogWidget(this); m_log_config_widget = new LogConfigWidget(this); m_memory_widget = new MemoryWidget(Core::System::GetInstance(), this); @@ -471,6 +471,7 @@ void MainWindow::CreateComponents() m_code_widget->SetAddress(addr, CodeViewWidget::SetAddressUpdate::WithDetailedUpdate); }; + connect(m_jit_widget, &JITWidget::SetCodeAddress, m_code_widget, &CodeWidget::OnSetCodeAddress); connect(m_watch_widget, &WatchWidget::RequestMemoryBreakpoint, request_memory_breakpoint); connect(m_watch_widget, &WatchWidget::ShowMemory, m_memory_widget, &MemoryWidget::SetAddress); connect(m_register_widget, &RegisterWidget::RequestMemoryBreakpoint, request_memory_breakpoint); @@ -485,7 +486,8 @@ void MainWindow::CreateComponents() connect(m_code_widget, &CodeWidget::BreakpointsChanged, m_breakpoint_widget, &BreakpointWidget::Update); - connect(m_code_widget, &CodeWidget::RequestPPCComparison, m_jit_widget, &JITWidget::Compare); + connect(m_code_widget, &CodeWidget::RequestPPCComparison, m_jit_widget, + &JITWidget::OnRequestPPCComparison); connect(m_code_widget, &CodeWidget::ShowMemory, m_memory_widget, &MemoryWidget::SetAddress); connect(m_memory_widget, &MemoryWidget::BreakpointsChanged, m_breakpoint_widget, &BreakpointWidget::Update); diff --git a/Source/Core/DolphinQt/MenuBar.cpp b/Source/Core/DolphinQt/MenuBar.cpp index 46bd68b6d450..a7be58d3e881 100644 --- a/Source/Core/DolphinQt/MenuBar.cpp +++ b/Source/Core/DolphinQt/MenuBar.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -95,6 +96,7 @@ MenuBar::MenuBar(QWidget* parent) : QMenuBar(parent) connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [=, this](Core::State state) { OnEmulationStateChanged(state); }); + connect(&Settings::Instance(), &Settings::ConfigChanged, this, &MenuBar::OnConfigChanged); connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this, [this] { OnEmulationStateChanged(Core::GetState(Core::System::GetInstance())); }); @@ -162,7 +164,8 @@ void MenuBar::OnEmulationStateChanged(Core::State state) m_jit_clear_cache->setEnabled(running); m_jit_log_coverage->setEnabled(!running); m_jit_search_instruction->setEnabled(running); - m_jit_write_cache_log_dump->setEnabled(running && jit_exists); + m_jit_wipe_profiling_data->setEnabled(jit_exists); + m_jit_write_cache_log_dump->setEnabled(jit_exists); // Symbols m_symbols->setEnabled(running); @@ -173,6 +176,12 @@ void MenuBar::OnEmulationStateChanged(Core::State state) OnDebugModeToggled(Settings::Instance().IsDebugModeEnabled()); } +void MenuBar::OnConfigChanged() +{ + const QSignalBlocker blocker(m_jit_profile_blocks); + m_jit_profile_blocks->setChecked(Config::Get(Config::MAIN_DEBUG_JIT_ENABLE_PROFILING)); +} + void MenuBar::OnDebugModeToggled(bool enabled) { // Options @@ -203,6 +212,12 @@ void MenuBar::OnDebugModeToggled(bool enabled) } } +void MenuBar::OnWipeJitBlockProfilingData() +{ + auto& system = Core::System::GetInstance(); + system.GetJitInterface().WipeBlockProfilingData(Core::CPUThreadGuard{system}); +} + void MenuBar::OnWriteJitBlockLogDump() { const std::string filename = fmt::format("{}{}.txt", File::GetUserPath(D_DUMPDEBUG_JITBLOCKS_IDX), @@ -927,6 +942,8 @@ void MenuBar::AddJITMenu() connect(m_jit_profile_blocks, &QAction::toggled, [](bool enabled) { Config::SetBaseOrCurrent(Config::MAIN_DEBUG_JIT_ENABLE_PROFILING, enabled); }); + m_jit_wipe_profiling_data = m_jit->addAction(tr("Wipe JIT Block Profiling Data"), this, + &MenuBar::OnWipeJitBlockProfilingData); m_jit_write_cache_log_dump = m_jit->addAction(tr("Write JIT Block Log Dump"), this, &MenuBar::OnWriteJitBlockLogDump); diff --git a/Source/Core/DolphinQt/MenuBar.h b/Source/Core/DolphinQt/MenuBar.h index b6620291d22a..f9d0c6ab552b 100644 --- a/Source/Core/DolphinQt/MenuBar.h +++ b/Source/Core/DolphinQt/MenuBar.h @@ -127,6 +127,7 @@ class MenuBar final : public QMenuBar private: void OnEmulationStateChanged(Core::State state); + void OnConfigChanged(); void AddFileMenu(); @@ -185,6 +186,7 @@ class MenuBar final : public QMenuBar void OnRecordingStatusChanged(bool recording); void OnReadOnlyModeChanged(bool read_only); void OnDebugModeToggled(bool enabled); + void OnWipeJitBlockProfilingData(); void OnWriteJitBlockLogDump(); QString GetSignatureSelector() const; @@ -270,6 +272,7 @@ class MenuBar final : public QMenuBar QAction* m_jit_log_coverage; QAction* m_jit_search_instruction; QAction* m_jit_profile_blocks; + QAction* m_jit_wipe_profiling_data; QAction* m_jit_write_cache_log_dump; QAction* m_jit_off; QAction* m_jit_loadstore_off; diff --git a/Source/Core/DolphinQt/QtUtils/ClickableStatusBar.h b/Source/Core/DolphinQt/QtUtils/ClickableStatusBar.h new file mode 100644 index 000000000000..6fb92446e381 --- /dev/null +++ b/Source/Core/DolphinQt/QtUtils/ClickableStatusBar.h @@ -0,0 +1,22 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +// I wanted a QStatusBar that emits a signal when clicked. Qt only provides event overrides. +class ClickableStatusBar final : public QStatusBar +{ + Q_OBJECT + +public: + explicit ClickableStatusBar(QWidget* parent) : QStatusBar(parent) {} + ~ClickableStatusBar() override = default; + +signals: + void pressed(); + +protected: + void mousePressEvent(QMouseEvent* event) override { emit pressed(); } +}; diff --git a/Source/Core/DolphinTool/ToolHeadlessPlatform.cpp b/Source/Core/DolphinTool/ToolHeadlessPlatform.cpp index 8c1772406fdc..7782df638e3b 100644 --- a/Source/Core/DolphinTool/ToolHeadlessPlatform.cpp +++ b/Source/Core/DolphinTool/ToolHeadlessPlatform.cpp @@ -61,6 +61,14 @@ void Host_UpdateDisasmDialog() { } +void Host_JitCacheCleared() +{ +} + +void Host_JitProfileDataWiped() +{ +} + void Host_UpdateMainFrame() { } diff --git a/Source/Core/UICommon/CMakeLists.txt b/Source/Core/UICommon/CMakeLists.txt index 22e01d8d760c..4407e0a123d9 100644 --- a/Source/Core/UICommon/CMakeLists.txt +++ b/Source/Core/UICommon/CMakeLists.txt @@ -3,8 +3,6 @@ add_library(uicommon AutoUpdate.h CommandLineParse.cpp CommandLineParse.h - Disassembler.cpp - Disassembler.h DiscordPresence.cpp DiscordPresence.h GameFile.cpp @@ -37,11 +35,6 @@ PRIVATE $<$:${IOK_LIBRARY}> ) -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(uicommon PRIVATE bdisasm) -endif() - if(ENABLE_X11 AND X11_FOUND) target_include_directories(uicommon PRIVATE ${X11_INCLUDE_DIR}) target_sources(uicommon PRIVATE X11Utils.cpp) @@ -52,16 +45,6 @@ if(TARGET LibUSB::LibUSB) target_link_libraries(uicommon PRIVATE LibUSB::LibUSB) endif() -if(ENABLE_LLVM) - find_package(LLVM CONFIG QUIET) - if(LLVM_FOUND AND TARGET LLVM) - message(STATUS "LLVM found, enabling LLVM support in disassembler") - target_compile_definitions(uicommon PRIVATE HAVE_LLVM) - target_link_libraries(uicommon PRIVATE LLVM) - target_include_directories(uicommon PRIVATE ${LLVM_INCLUDE_DIRS}) - endif() -endif() - if(USE_DISCORD_PRESENCE) target_compile_definitions(uicommon PRIVATE -DUSE_DISCORD_PRESENCE) target_link_libraries(uicommon PRIVATE discord-rpc) diff --git a/Source/Core/UICommon/Disassembler.cpp b/Source/Core/UICommon/Disassembler.cpp deleted file mode 100644 index 28857f6a6ce6..000000000000 --- a/Source/Core/UICommon/Disassembler.cpp +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright 2008 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "UICommon/Disassembler.h" - -#include - -#if defined(HAVE_LLVM) -#include -#include -#include -#elif defined(_M_X86_64) -#include // 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 GetNewDisassembler(const std::string& arch) -{ -#if defined(HAVE_LLVM) - if (arch == "x86") - return std::make_unique("x86_64-none-unknown"); - if (arch == "aarch64") - return std::make_unique("aarch64-none-unknown", 4, "cortex-a57"); - if (arch == "armv7") - return std::make_unique("armv7-none-unknown", 4, "cortex-a15"); -#elif defined(_M_X86_64) - if (arch == "x86") - return std::make_unique(); -#endif - return std::make_unique(); -} - -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); -} diff --git a/Source/Core/UICommon/Disassembler.h b/Source/Core/UICommon/Disassembler.h deleted file mode 100644 index 6c193c49c0c2..000000000000 --- a/Source/Core/UICommon/Disassembler.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2008 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#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 GetNewDisassembler(const std::string& arch); -DisassembleResult DisassembleBlock(HostDisassembler* disasm, u32 address); diff --git a/Source/DSPTool/StubHost.cpp b/Source/DSPTool/StubHost.cpp index d32346a45772..b8427762c677 100644 --- a/Source/DSPTool/StubHost.cpp +++ b/Source/DSPTool/StubHost.cpp @@ -41,6 +41,12 @@ bool Host_UpdateDiscordPresenceRaw(const std::string& details, const std::string void Host_UpdateDisasmDialog() { } +void Host_JitCacheCleared() +{ +} +void Host_JitProfileDataWiped() +{ +} void Host_UpdateMainFrame() { } diff --git a/Source/UnitTests/Core/PageFaultTest.cpp b/Source/UnitTests/Core/PageFaultTest.cpp index fd58e1fb45e4..b893c16b1a58 100644 --- a/Source/UnitTests/Core/PageFaultTest.cpp +++ b/Source/UnitTests/Core/PageFaultTest.cpp @@ -40,6 +40,11 @@ class PageFaultFakeJit : public JitBase // JitBase methods JitBaseBlockCache* GetBlockCache() override { return nullptr; } void Jit(u32 em_address) override {} + void EraseSingleBlock(const JitBlock& block) override {} + void DisasmNearCode(const JitBlock&, std::ostream& stream, std::size_t&) const override {} + void DisasmFarCode(const JitBlock&, std::ostream& stream, std::size_t&) const override {} + std::pair GetNearMemoryInfo() const override { return {}; } + std::pair GetFarMemoryInfo() const override { return {}; } const CommonAsmRoutinesBase* GetAsmRoutines() override { return nullptr; } virtual bool HandleFault(uintptr_t access_address, SContext* ctx) override { diff --git a/Source/UnitTests/StubHost.cpp b/Source/UnitTests/StubHost.cpp index 47200bd40c77..d8f3a68170f0 100644 --- a/Source/UnitTests/StubHost.cpp +++ b/Source/UnitTests/StubHost.cpp @@ -41,6 +41,12 @@ bool Host_UpdateDiscordPresenceRaw(const std::string& details, const std::string void Host_UpdateDisasmDialog() { } +void Host_JitCacheCleared() +{ +} +void Host_JitProfileDataWiped() +{ +} void Host_UpdateMainFrame() { }