Merge pull request #12714 from mitaclaw/jit-widget-refresh

DolphinQt: JIT Widget Refresh
This commit is contained in:
JMC47
2024-10-21 12:03:50 -04:00
committed by GitHub
51 changed files with 2279 additions and 611 deletions
+25
View File
@@ -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.
+11
View File
@@ -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
*/
@@ -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>
+24
View File
@@ -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)
{
+29
View File
@@ -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)
+158
View File
@@ -0,0 +1,158 @@
// Copyright 2008 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Common/HostDisassembler.h"
#include <span>
#include <fmt/format.h>
#include <fmt/ostream.h>
#include <fmt/ranges.h>
#if defined(HAVE_LLVM)
#include <llvm-c/Disassembler.h>
#include <llvm-c/Target.h>
#elif defined(_M_X86_64)
#include <disasm.h> // Bochs
#endif
#if defined(HAVE_LLVM)
class HostDisassemblerLLVM final : public HostDisassembler
{
public:
explicit HostDisassemblerLLVM(const char* host_disasm, const char* cpu = "",
std::size_t inst_size = 0);
~HostDisassemblerLLVM();
private:
LLVMDisasmContextRef m_llvm_context;
std::size_t m_instruction_size;
std::size_t Disassemble(const u8* begin, const u8* end, std::ostream& stream) override;
};
HostDisassemblerLLVM::HostDisassemblerLLVM(const char* host_disasm, const char* cpu,
std::size_t inst_size)
: m_instruction_size(inst_size)
{
LLVMInitializeAllTargetInfos();
LLVMInitializeAllTargetMCs();
LLVMInitializeAllDisassemblers();
m_llvm_context = LLVMCreateDisasmCPU(host_disasm, cpu, nullptr, 0, nullptr, nullptr);
// Couldn't create llvm context
if (!m_llvm_context)
return;
LLVMSetDisasmOptions(m_llvm_context, LLVMDisassembler_Option_AsmPrinterVariant |
LLVMDisassembler_Option_PrintLatency);
}
HostDisassemblerLLVM::~HostDisassemblerLLVM()
{
if (m_llvm_context)
LLVMDisasmDispose(m_llvm_context);
}
std::size_t HostDisassemblerLLVM::Disassemble(const u8* begin, const u8* end, std::ostream& stream)
{
std::size_t instruction_count = 0;
if (!m_llvm_context)
return instruction_count;
while (begin < end)
{
char inst_disasm[256];
const auto inst_size =
LLVMDisasmInstruction(m_llvm_context, const_cast<u8*>(begin), static_cast<u64>(end - begin),
reinterpret_cast<u64>(begin), inst_disasm, sizeof(inst_disasm));
if (inst_size == 0)
{
if (m_instruction_size != 0)
{
// If we are on an architecture that has a fixed instruction
// size, we can continue onward past this bad instruction.
fmt::println(stream, "{}\tInvalid inst: {:02x}", fmt::ptr(begin),
fmt::join(std::span{begin, m_instruction_size}, ""));
begin += m_instruction_size;
}
else
{
// We can't continue if we are on an architecture that has flexible
// instruction sizes. Dump the rest of the block instead.
fmt::println(stream, "{}\tInvalid inst: {:02x}", fmt::ptr(begin),
fmt::join(std::span{begin, end}, ""));
break;
}
}
else
{
fmt::println(stream, "{}{}", fmt::ptr(begin), inst_disasm);
begin += inst_size;
}
++instruction_count;
}
return instruction_count;
}
#elif defined(_M_X86_64)
class HostDisassemblerBochs final : public HostDisassembler
{
public:
explicit HostDisassemblerBochs();
~HostDisassemblerBochs() = default;
private:
disassembler m_disasm;
std::size_t Disassemble(const u8* begin, const u8* end, std::ostream& stream) override;
};
HostDisassemblerBochs::HostDisassemblerBochs()
{
m_disasm.set_syntax_intel();
}
std::size_t HostDisassemblerBochs::Disassemble(const u8* begin, const u8* end, std::ostream& stream)
{
std::size_t instruction_count = 0;
while (begin < end)
{
char inst_disasm[256];
const auto inst_size =
m_disasm.disasm64(0, reinterpret_cast<bx_address>(begin), begin, inst_disasm);
fmt::println(stream, "{}\t{}", fmt::ptr(begin), inst_disasm);
begin += inst_size;
++instruction_count;
}
return instruction_count;
}
#endif
std::unique_ptr<HostDisassembler> HostDisassembler::Factory(Platform arch)
{
switch (arch)
{
#if defined(HAVE_LLVM)
case Platform::x86_64:
return std::make_unique<HostDisassemblerLLVM>("x86_64-none-unknown");
case Platform::aarch64:
return std::make_unique<HostDisassemblerLLVM>("aarch64-none-unknown", "cortex-a57", 4);
#elif defined(_M_X86_64)
case Platform::x86_64:
return std::make_unique<HostDisassemblerBochs>();
#else
case Platform{}: // warning C4065: "switch statement contains 'default' but no 'case' labels"
#endif
default:
return std::make_unique<HostDisassembler>();
}
}
std::size_t HostDisassembler::Disassemble(const u8* begin, const u8* end, std::ostream& stream)
{
fmt::println(stream, "{}\t{:02x}", fmt::ptr(begin), fmt::join(std::span{begin, end}, ""));
return 0;
}
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2008 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstddef>
#include <iosfwd>
#include <memory>
#include "Common/CommonTypes.h"
class HostDisassembler
{
public:
enum class Platform
{
x86_64,
aarch64,
};
virtual ~HostDisassembler() = default;
static std::unique_ptr<HostDisassembler> Factory(Platform arch);
virtual std::size_t Disassemble(const u8* begin, const u8* end, std::ostream& stream);
};
+1 -5
View File
@@ -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
+5
View File
@@ -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;
+1
View File
@@ -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);
+2
View File
@@ -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;
}
+67 -48
View File
@@ -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());
}
+11 -3
View File
@@ -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);
+93 -30
View File
@@ -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