mirror of
https://github.com/dolphin-emu/dolphin
synced 2024-11-04 20:43:44 -05:00
0371b74ebf
Flushing efficiency down the drain because I wrote unsafe code to achieve it... I hope I can recover this.
455 lines
14 KiB
C++
455 lines
14 KiB
C++
// Copyright 2024 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "DolphinQt/Debugger/JitBlockTableModel.h"
|
|
|
|
#include <array>
|
|
#include <span>
|
|
|
|
#include "Common/Assert.h"
|
|
#include "Common/Unreachable.h"
|
|
#include "Core/Core.h"
|
|
#include "Core/PowerPC/JitInterface.h"
|
|
#include "Core/PowerPC/PPCSymbolDB.h"
|
|
#include "DolphinQt/Host.h"
|
|
#include "DolphinQt/Settings.h"
|
|
|
|
const JitBlock& JitBlockTableModel::GetJitBlock(const QModelIndex& index) const
|
|
{
|
|
ASSERT(index.isValid());
|
|
return m_jit_blocks[index.row()];
|
|
}
|
|
|
|
void JitBlockTableModel::SumOverallCosts()
|
|
{
|
|
m_overall_cycles_spent = 0;
|
|
m_overall_time_spent = {};
|
|
for (const JitBlock& block : m_jit_blocks)
|
|
{
|
|
if (block.profile_data == nullptr)
|
|
continue;
|
|
m_overall_cycles_spent += block.profile_data->cycles_spent;
|
|
m_overall_time_spent += block.profile_data->time_spent;
|
|
};
|
|
}
|
|
|
|
static QVariant GetSymbolNameQVariant(const Common::Symbol* symbol)
|
|
{
|
|
return symbol ? QString::fromStdString(symbol->name) : QVariant{};
|
|
}
|
|
|
|
void JitBlockTableModel::PrefetchSymbols()
|
|
{
|
|
m_symbol_list.clear();
|
|
m_symbol_list.reserve(m_jit_blocks.size());
|
|
// If the table viewing this model will be accessing every element,
|
|
// it would be a waste of effort to lazy-initialize the symbol list.
|
|
if (m_sorting_by_symbols || m_filtering_by_symbols)
|
|
{
|
|
for (const JitBlock& block : m_jit_blocks)
|
|
{
|
|
m_symbol_list.emplace_back(
|
|
GetSymbolNameQVariant(m_ppc_symbol_db.GetSymbolFromAddr(block.effectiveAddress)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (const JitBlock& block : m_jit_blocks)
|
|
{
|
|
m_symbol_list.emplace_back([this, &block]() {
|
|
return GetSymbolNameQVariant(m_ppc_symbol_db.GetSymbolFromAddr(block.effectiveAddress));
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
void JitBlockTableModel::Clear()
|
|
{
|
|
emit layoutAboutToBeChanged();
|
|
m_jit_blocks.clear();
|
|
m_symbol_list.clear();
|
|
emit layoutChanged();
|
|
}
|
|
|
|
void JitBlockTableModel::Update(Core::State state)
|
|
{
|
|
emit layoutAboutToBeChanged();
|
|
m_jit_blocks.clear();
|
|
if (state == Core::State::Paused)
|
|
{
|
|
m_jit_blocks.reserve(m_jit_interface.GetBlockCount());
|
|
m_jit_interface.RunOnBlocks(Core::CPUThreadGuard{m_system}, [this](const JitBlock& block) {
|
|
m_jit_blocks.emplace_back(block);
|
|
});
|
|
SumOverallCosts();
|
|
}
|
|
PrefetchSymbols();
|
|
emit layoutChanged();
|
|
}
|
|
|
|
void JitBlockTableModel::UpdateProfileData()
|
|
{
|
|
const int row_count = rowCount();
|
|
if (row_count <= 0)
|
|
return;
|
|
SumOverallCosts();
|
|
static const QList<int> roles = {Qt::DisplayRole};
|
|
const int last = row_count - 1;
|
|
emit dataChanged(createIndex(0, Column::RunCount), createIndex(last, Column::TimePercent), roles);
|
|
}
|
|
|
|
void JitBlockTableModel::UpdateSymbols()
|
|
{
|
|
const int row_count = rowCount();
|
|
if (row_count <= 0)
|
|
return;
|
|
PrefetchSymbols();
|
|
static const QList<int> roles = {Qt::DisplayRole};
|
|
const int last = row_count - 1;
|
|
emit dataChanged(createIndex(0, Column::Symbol), createIndex(last, Column::Symbol), roles);
|
|
}
|
|
|
|
void JitBlockTableModel::ConnectSlots()
|
|
{
|
|
auto* const host = Host::GetInstance();
|
|
connect(host, &Host::JitCacheInvalidation, this, &JitBlockTableModel::OnJitCacheInvalidation);
|
|
connect(host, &Host::JitProfileDataWiped, this, &JitBlockTableModel::OnJitProfileDataWiped);
|
|
connect(host, &Host::UpdateDisasmDialog, this, &JitBlockTableModel::OnUpdateDisasmDialog);
|
|
connect(host, &Host::PPCSymbolsChanged, this, &JitBlockTableModel::OnPPCSymbolsUpdated);
|
|
connect(host, &Host::PPCBreakpointsChanged, this, &JitBlockTableModel::OnPPCBreakpointsChanged);
|
|
auto* const settings = &Settings::Instance();
|
|
connect(settings, &Settings::EmulationStateChanged, this,
|
|
&JitBlockTableModel::OnEmulationStateChanged);
|
|
}
|
|
|
|
void JitBlockTableModel::DisconnectSlots()
|
|
{
|
|
auto* const host = Host::GetInstance();
|
|
disconnect(host, &Host::JitCacheInvalidation, this, &JitBlockTableModel::OnJitCacheInvalidation);
|
|
disconnect(host, &Host::JitProfileDataWiped, this, &JitBlockTableModel::OnJitProfileDataWiped);
|
|
disconnect(host, &Host::UpdateDisasmDialog, this, &JitBlockTableModel::OnUpdateDisasmDialog);
|
|
disconnect(host, &Host::PPCSymbolsChanged, this, &JitBlockTableModel::OnPPCSymbolsUpdated);
|
|
disconnect(host, &Host::PPCBreakpointsChanged, this,
|
|
&JitBlockTableModel::OnPPCBreakpointsChanged);
|
|
auto* const settings = &Settings::Instance();
|
|
disconnect(settings, &Settings::EmulationStateChanged, this,
|
|
&JitBlockTableModel::OnEmulationStateChanged);
|
|
}
|
|
|
|
void JitBlockTableModel::Show()
|
|
{
|
|
ConnectSlots();
|
|
// Every slot that may have missed a signal while this model was hidden can be handled by:
|
|
Update(Core::GetState(m_system));
|
|
}
|
|
|
|
void JitBlockTableModel::Hide()
|
|
{
|
|
DisconnectSlots();
|
|
Clear();
|
|
}
|
|
|
|
void JitBlockTableModel::OnShowSignal()
|
|
{
|
|
Show();
|
|
}
|
|
|
|
void JitBlockTableModel::OnHideSignal()
|
|
{
|
|
Hide();
|
|
}
|
|
|
|
void JitBlockTableModel::OnSortIndicatorChanged(int logicalIndex, Qt::SortOrder)
|
|
{
|
|
m_sorting_by_symbols = logicalIndex == Column::Symbol;
|
|
}
|
|
|
|
void JitBlockTableModel::OnFilterSymbolTextChanged(const QString& string)
|
|
{
|
|
m_filtering_by_symbols = !string.isEmpty();
|
|
}
|
|
|
|
void JitBlockTableModel::OnJitCacheInvalidation()
|
|
{
|
|
Update(Core::GetState(m_system));
|
|
}
|
|
|
|
void JitBlockTableModel::OnJitProfileDataWiped()
|
|
{
|
|
UpdateProfileData();
|
|
}
|
|
|
|
void JitBlockTableModel::OnUpdateDisasmDialog()
|
|
{
|
|
// This should hopefully catch all the little things that lead to stale JitBlock references.
|
|
Update(Core::GetState(m_system));
|
|
}
|
|
|
|
void JitBlockTableModel::OnPPCSymbolsUpdated()
|
|
{
|
|
// Previously, this was only a call to `UpdateSymbols`, but HLE patch engine code can
|
|
// invalidate JIT blocks when specific symbols are loaded. What can be done about it?
|
|
Update(Core::GetState(m_system));
|
|
}
|
|
|
|
void JitBlockTableModel::OnPPCBreakpointsChanged()
|
|
{
|
|
Update(Core::GetState(m_system));
|
|
}
|
|
|
|
void JitBlockTableModel::OnEmulationStateChanged(Core::State state)
|
|
{
|
|
Update(state);
|
|
}
|
|
|
|
static QString GetQStringDescription(const CPUEmuFeatureFlags flags)
|
|
{
|
|
static const std::array<QString, (FEATURE_FLAG_END_OF_ENUMERATION - 1) << 1> descriptions = {
|
|
QStringLiteral(""), QStringLiteral("DR"),
|
|
QStringLiteral("IR"), QStringLiteral("DR|IR"),
|
|
QStringLiteral("PERFMON"), QStringLiteral("DR|PERFMON"),
|
|
QStringLiteral("IR|PERFMON"), QStringLiteral("DR|IR|PERFMON"),
|
|
};
|
|
return descriptions[flags];
|
|
}
|
|
|
|
static QVariant GetValidSymbolStringVariant(const QVariant& symbol_name_v)
|
|
{
|
|
if (symbol_name_v.isValid())
|
|
return symbol_name_v;
|
|
return QStringLiteral(" --- ");
|
|
}
|
|
|
|
QVariant JitBlockTableModel::DisplayRoleData(const QModelIndex& index) const
|
|
{
|
|
const int column = index.column();
|
|
if (column == Column::Symbol)
|
|
return GetValidSymbolStringVariant(*m_symbol_list[index.row()]);
|
|
|
|
const JitBlock& jit_block = m_jit_blocks[index.row()];
|
|
switch (column)
|
|
{
|
|
case Column::PPCFeatureFlags:
|
|
return GetQStringDescription(jit_block.feature_flags);
|
|
case Column::EffectiveAddress:
|
|
return QString::number(jit_block.effectiveAddress, 16);
|
|
case Column::CodeBufferSize:
|
|
return QString::number(jit_block.originalSize * sizeof(UGeckoInstruction));
|
|
case Column::RepeatInstructions:
|
|
return QString::number(jit_block.originalSize - jit_block.physical_addresses.size());
|
|
case Column::HostNearCodeSize:
|
|
return QString::number(jit_block.near_end - jit_block.near_begin);
|
|
case Column::HostFarCodeSize:
|
|
return QString::number(jit_block.far_end - jit_block.far_begin);
|
|
}
|
|
const JitBlock::ProfileData* const profile_data = jit_block.profile_data.get();
|
|
if (profile_data == nullptr)
|
|
return QStringLiteral(" --- ");
|
|
switch (column)
|
|
{
|
|
case Column::RunCount:
|
|
return QString::number(profile_data->run_count);
|
|
case Column::CyclesSpent:
|
|
return QString::number(profile_data->cycles_spent);
|
|
case Column::CyclesAverage:
|
|
if (profile_data->run_count == 0)
|
|
return QStringLiteral(" --- ");
|
|
return QString::number(
|
|
static_cast<double>(profile_data->cycles_spent) / profile_data->run_count, 'f', 6);
|
|
case Column::CyclesPercent:
|
|
if (m_overall_cycles_spent == 0)
|
|
return QStringLiteral(" --- ");
|
|
return QStringLiteral("%1%").arg(100.0 * profile_data->cycles_spent / m_overall_cycles_spent,
|
|
10, 'f', 6);
|
|
case Column::TimeSpent:
|
|
{
|
|
const auto cast_time =
|
|
std::chrono::duration_cast<std::chrono::nanoseconds>(profile_data->time_spent);
|
|
return QString::number(cast_time.count());
|
|
}
|
|
case Column::TimeAverage:
|
|
{
|
|
if (profile_data->run_count == 0)
|
|
return QStringLiteral(" --- ");
|
|
const auto cast_time = std::chrono::duration_cast<std::chrono::duration<double, std::nano>>(
|
|
profile_data->time_spent);
|
|
return QString::number(cast_time.count() / profile_data->run_count, 'f', 6);
|
|
}
|
|
case Column::TimePercent:
|
|
{
|
|
if (m_overall_time_spent == JitBlock::ProfileData::Clock::duration{})
|
|
return QStringLiteral(" --- ");
|
|
return QStringLiteral("%1%").arg(
|
|
100.0 * profile_data->time_spent.count() / m_overall_time_spent.count(), 10, 'f', 6);
|
|
}
|
|
}
|
|
static_assert(Column::NumberOfColumns == 14);
|
|
Common::Unreachable();
|
|
}
|
|
|
|
QVariant JitBlockTableModel::TextAlignmentRoleData(const QModelIndex& index) const
|
|
{
|
|
switch (index.column())
|
|
{
|
|
case Column::PPCFeatureFlags:
|
|
case Column::EffectiveAddress:
|
|
return Qt::AlignCenter;
|
|
case Column::CodeBufferSize:
|
|
case Column::RepeatInstructions:
|
|
case Column::HostNearCodeSize:
|
|
case Column::HostFarCodeSize:
|
|
case Column::RunCount:
|
|
case Column::CyclesSpent:
|
|
case Column::CyclesAverage:
|
|
case Column::CyclesPercent:
|
|
case Column::TimeSpent:
|
|
case Column::TimeAverage:
|
|
case Column::TimePercent:
|
|
return QVariant::fromValue(Qt::AlignRight | Qt::AlignVCenter);
|
|
case Column::Symbol:
|
|
return QVariant::fromValue(Qt::AlignLeft | Qt::AlignVCenter);
|
|
}
|
|
static_assert(Column::NumberOfColumns == 14);
|
|
Common::Unreachable();
|
|
}
|
|
|
|
QVariant JitBlockTableModel::SortRoleData(const QModelIndex& index) const
|
|
{
|
|
const int column = index.column();
|
|
if (column == Column::Symbol)
|
|
return *m_symbol_list[index.row()];
|
|
|
|
const JitBlock& jit_block = m_jit_blocks[index.row()];
|
|
switch (column)
|
|
{
|
|
case Column::PPCFeatureFlags:
|
|
return jit_block.feature_flags;
|
|
case Column::EffectiveAddress:
|
|
return jit_block.effectiveAddress;
|
|
case Column::CodeBufferSize:
|
|
return static_cast<qulonglong>(jit_block.originalSize);
|
|
case Column::RepeatInstructions:
|
|
return static_cast<qulonglong>(jit_block.originalSize - jit_block.physical_addresses.size());
|
|
case Column::HostNearCodeSize:
|
|
return static_cast<qulonglong>(jit_block.near_end - jit_block.near_begin);
|
|
case Column::HostFarCodeSize:
|
|
return static_cast<qulonglong>(jit_block.far_end - jit_block.far_begin);
|
|
}
|
|
const JitBlock::ProfileData* const profile_data = jit_block.profile_data.get();
|
|
if (profile_data == nullptr)
|
|
return QVariant();
|
|
switch (column)
|
|
{
|
|
case Column::RunCount:
|
|
return static_cast<qulonglong>(profile_data->run_count);
|
|
case Column::CyclesSpent:
|
|
case Column::CyclesPercent:
|
|
return static_cast<qulonglong>(profile_data->cycles_spent);
|
|
case Column::CyclesAverage:
|
|
if (profile_data->run_count == 0)
|
|
return QVariant();
|
|
return static_cast<double>(profile_data->cycles_spent) / profile_data->run_count;
|
|
case Column::TimeSpent:
|
|
case Column::TimePercent:
|
|
return static_cast<qulonglong>(profile_data->time_spent.count());
|
|
case Column::TimeAverage:
|
|
if (profile_data->run_count == 0)
|
|
return QVariant();
|
|
return static_cast<double>(profile_data->time_spent.count()) / profile_data->run_count;
|
|
}
|
|
static_assert(Column::NumberOfColumns == 14);
|
|
Common::Unreachable();
|
|
}
|
|
|
|
QVariant JitBlockTableModel::data(const QModelIndex& index, int role) const
|
|
{
|
|
switch (role)
|
|
{
|
|
case Qt::DisplayRole:
|
|
return DisplayRoleData(index);
|
|
case Qt::TextAlignmentRole:
|
|
return TextAlignmentRoleData(index);
|
|
case UserRole::SortRole:
|
|
return SortRoleData(index);
|
|
}
|
|
return QVariant();
|
|
}
|
|
|
|
QVariant JitBlockTableModel::headerData(int section, Qt::Orientation orientation, int role) const
|
|
{
|
|
if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
|
|
return QVariant();
|
|
|
|
// These abbreviated table header display names have unabbreviated counterparts in JITWidget.cpp.
|
|
static constexpr std::array<const char*, Column::NumberOfColumns> headers = {
|
|
// i18n: PPC Feature Flags
|
|
QT_TR_NOOP("PPC Feat. Flags"),
|
|
// i18n: Effective Address
|
|
QT_TR_NOOP("Eff. Address"),
|
|
// i18n: Code Buffer Size
|
|
QT_TR_NOOP("Code Buff. Size"),
|
|
// i18n: Repeat Instructions
|
|
QT_TR_NOOP("Repeat Instr."),
|
|
// i18n: Host Near Code Size
|
|
QT_TR_NOOP("Host N. Size"),
|
|
// i18n: Host Far Code Size
|
|
QT_TR_NOOP("Host F. Size"),
|
|
QT_TR_NOOP("Run Count"),
|
|
QT_TR_NOOP("Cycles Spent"),
|
|
// i18n: Cycles Average
|
|
QT_TR_NOOP("Cycles Avg."),
|
|
// i18n: Cycles Percent
|
|
QT_TR_NOOP("Cycles %"),
|
|
QT_TR_NOOP("Time Spent (ns)"),
|
|
// i18n: Time Average (ns)
|
|
QT_TR_NOOP("Time Avg. (ns)"),
|
|
// i18n: Time Percent
|
|
QT_TR_NOOP("Time %"),
|
|
QT_TR_NOOP("Symbol"),
|
|
};
|
|
|
|
return tr(headers[section]);
|
|
}
|
|
|
|
int JitBlockTableModel::rowCount(const QModelIndex& parent) const
|
|
{
|
|
if (parent.isValid()) [[unlikely]]
|
|
return 0;
|
|
return m_jit_blocks.size();
|
|
}
|
|
|
|
int JitBlockTableModel::columnCount(const QModelIndex& parent) const
|
|
{
|
|
if (parent.isValid()) [[unlikely]]
|
|
return 0;
|
|
return Column::NumberOfColumns;
|
|
}
|
|
|
|
bool JitBlockTableModel::removeRows(int row, int count, const QModelIndex& parent)
|
|
{
|
|
if (parent.isValid() || row < 0) [[unlikely]]
|
|
return false;
|
|
if (count <= 0) [[unlikely]]
|
|
return true;
|
|
|
|
beginRemoveRows(parent, row, row + count - 1); // Last is inclusive in Qt!
|
|
for (const JitBlock& block :
|
|
std::span{m_jit_blocks.data() + row, static_cast<std::size_t>(count)})
|
|
{
|
|
m_jit_interface.EraseSingleBlock(block);
|
|
}
|
|
m_jit_blocks.remove(row, count);
|
|
m_symbol_list.remove(row, count);
|
|
endRemoveRows();
|
|
return true;
|
|
}
|
|
|
|
JitBlockTableModel::JitBlockTableModel(Core::System& system, JitInterface& jit_interface,
|
|
PPCSymbolDB& ppc_symbol_db, QObject* parent)
|
|
: QAbstractTableModel(parent), m_system(system), m_jit_interface(jit_interface),
|
|
m_ppc_symbol_db(ppc_symbol_db)
|
|
{
|
|
}
|
|
|
|
JitBlockTableModel::~JitBlockTableModel() = default;
|