diff options
| author | Admiral H. Curtiss <pikachu025@gmail.com> | 2024-03-02 14:13:54 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-03-02 14:13:54 +0100 |
| commit | f2e04e06030f4d9e3d120ba90d846600fb2897ce (patch) | |
| tree | d43b588825360ecbd7e7366ba6e23e43cc3648e5 /Source/Core/DolphinQt/Debugger/BranchWatchTableModel.cpp | |
| parent | 8840f7f8bc218846ca5950b59da59997b5784d3a (diff) | |
| parent | 8134c8a57211f54cf2109ae3c068c0c94370f786 (diff) | |
Merge pull request #12359 from mitaclaw/code-diff-dialog-refresh
BranchWatchDialog: A Total Replacement for CodeDiffDialog
Diffstat (limited to 'Source/Core/DolphinQt/Debugger/BranchWatchTableModel.cpp')
| -rw-r--r-- | Source/Core/DolphinQt/Debugger/BranchWatchTableModel.cpp | 502 |
1 files changed, 502 insertions, 0 deletions
diff --git a/Source/Core/DolphinQt/Debugger/BranchWatchTableModel.cpp b/Source/Core/DolphinQt/Debugger/BranchWatchTableModel.cpp new file mode 100644 index 0000000000..800973a6dd --- /dev/null +++ b/Source/Core/DolphinQt/Debugger/BranchWatchTableModel.cpp @@ -0,0 +1,502 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "DolphinQt/Debugger/BranchWatchTableModel.h" + +#include <algorithm> +#include <array> +#include <cstddef> + +#include <QBrush> + +#include "Common/Assert.h" +#include "Common/GekkoDisassembler.h" +#include "Core/Debugger/BranchWatch.h" +#include "Core/PowerPC/PPCSymbolDB.h" + +QVariant BranchWatchTableModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + switch (role) + { + case Qt::DisplayRole: + return DisplayRoleData(index); + case Qt::FontRole: + return FontRoleData(index); + case Qt::TextAlignmentRole: + return TextAlignmentRoleData(index); + case Qt::ForegroundRole: + return ForegroundRoleData(index); + case UserRole::ClickRole: + return ClickRoleData(index); + case UserRole::SortRole: + return SortRoleData(index); + } + return QVariant(); +} + +QVariant BranchWatchTableModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Vertical || role != Qt::DisplayRole) + return QVariant(); + + static constexpr std::array<const char*, Column::NumberOfColumns> headers = { + QT_TR_NOOP("Instr."), QT_TR_NOOP("Cond."), + QT_TR_NOOP("Origin"), QT_TR_NOOP("Destination"), + QT_TR_NOOP("Recent Hits"), QT_TR_NOOP("Total Hits"), + QT_TR_NOOP("Origin Symbol"), QT_TR_NOOP("Destination Symbol")}; + return tr(headers[section]); +} + +int BranchWatchTableModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) + return 0; + return static_cast<int>(m_branch_watch.GetSelection().size()); +} + +int BranchWatchTableModel::columnCount(const QModelIndex& parent) const +{ + if (parent.isValid()) + return 0; + return Column::NumberOfColumns; +} + +bool BranchWatchTableModel::removeRows(int row, int count, const QModelIndex& parent) +{ + if (parent.isValid() || row < 0) + return false; + if (count <= 0) + return true; + + auto& selection = m_branch_watch.GetSelection(); + beginRemoveRows(parent, row, row + count - 1); // Last is inclusive in Qt! + selection.erase(selection.begin() + row, selection.begin() + row + count); + m_symbol_list.remove(row, count); + endRemoveRows(); + return true; +} + +void BranchWatchTableModel::OnClearBranchWatch(const Core::CPUThreadGuard& guard) +{ + emit layoutAboutToBeChanged(); + m_branch_watch.Clear(guard); + m_symbol_list.clear(); + emit layoutChanged(); +} + +void BranchWatchTableModel::OnCodePathWasTaken(const Core::CPUThreadGuard& guard) +{ + emit layoutAboutToBeChanged(); + m_branch_watch.IsolateHasExecuted(guard); + PrefetchSymbols(); + emit layoutChanged(); +} + +void BranchWatchTableModel::OnCodePathNotTaken(const Core::CPUThreadGuard& guard) +{ + emit layoutAboutToBeChanged(); + m_branch_watch.IsolateNotExecuted(guard); + PrefetchSymbols(); + emit layoutChanged(); +} + +void BranchWatchTableModel::OnBranchWasOverwritten(const Core::CPUThreadGuard& guard) +{ + emit layoutAboutToBeChanged(); + m_branch_watch.IsolateWasOverwritten(guard); + PrefetchSymbols(); + emit layoutChanged(); +} + +void BranchWatchTableModel::OnBranchNotOverwritten(const Core::CPUThreadGuard& guard) +{ + emit layoutAboutToBeChanged(); + m_branch_watch.IsolateNotOverwritten(guard); + PrefetchSymbols(); + emit layoutChanged(); +} + +void BranchWatchTableModel::OnWipeRecentHits() +{ + const int row_count = rowCount(); + if (row_count <= 0) + return; + static const QList<int> roles = {Qt::DisplayRole}; + m_branch_watch.UpdateHitsSnapshot(); + const int last = row_count - 1; + emit dataChanged(createIndex(0, Column::RecentHits), createIndex(last, Column::RecentHits), + roles); +} + +void BranchWatchTableModel::OnWipeInspection() +{ + const int row_count = rowCount(); + if (row_count <= 0) + return; + static const QList<int> roles = {Qt::FontRole, Qt::ForegroundRole}; + m_branch_watch.ClearSelectionInspection(); + const int last = row_count - 1; + emit dataChanged(createIndex(0, Column::Origin), createIndex(last, Column::Destination), roles); + emit dataChanged(createIndex(0, Column::OriginSymbol), createIndex(last, Column::DestinSymbol), + roles); +} + +void BranchWatchTableModel::OnDelete(QModelIndexList index_list) +{ + std::sort(index_list.begin(), index_list.end()); + // TODO C++20: std::ranges::reverse_view + for (auto iter = index_list.rbegin(); iter != index_list.rend(); ++iter) + { + if (!iter->isValid()) + continue; + removeRow(iter->row()); + } +} + +void BranchWatchTableModel::Save(const Core::CPUThreadGuard& guard, std::FILE* file) const +{ + m_branch_watch.Save(guard, file); +} + +void BranchWatchTableModel::Load(const Core::CPUThreadGuard& guard, std::FILE* file) +{ + emit layoutAboutToBeChanged(); + m_branch_watch.Load(guard, file); + PrefetchSymbols(); + emit layoutChanged(); +} + +void BranchWatchTableModel::UpdateSymbols() +{ + const int row_count = rowCount(); + if (row_count <= 0) + return; + static const QList<int> roles = {Qt::DisplayRole}; + PrefetchSymbols(); + const int last = row_count - 1; + emit dataChanged(createIndex(0, Column::OriginSymbol), createIndex(last, Column::DestinSymbol), + roles); +} + +void BranchWatchTableModel::UpdateHits() +{ + const int row_count = rowCount(); + if (row_count <= 0) + return; + static const QList<int> roles = {Qt::DisplayRole}; + const int last = row_count - 1; + emit dataChanged(createIndex(0, Column::RecentHits), createIndex(last, Column::TotalHits), roles); +} + +void BranchWatchTableModel::SetInspected(const QModelIndex& index) +{ + const int row = index.row(); + switch (index.column()) + { + case Column::Origin: + SetOriginInspected(m_branch_watch.GetSelection()[row].collection_ptr->first.origin_addr); + return; + case Column::Destination: + SetDestinInspected(m_branch_watch.GetSelection()[row].collection_ptr->first.destin_addr, false); + return; + case Column::OriginSymbol: + SetSymbolInspected(m_symbol_list[row].origin_addr.value<u32>(), false); + return; + case Column::DestinSymbol: + SetSymbolInspected(m_symbol_list[row].destin_addr.value<u32>(), false); + return; + } +} + +void BranchWatchTableModel::SetOriginInspected(u32 origin_addr) +{ + using Inspection = Core::BranchWatchSelectionInspection; + static const QList<int> roles = {Qt::FontRole, Qt::ForegroundRole}; + + const Core::BranchWatch::Selection& selection = m_branch_watch.GetSelection(); + for (std::size_t i = 0; i < selection.size(); ++i) + { + if (selection[i].collection_ptr->first.origin_addr != origin_addr) + continue; + m_branch_watch.SetSelectedInspected(i, Inspection::SetOriginNOP); + const QModelIndex index = createIndex(static_cast<int>(i), Column::Origin); + emit dataChanged(index, index, roles); + } +} + +void BranchWatchTableModel::SetDestinInspected(u32 destin_addr, bool nested) +{ + using Inspection = Core::BranchWatchSelectionInspection; + static const QList<int> roles = {Qt::FontRole, Qt::ForegroundRole}; + + const Core::BranchWatch::Selection& selection = m_branch_watch.GetSelection(); + for (std::size_t i = 0; i < selection.size(); ++i) + { + if (selection[i].collection_ptr->first.destin_addr != destin_addr) + continue; + m_branch_watch.SetSelectedInspected(i, Inspection::SetDestinBLR); + const QModelIndex index = createIndex(static_cast<int>(i), Column::Destination); + emit dataChanged(index, index, roles); + } + + if (nested) + return; + SetSymbolInspected(destin_addr, true); +} + +void BranchWatchTableModel::SetSymbolInspected(u32 symbol_addr, bool nested) +{ + using Inspection = Core::BranchWatchSelectionInspection; + static const QList<int> roles = {Qt::FontRole, Qt::ForegroundRole}; + + for (qsizetype i = 0; i < m_symbol_list.size(); ++i) + { + const SymbolListValueType& value = m_symbol_list[i]; + if (value.origin_addr.isValid() && value.origin_addr.value<u32>() == symbol_addr) + { + m_branch_watch.SetSelectedInspected(i, Inspection::SetOriginSymbolBLR); + const QModelIndex index = createIndex(i, Column::OriginSymbol); + emit dataChanged(index, index, roles); + } + if (value.destin_addr.isValid() && value.destin_addr.value<u32>() == symbol_addr) + { + m_branch_watch.SetSelectedInspected(i, Inspection::SetDestinSymbolBLR); + const QModelIndex index = createIndex(i, Column::DestinSymbol); + emit dataChanged(index, index, roles); + } + } + + if (nested) + return; + SetDestinInspected(symbol_addr, true); +} + +void BranchWatchTableModel::PrefetchSymbols() +{ + if (m_branch_watch.GetRecordingPhase() != Core::BranchWatch::Phase::Reduction) + return; + + const Core::BranchWatch::Selection& selection = m_branch_watch.GetSelection(); + m_symbol_list.clear(); + m_symbol_list.reserve(selection.size()); + for (const Core::BranchWatch::Selection::value_type& value : selection) + { + const Core::BranchWatch::Collection::value_type* const kv = value.collection_ptr; + m_symbol_list.emplace_back(g_symbolDB.GetSymbolFromAddr(kv->first.origin_addr), + g_symbolDB.GetSymbolFromAddr(kv->first.destin_addr)); + } +} + +static QVariant GetValidSymbolStringVariant(const QVariant& symbol_name_v) +{ + if (symbol_name_v.isValid()) + return symbol_name_v; + return QStringLiteral(" --- "); +} + +static QString GetInstructionMnemonic(u32 hex) +{ + const std::string disas = Common::GekkoDisassembler::Disassemble(hex, 0); + const std::string::size_type split = disas.find('\t'); + // I wish I could disassemble just the mnemonic! + if (split == std::string::npos) + return QString::fromStdString(disas); + return QString::fromLatin1(disas.data(), split); +} + +static bool BranchIsUnconditional(UGeckoInstruction inst) +{ + if (inst.OPCD == 18) // bx + return true; + // If BranchWatch is doing its job, the input will be only bcx, bclrx, and bcctrx instructions. + DEBUG_ASSERT(inst.OPCD == 16 || (inst.OPCD == 19 && (inst.SUBOP10 == 16 || inst.SUBOP10 == 528))); + if ((inst.BO & 0b10100) == 0b10100) // 1z1zz - Branch always + return true; + return false; +} + +static QString GetConditionString(const Core::BranchWatch::Selection::value_type& value, + const Core::BranchWatch::Collection::value_type* kv) +{ + if (value.condition == false) + return BranchWatchTableModel::tr("false"); + if (BranchIsUnconditional(kv->first.original_inst)) + return QStringLiteral(""); + return BranchWatchTableModel::tr("true"); +} + +QVariant BranchWatchTableModel::DisplayRoleData(const QModelIndex& index) const +{ + switch (index.column()) + { + case Column::OriginSymbol: + return GetValidSymbolStringVariant(m_symbol_list[index.row()].origin_name); + case Column::DestinSymbol: + return GetValidSymbolStringVariant(m_symbol_list[index.row()].destin_name); + } + const Core::BranchWatch::Selection::value_type& value = + m_branch_watch.GetSelection()[index.row()]; + const Core::BranchWatch::Collection::value_type* kv = value.collection_ptr; + switch (index.column()) + { + case Column::Instruction: + return GetInstructionMnemonic(kv->first.original_inst.hex); + case Column::Condition: + return GetConditionString(value, kv); + case Column::Origin: + return QString::number(kv->first.origin_addr, 16); + case Column::Destination: + return QString::number(kv->first.destin_addr, 16); + case Column::RecentHits: + return QString::number(kv->second.total_hits - kv->second.hits_snapshot); + case Column::TotalHits: + return QString::number(kv->second.total_hits); + } + return QVariant(); +} + +QVariant BranchWatchTableModel::FontRoleData(const QModelIndex& index) const +{ + m_font.setBold([&]() -> bool { + switch (index.column()) + { + using Inspection = Core::BranchWatchSelectionInspection; + case Column::Origin: + return (m_branch_watch.GetSelection()[index.row()].inspection & Inspection::SetOriginNOP) != + Inspection{}; + case Column::Destination: + return (m_branch_watch.GetSelection()[index.row()].inspection & Inspection::SetDestinBLR) != + Inspection{}; + case Column::OriginSymbol: + return (m_branch_watch.GetSelection()[index.row()].inspection & + Inspection::SetOriginSymbolBLR) != Inspection{}; + case Column::DestinSymbol: + return (m_branch_watch.GetSelection()[index.row()].inspection & + Inspection::SetDestinSymbolBLR) != Inspection{}; + } + // Importantly, this code path avoids subscripting the selection to get an inspection value. + return false; + }()); + return m_font; +} + +QVariant BranchWatchTableModel::TextAlignmentRoleData(const QModelIndex& index) const +{ + // Qt enums become QFlags when operators are used. QVariant's constructors don't support QFlags. + switch (index.column()) + { + case Column::Condition: + case Column::Origin: + case Column::Destination: + return Qt::AlignCenter; + case Column::RecentHits: + case Column::TotalHits: + return QVariant::fromValue(Qt::AlignRight | Qt::AlignVCenter); + case Column::Instruction: + case Column::OriginSymbol: + case Column::DestinSymbol: + return QVariant::fromValue(Qt::AlignLeft | Qt::AlignVCenter); + } + return QVariant(); +} + +QVariant BranchWatchTableModel::ForegroundRoleData(const QModelIndex& index) const +{ + switch (index.column()) + { + using Inspection = Core::BranchWatchSelectionInspection; + case Column::Origin: + { + const Inspection inspection = m_branch_watch.GetSelection()[index.row()].inspection; + return (inspection & Inspection::SetOriginNOP) != Inspection{} ? QBrush(Qt::red) : QVariant(); + } + case Column::Destination: + { + const Inspection inspection = m_branch_watch.GetSelection()[index.row()].inspection; + return (inspection & Inspection::SetDestinBLR) != Inspection{} ? QBrush(Qt::red) : QVariant(); + } + case Column::OriginSymbol: + { + const Inspection inspection = m_branch_watch.GetSelection()[index.row()].inspection; + return (inspection & Inspection::SetOriginSymbolBLR) != Inspection{} ? QBrush(Qt::red) : + QVariant(); + } + case Column::DestinSymbol: + { + const Inspection inspection = m_branch_watch.GetSelection()[index.row()].inspection; + return (inspection & Inspection::SetDestinSymbolBLR) != Inspection{} ? QBrush(Qt::red) : + QVariant(); + } + } + // Importantly, this code path avoids subscripting the selection to get an inspection value. + return QVariant(); +} + +QVariant BranchWatchTableModel::ClickRoleData(const QModelIndex& index) const +{ + switch (index.column()) + { + case Column::OriginSymbol: + return m_symbol_list[index.row()].origin_addr; + case Column::DestinSymbol: + return m_symbol_list[index.row()].destin_addr; + } + const Core::BranchWatch::Collection::value_type* kv = + m_branch_watch.GetSelection()[index.row()].collection_ptr; + switch (index.column()) + { + case Column::Instruction: + return kv->first.original_inst.hex; + case Column::Origin: + return kv->first.origin_addr; + case Column::Destination: + return kv->first.destin_addr; + } + return QVariant(); +} + +// 0 == false, 1 == true, 2 == unconditional +static int GetConditionInteger(const Core::BranchWatch::Selection::value_type& value, + const Core::BranchWatch::Collection::value_type* kv) +{ + if (value.condition == false) + return 0; + if (BranchIsUnconditional(kv->first.original_inst)) + return 2; + return 1; +} + +QVariant BranchWatchTableModel::SortRoleData(const QModelIndex& index) const +{ + switch (index.column()) + { + case Column::OriginSymbol: + return m_symbol_list[index.row()].origin_name; + case Column::DestinSymbol: + return m_symbol_list[index.row()].destin_name; + } + const Core::BranchWatch::Selection::value_type& selection_value = + m_branch_watch.GetSelection()[index.row()]; + const Core::BranchWatch::Collection::value_type* kv = selection_value.collection_ptr; + switch (index.column()) + { + // QVariant's ctor only supports (unsigned) int and (unsigned) long long for some stupid reason. + // std::size_t is unsigned long on some platforms, which results in an ambiguous conversion. + case Column::Instruction: + return GetInstructionMnemonic(kv->first.original_inst.hex); + case Column::Condition: + return GetConditionInteger(selection_value, kv); + case Column::Origin: + return kv->first.origin_addr; + case Column::Destination: + return kv->first.destin_addr; + case Column::RecentHits: + return qulonglong{kv->second.total_hits - kv->second.hits_snapshot}; + case Column::TotalHits: + return qulonglong{kv->second.total_hits}; + } + return QVariant(); +} |
