summaryrefslogtreecommitdiff
path: root/Source
diff options
context:
space:
mode:
authormitaclaw <140017135+mitaclaw@users.noreply.github.com>2023-12-07 09:18:38 -0800
committermitaclaw <140017135+mitaclaw@users.noreply.github.com>2024-02-26 19:38:27 -0800
commit67f60bec7e59dbb24930b23246fa44aa089c9861 (patch)
tree4717bf9925bcf97413b8a1b92a28a24bcc6ec702 /Source
parent5090a028e6e4125b780bcb1c76dcd8fe956a0b97 (diff)
PowerPC: Implement BranchWatch
This new component can track code paths by watching branch hits.
Diffstat (limited to 'Source')
-rw-r--r--Source/Core/Core/CMakeLists.txt2
-rw-r--r--Source/Core/Core/Debugger/BranchWatch.cpp314
-rw-r--r--Source/Core/Core/Debugger/BranchWatch.h278
-rw-r--r--Source/Core/Core/PowerPC/Interpreter/Interpreter.cpp5
-rw-r--r--Source/Core/Core/PowerPC/Interpreter/Interpreter.h7
-rw-r--r--Source/Core/Core/PowerPC/JitCommon/JitBase.cpp2
-rw-r--r--Source/Core/Core/PowerPC/JitCommon/JitBase.h4
-rw-r--r--Source/Core/Core/PowerPC/PowerPC.h4
-rw-r--r--Source/Core/Core/System.cpp4
-rw-r--r--Source/Core/DolphinLib.props2
10 files changed, 614 insertions, 8 deletions
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 <algorithm>
+#include <cstddef>
+#include <cstdio>
+
+#include <fmt/format.h>
+
+#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 <cstddef>
+#include <cstdio>
+#include <functional>
+#include <unordered_map>
+#include <vector>
+
+#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<u64>(*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<Core::BranchWatchCollectionKey>
+{
+ std::size_t operator()(const Core::BranchWatchCollectionKey& s) const noexcept
+ {
+ return std::hash<u64>{}(static_cast<const Core::FakeBranchWatchCollectionKey&>(s));
+ }
+};
+
+namespace Core
+{
+inline bool operator==(const BranchWatchCollectionKey& lhs,
+ const BranchWatchCollectionKey& rhs) noexcept
+{
+ const std::hash<BranchWatchCollectionKey> 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<BranchWatchSelectionInspection>(Common::ToUnderlying(lhs) |
+ Common::ToUnderlying(rhs));
+}
+
+constexpr BranchWatchSelectionInspection operator&(BranchWatchSelectionInspection lhs,
+ BranchWatchSelectionInspection rhs)
+{
+ return static_cast<BranchWatchSelectionInspection>(Common::ToUnderlying(lhs) &
+ Common::ToUnderlying(rhs));
+}
+
+constexpr BranchWatchSelectionInspection& operator|=(BranchWatchSelectionInspection& self,
+ BranchWatchSelectionInspection other)
+{
+ return self = self | other;
+}
+
+using BranchWatchCollection =
+ std::unordered_map<BranchWatchCollectionKey, BranchWatchCollectionValue>;
+
+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<BranchWatchSelectionValueType>;
+
+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<FakeBranchWatchCollectionKey>(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<FakeBranchWatchCollectionKey>(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<FakeBranchWatchCollectionKey>(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<FakeBranchWatchCollectionKey>(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<FakeBranchWatchCollectionKey>(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<FakeBranchWatchCollectionKey>(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 @@
<ClInclude Include="Core\Core.h" />
<ClInclude Include="Core\CoreTiming.h" />
<ClInclude Include="Core\CPUThreadConfigCallback.h" />
+ <ClInclude Include="Core\Debugger\BranchWatch.h" />
<ClInclude Include="Core\Debugger\CodeTrace.h" />
<ClInclude Include="Core\Debugger\DebugInterface.h" />
<ClInclude Include="Core\Debugger\Debugger_SymbolMap.h" />
@@ -868,6 +869,7 @@
<ClCompile Include="Core\Core.cpp" />
<ClCompile Include="Core\CoreTiming.cpp" />
<ClCompile Include="Core\CPUThreadConfigCallback.cpp" />
+ <ClCompile Include="Core\Debugger\BranchWatch.cpp" />
<ClCompile Include="Core\Debugger\CodeTrace.cpp" />
<ClCompile Include="Core\Debugger\Debugger_SymbolMap.cpp" />
<ClCompile Include="Core\Debugger\Dump.cpp" />