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