mirror of
https://github.com/izzy2lost/dolphin.git
synced 2026-06-19 01:16:48 -07:00
Merge pull request #12714 from mitaclaw/jit-widget-refresh
DolphinQt: JIT Widget Refresh
This commit is contained in:
+25
@@ -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>) {
|
||||
|
||||
@@ -391,6 +391,11 @@ public final class NativeLibrary
|
||||
*/
|
||||
public static native boolean IsUninitialized();
|
||||
|
||||
/**
|
||||
* Re-initialize software JitBlock profiling data
|
||||
*/
|
||||
public static native void WipeJitBlockProfilingData();
|
||||
|
||||
/**
|
||||
* Writes out the JitBlock Cache log dump
|
||||
*/
|
||||
|
||||
+10
@@ -2013,6 +2013,16 @@ class SettingsFragmentPresenter(
|
||||
0
|
||||
)
|
||||
)
|
||||
sl.add(
|
||||
RunRunnable(
|
||||
context,
|
||||
R.string.debug_jit_wipe_block_profiling_data,
|
||||
0,
|
||||
R.string.debug_jit_wipe_block_profiling_data_alert,
|
||||
0,
|
||||
true
|
||||
) { NativeLibrary.WipeJitBlockProfilingData() }
|
||||
)
|
||||
sl.add(
|
||||
RunRunnable(
|
||||
context,
|
||||
|
||||
@@ -412,6 +412,8 @@
|
||||
<string name="debug_large_entry_points_map">Disable Large Entry Points Map</string>
|
||||
<string name="debug_jit_profiling_header">Jit Profiling</string>
|
||||
<string name="debug_jit_enable_block_profiling">Enable Jit Block Profiling</string>
|
||||
<string name="debug_jit_wipe_block_profiling_data">Wipe Jit Block Profiling Data</string>
|
||||
<string name="debug_jit_wipe_block_profiling_data_alert">Re-initialize JIT block profiling data?</string>
|
||||
<string name="debug_jit_write_block_log_dump">Write Jit Block Log Dump</string>
|
||||
<string name="debug_jit_header">Jit</string>
|
||||
<string name="debug_jitoff">Jit Disabled</string>
|
||||
|
||||
@@ -146,6 +146,14 @@ void Host_UpdateDisasmDialog()
|
||||
{
|
||||
}
|
||||
|
||||
void Host_JitCacheCleared()
|
||||
{
|
||||
}
|
||||
|
||||
void Host_JitProfileDataWiped()
|
||||
{
|
||||
}
|
||||
|
||||
void Host_UpdateMainFrame()
|
||||
{
|
||||
}
|
||||
@@ -410,6 +418,22 @@ JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetMaxLogLev
|
||||
return static_cast<jint>(Common::Log::MAX_LOGLEVEL);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_WipeJitBlockProfilingData(
|
||||
JNIEnv* env, jclass native_library_class)
|
||||
{
|
||||
HostThreadLock guard;
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& jit_interface = system.GetJitInterface();
|
||||
const Core::CPUThreadGuard cpu_guard(system);
|
||||
if (jit_interface.GetCore() == nullptr)
|
||||
{
|
||||
env->CallStaticVoidMethod(native_library_class, IDCache::GetDisplayToastMsg(),
|
||||
ToJString(env, Common::GetStringT("JIT is not active")), JNI_FALSE);
|
||||
return;
|
||||
}
|
||||
jit_interface.WipeBlockProfilingData(cpu_guard);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_WriteJitBlockLogDump(
|
||||
JNIEnv* env, jclass native_library_class)
|
||||
{
|
||||
|
||||
@@ -75,6 +75,8 @@ add_library(common
|
||||
Hash.cpp
|
||||
Hash.h
|
||||
HookableEvent.h
|
||||
HostDisassembler.cpp
|
||||
HostDisassembler.h
|
||||
HttpRequest.cpp
|
||||
HttpRequest.h
|
||||
Image.cpp
|
||||
@@ -180,6 +182,11 @@ PRIVATE
|
||||
${VTUNE_LIBRARIES}
|
||||
)
|
||||
|
||||
if ((DEFINED CMAKE_ANDROID_ARCH_ABI AND CMAKE_ANDROID_ARCH_ABI MATCHES "x86|x86_64") OR
|
||||
(NOT DEFINED CMAKE_ANDROID_ARCH_ABI AND _M_X86_64))
|
||||
target_link_libraries(common PRIVATE bdisasm)
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
target_link_libraries(common
|
||||
PRIVATE
|
||||
@@ -330,6 +337,28 @@ if(OPROFILE_FOUND)
|
||||
target_link_libraries(common PRIVATE OProfile::OProfile)
|
||||
endif()
|
||||
|
||||
if(ENABLE_LLVM)
|
||||
find_package(LLVM CONFIG)
|
||||
if(LLVM_FOUND)
|
||||
message(STATUS "LLVM found, enabling LLVM support in disassembler")
|
||||
target_compile_definitions(common PRIVATE HAVE_LLVM)
|
||||
# Minimal documentation about LLVM's CMake functions is available here:
|
||||
# https://releases.llvm.org/16.0.0/docs/CMake.html#embedding-llvm-in-your-project
|
||||
# https://groups.google.com/g/llvm-dev/c/YeEVe7HTasQ?pli=1
|
||||
#
|
||||
# However, you have to read the source code in any case.
|
||||
# Look for LLVM-Config.cmake in your (Unix) system:
|
||||
# $ find /usr -name LLVM-Config\\.cmake 2>/dev/null
|
||||
llvm_expand_pseudo_components(LLVM_EXPAND_COMPONENTS
|
||||
AllTargetsInfos AllTargetsDisassemblers AllTargetsCodeGens
|
||||
)
|
||||
llvm_config(common USE_SHARED
|
||||
mcdisassembler target ${LLVM_EXPAND_COMPONENTS}
|
||||
)
|
||||
target_include_directories(common PRIVATE ${LLVM_INCLUDE_DIRS})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(UNIX)
|
||||
# Posix networking code needs to be fixed for Windows
|
||||
add_executable(traversal_server TraversalServer.cpp)
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
// Copyright 2008 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Common/HostDisassembler.h"
|
||||
|
||||
#include <span>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ostream.h>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
#if defined(HAVE_LLVM)
|
||||
#include <llvm-c/Disassembler.h>
|
||||
#include <llvm-c/Target.h>
|
||||
#elif defined(_M_X86_64)
|
||||
#include <disasm.h> // Bochs
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_LLVM)
|
||||
class HostDisassemblerLLVM final : public HostDisassembler
|
||||
{
|
||||
public:
|
||||
explicit HostDisassemblerLLVM(const char* host_disasm, const char* cpu = "",
|
||||
std::size_t inst_size = 0);
|
||||
~HostDisassemblerLLVM();
|
||||
|
||||
private:
|
||||
LLVMDisasmContextRef m_llvm_context;
|
||||
std::size_t m_instruction_size;
|
||||
|
||||
std::size_t Disassemble(const u8* begin, const u8* end, std::ostream& stream) override;
|
||||
};
|
||||
|
||||
HostDisassemblerLLVM::HostDisassemblerLLVM(const char* host_disasm, const char* cpu,
|
||||
std::size_t inst_size)
|
||||
: m_instruction_size(inst_size)
|
||||
{
|
||||
LLVMInitializeAllTargetInfos();
|
||||
LLVMInitializeAllTargetMCs();
|
||||
LLVMInitializeAllDisassemblers();
|
||||
|
||||
m_llvm_context = LLVMCreateDisasmCPU(host_disasm, cpu, nullptr, 0, nullptr, nullptr);
|
||||
|
||||
// Couldn't create llvm context
|
||||
if (!m_llvm_context)
|
||||
return;
|
||||
|
||||
LLVMSetDisasmOptions(m_llvm_context, LLVMDisassembler_Option_AsmPrinterVariant |
|
||||
LLVMDisassembler_Option_PrintLatency);
|
||||
}
|
||||
|
||||
HostDisassemblerLLVM::~HostDisassemblerLLVM()
|
||||
{
|
||||
if (m_llvm_context)
|
||||
LLVMDisasmDispose(m_llvm_context);
|
||||
}
|
||||
|
||||
std::size_t HostDisassemblerLLVM::Disassemble(const u8* begin, const u8* end, std::ostream& stream)
|
||||
{
|
||||
std::size_t instruction_count = 0;
|
||||
if (!m_llvm_context)
|
||||
return instruction_count;
|
||||
|
||||
while (begin < end)
|
||||
{
|
||||
char inst_disasm[256];
|
||||
|
||||
const auto inst_size =
|
||||
LLVMDisasmInstruction(m_llvm_context, const_cast<u8*>(begin), static_cast<u64>(end - begin),
|
||||
reinterpret_cast<u64>(begin), inst_disasm, sizeof(inst_disasm));
|
||||
if (inst_size == 0)
|
||||
{
|
||||
if (m_instruction_size != 0)
|
||||
{
|
||||
// If we are on an architecture that has a fixed instruction
|
||||
// size, we can continue onward past this bad instruction.
|
||||
fmt::println(stream, "{}\tInvalid inst: {:02x}", fmt::ptr(begin),
|
||||
fmt::join(std::span{begin, m_instruction_size}, ""));
|
||||
begin += m_instruction_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We can't continue if we are on an architecture that has flexible
|
||||
// instruction sizes. Dump the rest of the block instead.
|
||||
fmt::println(stream, "{}\tInvalid inst: {:02x}", fmt::ptr(begin),
|
||||
fmt::join(std::span{begin, end}, ""));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fmt::println(stream, "{}{}", fmt::ptr(begin), inst_disasm);
|
||||
begin += inst_size;
|
||||
}
|
||||
++instruction_count;
|
||||
}
|
||||
return instruction_count;
|
||||
}
|
||||
#elif defined(_M_X86_64)
|
||||
class HostDisassemblerBochs final : public HostDisassembler
|
||||
{
|
||||
public:
|
||||
explicit HostDisassemblerBochs();
|
||||
~HostDisassemblerBochs() = default;
|
||||
|
||||
private:
|
||||
disassembler m_disasm;
|
||||
|
||||
std::size_t Disassemble(const u8* begin, const u8* end, std::ostream& stream) override;
|
||||
};
|
||||
|
||||
HostDisassemblerBochs::HostDisassemblerBochs()
|
||||
{
|
||||
m_disasm.set_syntax_intel();
|
||||
}
|
||||
|
||||
std::size_t HostDisassemblerBochs::Disassemble(const u8* begin, const u8* end, std::ostream& stream)
|
||||
{
|
||||
std::size_t instruction_count = 0;
|
||||
while (begin < end)
|
||||
{
|
||||
char inst_disasm[256];
|
||||
|
||||
const auto inst_size =
|
||||
m_disasm.disasm64(0, reinterpret_cast<bx_address>(begin), begin, inst_disasm);
|
||||
fmt::println(stream, "{}\t{}", fmt::ptr(begin), inst_disasm);
|
||||
begin += inst_size;
|
||||
++instruction_count;
|
||||
}
|
||||
return instruction_count;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::unique_ptr<HostDisassembler> HostDisassembler::Factory(Platform arch)
|
||||
{
|
||||
switch (arch)
|
||||
{
|
||||
#if defined(HAVE_LLVM)
|
||||
case Platform::x86_64:
|
||||
return std::make_unique<HostDisassemblerLLVM>("x86_64-none-unknown");
|
||||
case Platform::aarch64:
|
||||
return std::make_unique<HostDisassemblerLLVM>("aarch64-none-unknown", "cortex-a57", 4);
|
||||
#elif defined(_M_X86_64)
|
||||
case Platform::x86_64:
|
||||
return std::make_unique<HostDisassemblerBochs>();
|
||||
#else
|
||||
case Platform{}: // warning C4065: "switch statement contains 'default' but no 'case' labels"
|
||||
#endif
|
||||
default:
|
||||
return std::make_unique<HostDisassembler>();
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t HostDisassembler::Disassemble(const u8* begin, const u8* end, std::ostream& stream)
|
||||
{
|
||||
fmt::println(stream, "{}\t{:02x}", fmt::ptr(begin), fmt::join(std::span{begin, end}, ""));
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2008 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <iosfwd>
|
||||
#include <memory>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
class HostDisassembler
|
||||
{
|
||||
public:
|
||||
enum class Platform
|
||||
{
|
||||
x86_64,
|
||||
aarch64,
|
||||
};
|
||||
|
||||
virtual ~HostDisassembler() = default;
|
||||
|
||||
static std::unique_ptr<HostDisassembler> Factory(Platform arch);
|
||||
|
||||
virtual std::size_t Disassemble(const u8* begin, const u8* end, std::ostream& stream);
|
||||
};
|
||||
@@ -479,6 +479,7 @@ add_library(core
|
||||
PatchEngine.h
|
||||
PowerPC/BreakPoints.cpp
|
||||
PowerPC/BreakPoints.h
|
||||
PowerPC/CachedInterpreter/CachedInterpreter_Disassembler.cpp
|
||||
PowerPC/CachedInterpreter/CachedInterpreter.cpp
|
||||
PowerPC/CachedInterpreter/CachedInterpreter.h
|
||||
PowerPC/CachedInterpreter/CachedInterpreterBlockCache.cpp
|
||||
@@ -652,11 +653,6 @@ PRIVATE
|
||||
ZLIB::ZLIB
|
||||
)
|
||||
|
||||
if ((DEFINED CMAKE_ANDROID_ARCH_ABI AND CMAKE_ANDROID_ARCH_ABI MATCHES "x86|x86_64") OR
|
||||
(NOT DEFINED CMAKE_ANDROID_ARCH_ABI AND _M_X86_64))
|
||||
target_link_libraries(core PRIVATE bdisasm)
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
target_link_libraries(core
|
||||
PRIVATE
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -60,6 +60,8 @@ void Host_PPCSymbolsChanged();
|
||||
void Host_RefreshDSPDebuggerWindow();
|
||||
void Host_RequestRenderWindowSize(int width, int height);
|
||||
void Host_UpdateDisasmDialog();
|
||||
void Host_JitCacheCleared();
|
||||
void Host_JitProfileDataWiped();
|
||||
void Host_UpdateMainFrame();
|
||||
void Host_UpdateTitle(const std::string& title);
|
||||
void Host_YieldToUI();
|
||||
|
||||
@@ -3,13 +3,22 @@
|
||||
|
||||
#include "Core/PowerPC/CachedInterpreter/CachedInterpreter.h"
|
||||
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/GekkoDisassembler.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/HLE/HLE.h"
|
||||
#include "Core/HW/CPU.h"
|
||||
#include "Core/Host.h"
|
||||
#include "Core/PowerPC/Gekko.h"
|
||||
#include "Core/PowerPC/Interpreter/Interpreter.h"
|
||||
#include "Core/PowerPC/Jit64Common/Jit64Constants.h"
|
||||
@@ -289,14 +298,15 @@ void CachedInterpreter::Jit(u32 em_address, bool clear_cache_and_retry_on_failur
|
||||
b->near_end = GetWritableCodePtr();
|
||||
b->far_begin = b->far_end = nullptr;
|
||||
|
||||
b->codeSize = static_cast<u32>(b->near_end - b->normalEntry);
|
||||
b->originalSize = code_block.m_num_instructions;
|
||||
|
||||
// Mark the memory region that this code block uses in the RangeSizeSet.
|
||||
if (b->near_begin != b->near_end)
|
||||
m_free_ranges.erase(b->near_begin, b->near_end);
|
||||
|
||||
m_block_cache.FinalizeBlock(*b, jo.enableBlocklink, code_block.m_physical_addresses);
|
||||
m_block_cache.FinalizeBlock(*b, jo.enableBlocklink, code_block, m_code_buffer);
|
||||
|
||||
#ifdef JIT_LOG_GENERATED_CODE
|
||||
LogGeneratedCode();
|
||||
#endif
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -370,15 +380,16 @@ bool CachedInterpreter::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
|
||||
{interpreter, Interpreter::GetInterpreterOp(op.inst), js.compilerPC, op.inst},
|
||||
power_pc,
|
||||
js.downcountAmount};
|
||||
Write(op.canEndBlock ? InterpretAndCheckExceptions<true> :
|
||||
InterpretAndCheckExceptions<false>,
|
||||
Write(op.canEndBlock ? CallbackCast(InterpretAndCheckExceptions<true>) :
|
||||
CallbackCast(InterpretAndCheckExceptions<false>),
|
||||
operands);
|
||||
}
|
||||
else
|
||||
{
|
||||
const InterpretOperands operands = {interpreter, Interpreter::GetInterpreterOp(op.inst),
|
||||
js.compilerPC, op.inst};
|
||||
Write(op.canEndBlock ? Interpret<true> : Interpret<false>, operands);
|
||||
Write(op.canEndBlock ? CallbackCast(Interpret<true>) : CallbackCast(Interpret<false>),
|
||||
operands);
|
||||
}
|
||||
|
||||
if (op.branchIsIdleLoop)
|
||||
@@ -401,6 +412,29 @@ bool CachedInterpreter::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
|
||||
return true;
|
||||
}
|
||||
|
||||
void CachedInterpreter::EraseSingleBlock(const JitBlock& block)
|
||||
{
|
||||
m_block_cache.EraseSingleBlock(block);
|
||||
FreeRanges();
|
||||
}
|
||||
|
||||
std::vector<JitBase::MemoryStats> CachedInterpreter::GetMemoryStats() const
|
||||
{
|
||||
return {{"free", m_free_ranges.get_stats()}};
|
||||
}
|
||||
|
||||
std::size_t CachedInterpreter::DisassembleNearCode(const JitBlock& block,
|
||||
std::ostream& stream) const
|
||||
{
|
||||
return Disassemble(block, stream);
|
||||
}
|
||||
|
||||
std::size_t CachedInterpreter::DisassembleFarCode(const JitBlock& block, std::ostream& stream) const
|
||||
{
|
||||
stream << "N/A\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CachedInterpreter::ClearCache()
|
||||
{
|
||||
m_block_cache.Clear();
|
||||
@@ -408,4 +442,24 @@ void CachedInterpreter::ClearCache()
|
||||
ClearCodeSpace();
|
||||
ResetFreeMemoryRanges();
|
||||
RefreshConfig();
|
||||
Host_JitCacheCleared();
|
||||
}
|
||||
|
||||
void CachedInterpreter::LogGeneratedCode() const
|
||||
{
|
||||
std::ostringstream stream;
|
||||
|
||||
stream << "\nPPC Code Buffer:\n";
|
||||
for (const PPCAnalyst::CodeOp& op :
|
||||
std::span{m_code_buffer.data(), code_block.m_num_instructions})
|
||||
{
|
||||
fmt::print(stream, "0x{:08x}\t\t{}\n", op.address,
|
||||
Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address));
|
||||
}
|
||||
|
||||
stream << "\nHost Code:\n";
|
||||
Disassemble(*js.curBlock, stream);
|
||||
|
||||
// TODO C++20: std::ostringstream::view()
|
||||
DEBUG_LOG_FMT(DYNA_REC, "{}", std::move(stream).str());
|
||||
}
|
||||
|
||||
@@ -46,6 +46,14 @@ public:
|
||||
void Jit(u32 address, bool clear_cache_and_retry_on_failure);
|
||||
bool DoJit(u32 address, JitBlock* b, u32 nextPC);
|
||||
|
||||
void EraseSingleBlock(const JitBlock& block) override;
|
||||
std::vector<MemoryStats> GetMemoryStats() const override;
|
||||
|
||||
static std::size_t Disassemble(const JitBlock& block, std::ostream& stream);
|
||||
|
||||
std::size_t DisassembleNearCode(const JitBlock& block, std::ostream& stream) const override;
|
||||
std::size_t DisassembleFarCode(const JitBlock& block, std::ostream& stream) const override;
|
||||
|
||||
JitBaseBlockCache* GetBlockCache() override { return &m_block_cache; }
|
||||
const char* GetName() const override { return "Cached Interpreter"; }
|
||||
const CommonAsmRoutinesBase* GetAsmRoutines() override { return nullptr; }
|
||||
@@ -63,6 +71,8 @@ private:
|
||||
void FreeRanges();
|
||||
void ResetFreeMemoryRanges();
|
||||
|
||||
void LogGeneratedCode() const;
|
||||
|
||||
struct StartProfiledBlockOperands;
|
||||
template <bool profiled>
|
||||
struct EndBlockOperands;
|
||||
@@ -74,20 +84,33 @@ private:
|
||||
struct CheckIdleOperands;
|
||||
|
||||
static s32 StartProfiledBlock(PowerPC::PowerPCState& ppc_state,
|
||||
const StartProfiledBlockOperands& profile_data);
|
||||
const StartProfiledBlockOperands& operands);
|
||||
static s32 StartProfiledBlock(std::ostream& stream, const StartProfiledBlockOperands& operands);
|
||||
template <bool profiled>
|
||||
static s32 EndBlock(PowerPC::PowerPCState& ppc_state, const EndBlockOperands<profiled>& operands);
|
||||
template <bool profiled>
|
||||
static s32 EndBlock(std::ostream& stream, const EndBlockOperands<profiled>& operands);
|
||||
template <bool write_pc>
|
||||
static s32 Interpret(PowerPC::PowerPCState& ppc_state, const InterpretOperands& operands);
|
||||
template <bool write_pc>
|
||||
static s32 Interpret(std::ostream& stream, const InterpretOperands& operands);
|
||||
template <bool write_pc>
|
||||
static s32 InterpretAndCheckExceptions(PowerPC::PowerPCState& ppc_state,
|
||||
const InterpretAndCheckExceptionsOperands& operands);
|
||||
template <bool write_pc>
|
||||
static s32 InterpretAndCheckExceptions(std::ostream& stream,
|
||||
const InterpretAndCheckExceptionsOperands& operands);
|
||||
static s32 HLEFunction(PowerPC::PowerPCState& ppc_state, const HLEFunctionOperands& operands);
|
||||
static s32 HLEFunction(std::ostream& stream, const HLEFunctionOperands& operands);
|
||||
static s32 WriteBrokenBlockNPC(PowerPC::PowerPCState& ppc_state,
|
||||
const WriteBrokenBlockNPCOperands& operands);
|
||||
static s32 WriteBrokenBlockNPC(std::ostream& stream, const WriteBrokenBlockNPCOperands& operands);
|
||||
static s32 CheckFPU(PowerPC::PowerPCState& ppc_state, const CheckHaltOperands& operands);
|
||||
static s32 CheckFPU(std::ostream& stream, const CheckHaltOperands& operands);
|
||||
static s32 CheckBreakpoint(PowerPC::PowerPCState& ppc_state, const CheckHaltOperands& operands);
|
||||
static s32 CheckBreakpoint(std::ostream& stream, const CheckHaltOperands& operands);
|
||||
static s32 CheckIdle(PowerPC::PowerPCState& ppc_state, const CheckIdleOperands& operands);
|
||||
static s32 CheckIdle(std::ostream& stream, const CheckIdleOperands& operands);
|
||||
|
||||
HyoutaUtilities::RangeSizeSet<u8*> m_free_ranges;
|
||||
CachedInterpreterBlockCache m_block_cache;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <iosfwd>
|
||||
#include <type_traits>
|
||||
|
||||
#include "Common/CodeBlock.h"
|
||||
@@ -24,6 +25,11 @@ protected:
|
||||
using Callback = s32 (*)(PowerPC::PowerPCState& ppc_state, const Operands& operands);
|
||||
using AnyCallback = s32 (*)(PowerPC::PowerPCState& ppc_state, const void* operands);
|
||||
|
||||
template <class Operands>
|
||||
static consteval Callback<Operands> CallbackCast(Callback<Operands> callback)
|
||||
{
|
||||
return callback;
|
||||
}
|
||||
template <class Operands>
|
||||
static AnyCallback AnyCallbackCast(Callback<Operands> callback)
|
||||
{
|
||||
@@ -31,6 +37,21 @@ protected:
|
||||
}
|
||||
static consteval AnyCallback AnyCallbackCast(AnyCallback callback) { return callback; }
|
||||
|
||||
// Disassemble callbacks will always return the distance to the next callback.
|
||||
template <class Operands>
|
||||
using Disassemble = s32 (*)(std::ostream& stream, const Operands& operands);
|
||||
using AnyDisassemble = s32 (*)(std::ostream& stream, const void* operands);
|
||||
|
||||
template <class Operands>
|
||||
static AnyDisassemble AnyDisassembleCast(Disassemble<Operands> disassemble)
|
||||
{
|
||||
return reinterpret_cast<AnyDisassemble>(disassemble);
|
||||
}
|
||||
static consteval AnyDisassemble AnyDisassembleCast(AnyDisassemble disassemble)
|
||||
{
|
||||
return disassemble;
|
||||
}
|
||||
|
||||
public:
|
||||
CachedInterpreterEmitter() = default;
|
||||
explicit CachedInterpreterEmitter(u8* begin, u8* end) : m_code(begin), m_code_end(end) {}
|
||||
@@ -63,6 +84,7 @@ public:
|
||||
}
|
||||
|
||||
static s32 PoisonCallback(PowerPC::PowerPCState& ppc_state, const void* operands);
|
||||
static s32 PoisonCallback(std::ostream& stream, const void* operands);
|
||||
|
||||
private:
|
||||
void Write(AnyCallback callback, const void* operands, std::size_t size);
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Core/PowerPC/CachedInterpreter/CachedInterpreter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
#include "Core/HLE/HLE.h"
|
||||
|
||||
s32 CachedInterpreterEmitter::PoisonCallback(std::ostream& stream, const void* operands)
|
||||
{
|
||||
stream << "PoisonCallback()\n";
|
||||
return sizeof(AnyCallback);
|
||||
}
|
||||
|
||||
s32 CachedInterpreter::StartProfiledBlock(std::ostream& stream,
|
||||
const StartProfiledBlockOperands& operands)
|
||||
{
|
||||
stream << "StartProfiledBlock()\n";
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
template <bool profiled>
|
||||
s32 CachedInterpreter::EndBlock(std::ostream& stream, const EndBlockOperands<profiled>& operands)
|
||||
{
|
||||
fmt::println(stream, "EndBlock<profiled={}>(downcount={}, num_load_stores={}, num_fp_inst={})",
|
||||
profiled, operands.downcount, operands.num_load_stores, operands.num_fp_inst);
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
template <bool write_pc>
|
||||
s32 CachedInterpreter::Interpret(std::ostream& stream, const InterpretOperands& operands)
|
||||
{
|
||||
fmt::println(stream, "Interpret<write_pc={:5}>(current_pc=0x{:08x}, inst=0x{:08x})", write_pc,
|
||||
operands.current_pc, operands.inst.hex);
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
template <bool write_pc>
|
||||
s32 CachedInterpreter::InterpretAndCheckExceptions(
|
||||
std::ostream& stream, const InterpretAndCheckExceptionsOperands& operands)
|
||||
{
|
||||
fmt::println(stream,
|
||||
"InterpretAndCheckExceptions<write_pc={:5}>(current_pc=0x{:08x}, inst=0x{:08x}, "
|
||||
"downcount={})",
|
||||
write_pc, operands.current_pc, operands.inst.hex, operands.downcount);
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
s32 CachedInterpreter::HLEFunction(std::ostream& stream, const HLEFunctionOperands& operands)
|
||||
{
|
||||
const auto& [system, current_pc, hook_index] = operands;
|
||||
fmt::println(stream, "HLEFunction(current_pc=0x{:08x}, hook_index={}) [\"{}\"]", current_pc,
|
||||
hook_index, HLE::GetHookNameByIndex(hook_index));
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
s32 CachedInterpreter::WriteBrokenBlockNPC(std::ostream& stream,
|
||||
const WriteBrokenBlockNPCOperands& operands)
|
||||
{
|
||||
const auto& [current_pc] = operands;
|
||||
fmt::println(stream, "WriteBrokenBlockNPC(current_pc=0x{:08x})", current_pc);
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
s32 CachedInterpreter::CheckFPU(std::ostream& stream, const CheckHaltOperands& operands)
|
||||
{
|
||||
const auto& [power_pc, current_pc, downcount] = operands;
|
||||
fmt::println(stream, "CheckFPU(current_pc=0x{:08x}, downcount={})", current_pc, downcount);
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
s32 CachedInterpreter::CheckBreakpoint(std::ostream& stream, const CheckHaltOperands& operands)
|
||||
{
|
||||
const auto& [power_pc, current_pc, downcount] = operands;
|
||||
fmt::println(stream, "CheckBreakpoint(current_pc=0x{:08x}, downcount={})", current_pc, downcount);
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
s32 CachedInterpreter::CheckIdle(std::ostream& stream, const CheckIdleOperands& operands)
|
||||
{
|
||||
const auto& [core_timing, idle_pc] = operands;
|
||||
fmt::println(stream, "CheckIdle(idle_pc=0x{:08x})", idle_pc);
|
||||
return sizeof(AnyCallback) + sizeof(operands);
|
||||
}
|
||||
|
||||
static std::once_flag s_sorted_lookup_flag;
|
||||
|
||||
std::size_t CachedInterpreter::Disassemble(const JitBlock& block, std::ostream& stream)
|
||||
{
|
||||
using LookupKV = std::pair<AnyCallback, AnyDisassemble>;
|
||||
|
||||
// clang-format off
|
||||
#define LOOKUP_KV(...) {AnyCallbackCast(__VA_ARGS__), AnyDisassembleCast(__VA_ARGS__)}
|
||||
// clang-format on
|
||||
|
||||
// Function addresses aren't known at compile-time, so this array is sorted at run-time.
|
||||
static auto sorted_lookup = std::to_array<LookupKV>({
|
||||
LOOKUP_KV(CachedInterpreter::PoisonCallback),
|
||||
LOOKUP_KV(CachedInterpreter::StartProfiledBlock),
|
||||
LOOKUP_KV(CachedInterpreter::EndBlock<false>),
|
||||
LOOKUP_KV(CachedInterpreter::EndBlock<true>),
|
||||
LOOKUP_KV(CachedInterpreter::Interpret<false>),
|
||||
LOOKUP_KV(CachedInterpreter::Interpret<true>),
|
||||
LOOKUP_KV(CachedInterpreter::InterpretAndCheckExceptions<false>),
|
||||
LOOKUP_KV(CachedInterpreter::InterpretAndCheckExceptions<true>),
|
||||
LOOKUP_KV(CachedInterpreter::HLEFunction),
|
||||
LOOKUP_KV(CachedInterpreter::WriteBrokenBlockNPC),
|
||||
LOOKUP_KV(CachedInterpreter::CheckFPU),
|
||||
LOOKUP_KV(CachedInterpreter::CheckBreakpoint),
|
||||
LOOKUP_KV(CachedInterpreter::CheckIdle),
|
||||
});
|
||||
|
||||
#undef LOOKUP_KV
|
||||
|
||||
std::call_once(s_sorted_lookup_flag, []() {
|
||||
const auto end = std::ranges::sort(sorted_lookup, {}, &LookupKV::first);
|
||||
ASSERT_MSG(DYNA_REC, std::ranges::adjacent_find(sorted_lookup, {}, &LookupKV::first) == end,
|
||||
"Sorted lookup should not contain duplicate keys.");
|
||||
});
|
||||
|
||||
std::size_t instruction_count = 0;
|
||||
for (const u8* normal_entry = block.normalEntry; normal_entry != block.near_end;
|
||||
++instruction_count)
|
||||
{
|
||||
const auto callback = *reinterpret_cast<const AnyCallback*>(normal_entry);
|
||||
const auto kv = std::ranges::lower_bound(sorted_lookup, callback, {}, &LookupKV::first);
|
||||
if (kv != sorted_lookup.end() && kv->first == callback)
|
||||
{
|
||||
normal_entry += kv->second(stream, normal_entry + sizeof(AnyCallback));
|
||||
continue;
|
||||
}
|
||||
stream << "UNKNOWN OR ILLEGAL CALLBACK\n";
|
||||
break;
|
||||
}
|
||||
return instruction_count;
|
||||
}
|
||||
@@ -4,11 +4,12 @@
|
||||
#include "Core/PowerPC/Jit64/Jit.h"
|
||||
|
||||
#include <map>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include <disasm.h>
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
// for the PROFILER stuff
|
||||
#ifdef _WIN32
|
||||
@@ -18,6 +19,7 @@
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/EnumUtils.h"
|
||||
#include "Common/GekkoDisassembler.h"
|
||||
#include "Common/HostDisassembler.h"
|
||||
#include "Common/IOFile.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/StringUtil.h"
|
||||
@@ -30,6 +32,7 @@
|
||||
#include "Core/HW/GPFifo.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/HW/ProcessorInterface.h"
|
||||
#include "Core/Host.h"
|
||||
#include "Core/MachineContext.h"
|
||||
#include "Core/PatchEngine.h"
|
||||
#include "Core/PowerPC/Interpreter/Interpreter.h"
|
||||
@@ -116,7 +119,9 @@ using namespace PowerPC;
|
||||
and such, but it's currently limited to integer ops only. This can definitely be made better.
|
||||
*/
|
||||
|
||||
Jit64::Jit64(Core::System& system) : JitBase(system), QuantizedMemoryRoutines(*this)
|
||||
Jit64::Jit64(Core::System& system)
|
||||
: JitBase(system), QuantizedMemoryRoutines(*this),
|
||||
m_disassembler(HostDisassembler::Factory(HostDisassembler::Platform::x86_64))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -308,6 +313,18 @@ void Jit64::ClearCache()
|
||||
RefreshConfig();
|
||||
asm_routines.Regenerate();
|
||||
ResetFreeMemoryRanges();
|
||||
Host_JitCacheCleared();
|
||||
}
|
||||
|
||||
void Jit64::FreeRanges()
|
||||
{
|
||||
// Check if any code blocks have been freed in the block cache and transfer this information to
|
||||
// the local rangesets to allow overwriting them with new code.
|
||||
for (const auto& [from, to] : blocks.GetRangesToFreeNear())
|
||||
m_free_ranges_near.insert(from, to);
|
||||
for (const auto& [from, to] : blocks.GetRangesToFreeFar())
|
||||
m_free_ranges_far.insert(from, to);
|
||||
blocks.ClearRangesToFree();
|
||||
}
|
||||
|
||||
void Jit64::ResetFreeMemoryRanges()
|
||||
@@ -746,14 +763,7 @@ void Jit64::Jit(u32 em_address, bool clear_cache_and_retry_on_failure)
|
||||
}
|
||||
ClearCache();
|
||||
}
|
||||
|
||||
// Check if any code blocks have been freed in the block cache and transfer this information to
|
||||
// the local rangesets to allow overwriting them with new code.
|
||||
for (auto range : blocks.GetRangesToFreeNear())
|
||||
m_free_ranges_near.insert(range.first, range.second);
|
||||
for (auto range : blocks.GetRangesToFreeFar())
|
||||
m_free_ranges_far.insert(range.first, range.second);
|
||||
blocks.ClearRangesToFree();
|
||||
FreeRanges();
|
||||
|
||||
std::size_t block_size = m_code_buffer.size();
|
||||
|
||||
@@ -822,7 +832,11 @@ void Jit64::Jit(u32 em_address, bool clear_cache_and_retry_on_failure)
|
||||
b->far_begin = far_start;
|
||||
b->far_end = far_end;
|
||||
|
||||
blocks.FinalizeBlock(*b, jo.enableBlocklink, code_block.m_physical_addresses);
|
||||
blocks.FinalizeBlock(*b, jo.enableBlocklink, code_block, m_code_buffer);
|
||||
|
||||
#ifdef JIT_LOG_GENERATED_CODE
|
||||
LogGeneratedCode();
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1178,6 +1192,12 @@ bool Jit64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
|
||||
WriteExit(nextPC);
|
||||
}
|
||||
|
||||
// When linking to an entry point immediately following it in memory, a JIT block's furthest
|
||||
// exit can, as a micro-optimization, overwrite the JMP instruction with a multibyte NOP.
|
||||
// See: 'JitBlockCache::WriteLinkBlock'
|
||||
// In order to do this in a non-sketchy way, a JIT block must own the alignment padding bytes.
|
||||
AlignCode4(); // TODO: Test if this or AlignCode16 make a difference from GetCodePtr
|
||||
|
||||
if (HasWriteFailed() || m_far_code.HasWriteFailed())
|
||||
{
|
||||
if (HasWriteFailed())
|
||||
@@ -1188,16 +1208,30 @@ bool Jit64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
|
||||
return false;
|
||||
}
|
||||
|
||||
b->codeSize = static_cast<u32>(GetCodePtr() - b->normalEntry);
|
||||
b->originalSize = code_block.m_num_instructions;
|
||||
|
||||
#ifdef JIT_LOG_GENERATED_CODE
|
||||
LogGeneratedX86(code_block.m_num_instructions, m_code_buffer, start, b);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Jit64::EraseSingleBlock(const JitBlock& block)
|
||||
{
|
||||
blocks.EraseSingleBlock(block);
|
||||
FreeRanges();
|
||||
}
|
||||
|
||||
std::vector<JitBase::MemoryStats> Jit64::GetMemoryStats() const
|
||||
{
|
||||
return {{"near", m_free_ranges_near.get_stats()}, {"far", m_free_ranges_far.get_stats()}};
|
||||
}
|
||||
|
||||
std::size_t Jit64::DisassembleNearCode(const JitBlock& block, std::ostream& stream) const
|
||||
{
|
||||
return m_disassembler->Disassemble(block.normalEntry, block.near_end, stream);
|
||||
}
|
||||
|
||||
std::size_t Jit64::DisassembleFarCode(const JitBlock& block, std::ostream& stream) const
|
||||
{
|
||||
return m_disassembler->Disassemble(block.far_begin, block.far_end, stream);
|
||||
}
|
||||
|
||||
BitSet8 Jit64::ComputeStaticGQRs(const PPCAnalyst::CodeBlock& cb) const
|
||||
{
|
||||
return cb.m_gqr_used & ~cb.m_gqr_modified;
|
||||
@@ -1277,39 +1311,24 @@ bool Jit64::HandleFunctionHooking(u32 address)
|
||||
return true;
|
||||
}
|
||||
|
||||
void LogGeneratedX86(size_t size, const PPCAnalyst::CodeBuffer& code_buffer, const u8* normalEntry,
|
||||
const JitBlock* b)
|
||||
void Jit64::LogGeneratedCode() const
|
||||
{
|
||||
for (size_t i = 0; i < size; i++)
|
||||
std::ostringstream stream;
|
||||
|
||||
stream << "\nPPC Code Buffer:\n";
|
||||
for (const PPCAnalyst::CodeOp& op :
|
||||
std::span{m_code_buffer.data(), code_block.m_num_instructions})
|
||||
{
|
||||
const PPCAnalyst::CodeOp& op = code_buffer[i];
|
||||
const std::string disasm = Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address);
|
||||
DEBUG_LOG_FMT(DYNA_REC, "IR_X86 PPC: {:08x} {}\n", op.address, disasm);
|
||||
fmt::print(stream, "0x{:08x}\t\t{}\n", op.address,
|
||||
Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address));
|
||||
}
|
||||
|
||||
disassembler x64disasm;
|
||||
x64disasm.set_syntax_intel();
|
||||
const JitBlock* const block = js.curBlock;
|
||||
stream << "\nHost Near Code:\n";
|
||||
m_disassembler->Disassemble(block->normalEntry, block->near_end, stream);
|
||||
stream << "\nHost Far Code:\n";
|
||||
m_disassembler->Disassemble(block->far_begin, block->far_end, stream);
|
||||
|
||||
u64 disasmPtr = reinterpret_cast<u64>(normalEntry);
|
||||
const u8* end = normalEntry + b->codeSize;
|
||||
|
||||
while (reinterpret_cast<u8*>(disasmPtr) < end)
|
||||
{
|
||||
char sptr[1000] = "";
|
||||
disasmPtr += x64disasm.disasm64(disasmPtr, disasmPtr, reinterpret_cast<u8*>(disasmPtr), sptr);
|
||||
DEBUG_LOG_FMT(DYNA_REC, "IR_X86 x86: {}", sptr);
|
||||
}
|
||||
|
||||
if (b->codeSize <= 250)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << std::hex;
|
||||
for (u8 i = 0; i <= b->codeSize; i++)
|
||||
{
|
||||
ss.width(2);
|
||||
ss.fill('0');
|
||||
ss << static_cast<u32>(*(normalEntry + i));
|
||||
}
|
||||
DEBUG_LOG_FMT(DYNA_REC, "IR_X86 bin: {}\n\n\n", ss.str());
|
||||
}
|
||||
// TODO C++20: std::ostringstream::view()
|
||||
DEBUG_LOG_FMT(DYNA_REC, "{}", std::move(stream).str());
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "Core/PowerPC/JitCommon/JitBase.h"
|
||||
#include "Core/PowerPC/JitCommon/JitCache.h"
|
||||
|
||||
class HostDisassembler;
|
||||
namespace PPCAnalyst
|
||||
{
|
||||
struct CodeBlock;
|
||||
@@ -65,6 +66,12 @@ public:
|
||||
void Jit(u32 em_address, bool clear_cache_and_retry_on_failure);
|
||||
bool DoJit(u32 em_address, JitBlock* b, u32 nextPC);
|
||||
|
||||
void EraseSingleBlock(const JitBlock& block) override;
|
||||
std::vector<MemoryStats> GetMemoryStats() const override;
|
||||
|
||||
std::size_t DisassembleNearCode(const JitBlock& block, std::ostream& stream) const override;
|
||||
std::size_t DisassembleFarCode(const JitBlock& block, std::ostream& stream) const override;
|
||||
|
||||
// Finds a free memory region and sets the near and far code emitters to point at that region.
|
||||
// Returns false if no free memory region can be found for either of the two.
|
||||
bool SetEmitterStateToFreeCodeRegion();
|
||||
@@ -266,8 +273,11 @@ private:
|
||||
|
||||
bool HandleFunctionHooking(u32 address);
|
||||
|
||||
void FreeRanges();
|
||||
void ResetFreeMemoryRanges();
|
||||
|
||||
void LogGeneratedCode() const;
|
||||
|
||||
static void ImHere(Jit64& jit);
|
||||
|
||||
JitBlockCache blocks{*this};
|
||||
@@ -284,7 +294,5 @@ private:
|
||||
const bool m_im_here_debug = false;
|
||||
const bool m_im_here_log = false;
|
||||
std::map<u32, int> m_been_here;
|
||||
std::unique_ptr<HostDisassembler> m_disassembler;
|
||||
};
|
||||
|
||||
void LogGeneratedX86(size_t size, const PPCAnalyst::CodeBuffer& code_buffer, const u8* normalEntry,
|
||||
const JitBlock* b);
|
||||
|
||||
@@ -5,10 +5,17 @@
|
||||
|
||||
#include <cstdio>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
#include "Common/Arm64Emitter.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/EnumUtils.h"
|
||||
#include "Common/GekkoDisassembler.h"
|
||||
#include "Common/HostDisassembler.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
@@ -22,6 +29,7 @@
|
||||
#include "Core/HW/GPFifo.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/HW/ProcessorInterface.h"
|
||||
#include "Core/Host.h"
|
||||
#include "Core/PatchEngine.h"
|
||||
#include "Core/PowerPC/Interpreter/Interpreter.h"
|
||||
#include "Core/PowerPC/JitArm64/JitArm64_RegCache.h"
|
||||
@@ -39,7 +47,9 @@ constexpr size_t NEAR_CODE_SIZE = 1024 * 1024 * 64;
|
||||
constexpr size_t FAR_CODE_SIZE = 1024 * 1024 * 64;
|
||||
constexpr size_t TOTAL_CODE_SIZE = NEAR_CODE_SIZE * 2 + FAR_CODE_SIZE * 2;
|
||||
|
||||
JitArm64::JitArm64(Core::System& system) : JitBase(system), m_float_emit(this)
|
||||
JitArm64::JitArm64(Core::System& system)
|
||||
: JitBase(system), m_float_emit(this),
|
||||
m_disassembler(HostDisassembler::Factory(HostDisassembler::Platform::aarch64))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -186,6 +196,36 @@ void JitArm64::GenerateAsmAndResetFreeMemoryRanges()
|
||||
|
||||
ResetFreeMemoryRanges(routines_near_end - routines_near_start,
|
||||
routines_far_end - routines_far_start);
|
||||
|
||||
Host_JitCacheCleared();
|
||||
}
|
||||
|
||||
void JitArm64::FreeRanges()
|
||||
{
|
||||
// Check if any code blocks have been freed in the block cache and transfer this information to
|
||||
// the local rangesets to allow overwriting them with new code.
|
||||
for (const auto& [from, to] : blocks.GetRangesToFreeNear())
|
||||
{
|
||||
const auto first_fastmem_area = m_fault_to_handler.upper_bound(from);
|
||||
auto last_fastmem_area = first_fastmem_area;
|
||||
const auto end = m_fault_to_handler.end();
|
||||
while (last_fastmem_area != end && last_fastmem_area->first <= to)
|
||||
++last_fastmem_area;
|
||||
m_fault_to_handler.erase(first_fastmem_area, last_fastmem_area);
|
||||
|
||||
if (from < m_near_code_0.GetCodeEnd())
|
||||
m_free_ranges_near_0.insert(from, to);
|
||||
else
|
||||
m_free_ranges_near_1.insert(from, to);
|
||||
}
|
||||
for (const auto& [from, to] : blocks.GetRangesToFreeFar())
|
||||
{
|
||||
if (from < m_far_code_0.GetCodeEnd())
|
||||
m_free_ranges_far_0.insert(from, to);
|
||||
else
|
||||
m_free_ranges_far_1.insert(from, to);
|
||||
}
|
||||
blocks.ClearRangesToFree();
|
||||
}
|
||||
|
||||
void JitArm64::ResetFreeMemoryRanges(size_t routines_near_size, size_t routines_far_size)
|
||||
@@ -911,31 +951,7 @@ void JitArm64::Jit(u32 em_address, bool clear_cache_and_retry_on_failure)
|
||||
|
||||
if (SConfig::GetInstance().bJITNoBlockCache)
|
||||
ClearCache();
|
||||
|
||||
// Check if any code blocks have been freed in the block cache and transfer this information to
|
||||
// the local rangesets to allow overwriting them with new code.
|
||||
for (auto range : blocks.GetRangesToFreeNear())
|
||||
{
|
||||
auto first_fastmem_area = m_fault_to_handler.upper_bound(range.first);
|
||||
auto last_fastmem_area = first_fastmem_area;
|
||||
auto end = m_fault_to_handler.end();
|
||||
while (last_fastmem_area != end && last_fastmem_area->first <= range.second)
|
||||
++last_fastmem_area;
|
||||
m_fault_to_handler.erase(first_fastmem_area, last_fastmem_area);
|
||||
|
||||
if (range.first < m_near_code_0.GetCodeEnd())
|
||||
m_free_ranges_near_0.insert(range.first, range.second);
|
||||
else
|
||||
m_free_ranges_near_1.insert(range.first, range.second);
|
||||
}
|
||||
for (auto range : blocks.GetRangesToFreeFar())
|
||||
{
|
||||
if (range.first < m_far_code_0.GetCodeEnd())
|
||||
m_free_ranges_far_0.insert(range.first, range.second);
|
||||
else
|
||||
m_free_ranges_far_1.insert(range.first, range.second);
|
||||
}
|
||||
blocks.ClearRangesToFree();
|
||||
FreeRanges();
|
||||
|
||||
const Common::ScopedJITPageWriteAndNoExecute enable_jit_page_writes;
|
||||
|
||||
@@ -1010,7 +1026,11 @@ void JitArm64::Jit(u32 em_address, bool clear_cache_and_retry_on_failure)
|
||||
b->far_begin = far_start;
|
||||
b->far_end = far_end;
|
||||
|
||||
blocks.FinalizeBlock(*b, jo.enableBlocklink, code_block.m_physical_addresses);
|
||||
blocks.FinalizeBlock(*b, jo.enableBlocklink, code_block, m_code_buffer);
|
||||
|
||||
#ifdef JIT_LOG_GENERATED_CODE
|
||||
LogGeneratedCode();
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1030,6 +1050,30 @@ void JitArm64::Jit(u32 em_address, bool clear_cache_and_retry_on_failure)
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
void JitArm64::EraseSingleBlock(const JitBlock& block)
|
||||
{
|
||||
blocks.EraseSingleBlock(block);
|
||||
FreeRanges();
|
||||
}
|
||||
|
||||
std::vector<JitBase::MemoryStats> JitArm64::GetMemoryStats() const
|
||||
{
|
||||
return {{"near_0", m_free_ranges_near_0.get_stats()},
|
||||
{"near_1", m_free_ranges_near_1.get_stats()},
|
||||
{"far_0", m_free_ranges_far_0.get_stats()},
|
||||
{"far_1", m_free_ranges_far_1.get_stats()}};
|
||||
}
|
||||
|
||||
std::size_t JitArm64::DisassembleNearCode(const JitBlock& block, std::ostream& stream) const
|
||||
{
|
||||
return m_disassembler->Disassemble(block.normalEntry, block.near_end, stream);
|
||||
}
|
||||
|
||||
std::size_t JitArm64::DisassembleFarCode(const JitBlock& block, std::ostream& stream) const
|
||||
{
|
||||
return m_disassembler->Disassemble(block.far_begin, block.far_end, stream);
|
||||
}
|
||||
|
||||
std::optional<size_t> JitArm64::SetEmitterStateToFreeCodeRegion()
|
||||
{
|
||||
// Find some large free memory blocks and set code emitters to point at them. If we can't find
|
||||
@@ -1347,11 +1391,30 @@ bool JitArm64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
|
||||
return false;
|
||||
}
|
||||
|
||||
b->codeSize = static_cast<u32>(GetCodePtr() - b->normalEntry);
|
||||
b->originalSize = code_block.m_num_instructions;
|
||||
|
||||
FlushIcache();
|
||||
m_far_code.FlushIcache();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void JitArm64::LogGeneratedCode() const
|
||||
{
|
||||
std::ostringstream stream;
|
||||
|
||||
stream << "\nPPC Code Buffer:\n";
|
||||
for (const PPCAnalyst::CodeOp& op :
|
||||
std::span{m_code_buffer.data(), code_block.m_num_instructions})
|
||||
{
|
||||
fmt::print(stream, "0x{:08x}\t\t{}\n", op.address,
|
||||
Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address));
|
||||
}
|
||||
|
||||
const JitBlock* const block = js.curBlock;
|
||||
stream << "\nHost Near Code:\n";
|
||||
m_disassembler->Disassemble(block->normalEntry, block->near_end, stream);
|
||||
stream << "\nHost Far Code:\n";
|
||||
m_disassembler->Disassemble(block->far_begin, block->far_end, stream);
|
||||
|
||||
// TODO C++20: std::ostringstream::view()
|
||||
DEBUG_LOG_FMT(DYNA_REC, "{}", std::move(stream).str());
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user