mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2024-05-20 12:58:05 -04:00
Compare commits
9 commits
49534f4b79
...
4ba6056217
Author | SHA1 | Date | |
---|---|---|---|
4ba6056217 | |||
2da5f42426 | |||
97adb327e4 | |||
12a5004490 | |||
802c344f93 | |||
2665921dce | |||
acb162eb10 | |||
1a3f3d7842 | |||
c7f65d3dc9 |
25
Externals/rangeset/include/rangeset/rangeset.h
vendored
25
Externals/rangeset/include/rangeset/rangeset.h
vendored
|
@ -3,6 +3,7 @@
|
|||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
namespace HyoutaUtilities {
|
||||
template <typename T> class RangeSet {
|
||||
|
@ -254,7 +255,31 @@ public:
|
|||
return !(*this == other);
|
||||
}
|
||||
|
||||
// Get free size and fragmentation ratio
|
||||
std::pair<std::size_t, double> get_stats() const {
|
||||
std::size_t free_total = 0;
|
||||
if (begin() == end())
|
||||
return {free_total, 1.0};
|
||||
std::size_t largest_size = 0;
|
||||
for (auto iter = begin(); iter != end(); ++iter) {
|
||||
const std::size_t size = calc_size(iter.from(), iter.to());
|
||||
if (size > largest_size)
|
||||
largest_size = size;
|
||||
free_total += size;
|
||||
}
|
||||
return {free_total, static_cast<double>(free_total - largest_size) / free_total};
|
||||
}
|
||||
|
||||
private:
|
||||
static std::size_t calc_size(T from, T to) {
|
||||
if constexpr (std::is_pointer_v<T>) {
|
||||
// For pointers we don't want pointer arithmetic here, else void* breaks.
|
||||
return reinterpret_cast<std::size_t>(to) - reinterpret_cast<std::size_t>(from);
|
||||
} else {
|
||||
return static_cast<std::size_t>(to - from);
|
||||
}
|
||||
}
|
||||
|
||||
// Assumptions that can be made about the data:
|
||||
// - Range are stored in the form [from, to[
|
||||
// That is, the starting value is inclusive, and the end value is exclusive.
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace HyoutaUtilities {
|
||||
// Like RangeSet, but additionally stores a map of the ranges sorted by their size, for quickly finding the largest or
|
||||
|
@ -398,6 +399,16 @@ public:
|
|||
return !(*this == other);
|
||||
}
|
||||
|
||||
// Get free size and fragmentation ratio
|
||||
std::pair<std::size_t, double> get_stats() const {
|
||||
std::size_t free_total = 0;
|
||||
if (begin() == end())
|
||||
return {free_total, 1.0};
|
||||
for (auto iter = begin(); iter != end(); ++iter)
|
||||
free_total += calc_size(iter.from(), iter.to());
|
||||
return {free_total, static_cast<double>(free_total - Sizes.begin()->first) / free_total};
|
||||
}
|
||||
|
||||
private:
|
||||
static SizeT calc_size(T from, T to) {
|
||||
if constexpr (std::is_pointer_v<T>) {
|
||||
|
|
|
@ -385,6 +385,11 @@ public final class NativeLibrary
|
|||
|
||||
public static native boolean IsRunningAndUnpaused();
|
||||
|
||||
/**
|
||||
* Re-initialize software JitBlock profiling data
|
||||
*/
|
||||
public static native void WipeJitBlockProfilingData();
|
||||
|
||||
/**
|
||||
* Writes out the JitBlock Cache log dump
|
||||
*/
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -408,6 +408,8 @@
|
|||
<string name="debug_large_entry_points_map">Disable Large Entry Points Map</string>
|
||||
<string name="debug_jit_profiling_header">Jit Profiling</string>
|
||||
<string name="debug_jit_enable_block_profiling">Enable Jit Block Profiling</string>
|
||||
<string name="debug_jit_wipe_block_profiling_data">Wipe Jit Block Profiling Data</string>
|
||||
<string name="debug_jit_wipe_block_profiling_data_alert">Re-initialize JIT block profiling data?</string>
|
||||
<string name="debug_jit_write_block_log_dump">Write Jit Block Log Dump</string>
|
||||
<string name="debug_jit_header">Jit</string>
|
||||
<string name="debug_jitoff">Jit Disabled</string>
|
||||
|
|
|
@ -147,6 +147,14 @@ void Host_UpdateDisasmDialog()
|
|||
{
|
||||
}
|
||||
|
||||
void Host_JitCacheCleared()
|
||||
{
|
||||
}
|
||||
|
||||
void Host_JitProfileDataWiped()
|
||||
{
|
||||
}
|
||||
|
||||
void Host_UpdateMainFrame()
|
||||
{
|
||||
}
|
||||
|
@ -406,6 +414,22 @@ JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetMaxLogLev
|
|||
return static_cast<jint>(Common::Log::MAX_LOGLEVEL);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_WipeJitBlockProfilingData(
|
||||
JNIEnv* env, jclass native_library_class)
|
||||
{
|
||||
HostThreadLock guard;
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& jit_interface = system.GetJitInterface();
|
||||
if (jit_interface.GetCore() == nullptr)
|
||||
{
|
||||
env->CallStaticVoidMethod(native_library_class, IDCache::GetDisplayToastMsg(),
|
||||
ToJString(env, Common::GetStringT("JIT is not active")),
|
||||
static_cast<jboolean>(false));
|
||||
return;
|
||||
}
|
||||
jit_interface.WipeBlockProfilingData(Core::CPUThreadGuard{system});
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_WriteJitBlockLogDump(
|
||||
JNIEnv* env, jclass native_library_class)
|
||||
{
|
||||
|
|
|
@ -75,6 +75,8 @@ add_library(common
|
|||
Hash.cpp
|
||||
Hash.h
|
||||
HookableEvent.h
|
||||
HostDisassembler.cpp
|
||||
HostDisassembler.h
|
||||
HttpRequest.cpp
|
||||
HttpRequest.h
|
||||
Image.cpp
|
||||
|
@ -143,6 +145,7 @@ add_library(common
|
|||
TraversalClient.h
|
||||
TraversalProto.h
|
||||
TypeUtils.h
|
||||
Unreachable.h
|
||||
UPnP.cpp
|
||||
UPnP.h
|
||||
VariantUtil.h
|
||||
|
@ -178,6 +181,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
|
||||
|
@ -328,6 +336,26 @@ if(OPROFILE_FOUND)
|
|||
target_link_libraries(common PRIVATE OProfile::OProfile)
|
||||
endif()
|
||||
|
||||
if(ENABLE_LLVM)
|
||||
find_package(LLVM CONFIG)
|
||||
if(LLVM_FOUND)
|
||||
message(STATUS "LLVM found, enabling LLVM support in disassembler")
|
||||
# Minimal documentation about LLVM's CMake functions is available here:
|
||||
# https://releases.llvm.org/16.0.0/docs/CMake.html#embedding-llvm-in-your-project
|
||||
# https://groups.google.com/g/llvm-dev/c/YeEVe7HTasQ?pli=1
|
||||
#
|
||||
# However, you have to read the source code in any case.
|
||||
# Look for LLVM-Config.cmake in your (Unix) system:
|
||||
# $ find /usr -name LLVM-Config\\.cmake 2>/dev/null
|
||||
llvm_expand_pseudo_components(LLVM_EXPAND_COMPONENTS
|
||||
AllTargetsInfos AllTargetsDisassemblers AllTargetsCodeGens
|
||||
)
|
||||
llvm_config(common USE_SHARED
|
||||
mcdisassembler target ${LLVM_EXPAND_COMPONENTS}
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(UNIX)
|
||||
# Posix networking code needs to be fixed for Windows
|
||||
add_executable(traversal_server TraversalServer.cpp)
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace Common
|
|||
// having to prefix them with gen-> or something similar.
|
||||
// Example implementation:
|
||||
// class JIT : public CodeBlock<ARMXEmitter> {}
|
||||
template <class T>
|
||||
template <class T, bool executable = true>
|
||||
class CodeBlock : public T
|
||||
{
|
||||
private:
|
||||
|
@ -53,7 +53,10 @@ public:
|
|||
{
|
||||
region_size = size;
|
||||
total_region_size = size;
|
||||
region = static_cast<u8*>(Common::AllocateExecutableMemory(total_region_size));
|
||||
if constexpr (executable)
|
||||
region = static_cast<u8*>(Common::AllocateExecutableMemory(total_region_size));
|
||||
else
|
||||
region = static_cast<u8*>(Common::AllocateMemoryPages(total_region_size));
|
||||
T::SetCodePtr(region, region + size);
|
||||
}
|
||||
|
||||
|
|
177
Source/Core/Common/HostDisassembler.cpp
Normal file
177
Source/Core/Common/HostDisassembler.cpp
Normal file
|
@ -0,0 +1,177 @@
|
|||
// Copyright 2008 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Common/HostDisassembler.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
#if defined(HAVE_LLVM)
|
||||
#include <llvm-c/Disassembler.h>
|
||||
#include <llvm-c/Target.h>
|
||||
#elif defined(_M_X86_64)
|
||||
#include <disasm.h> // Bochs
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_LLVM)
|
||||
class HostDisassemblerLLVM final : public HostDisassembler
|
||||
{
|
||||
public:
|
||||
explicit HostDisassemblerLLVM(const char* host_disasm, const char* cpu = "",
|
||||
std::size_t inst_size = 0);
|
||||
~HostDisassemblerLLVM();
|
||||
|
||||
private:
|
||||
LLVMDisasmContextRef m_llvm_context;
|
||||
std::size_t m_instruction_size;
|
||||
|
||||
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<u8*>(begin), static_cast<u64>(end - begin),
|
||||
reinterpret_cast<std::uintptr_t>(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<std::uintptr_t>(begin),
|
||||
reinterpret_cast<std::uintptr_t>(begin),
|
||||
const_cast<u8*>(begin), inst_disasm);
|
||||
fmt::println(stream, "{}\t{}", fmt::ptr(begin), inst_disasm);
|
||||
begin += inst_size;
|
||||
++instruction_count;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
std::unique_ptr<HostDisassembler> HostDisassembler::Factory(Platform arch)
|
||||
{
|
||||
switch (arch)
|
||||
{
|
||||
#if defined(HAVE_LLVM)
|
||||
case Platform::x86_64:
|
||||
return std::make_unique<HostDisassemblerLLVM>("x86_64-none-unknown");
|
||||
case Platform::aarch64:
|
||||
return std::make_unique<HostDisassemblerLLVM>("aarch64-none-unknown", "cortex-a57", 4);
|
||||
#elif defined(_M_X86_64)
|
||||
case Platform::x86_64:
|
||||
return std::make_unique<HostDisassemblerBochs>();
|
||||
#else
|
||||
case Platform{}: // warning C4065: "switch statement contains 'default' but no 'case' labels"
|
||||
#endif
|
||||
default:
|
||||
return std::make_unique<HostDisassembler>();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
30
Source/Core/Common/HostDisassembler.h
Normal file
30
Source/Core/Common/HostDisassembler.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Copyright 2008 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <iosfwd>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
class HostDisassembler
|
||||
{
|
||||
public:
|
||||
enum class Platform
|
||||
{
|
||||
x86_64,
|
||||
aarch64,
|
||||
};
|
||||
|
||||
virtual ~HostDisassembler() = default;
|
||||
|
||||
static std::unique_ptr<HostDisassembler> Factory(Platform arch);
|
||||
|
||||
virtual 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);
|
||||
};
|
21
Source/Core/Common/Unreachable.h
Normal file
21
Source/Core/Common/Unreachable.h
Normal file
|
@ -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
|
|
@ -481,8 +481,10 @@ add_library(core
|
|||
PowerPC/BreakPoints.h
|
||||
PowerPC/CachedInterpreter/CachedInterpreter.cpp
|
||||
PowerPC/CachedInterpreter/CachedInterpreter.h
|
||||
PowerPC/CachedInterpreter/InterpreterBlockCache.cpp
|
||||
PowerPC/CachedInterpreter/InterpreterBlockCache.h
|
||||
PowerPC/CachedInterpreter/CachedInterpreterBlockCache.cpp
|
||||
PowerPC/CachedInterpreter/CachedInterpreterBlockCache.h
|
||||
PowerPC/CachedInterpreter/CachedInterpreterEmitter.cpp
|
||||
PowerPC/CachedInterpreter/CachedInterpreterEmitter.h
|
||||
PowerPC/ConditionRegister.cpp
|
||||
PowerPC/ConditionRegister.h
|
||||
PowerPC/Expression.cpp
|
||||
|
@ -650,11 +652,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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -3,9 +3,19 @@
|
|||
|
||||
#include "Core/PowerPC/CachedInterpreter/CachedInterpreter.h"
|
||||
|
||||
#include <array>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/GekkoDisassembler.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/HLE/HLE.h"
|
||||
#include "Core/HW/CPU.h"
|
||||
|
@ -16,65 +26,7 @@
|
|||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
struct CachedInterpreter::Instruction
|
||||
{
|
||||
using CommonCallback = void (*)(UGeckoInstruction);
|
||||
using ConditionalCallback = bool (*)(u32);
|
||||
using InterpreterCallback = void (*)(Interpreter&, UGeckoInstruction);
|
||||
using CachedInterpreterCallback = void (*)(CachedInterpreter&, UGeckoInstruction);
|
||||
using ConditionalCachedInterpreterCallback = bool (*)(CachedInterpreter&, u32);
|
||||
|
||||
Instruction() {}
|
||||
Instruction(const CommonCallback c, UGeckoInstruction i)
|
||||
: common_callback(c), data(i.hex), type(Type::Common)
|
||||
{
|
||||
}
|
||||
|
||||
Instruction(const ConditionalCallback c, u32 d)
|
||||
: conditional_callback(c), data(d), type(Type::Conditional)
|
||||
{
|
||||
}
|
||||
|
||||
Instruction(const InterpreterCallback c, UGeckoInstruction i)
|
||||
: interpreter_callback(c), data(i.hex), type(Type::Interpreter)
|
||||
{
|
||||
}
|
||||
|
||||
Instruction(const CachedInterpreterCallback c, UGeckoInstruction i)
|
||||
: cached_interpreter_callback(c), data(i.hex), type(Type::CachedInterpreter)
|
||||
{
|
||||
}
|
||||
|
||||
Instruction(const ConditionalCachedInterpreterCallback c, u32 d)
|
||||
: conditional_cached_interpreter_callback(c), data(d),
|
||||
type(Type::ConditionalCachedInterpreter)
|
||||
{
|
||||
}
|
||||
|
||||
enum class Type
|
||||
{
|
||||
Abort,
|
||||
Common,
|
||||
Conditional,
|
||||
Interpreter,
|
||||
CachedInterpreter,
|
||||
ConditionalCachedInterpreter,
|
||||
};
|
||||
|
||||
union
|
||||
{
|
||||
const CommonCallback common_callback = nullptr;
|
||||
const ConditionalCallback conditional_callback;
|
||||
const InterpreterCallback interpreter_callback;
|
||||
const CachedInterpreterCallback cached_interpreter_callback;
|
||||
const ConditionalCachedInterpreterCallback conditional_cached_interpreter_callback;
|
||||
};
|
||||
|
||||
u32 data = 0;
|
||||
Type type = Type::Abort;
|
||||
};
|
||||
|
||||
CachedInterpreter::CachedInterpreter(Core::System& system) : JitBase(system)
|
||||
CachedInterpreter::CachedInterpreter(Core::System& system) : JitBase(system), m_block_cache(*this)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -84,7 +36,8 @@ void CachedInterpreter::Init()
|
|||
{
|
||||
RefreshConfig();
|
||||
|
||||
m_code.reserve(CODE_SIZE / sizeof(Instruction));
|
||||
AllocCodeSpace(CODE_SIZE);
|
||||
ResetFreeMemoryRanges();
|
||||
|
||||
jo.enableBlocklink = false;
|
||||
|
||||
|
@ -100,11 +53,6 @@ void CachedInterpreter::Shutdown()
|
|||
m_block_cache.Shutdown();
|
||||
}
|
||||
|
||||
u8* CachedInterpreter::GetCodePtr()
|
||||
{
|
||||
return reinterpret_cast<u8*>(m_code.data() + m_code.size());
|
||||
}
|
||||
|
||||
void CachedInterpreter::ExecuteOneBlock()
|
||||
{
|
||||
const u8* normal_entry = m_block_cache.Dispatch();
|
||||
|
@ -114,40 +62,13 @@ void CachedInterpreter::ExecuteOneBlock()
|
|||
return;
|
||||
}
|
||||
|
||||
const Instruction* code = reinterpret_cast<const Instruction*>(normal_entry);
|
||||
auto& interpreter = m_system.GetInterpreter();
|
||||
|
||||
for (; code->type != Instruction::Type::Abort; ++code)
|
||||
for (auto& ppc_state = m_ppc_state;;)
|
||||
{
|
||||
switch (code->type)
|
||||
{
|
||||
case Instruction::Type::Common:
|
||||
code->common_callback(UGeckoInstruction(code->data));
|
||||
const auto callback = *reinterpret_cast<const AnyCallback*>(normal_entry);
|
||||
if (const auto distance = callback(ppc_state, normal_entry + sizeof(callback)))
|
||||
normal_entry += distance;
|
||||
else
|
||||
break;
|
||||
|
||||
case Instruction::Type::Conditional:
|
||||
if (code->conditional_callback(code->data))
|
||||
return;
|
||||
break;
|
||||
|
||||
case Instruction::Type::Interpreter:
|
||||
code->interpreter_callback(interpreter, UGeckoInstruction(code->data));
|
||||
break;
|
||||
|
||||
case Instruction::Type::CachedInterpreter:
|
||||
code->cached_interpreter_callback(*this, UGeckoInstruction(code->data));
|
||||
break;
|
||||
|
||||
case Instruction::Type::ConditionalCachedInterpreter:
|
||||
if (code->conditional_cached_interpreter_callback(*this, code->data))
|
||||
return;
|
||||
break;
|
||||
|
||||
default:
|
||||
ERROR_LOG_FMT(POWERPC, "Unknown CachedInterpreter Instruction: {}",
|
||||
static_cast<int>(code->type));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -177,94 +98,167 @@ void CachedInterpreter::SingleStep()
|
|||
ExecuteOneBlock();
|
||||
}
|
||||
|
||||
void CachedInterpreter::EndBlock(CachedInterpreter& cached_interpreter, UGeckoInstruction data)
|
||||
s32 CachedInterpreter::EndBlock(PowerPC::PowerPCState& ppc_state, const EndBlockOperands& operands)
|
||||
{
|
||||
auto& ppc_state = cached_interpreter.m_ppc_state;
|
||||
const auto& [downcount, num_load_stores, num_fp_inst] = operands;
|
||||
ppc_state.pc = ppc_state.npc;
|
||||
ppc_state.downcount -= data.hex;
|
||||
PowerPC::UpdatePerformanceMonitor(data.hex, 0, 0, ppc_state);
|
||||
ppc_state.downcount -= downcount;
|
||||
PowerPC::UpdatePerformanceMonitor(downcount, num_load_stores, num_fp_inst, ppc_state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CachedInterpreter::UpdateNumLoadStoreInstructions(CachedInterpreter& cached_interpreter,
|
||||
UGeckoInstruction data)
|
||||
s32 CachedInterpreter::EndBlock(std::ostream& stream, const EndBlockOperands& operands)
|
||||
{
|
||||
PowerPC::UpdatePerformanceMonitor(0, data.hex, 0, cached_interpreter.m_ppc_state);
|
||||
const auto& [downcount, num_load_stores, num_fp_inst] = operands;
|
||||
fmt::println(stream, "EndBlock(downcount={}, num_load_stores={}, num_fp_inst={})", downcount,
|
||||
num_load_stores, num_fp_inst);
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
void CachedInterpreter::UpdateNumFloatingPointInstructions(CachedInterpreter& cached_interpreter,
|
||||
UGeckoInstruction data)
|
||||
s32 CachedInterpreter::Interpret(PowerPC::PowerPCState& ppc_state,
|
||||
const InterpretOperands& operands)
|
||||
{
|
||||
PowerPC::UpdatePerformanceMonitor(0, 0, data.hex, cached_interpreter.m_ppc_state);
|
||||
const auto& [interpreter, func, current_pc, inst] = operands;
|
||||
func(interpreter, inst);
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
void CachedInterpreter::WritePC(CachedInterpreter& cached_interpreter, UGeckoInstruction data)
|
||||
s32 CachedInterpreter::Interpret(std::ostream& stream, const InterpretOperands& operands)
|
||||
{
|
||||
auto& ppc_state = cached_interpreter.m_ppc_state;
|
||||
ppc_state.pc = data.hex;
|
||||
ppc_state.npc = data.hex + 4;
|
||||
const auto& [interpreter, func, current_pc, inst] = operands;
|
||||
fmt::println(stream, "0x{:08x} {}", current_pc,
|
||||
Common::GekkoDisassembler::Disassemble(inst.hex, current_pc));
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
void CachedInterpreter::WriteBrokenBlockNPC(CachedInterpreter& cached_interpreter,
|
||||
UGeckoInstruction data)
|
||||
s32 CachedInterpreter::InterpretAndCheckExceptions(
|
||||
PowerPC::PowerPCState& ppc_state, const InterpretAndCheckExceptionsOperands& operands)
|
||||
{
|
||||
cached_interpreter.m_ppc_state.npc = data.hex;
|
||||
const auto& [interpreter, func, current_pc, inst, power_pc, downcount] = operands;
|
||||
func(interpreter, inst);
|
||||
if ((ppc_state.Exceptions & (EXCEPTION_DSI | EXCEPTION_PROGRAM)) != 0)
|
||||
{
|
||||
ppc_state.pc = current_pc;
|
||||
ppc_state.downcount -= downcount;
|
||||
power_pc.CheckExceptions();
|
||||
return 0;
|
||||
}
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
bool CachedInterpreter::CheckFPU(CachedInterpreter& cached_interpreter, u32 data)
|
||||
s32 CachedInterpreter::InterpretAndCheckExceptions(
|
||||
std::ostream& stream, const InterpretAndCheckExceptionsOperands& operands)
|
||||
{
|
||||
auto& ppc_state = cached_interpreter.m_ppc_state;
|
||||
const auto& [interpreter, func, current_pc, inst, power_pc, downcount] = operands;
|
||||
fmt::println(stream, "0x{:08x} {} [exception possible: downcount={}]", current_pc,
|
||||
Common::GekkoDisassembler::Disassemble(inst.hex, current_pc), downcount);
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
s32 CachedInterpreter::HLEFunction(PowerPC::PowerPCState& ppc_state,
|
||||
const HLEFunctionOperands& operands)
|
||||
{
|
||||
const auto& [system, current_pc, hook_index] = operands;
|
||||
ppc_state.pc = current_pc;
|
||||
HLE::Execute(Core::CPUThreadGuard{system}, current_pc, hook_index);
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
s32 CachedInterpreter::HLEFunction(std::ostream& stream, const HLEFunctionOperands& operands)
|
||||
{
|
||||
const auto& [system, current_pc, hook_index] = operands;
|
||||
fmt::println(stream, "HLEFunction(current_pc=0x{:08x}, hook_index={}) [\"{}\"]", current_pc,
|
||||
hook_index, HLE::GetHookNameByIndex(hook_index));
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
s32 CachedInterpreter::WritePC(PowerPC::PowerPCState& ppc_state, const WritePCOperands& operands)
|
||||
{
|
||||
const auto& [current_pc] = operands;
|
||||
ppc_state.pc = current_pc;
|
||||
ppc_state.npc = current_pc + 4;
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
s32 CachedInterpreter::WritePC(std::ostream& stream, const WritePCOperands& operands)
|
||||
{
|
||||
const auto& [current_pc] = operands;
|
||||
fmt::println(stream, "WritePC(current_pc=0x{:08x})", current_pc);
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
s32 CachedInterpreter::WriteBrokenBlockNPC(PowerPC::PowerPCState& ppc_state,
|
||||
const WritePCOperands& operands)
|
||||
{
|
||||
const auto& [current_pc] = operands;
|
||||
ppc_state.npc = current_pc;
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
s32 CachedInterpreter::WriteBrokenBlockNPC(std::ostream& stream, const WritePCOperands& operands)
|
||||
{
|
||||
const auto& [current_pc] = operands;
|
||||
fmt::println(stream, "WriteBrokenBlockNPC(current_pc=0x{:08x})", current_pc);
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
s32 CachedInterpreter::CheckFPU(PowerPC::PowerPCState& ppc_state, const CheckFPUOperands& operands)
|
||||
{
|
||||
const auto& [power_pc, current_pc, downcount] = operands;
|
||||
if (!ppc_state.msr.FP)
|
||||
{
|
||||
ppc_state.pc = current_pc;
|
||||
ppc_state.downcount -= downcount;
|
||||
ppc_state.Exceptions |= EXCEPTION_FPU_UNAVAILABLE;
|
||||
cached_interpreter.m_system.GetPowerPC().CheckExceptions();
|
||||
ppc_state.downcount -= data;
|
||||
return true;
|
||||
power_pc.CheckExceptions();
|
||||
return 0;
|
||||
}
|
||||
return false;
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
bool CachedInterpreter::CheckDSI(CachedInterpreter& cached_interpreter, u32 data)
|
||||
s32 CachedInterpreter::CheckFPU(std::ostream& stream, const CheckFPUOperands& operands)
|
||||
{
|
||||
auto& ppc_state = cached_interpreter.m_ppc_state;
|
||||
if (ppc_state.Exceptions & EXCEPTION_DSI)
|
||||
{
|
||||
cached_interpreter.m_system.GetPowerPC().CheckExceptions();
|
||||
ppc_state.downcount -= data;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
const auto& [power_pc, current_pc, downcount] = operands;
|
||||
fmt::println(stream, "CheckFPU(current_pc=0x{:08x}, downcount={})", current_pc, downcount);
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
bool CachedInterpreter::CheckProgramException(CachedInterpreter& cached_interpreter, u32 data)
|
||||
s32 CachedInterpreter::CheckBreakpoint(PowerPC::PowerPCState& ppc_state,
|
||||
const CheckBreakpointOperands& operands)
|
||||
{
|
||||
auto& ppc_state = cached_interpreter.m_ppc_state;
|
||||
if (ppc_state.Exceptions & EXCEPTION_PROGRAM)
|
||||
const auto& [power_pc, cpu_state, current_pc, downcount] = operands;
|
||||
ppc_state.pc = current_pc;
|
||||
if (power_pc.CheckBreakPoints(); *cpu_state != CPU::State::Running)
|
||||
{
|
||||
cached_interpreter.m_system.GetPowerPC().CheckExceptions();
|
||||
ppc_state.downcount -= data;
|
||||
return true;
|
||||
// Accessing PowerPCState through power_pc instead of ppc_state produces better assembly.
|
||||
power_pc.GetPPCState().downcount -= downcount;
|
||||
return 0;
|
||||
}
|
||||
return false;
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
bool CachedInterpreter::CheckBreakpoint(CachedInterpreter& cached_interpreter, u32 data)
|
||||
s32 CachedInterpreter::CheckBreakpoint(std::ostream& stream,
|
||||
const CheckBreakpointOperands& operands)
|
||||
{
|
||||
cached_interpreter.m_system.GetPowerPC().CheckBreakPoints();
|
||||
if (cached_interpreter.m_system.GetCPU().GetState() != CPU::State::Running)
|
||||
{
|
||||
cached_interpreter.m_ppc_state.downcount -= data;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
const auto& [power_pc, cpu_state, current_pc, downcount] = operands;
|
||||
fmt::println(stream, "CheckBreakpoint(current_pc=0x{:08x}, downcount={})", current_pc, downcount);
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
bool CachedInterpreter::CheckIdle(CachedInterpreter& cached_interpreter, u32 idle_pc)
|
||||
s32 CachedInterpreter::CheckIdle(PowerPC::PowerPCState& ppc_state,
|
||||
const CheckIdleOperands& operands)
|
||||
{
|
||||
if (cached_interpreter.m_ppc_state.npc == idle_pc)
|
||||
{
|
||||
cached_interpreter.m_system.GetCoreTiming().Idle();
|
||||
}
|
||||
return false;
|
||||
const auto& [core_timing, idle_pc] = operands;
|
||||
if (ppc_state.npc == idle_pc)
|
||||
core_timing.Idle();
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
s32 CachedInterpreter::CheckIdle(std::ostream& stream, const CheckIdleOperands& operands)
|
||||
{
|
||||
const auto& [core_timing, idle_pc] = operands;
|
||||
fmt::println(stream, "CheckIdle(idle_pc=0x{:08x})", idle_pc);
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
bool CachedInterpreter::HandleFunctionHooking(u32 address)
|
||||
|
@ -275,27 +269,56 @@ bool CachedInterpreter::HandleFunctionHooking(u32 address)
|
|||
if (!result)
|
||||
return false;
|
||||
|
||||
m_code.emplace_back(WritePC, address);
|
||||
m_code.emplace_back(Interpreter::HLEFunction, result.hook_index);
|
||||
Write(HLEFunction, {m_system, address, result.hook_index});
|
||||
|
||||
if (result.type != HLE::HookType::Replace)
|
||||
return false;
|
||||
|
||||
m_code.emplace_back(EndBlock, js.downcountAmount);
|
||||
m_code.emplace_back();
|
||||
js.downcountAmount += js.st.numCycles;
|
||||
Write(EndBlock, {js.downcountAmount, js.numLoadStoreInst, js.numFloatingPointInst});
|
||||
return true;
|
||||
}
|
||||
|
||||
void CachedInterpreter::Jit(u32 address)
|
||||
bool CachedInterpreter::SetEmitterStateToFreeCodeRegion()
|
||||
{
|
||||
if (m_code.size() >= CODE_SIZE / sizeof(Instruction) - 0x1000 ||
|
||||
SConfig::GetInstance().bJITNoBlockCache)
|
||||
const auto free = m_free_ranges.by_size_begin();
|
||||
if (free == m_free_ranges.by_size_end())
|
||||
{
|
||||
WARN_LOG_FMT(DYNA_REC, "Failed to find free memory region in code region.");
|
||||
return false;
|
||||
}
|
||||
SetCodePtr(free.from(), free.to());
|
||||
return true;
|
||||
}
|
||||
|
||||
void CachedInterpreter::FreeRanges()
|
||||
{
|
||||
for (const auto& [from, to] : m_block_cache.GetRangesToFree())
|
||||
m_free_ranges.insert(from, to);
|
||||
m_block_cache.ClearRangesToFree();
|
||||
}
|
||||
|
||||
void CachedInterpreter::ResetFreeMemoryRanges()
|
||||
{
|
||||
m_free_ranges.clear();
|
||||
m_free_ranges.insert(region, region + region_size);
|
||||
}
|
||||
|
||||
void CachedInterpreter::Jit(u32 em_address)
|
||||
{
|
||||
Jit(em_address, true);
|
||||
}
|
||||
|
||||
void CachedInterpreter::Jit(u32 em_address, bool clear_cache_and_retry_on_failure)
|
||||
{
|
||||
if (IsAlmostFull() || SConfig::GetInstance().bJITNoBlockCache)
|
||||
{
|
||||
ClearCache();
|
||||
}
|
||||
FreeRanges();
|
||||
|
||||
const u32 nextPC =
|
||||
analyzer.Analyze(m_ppc_state.pc, &code_block, &m_code_buffer, m_code_buffer.size());
|
||||
analyzer.Analyze(em_address, &code_block, &m_code_buffer, m_code_buffer.size());
|
||||
if (code_block.m_memory_exception)
|
||||
{
|
||||
// Address of instruction could not be translated
|
||||
|
@ -306,9 +329,43 @@ void CachedInterpreter::Jit(u32 address)
|
|||
return;
|
||||
}
|
||||
|
||||
JitBlock* b = m_block_cache.AllocateBlock(m_ppc_state.pc);
|
||||
if (SetEmitterStateToFreeCodeRegion())
|
||||
{
|
||||
JitBlock* b = m_block_cache.AllocateBlock(em_address);
|
||||
b->normalEntry = b->near_begin = GetWritableCodePtr();
|
||||
|
||||
js.blockStart = m_ppc_state.pc;
|
||||
if (DoJit(em_address, b, nextPC))
|
||||
{
|
||||
// Record what memory region was used so we know what to free if this block gets invalidated.
|
||||
b->near_end = GetWritableCodePtr();
|
||||
b->far_begin = b->far_end = nullptr;
|
||||
|
||||
// Mark the memory region that this code block uses in the RangeSizeSet.
|
||||
if (b->near_begin != b->near_end)
|
||||
m_free_ranges.erase(b->near_begin, b->near_end);
|
||||
|
||||
m_block_cache.FinalizeBlock(*b, jo.enableBlocklink, code_block, m_code_buffer);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (clear_cache_and_retry_on_failure)
|
||||
{
|
||||
WARN_LOG_FMT(DYNA_REC, "flushing code caches, please report if this happens a lot");
|
||||
ClearCache();
|
||||
Jit(em_address, false);
|
||||
return;
|
||||
}
|
||||
|
||||
PanicAlertFmtT("JIT failed to find code space after a cache clear. This should never happen. "
|
||||
"Please report this incident on the bug tracker. Dolphin will now exit.");
|
||||
std::exit(-1);
|
||||
}
|
||||
|
||||
bool CachedInterpreter::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
|
||||
{
|
||||
js.blockStart = em_address;
|
||||
js.firstFPInstructionFound = false;
|
||||
js.fifoBytesSinceCheck = 0;
|
||||
js.downcountAmount = 0;
|
||||
|
@ -316,85 +373,161 @@ void CachedInterpreter::Jit(u32 address)
|
|||
js.numFloatingPointInst = 0;
|
||||
js.curBlock = b;
|
||||
|
||||
b->normalEntry = b->near_begin = GetCodePtr();
|
||||
auto& interpreter = m_system.GetInterpreter();
|
||||
auto& power_pc = m_system.GetPowerPC();
|
||||
auto& cpu = m_system.GetCPU();
|
||||
auto& breakpoints = power_pc.GetBreakPoints();
|
||||
|
||||
for (u32 i = 0; i < code_block.m_num_instructions; i++)
|
||||
{
|
||||
PPCAnalyst::CodeOp& op = m_code_buffer[i];
|
||||
js.op = &op;
|
||||
|
||||
js.compilerPC = op.address;
|
||||
js.instructionsLeft = (code_block.m_num_instructions - 1) - i;
|
||||
js.downcountAmount += op.opinfo->num_cycles;
|
||||
if (op.opinfo->flags & FL_LOADSTORE)
|
||||
++js.numLoadStoreInst;
|
||||
if (op.opinfo->flags & FL_USE_FPU)
|
||||
++js.numFloatingPointInst;
|
||||
|
||||
if (HandleFunctionHooking(op.address))
|
||||
if (HandleFunctionHooking(js.compilerPC))
|
||||
break;
|
||||
|
||||
if (!op.skip)
|
||||
{
|
||||
const bool breakpoint =
|
||||
m_enable_debugging &&
|
||||
m_system.GetPowerPC().GetBreakPoints().IsAddressBreakPoint(op.address);
|
||||
const bool check_fpu = (op.opinfo->flags & FL_USE_FPU) && !js.firstFPInstructionFound;
|
||||
const bool endblock = (op.opinfo->flags & FL_ENDBLOCK) != 0;
|
||||
const bool memcheck = (op.opinfo->flags & FL_LOADSTORE) && jo.memcheck;
|
||||
const bool check_program_exception = !endblock && ShouldHandleFPExceptionForInstruction(&op);
|
||||
const bool idle_loop = op.branchIsIdleLoop;
|
||||
|
||||
if (breakpoint || check_fpu || endblock || memcheck || check_program_exception)
|
||||
m_code.emplace_back(WritePC, op.address);
|
||||
|
||||
if (breakpoint)
|
||||
m_code.emplace_back(CheckBreakpoint, js.downcountAmount);
|
||||
|
||||
if (check_fpu)
|
||||
if (IsDebuggingEnabled() && !cpu.IsStepping() &&
|
||||
breakpoints.IsAddressBreakPoint(js.compilerPC))
|
||||
{
|
||||
m_code.emplace_back(CheckFPU, js.downcountAmount);
|
||||
Write(CheckBreakpoint, {power_pc, cpu.GetStatePtr(), js.compilerPC, js.downcountAmount});
|
||||
}
|
||||
if (!js.firstFPInstructionFound && (op.opinfo->flags & FL_USE_FPU) != 0)
|
||||
{
|
||||
Write(CheckFPU, {power_pc, js.compilerPC, js.downcountAmount});
|
||||
js.firstFPInstructionFound = true;
|
||||
}
|
||||
|
||||
m_code.emplace_back(Interpreter::GetInterpreterOp(op.inst), op.inst);
|
||||
if (memcheck)
|
||||
m_code.emplace_back(CheckDSI, js.downcountAmount);
|
||||
if (check_program_exception)
|
||||
m_code.emplace_back(CheckProgramException, js.downcountAmount);
|
||||
if (idle_loop)
|
||||
m_code.emplace_back(CheckIdle, js.blockStart);
|
||||
if (endblock)
|
||||
if (op.canEndBlock)
|
||||
Write(WritePC, {js.compilerPC});
|
||||
|
||||
// Instruction may cause a DSI Exception or Program Exception.
|
||||
if ((jo.memcheck && (op.opinfo->flags & FL_LOADSTORE) != 0) ||
|
||||
(!op.canEndBlock && ShouldHandleFPExceptionForInstruction(&op)))
|
||||
{
|
||||
m_code.emplace_back(EndBlock, js.downcountAmount);
|
||||
if (js.numLoadStoreInst != 0)
|
||||
m_code.emplace_back(UpdateNumLoadStoreInstructions, js.numLoadStoreInst);
|
||||
if (js.numFloatingPointInst != 0)
|
||||
m_code.emplace_back(UpdateNumFloatingPointInstructions, js.numFloatingPointInst);
|
||||
Write(InterpretAndCheckExceptions, {interpreter, Interpreter::GetInterpreterOp(op.inst),
|
||||
js.compilerPC, op.inst, power_pc, js.downcountAmount});
|
||||
}
|
||||
else
|
||||
{
|
||||
Write(Interpret,
|
||||
{interpreter, Interpreter::GetInterpreterOp(op.inst), js.compilerPC, op.inst});
|
||||
}
|
||||
|
||||
if (op.branchIsIdleLoop)
|
||||
Write(CheckIdle, {m_system.GetCoreTiming(), js.blockStart});
|
||||
if (op.canEndBlock)
|
||||
Write(EndBlock, {js.downcountAmount, js.numLoadStoreInst, js.numFloatingPointInst});
|
||||
}
|
||||
}
|
||||
if (code_block.m_broken)
|
||||
{
|
||||
m_code.emplace_back(WriteBrokenBlockNPC, nextPC);
|
||||
m_code.emplace_back(EndBlock, js.downcountAmount);
|
||||
if (js.numLoadStoreInst != 0)
|
||||
m_code.emplace_back(UpdateNumLoadStoreInstructions, js.numLoadStoreInst);
|
||||
if (js.numFloatingPointInst != 0)
|
||||
m_code.emplace_back(UpdateNumFloatingPointInstructions, js.numFloatingPointInst);
|
||||
Write(WriteBrokenBlockNPC, {nextPC});
|
||||
Write(EndBlock, {js.downcountAmount, js.numLoadStoreInst, js.numFloatingPointInst});
|
||||
}
|
||||
m_code.emplace_back();
|
||||
|
||||
b->near_end = GetCodePtr();
|
||||
b->far_begin = nullptr;
|
||||
b->far_end = nullptr;
|
||||
if (HasWriteFailed())
|
||||
{
|
||||
WARN_LOG_FMT(DYNA_REC, "JIT ran out of space in code region during code generation.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
b->codeSize = static_cast<u32>(GetCodePtr() - b->normalEntry);
|
||||
b->originalSize = code_block.m_num_instructions;
|
||||
void CachedInterpreter::EraseSingleBlock(const JitBlock& block)
|
||||
{
|
||||
m_block_cache.EraseSingleBlock(block);
|
||||
}
|
||||
|
||||
m_block_cache.FinalizeBlock(*b, jo.enableBlocklink, code_block.m_physical_addresses);
|
||||
void CachedInterpreter::DisasmNearCode(const JitBlock& block, std::ostream& stream,
|
||||
std::size_t& instruction_count) const
|
||||
{
|
||||
using LookupKV = std::pair<AnyCallback, AnyDisassemble>;
|
||||
static const auto sorted_lookup = []() {
|
||||
auto unsorted_lookup = std::to_array<LookupKV>({
|
||||
#define LOOKUP_KV(scope, name) {AnyCallbackCast(scope::name), AnyDisassembleCast(scope::name)}
|
||||
LOOKUP_KV(CachedInterpreter, EndBlock),
|
||||
LOOKUP_KV(CachedInterpreter, Interpret),
|
||||
LOOKUP_KV(CachedInterpreter, InterpretAndCheckExceptions),
|
||||
LOOKUP_KV(CachedInterpreter, HLEFunction),
|
||||
LOOKUP_KV(CachedInterpreter, WritePC),
|
||||
LOOKUP_KV(CachedInterpreter, WriteBrokenBlockNPC),
|
||||
LOOKUP_KV(CachedInterpreter, CheckFPU),
|
||||
LOOKUP_KV(CachedInterpreter, CheckBreakpoint),
|
||||
LOOKUP_KV(CachedInterpreter, CheckIdle),
|
||||
LOOKUP_KV(CachedInterpreter, PoisonCallback),
|
||||
#undef LOOKUP_KV
|
||||
});
|
||||
std::ranges::sort(unsorted_lookup, {}, &LookupKV::first);
|
||||
ASSERT_MSG(DYNA_REC,
|
||||
std::ranges::adjacent_find(unsorted_lookup, {}, &LookupKV::first) ==
|
||||
unsorted_lookup.end(),
|
||||
"Sorted lookup should not contain duplicate keys.");
|
||||
return unsorted_lookup;
|
||||
}();
|
||||
|
||||
instruction_count = 0;
|
||||
for (const u8* normal_entry = block.normalEntry; normal_entry != block.near_end;
|
||||
++instruction_count)
|
||||
{
|
||||
const auto callback = *reinterpret_cast<const AnyCallback*>(normal_entry);
|
||||
const auto kv = std::ranges::lower_bound(sorted_lookup, callback, {}, &LookupKV::first);
|
||||
if (kv != sorted_lookup.end() && kv->first == callback)
|
||||
{
|
||||
normal_entry += kv->second(stream, normal_entry + sizeof(AnyCallback));
|
||||
continue;
|
||||
}
|
||||
stream << "UNKNOWN OR ILLEGAL CALLBACK\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CachedInterpreter::DisasmFarCode(const JitBlock& block, std::ostream& stream,
|
||||
std::size_t& instruction_count) const
|
||||
{
|
||||
instruction_count = 0;
|
||||
stream << "N/A\n";
|
||||
}
|
||||
|
||||
std::vector<JitBase::MemoryStats> CachedInterpreter::GetMemoryStats() const
|
||||
{
|
||||
return {{"free", m_free_ranges.get_stats()}};
|
||||
}
|
||||
|
||||
void CachedInterpreter::ClearCache()
|
||||
{
|
||||
m_code.clear();
|
||||
m_block_cache.Clear();
|
||||
m_block_cache.ClearRangesToFree();
|
||||
ClearCodeSpace();
|
||||
ResetFreeMemoryRanges();
|
||||
RefreshConfig();
|
||||
JitBase::ClearCache();
|
||||
}
|
||||
|
||||
void CachedInterpreter::LogGeneratedCode() const
|
||||
{
|
||||
std::ostringstream stream;
|
||||
|
||||
stream << "\nPPC Code Buffer:\n";
|
||||
for (const PPCAnalyst::CodeOp& op :
|
||||
std::span{m_code_buffer.data(), code_block.m_num_instructions})
|
||||
{
|
||||
fmt::print(stream, "0x{:08x}\t\t{}\n", op.address,
|
||||
Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address));
|
||||
}
|
||||
|
||||
std::size_t dummy;
|
||||
stream << "\nHost Code:\n";
|
||||
DisasmNearCode(*js.curBlock, stream, dummy);
|
||||
|
||||
DEBUG_LOG_FMT(DYNA_REC, "{}", stream.view());
|
||||
}
|
||||
|
|
|
@ -3,14 +3,27 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <cstddef>
|
||||
|
||||
#include <rangeset/rangesizeset.h>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/PowerPC/CachedInterpreter/InterpreterBlockCache.h"
|
||||
#include "Core/PowerPC/CachedInterpreter/CachedInterpreterBlockCache.h"
|
||||
#include "Core/PowerPC/CachedInterpreter/CachedInterpreterEmitter.h"
|
||||
#include "Core/PowerPC/JitCommon/JitBase.h"
|
||||
#include "Core/PowerPC/PPCAnalyst.h"
|
||||
|
||||
class CachedInterpreter : public JitBase
|
||||
namespace CoreTiming
|
||||
{
|
||||
class CoreTimingManager;
|
||||
}
|
||||
namespace CPU
|
||||
{
|
||||
enum class State;
|
||||
}
|
||||
class Interpreter;
|
||||
|
||||
class CachedInterpreter : public JitBase, public CachedInterpreterCodeBlock
|
||||
{
|
||||
public:
|
||||
explicit CachedInterpreter(Core::System& system);
|
||||
|
@ -30,32 +43,124 @@ public:
|
|||
void SingleStep() override;
|
||||
|
||||
void Jit(u32 address) override;
|
||||
void Jit(u32 address, bool clear_cache_and_retry_on_failure);
|
||||
bool DoJit(u32 address, JitBlock* b, u32 nextPC);
|
||||
|
||||
void EraseSingleBlock(const JitBlock& block) override;
|
||||
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;
|
||||
virtual std::vector<JitBase::MemoryStats> GetMemoryStats() const override;
|
||||
|
||||
JitBaseBlockCache* GetBlockCache() override { return &m_block_cache; }
|
||||
const char* GetName() const override { return "Cached Interpreter"; }
|
||||
const CommonAsmRoutinesBase* GetAsmRoutines() override { return nullptr; }
|
||||
|
||||
private:
|
||||
struct Instruction;
|
||||
|
||||
u8* GetCodePtr();
|
||||
void ExecuteOneBlock();
|
||||
|
||||
bool HandleFunctionHooking(u32 address);
|
||||
|
||||
static void EndBlock(CachedInterpreter& cached_interpreter, UGeckoInstruction data);
|
||||
static void UpdateNumLoadStoreInstructions(CachedInterpreter& cached_interpreter,
|
||||
UGeckoInstruction data);
|
||||
static void UpdateNumFloatingPointInstructions(CachedInterpreter& cached_interpreter,
|
||||
UGeckoInstruction data);
|
||||
static void WritePC(CachedInterpreter& cached_interpreter, UGeckoInstruction data);
|
||||
static void WriteBrokenBlockNPC(CachedInterpreter& cached_interpreter, UGeckoInstruction data);
|
||||
static bool CheckFPU(CachedInterpreter& cached_interpreter, u32 data);
|
||||
static bool CheckDSI(CachedInterpreter& cached_interpreter, u32 data);
|
||||
static bool CheckProgramException(CachedInterpreter& cached_interpreter, u32 data);
|
||||
static bool CheckBreakpoint(CachedInterpreter& cached_interpreter, u32 data);
|
||||
static bool CheckIdle(CachedInterpreter& cached_interpreter, u32 idle_pc);
|
||||
// Finds a free memory region and sets the code emitter to point at that region.
|
||||
// Returns false if no free memory region can be found.
|
||||
bool SetEmitterStateToFreeCodeRegion();
|
||||
void LogGeneratedCode() const;
|
||||
|
||||
BlockCache m_block_cache{*this};
|
||||
std::vector<Instruction> m_code;
|
||||
void FreeRanges();
|
||||
void ResetFreeMemoryRanges();
|
||||
|
||||
struct EndBlockOperands;
|
||||
struct InterpretOperands;
|
||||
struct InterpretAndCheckExceptionsOperands;
|
||||
struct HLEFunctionOperands;
|
||||
struct WritePCOperands;
|
||||
struct CheckFPUOperands;
|
||||
struct CheckBreakpointOperands;
|
||||
struct CheckIdleOperands;
|
||||
|
||||
static s32 EndBlock(PowerPC::PowerPCState& ppc_state, const EndBlockOperands& operands);
|
||||
static s32 EndBlock(std::ostream& stream, const EndBlockOperands& operands);
|
||||
static s32 Interpret(PowerPC::PowerPCState& ppc_state, const InterpretOperands& operands);
|
||||
static s32 Interpret(std::ostream& stream, const InterpretOperands& operands);
|
||||
static s32 InterpretAndCheckExceptions(PowerPC::PowerPCState& ppc_state,
|
||||
const InterpretAndCheckExceptionsOperands& operands);
|
||||
static s32 InterpretAndCheckExceptions(std::ostream& stream,
|
||||
const InterpretAndCheckExceptionsOperands& operands);
|
||||
static s32 HLEFunction(PowerPC::PowerPCState& ppc_state, const HLEFunctionOperands& operands);
|
||||
static s32 HLEFunction(std::ostream& stream, const HLEFunctionOperands& operands);
|
||||
static s32 WritePC(PowerPC::PowerPCState& ppc_state, const WritePCOperands& operands);
|
||||
static s32 WritePC(std::ostream& stream, const WritePCOperands& operands);
|
||||
static s32 WriteBrokenBlockNPC(PowerPC::PowerPCState& ppc_state, const WritePCOperands& operands);
|
||||
static s32 WriteBrokenBlockNPC(std::ostream& stream, const WritePCOperands& operands);
|
||||
static s32 CheckFPU(PowerPC::PowerPCState& ppc_state, const CheckFPUOperands& operands);
|
||||
static s32 CheckFPU(std::ostream& stream, const CheckFPUOperands& operands);
|
||||
static s32 CheckBreakpoint(PowerPC::PowerPCState& ppc_state,
|
||||
const CheckBreakpointOperands& operands);
|
||||
static s32 CheckBreakpoint(std::ostream& stream, const CheckBreakpointOperands& operands);
|
||||
static s32 CheckIdle(PowerPC::PowerPCState& ppc_state, const CheckIdleOperands& operands);
|
||||
static s32 CheckIdle(std::ostream& stream, const CheckIdleOperands& operands);
|
||||
|
||||
HyoutaUtilities::RangeSizeSet<u8*> m_free_ranges;
|
||||
CachedInterpreterBlockCache m_block_cache;
|
||||
};
|
||||
|
||||
struct CachedInterpreter::EndBlockOperands
|
||||
{
|
||||
u32 downcount;
|
||||
u32 num_load_stores;
|
||||
u32 num_fp_inst;
|
||||
u32 : 32;
|
||||
};
|
||||
|
||||
struct CachedInterpreter::InterpretOperands
|
||||
{
|
||||
Interpreter& interpreter;
|
||||
void (*func)(Interpreter&, UGeckoInstruction); // Interpreter::Instruction
|
||||
u32 current_pc;
|
||||
UGeckoInstruction inst;
|
||||
};
|
||||
|
||||
struct CachedInterpreter::InterpretAndCheckExceptionsOperands
|
||||
{
|
||||
Interpreter& interpreter;
|
||||
void (*func)(Interpreter&, UGeckoInstruction); // Interpreter::Instruction
|
||||
u32 current_pc;
|
||||
UGeckoInstruction inst;
|
||||
PowerPC::PowerPCManager& power_pc;
|
||||
u32 downcount;
|
||||
};
|
||||
|
||||
struct CachedInterpreter::HLEFunctionOperands
|
||||
{
|
||||
Core::System& system;
|
||||
u32 current_pc;
|
||||
u32 hook_index;
|
||||
};
|
||||
|
||||
struct CachedInterpreter::WritePCOperands
|
||||
{
|
||||
u32 current_pc;
|
||||
u32 : 32;
|
||||
};
|
||||
|
||||
struct CachedInterpreter::CheckFPUOperands
|
||||
{
|
||||
PowerPC::PowerPCManager& power_pc;
|
||||
u32 current_pc;
|
||||
u32 downcount;
|
||||
};
|
||||
|
||||
struct CachedInterpreter::CheckBreakpointOperands
|
||||
{
|
||||
PowerPC::PowerPCManager& power_pc;
|
||||
const CPU::State* cpu_state;
|
||||
u32 current_pc;
|
||||
u32 downcount;
|
||||
};
|
||||
|
||||
struct CachedInterpreter::CheckIdleOperands
|
||||
{
|
||||
CoreTiming::CoreTimingManager& core_timing;
|
||||
u32 idle_pc;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Core/PowerPC/CachedInterpreter/CachedInterpreterBlockCache.h"
|
||||
|
||||
#include "Core/PowerPC/CachedInterpreter/CachedInterpreterEmitter.h"
|
||||
#include "Core/PowerPC/JitCommon/JitBase.h"
|
||||
|
||||
CachedInterpreterBlockCache::CachedInterpreterBlockCache(JitBase& jit) : JitBaseBlockCache{jit}
|
||||
{
|
||||
}
|
||||
|
||||
void CachedInterpreterBlockCache::Init()
|
||||
{
|
||||
JitBaseBlockCache::Init();
|
||||
ClearRangesToFree();
|
||||
}
|
||||
|
||||
void CachedInterpreterBlockCache::DestroyBlock(JitBlock& block)
|
||||
{
|
||||
JitBaseBlockCache::DestroyBlock(block);
|
||||
|
||||
if (block.near_begin != block.near_end)
|
||||
m_ranges_to_free_on_next_codegen.emplace_back(block.near_begin, block.near_end);
|
||||
}
|
||||
|
||||
void CachedInterpreterBlockCache::ClearRangesToFree()
|
||||
{
|
||||
m_ranges_to_free_on_next_codegen.clear();
|
||||
}
|
||||
|
||||
void CachedInterpreterBlockCache::WriteLinkBlock(const JitBlock::LinkData& source,
|
||||
const JitBlock* dest)
|
||||
{
|
||||
}
|
||||
|
||||
void CachedInterpreterBlockCache::WriteDestroyBlock(const JitBlock& block)
|
||||
{
|
||||
CachedInterpreterEmitter emitter(block.normalEntry, block.near_end);
|
||||
emitter.Write(CachedInterpreterEmitter::PoisonCallback);
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/PowerPC/JitCommon/JitCache.h"
|
||||
|
||||
class JitBase;
|
||||
|
||||
class CachedInterpreterBlockCache final : public JitBaseBlockCache
|
||||
{
|
||||
public:
|
||||
explicit CachedInterpreterBlockCache(JitBase& jit);
|
||||
|
||||
void Init() override;
|
||||
|
||||
void DestroyBlock(JitBlock& block) override;
|
||||
|
||||
void ClearRangesToFree();
|
||||
|
||||
const std::vector<std::pair<u8*, u8*>>& GetRangesToFree() const
|
||||
{
|
||||
return m_ranges_to_free_on_next_codegen;
|
||||
};
|
||||
|
||||
private:
|
||||
void WriteLinkBlock(const JitBlock::LinkData& source, const JitBlock* dest) override;
|
||||
void WriteDestroyBlock(const JitBlock& block) override;
|
||||
|
||||
std::vector<std::pair<u8*, u8*>> m_ranges_to_free_on_next_codegen;
|
||||
};
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Core/PowerPC/CachedInterpreter/CachedInterpreterEmitter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
|
||||
void CachedInterpreterEmitter::Write(AnyCallback callback, const void* operands, std::size_t size)
|
||||
{
|
||||
DEBUG_ASSERT(reinterpret_cast<std::uintptr_t>(m_code) % alignof(AnyCallback) == 0);
|
||||
if (m_code + sizeof(callback) + size >= m_code_end)
|
||||
{
|
||||
m_write_failed = true;
|
||||
return;
|
||||
}
|
||||
std::memcpy(m_code, &callback, sizeof(callback));
|
||||
m_code += sizeof(callback);
|
||||
std::memcpy(m_code, operands, size);
|
||||
m_code += size;
|
||||
}
|
||||
|
||||
s32 CachedInterpreterEmitter::PoisonCallback(PowerPC::PowerPCState& ppc_state, const void* operands)
|
||||
{
|
||||
PanicAlertFmtT("The Cached Interpreter reached a poisoned callback. This should never happen!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
s32 CachedInterpreterEmitter::PoisonCallback(std::ostream& stream, const void* operands)
|
||||
{
|
||||
stream << "PoisonCallback()\n";
|
||||
return sizeof(AnyCallback);
|
||||
}
|
||||
|
||||
void CachedInterpreterCodeBlock::PoisonMemory()
|
||||
{
|
||||
DEBUG_ASSERT(reinterpret_cast<std::uintptr_t>(region) % alignof(AnyCallback) == 0);
|
||||
DEBUG_ASSERT(region_size % sizeof(AnyCallback) == 0);
|
||||
std::fill(reinterpret_cast<AnyCallback*>(region),
|
||||
reinterpret_cast<AnyCallback*>(region + region_size), AnyCallbackCast(PoisonCallback));
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <iosfwd>
|
||||
#include <type_traits>
|
||||
|
||||
#include "Common/CodeBlock.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
namespace PowerPC
|
||||
{
|
||||
struct PowerPCState;
|
||||
}
|
||||
|
||||
class CachedInterpreterEmitter
|
||||
{
|
||||
protected:
|
||||
// The const void* parameter is a type-erased reference to operands. The return value of most
|
||||
// callbacks is the distance in memory to the next callback. If a callback returns 0, the block
|
||||
// will be exited. The return value is signed to support block-linking. 32-bit return values seem
|
||||
// to perform better than 64-bit ones.
|
||||
using AnyCallback = s32 (*)(PowerPC::PowerPCState& ppc_state, const void* operands);
|
||||
// Disassemble callbacks will always return the distance to the next callback.
|
||||
using AnyDisassemble = s32 (*)(std::ostream& stream, const void* operands);
|
||||
|
||||
template <class Operands>
|
||||
static AnyCallback AnyCallbackCast(s32 (*callback)(PowerPC::PowerPCState&, const Operands&))
|
||||
{
|
||||
return reinterpret_cast<AnyCallback>(callback);
|
||||
}
|
||||
static AnyCallback AnyCallbackCast(AnyCallback callback) { return callback; }
|
||||
|
||||
template <class Operands>
|
||||
static AnyDisassemble AnyDisassembleCast(s32 (*disassemble)(std::ostream&, const Operands&))
|
||||
{
|
||||
return reinterpret_cast<AnyDisassemble>(disassemble);
|
||||
}
|
||||
static AnyDisassemble AnyDisassembleCast(AnyDisassemble disassemble) { return disassemble; }
|
||||
|
||||
private:
|
||||
void Write(AnyCallback callback, const void* operands, std::size_t size);
|
||||
|
||||
public:
|
||||
CachedInterpreterEmitter() = default;
|
||||
explicit CachedInterpreterEmitter(u8* begin, u8* end) : m_code(begin), m_code_end(end) {}
|
||||
|
||||
template <class Operands>
|
||||
void Write(s32 (*callback)(PowerPC::PowerPCState& ppc_state, const Operands&),
|
||||
const Operands& operands)
|
||||
{
|
||||
// I would use std::is_trivial_v, but almost every operands struct uses
|
||||
// references instead of pointers to make the callback functions nicer.
|
||||
static_assert(
|
||||
std::is_trivially_copyable_v<Operands> && std::is_trivially_destructible_v<Operands> &&
|
||||
alignof(Operands) <= alignof(AnyCallback) && sizeof(Operands) % alignof(AnyCallback) == 0);
|
||||
Write(AnyCallbackCast(callback), &operands, sizeof(Operands));
|
||||
}
|
||||
void Write(AnyCallback callback) { Write(callback, nullptr, 0); }
|
||||
|
||||
const u8* GetCodePtr() const { return m_code; }
|
||||
u8* GetWritableCodePtr() { return m_code; }
|
||||
const u8* GetCodeEnd() const { return m_code_end; };
|
||||
u8* GetWritableCodeEnd() { return m_code_end; };
|
||||
// Should be checked after a block of code has been generated to see if the code has been
|
||||
// successfully written to memory. Do not call the generated code when this returns true!
|
||||
bool HasWriteFailed() const { return m_write_failed; }
|
||||
|
||||
void SetCodePtr(u8* begin, u8* end)
|
||||
{
|
||||
m_code = begin;
|
||||
m_code_end = end;
|
||||
m_write_failed = false;
|
||||
};
|
||||
|
||||
static s32 PoisonCallback(PowerPC::PowerPCState& ppc_state, const void* operands);
|
||||
static s32 PoisonCallback(std::ostream& stream, const void* operands);
|
||||
|
||||
private:
|
||||
// Pointer to memory where code will be emitted to.
|
||||
u8* m_code = nullptr;
|
||||
// Pointer past the end of the memory region we're allowed to emit to.
|
||||
// Writes that would reach this memory are refused and will set the m_write_failed flag instead.
|
||||
u8* m_code_end = nullptr;
|
||||
// Set to true when a write request happens that would write past m_code_end.
|
||||
// Must be cleared with SetCodePtr() afterwards.
|
||||
bool m_write_failed = false;
|
||||
};
|
||||
|
||||
class CachedInterpreterCodeBlock : public Common::CodeBlock<CachedInterpreterEmitter, false>
|
||||
{
|
||||
private:
|
||||
void PoisonMemory() override;
|
||||
};
|
|
@ -1,14 +0,0 @@
|
|||
// Copyright 2016 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Core/PowerPC/CachedInterpreter/InterpreterBlockCache.h"
|
||||
|
||||
#include "Core/PowerPC/JitCommon/JitBase.h"
|
||||
|
||||
BlockCache::BlockCache(JitBase& jit) : JitBaseBlockCache{jit}
|
||||
{
|
||||
}
|
||||
|
||||
void BlockCache::WriteLinkBlock(const JitBlock::LinkData& source, const JitBlock* dest)
|
||||
{
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
// Copyright 2016 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Core/PowerPC/JitCommon/JitCache.h"
|
||||
|
||||
class JitBase;
|
||||
|
||||
class BlockCache final : public JitBaseBlockCache
|
||||
{
|
||||
public:
|
||||
explicit BlockCache(JitBase& jit);
|
||||
|
||||
private:
|
||||
void WriteLinkBlock(const JitBlock::LinkData& source, const JitBlock* dest) override;
|
||||
};
|
|
@ -4,11 +4,12 @@
|
|||
#include "Core/PowerPC/Jit64/Jit.h"
|
||||
|
||||
#include <map>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include <disasm.h>
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
// for the PROFILER stuff
|
||||
#ifdef _WIN32
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -1192,16 +1205,32 @@ bool Jit64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
|
|||
return false;
|
||||
}
|
||||
|
||||
b->codeSize = static_cast<u32>(GetCodePtr() - b->normalEntry);
|
||||
b->originalSize = code_block.m_num_instructions;
|
||||
|
||||
#ifdef JIT_LOG_GENERATED_CODE
|
||||
LogGeneratedX86(code_block.m_num_instructions, m_code_buffer, start, b);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Jit64::EraseSingleBlock(const JitBlock& block)
|
||||
{
|
||||
blocks.EraseSingleBlock(block);
|
||||
FreeRanges();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
std::vector<JitBase::MemoryStats> Jit64::GetMemoryStats() const
|
||||
{
|
||||
return {{"near", m_free_ranges_near.get_stats()}, {"far", m_free_ranges_far.get_stats()}};
|
||||
}
|
||||
|
||||
BitSet8 Jit64::ComputeStaticGQRs(const PPCAnalyst::CodeBlock& cb) const
|
||||
{
|
||||
return cb.m_gqr_used & ~cb.m_gqr_modified;
|
||||
|
@ -1281,39 +1310,24 @@ bool Jit64::HandleFunctionHooking(u32 address)
|
|||
return true;
|
||||
}
|
||||
|
||||
void LogGeneratedX86(size_t size, const PPCAnalyst::CodeBuffer& code_buffer, const u8* normalEntry,
|
||||
const JitBlock* b)
|
||||
void Jit64::LogGeneratedCode() const
|
||||
{
|
||||
for (size_t i = 0; i < size; i++)
|
||||
std::ostringstream stream;
|
||||
|
||||
stream << "\nPPC Code Buffer:\n";
|
||||
for (const PPCAnalyst::CodeOp& op :
|
||||
std::span{m_code_buffer.data(), code_block.m_num_instructions})
|
||||
{
|
||||
const PPCAnalyst::CodeOp& op = code_buffer[i];
|
||||
const std::string disasm = Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address);
|
||||
DEBUG_LOG_FMT(DYNA_REC, "IR_X86 PPC: {:08x} {}\n", op.address, disasm);
|
||||
fmt::print(stream, "0x{:08x}\t\t{}\n", op.address,
|
||||
Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address));
|
||||
}
|
||||
|
||||
disassembler x64disasm;
|
||||
x64disasm.set_syntax_intel();
|
||||
const JitBlock* const block = js.curBlock;
|
||||
stream << "\nHost Near Code:\n";
|
||||
m_disassembler->Disassemble(block->normalEntry, block->near_end, stream);
|
||||
stream << "\nHost Far Code:\n";
|
||||
m_disassembler->Disassemble(block->far_begin, block->far_end, stream);
|
||||
|
||||
u64 disasmPtr = reinterpret_cast<u64>(normalEntry);
|
||||
const u8* end = normalEntry + b->codeSize;
|
||||
|
||||
while (reinterpret_cast<u8*>(disasmPtr) < end)
|
||||
{
|
||||
char sptr[1000] = "";
|
||||
disasmPtr += x64disasm.disasm64(disasmPtr, disasmPtr, reinterpret_cast<u8*>(disasmPtr), sptr);
|
||||
DEBUG_LOG_FMT(DYNA_REC, "IR_X86 x86: {}", sptr);
|
||||
}
|
||||
|
||||
if (b->codeSize <= 250)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << std::hex;
|
||||
for (u8 i = 0; i <= b->codeSize; i++)
|
||||
{
|
||||
ss.width(2);
|
||||
ss.fill('0');
|
||||
ss << static_cast<u32>(*(normalEntry + i));
|
||||
}
|
||||
DEBUG_LOG_FMT(DYNA_REC, "IR_X86 bin: {}\n\n\n", ss.str());
|
||||
}
|
||||
// TODO C++20: std::ostringstream::view()
|
||||
DEBUG_LOG_FMT(DYNA_REC, "{}", std::move(stream).str());
|
||||
}
|
||||
|
|
|
@ -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,13 @@ public:
|
|||
void Jit(u32 em_address, bool clear_cache_and_retry_on_failure);
|
||||
bool DoJit(u32 em_address, JitBlock* b, u32 nextPC);
|
||||
|
||||
void EraseSingleBlock(const JitBlock& block) override;
|
||||
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::vector<MemoryStats> GetMemoryStats() 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 +274,11 @@ private:
|
|||
|
||||
bool HandleFunctionHooking(u32 address);
|
||||
|
||||
void FreeRanges();
|
||||
void ResetFreeMemoryRanges();
|
||||
|
||||
void LogGeneratedCode() const;
|
||||
|
||||
static void ImHere(Jit64& jit);
|
||||
|
||||
JitBlockCache blocks{*this};
|
||||
|
@ -284,7 +295,5 @@ private:
|
|||
const bool m_im_here_debug = false;
|
||||
const bool m_im_here_log = false;
|
||||
std::map<u32, int> m_been_here;
|
||||
std::unique_ptr<HostDisassembler> m_disassembler;
|
||||
};
|
||||
|
||||
void LogGeneratedX86(size_t size, const PPCAnalyst::CodeBuffer& code_buffer, const u8* normalEntry,
|
||||
const JitBlock* b);
|
||||
|
|
|
@ -5,9 +5,16 @@
|
|||
|
||||
#include <cstdio>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
#include "Common/Arm64Emitter.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/GekkoDisassembler.h"
|
||||
#include "Common/HostDisassembler.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
|
@ -38,7 +45,9 @@ constexpr size_t NEAR_CODE_SIZE = 1024 * 1024 * 64;
|
|||
constexpr size_t FAR_CODE_SIZE = 1024 * 1024 * 64;
|
||||
constexpr size_t TOTAL_CODE_SIZE = NEAR_CODE_SIZE * 2 + FAR_CODE_SIZE * 2;
|
||||
|
||||
JitArm64::JitArm64(Core::System& system) : JitBase(system), m_float_emit(this)
|
||||
JitArm64::JitArm64(Core::System& system)
|
||||
: JitBase(system), m_float_emit(this),
|
||||
m_disassembler(HostDisassembler::Factory(HostDisassembler::Platform::aarch64))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -185,6 +194,37 @@ void JitArm64::GenerateAsmAndResetFreeMemoryRanges()
|
|||
|
||||
ResetFreeMemoryRanges(routines_near_end - routines_near_start,
|
||||
routines_far_end - routines_far_start);
|
||||
|
||||
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);
|
||||
|
||||
if (range.first < m_near_code_0.GetCodeEnd())
|
||||
m_free_ranges_near_0.insert(range.first, range.second);
|
||||
else
|
||||
m_free_ranges_near_1.insert(range.first, range.second);
|
||||
}
|
||||
for (auto range : blocks.GetRangesToFreeFar())
|
||||
{
|
||||
if (range.first < m_far_code_0.GetCodeEnd())
|
||||
m_free_ranges_far_0.insert(range.first, range.second);
|
||||
else
|
||||
m_free_ranges_far_1.insert(range.first, range.second);
|
||||
}
|
||||
blocks.ClearRangesToFree();
|
||||
blocks.ClearRangesToFree();
|
||||
}
|
||||
|
||||
void JitArm64::ResetFreeMemoryRanges(size_t routines_near_size, size_t routines_far_size)
|
||||
|
@ -910,31 +950,7 @@ void JitArm64::Jit(u32 em_address, bool clear_cache_and_retry_on_failure)
|
|||
|
||||
if (SConfig::GetInstance().bJITNoBlockCache)
|
||||
ClearCache();
|
||||
|
||||
// Check if any code blocks have been freed in the block cache and transfer this information to
|
||||
// the local rangesets to allow overwriting them with new code.
|
||||
for (auto range : blocks.GetRangesToFreeNear())
|
||||
{
|
||||
auto first_fastmem_area = m_fault_to_handler.upper_bound(range.first);
|
||||
auto last_fastmem_area = first_fastmem_area;
|
||||
auto end = m_fault_to_handler.end();
|
||||
while (last_fastmem_area != end && last_fastmem_area->first <= range.second)
|
||||
++last_fastmem_area;
|
||||
m_fault_to_handler.erase(first_fastmem_area, last_fastmem_area);
|
||||
|
||||
if (range.first < m_near_code_0.GetCodeEnd())
|
||||
m_free_ranges_near_0.insert(range.first, range.second);
|
||||
else
|
||||
m_free_ranges_near_1.insert(range.first, range.second);
|
||||
}
|
||||
for (auto range : blocks.GetRangesToFreeFar())
|
||||
{
|
||||
if (range.first < m_far_code_0.GetCodeEnd())
|
||||
m_free_ranges_far_0.insert(range.first, range.second);
|
||||
else
|
||||
m_free_ranges_far_1.insert(range.first, range.second);
|
||||
}
|
||||
blocks.ClearRangesToFree();
|
||||
FreeRanges();
|
||||
|
||||
const Common::ScopedJITPageWriteAndNoExecute enable_jit_page_writes;
|
||||
|
||||
|
@ -1009,7 +1025,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;
|
||||
}
|
||||
}
|
||||
|
@ -1029,6 +1049,32 @@ 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);
|
||||
}
|
||||
|
||||
std::vector<JitBase::MemoryStats> JitArm64::GetMemoryStats() const
|
||||
{
|
||||
return {{"near_0", m_free_ranges_near_0.get_stats()},
|
||||
{"near_1", m_free_ranges_near_1.get_stats()},
|
||||
{"far_0", m_free_ranges_far_0.get_stats()},
|
||||
{"far_1", m_free_ranges_far_1.get_stats()}};
|
||||
}
|
||||
|
||||
std::optional<size_t> JitArm64::SetEmitterStateToFreeCodeRegion()
|
||||
{
|
||||
// Find some large free memory blocks and set code emitters to point at them. If we can't find
|
||||
|
@ -1351,11 +1397,30 @@ bool JitArm64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
|
|||
return false;
|
||||
}
|
||||
|
||||
b->codeSize = static_cast<u32>(GetCodePtr() - b->normalEntry);
|
||||
b->originalSize = code_block.m_num_instructions;
|
||||
|
||||
FlushIcache();
|
||||
m_far_code.FlushIcache();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void JitArm64::LogGeneratedCode() const
|
||||
{
|
||||
std::ostringstream stream;
|
||||
|
||||
stream << "\nPPC Code Buffer:\n";
|
||||
for (const PPCAnalyst::CodeOp& op :
|
||||
std::span{m_code_buffer.data(), code_block.m_num_instructions})
|
||||
{
|
||||
fmt::print(stream, "0x{:08x}\t\t{}\n", op.address,
|
||||
Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address));
|
||||
}
|
||||
|
||||
const JitBlock* const block = js.curBlock;
|
||||
stream << "\nHost Near Code:\n";
|
||||
m_disassembler->Disassemble(block->normalEntry, block->near_end, stream);
|
||||
stream << "\nHost Far Code:\n";
|
||||
m_disassembler->Disassemble(block->far_begin, block->far_end, stream);
|
||||
|
||||
// TODO C++20: std::ostringstream::view()
|
||||
DEBUG_LOG_FMT(DYNA_REC, "{}", std::move(stream).str());
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
#include "Core/PowerPC/JitCommon/JitBase.h"
|
||||
#include "Core/PowerPC/PPCAnalyst.h"
|
||||
|
||||
class HostDisassembler;
|
||||
|
||||
class JitArm64 : public JitBase, public Arm64Gen::ARM64CodeBlock, public CommonAsmRoutinesBase
|
||||
{
|
||||
public:
|
||||
|
@ -48,6 +50,13 @@ public:
|
|||
void Jit(u32 em_address) override;
|
||||
void Jit(u32 em_address, bool clear_cache_and_retry_on_failure);
|
||||
|
||||
void EraseSingleBlock(const JitBlock& block) override;
|
||||
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::vector<MemoryStats> GetMemoryStats() const override;
|
||||
|
||||
const char* GetName() const override { return "JITARM64"; }
|
||||
|
||||
// OPCODES
|
||||
|
@ -294,11 +303,14 @@ protected:
|
|||
void Cleanup();
|
||||
void ResetStack();
|
||||
|
||||
void FreeRanges();
|
||||
void GenerateAsmAndResetFreeMemoryRanges();
|
||||
void ResetFreeMemoryRanges(size_t routines_near_size, size_t routines_far_size);
|
||||
|
||||
void IntializeSpeculativeConstants();
|
||||
|
||||
void LogGeneratedCode() const;
|
||||
|
||||
// AsmRoutines
|
||||
void GenerateAsm();
|
||||
void GenerateCommonAsm();
|
||||
|
@ -409,4 +421,6 @@ protected:
|
|||
HyoutaUtilities::RangeSizeSet<u8*> m_free_ranges_near_1;
|
||||
HyoutaUtilities::RangeSizeSet<u8*> m_free_ranges_far_0;
|
||||
HyoutaUtilities::RangeSizeSet<u8*> m_free_ranges_far_1;
|
||||
|
||||
std::unique_ptr<HostDisassembler> m_disassembler;
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -5,9 +5,12 @@
|
|||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <iosfwd>
|
||||
#include <map>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/BitSet.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
|
@ -186,6 +189,8 @@ public:
|
|||
JitBase& operator=(JitBase&&) = delete;
|
||||
~JitBase() override;
|
||||
|
||||
void ClearCache() override;
|
||||
|
||||
bool IsProfilingEnabled() const { return m_enable_profiling; }
|
||||
bool IsDebuggingEnabled() const { return m_enable_debugging; }
|
||||
|
||||
|
@ -194,6 +199,15 @@ public:
|
|||
|
||||
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;
|
||||
|
||||
using MemoryStats = std::pair<std::string_view, std::pair<std::size_t, double>>;
|
||||
virtual std::vector<MemoryStats> GetMemoryStats() const = 0;
|
||||
|
||||
virtual const CommonAsmRoutinesBase* GetAsmRoutines() = 0;
|
||||
|
||||
virtual bool HandleFault(uintptr_t access_address, SContext* ctx) = 0;
|
||||
|
|
|
@ -9,12 +9,14 @@
|
|||
#include <functional>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/JitRegister.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/Host.h"
|
||||
#include "Core/PowerPC/JitCommon/JitBase.h"
|
||||
#include "Core/PowerPC/MMU.h"
|
||||
#include "Core/PowerPC/PPCSymbolDB.h"
|
||||
|
@ -117,6 +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<u32>& 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 PPCAnalyst::CodeOp& op : std::span{code_buffer.data(), 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();
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "Common/CommonTypes.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/PowerPC/Gekko.h"
|
||||
#include "Core/PowerPC/PPCAnalyst.h"
|
||||
|
||||
class JitBase;
|
||||
|
||||
|
@ -44,9 +45,6 @@ struct JitBlockData
|
|||
// and valid_block in particular). This is useful because of
|
||||
// of the way the instruction cache works on PowerPC.
|
||||
u32 physicalAddress;
|
||||
// The number of bytes of JIT'ed code contained in this block. Mostly
|
||||
// useful for logging.
|
||||
u32 codeSize;
|
||||
// The number of PPC instructions represented by this block. Mostly
|
||||
// useful for logging.
|
||||
u32 originalSize;
|
||||
|
@ -105,6 +103,10 @@ struct JitBlock : public JitBlockData
|
|||
// This set stores all physical addresses of all occupied instructions.
|
||||
std::set<u32> physical_addresses;
|
||||
|
||||
// This is only available when debugging is enabled. It is a trimmed-down copy of the
|
||||
// PPCAnalyst::CodeBuffer used to recompile this block, including repeat instructions.
|
||||
std::vector<std::pair<u32, UGeckoInstruction>> original_buffer;
|
||||
|
||||
std::unique_ptr<ProfileData> profile_data;
|
||||
};
|
||||
|
||||
|
@ -161,9 +163,12 @@ public:
|
|||
u8** GetEntryPoints();
|
||||
JitBlock** GetFastBlockMapFallback();
|
||||
void RunOnBlocks(const Core::CPUThreadGuard& guard, std::function<void(const JitBlock&)> f) const;
|
||||
void WipeBlockProfilingData(const Core::CPUThreadGuard& guard);
|
||||
std::size_t GetBlockCount() const { return block_map.size(); }
|
||||
|
||||
JitBlock* AllocateBlock(u32 em_address);
|
||||
void FinalizeBlock(JitBlock& block, bool block_link, const std::set<u32>& physical_addresses);
|
||||
void FinalizeBlock(JitBlock& block, bool block_link, PPCAnalyst::CodeBlock& code_block,
|
||||
const PPCAnalyst::CodeBuffer& code_buffer);
|
||||
|
||||
// Look for the block in the slow but accurate way.
|
||||
// This function shall be used if FastLookupIndexForAddress() failed.
|
||||
|
@ -179,6 +184,7 @@ public:
|
|||
void InvalidateICache(u32 address, u32 length, bool forced);
|
||||
void InvalidateICacheLine(u32 address);
|
||||
void ErasePhysicalRange(u32 address, u32 length);
|
||||
void EraseSingleBlock(const JitBlock& block);
|
||||
|
||||
u32* GetBlockBitSet() const;
|
||||
|
||||
|
|
|
@ -106,6 +106,26 @@ void JitInterface::UpdateMembase()
|
|||
}
|
||||
}
|
||||
|
||||
void JitInterface::RunOnBlocks(const Core::CPUThreadGuard& guard,
|
||||
std::function<void(const JitBlock&)> f) const
|
||||
{
|
||||
if (m_jit)
|
||||
m_jit->GetBlockCache()->RunOnBlocks(guard, std::move(f));
|
||||
}
|
||||
|
||||
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<std::string_view, (FEATURE_FLAG_END_OF_ENUMERATION - 1) << 1>
|
||||
|
@ -182,48 +202,6 @@ void JitInterface::JitBlockLogDump(const Core::CPUThreadGuard& guard, std::FILE*
|
|||
}
|
||||
}
|
||||
|
||||
std::variant<JitInterface::GetHostCodeError, JitInterface::GetHostCodeResult>
|
||||
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,33 @@ 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::vector<JitBase::MemoryStats> JitInterface::GetMemoryStats() const
|
||||
{
|
||||
if (m_jit)
|
||||
return m_jit->GetMemoryStats();
|
||||
return {};
|
||||
}
|
||||
|
||||
void JitInterface::InvalidateICache(u32 address, u32 size, bool forced)
|
||||
{
|
||||
if (m_jit)
|
||||
|
|
|
@ -6,9 +6,11 @@
|
|||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
#include <iosfwd>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/MachineContext.h"
|
||||
|
@ -16,6 +18,7 @@
|
|||
class CPUCoreBase;
|
||||
class PointerWrap;
|
||||
class JitBase;
|
||||
struct JitBlock;
|
||||
|
||||
namespace Core
|
||||
{
|
||||
|
@ -57,7 +60,9 @@ public:
|
|||
|
||||
void UpdateMembase();
|
||||
void JitBlockLogDump(const Core::CPUThreadGuard& guard, std::FILE* file) const;
|
||||
std::variant<GetHostCodeError, GetHostCodeResult> GetHostCode(u32 address) const;
|
||||
void RunOnBlocks(const Core::CPUThreadGuard& guard, std::function<void(const JitBlock&)> f) const;
|
||||
void WipeBlockProfilingData(const Core::CPUThreadGuard& guard);
|
||||
std::size_t GetBlockCount() const;
|
||||
|
||||
// Memory Utilities
|
||||
bool HandleFault(uintptr_t access_address, SContext* ctx);
|
||||
|
@ -71,6 +76,18 @@ public:
|
|||
// 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;
|
||||
using MemoryStats = std::pair<std::string_view, std::pair<std::size_t, double>>;
|
||||
std::vector<MemoryStats> GetMemoryStats() 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);
|
||||
|
|
|
@ -117,6 +117,7 @@
|
|||
<ClInclude Include="Common\GL\GLX11Window.h" />
|
||||
<ClInclude Include="Common\Hash.h" />
|
||||
<ClInclude Include="Common\HookableEvent.h" />
|
||||
<ClInclude Include="Common\HostDisassembler.h" />
|
||||
<ClInclude Include="Common\HRWrap.h" />
|
||||
<ClInclude Include="Common\HttpRequest.h" />
|
||||
<ClInclude Include="Common\Image.h" />
|
||||
|
@ -165,6 +166,7 @@
|
|||
<ClInclude Include="Common\TraversalClient.h" />
|
||||
<ClInclude Include="Common\TraversalProto.h" />
|
||||
<ClInclude Include="Common\TypeUtils.h" />
|
||||
<ClInclude Include="Common\Unreachable.h" />
|
||||
<ClInclude Include="Common\UPnP.h" />
|
||||
<ClInclude Include="Common\VariantUtil.h" />
|
||||
<ClInclude Include="Common\Version.h" />
|
||||
|
@ -427,7 +429,8 @@
|
|||
<ClInclude Include="Core\PatchEngine.h" />
|
||||
<ClInclude Include="Core\PowerPC\BreakPoints.h" />
|
||||
<ClInclude Include="Core\PowerPC\CachedInterpreter\CachedInterpreter.h" />
|
||||
<ClInclude Include="Core\PowerPC\CachedInterpreter\InterpreterBlockCache.h" />
|
||||
<ClInclude Include="Core\PowerPC\CachedInterpreter\CachedInterpreterBlockCache.h" />
|
||||
<ClInclude Include="Core\PowerPC\CachedInterpreter\CachedInterpreterEmitter.h" />
|
||||
<ClInclude Include="Core\PowerPC\ConditionRegister.h" />
|
||||
<ClInclude Include="Core\PowerPC\CPUCoreBase.h" />
|
||||
<ClInclude Include="Core\PowerPC\Expression.h" />
|
||||
|
@ -547,7 +550,6 @@
|
|||
<ClInclude Include="InputCommon\KeyboardStatus.h" />
|
||||
<ClInclude Include="UICommon\AutoUpdate.h" />
|
||||
<ClInclude Include="UICommon\CommandLineParse.h" />
|
||||
<ClInclude Include="UICommon\Disassembler.h" />
|
||||
<ClInclude Include="UICommon\DiscordPresence.h" />
|
||||
<ClInclude Include="UICommon\GameFile.h" />
|
||||
<ClInclude Include="UICommon\GameFileCache.h" />
|
||||
|
@ -807,6 +809,7 @@
|
|||
<ClCompile Include="Common\GL\GLInterface\WGL.cpp" />
|
||||
<ClCompile Include="Common\GL\GLUtil.cpp" />
|
||||
<ClCompile Include="Common\Hash.cpp" />
|
||||
<ClCompile Include="Common\HostDisassembler.cpp" />
|
||||
<ClCompile Include="Common\HRWrap.cpp" />
|
||||
<ClCompile Include="Common\HttpRequest.cpp" />
|
||||
<ClCompile Include="Common\Image.cpp" />
|
||||
|
@ -1087,7 +1090,8 @@
|
|||
<ClCompile Include="Core\PatchEngine.cpp" />
|
||||
<ClCompile Include="Core\PowerPC\BreakPoints.cpp" />
|
||||
<ClCompile Include="Core\PowerPC\CachedInterpreter\CachedInterpreter.cpp" />
|
||||
<ClCompile Include="Core\PowerPC\CachedInterpreter\InterpreterBlockCache.cpp" />
|
||||
<ClCompile Include="Core\PowerPC\CachedInterpreter\CachedInterpreterBlockCache.cpp" />
|
||||
<ClCompile Include="Core\PowerPC\CachedInterpreter\CachedInterpreterEmitter.cpp" />
|
||||
<ClCompile Include="Core\PowerPC\ConditionRegister.cpp" />
|
||||
<ClCompile Include="Core\PowerPC\Expression.cpp" />
|
||||
<ClCompile Include="Core\PowerPC\GDBStub.cpp" />
|
||||
|
@ -1202,7 +1206,6 @@
|
|||
<ClCompile Include="InputCommon\InputProfile.cpp" />
|
||||
<ClCompile Include="UICommon\AutoUpdate.cpp" />
|
||||
<ClCompile Include="UICommon\CommandLineParse.cpp" />
|
||||
<ClCompile Include="UICommon\Disassembler.cpp" />
|
||||
<ClCompile Include="UICommon\DiscordPresence.cpp" />
|
||||
<ClCompile Include="UICommon\GameFile.cpp" />
|
||||
<ClCompile Include="UICommon\GameFileCache.cpp" />
|
||||
|
|
|
@ -86,6 +86,14 @@ void Host_UpdateDisasmDialog()
|
|||
{
|
||||
}
|
||||
|
||||
void Host_JitCacheCleared()
|
||||
{
|
||||
}
|
||||
|
||||
void Host_JitProfileDataWiped()
|
||||
{
|
||||
}
|
||||
|
||||
void Host_UpdateMainFrame()
|
||||
{
|
||||
s_update_main_frame_event.Set();
|
||||
|
|
|
@ -222,6 +222,8 @@ add_executable(dolphin-emu
|
|||
Debugger/CodeWidget.h
|
||||
Debugger/GekkoSyntaxHighlight.cpp
|
||||
Debugger/GekkoSyntaxHighlight.h
|
||||
Debugger/JitBlockTableModel.cpp
|
||||
Debugger/JitBlockTableModel.h
|
||||
Debugger/JITWidget.cpp
|
||||
Debugger/JITWidget.h
|
||||
Debugger/MemoryViewWidget.cpp
|
||||
|
@ -297,6 +299,7 @@ add_executable(dolphin-emu
|
|||
QtUtils/BlockUserInputFilter.h
|
||||
QtUtils/ClearLayoutRecursively.cpp
|
||||
QtUtils/ClearLayoutRecursively.h
|
||||
QtUtils/ClickableStatusBar.h
|
||||
QtUtils/DolphinFileDialog.cpp
|
||||
QtUtils/DolphinFileDialog.h
|
||||
QtUtils/DoubleClickEventFilter.cpp
|
||||
|
|
|
@ -878,7 +878,7 @@ void CodeViewWidget::OnPPCComparison()
|
|||
{
|
||||
const u32 addr = GetContextAddress();
|
||||
|
||||
emit RequestPPCComparison(addr);
|
||||
emit RequestPPCComparison(addr, m_system.GetPPCState().msr.IR);
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnAddFunction()
|
||||
|
|
|
@ -55,7 +55,7 @@ public:
|
|||
u32 AddressForRow(int row) const;
|
||||
|
||||
signals:
|
||||
void RequestPPCComparison(u32 addr);
|
||||
void RequestPPCComparison(u32 address, bool effective);
|
||||
void ShowMemory(u32 address);
|
||||
void BreakpointsChanged();
|
||||
void UpdateCodeWidget();
|
||||
|
|
|
@ -222,6 +222,11 @@ void CodeWidget::OnPPCSymbolsChanged()
|
|||
}
|
||||
}
|
||||
|
||||
void CodeWidget::OnSetCodeAddress(u32 address)
|
||||
{
|
||||
SetAddress(address, CodeViewWidget::SetAddressUpdate::WithDetailedUpdate);
|
||||
}
|
||||
|
||||
void CodeWidget::OnSearchAddress()
|
||||
{
|
||||
bool good = true;
|
||||
|
|
|
@ -51,7 +51,7 @@ public:
|
|||
void UpdateSymbols();
|
||||
signals:
|
||||
void BreakpointsChanged();
|
||||
void RequestPPCComparison(u32 addr);
|
||||
void RequestPPCComparison(u32 address, bool effective);
|
||||
void ShowMemory(u32 address);
|
||||
|
||||
private:
|
||||
|
@ -61,6 +61,10 @@ private:
|
|||
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();
|
||||
|
|
|
@ -3,216 +3,648 @@
|
|||
|
||||
#include "DolphinQt/Debugger/JITWidget.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <utility>
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QHeaderView>
|
||||
#include <QLineEdit>
|
||||
#include <QMenu>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QPushButton>
|
||||
#include <QSignalBlocker>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QSplitter>
|
||||
#include <QTableWidget>
|
||||
#include <QTextBrowser>
|
||||
#include <QTableView>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
#include "Common/CommonFuncs.h"
|
||||
#include "Common/GekkoDisassembler.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/PowerPC/PPCAnalyst.h"
|
||||
#include "Core/PowerPC/JitCommon/JitCache.h"
|
||||
#include "Core/PowerPC/JitInterface.h"
|
||||
#include "Core/PowerPC/MMU.h"
|
||||
#include "Core/PowerPC/PPCSymbolDB.h"
|
||||
#include "Core/System.h"
|
||||
#include "UICommon/Disassembler.h"
|
||||
|
||||
#include "DolphinQt/Debugger/JitBlockTableModel.h"
|
||||
#include "DolphinQt/Host.h"
|
||||
#include "DolphinQt/QtUtils/ClickableStatusBar.h"
|
||||
#include "DolphinQt/QtUtils/FromStdString.h"
|
||||
#include "DolphinQt/QtUtils/ModalMessageBox.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
#include "UICommon/UICommon.h"
|
||||
|
||||
JITWidget::JITWidget(QWidget* parent) : QDockWidget(parent)
|
||||
class JitBlockProxyModel final : public QSortFilterProxyModel
|
||||
{
|
||||
setWindowTitle(tr("JIT Blocks"));
|
||||
setObjectName(QStringLiteral("jitwidget"));
|
||||
friend JITWidget;
|
||||
|
||||
setHidden(!Settings::Instance().IsJITVisible() || !Settings::Instance().IsDebugModeEnabled());
|
||||
public:
|
||||
const JitBlock& GetJitBlock(const QModelIndex& index);
|
||||
|
||||
setAllowedAreas(Qt::AllDockWidgetAreas);
|
||||
public: // Qt slots
|
||||
void OnSymbolTextChanged(const QString& text);
|
||||
template <std::optional<u32> 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<u32> m_em_address_min, m_em_address_max, m_pm_address_covered;
|
||||
QString m_symbol_name = {};
|
||||
};
|
||||
|
||||
connect(&Settings::Instance(), &Settings::JITVisibilityChanged, this,
|
||||
[this](bool visible) { setHidden(!visible); });
|
||||
|
||||
connect(&Settings::Instance(), &Settings::DebugModeToggled, this,
|
||||
[this](bool enabled) { setHidden(!enabled || !Settings::Instance().IsJITVisible()); });
|
||||
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, &JITWidget::Update);
|
||||
connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this, &JITWidget::Update);
|
||||
|
||||
ConnectWidgets();
|
||||
|
||||
#if defined(_M_X86_64)
|
||||
m_disassembler = GetNewDisassembler("x86");
|
||||
#elif defined(_M_ARM_64)
|
||||
m_disassembler = GetNewDisassembler("aarch64");
|
||||
#else
|
||||
m_disassembler = GetNewDisassembler("UNK");
|
||||
#endif
|
||||
const JitBlock& JitBlockProxyModel::GetJitBlock(const QModelIndex& index)
|
||||
{
|
||||
return sourceModel()->GetJitBlock(mapToSource(index));
|
||||
}
|
||||
|
||||
JITWidget::~JITWidget()
|
||||
void JitBlockProxyModel::OnSymbolTextChanged(const QString& text)
|
||||
{
|
||||
m_symbol_name = text;
|
||||
invalidateRowsFilter();
|
||||
}
|
||||
|
||||
template <std::optional<u32> JitBlockProxyModel::*member>
|
||||
void JitBlockProxyModel::OnAddressTextChanged(const QString& text)
|
||||
{
|
||||
bool ok = false;
|
||||
if (const u32 value = text.toUInt(&ok, 16); ok)
|
||||
this->*member = value;
|
||||
else
|
||||
this->*member = std::nullopt;
|
||||
invalidateRowsFilter();
|
||||
}
|
||||
|
||||
JitBlockProxyModel::JitBlockProxyModel(QObject* parent) : QSortFilterProxyModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
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<const QString*>(symbol_name_v.data())
|
||||
->contains(m_symbol_name, Qt::CaseInsensitive))
|
||||
return false;
|
||||
}
|
||||
const JitBlock& block = sourceModel()->GetJitBlockRefs()[source_row];
|
||||
if (m_em_address_min.has_value())
|
||||
{
|
||||
if (block.effectiveAddress < m_em_address_min.value())
|
||||
return false;
|
||||
}
|
||||
if (m_em_address_max.has_value())
|
||||
{
|
||||
if (block.effectiveAddress > m_em_address_max.value())
|
||||
return false;
|
||||
}
|
||||
if (m_pm_address_covered.has_value())
|
||||
{
|
||||
if (!block.physical_addresses.contains(m_pm_address_covered.value()))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Virtual setSourceModel is forbidden for type-safety reasons.
|
||||
void JitBlockProxyModel::setSourceModel(QAbstractItemModel* source_model)
|
||||
{
|
||||
Crash();
|
||||
}
|
||||
|
||||
void JitBlockProxyModel::setSourceModel(JitBlockTableModel* source_model)
|
||||
{
|
||||
QSortFilterProxyModel::setSourceModel(source_model);
|
||||
}
|
||||
|
||||
JitBlockTableModel* JitBlockProxyModel::sourceModel() const
|
||||
{
|
||||
return static_cast<JitBlockTableModel*>(QSortFilterProxyModel::sourceModel());
|
||||
}
|
||||
|
||||
void JITWidget::UpdateProfilingButton()
|
||||
{
|
||||
const QSignalBlocker blocker(m_toggle_profiling_button);
|
||||
const bool enabled = Config::Get(Config::MAIN_DEBUG_JIT_ENABLE_PROFILING);
|
||||
m_toggle_profiling_button->setText(enabled ? tr("Stop Profiling") : tr("Start Profiling"));
|
||||
m_toggle_profiling_button->setChecked(enabled);
|
||||
}
|
||||
|
||||
void JITWidget::UpdateOtherButtons(Core::State state)
|
||||
{
|
||||
const bool jit_exists = m_system.GetJitInterface().GetCore() != nullptr;
|
||||
m_clear_cache_button->setEnabled(jit_exists);
|
||||
m_wipe_profiling_button->setEnabled(jit_exists);
|
||||
}
|
||||
|
||||
void JITWidget::UpdateDebugFont(const QFont& font)
|
||||
{
|
||||
m_table_view->setFont(font);
|
||||
m_ppc_asm_widget->setFont(font);
|
||||
m_host_near_asm_widget->setFont(font);
|
||||
m_host_far_asm_widget->setFont(font);
|
||||
}
|
||||
|
||||
void JITWidget::ClearDisassembly()
|
||||
{
|
||||
m_ppc_asm_widget->clear();
|
||||
m_host_near_asm_widget->clear();
|
||||
m_host_far_asm_widget->clear();
|
||||
m_status_bar->clearMessage();
|
||||
}
|
||||
|
||||
void JITWidget::ShowFreeMemoryStatus()
|
||||
{
|
||||
const std::vector memory_stats = m_system.GetJitInterface().GetMemoryStats();
|
||||
QString message = tr("Free memory:");
|
||||
for (const auto& [name, stats] : memory_stats)
|
||||
{
|
||||
message.append(tr(" %1 %2 (%3% fragmented)")
|
||||
.arg(QString::fromStdString(UICommon::FormatSize(stats.first, 2)))
|
||||
.arg(QtUtils::FromStdString(name))
|
||||
.arg(stats.second * 100.0, 3, 'f', 2));
|
||||
}
|
||||
m_status_bar->showMessage(message);
|
||||
}
|
||||
|
||||
void JITWidget::UpdateContent(Core::State state)
|
||||
{
|
||||
ClearDisassembly();
|
||||
if (state == Core::State::Paused)
|
||||
ShowFreeMemoryStatus();
|
||||
}
|
||||
|
||||
static void DisassembleCodeBuffer(const JitBlock& block, PPCSymbolDB& ppc_symbol_db,
|
||||
std::ostream& stream)
|
||||
{
|
||||
// Instructions are 4 byte aligned, so next_address = 1 will never produce a false-negative.
|
||||
for (u32 next_address = 1; const auto& [address, inst] : block.original_buffer)
|
||||
{
|
||||
if (address != next_address)
|
||||
{
|
||||
stream << ppc_symbol_db.GetDescription(address) << '\n';
|
||||
next_address = address;
|
||||
}
|
||||
fmt::print(stream, "0x{:08x}\t{}\n", address,
|
||||
Common::GekkoDisassembler::Disassemble(inst.hex, address));
|
||||
next_address += sizeof(UGeckoInstruction);
|
||||
}
|
||||
}
|
||||
|
||||
void JITWidget::CrossDisassemble(const JitBlock& block)
|
||||
{
|
||||
// TODO C++20: std::ostringstream::view() + QtUtils::FromStdString + std::ostream::seekp(0) would
|
||||
// save a lot of wasted allocation here, but compiler support for the first thing isn't here yet.
|
||||
std::ostringstream stream;
|
||||
DisassembleCodeBuffer(block, m_system.GetPPCSymbolDB(), stream);
|
||||
m_ppc_asm_widget->setPlainText(QString::fromStdString(std::move(stream).str()));
|
||||
|
||||
auto& jit_interface = m_system.GetJitInterface();
|
||||
|
||||
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 long long 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));
|
||||
}
|
||||
|
||||
void JITWidget::CrossDisassemble(const QModelIndex& index)
|
||||
{
|
||||
if (index.isValid())
|
||||
{
|
||||
CrossDisassemble(m_table_proxy->GetJitBlock(index));
|
||||
return;
|
||||
}
|
||||
UpdateContent(Core::GetState(m_system));
|
||||
}
|
||||
|
||||
void JITWidget::CrossDisassemble()
|
||||
{
|
||||
CrossDisassemble(m_table_view->currentIndex());
|
||||
}
|
||||
|
||||
void JITWidget::TableEraseBlocks()
|
||||
{
|
||||
auto* const selection_model = m_table_view->selectionModel();
|
||||
// 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::ranges::transform(index_list, index_list.begin(), [this](const QModelIndex& index) {
|
||||
return m_table_proxy->mapToSource(index);
|
||||
});
|
||||
std::ranges::sort(index_list, std::less{}); // QModelIndex is incompatible with std::ranges::less
|
||||
for (const QModelIndex& index : std::ranges::reverse_view{index_list})
|
||||
{
|
||||
if (!index.isValid())
|
||||
continue;
|
||||
m_table_model->removeRow(index.row());
|
||||
}
|
||||
|
||||
connect(selection_model, &QItemSelectionModel::currentChanged, this,
|
||||
&JITWidget::OnTableCurrentChanged);
|
||||
selection_model->clear();
|
||||
}
|
||||
|
||||
void JITWidget::LoadQSettings()
|
||||
{
|
||||
auto& settings = Settings::GetQSettings();
|
||||
|
||||
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::SaveQSettings() const
|
||||
{
|
||||
auto& settings = Settings::GetQSettings();
|
||||
|
||||
settings.setValue(QStringLiteral("jitwidget/geometry"), saveGeometry());
|
||||
settings.setValue(QStringLiteral("jitwidget/floating"), isFloating());
|
||||
settings.setValue(QStringLiteral("jitwidget/tableheader/state"),
|
||||
m_table_view->horizontalHeader()->saveState());
|
||||
settings.setValue(QStringLiteral("jitwidget/tablesplitter"), m_table_splitter->saveState());
|
||||
settings.setValue(QStringLiteral("jitwidget/asmsplitter"), m_asm_splitter->saveState());
|
||||
settings.setValue(QStringLiteral("jitwidget/disasmsplitter"), m_disasm_splitter->saveState());
|
||||
}
|
||||
|
||||
void JITWidget::CreateWidgets()
|
||||
void JITWidget::ConnectSlots()
|
||||
{
|
||||
m_table_widget = new QTableWidget;
|
||||
|
||||
m_table_widget->setTabKeyNavigation(false);
|
||||
m_table_widget->setColumnCount(7);
|
||||
m_table_widget->setHorizontalHeaderLabels(
|
||||
{tr("Address"), tr("PPC Size"), tr("Host Size"),
|
||||
// i18n: The symbolic name of a code block
|
||||
tr("Symbol"),
|
||||
// i18n: These are the kinds of flags that a CPU uses (e.g. carry),
|
||||
// not the kinds of flags that represent e.g. countries
|
||||
tr("Flags"),
|
||||
// i18n: The number of times a code block has been executed
|
||||
tr("NumExec"),
|
||||
// i18n: Performance cost, not monetary cost
|
||||
tr("Cost")});
|
||||
|
||||
m_ppc_asm_widget = new QTextBrowser;
|
||||
m_host_asm_widget = new QTextBrowser;
|
||||
|
||||
m_table_splitter = new QSplitter(Qt::Vertical);
|
||||
m_asm_splitter = new QSplitter(Qt::Horizontal);
|
||||
|
||||
m_refresh_button = new QPushButton(tr("Refresh"));
|
||||
|
||||
m_table_splitter->addWidget(m_table_widget);
|
||||
m_table_splitter->addWidget(m_asm_splitter);
|
||||
|
||||
m_asm_splitter->addWidget(m_ppc_asm_widget);
|
||||
m_asm_splitter->addWidget(m_host_asm_widget);
|
||||
|
||||
QWidget* widget = new QWidget;
|
||||
auto* layout = new QVBoxLayout;
|
||||
layout->setContentsMargins(2, 2, 2, 2);
|
||||
widget->setLayout(layout);
|
||||
|
||||
layout->addWidget(m_table_splitter);
|
||||
layout->addWidget(m_refresh_button);
|
||||
|
||||
setWidget(widget);
|
||||
auto* const host = Host::GetInstance();
|
||||
connect(host, &Host::JitCacheCleared, this, &JITWidget::OnJitCacheCleared);
|
||||
connect(host, &Host::UpdateDisasmDialog, this, &JITWidget::OnUpdateDisasmDialog);
|
||||
connect(host, &Host::PPCSymbolsChanged, this, &JITWidget::OnPPCSymbolsUpdated);
|
||||
auto* const settings = &Settings::Instance();
|
||||
connect(settings, &Settings::ConfigChanged, this, &JITWidget::OnConfigChanged);
|
||||
connect(settings, &Settings::DebugFontChanged, this, &JITWidget::OnDebugFontChanged);
|
||||
connect(settings, &Settings::EmulationStateChanged, this, &JITWidget::OnEmulationStateChanged);
|
||||
}
|
||||
|
||||
void JITWidget::ConnectWidgets()
|
||||
void JITWidget::DisconnectSlots()
|
||||
{
|
||||
connect(m_refresh_button, &QPushButton::clicked, this, &JITWidget::Update);
|
||||
auto* const host = Host::GetInstance();
|
||||
disconnect(host, &Host::JitCacheCleared, this, &JITWidget::OnJitCacheCleared);
|
||||
disconnect(host, &Host::UpdateDisasmDialog, this, &JITWidget::OnUpdateDisasmDialog);
|
||||
disconnect(host, &Host::PPCSymbolsChanged, this, &JITWidget::OnPPCSymbolsUpdated);
|
||||
auto* const settings = &Settings::Instance();
|
||||
disconnect(settings, &Settings::ConfigChanged, this, &JITWidget::OnConfigChanged);
|
||||
disconnect(settings, &Settings::DebugFontChanged, this, &JITWidget::OnDebugFontChanged);
|
||||
disconnect(settings, &Settings::EmulationStateChanged, this, &JITWidget::OnEmulationStateChanged);
|
||||
}
|
||||
|
||||
void JITWidget::Compare(u32 address)
|
||||
void JITWidget::Hide()
|
||||
{
|
||||
m_address = address;
|
||||
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();
|
||||
}
|
||||
|
||||
QMenu* JITWidget::GetTableContextMenu()
|
||||
{
|
||||
if (m_table_context_menu == nullptr)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
QMenu* JITWidget::GetColumnVisibilityMenu()
|
||||
{
|
||||
if (m_column_visibility_menu == nullptr)
|
||||
{
|
||||
m_column_visibility_menu = new QMenu(this);
|
||||
static constexpr std::array<const char*, Column::NumberOfColumns> 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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
void JITWidget::OnRequestPPCComparison(u32 address, bool effective)
|
||||
{
|
||||
Settings::Instance().SetJITVisible(true);
|
||||
raise();
|
||||
m_host_asm_widget->setFocus();
|
||||
|
||||
Update();
|
||||
if (effective)
|
||||
{
|
||||
const std::optional<u32> pm_address = m_system.GetMMU().GetTranslatedAddress(address);
|
||||
if (!pm_address.has_value())
|
||||
{
|
||||
ModalMessageBox::warning(
|
||||
this, tr("Error"),
|
||||
tr("Effective address %1 has no physical address translation.").arg(address, 0, 16));
|
||||
return;
|
||||
}
|
||||
address = pm_address.value();
|
||||
}
|
||||
m_pm_address_covered_line_edit->setText(QString::number(address, 16));
|
||||
}
|
||||
|
||||
void JITWidget::Update()
|
||||
void JITWidget::OnVisibilityToggled(bool visible)
|
||||
{
|
||||
if (!isVisible())
|
||||
return;
|
||||
setHidden(!visible);
|
||||
}
|
||||
|
||||
if (!m_address || (Core::GetState(Core::System::GetInstance()) != Core::State::Paused))
|
||||
void JITWidget::OnDebugModeToggled(bool enabled)
|
||||
{
|
||||
setHidden(!enabled || !Settings::Instance().IsJITVisible());
|
||||
}
|
||||
|
||||
void JITWidget::OnToggleProfiling(bool enabled)
|
||||
{
|
||||
Config::SetBaseOrCurrent(Config::MAIN_DEBUG_JIT_ENABLE_PROFILING, enabled);
|
||||
}
|
||||
|
||||
void JITWidget::OnClearCache()
|
||||
{
|
||||
m_system.GetJitInterface().ClearCache(Core::CPUThreadGuard{m_system});
|
||||
}
|
||||
|
||||
void JITWidget::OnWipeProfiling()
|
||||
{
|
||||
m_system.GetJitInterface().WipeBlockProfilingData(Core::CPUThreadGuard{m_system});
|
||||
}
|
||||
|
||||
void JITWidget::OnTableCurrentChanged(const QModelIndex& current, const QModelIndex& previous)
|
||||
{
|
||||
CrossDisassemble(current);
|
||||
}
|
||||
|
||||
void JITWidget::OnTableDoubleClicked(const QModelIndex& index)
|
||||
{
|
||||
emit SetCodeAddress(m_table_proxy->GetJitBlock(index).effectiveAddress);
|
||||
}
|
||||
|
||||
void JITWidget::OnTableContextMenu(const QPoint& pos)
|
||||
{
|
||||
// There needs to be an option somewhere for a user to recover from hiding every column.
|
||||
if (m_table_view->horizontalHeader()->hiddenSectionCount() == Column::NumberOfColumns)
|
||||
{
|
||||
m_ppc_asm_widget->setHtml(QStringLiteral("<i>%1</i>").arg(tr("(ppc)")));
|
||||
m_host_asm_widget->setHtml(QStringLiteral("<i>%1</i>").arg(tr("(host)")));
|
||||
GetColumnVisibilityMenu()->exec(m_table_view->viewport()->mapToGlobal(pos));
|
||||
return;
|
||||
}
|
||||
GetTableContextMenu()->exec(m_table_view->viewport()->mapToGlobal(pos));
|
||||
}
|
||||
|
||||
// TODO: Actually do something with the table (Wx doesn't)
|
||||
void JITWidget::OnTableHeaderContextMenu(const QPoint& pos)
|
||||
{
|
||||
GetColumnVisibilityMenu()->exec(m_table_view->horizontalHeader()->mapToGlobal(pos));
|
||||
}
|
||||
|
||||
// Get host side code disassembly
|
||||
auto host_instructions_disasm = DisassembleBlock(m_disassembler.get(), m_address);
|
||||
m_address = host_instructions_disasm.entry_address;
|
||||
void JITWidget::OnTableMenuViewCode()
|
||||
{
|
||||
// TODO: CodeWidget doesn't support it yet, but eventually signal if the address 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);
|
||||
}
|
||||
|
||||
m_host_asm_widget->setHtml(
|
||||
QStringLiteral("<pre>%1</pre>").arg(QString::fromStdString(host_instructions_disasm.text)));
|
||||
void JITWidget::OnTableMenuEraseBlocks()
|
||||
{
|
||||
TableEraseBlocks();
|
||||
if (Core::GetState(m_system) == Core::State::Paused)
|
||||
ShowFreeMemoryStatus();
|
||||
}
|
||||
|
||||
// == Fill in ppc box
|
||||
u32 ppc_addr = m_address;
|
||||
PPCAnalyst::CodeBuffer code_buffer(32000);
|
||||
PPCAnalyst::BlockStats st;
|
||||
PPCAnalyst::BlockRegStats gpa;
|
||||
PPCAnalyst::BlockRegStats fpa;
|
||||
PPCAnalyst::CodeBlock code_block;
|
||||
PPCAnalyst::PPCAnalyzer analyzer;
|
||||
analyzer.SetDebuggingEnabled(Config::IsDebuggingEnabled());
|
||||
analyzer.SetBranchFollowingEnabled(Config::Get(Config::MAIN_JIT_FOLLOW_BRANCH));
|
||||
analyzer.SetFloatExceptionsEnabled(Config::Get(Config::MAIN_FLOAT_EXCEPTIONS));
|
||||
analyzer.SetDivByZeroExceptionsEnabled(Config::Get(Config::MAIN_DIVIDE_BY_ZERO_EXCEPTIONS));
|
||||
analyzer.SetOption(PPCAnalyst::PPCAnalyzer::OPTION_CONDITIONAL_CONTINUE);
|
||||
analyzer.SetOption(PPCAnalyst::PPCAnalyzer::OPTION_BRANCH_FOLLOW);
|
||||
void JITWidget::OnStatusBarPressed()
|
||||
{
|
||||
if (Core::GetState(m_system) == Core::State::Paused)
|
||||
ShowFreeMemoryStatus();
|
||||
}
|
||||
|
||||
code_block.m_stats = &st;
|
||||
code_block.m_gpa = &gpa;
|
||||
code_block.m_fpa = &fpa;
|
||||
void JITWidget::OnJitCacheCleared()
|
||||
{
|
||||
if (Core::GetState(m_system) != Core::State::Paused)
|
||||
return;
|
||||
ClearDisassembly();
|
||||
ShowFreeMemoryStatus();
|
||||
}
|
||||
|
||||
if (analyzer.Analyze(ppc_addr, &code_block, &code_buffer, code_buffer.size()) != 0xFFFFFFFF)
|
||||
{
|
||||
std::string ppc_disasm_str;
|
||||
auto ppc_disasm = std::back_inserter(ppc_disasm_str);
|
||||
for (u32 i = 0; i < code_block.m_num_instructions; i++)
|
||||
{
|
||||
const PPCAnalyst::CodeOp& op = code_buffer[i];
|
||||
const std::string opcode = Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address);
|
||||
fmt::format_to(ppc_disasm, "{:08x} {}\n", op.address, opcode);
|
||||
}
|
||||
void JITWidget::OnUpdateDisasmDialog()
|
||||
{
|
||||
if (Core::GetState(m_system) != Core::State::Paused)
|
||||
return;
|
||||
CrossDisassemble();
|
||||
}
|
||||
|
||||
// Add stats to the end of the ppc box since it's generally the shortest.
|
||||
fmt::format_to(ppc_disasm, "\n{} estimated cycles", st.numCycles);
|
||||
fmt::format_to(ppc_disasm, "\nNum instr: PPC: {} Host: {}", code_block.m_num_instructions,
|
||||
host_instructions_disasm.instruction_count);
|
||||
if (code_block.m_num_instructions != 0 && host_instructions_disasm.instruction_count != 0)
|
||||
{
|
||||
fmt::format_to(
|
||||
ppc_disasm, " (blowup: {}%)",
|
||||
100 * host_instructions_disasm.instruction_count / code_block.m_num_instructions - 100);
|
||||
}
|
||||
void JITWidget::OnPPCSymbolsUpdated()
|
||||
{
|
||||
if (Core::GetState(m_system) != Core::State::Paused)
|
||||
return;
|
||||
CrossDisassemble();
|
||||
}
|
||||
|
||||
fmt::format_to(ppc_disasm, "\nNum bytes: PPC: {} Host: {}", code_block.m_num_instructions * 4,
|
||||
host_instructions_disasm.code_size);
|
||||
if (code_block.m_num_instructions != 0 && host_instructions_disasm.code_size != 0)
|
||||
{
|
||||
fmt::format_to(
|
||||
ppc_disasm, " (blowup: {}%)",
|
||||
100 * host_instructions_disasm.code_size / (4 * code_block.m_num_instructions) - 100);
|
||||
}
|
||||
void JITWidget::OnConfigChanged()
|
||||
{
|
||||
UpdateProfilingButton();
|
||||
}
|
||||
|
||||
m_ppc_asm_widget->setHtml(
|
||||
QStringLiteral("<pre>%1</pre>").arg(QString::fromStdString(ppc_disasm_str)));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_host_asm_widget->setHtml(
|
||||
QStringLiteral("<pre>%1</pre>")
|
||||
.arg(QString::fromStdString(fmt::format("(non-code address: {:08x})", m_address))));
|
||||
m_ppc_asm_widget->setHtml(QStringLiteral("<i>---</i>"));
|
||||
}
|
||||
void JITWidget::OnDebugFontChanged(const QFont& font)
|
||||
{
|
||||
UpdateDebugFont(font);
|
||||
}
|
||||
|
||||
void JITWidget::OnEmulationStateChanged(Core::State state)
|
||||
{
|
||||
UpdateOtherButtons(state);
|
||||
UpdateContent(state);
|
||||
}
|
||||
|
||||
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 +652,14 @@ void JITWidget::closeEvent(QCloseEvent*)
|
|||
Settings::Instance().SetJITVisible(false);
|
||||
}
|
||||
|
||||
void JITWidget::showEvent(QShowEvent* event)
|
||||
void JITWidget::hideEvent(QHideEvent*)
|
||||
{
|
||||
Update();
|
||||
emit HideSignal();
|
||||
Hide();
|
||||
}
|
||||
|
||||
void JITWidget::showEvent(QShowEvent*)
|
||||
{
|
||||
emit ShowSignal();
|
||||
Show();
|
||||
}
|
||||
|
|
|
@ -4,42 +4,128 @@
|
|||
#pragma once
|
||||
|
||||
#include <QDockWidget>
|
||||
#include <memory>
|
||||
|
||||
#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 QMenu;
|
||||
class QPlainTextEdit;
|
||||
class QPushButton;
|
||||
class QShowEvent;
|
||||
class QSplitter;
|
||||
class QTextBrowser;
|
||||
class QTableWidget;
|
||||
class QPushButton;
|
||||
class HostDisassembler;
|
||||
class QTableView;
|
||||
|
||||
class JITWidget : public QDockWidget
|
||||
class JITWidget final : public QDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit JITWidget(QWidget* parent = nullptr);
|
||||
~JITWidget();
|
||||
|
||||
void Compare(u32 address);
|
||||
|
||||
private:
|
||||
void Update();
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
using Column = JitBlockTableModelColumn::EnumType;
|
||||
using UserRole = JitBlockTableModelUserRole::EnumType;
|
||||
|
||||
void closeEvent(QCloseEvent*) override;
|
||||
private:
|
||||
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);
|
||||
|
||||
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;
|
||||
QSplitter* m_table_splitter;
|
||||
QSplitter* m_asm_splitter;
|
||||
QPushButton* m_refresh_button;
|
||||
private:
|
||||
Core::System& m_system;
|
||||
|
||||
std::unique_ptr<HostDisassembler> m_disassembler;
|
||||
u32 m_address = 0;
|
||||
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_disasm_splitter;
|
||||
ClickableStatusBar* m_status_bar;
|
||||
|
||||
QMenu* m_table_context_menu = nullptr;
|
||||
QMenu* m_column_visibility_menu = nullptr;
|
||||
};
|
||||
|
|
421
Source/Core/DolphinQt/Debugger/JitBlockTableModel.cpp
Normal file
421
Source/Core/DolphinQt/Debugger/JitBlockTableModel.cpp
Normal file
|
@ -0,0 +1,421 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "DolphinQt/Debugger/JitBlockTableModel.h"
|
||||
|
||||
#include <array>
|
||||
#include <span>
|
||||
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/Unreachable.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/PowerPC/JitInterface.h"
|
||||
#include "Core/PowerPC/PPCSymbolDB.h"
|
||||
#include "DolphinQt/Host.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
const JitBlock& JitBlockTableModel::GetJitBlock(const QModelIndex& index) const
|
||||
{
|
||||
ASSERT(index.isValid());
|
||||
return m_jit_blocks[index.row()];
|
||||
}
|
||||
|
||||
void JitBlockTableModel::SumOverallCosts()
|
||||
{
|
||||
m_overall_cycles_spent = 0;
|
||||
m_overall_time_spent = {};
|
||||
for (const JitBlock& block : m_jit_blocks)
|
||||
{
|
||||
if (block.profile_data == nullptr)
|
||||
continue;
|
||||
m_overall_cycles_spent += block.profile_data->cycles_spent;
|
||||
m_overall_time_spent += block.profile_data->time_spent;
|
||||
};
|
||||
}
|
||||
|
||||
static QVariant GetSymbolNameQVariant(const Common::Symbol* symbol)
|
||||
{
|
||||
return symbol ? QString::fromStdString(symbol->name) : QVariant{};
|
||||
}
|
||||
|
||||
void JitBlockTableModel::PrefetchSymbols()
|
||||
{
|
||||
m_symbol_list.clear();
|
||||
m_symbol_list.reserve(m_jit_blocks.size());
|
||||
// If the table viewing this model will be accessing every element,
|
||||
// it would be a waste of effort to lazy-initialize the symbol list.
|
||||
if (m_sorting_by_symbols || m_filtering_by_symbols)
|
||||
{
|
||||
for (const JitBlock& block : m_jit_blocks)
|
||||
{
|
||||
m_symbol_list.emplace_back(
|
||||
GetSymbolNameQVariant(m_ppc_symbol_db.GetSymbolFromAddr(block.effectiveAddress)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const JitBlock& block : m_jit_blocks)
|
||||
{
|
||||
m_symbol_list.emplace_back([this, &block]() {
|
||||
return GetSymbolNameQVariant(m_ppc_symbol_db.GetSymbolFromAddr(block.effectiveAddress));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JitBlockTableModel::Clear()
|
||||
{
|
||||
emit layoutAboutToBeChanged();
|
||||
m_jit_blocks.clear();
|
||||
m_symbol_list.clear();
|
||||
emit layoutChanged();
|
||||
}
|
||||
|
||||
void JitBlockTableModel::Update(Core::State state)
|
||||
{
|
||||
emit layoutAboutToBeChanged();
|
||||
m_jit_blocks.clear();
|
||||
if (state == Core::State::Paused)
|
||||
{
|
||||
m_jit_blocks.reserve(m_jit_interface.GetBlockCount());
|
||||
m_jit_interface.RunOnBlocks(Core::CPUThreadGuard{m_system}, [this](const JitBlock& block) {
|
||||
m_jit_blocks.emplace_back(block);
|
||||
});
|
||||
SumOverallCosts();
|
||||
}
|
||||
PrefetchSymbols();
|
||||
emit layoutChanged();
|
||||
}
|
||||
|
||||
void JitBlockTableModel::UpdateProfileData()
|
||||
{
|
||||
const int row_count = rowCount();
|
||||
if (row_count <= 0)
|
||||
return;
|
||||
SumOverallCosts();
|
||||
static const QList<int> roles = {Qt::DisplayRole};
|
||||
const int last = row_count - 1;
|
||||
emit dataChanged(createIndex(0, Column::RunCount), createIndex(last, Column::TimePercent), roles);
|
||||
}
|
||||
|
||||
void JitBlockTableModel::UpdateSymbols()
|
||||
{
|
||||
const int row_count = rowCount();
|
||||
if (row_count <= 0)
|
||||
return;
|
||||
PrefetchSymbols();
|
||||
static const QList<int> roles = {Qt::DisplayRole};
|
||||
const int last = row_count - 1;
|
||||
emit dataChanged(createIndex(0, Column::Symbol), createIndex(last, Column::Symbol), roles);
|
||||
}
|
||||
|
||||
void JitBlockTableModel::ConnectSlots()
|
||||
{
|
||||
auto* const host = Host::GetInstance();
|
||||
connect(host, &Host::JitCacheCleared, this, &JitBlockTableModel::OnJitCacheCleared);
|
||||
connect(host, &Host::JitProfileDataWiped, this, &JitBlockTableModel::OnJitProfileDataWiped);
|
||||
connect(host, &Host::UpdateDisasmDialog, this, &JitBlockTableModel::OnUpdateDisasmDialog);
|
||||
connect(host, &Host::PPCSymbolsChanged, this, &JitBlockTableModel::OnPPCSymbolsUpdated);
|
||||
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<QString, (FEATURE_FLAG_END_OF_ENUMERATION - 1) << 1> descriptions = {
|
||||
QStringLiteral(""), QStringLiteral("DR"),
|
||||
QStringLiteral("IR"), QStringLiteral("DR|IR"),
|
||||
QStringLiteral("PERFMON"), QStringLiteral("DR|PERFMON"),
|
||||
QStringLiteral("IR|PERFMON"), QStringLiteral("DR|IR|PERFMON"),
|
||||
};
|
||||
return descriptions[flags];
|
||||
}
|
||||
|
||||
static QVariant GetValidSymbolStringVariant(const QVariant& symbol_name_v)
|
||||
{
|
||||
if (symbol_name_v.isValid())
|
||||
return symbol_name_v;
|
||||
return QStringLiteral(" --- ");
|
||||
}
|
||||
|
||||
QVariant JitBlockTableModel::DisplayRoleData(const QModelIndex& index) const
|
||||
{
|
||||
const int column = index.column();
|
||||
if (column == Column::Symbol)
|
||||
return GetValidSymbolStringVariant(*m_symbol_list[index.row()]);
|
||||
|
||||
const JitBlock& jit_block = m_jit_blocks[index.row()];
|
||||
switch (column)
|
||||
{
|
||||
case Column::PPCFeatureFlags:
|
||||
return GetQStringDescription(jit_block.feature_flags);
|
||||
case Column::EffectiveAddress:
|
||||
return QString::number(jit_block.effectiveAddress, 16);
|
||||
case Column::CodeBufferSize:
|
||||
return QString::number(jit_block.originalSize * sizeof(UGeckoInstruction));
|
||||
case Column::RepeatInstructions:
|
||||
return QString::number(jit_block.originalSize - jit_block.physical_addresses.size());
|
||||
case Column::HostNearCodeSize:
|
||||
return QString::number(jit_block.near_end - jit_block.near_begin);
|
||||
case Column::HostFarCodeSize:
|
||||
return QString::number(jit_block.far_end - jit_block.far_begin);
|
||||
}
|
||||
const JitBlock::ProfileData* const profile_data = jit_block.profile_data.get();
|
||||
if (profile_data == nullptr)
|
||||
return QStringLiteral(" --- ");
|
||||
switch (column)
|
||||
{
|
||||
case Column::RunCount:
|
||||
return QString::number(profile_data->run_count);
|
||||
case Column::CyclesSpent:
|
||||
return QString::number(profile_data->cycles_spent);
|
||||
case Column::CyclesAverage:
|
||||
if (profile_data->run_count == 0)
|
||||
return QStringLiteral(" --- ");
|
||||
return QString::number(
|
||||
static_cast<double>(profile_data->cycles_spent) / profile_data->run_count, 'f', 6);
|
||||
case Column::CyclesPercent:
|
||||
if (m_overall_cycles_spent == 0)
|
||||
return QStringLiteral(" --- ");
|
||||
return QStringLiteral("%1%").arg(100.0 * profile_data->cycles_spent / m_overall_cycles_spent,
|
||||
10, 'f', 6);
|
||||
case Column::TimeSpent:
|
||||
{
|
||||
const auto cast_time =
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(profile_data->time_spent);
|
||||
return QString::number(cast_time.count());
|
||||
}
|
||||
case Column::TimeAverage:
|
||||
{
|
||||
if (profile_data->run_count == 0)
|
||||
return QStringLiteral(" --- ");
|
||||
const auto cast_time = std::chrono::duration_cast<std::chrono::duration<double, std::nano>>(
|
||||
profile_data->time_spent);
|
||||
return QString::number(cast_time.count() / profile_data->run_count, 'f', 6);
|
||||
}
|
||||
case Column::TimePercent:
|
||||
{
|
||||
if (m_overall_time_spent == JitBlock::ProfileData::Clock::duration{})
|
||||
return QStringLiteral(" --- ");
|
||||
return QStringLiteral("%1%").arg(
|
||||
100.0 * profile_data->time_spent.count() / m_overall_time_spent.count(), 10, 'f', 6);
|
||||
}
|
||||
}
|
||||
static_assert(Column::NumberOfColumns == 14);
|
||||
Common::Unreachable();
|
||||
}
|
||||
|
||||
QVariant JitBlockTableModel::TextAlignmentRoleData(const QModelIndex& index) const
|
||||
{
|
||||
switch (index.column())
|
||||
{
|
||||
case Column::PPCFeatureFlags:
|
||||
case Column::EffectiveAddress:
|
||||
return Qt::AlignCenter;
|
||||
case Column::CodeBufferSize:
|
||||
case Column::RepeatInstructions:
|
||||
case Column::HostNearCodeSize:
|
||||
case Column::HostFarCodeSize:
|
||||
case Column::RunCount:
|
||||
case Column::CyclesSpent:
|
||||
case Column::CyclesAverage:
|
||||
case Column::CyclesPercent:
|
||||
case Column::TimeSpent:
|
||||
case Column::TimeAverage:
|
||||
case Column::TimePercent:
|
||||
return QVariant::fromValue(Qt::AlignRight | Qt::AlignVCenter);
|
||||
case Column::Symbol:
|
||||
return QVariant::fromValue(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
}
|
||||
static_assert(Column::NumberOfColumns == 14);
|
||||
Common::Unreachable();
|
||||
}
|
||||
|
||||
QVariant JitBlockTableModel::SortRoleData(const QModelIndex& index) const
|
||||
{
|
||||
const int column = index.column();
|
||||
if (column == Column::Symbol)
|
||||
return *m_symbol_list[index.row()];
|
||||
|
||||
const JitBlock& jit_block = m_jit_blocks[index.row()];
|
||||
switch (column)
|
||||
{
|
||||
case Column::PPCFeatureFlags:
|
||||
return jit_block.feature_flags;
|
||||
case Column::EffectiveAddress:
|
||||
return jit_block.effectiveAddress;
|
||||
case Column::CodeBufferSize:
|
||||
return static_cast<qulonglong>(jit_block.originalSize);
|
||||
case Column::RepeatInstructions:
|
||||
return static_cast<qulonglong>(jit_block.originalSize - jit_block.physical_addresses.size());
|
||||
case Column::HostNearCodeSize:
|
||||
return static_cast<qulonglong>(jit_block.near_end - jit_block.near_begin);
|
||||
case Column::HostFarCodeSize:
|
||||
return static_cast<qulonglong>(jit_block.far_end - jit_block.far_begin);
|
||||
}
|
||||
const JitBlock::ProfileData* const profile_data = jit_block.profile_data.get();
|
||||
if (profile_data == nullptr)
|
||||
return QVariant();
|
||||
switch (column)
|
||||
{
|
||||
case Column::RunCount:
|
||||
return static_cast<qulonglong>(profile_data->run_count);
|
||||
case Column::CyclesSpent:
|
||||
case Column::CyclesPercent:
|
||||
return static_cast<qulonglong>(profile_data->cycles_spent);
|
||||
case Column::CyclesAverage:
|
||||
if (profile_data->run_count == 0)
|
||||
return QVariant();
|
||||
return static_cast<double>(profile_data->cycles_spent) / profile_data->run_count;
|
||||
case Column::TimeSpent:
|
||||
case Column::TimePercent:
|
||||
return static_cast<qulonglong>(profile_data->time_spent.count());
|
||||
case Column::TimeAverage:
|
||||
if (profile_data->run_count == 0)
|
||||
return QVariant();
|
||||
return static_cast<double>(profile_data->time_spent.count()) / profile_data->run_count;
|
||||
}
|
||||
static_assert(Column::NumberOfColumns == 14);
|
||||
Common::Unreachable();
|
||||
}
|
||||
|
||||
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<const char*, Column::NumberOfColumns> 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.data() + row, static_cast<std::size_t>(count)})
|
||||
m_jit_interface.EraseSingleBlock(block);
|
||||
m_jit_blocks.remove(row, count);
|
||||
m_symbol_list.remove(row, count);
|
||||
endRemoveRows();
|
||||
return true;
|
||||
}
|
129
Source/Core/DolphinQt/Debugger/JitBlockTableModel.h
Normal file
129
Source/Core/DolphinQt/Debugger/JitBlockTableModel.h
Normal file
|
@ -0,0 +1,129 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QList>
|
||||
#include <QVariant>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Lazy.h"
|
||||
#include "Core/PowerPC/JitCommon/JitCache.h"
|
||||
|
||||
namespace Core
|
||||
{
|
||||
enum class State;
|
||||
class System;
|
||||
} // namespace Core
|
||||
class JitInterface;
|
||||
class PPCSymbolDB;
|
||||
class QString;
|
||||
|
||||
namespace JitBlockTableModelColumn
|
||||
{
|
||||
enum EnumType : int
|
||||
{
|
||||
PPCFeatureFlags = 0,
|
||||
EffectiveAddress,
|
||||
CodeBufferSize,
|
||||
RepeatInstructions,
|
||||
HostNearCodeSize,
|
||||
HostFarCodeSize,
|
||||
RunCount,
|
||||
CyclesSpent,
|
||||
CyclesAverage,
|
||||
CyclesPercent,
|
||||
TimeSpent,
|
||||
TimeAverage,
|
||||
TimePercent,
|
||||
Symbol,
|
||||
NumberOfColumns,
|
||||
};
|
||||
}
|
||||
|
||||
namespace JitBlockTableModelUserRole
|
||||
{
|
||||
enum EnumType : int
|
||||
{
|
||||
SortRole = Qt::UserRole,
|
||||
};
|
||||
}
|
||||
|
||||
class JitBlockTableModel final : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using Column = JitBlockTableModelColumn::EnumType;
|
||||
using UserRole = JitBlockTableModelUserRole::EnumType;
|
||||
using JitBlockRefs = QList<std::reference_wrapper<const JitBlock>>;
|
||||
using SymbolListValueType = Common::Lazy<QVariant>;
|
||||
using SymbolList = QList<SymbolListValueType>;
|
||||
|
||||
public:
|
||||
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;
|
||||
};
|
|
@ -145,6 +145,7 @@
|
|||
<ClCompile Include="Debugger\CodeViewWidget.cpp" />
|
||||
<ClCompile Include="Debugger\CodeWidget.cpp" />
|
||||
<ClCompile Include="Debugger\GekkoSyntaxHighlight.cpp" />
|
||||
<ClCompile Include="Debugger\JitBlockTableModel.cpp" />
|
||||
<ClCompile Include="Debugger\JITWidget.cpp" />
|
||||
<ClCompile Include="Debugger\MemoryViewWidget.cpp" />
|
||||
<ClCompile Include="Debugger\MemoryWidget.cpp" />
|
||||
|
@ -249,6 +250,7 @@
|
|||
<ClInclude Include="GBAHost.h" />
|
||||
<ClInclude Include="QtUtils\ActionHelper.h" />
|
||||
<ClInclude Include="QtUtils\ClearLayoutRecursively.h" />
|
||||
<QtMoc Include="QtUtils\ClickableStatusBar.h" />
|
||||
<ClInclude Include="QtUtils\DolphinFileDialog.h" />
|
||||
<ClInclude Include="QtUtils\ImageConverter.h" />
|
||||
<ClInclude Include="QtUtils\ModalMessageBox.h" />
|
||||
|
@ -359,6 +361,7 @@
|
|||
<QtMoc Include="Debugger\CodeViewWidget.h" />
|
||||
<QtMoc Include="Debugger\CodeWidget.h" />
|
||||
<QtMoc Include="Debugger\GekkoSyntaxHighlight.h" />
|
||||
<QtMoc Include="Debugger\JitBlockTableModel.h" />
|
||||
<QtMoc Include="Debugger\JITWidget.h" />
|
||||
<QtMoc Include="Debugger\MemoryViewWidget.h" />
|
||||
<QtMoc Include="Debugger\MemoryWidget.h" />
|
||||
|
|
|
@ -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(); });
|
||||
|
|
|
@ -38,6 +38,8 @@ signals:
|
|||
void RequestStop();
|
||||
void RequestRenderSize(int w, int h);
|
||||
void UpdateDisasmDialog();
|
||||
void JitCacheCleared();
|
||||
void JitProfileDataWiped();
|
||||
void PPCSymbolsChanged();
|
||||
|
||||
private:
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <QFontDialog>
|
||||
#include <QInputDialog>
|
||||
#include <QMap>
|
||||
#include <QSignalBlocker>
|
||||
#include <QUrl>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
@ -95,6 +96,7 @@ MenuBar::MenuBar(QWidget* parent) : QMenuBar(parent)
|
|||
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
|
||||
[=, this](Core::State state) { OnEmulationStateChanged(state); });
|
||||
connect(&Settings::Instance(), &Settings::ConfigChanged, this, &MenuBar::OnConfigChanged);
|
||||
connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this,
|
||||
[this] { OnEmulationStateChanged(Core::GetState(Core::System::GetInstance())); });
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
|
|
@ -127,6 +127,7 @@ signals:
|
|||
|
||||
private:
|
||||
void OnEmulationStateChanged(Core::State state);
|
||||
void OnConfigChanged();
|
||||
|
||||
void AddFileMenu();
|
||||
|
||||
|
@ -185,6 +186,7 @@ private:
|
|||
void OnRecordingStatusChanged(bool recording);
|
||||
void OnReadOnlyModeChanged(bool read_only);
|
||||
void OnDebugModeToggled(bool enabled);
|
||||
void OnWipeJitBlockProfilingData();
|
||||
void OnWriteJitBlockLogDump();
|
||||
|
||||
QString GetSignatureSelector() const;
|
||||
|
@ -270,6 +272,7 @@ private:
|
|||
QAction* m_jit_log_coverage;
|
||||
QAction* m_jit_search_instruction;
|
||||
QAction* m_jit_profile_blocks;
|
||||
QAction* m_jit_wipe_profiling_data;
|
||||
QAction* m_jit_write_cache_log_dump;
|
||||
QAction* m_jit_off;
|
||||
QAction* m_jit_loadstore_off;
|
||||
|
|
22
Source/Core/DolphinQt/QtUtils/ClickableStatusBar.h
Normal file
22
Source/Core/DolphinQt/QtUtils/ClickableStatusBar.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QStatusBar>
|
||||
|
||||
// I wanted a QStatusBar that emits a signal when clicked. Qt only provides event overrides.
|
||||
class ClickableStatusBar final : public QStatusBar
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ClickableStatusBar(QWidget* parent) : QStatusBar(parent) {}
|
||||
~ClickableStatusBar() override = default;
|
||||
|
||||
signals:
|
||||
void pressed();
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent* event) override { emit pressed(); }
|
||||
};
|
|
@ -61,6 +61,14 @@ void Host_UpdateDisasmDialog()
|
|||
{
|
||||
}
|
||||
|
||||
void Host_JitCacheCleared()
|
||||
{
|
||||
}
|
||||
|
||||
void Host_JitProfileDataWiped()
|
||||
{
|
||||
}
|
||||
|
||||
void Host_UpdateMainFrame()
|
||||
{
|
||||
}
|
||||
|
|
|
@ -3,8 +3,6 @@ add_library(uicommon
|
|||
AutoUpdate.h
|
||||
CommandLineParse.cpp
|
||||
CommandLineParse.h
|
||||
Disassembler.cpp
|
||||
Disassembler.h
|
||||
DiscordPresence.cpp
|
||||
DiscordPresence.h
|
||||
GameFile.cpp
|
||||
|
@ -51,26 +49,6 @@ if(TARGET LibUSB::LibUSB)
|
|||
target_link_libraries(uicommon PRIVATE LibUSB::LibUSB)
|
||||
endif()
|
||||
|
||||
if(ENABLE_LLVM)
|
||||
find_package(LLVM CONFIG)
|
||||
if(LLVM_FOUND)
|
||||
message(STATUS "LLVM found, enabling LLVM support in disassembler")
|
||||
# Minimal documentation about LLVM's CMake functions is available here:
|
||||
# https://releases.llvm.org/16.0.0/docs/CMake.html#embedding-llvm-in-your-project
|
||||
# https://groups.google.com/g/llvm-dev/c/YeEVe7HTasQ?pli=1
|
||||
#
|
||||
# However, you have to read the source code in any case.
|
||||
# Look for LLVM-Config.cmake in your (Unix) system:
|
||||
# $ find /usr -name LLVM-Config\\.cmake 2>/dev/null
|
||||
llvm_expand_pseudo_components(LLVM_EXPAND_COMPONENTS
|
||||
AllTargetsInfos AllTargetsDisassemblers AllTargetsCodeGens
|
||||
)
|
||||
llvm_config(uicommon USE_SHARED
|
||||
mcdisassembler target ${LLVM_EXPAND_COMPONENTS}
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(USE_DISCORD_PRESENCE)
|
||||
target_compile_definitions(uicommon PRIVATE -DUSE_DISCORD_PRESENCE)
|
||||
target_link_libraries(uicommon PRIVATE discord-rpc)
|
||||
|
|
|
@ -1,208 +0,0 @@
|
|||
// Copyright 2008 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "UICommon/Disassembler.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#if defined(HAVE_LLVM)
|
||||
#include <fmt/format.h>
|
||||
#include <llvm-c/Disassembler.h>
|
||||
#include <llvm-c/Target.h>
|
||||
#elif defined(_M_X86_64)
|
||||
#include <disasm.h> // Bochs
|
||||
#endif
|
||||
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/VariantUtil.h"
|
||||
#include "Core/PowerPC/JitInterface.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
#if defined(HAVE_LLVM)
|
||||
class HostDisassemblerLLVM : public HostDisassembler
|
||||
{
|
||||
public:
|
||||
HostDisassemblerLLVM(const std::string& host_disasm, int inst_size = -1,
|
||||
const std::string& cpu = "");
|
||||
~HostDisassemblerLLVM()
|
||||
{
|
||||
if (m_can_disasm)
|
||||
LLVMDisasmDispose(m_llvm_context);
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_can_disasm;
|
||||
LLVMDisasmContextRef m_llvm_context;
|
||||
int m_instruction_size;
|
||||
|
||||
std::string DisassembleHostBlock(const u8* code_start, const u32 code_size,
|
||||
u32* host_instructions_count, u64 starting_pc) override;
|
||||
};
|
||||
|
||||
HostDisassemblerLLVM::HostDisassemblerLLVM(const std::string& host_disasm, int inst_size,
|
||||
const std::string& cpu)
|
||||
: m_can_disasm(false), m_instruction_size(inst_size)
|
||||
{
|
||||
LLVMInitializeAllTargetInfos();
|
||||
LLVMInitializeAllTargetMCs();
|
||||
LLVMInitializeAllDisassemblers();
|
||||
|
||||
m_llvm_context =
|
||||
LLVMCreateDisasmCPU(host_disasm.c_str(), cpu.c_str(), nullptr, 0, nullptr, nullptr);
|
||||
|
||||
// Couldn't create llvm context
|
||||
if (!m_llvm_context)
|
||||
return;
|
||||
|
||||
LLVMSetDisasmOptions(m_llvm_context, LLVMDisassembler_Option_AsmPrinterVariant |
|
||||
LLVMDisassembler_Option_PrintLatency);
|
||||
|
||||
m_can_disasm = true;
|
||||
}
|
||||
|
||||
std::string HostDisassemblerLLVM::DisassembleHostBlock(const u8* code_start, const u32 code_size,
|
||||
u32* host_instructions_count,
|
||||
u64 starting_pc)
|
||||
{
|
||||
if (!m_can_disasm)
|
||||
return "(No LLVM context)";
|
||||
|
||||
u8* disasmPtr = (u8*)code_start;
|
||||
const u8* end = code_start + code_size;
|
||||
|
||||
std::ostringstream x86_disasm;
|
||||
while ((u8*)disasmPtr < end)
|
||||
{
|
||||
char inst_disasm[256];
|
||||
size_t inst_size = LLVMDisasmInstruction(m_llvm_context, disasmPtr, (u64)(end - disasmPtr),
|
||||
starting_pc, inst_disasm, 256);
|
||||
|
||||
x86_disasm << "0x" << std::hex << starting_pc << "\t";
|
||||
if (!inst_size)
|
||||
{
|
||||
x86_disasm << "Invalid inst:";
|
||||
|
||||
if (m_instruction_size != -1)
|
||||
{
|
||||
// If we are on an architecture that has a fixed instruction size
|
||||
// We can continue onward past this bad instruction.
|
||||
std::string inst_str;
|
||||
for (int i = 0; i < m_instruction_size; ++i)
|
||||
inst_str += fmt::format("{:02x}", disasmPtr[i]);
|
||||
|
||||
x86_disasm << inst_str << std::endl;
|
||||
disasmPtr += m_instruction_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We can't continue if we are on an architecture that has flexible instruction sizes
|
||||
// Dump the rest of the block instead
|
||||
std::string code_block;
|
||||
for (int i = 0; (disasmPtr + i) < end; ++i)
|
||||
code_block += fmt::format("{:02x}", disasmPtr[i]);
|
||||
|
||||
x86_disasm << code_block << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
x86_disasm << inst_disasm << std::endl;
|
||||
disasmPtr += inst_size;
|
||||
starting_pc += inst_size;
|
||||
}
|
||||
|
||||
(*host_instructions_count)++;
|
||||
}
|
||||
|
||||
return x86_disasm.str();
|
||||
}
|
||||
#elif defined(_M_X86_64)
|
||||
class HostDisassemblerX86 : public HostDisassembler
|
||||
{
|
||||
public:
|
||||
HostDisassemblerX86();
|
||||
|
||||
private:
|
||||
disassembler m_disasm;
|
||||
|
||||
std::string DisassembleHostBlock(const u8* code_start, const u32 code_size,
|
||||
u32* host_instructions_count, u64 starting_pc) override;
|
||||
};
|
||||
|
||||
HostDisassemblerX86::HostDisassemblerX86()
|
||||
{
|
||||
m_disasm.set_syntax_intel();
|
||||
}
|
||||
|
||||
std::string HostDisassemblerX86::DisassembleHostBlock(const u8* code_start, const u32 code_size,
|
||||
u32* host_instructions_count, u64 starting_pc)
|
||||
{
|
||||
u64 disasmPtr = (u64)code_start;
|
||||
const u8* end = code_start + code_size;
|
||||
|
||||
std::ostringstream x86_disasm;
|
||||
while ((u8*)disasmPtr < end)
|
||||
{
|
||||
char inst_disasm[256];
|
||||
disasmPtr += m_disasm.disasm64(disasmPtr, disasmPtr, (u8*)disasmPtr, inst_disasm);
|
||||
x86_disasm << inst_disasm << std::endl;
|
||||
(*host_instructions_count)++;
|
||||
}
|
||||
|
||||
return x86_disasm.str();
|
||||
}
|
||||
#endif
|
||||
|
||||
std::unique_ptr<HostDisassembler> GetNewDisassembler(const std::string& arch)
|
||||
{
|
||||
#if defined(HAVE_LLVM)
|
||||
if (arch == "x86")
|
||||
return std::make_unique<HostDisassemblerLLVM>("x86_64-none-unknown");
|
||||
if (arch == "aarch64")
|
||||
return std::make_unique<HostDisassemblerLLVM>("aarch64-none-unknown", 4, "cortex-a57");
|
||||
if (arch == "armv7")
|
||||
return std::make_unique<HostDisassemblerLLVM>("armv7-none-unknown", 4, "cortex-a15");
|
||||
#elif defined(_M_X86_64)
|
||||
if (arch == "x86")
|
||||
return std::make_unique<HostDisassemblerX86>();
|
||||
#endif
|
||||
return std::make_unique<HostDisassembler>();
|
||||
}
|
||||
|
||||
DisassembleResult DisassembleBlock(HostDisassembler* disasm, u32 address)
|
||||
{
|
||||
auto res = Core::System::GetInstance().GetJitInterface().GetHostCode(address);
|
||||
|
||||
return std::visit(overloaded{[&](JitInterface::GetHostCodeError error) {
|
||||
DisassembleResult result;
|
||||
switch (error)
|
||||
{
|
||||
case JitInterface::GetHostCodeError::NoJitActive:
|
||||
result.text = "(No JIT active)";
|
||||
break;
|
||||
case JitInterface::GetHostCodeError::NoTranslation:
|
||||
result.text = "(No translation)";
|
||||
break;
|
||||
default:
|
||||
ASSERT(false);
|
||||
break;
|
||||
}
|
||||
result.entry_address = address;
|
||||
result.instruction_count = 0;
|
||||
result.code_size = 0;
|
||||
return result;
|
||||
},
|
||||
[&](JitInterface::GetHostCodeResult host_result) {
|
||||
DisassembleResult new_result;
|
||||
u32 instruction_count = 0;
|
||||
new_result.text = disasm->DisassembleHostBlock(
|
||||
host_result.code, host_result.code_size, &instruction_count,
|
||||
(u64)host_result.code);
|
||||
new_result.entry_address = host_result.entry_address;
|
||||
new_result.code_size = host_result.code_size;
|
||||
new_result.instruction_count = instruction_count;
|
||||
return new_result;
|
||||
}},
|
||||
res);
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
// Copyright 2008 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
class HostDisassembler
|
||||
{
|
||||
public:
|
||||
virtual ~HostDisassembler() {}
|
||||
virtual std::string DisassembleHostBlock(const u8* code_start, const u32 code_size,
|
||||
u32* host_instructions_count, u64 starting_pc)
|
||||
{
|
||||
return "(No disassembler)";
|
||||
}
|
||||
};
|
||||
|
||||
struct DisassembleResult
|
||||
{
|
||||
std::string text;
|
||||
u32 entry_address = 0;
|
||||
u32 instruction_count = 0;
|
||||
u32 code_size = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<HostDisassembler> GetNewDisassembler(const std::string& arch);
|
||||
DisassembleResult DisassembleBlock(HostDisassembler* disasm, u32 address);
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
|
|
|
@ -40,6 +40,10 @@ public:
|
|||
// 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::vector<MemoryStats> GetMemoryStats() const override { return {}; }
|
||||
const CommonAsmRoutinesBase* GetAsmRoutines() override { return nullptr; }
|
||||
virtual bool HandleFault(uintptr_t access_address, SContext* ctx) override
|
||||
{
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue