mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2024-11-14 21:37:52 -07:00
PowerPC: Implement BranchWatch
This new component can track code paths by watching branch hits.
This commit is contained in:
parent
5090a028e6
commit
67f60bec7e
@ -61,6 +61,8 @@ add_library(core
|
|||||||
CoreTiming.h
|
CoreTiming.h
|
||||||
CPUThreadConfigCallback.cpp
|
CPUThreadConfigCallback.cpp
|
||||||
CPUThreadConfigCallback.h
|
CPUThreadConfigCallback.h
|
||||||
|
Debugger/BranchWatch.cpp
|
||||||
|
Debugger/BranchWatch.h
|
||||||
Debugger/CodeTrace.cpp
|
Debugger/CodeTrace.cpp
|
||||||
Debugger/CodeTrace.h
|
Debugger/CodeTrace.h
|
||||||
Debugger/DebugInterface.h
|
Debugger/DebugInterface.h
|
||||||
|
314
Source/Core/Core/Debugger/BranchWatch.cpp
Normal file
314
Source/Core/Core/Debugger/BranchWatch.cpp
Normal file
@ -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
|
278
Source/Core/Core/Debugger/BranchWatch.h
Normal file
278
Source/Core/Core/Debugger/BranchWatch.h
Normal file
@ -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
|
@ -64,8 +64,9 @@ void Interpreter::UpdatePC()
|
|||||||
m_ppc_state.pc = m_ppc_state.npc;
|
m_ppc_state.pc = m_ppc_state.npc;
|
||||||
}
|
}
|
||||||
|
|
||||||
Interpreter::Interpreter(Core::System& system, PowerPC::PowerPCState& ppc_state, PowerPC::MMU& mmu)
|
Interpreter::Interpreter(Core::System& system, PowerPC::PowerPCState& ppc_state, PowerPC::MMU& mmu,
|
||||||
: m_system(system), m_ppc_state(ppc_state), m_mmu(mmu)
|
Core::BranchWatch& branch_watch)
|
||||||
|
: m_system(system), m_ppc_state(ppc_state), m_mmu(mmu), m_branch_watch(branch_watch)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,8 +11,9 @@
|
|||||||
|
|
||||||
namespace Core
|
namespace Core
|
||||||
{
|
{
|
||||||
|
class BranchWatch;
|
||||||
class System;
|
class System;
|
||||||
}
|
} // namespace Core
|
||||||
namespace PowerPC
|
namespace PowerPC
|
||||||
{
|
{
|
||||||
class MMU;
|
class MMU;
|
||||||
@ -22,7 +23,8 @@ struct PowerPCState;
|
|||||||
class Interpreter : public CPUCoreBase
|
class Interpreter : public CPUCoreBase
|
||||||
{
|
{
|
||||||
public:
|
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(const Interpreter&) = delete;
|
||||||
Interpreter(Interpreter&&) = delete;
|
Interpreter(Interpreter&&) = delete;
|
||||||
Interpreter& operator=(const Interpreter&) = delete;
|
Interpreter& operator=(const Interpreter&) = delete;
|
||||||
@ -314,6 +316,7 @@ private:
|
|||||||
Core::System& m_system;
|
Core::System& m_system;
|
||||||
PowerPC::PowerPCState& m_ppc_state;
|
PowerPC::PowerPCState& m_ppc_state;
|
||||||
PowerPC::MMU& m_mmu;
|
PowerPC::MMU& m_mmu;
|
||||||
|
Core::BranchWatch& m_branch_watch;
|
||||||
|
|
||||||
UGeckoInstruction m_prev_inst{};
|
UGeckoInstruction m_prev_inst{};
|
||||||
u32 m_last_pc = 0;
|
u32 m_last_pc = 0;
|
||||||
|
@ -94,7 +94,7 @@ void JitTrampoline(JitBase& jit, u32 em_address)
|
|||||||
|
|
||||||
JitBase::JitBase(Core::System& system)
|
JitBase::JitBase(Core::System& system)
|
||||||
: m_code_buffer(code_buffer_size), m_system(system), m_ppc_state(system.GetPPCState()),
|
: 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] {
|
m_registered_config_callback_id = CPUThreadConfigCallback::AddConfigChangedCallback([this] {
|
||||||
if (DoesConfigNeedRefresh())
|
if (DoesConfigNeedRefresh())
|
||||||
|
@ -23,8 +23,9 @@
|
|||||||
|
|
||||||
namespace Core
|
namespace Core
|
||||||
{
|
{
|
||||||
|
class BranchWatch;
|
||||||
class System;
|
class System;
|
||||||
}
|
} // namespace Core
|
||||||
namespace PowerPC
|
namespace PowerPC
|
||||||
{
|
{
|
||||||
class MMU;
|
class MMU;
|
||||||
@ -206,6 +207,7 @@ public:
|
|||||||
Core::System& m_system;
|
Core::System& m_system;
|
||||||
PowerPC::PowerPCState& m_ppc_state;
|
PowerPC::PowerPCState& m_ppc_state;
|
||||||
PowerPC::MMU& m_mmu;
|
PowerPC::MMU& m_mmu;
|
||||||
|
Core::BranchWatch& m_branch_watch;
|
||||||
};
|
};
|
||||||
|
|
||||||
void JitTrampoline(JitBase& jit, u32 em_address);
|
void JitTrampoline(JitBase& jit, u32 em_address);
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
|
||||||
#include "Core/CPUThreadConfigCallback.h"
|
#include "Core/CPUThreadConfigCallback.h"
|
||||||
|
#include "Core/Debugger/BranchWatch.h"
|
||||||
#include "Core/Debugger/PPCDebugInterface.h"
|
#include "Core/Debugger/PPCDebugInterface.h"
|
||||||
#include "Core/PowerPC/BreakPoints.h"
|
#include "Core/PowerPC/BreakPoints.h"
|
||||||
#include "Core/PowerPC/ConditionRegister.h"
|
#include "Core/PowerPC/ConditionRegister.h"
|
||||||
@ -298,6 +299,8 @@ public:
|
|||||||
const MemChecks& GetMemChecks() const { return m_memchecks; }
|
const MemChecks& GetMemChecks() const { return m_memchecks; }
|
||||||
PPCDebugInterface& GetDebugInterface() { return m_debug_interface; }
|
PPCDebugInterface& GetDebugInterface() { return m_debug_interface; }
|
||||||
const PPCDebugInterface& GetDebugInterface() const { 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:
|
private:
|
||||||
void InitializeCPUCore(CPUCore cpu_core);
|
void InitializeCPUCore(CPUCore cpu_core);
|
||||||
@ -314,6 +317,7 @@ private:
|
|||||||
BreakPoints m_breakpoints;
|
BreakPoints m_breakpoints;
|
||||||
MemChecks m_memchecks;
|
MemChecks m_memchecks;
|
||||||
PPCDebugInterface m_debug_interface;
|
PPCDebugInterface m_debug_interface;
|
||||||
|
Core::BranchWatch m_branch_watch;
|
||||||
|
|
||||||
CPUThreadConfigCallback::ConfigChangedCallbackID m_registered_config_callback_id;
|
CPUThreadConfigCallback::ConfigChangedCallbackID m_registered_config_callback_id;
|
||||||
|
|
||||||
|
@ -52,8 +52,8 @@ struct System::Impl
|
|||||||
m_memory(system), m_pixel_engine{system}, m_power_pc(system),
|
m_memory(system), m_pixel_engine{system}, m_power_pc(system),
|
||||||
m_mmu(system, m_memory, m_power_pc), m_processor_interface(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_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_interpreter(system, m_power_pc.GetPPCState(), m_mmu, m_power_pc.GetBranchWatch()),
|
||||||
m_fifo_player(system), m_fifo_recorder(system), m_movie(system)
|
m_jit_interface(system), m_fifo_player(system), m_fifo_recorder(system), m_movie(system)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,6 +202,7 @@
|
|||||||
<ClInclude Include="Core\Core.h" />
|
<ClInclude Include="Core\Core.h" />
|
||||||
<ClInclude Include="Core\CoreTiming.h" />
|
<ClInclude Include="Core\CoreTiming.h" />
|
||||||
<ClInclude Include="Core\CPUThreadConfigCallback.h" />
|
<ClInclude Include="Core\CPUThreadConfigCallback.h" />
|
||||||
|
<ClInclude Include="Core\Debugger\BranchWatch.h" />
|
||||||
<ClInclude Include="Core\Debugger\CodeTrace.h" />
|
<ClInclude Include="Core\Debugger\CodeTrace.h" />
|
||||||
<ClInclude Include="Core\Debugger\DebugInterface.h" />
|
<ClInclude Include="Core\Debugger\DebugInterface.h" />
|
||||||
<ClInclude Include="Core\Debugger\Debugger_SymbolMap.h" />
|
<ClInclude Include="Core\Debugger\Debugger_SymbolMap.h" />
|
||||||
@ -868,6 +869,7 @@
|
|||||||
<ClCompile Include="Core\Core.cpp" />
|
<ClCompile Include="Core\Core.cpp" />
|
||||||
<ClCompile Include="Core\CoreTiming.cpp" />
|
<ClCompile Include="Core\CoreTiming.cpp" />
|
||||||
<ClCompile Include="Core\CPUThreadConfigCallback.cpp" />
|
<ClCompile Include="Core\CPUThreadConfigCallback.cpp" />
|
||||||
|
<ClCompile Include="Core\Debugger\BranchWatch.cpp" />
|
||||||
<ClCompile Include="Core\Debugger\CodeTrace.cpp" />
|
<ClCompile Include="Core\Debugger\CodeTrace.cpp" />
|
||||||
<ClCompile Include="Core\Debugger\Debugger_SymbolMap.cpp" />
|
<ClCompile Include="Core\Debugger\Debugger_SymbolMap.cpp" />
|
||||||
<ClCompile Include="Core\Debugger\Dump.cpp" />
|
<ClCompile Include="Core\Debugger\Dump.cpp" />
|
||||||
|
Loading…
Reference in New Issue
Block a user