From 67f60bec7e59dbb24930b23246fa44aa089c9861 Mon Sep 17 00:00:00 2001 From: mitaclaw <140017135+mitaclaw@users.noreply.github.com> Date: Thu, 7 Dec 2023 09:18:38 -0800 Subject: [PATCH] PowerPC: Implement BranchWatch This new component can track code paths by watching branch hits. --- Source/Core/Core/CMakeLists.txt | 2 + Source/Core/Core/Debugger/BranchWatch.cpp | 314 ++++++++++++++++++ Source/Core/Core/Debugger/BranchWatch.h | 278 ++++++++++++++++ .../Core/PowerPC/Interpreter/Interpreter.cpp | 5 +- .../Core/PowerPC/Interpreter/Interpreter.h | 7 +- .../Core/Core/PowerPC/JitCommon/JitBase.cpp | 2 +- Source/Core/Core/PowerPC/JitCommon/JitBase.h | 4 +- Source/Core/Core/PowerPC/PowerPC.h | 4 + Source/Core/Core/System.cpp | 4 +- Source/Core/DolphinLib.props | 2 + 10 files changed, 614 insertions(+), 8 deletions(-) create mode 100644 Source/Core/Core/Debugger/BranchWatch.cpp create mode 100644 Source/Core/Core/Debugger/BranchWatch.h diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index 1dcbc14ce7..290947e722 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -61,6 +61,8 @@ add_library(core CoreTiming.h CPUThreadConfigCallback.cpp CPUThreadConfigCallback.h + Debugger/BranchWatch.cpp + Debugger/BranchWatch.h Debugger/CodeTrace.cpp Debugger/CodeTrace.h Debugger/DebugInterface.h diff --git a/Source/Core/Core/Debugger/BranchWatch.cpp b/Source/Core/Core/Debugger/BranchWatch.cpp new file mode 100644 index 0000000000..cefc38ddb2 --- /dev/null +++ b/Source/Core/Core/Debugger/BranchWatch.cpp @@ -0,0 +1,314 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/Debugger/BranchWatch.h" + +#include +#include +#include + +#include + +#include "Common/Assert.h" +#include "Common/BitField.h" +#include "Common/CommonTypes.h" +#include "Core/Core.h" +#include "Core/PowerPC/Gekko.h" +#include "Core/PowerPC/MMU.h" + +namespace Core +{ +void BranchWatch::Clear(const CPUThreadGuard&) +{ + m_selection.clear(); + m_collection_vt.clear(); + m_collection_vf.clear(); + m_collection_pt.clear(); + m_collection_pf.clear(); + m_recording_phase = Phase::Blacklist; + m_blacklist_size = 0; +} + +// This is a bitfield aggregate of metadata required to reconstruct a BranchWatch's Collections and +// Selection from a text file (a snapshot). For maximum forward compatibility, should that ever be +// required, the StorageType is an unsigned long long instead of something more reasonable like an +// unsigned int or u8. This is because the snapshot text file format contains no version info. +union USnapshotMetadata +{ + using Inspection = BranchWatch::SelectionInspection; + using StorageType = unsigned long long; + + static_assert(Inspection::EndOfEnumeration == Inspection{(1u << 3) + 1}); + + StorageType hex; + + BitField<0, 1, bool, StorageType> is_virtual; + BitField<1, 1, bool, StorageType> condition; + BitField<2, 1, bool, StorageType> is_selected; + BitField<3, 4, Inspection, StorageType> inspection; + + USnapshotMetadata() : hex(0) {} + explicit USnapshotMetadata(bool is_virtual_, bool condition_, bool is_selected_, + Inspection inspection_) + : USnapshotMetadata() + { + is_virtual = is_virtual_; + condition = condition_; + is_selected = is_selected_; + inspection = inspection_; + } +}; + +void BranchWatch::Save(const CPUThreadGuard& guard, std::FILE* file) const +{ + if (!CanSave()) + { + ASSERT_MSG(CORE, false, "BranchWatch can not be saved."); + return; + } + if (file == nullptr) + return; + + const auto routine = [&](const Collection& collection, bool is_virtual, bool condition) { + for (const Collection::value_type& kv : collection) + { + const auto iter = std::find_if( + m_selection.begin(), m_selection.end(), + [&](const Selection::value_type& value) { return value.collection_ptr == &kv; }); + fmt::println(file, "{:08x} {:08x} {:08x} {} {} {:x}", kv.first.origin_addr, + kv.first.destin_addr, kv.first.original_inst.hex, kv.second.total_hits, + kv.second.hits_snapshot, + iter == m_selection.end() ? + USnapshotMetadata(is_virtual, condition, false, {}).hex : + USnapshotMetadata(is_virtual, condition, true, iter->inspection).hex); + } + }; + routine(m_collection_vt, true, true); + routine(m_collection_pt, false, true); + routine(m_collection_vf, true, false); + routine(m_collection_pf, false, false); +} + +void BranchWatch::Load(const CPUThreadGuard& guard, std::FILE* file) +{ + if (file == nullptr) + return; + + Clear(guard); + + u32 origin_addr, destin_addr, inst_hex; + std::size_t total_hits, hits_snapshot; + USnapshotMetadata snapshot_metadata = {}; + while (std::fscanf(file, "%x %x %x %zu %zu %llx", &origin_addr, &destin_addr, &inst_hex, + &total_hits, &hits_snapshot, &snapshot_metadata.hex) == 6) + { + const bool is_virtual = snapshot_metadata.is_virtual; + const bool condition = snapshot_metadata.condition; + + const auto [kv_iter, emplace_success] = + GetCollection(is_virtual, condition) + .try_emplace({{origin_addr, destin_addr}, inst_hex}, + BranchWatchCollectionValue{total_hits, hits_snapshot}); + + if (!emplace_success) + continue; + + if (snapshot_metadata.is_selected) + { + // TODO C++20: Parenthesized initialization of aggregates has bad compiler support. + m_selection.emplace_back(BranchWatchSelectionValueType{&*kv_iter, is_virtual, condition, + snapshot_metadata.inspection}); + } + else if (hits_snapshot != 0) + { + ++m_blacklist_size; // This will be very wrong when not in Blacklist mode. That's ok. + } + } + + if (!m_selection.empty()) + m_recording_phase = Phase::Reduction; +} + +void BranchWatch::IsolateHasExecuted(const CPUThreadGuard&) +{ + switch (m_recording_phase) + { + case Phase::Blacklist: + { + m_selection.reserve(GetCollectionSize() - m_blacklist_size); + const auto routine = [&](Collection& collection, bool is_virtual, bool condition) { + for (Collection::value_type& kv : collection) + { + if (kv.second.hits_snapshot == 0) + { + // TODO C++20: Parenthesized initialization of aggregates has bad compiler support. + m_selection.emplace_back( + BranchWatchSelectionValueType{&kv, is_virtual, condition, SelectionInspection{}}); + kv.second.hits_snapshot = kv.second.total_hits; + } + } + }; + routine(m_collection_vt, true, true); + routine(m_collection_vf, true, false); + routine(m_collection_pt, false, true); + routine(m_collection_pf, false, false); + m_recording_phase = Phase::Reduction; + return; + } + case Phase::Reduction: + std::erase_if(m_selection, [](const Selection::value_type& value) -> bool { + Collection::value_type* const kv = value.collection_ptr; + if (kv->second.total_hits == kv->second.hits_snapshot) + return true; + kv->second.hits_snapshot = kv->second.total_hits; + return false; + }); + return; + } +} + +void BranchWatch::IsolateNotExecuted(const CPUThreadGuard&) +{ + switch (m_recording_phase) + { + case Phase::Blacklist: + { + const auto routine = [&](Collection& collection) { + for (Collection::value_type& kv : collection) + kv.second.hits_snapshot = kv.second.total_hits; + }; + routine(m_collection_vt); + routine(m_collection_vf); + routine(m_collection_pt); + routine(m_collection_pf); + m_blacklist_size = GetCollectionSize(); + return; + } + case Phase::Reduction: + std::erase_if(m_selection, [](const Selection::value_type& value) -> bool { + Collection::value_type* const kv = value.collection_ptr; + if (kv->second.total_hits != kv->second.hits_snapshot) + return true; + kv->second.hits_snapshot = kv->second.total_hits; + return false; + }); + return; + } +} + +void BranchWatch::IsolateWasOverwritten(const CPUThreadGuard& guard) +{ + if (Core::GetState() == Core::State::Uninitialized) + { + ASSERT_MSG(CORE, false, "Core is uninitialized."); + return; + } + switch (m_recording_phase) + { + case Phase::Blacklist: + { + // This is a dirty hack of the assumptions that make the blacklist phase work. If the + // hits_snapshot is non-zero while in the blacklist phase, that means it has been marked + // for exclusion from the transition to the reduction phase. + const auto routine = [&](Collection& collection, PowerPC::RequestedAddressSpace address_space) { + for (Collection::value_type& kv : collection) + { + if (kv.second.hits_snapshot == 0) + { + const std::optional read_result = + PowerPC::MMU::HostTryReadInstruction(guard, kv.first.origin_addr, address_space); + if (!read_result.has_value()) + continue; + if (kv.first.original_inst.hex == read_result->value) + kv.second.hits_snapshot = ++m_blacklist_size; // Any non-zero number will work. + } + } + }; + routine(m_collection_vt, PowerPC::RequestedAddressSpace::Virtual); + routine(m_collection_vf, PowerPC::RequestedAddressSpace::Virtual); + routine(m_collection_pt, PowerPC::RequestedAddressSpace::Physical); + routine(m_collection_pf, PowerPC::RequestedAddressSpace::Physical); + return; + } + case Phase::Reduction: + std::erase_if(m_selection, [&guard](const Selection::value_type& value) -> bool { + const std::optional read_result = PowerPC::MMU::HostTryReadInstruction( + guard, value.collection_ptr->first.origin_addr, + value.is_virtual ? PowerPC::RequestedAddressSpace::Virtual : + PowerPC::RequestedAddressSpace::Physical); + if (!read_result.has_value()) + return false; + return value.collection_ptr->first.original_inst.hex == read_result->value; + }); + return; + } +} + +void BranchWatch::IsolateNotOverwritten(const CPUThreadGuard& guard) +{ + if (Core::GetState() == Core::State::Uninitialized) + { + ASSERT_MSG(CORE, false, "Core is uninitialized."); + return; + } + switch (m_recording_phase) + { + case Phase::Blacklist: + { + // Same dirty hack with != rather than ==, see above for details + const auto routine = [&](Collection& collection, PowerPC::RequestedAddressSpace address_space) { + for (Collection::value_type& kv : collection) + if (kv.second.hits_snapshot == 0) + { + const std::optional read_result = + PowerPC::MMU::HostTryReadInstruction(guard, kv.first.origin_addr, address_space); + if (!read_result.has_value()) + continue; + if (kv.first.original_inst.hex != read_result->value) + kv.second.hits_snapshot = ++m_blacklist_size; // Any non-zero number will work. + } + }; + routine(m_collection_vt, PowerPC::RequestedAddressSpace::Virtual); + routine(m_collection_vf, PowerPC::RequestedAddressSpace::Virtual); + routine(m_collection_pt, PowerPC::RequestedAddressSpace::Physical); + routine(m_collection_pf, PowerPC::RequestedAddressSpace::Physical); + return; + } + case Phase::Reduction: + std::erase_if(m_selection, [&guard](const Selection::value_type& value) -> bool { + const std::optional read_result = PowerPC::MMU::HostTryReadInstruction( + guard, value.collection_ptr->first.origin_addr, + value.is_virtual ? PowerPC::RequestedAddressSpace::Virtual : + PowerPC::RequestedAddressSpace::Physical); + if (!read_result.has_value()) + return false; + return value.collection_ptr->first.original_inst.hex != read_result->value; + }); + return; + } +} + +void BranchWatch::UpdateHitsSnapshot() +{ + switch (m_recording_phase) + { + case Phase::Reduction: + for (Selection::value_type& value : m_selection) + value.collection_ptr->second.hits_snapshot = value.collection_ptr->second.total_hits; + return; + case Phase::Blacklist: + return; + } +} + +void BranchWatch::ClearSelectionInspection() +{ + std::for_each(m_selection.begin(), m_selection.end(), + [](Selection::value_type& value) { value.inspection = {}; }); +} + +void BranchWatch::SetSelectedInspected(std::size_t idx, SelectionInspection inspection) +{ + m_selection[idx].inspection |= inspection; +} +} // namespace Core diff --git a/Source/Core/Core/Debugger/BranchWatch.h b/Source/Core/Core/Debugger/BranchWatch.h new file mode 100644 index 0000000000..be4972bc91 --- /dev/null +++ b/Source/Core/Core/Debugger/BranchWatch.h @@ -0,0 +1,278 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "Common/BitUtils.h" +#include "Common/CommonTypes.h" +#include "Common/EnumUtils.h" +#include "Core/PowerPC/Gekko.h" + +namespace Core +{ +class CPUThreadGuard; +} + +namespace Core +{ +struct FakeBranchWatchCollectionKey +{ + u32 origin_addr; + u32 destin_addr; + + // TODO C++20: constexpr w/ std::bit_cast + inline operator u64() const { return Common::BitCast(*this); } +}; +struct BranchWatchCollectionKey : FakeBranchWatchCollectionKey +{ + UGeckoInstruction original_inst; +}; +struct BranchWatchCollectionValue +{ + std::size_t total_hits = 0; + std::size_t hits_snapshot = 0; +}; +} // namespace Core + +template <> +struct std::hash +{ + std::size_t operator()(const Core::BranchWatchCollectionKey& s) const noexcept + { + return std::hash{}(static_cast(s)); + } +}; + +namespace Core +{ +inline bool operator==(const BranchWatchCollectionKey& lhs, + const BranchWatchCollectionKey& rhs) noexcept +{ + const std::hash hash; + return hash(lhs) == hash(rhs) && lhs.original_inst.hex == rhs.original_inst.hex; +} + +enum class BranchWatchSelectionInspection : u8 +{ + SetOriginNOP = 1u << 0, + SetDestinBLR = 1u << 1, + SetOriginSymbolBLR = 1u << 2, + SetDestinSymbolBLR = 1u << 3, + EndOfEnumeration, +}; + +constexpr BranchWatchSelectionInspection operator|(BranchWatchSelectionInspection lhs, + BranchWatchSelectionInspection rhs) +{ + return static_cast(Common::ToUnderlying(lhs) | + Common::ToUnderlying(rhs)); +} + +constexpr BranchWatchSelectionInspection operator&(BranchWatchSelectionInspection lhs, + BranchWatchSelectionInspection rhs) +{ + return static_cast(Common::ToUnderlying(lhs) & + Common::ToUnderlying(rhs)); +} + +constexpr BranchWatchSelectionInspection& operator|=(BranchWatchSelectionInspection& self, + BranchWatchSelectionInspection other) +{ + return self = self | other; +} + +using BranchWatchCollection = + std::unordered_map; + +struct BranchWatchSelectionValueType +{ + using Inspection = BranchWatchSelectionInspection; + + BranchWatchCollection::value_type* collection_ptr; + bool is_virtual; + bool condition; + // This is moreso a GUI thing, but it works best in the Core code for multiple reasons. + Inspection inspection; +}; + +using BranchWatchSelection = std::vector; + +enum class BranchWatchPhase : bool +{ + Blacklist, + Reduction, +}; + +class BranchWatch final // Class is final to enforce the safety of GetOffsetOfRecordingActive(). +{ +public: + using Collection = BranchWatchCollection; + using Selection = BranchWatchSelection; + using Phase = BranchWatchPhase; + using SelectionInspection = BranchWatchSelectionInspection; + + bool GetRecordingActive() const { return m_recording_active; } + void SetRecordingActive(bool active) { m_recording_active = active; } + void Start() { SetRecordingActive(true); } + void Pause() { SetRecordingActive(false); } + void Clear(const CPUThreadGuard& guard); + + void Save(const CPUThreadGuard& guard, std::FILE* file) const; + void Load(const CPUThreadGuard& guard, std::FILE* file); + + void IsolateHasExecuted(const CPUThreadGuard& guard); + void IsolateNotExecuted(const CPUThreadGuard& guard); + void IsolateWasOverwritten(const CPUThreadGuard& guard); + void IsolateNotOverwritten(const CPUThreadGuard& guard); + void UpdateHitsSnapshot(); + void ClearSelectionInspection(); + void SetSelectedInspected(std::size_t idx, SelectionInspection inspection); + + Selection& GetSelection() { return m_selection; } + const Selection& GetSelection() const { return m_selection; } + + std::size_t GetCollectionSize() const + { + return m_collection_vt.size() + m_collection_vf.size() + m_collection_pt.size() + + m_collection_pf.size(); + } + std::size_t GetBlacklistSize() const { return m_blacklist_size; } + Phase GetRecordingPhase() const { return m_recording_phase; }; + + // An empty selection in reduction mode can't be reconstructed when loading from a file. + bool CanSave() const { return !(m_recording_phase == Phase::Reduction && m_selection.empty()); } + + // All Hit member functions are for the CPUThread only. The static ones are static to remain + // compatible with the JITs' ABI_CallFunction function, which doesn't support non-static member + // functions. HitXX_fk are optimized for when origin and destination can be passed in one register + // easily as a Core::FakeBranchWatchCollectionKey (abbreviated as "fk"). HitXX_fk_n are the same, + // but also increment the total_hits by N (see dcbx JIT code). + static void HitVirtualTrue_fk(BranchWatch* branch_watch, u64 fake_key, u32 inst) + { + branch_watch->m_collection_vt[{Common::BitCast(fake_key), inst}] + .total_hits += 1; + } + + static void HitPhysicalTrue_fk(BranchWatch* branch_watch, u64 fake_key, u32 inst) + { + branch_watch->m_collection_pt[{Common::BitCast(fake_key), inst}] + .total_hits += 1; + } + + static void HitVirtualFalse_fk(BranchWatch* branch_watch, u64 fake_key, u32 inst) + { + branch_watch->m_collection_vf[{Common::BitCast(fake_key), inst}] + .total_hits += 1; + } + + static void HitPhysicalFalse_fk(BranchWatch* branch_watch, u64 fake_key, u32 inst) + { + branch_watch->m_collection_pf[{Common::BitCast(fake_key), inst}] + .total_hits += 1; + } + + static void HitVirtualTrue_fk_n(BranchWatch* branch_watch, u64 fake_key, u32 inst, u32 n) + { + branch_watch->m_collection_vt[{Common::BitCast(fake_key), inst}] + .total_hits += n; + } + + static void HitPhysicalTrue_fk_n(BranchWatch* branch_watch, u64 fake_key, u32 inst, u32 n) + { + branch_watch->m_collection_pt[{Common::BitCast(fake_key), inst}] + .total_hits += n; + } + + // HitVirtualFalse_fk_n and HitPhysicalFalse_fk_n are never used, so they are omitted here. + + static void HitVirtualTrue(BranchWatch* branch_watch, u32 origin, u32 destination, u32 inst) + { + HitVirtualTrue_fk(branch_watch, FakeBranchWatchCollectionKey{origin, destination}, inst); + } + + static void HitPhysicalTrue(BranchWatch* branch_watch, u32 origin, u32 destination, u32 inst) + { + HitPhysicalTrue_fk(branch_watch, FakeBranchWatchCollectionKey{origin, destination}, inst); + } + + static void HitVirtualFalse(BranchWatch* branch_watch, u32 origin, u32 destination, u32 inst) + { + HitVirtualFalse_fk(branch_watch, FakeBranchWatchCollectionKey{origin, destination}, inst); + } + + static void HitPhysicalFalse(BranchWatch* branch_watch, u32 origin, u32 destination, u32 inst) + { + HitPhysicalFalse_fk(branch_watch, FakeBranchWatchCollectionKey{origin, destination}, inst); + } + + void HitTrue(u32 origin, u32 destination, UGeckoInstruction inst, bool translate) + { + if (translate) + HitVirtualTrue(this, origin, destination, inst.hex); + else + HitPhysicalTrue(this, origin, destination, inst.hex); + } + + void HitFalse(u32 origin, u32 destination, UGeckoInstruction inst, bool translate) + { + if (translate) + HitVirtualFalse(this, origin, destination, inst.hex); + else + HitPhysicalFalse(this, origin, destination, inst.hex); + } + + // The JIT needs this value, but doesn't need to be a full-on friend. + static constexpr int GetOffsetOfRecordingActive() + { +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#endif + return offsetof(BranchWatch, m_recording_active); +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + } + +private: + Collection& GetCollectionV(bool condition) + { + if (condition) + return m_collection_vt; + return m_collection_vf; + } + + Collection& GetCollectionP(bool condition) + { + if (condition) + return m_collection_pt; + return m_collection_pf; + } + + Collection& GetCollection(bool is_virtual, bool condition) + { + if (is_virtual) + return GetCollectionV(condition); + return GetCollectionP(condition); + } + + std::size_t m_blacklist_size = 0; + Phase m_recording_phase = Phase::Blacklist; + bool m_recording_active = false; + Collection m_collection_vt; // virtual address space | true path + Collection m_collection_vf; // virtual address space | false path + Collection m_collection_pt; // physical address space | true path + Collection m_collection_pf; // physical address space | false path + Selection m_selection; +}; + +#if _M_X86_64 +static_assert(BranchWatch::GetOffsetOfRecordingActive() < 0x80); // Makes JIT code smaller. +#endif +} // namespace Core diff --git a/Source/Core/Core/PowerPC/Interpreter/Interpreter.cpp b/Source/Core/Core/PowerPC/Interpreter/Interpreter.cpp index 71f01d13b7..fc5f1d2aba 100644 --- a/Source/Core/Core/PowerPC/Interpreter/Interpreter.cpp +++ b/Source/Core/Core/PowerPC/Interpreter/Interpreter.cpp @@ -64,8 +64,9 @@ void Interpreter::UpdatePC() m_ppc_state.pc = m_ppc_state.npc; } -Interpreter::Interpreter(Core::System& system, PowerPC::PowerPCState& ppc_state, PowerPC::MMU& mmu) - : m_system(system), m_ppc_state(ppc_state), m_mmu(mmu) +Interpreter::Interpreter(Core::System& system, PowerPC::PowerPCState& ppc_state, PowerPC::MMU& mmu, + Core::BranchWatch& branch_watch) + : m_system(system), m_ppc_state(ppc_state), m_mmu(mmu), m_branch_watch(branch_watch) { } diff --git a/Source/Core/Core/PowerPC/Interpreter/Interpreter.h b/Source/Core/Core/PowerPC/Interpreter/Interpreter.h index 90595de068..d51ad1a2dd 100644 --- a/Source/Core/Core/PowerPC/Interpreter/Interpreter.h +++ b/Source/Core/Core/PowerPC/Interpreter/Interpreter.h @@ -11,8 +11,9 @@ namespace Core { +class BranchWatch; class System; -} +} // namespace Core namespace PowerPC { class MMU; @@ -22,7 +23,8 @@ struct PowerPCState; class Interpreter : public CPUCoreBase { public: - Interpreter(Core::System& system, PowerPC::PowerPCState& ppc_state, PowerPC::MMU& mmu); + Interpreter(Core::System& system, PowerPC::PowerPCState& ppc_state, PowerPC::MMU& mmu, + Core::BranchWatch& branch_watch); Interpreter(const Interpreter&) = delete; Interpreter(Interpreter&&) = delete; Interpreter& operator=(const Interpreter&) = delete; @@ -314,6 +316,7 @@ private: Core::System& m_system; PowerPC::PowerPCState& m_ppc_state; PowerPC::MMU& m_mmu; + Core::BranchWatch& m_branch_watch; UGeckoInstruction m_prev_inst{}; u32 m_last_pc = 0; diff --git a/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp b/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp index c1cce80401..87f017f7f8 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp +++ b/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp @@ -94,7 +94,7 @@ void JitTrampoline(JitBase& jit, u32 em_address) JitBase::JitBase(Core::System& system) : m_code_buffer(code_buffer_size), m_system(system), m_ppc_state(system.GetPPCState()), - m_mmu(system.GetMMU()) + m_mmu(system.GetMMU()), m_branch_watch(system.GetPowerPC().GetBranchWatch()) { m_registered_config_callback_id = CPUThreadConfigCallback::AddConfigChangedCallback([this] { if (DoesConfigNeedRefresh()) diff --git a/Source/Core/Core/PowerPC/JitCommon/JitBase.h b/Source/Core/Core/PowerPC/JitCommon/JitBase.h index 00fea6ef84..1d947da5ea 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitBase.h +++ b/Source/Core/Core/PowerPC/JitCommon/JitBase.h @@ -23,8 +23,9 @@ namespace Core { +class BranchWatch; class System; -} +} // namespace Core namespace PowerPC { class MMU; @@ -206,6 +207,7 @@ public: Core::System& m_system; PowerPC::PowerPCState& m_ppc_state; PowerPC::MMU& m_mmu; + Core::BranchWatch& m_branch_watch; }; void JitTrampoline(JitBase& jit, u32 em_address); diff --git a/Source/Core/Core/PowerPC/PowerPC.h b/Source/Core/Core/PowerPC/PowerPC.h index c0611ae916..750f0c6f69 100644 --- a/Source/Core/Core/PowerPC/PowerPC.h +++ b/Source/Core/Core/PowerPC/PowerPC.h @@ -14,6 +14,7 @@ #include "Common/CommonTypes.h" #include "Core/CPUThreadConfigCallback.h" +#include "Core/Debugger/BranchWatch.h" #include "Core/Debugger/PPCDebugInterface.h" #include "Core/PowerPC/BreakPoints.h" #include "Core/PowerPC/ConditionRegister.h" @@ -298,6 +299,8 @@ public: const MemChecks& GetMemChecks() const { return m_memchecks; } PPCDebugInterface& GetDebugInterface() { return m_debug_interface; } const PPCDebugInterface& GetDebugInterface() const { return m_debug_interface; } + Core::BranchWatch& GetBranchWatch() { return m_branch_watch; } + const Core::BranchWatch& GetBranchWatch() const { return m_branch_watch; } private: void InitializeCPUCore(CPUCore cpu_core); @@ -314,6 +317,7 @@ private: BreakPoints m_breakpoints; MemChecks m_memchecks; PPCDebugInterface m_debug_interface; + Core::BranchWatch m_branch_watch; CPUThreadConfigCallback::ConfigChangedCallbackID m_registered_config_callback_id; diff --git a/Source/Core/Core/System.cpp b/Source/Core/Core/System.cpp index 9c97e6febe..2da55975de 100644 --- a/Source/Core/Core/System.cpp +++ b/Source/Core/Core/System.cpp @@ -52,8 +52,8 @@ struct System::Impl m_memory(system), m_pixel_engine{system}, m_power_pc(system), m_mmu(system, m_memory, m_power_pc), m_processor_interface(system), m_serial_interface(system), m_system_timers(system), m_video_interface(system), - m_interpreter(system, m_power_pc.GetPPCState(), m_mmu), m_jit_interface(system), - m_fifo_player(system), m_fifo_recorder(system), m_movie(system) + m_interpreter(system, m_power_pc.GetPPCState(), m_mmu, m_power_pc.GetBranchWatch()), + m_jit_interface(system), m_fifo_player(system), m_fifo_recorder(system), m_movie(system) { } diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 42ae7ba5e6..a5039859f9 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -202,6 +202,7 @@ + @@ -868,6 +869,7 @@ +