mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-22 22:00:39 -06:00
Merge pull request #12714 from mitaclaw/jit-widget-refresh
DolphinQt: JIT Widget Refresh
This commit is contained in:
@ -222,6 +222,8 @@ add_executable(dolphin-emu
|
||||
Debugger/CodeWidget.h
|
||||
Debugger/GekkoSyntaxHighlight.cpp
|
||||
Debugger/GekkoSyntaxHighlight.h
|
||||
Debugger/JitBlockTableModel.cpp
|
||||
Debugger/JitBlockTableModel.h
|
||||
Debugger/JITWidget.cpp
|
||||
Debugger/JITWidget.h
|
||||
Debugger/MemoryViewWidget.cpp
|
||||
@ -297,6 +299,7 @@ add_executable(dolphin-emu
|
||||
QtUtils/BlockUserInputFilter.h
|
||||
QtUtils/ClearLayoutRecursively.cpp
|
||||
QtUtils/ClearLayoutRecursively.h
|
||||
QtUtils/ClickableStatusBar.h
|
||||
QtUtils/DolphinFileDialog.cpp
|
||||
QtUtils/DolphinFileDialog.h
|
||||
QtUtils/DoubleClickEventFilter.cpp
|
||||
|
@ -881,7 +881,7 @@ void CodeViewWidget::OnPPCComparison()
|
||||
{
|
||||
const u32 addr = GetContextAddress();
|
||||
|
||||
emit RequestPPCComparison(addr);
|
||||
emit RequestPPCComparison(addr, m_system.GetPPCState().msr.IR);
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnAddFunction()
|
||||
|
@ -55,7 +55,7 @@ public:
|
||||
u32 AddressForRow(int row) const;
|
||||
|
||||
signals:
|
||||
void RequestPPCComparison(u32 addr);
|
||||
void RequestPPCComparison(u32 address, bool translate_address);
|
||||
void ShowMemory(u32 address);
|
||||
void UpdateCodeWidget();
|
||||
|
||||
|
@ -210,6 +210,11 @@ void CodeWidget::OnBranchWatchDialog()
|
||||
m_branch_watch_dialog->activateWindow();
|
||||
}
|
||||
|
||||
void CodeWidget::OnSetCodeAddress(u32 address)
|
||||
{
|
||||
SetAddress(address, CodeViewWidget::SetAddressUpdate::WithDetailedUpdate);
|
||||
}
|
||||
|
||||
void CodeWidget::OnPPCSymbolsChanged()
|
||||
{
|
||||
UpdateSymbols();
|
||||
|
@ -43,6 +43,7 @@ public:
|
||||
void SetPC();
|
||||
|
||||
void OnBranchWatchDialog();
|
||||
void OnSetCodeAddress(u32 address);
|
||||
void ToggleBreakpoint();
|
||||
void AddBreakpoint();
|
||||
void SetAddress(u32 address, CodeViewWidget::SetAddressUpdate update);
|
||||
@ -50,7 +51,7 @@ public:
|
||||
void Update();
|
||||
void UpdateSymbols();
|
||||
signals:
|
||||
void RequestPPCComparison(u32 addr);
|
||||
void RequestPPCComparison(u32 address, bool translate_address);
|
||||
void ShowMemory(u32 address);
|
||||
|
||||
private:
|
||||
|
@ -3,216 +3,494 @@
|
||||
|
||||
#include "DolphinQt/Debugger/JITWidget.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <utility>
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QHeaderView>
|
||||
#include <QLineEdit>
|
||||
#include <QMenu>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QPushButton>
|
||||
#include <QSignalBlocker>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QSplitter>
|
||||
#include <QTableWidget>
|
||||
#include <QTextBrowser>
|
||||
#include <QTableView>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
#include "Common/CommonFuncs.h"
|
||||
#include "Common/GekkoDisassembler.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/PowerPC/PPCAnalyst.h"
|
||||
#include "Core/PowerPC/JitCommon/JitCache.h"
|
||||
#include "Core/PowerPC/JitInterface.h"
|
||||
#include "Core/PowerPC/MMU.h"
|
||||
#include "Core/PowerPC/PPCSymbolDB.h"
|
||||
#include "Core/System.h"
|
||||
#include "UICommon/Disassembler.h"
|
||||
|
||||
#include "DolphinQt/Debugger/JitBlockTableModel.h"
|
||||
#include "DolphinQt/Host.h"
|
||||
#include "DolphinQt/QtUtils/ClickableStatusBar.h"
|
||||
#include "DolphinQt/QtUtils/FromStdString.h"
|
||||
#include "DolphinQt/QtUtils/ModalMessageBox.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
#include "UICommon/UICommon.h"
|
||||
|
||||
JITWidget::JITWidget(QWidget* parent) : QDockWidget(parent)
|
||||
class JitBlockProxyModel final : public QSortFilterProxyModel
|
||||
{
|
||||
setWindowTitle(tr("JIT Blocks"));
|
||||
setObjectName(QStringLiteral("jitwidget"));
|
||||
friend JITWidget;
|
||||
|
||||
setHidden(!Settings::Instance().IsJITVisible() || !Settings::Instance().IsDebugModeEnabled());
|
||||
public:
|
||||
explicit JitBlockProxyModel(QObject* parent = nullptr);
|
||||
~JitBlockProxyModel() override;
|
||||
|
||||
setAllowedAreas(Qt::AllDockWidgetAreas);
|
||||
JitBlockProxyModel(const JitBlockProxyModel&) = delete;
|
||||
JitBlockProxyModel(JitBlockProxyModel&&) = delete;
|
||||
JitBlockProxyModel& operator=(const JitBlockProxyModel&) = delete;
|
||||
JitBlockProxyModel& operator=(JitBlockProxyModel&&) = delete;
|
||||
|
||||
auto& settings = Settings::GetQSettings();
|
||||
bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override;
|
||||
[[noreturn]] void setSourceModel(QAbstractItemModel* source_model) override;
|
||||
void setSourceModel(JitBlockTableModel* source_model);
|
||||
JitBlockTableModel* sourceModel() const;
|
||||
|
||||
CreateWidgets();
|
||||
const JitBlock& GetJitBlock(const QModelIndex& index);
|
||||
|
||||
restoreGeometry(settings.value(QStringLiteral("jitwidget/geometry")).toByteArray());
|
||||
// macOS: setHidden() needs to be evaluated before setFloating() for proper window presentation
|
||||
// according to Settings
|
||||
setFloating(settings.value(QStringLiteral("jitwidget/floating")).toBool());
|
||||
// Always connected slots (external signals)
|
||||
void OnSymbolTextChanged(const QString& text);
|
||||
template <std::optional<u32> JitBlockProxyModel::*member>
|
||||
void OnAddressTextChanged(const QString& text);
|
||||
|
||||
m_table_splitter->restoreState(
|
||||
settings.value(QStringLiteral("jitwidget/tablesplitter")).toByteArray());
|
||||
m_asm_splitter->restoreState(
|
||||
settings.value(QStringLiteral("jitwidget/asmsplitter")).toByteArray());
|
||||
private:
|
||||
std::optional<u32> m_em_address_min, m_em_address_max, m_pm_address_covered;
|
||||
QString m_symbol_name = {};
|
||||
};
|
||||
|
||||
connect(&Settings::Instance(), &Settings::JITVisibilityChanged, this,
|
||||
[this](bool visible) { setHidden(!visible); });
|
||||
|
||||
connect(&Settings::Instance(), &Settings::DebugModeToggled, this,
|
||||
[this](bool enabled) { setHidden(!enabled || !Settings::Instance().IsJITVisible()); });
|
||||
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, &JITWidget::Update);
|
||||
connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this, &JITWidget::Update);
|
||||
|
||||
ConnectWidgets();
|
||||
|
||||
#if defined(_M_X86_64)
|
||||
m_disassembler = GetNewDisassembler("x86");
|
||||
#elif defined(_M_ARM_64)
|
||||
m_disassembler = GetNewDisassembler("aarch64");
|
||||
#else
|
||||
m_disassembler = GetNewDisassembler("UNK");
|
||||
#endif
|
||||
const JitBlock& JitBlockProxyModel::GetJitBlock(const QModelIndex& index)
|
||||
{
|
||||
return sourceModel()->GetJitBlock(mapToSource(index));
|
||||
}
|
||||
|
||||
JITWidget::~JITWidget()
|
||||
void JitBlockProxyModel::OnSymbolTextChanged(const QString& text)
|
||||
{
|
||||
m_symbol_name = text;
|
||||
invalidateRowsFilter();
|
||||
}
|
||||
|
||||
template <std::optional<u32> JitBlockProxyModel::*member>
|
||||
void JitBlockProxyModel::OnAddressTextChanged(const QString& text)
|
||||
{
|
||||
bool ok = false;
|
||||
if (const u32 value = text.toUInt(&ok, 16); ok)
|
||||
this->*member = value;
|
||||
else
|
||||
this->*member = std::nullopt;
|
||||
invalidateRowsFilter();
|
||||
}
|
||||
|
||||
bool JitBlockProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
|
||||
{
|
||||
if (source_parent.isValid()) [[unlikely]]
|
||||
return false;
|
||||
if (!m_symbol_name.isEmpty())
|
||||
{
|
||||
if (const QVariant& symbol_name_v = *sourceModel()->GetSymbolList()[source_row];
|
||||
!symbol_name_v.isValid() || !static_cast<const QString*>(symbol_name_v.data())
|
||||
->contains(m_symbol_name, Qt::CaseInsensitive))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const JitBlock& block = sourceModel()->GetJitBlockRefs()[source_row];
|
||||
if (m_em_address_min.has_value())
|
||||
{
|
||||
if (block.effectiveAddress < m_em_address_min.value())
|
||||
return false;
|
||||
}
|
||||
if (m_em_address_max.has_value())
|
||||
{
|
||||
if (block.effectiveAddress > m_em_address_max.value())
|
||||
return false;
|
||||
}
|
||||
if (m_pm_address_covered.has_value())
|
||||
{
|
||||
if (!block.physical_addresses.contains(m_pm_address_covered.value()))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Virtual setSourceModel is forbidden for type-safety reasons.
|
||||
void JitBlockProxyModel::setSourceModel(QAbstractItemModel* source_model)
|
||||
{
|
||||
Crash();
|
||||
}
|
||||
|
||||
void JitBlockProxyModel::setSourceModel(JitBlockTableModel* source_model)
|
||||
{
|
||||
QSortFilterProxyModel::setSourceModel(source_model);
|
||||
}
|
||||
|
||||
JitBlockTableModel* JitBlockProxyModel::sourceModel() const
|
||||
{
|
||||
return static_cast<JitBlockTableModel*>(QSortFilterProxyModel::sourceModel());
|
||||
}
|
||||
|
||||
JitBlockProxyModel::JitBlockProxyModel(QObject* parent) : QSortFilterProxyModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
JitBlockProxyModel::~JitBlockProxyModel() = default;
|
||||
|
||||
void JITWidget::UpdateProfilingButton()
|
||||
{
|
||||
const QSignalBlocker blocker(m_toggle_profiling_button);
|
||||
const bool enabled = Config::Get(Config::MAIN_DEBUG_JIT_ENABLE_PROFILING);
|
||||
m_toggle_profiling_button->setText(enabled ? tr("Stop Profiling") : tr("Start Profiling"));
|
||||
m_toggle_profiling_button->setChecked(enabled);
|
||||
}
|
||||
|
||||
void JITWidget::UpdateOtherButtons(Core::State state)
|
||||
{
|
||||
const bool jit_exists = m_system.GetJitInterface().GetCore() != nullptr;
|
||||
m_clear_cache_button->setEnabled(jit_exists);
|
||||
m_wipe_profiling_button->setEnabled(jit_exists);
|
||||
}
|
||||
|
||||
void JITWidget::UpdateDebugFont(const QFont& font)
|
||||
{
|
||||
m_table_view->setFont(font);
|
||||
m_ppc_asm_widget->setFont(font);
|
||||
m_host_near_asm_widget->setFont(font);
|
||||
m_host_far_asm_widget->setFont(font);
|
||||
}
|
||||
|
||||
void JITWidget::ClearDisassembly()
|
||||
{
|
||||
m_ppc_asm_widget->clear();
|
||||
m_host_near_asm_widget->clear();
|
||||
m_host_far_asm_widget->clear();
|
||||
m_status_bar->clearMessage();
|
||||
}
|
||||
|
||||
void JITWidget::ShowFreeMemoryStatus()
|
||||
{
|
||||
const std::vector memory_stats = m_system.GetJitInterface().GetMemoryStats();
|
||||
QString message = tr("Free memory:");
|
||||
for (const auto& [name, stats] : memory_stats)
|
||||
{
|
||||
const auto& [free_size, fragmentation_ratio] = stats;
|
||||
// i18n: Of each memory region, %1 is its remaining size displayed in an appropriate scale
|
||||
// of bytes (e.g. MiB), %2 is its untranslated name, and %3 is its fragmentation percentage.
|
||||
message.append(tr(" %1 %2 (%3% fragmented)")
|
||||
.arg(QString::fromStdString(UICommon::FormatSize(free_size, 2)))
|
||||
.arg(QtUtils::FromStdString(name))
|
||||
.arg(fragmentation_ratio * 100.0, 0, 'f', 2));
|
||||
}
|
||||
m_status_bar->showMessage(message);
|
||||
}
|
||||
|
||||
void JITWidget::UpdateContent(Core::State state)
|
||||
{
|
||||
ClearDisassembly();
|
||||
if (state == Core::State::Paused)
|
||||
ShowFreeMemoryStatus();
|
||||
}
|
||||
|
||||
static void DisassembleCodeBuffer(const JitBlock& block, PPCSymbolDB& ppc_symbol_db,
|
||||
std::ostream& stream)
|
||||
{
|
||||
// Instructions are 4 byte aligned, so next_address = 1 will never produce a false-negative.
|
||||
for (u32 next_address = 1; const auto& [address, inst] : block.original_buffer)
|
||||
{
|
||||
if (address != next_address)
|
||||
{
|
||||
stream << ppc_symbol_db.GetDescription(address) << '\n';
|
||||
next_address = address;
|
||||
}
|
||||
fmt::print(stream, "0x{:08x}\t{}\n", address,
|
||||
Common::GekkoDisassembler::Disassemble(inst.hex, address));
|
||||
next_address += sizeof(UGeckoInstruction);
|
||||
}
|
||||
}
|
||||
|
||||
void JITWidget::CrossDisassemble(const JitBlock& block)
|
||||
{
|
||||
// TODO C++20: std::ostringstream::view() + QtUtils::FromStdString + std::ostream::seekp(0) would
|
||||
// save a lot of wasted allocation here, but compiler support for the first thing isn't here yet.
|
||||
std::ostringstream stream;
|
||||
DisassembleCodeBuffer(block, m_system.GetPPCSymbolDB(), stream);
|
||||
m_ppc_asm_widget->setPlainText(QString::fromStdString(std::move(stream).str()));
|
||||
|
||||
auto& jit_interface = m_system.GetJitInterface();
|
||||
|
||||
const auto host_near_instruction_count = jit_interface.DisassembleNearCode(block, stream);
|
||||
m_host_near_asm_widget->setPlainText(QString::fromStdString(std::move(stream).str()));
|
||||
|
||||
const auto host_far_instruction_count = jit_interface.DisassembleFarCode(block, stream);
|
||||
m_host_far_asm_widget->setPlainText(QString::fromStdString(std::move(stream).str()));
|
||||
|
||||
// i18n: "near" and "far" refer to the near code cache and far code cache of Dolphin's JITs.
|
||||
// %1 and %2 are instruction counts from the near and far code caches, respectively. %3 is a
|
||||
// percentage calculated from how inefficient (in other words, "blown-up") a given JIT block's
|
||||
// recompilation was when considering the host instruction count vs the PPC instruction count.
|
||||
m_status_bar->showMessage(tr("Host instruction count: %1 near %2 far (%3% blowup)")
|
||||
.arg(host_near_instruction_count)
|
||||
.arg(host_far_instruction_count)
|
||||
.arg(static_cast<double>(100 * (host_near_instruction_count +
|
||||
host_far_instruction_count)) /
|
||||
block.originalSize -
|
||||
100.0,
|
||||
0, 'f', 2));
|
||||
}
|
||||
|
||||
void JITWidget::CrossDisassemble(const QModelIndex& index)
|
||||
{
|
||||
if (index.isValid())
|
||||
{
|
||||
CrossDisassemble(m_table_proxy->GetJitBlock(index));
|
||||
return;
|
||||
}
|
||||
UpdateContent(Core::GetState(m_system));
|
||||
}
|
||||
|
||||
void JITWidget::CrossDisassemble()
|
||||
{
|
||||
CrossDisassemble(m_table_view->currentIndex());
|
||||
}
|
||||
|
||||
void JITWidget::TableEraseBlocks()
|
||||
{
|
||||
auto* const selection_model = m_table_view->selectionModel();
|
||||
QModelIndexList index_list = selection_model->selectedRows();
|
||||
selection_model->clear(); // Side effect: currentChanged will be emitted (this is intended).
|
||||
|
||||
std::ranges::transform(index_list, index_list.begin(), [this](const QModelIndex& index) {
|
||||
return m_table_proxy->mapToSource(index);
|
||||
});
|
||||
std::ranges::sort(index_list, std::less{}); // QModelIndex is incompatible with std::ranges::less
|
||||
for (const QModelIndex& index : std::ranges::reverse_view{index_list})
|
||||
{
|
||||
if (!index.isValid())
|
||||
continue;
|
||||
m_table_model->removeRow(index.row());
|
||||
}
|
||||
}
|
||||
|
||||
void JITWidget::LoadQSettings()
|
||||
{
|
||||
auto& settings = Settings::GetQSettings();
|
||||
|
||||
restoreGeometry(settings.value(QStringLiteral("jitwidget/geometry")).toByteArray());
|
||||
setHidden(!Settings::Instance().IsJITVisible() || !Settings::Instance().IsDebugModeEnabled());
|
||||
// macOS: setFloating() needs to be after setHidden() for proper window presentation
|
||||
// according to Settings
|
||||
setFloating(settings.value(QStringLiteral("jitwidget/floating")).toBool());
|
||||
m_table_view->horizontalHeader()->restoreState(
|
||||
settings.value(QStringLiteral("jitwidget/tableheader/state")).toByteArray());
|
||||
m_table_splitter->restoreState(
|
||||
settings.value(QStringLiteral("jitwidget/tablesplitter")).toByteArray());
|
||||
m_disasm_splitter->restoreState(
|
||||
settings.value(QStringLiteral("jitwidget/disasmsplitter")).toByteArray());
|
||||
}
|
||||
|
||||
void JITWidget::SaveQSettings() const
|
||||
{
|
||||
auto& settings = Settings::GetQSettings();
|
||||
|
||||
settings.setValue(QStringLiteral("jitwidget/geometry"), saveGeometry());
|
||||
settings.setValue(QStringLiteral("jitwidget/floating"), isFloating());
|
||||
settings.setValue(QStringLiteral("jitwidget/tableheader/state"),
|
||||
m_table_view->horizontalHeader()->saveState());
|
||||
settings.setValue(QStringLiteral("jitwidget/tablesplitter"), m_table_splitter->saveState());
|
||||
settings.setValue(QStringLiteral("jitwidget/asmsplitter"), m_asm_splitter->saveState());
|
||||
settings.setValue(QStringLiteral("jitwidget/disasmsplitter"), m_disasm_splitter->saveState());
|
||||
}
|
||||
|
||||
void JITWidget::CreateWidgets()
|
||||
void JITWidget::ConnectSlots()
|
||||
{
|
||||
m_table_widget = new QTableWidget;
|
||||
|
||||
m_table_widget->setTabKeyNavigation(false);
|
||||
m_table_widget->setColumnCount(7);
|
||||
m_table_widget->setHorizontalHeaderLabels(
|
||||
{tr("Address"), tr("PPC Size"), tr("Host Size"),
|
||||
// i18n: The symbolic name of a code block
|
||||
tr("Symbol"),
|
||||
// i18n: These are the kinds of flags that a CPU uses (e.g. carry),
|
||||
// not the kinds of flags that represent e.g. countries
|
||||
tr("Flags"),
|
||||
// i18n: The number of times a code block has been executed
|
||||
tr("NumExec"),
|
||||
// i18n: Performance cost, not monetary cost
|
||||
tr("Cost")});
|
||||
|
||||
m_ppc_asm_widget = new QTextBrowser;
|
||||
m_host_asm_widget = new QTextBrowser;
|
||||
|
||||
m_table_splitter = new QSplitter(Qt::Vertical);
|
||||
m_asm_splitter = new QSplitter(Qt::Horizontal);
|
||||
|
||||
m_refresh_button = new QPushButton(tr("Refresh"));
|
||||
|
||||
m_table_splitter->addWidget(m_table_widget);
|
||||
m_table_splitter->addWidget(m_asm_splitter);
|
||||
|
||||
m_asm_splitter->addWidget(m_ppc_asm_widget);
|
||||
m_asm_splitter->addWidget(m_host_asm_widget);
|
||||
|
||||
QWidget* widget = new QWidget;
|
||||
auto* layout = new QVBoxLayout;
|
||||
layout->setContentsMargins(2, 2, 2, 2);
|
||||
widget->setLayout(layout);
|
||||
|
||||
layout->addWidget(m_table_splitter);
|
||||
layout->addWidget(m_refresh_button);
|
||||
|
||||
setWidget(widget);
|
||||
auto* const host = Host::GetInstance();
|
||||
connect(host, &Host::JitCacheCleared, this, &JITWidget::OnJitCacheCleared);
|
||||
connect(host, &Host::UpdateDisasmDialog, this, &JITWidget::OnUpdateDisasmDialog);
|
||||
connect(host, &Host::PPCSymbolsChanged, this, &JITWidget::OnPPCSymbolsUpdated);
|
||||
connect(host, &Host::PPCBreakpointsChanged, this, &JITWidget::OnPPCBreakpointsChanged);
|
||||
auto* const settings = &Settings::Instance();
|
||||
connect(settings, &Settings::ConfigChanged, this, &JITWidget::OnConfigChanged);
|
||||
connect(settings, &Settings::DebugFontChanged, this, &JITWidget::OnDebugFontChanged);
|
||||
connect(settings, &Settings::EmulationStateChanged, this, &JITWidget::OnEmulationStateChanged);
|
||||
}
|
||||
|
||||
void JITWidget::ConnectWidgets()
|
||||
void JITWidget::DisconnectSlots()
|
||||
{
|
||||
connect(m_refresh_button, &QPushButton::clicked, this, &JITWidget::Update);
|
||||
auto* const host = Host::GetInstance();
|
||||
disconnect(host, &Host::JitCacheCleared, this, &JITWidget::OnJitCacheCleared);
|
||||
disconnect(host, &Host::UpdateDisasmDialog, this, &JITWidget::OnUpdateDisasmDialog);
|
||||
disconnect(host, &Host::PPCSymbolsChanged, this, &JITWidget::OnPPCSymbolsUpdated);
|
||||
disconnect(host, &Host::PPCBreakpointsChanged, this, &JITWidget::OnPPCBreakpointsChanged);
|
||||
auto* const settings = &Settings::Instance();
|
||||
disconnect(settings, &Settings::ConfigChanged, this, &JITWidget::OnConfigChanged);
|
||||
disconnect(settings, &Settings::DebugFontChanged, this, &JITWidget::OnDebugFontChanged);
|
||||
disconnect(settings, &Settings::EmulationStateChanged, this, &JITWidget::OnEmulationStateChanged);
|
||||
}
|
||||
|
||||
void JITWidget::Compare(u32 address)
|
||||
void JITWidget::Show()
|
||||
{
|
||||
m_address = address;
|
||||
ConnectSlots();
|
||||
// Handle every slot that may have missed a signal while this widget was hidden.
|
||||
// OnJitCacheCleared() can be skipped.
|
||||
// OnUpdateDisasmDialog() can be skipped.
|
||||
// OnPPCSymbolsUpdated() can be skipped.
|
||||
// OnPPCBreakpointsChanged() can be skipped.
|
||||
OnConfigChanged();
|
||||
OnDebugFontChanged(Settings::Instance().GetDebugFont());
|
||||
OnEmulationStateChanged(Core::GetState(m_system));
|
||||
}
|
||||
|
||||
void JITWidget::Hide()
|
||||
{
|
||||
DisconnectSlots();
|
||||
ClearDisassembly();
|
||||
}
|
||||
|
||||
void JITWidget::OnRequestPPCComparison(u32 address, bool translate_address)
|
||||
{
|
||||
Settings::Instance().SetJITVisible(true);
|
||||
raise();
|
||||
m_host_asm_widget->setFocus();
|
||||
|
||||
Update();
|
||||
if (translate_address)
|
||||
{
|
||||
const std::optional<u32> pm_address = m_system.GetMMU().GetTranslatedAddress(address);
|
||||
if (!pm_address.has_value())
|
||||
{
|
||||
ModalMessageBox::warning(
|
||||
this, tr("Error"),
|
||||
tr("Effective address %1 has no physical address translation.").arg(address, 0, 16));
|
||||
return;
|
||||
}
|
||||
address = pm_address.value();
|
||||
}
|
||||
m_pm_address_covered_line_edit->setText(QString::number(address, 16));
|
||||
}
|
||||
|
||||
void JITWidget::Update()
|
||||
void JITWidget::OnVisibilityToggled(bool visible)
|
||||
{
|
||||
if (!isVisible())
|
||||
return;
|
||||
setHidden(!visible);
|
||||
}
|
||||
|
||||
if (!m_address || (Core::GetState(Core::System::GetInstance()) != Core::State::Paused))
|
||||
void JITWidget::OnDebugModeToggled(bool enabled)
|
||||
{
|
||||
setHidden(!enabled || !Settings::Instance().IsJITVisible());
|
||||
}
|
||||
|
||||
void JITWidget::OnToggleProfiling(bool enabled)
|
||||
{
|
||||
Config::SetBaseOrCurrent(Config::MAIN_DEBUG_JIT_ENABLE_PROFILING, enabled);
|
||||
}
|
||||
|
||||
void JITWidget::OnClearCache()
|
||||
{
|
||||
m_system.GetJitInterface().ClearCache(Core::CPUThreadGuard{m_system});
|
||||
}
|
||||
|
||||
void JITWidget::OnWipeProfiling()
|
||||
{
|
||||
m_system.GetJitInterface().WipeBlockProfilingData(Core::CPUThreadGuard{m_system});
|
||||
}
|
||||
|
||||
void JITWidget::OnTableCurrentChanged(const QModelIndex& current, const QModelIndex& previous)
|
||||
{
|
||||
CrossDisassemble(current);
|
||||
}
|
||||
|
||||
void JITWidget::OnTableDoubleClicked(const QModelIndex& index)
|
||||
{
|
||||
emit SetCodeAddress(m_table_proxy->GetJitBlock(index).effectiveAddress);
|
||||
}
|
||||
|
||||
void JITWidget::OnTableContextMenu(const QPoint& pos)
|
||||
{
|
||||
// There needs to be an option somewhere for a user to recover from hiding every column.
|
||||
if (m_table_view->horizontalHeader()->hiddenSectionCount() == Column::NumberOfColumns)
|
||||
{
|
||||
m_ppc_asm_widget->setHtml(QStringLiteral("<i>%1</i>").arg(tr("(ppc)")));
|
||||
m_host_asm_widget->setHtml(QStringLiteral("<i>%1</i>").arg(tr("(host)")));
|
||||
m_column_visibility_menu->exec(m_table_view->viewport()->mapToGlobal(pos));
|
||||
return;
|
||||
}
|
||||
m_table_context_menu->exec(m_table_view->viewport()->mapToGlobal(pos));
|
||||
}
|
||||
|
||||
// TODO: Actually do something with the table (Wx doesn't)
|
||||
void JITWidget::OnTableHeaderContextMenu(const QPoint& pos)
|
||||
{
|
||||
m_column_visibility_menu->exec(m_table_view->horizontalHeader()->mapToGlobal(pos));
|
||||
}
|
||||
|
||||
// Get host side code disassembly
|
||||
auto host_instructions_disasm = DisassembleBlock(m_disassembler.get(), m_address);
|
||||
m_address = host_instructions_disasm.entry_address;
|
||||
void JITWidget::OnTableMenuViewCode()
|
||||
{
|
||||
// TODO: CodeWidget doesn't support it yet, but eventually signal if the address should be
|
||||
// translated with ((block.feature_flags & CPUEmuFeatureFlags::FEATURE_FLAG_MSR_IR) != 0).
|
||||
if (const QModelIndex& index = m_table_view->currentIndex(); index.isValid())
|
||||
emit SetCodeAddress(m_table_proxy->GetJitBlock(index).effectiveAddress);
|
||||
}
|
||||
|
||||
m_host_asm_widget->setHtml(
|
||||
QStringLiteral("<pre>%1</pre>").arg(QString::fromStdString(host_instructions_disasm.text)));
|
||||
void JITWidget::OnTableMenuEraseBlocks()
|
||||
{
|
||||
TableEraseBlocks(); // Side effect: currentChanged will be emitted (this is intended).
|
||||
// Because currentChanged has been emitted, OnTableCurrentChanged has already handled the rest.
|
||||
}
|
||||
|
||||
// == Fill in ppc box
|
||||
u32 ppc_addr = m_address;
|
||||
PPCAnalyst::CodeBuffer code_buffer(32000);
|
||||
PPCAnalyst::BlockStats st;
|
||||
PPCAnalyst::BlockRegStats gpa;
|
||||
PPCAnalyst::BlockRegStats fpa;
|
||||
PPCAnalyst::CodeBlock code_block;
|
||||
PPCAnalyst::PPCAnalyzer analyzer;
|
||||
analyzer.SetDebuggingEnabled(Config::IsDebuggingEnabled());
|
||||
analyzer.SetBranchFollowingEnabled(Config::Get(Config::MAIN_JIT_FOLLOW_BRANCH));
|
||||
analyzer.SetFloatExceptionsEnabled(Config::Get(Config::MAIN_FLOAT_EXCEPTIONS));
|
||||
analyzer.SetDivByZeroExceptionsEnabled(Config::Get(Config::MAIN_DIVIDE_BY_ZERO_EXCEPTIONS));
|
||||
analyzer.SetOption(PPCAnalyst::PPCAnalyzer::OPTION_CONDITIONAL_CONTINUE);
|
||||
analyzer.SetOption(PPCAnalyst::PPCAnalyzer::OPTION_BRANCH_FOLLOW);
|
||||
void JITWidget::OnStatusBarPressed()
|
||||
{
|
||||
if (Core::GetState(m_system) == Core::State::Paused)
|
||||
ShowFreeMemoryStatus();
|
||||
}
|
||||
|
||||
code_block.m_stats = &st;
|
||||
code_block.m_gpa = &gpa;
|
||||
code_block.m_fpa = &fpa;
|
||||
void JITWidget::OnJitCacheCleared()
|
||||
{
|
||||
if (Core::GetState(m_system) != Core::State::Paused)
|
||||
return;
|
||||
ClearDisassembly();
|
||||
ShowFreeMemoryStatus();
|
||||
}
|
||||
|
||||
if (analyzer.Analyze(ppc_addr, &code_block, &code_buffer, code_buffer.size()) != 0xFFFFFFFF)
|
||||
{
|
||||
std::string ppc_disasm_str;
|
||||
auto ppc_disasm = std::back_inserter(ppc_disasm_str);
|
||||
for (u32 i = 0; i < code_block.m_num_instructions; i++)
|
||||
{
|
||||
const PPCAnalyst::CodeOp& op = code_buffer[i];
|
||||
const std::string opcode = Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address);
|
||||
fmt::format_to(ppc_disasm, "{:08x} {}\n", op.address, opcode);
|
||||
}
|
||||
void JITWidget::OnUpdateDisasmDialog()
|
||||
{
|
||||
if (Core::GetState(m_system) != Core::State::Paused)
|
||||
return;
|
||||
CrossDisassemble();
|
||||
}
|
||||
|
||||
// Add stats to the end of the ppc box since it's generally the shortest.
|
||||
fmt::format_to(ppc_disasm, "\n{} estimated cycles", st.numCycles);
|
||||
fmt::format_to(ppc_disasm, "\nNum instr: PPC: {} Host: {}", code_block.m_num_instructions,
|
||||
host_instructions_disasm.instruction_count);
|
||||
if (code_block.m_num_instructions != 0 && host_instructions_disasm.instruction_count != 0)
|
||||
{
|
||||
fmt::format_to(
|
||||
ppc_disasm, " (blowup: {}%)",
|
||||
100 * host_instructions_disasm.instruction_count / code_block.m_num_instructions - 100);
|
||||
}
|
||||
void JITWidget::OnPPCSymbolsUpdated()
|
||||
{
|
||||
if (Core::GetState(m_system) != Core::State::Paused)
|
||||
return;
|
||||
CrossDisassemble();
|
||||
}
|
||||
|
||||
fmt::format_to(ppc_disasm, "\nNum bytes: PPC: {} Host: {}", code_block.m_num_instructions * 4,
|
||||
host_instructions_disasm.code_size);
|
||||
if (code_block.m_num_instructions != 0 && host_instructions_disasm.code_size != 0)
|
||||
{
|
||||
fmt::format_to(
|
||||
ppc_disasm, " (blowup: {}%)",
|
||||
100 * host_instructions_disasm.code_size / (4 * code_block.m_num_instructions) - 100);
|
||||
}
|
||||
void JITWidget::OnPPCBreakpointsChanged()
|
||||
{
|
||||
// Whatever row(s) might have been selected could no longer exist, because adding or removing
|
||||
// breakpoints can invalidate JIT blocks. We must clear the selection to avoid stale indices.
|
||||
auto* const selection_model = m_table_view->selectionModel();
|
||||
selection_model->clear(); // Side effect: currentChanged will be emitted (this is intended).
|
||||
// Because currentChanged has been emitted, OnTableCurrentChanged has already handled the rest.
|
||||
}
|
||||
|
||||
m_ppc_asm_widget->setHtml(
|
||||
QStringLiteral("<pre>%1</pre>").arg(QString::fromStdString(ppc_disasm_str)));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_host_asm_widget->setHtml(
|
||||
QStringLiteral("<pre>%1</pre>")
|
||||
.arg(QString::fromStdString(fmt::format("(non-code address: {:08x})", m_address))));
|
||||
m_ppc_asm_widget->setHtml(QStringLiteral("<i>---</i>"));
|
||||
}
|
||||
void JITWidget::OnConfigChanged()
|
||||
{
|
||||
UpdateProfilingButton();
|
||||
}
|
||||
|
||||
void JITWidget::OnDebugFontChanged(const QFont& font)
|
||||
{
|
||||
UpdateDebugFont(font);
|
||||
}
|
||||
|
||||
void JITWidget::OnEmulationStateChanged(Core::State state)
|
||||
{
|
||||
UpdateOtherButtons(state);
|
||||
UpdateContent(state);
|
||||
}
|
||||
|
||||
void JITWidget::closeEvent(QCloseEvent*)
|
||||
@ -220,7 +498,186 @@ void JITWidget::closeEvent(QCloseEvent*)
|
||||
Settings::Instance().SetJITVisible(false);
|
||||
}
|
||||
|
||||
void JITWidget::showEvent(QShowEvent* event)
|
||||
void JITWidget::showEvent(QShowEvent*)
|
||||
{
|
||||
Update();
|
||||
emit ShowSignal();
|
||||
Show();
|
||||
}
|
||||
|
||||
void JITWidget::hideEvent(QHideEvent*)
|
||||
{
|
||||
emit HideSignal();
|
||||
Hide();
|
||||
}
|
||||
|
||||
JITWidget::JITWidget(Core::System& system, QWidget* parent) : QDockWidget(parent), m_system(system)
|
||||
{
|
||||
setWindowTitle(tr("JIT Blocks"));
|
||||
setObjectName(QStringLiteral("jitwidget"));
|
||||
setAllowedAreas(Qt::AllDockWidgetAreas);
|
||||
|
||||
auto* const settings = &Settings::Instance();
|
||||
connect(settings, &Settings::JITVisibilityChanged, this, &JITWidget::OnVisibilityToggled);
|
||||
connect(settings, &Settings::DebugModeToggled, this, &JITWidget::OnDebugModeToggled);
|
||||
|
||||
m_table_view = new QTableView(nullptr);
|
||||
m_table_proxy = new JitBlockProxyModel(m_table_view);
|
||||
m_table_model = new JitBlockTableModel(m_system, m_system.GetJitInterface(),
|
||||
m_system.GetPPCSymbolDB(), m_table_proxy);
|
||||
|
||||
connect(this, &JITWidget::HideSignal, m_table_model, &JitBlockTableModel::OnHideSignal);
|
||||
connect(this, &JITWidget::ShowSignal, m_table_model, &JitBlockTableModel::OnShowSignal);
|
||||
|
||||
m_table_proxy->setSourceModel(m_table_model);
|
||||
m_table_proxy->setSortRole(UserRole::SortRole);
|
||||
m_table_proxy->setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
|
||||
m_table_view->setModel(m_table_proxy);
|
||||
m_table_view->setSortingEnabled(true);
|
||||
m_table_view->sortByColumn(Column::EffectiveAddress, Qt::AscendingOrder);
|
||||
m_table_view->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
m_table_view->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
m_table_view->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
m_table_view->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
m_table_view->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
m_table_view->setCornerButtonEnabled(false);
|
||||
m_table_view->verticalHeader()->hide();
|
||||
connect(m_table_view, &QTableView::doubleClicked, this, &JITWidget::OnTableDoubleClicked);
|
||||
connect(m_table_view, &QTableView::customContextMenuRequested, this,
|
||||
&JITWidget::OnTableContextMenu);
|
||||
|
||||
auto* const horizontal_header = m_table_view->horizontalHeader();
|
||||
horizontal_header->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
horizontal_header->setStretchLastSection(true);
|
||||
horizontal_header->setSectionsMovable(true);
|
||||
horizontal_header->setFirstSectionMovable(true);
|
||||
connect(horizontal_header, &QHeaderView::sortIndicatorChanged, m_table_model,
|
||||
&JitBlockTableModel::OnSortIndicatorChanged);
|
||||
connect(horizontal_header, &QHeaderView::customContextMenuRequested, this,
|
||||
&JITWidget::OnTableHeaderContextMenu);
|
||||
|
||||
auto* const selection_model = m_table_view->selectionModel();
|
||||
connect(selection_model, &QItemSelectionModel::currentChanged, this,
|
||||
&JITWidget::OnTableCurrentChanged);
|
||||
|
||||
auto* const controls_layout = new QHBoxLayout(nullptr);
|
||||
const auto address_filter_routine = [&](QLineEdit* line_edit, const QString& placeholder_text,
|
||||
void (JitBlockProxyModel::*slot)(const QString&)) {
|
||||
line_edit->setPlaceholderText(placeholder_text);
|
||||
connect(line_edit, &QLineEdit::textChanged, m_table_proxy, slot);
|
||||
controls_layout->addWidget(line_edit);
|
||||
};
|
||||
address_filter_routine(
|
||||
new QLineEdit(nullptr), tr("Min Effective Address"),
|
||||
&JitBlockProxyModel::OnAddressTextChanged<&JitBlockProxyModel::m_em_address_min>);
|
||||
address_filter_routine(
|
||||
new QLineEdit(nullptr), tr("Max Effective Address"),
|
||||
&JitBlockProxyModel::OnAddressTextChanged<&JitBlockProxyModel::m_em_address_max>);
|
||||
address_filter_routine(
|
||||
m_pm_address_covered_line_edit = new QLineEdit(nullptr), tr("Recompiles Physical Address"),
|
||||
&JitBlockProxyModel::OnAddressTextChanged<&JitBlockProxyModel::m_pm_address_covered>);
|
||||
|
||||
auto* const symbol_name_line_edit = new QLineEdit(nullptr);
|
||||
symbol_name_line_edit->setPlaceholderText(tr("Symbol Name"));
|
||||
connect(symbol_name_line_edit, &QLineEdit::textChanged, m_table_model,
|
||||
&JitBlockTableModel::OnFilterSymbolTextChanged);
|
||||
connect(symbol_name_line_edit, &QLineEdit::textChanged, m_table_proxy,
|
||||
&JitBlockProxyModel::OnSymbolTextChanged);
|
||||
controls_layout->addWidget(symbol_name_line_edit);
|
||||
|
||||
m_toggle_profiling_button = new QPushButton(nullptr);
|
||||
m_toggle_profiling_button->setToolTip(
|
||||
tr("Toggle software JIT block profiling (will clear the JIT cache)."));
|
||||
m_toggle_profiling_button->setCheckable(true);
|
||||
connect(m_toggle_profiling_button, &QPushButton::toggled, this, &JITWidget::OnToggleProfiling);
|
||||
controls_layout->addWidget(m_toggle_profiling_button);
|
||||
|
||||
m_clear_cache_button = new QPushButton(tr("Clear Cache"), nullptr);
|
||||
connect(m_clear_cache_button, &QPushButton::clicked, this, &JITWidget::OnClearCache);
|
||||
controls_layout->addWidget(m_clear_cache_button);
|
||||
|
||||
m_wipe_profiling_button = new QPushButton(tr("Wipe Profiling"), nullptr);
|
||||
m_wipe_profiling_button->setToolTip(tr("Re-initialize software JIT block profiling data."));
|
||||
connect(m_wipe_profiling_button, &QPushButton::clicked, this, &JITWidget::OnWipeProfiling);
|
||||
controls_layout->addWidget(m_wipe_profiling_button);
|
||||
|
||||
m_disasm_splitter = new QSplitter(Qt::Horizontal, nullptr);
|
||||
const auto text_box_routine = [&](QPlainTextEdit* text_edit, const QString& placeholder_text) {
|
||||
text_edit->setWordWrapMode(QTextOption::NoWrap);
|
||||
text_edit->setPlaceholderText(placeholder_text);
|
||||
text_edit->setReadOnly(true);
|
||||
m_disasm_splitter->addWidget(text_edit);
|
||||
};
|
||||
text_box_routine(m_ppc_asm_widget = new QPlainTextEdit(nullptr), tr("PPC Instruction Coverage"));
|
||||
text_box_routine(m_host_near_asm_widget = new QPlainTextEdit(nullptr),
|
||||
tr("Host Near Code Cache"));
|
||||
text_box_routine(m_host_far_asm_widget = new QPlainTextEdit(nullptr), tr("Host Far Code Cache"));
|
||||
|
||||
m_table_splitter = new QSplitter(Qt::Vertical, nullptr);
|
||||
m_table_splitter->addWidget(m_table_view);
|
||||
m_table_splitter->addWidget(m_disasm_splitter);
|
||||
|
||||
m_status_bar = new ClickableStatusBar(nullptr);
|
||||
m_status_bar->setSizeGripEnabled(false);
|
||||
connect(m_status_bar, &ClickableStatusBar::pressed, this, &JITWidget::OnStatusBarPressed);
|
||||
|
||||
m_table_context_menu = new QMenu(this);
|
||||
m_table_context_menu->addAction(tr("View &Code"), this, &JITWidget::OnTableMenuViewCode);
|
||||
m_table_context_menu->addAction(tr("&Erase Block(s)"), this, &JITWidget::OnTableMenuEraseBlocks);
|
||||
|
||||
LoadQSettings();
|
||||
|
||||
m_column_visibility_menu = new QMenu(this);
|
||||
// These table header display names have abbreviated counterparts in JitBlockTableModel.cpp
|
||||
static constexpr std::array<const char*, Column::NumberOfColumns> headers = {
|
||||
QT_TR_NOOP("PPC Feature Flags"),
|
||||
// i18n: "Effective" means this memory address might be translated within the MMU.
|
||||
QT_TR_NOOP("Effective Address"),
|
||||
QT_TR_NOOP("Code Buffer Size"),
|
||||
// i18n: This means to say it is a count of PPC instructions recompiled more than once.
|
||||
QT_TR_NOOP("Repeat Instructions"),
|
||||
// i18n: "Near Code" refers to the near code cache of Dolphin's JITs.
|
||||
QT_TR_NOOP("Host Near Code Size"),
|
||||
// i18n: "Far Code" refers to the far code cache of Dolphin's JITs.
|
||||
QT_TR_NOOP("Host Far Code Size"),
|
||||
QT_TR_NOOP("Run Count"),
|
||||
// i18n: "Cycles" means instruction cycles.
|
||||
QT_TR_NOOP("Cycles Spent"),
|
||||
// i18n: "Cycles" means instruction cycles.
|
||||
QT_TR_NOOP("Cycles Average"),
|
||||
// i18n: "Cycles" means instruction cycles.
|
||||
QT_TR_NOOP("Cycles Percent"),
|
||||
// i18n: "ns" is an abbreviation of nanoseconds.
|
||||
QT_TR_NOOP("Time Spent (ns)"),
|
||||
// i18n: "ns" is an abbreviation of nanoseconds.
|
||||
QT_TR_NOOP("Time Average (ns)"),
|
||||
QT_TR_NOOP("Time Percent"),
|
||||
// i18n: "Symbol" means debugging symbol (its name in particular).
|
||||
QT_TR_NOOP("Symbol"),
|
||||
};
|
||||
for (int column = 0; column < Column::NumberOfColumns; ++column)
|
||||
{
|
||||
auto* const action =
|
||||
m_column_visibility_menu->addAction(tr(headers[column]), [this, column](bool enabled) {
|
||||
m_table_view->setColumnHidden(column, !enabled);
|
||||
});
|
||||
action->setChecked(!m_table_view->isColumnHidden(column));
|
||||
action->setCheckable(true);
|
||||
}
|
||||
|
||||
auto* const main_layout = new QVBoxLayout(nullptr);
|
||||
main_layout->setContentsMargins(2, 2, 2, 2);
|
||||
main_layout->setSpacing(0);
|
||||
main_layout->addLayout(controls_layout);
|
||||
main_layout->addWidget(m_table_splitter);
|
||||
main_layout->addWidget(m_status_bar);
|
||||
|
||||
auto* const main_widget = new QWidget(nullptr);
|
||||
main_widget->setLayout(main_layout);
|
||||
setWidget(main_widget);
|
||||
}
|
||||
|
||||
JITWidget::~JITWidget()
|
||||
{
|
||||
SaveQSettings();
|
||||
}
|
||||
|
@ -4,42 +4,128 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDockWidget>
|
||||
#include <memory>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
class BreakpointWidget;
|
||||
class ClickableStatusBar;
|
||||
class CodeWidget;
|
||||
namespace Core
|
||||
{
|
||||
enum class State;
|
||||
class System;
|
||||
} // namespace Core
|
||||
struct JitBlock;
|
||||
class JitBlockProxyModel;
|
||||
class JitBlockTableModel;
|
||||
namespace JitBlockTableModelColumn
|
||||
{
|
||||
enum EnumType : int;
|
||||
}
|
||||
namespace JitBlockTableModelUserRole
|
||||
{
|
||||
enum EnumType : int;
|
||||
}
|
||||
class MemoryWidget;
|
||||
class QCloseEvent;
|
||||
class QFont;
|
||||
class QLineEdit;
|
||||
class QMenu;
|
||||
class QPlainTextEdit;
|
||||
class QPushButton;
|
||||
class QShowEvent;
|
||||
class QSplitter;
|
||||
class QTextBrowser;
|
||||
class QTableWidget;
|
||||
class QPushButton;
|
||||
class HostDisassembler;
|
||||
class QTableView;
|
||||
|
||||
class JITWidget : public QDockWidget
|
||||
class JITWidget final : public QDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit JITWidget(QWidget* parent = nullptr);
|
||||
~JITWidget();
|
||||
|
||||
void Compare(u32 address);
|
||||
using Column = JitBlockTableModelColumn::EnumType;
|
||||
using UserRole = JitBlockTableModelUserRole::EnumType;
|
||||
|
||||
signals:
|
||||
void HideSignal();
|
||||
void ShowSignal();
|
||||
void SetCodeAddress(u32 address);
|
||||
|
||||
public:
|
||||
explicit JITWidget(Core::System& system, QWidget* parent = nullptr);
|
||||
~JITWidget() override;
|
||||
|
||||
JITWidget(const JITWidget&) = delete;
|
||||
JITWidget(JITWidget&&) = delete;
|
||||
JITWidget& operator=(const JITWidget&) = delete;
|
||||
JITWidget& operator=(JITWidget&&) = delete;
|
||||
|
||||
// Always connected slots (external signals)
|
||||
void OnRequestPPCComparison(u32 address, bool translate_address);
|
||||
|
||||
private:
|
||||
void Update();
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
|
||||
void closeEvent(QCloseEvent*) override;
|
||||
void closeEvent(QCloseEvent* event) override;
|
||||
void showEvent(QShowEvent* event) override;
|
||||
void hideEvent(QHideEvent* event) override;
|
||||
|
||||
QTableWidget* m_table_widget;
|
||||
QTextBrowser* m_ppc_asm_widget;
|
||||
QTextBrowser* m_host_asm_widget;
|
||||
void UpdateProfilingButton();
|
||||
void UpdateOtherButtons(Core::State state);
|
||||
void UpdateDebugFont(const QFont& font);
|
||||
void ClearDisassembly();
|
||||
void ShowFreeMemoryStatus();
|
||||
void UpdateContent(Core::State state);
|
||||
void CrossDisassemble(const JitBlock& block);
|
||||
void CrossDisassemble(const QModelIndex& index);
|
||||
void CrossDisassemble();
|
||||
void TableEraseBlocks();
|
||||
|
||||
// Setup and teardown
|
||||
void LoadQSettings();
|
||||
void SaveQSettings() const;
|
||||
void ConnectSlots();
|
||||
void DisconnectSlots();
|
||||
void Show();
|
||||
void Hide();
|
||||
|
||||
// Always connected slots (external signals)
|
||||
void OnVisibilityToggled(bool visible);
|
||||
void OnDebugModeToggled(bool visible);
|
||||
|
||||
// Always connected slots (internal signals)
|
||||
void OnToggleProfiling(bool enabled);
|
||||
void OnClearCache();
|
||||
void OnWipeProfiling();
|
||||
void OnTableCurrentChanged(const QModelIndex& current, const QModelIndex& previous);
|
||||
void OnTableDoubleClicked(const QModelIndex& index);
|
||||
void OnTableContextMenu(const QPoint& pos);
|
||||
void OnTableHeaderContextMenu(const QPoint& pos);
|
||||
void OnTableMenuViewCode();
|
||||
void OnTableMenuEraseBlocks();
|
||||
void OnStatusBarPressed();
|
||||
|
||||
// Conditionally connected slots (external signals)
|
||||
void OnJitCacheCleared();
|
||||
void OnUpdateDisasmDialog();
|
||||
void OnPPCSymbolsUpdated();
|
||||
void OnPPCBreakpointsChanged();
|
||||
void OnConfigChanged();
|
||||
void OnDebugFontChanged(const QFont& font);
|
||||
void OnEmulationStateChanged(Core::State state);
|
||||
|
||||
Core::System& m_system;
|
||||
|
||||
QLineEdit* m_pm_address_covered_line_edit;
|
||||
QPushButton* m_clear_cache_button;
|
||||
QPushButton* m_toggle_profiling_button;
|
||||
QPushButton* m_wipe_profiling_button;
|
||||
QTableView* m_table_view;
|
||||
JitBlockProxyModel* m_table_proxy;
|
||||
JitBlockTableModel* m_table_model;
|
||||
QPlainTextEdit* m_ppc_asm_widget;
|
||||
QPlainTextEdit* m_host_near_asm_widget;
|
||||
QPlainTextEdit* m_host_far_asm_widget;
|
||||
QSplitter* m_table_splitter;
|
||||
QSplitter* m_asm_splitter;
|
||||
QPushButton* m_refresh_button;
|
||||
QSplitter* m_disasm_splitter;
|
||||
ClickableStatusBar* m_status_bar;
|
||||
|
||||
std::unique_ptr<HostDisassembler> m_disassembler;
|
||||
u32 m_address = 0;
|
||||
QMenu* m_table_context_menu;
|
||||
QMenu* m_column_visibility_menu;
|
||||
};
|
||||
|
452
Source/Core/DolphinQt/Debugger/JitBlockTableModel.cpp
Normal file
452
Source/Core/DolphinQt/Debugger/JitBlockTableModel.cpp
Normal file
@ -0,0 +1,452 @@
|
||||
// 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::JitCacheCleared, this, &JitBlockTableModel::OnJitCacheCleared);
|
||||
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::JitCacheCleared, this, &JitBlockTableModel::OnJitCacheCleared);
|
||||
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::OnJitCacheCleared()
|
||||
{
|
||||
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()
|
||||
{
|
||||
UpdateSymbols();
|
||||
}
|
||||
|
||||
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;
|
126
Source/Core/DolphinQt/Debugger/JitBlockTableModel.h
Normal file
126
Source/Core/DolphinQt/Debugger/JitBlockTableModel.h
Normal file
@ -0,0 +1,126 @@
|
||||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QList>
|
||||
#include <QVariant>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Lazy.h"
|
||||
#include "Core/PowerPC/JitCommon/JitCache.h"
|
||||
|
||||
namespace Core
|
||||
{
|
||||
enum class State;
|
||||
class System;
|
||||
} // namespace Core
|
||||
class JitInterface;
|
||||
class PPCSymbolDB;
|
||||
class QString;
|
||||
|
||||
namespace JitBlockTableModelColumn
|
||||
{
|
||||
enum EnumType : int
|
||||
{
|
||||
PPCFeatureFlags = 0,
|
||||
EffectiveAddress,
|
||||
CodeBufferSize,
|
||||
RepeatInstructions,
|
||||
HostNearCodeSize,
|
||||
HostFarCodeSize,
|
||||
RunCount,
|
||||
CyclesSpent,
|
||||
CyclesAverage,
|
||||
CyclesPercent,
|
||||
TimeSpent,
|
||||
TimeAverage,
|
||||
TimePercent,
|
||||
Symbol,
|
||||
NumberOfColumns,
|
||||
};
|
||||
}
|
||||
|
||||
namespace JitBlockTableModelUserRole
|
||||
{
|
||||
enum EnumType : int
|
||||
{
|
||||
SortRole = Qt::UserRole,
|
||||
};
|
||||
}
|
||||
|
||||
class JitBlockTableModel final : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
using Column = JitBlockTableModelColumn::EnumType;
|
||||
using UserRole = JitBlockTableModelUserRole::EnumType;
|
||||
using JitBlockRefs = QList<std::reference_wrapper<const JitBlock>>;
|
||||
using SymbolListValueType = Common::Lazy<QVariant>;
|
||||
using SymbolList = QList<SymbolListValueType>;
|
||||
|
||||
public:
|
||||
explicit JitBlockTableModel(Core::System& system, JitInterface& jit_interface,
|
||||
PPCSymbolDB& ppc_symbol_db, QObject* parent = nullptr);
|
||||
~JitBlockTableModel() override;
|
||||
|
||||
JitBlockTableModel(const JitBlockTableModel&) = delete;
|
||||
JitBlockTableModel(JitBlockTableModel&&) = delete;
|
||||
JitBlockTableModel& operator=(const JitBlockTableModel&) = delete;
|
||||
JitBlockTableModel& operator=(JitBlockTableModel&&) = delete;
|
||||
|
||||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation,
|
||||
int role = Qt::DisplayRole) const override;
|
||||
int rowCount(const QModelIndex& parent = QModelIndex{}) const override;
|
||||
int columnCount(const QModelIndex& parent = QModelIndex{}) const override;
|
||||
bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex{}) override;
|
||||
|
||||
const JitBlock& GetJitBlock(const QModelIndex& index) const;
|
||||
const JitBlockRefs& GetJitBlockRefs() const { return m_jit_blocks; }
|
||||
const SymbolList& GetSymbolList() const { return m_symbol_list; }
|
||||
|
||||
// Always connected slots (external signals)
|
||||
void OnShowSignal();
|
||||
void OnHideSignal();
|
||||
void OnSortIndicatorChanged(int logicalIndex, Qt::SortOrder order);
|
||||
void OnFilterSymbolTextChanged(const QString& string);
|
||||
|
||||
private:
|
||||
[[nodiscard]] QVariant DisplayRoleData(const QModelIndex& index) const;
|
||||
[[nodiscard]] QVariant TextAlignmentRoleData(const QModelIndex& index) const;
|
||||
[[nodiscard]] QVariant SortRoleData(const QModelIndex& index) const;
|
||||
|
||||
void SumOverallCosts();
|
||||
void PrefetchSymbols();
|
||||
void Clear();
|
||||
void Update(Core::State state);
|
||||
void UpdateProfileData();
|
||||
void UpdateSymbols();
|
||||
|
||||
// Setup and teardown
|
||||
void ConnectSlots();
|
||||
void DisconnectSlots();
|
||||
void Show();
|
||||
void Hide();
|
||||
|
||||
// Conditionally connected slots (external signals)
|
||||
void OnJitCacheCleared();
|
||||
void OnJitProfileDataWiped();
|
||||
void OnUpdateDisasmDialog();
|
||||
void OnPPCSymbolsUpdated();
|
||||
void OnPPCBreakpointsChanged();
|
||||
void OnEmulationStateChanged(Core::State state);
|
||||
|
||||
Core::System& m_system;
|
||||
JitInterface& m_jit_interface;
|
||||
PPCSymbolDB& m_ppc_symbol_db;
|
||||
|
||||
JitBlockRefs m_jit_blocks;
|
||||
SymbolList m_symbol_list;
|
||||
u64 m_overall_cycles_spent;
|
||||
JitBlock::ProfileData::Clock::duration m_overall_time_spent;
|
||||
bool m_sorting_by_symbols = false;
|
||||
bool m_filtering_by_symbols = false;
|
||||
};
|
@ -145,6 +145,7 @@
|
||||
<ClCompile Include="Debugger\CodeViewWidget.cpp" />
|
||||
<ClCompile Include="Debugger\CodeWidget.cpp" />
|
||||
<ClCompile Include="Debugger\GekkoSyntaxHighlight.cpp" />
|
||||
<ClCompile Include="Debugger\JitBlockTableModel.cpp" />
|
||||
<ClCompile Include="Debugger\JITWidget.cpp" />
|
||||
<ClCompile Include="Debugger\MemoryViewWidget.cpp" />
|
||||
<ClCompile Include="Debugger\MemoryWidget.cpp" />
|
||||
@ -249,6 +250,7 @@
|
||||
<ClInclude Include="GBAHost.h" />
|
||||
<ClInclude Include="QtUtils\ActionHelper.h" />
|
||||
<ClInclude Include="QtUtils\ClearLayoutRecursively.h" />
|
||||
<QtMoc Include="QtUtils\ClickableStatusBar.h" />
|
||||
<ClInclude Include="QtUtils\DolphinFileDialog.h" />
|
||||
<ClInclude Include="QtUtils\ImageConverter.h" />
|
||||
<ClInclude Include="QtUtils\ModalMessageBox.h" />
|
||||
@ -359,6 +361,7 @@
|
||||
<QtMoc Include="Debugger\CodeViewWidget.h" />
|
||||
<QtMoc Include="Debugger\CodeWidget.h" />
|
||||
<QtMoc Include="Debugger\GekkoSyntaxHighlight.h" />
|
||||
<QtMoc Include="Debugger\JitBlockTableModel.h" />
|
||||
<QtMoc Include="Debugger\JITWidget.h" />
|
||||
<QtMoc Include="Debugger\MemoryViewWidget.h" />
|
||||
<QtMoc Include="Debugger\MemoryWidget.h" />
|
||||
|
@ -256,6 +256,16 @@ void Host_UpdateDisasmDialog()
|
||||
QueueOnObject(QApplication::instance(), [] { emit Host::GetInstance()->UpdateDisasmDialog(); });
|
||||
}
|
||||
|
||||
void Host_JitCacheCleared()
|
||||
{
|
||||
QueueOnObject(QApplication::instance(), [] { emit Host::GetInstance()->JitCacheCleared(); });
|
||||
}
|
||||
|
||||
void Host_JitProfileDataWiped()
|
||||
{
|
||||
QueueOnObject(QApplication::instance(), [] { emit Host::GetInstance()->JitProfileDataWiped(); });
|
||||
}
|
||||
|
||||
void Host_PPCSymbolsChanged()
|
||||
{
|
||||
QueueOnObject(QApplication::instance(), [] { emit Host::GetInstance()->PPCSymbolsChanged(); });
|
||||
|
@ -40,6 +40,8 @@ signals:
|
||||
void RequestStop();
|
||||
void RequestRenderSize(int w, int h);
|
||||
void UpdateDisasmDialog();
|
||||
void JitCacheCleared();
|
||||
void JitProfileDataWiped();
|
||||
void PPCSymbolsChanged();
|
||||
void PPCBreakpointsChanged();
|
||||
|
||||
|
@ -463,7 +463,7 @@ void MainWindow::CreateComponents()
|
||||
m_wii_tas_input_windows[i] = new WiiTASInputWindow(nullptr, i);
|
||||
}
|
||||
|
||||
m_jit_widget = new JITWidget(this);
|
||||
m_jit_widget = new JITWidget(Core::System::GetInstance(), this);
|
||||
m_log_widget = new LogWidget(this);
|
||||
m_log_config_widget = new LogConfigWidget(this);
|
||||
m_memory_widget = new MemoryWidget(Core::System::GetInstance(), this);
|
||||
@ -488,6 +488,7 @@ void MainWindow::CreateComponents()
|
||||
m_code_widget->SetAddress(addr, CodeViewWidget::SetAddressUpdate::WithDetailedUpdate);
|
||||
};
|
||||
|
||||
connect(m_jit_widget, &JITWidget::SetCodeAddress, m_code_widget, &CodeWidget::OnSetCodeAddress);
|
||||
connect(m_watch_widget, &WatchWidget::RequestMemoryBreakpoint, request_memory_breakpoint);
|
||||
connect(m_watch_widget, &WatchWidget::ShowMemory, m_memory_widget, &MemoryWidget::SetAddress);
|
||||
connect(m_register_widget, &RegisterWidget::RequestMemoryBreakpoint, request_memory_breakpoint);
|
||||
@ -500,7 +501,8 @@ void MainWindow::CreateComponents()
|
||||
connect(m_thread_widget, &ThreadWidget::RequestViewInMemory, request_view_in_memory);
|
||||
connect(m_thread_widget, &ThreadWidget::RequestViewInCode, request_view_in_code);
|
||||
|
||||
connect(m_code_widget, &CodeWidget::RequestPPCComparison, m_jit_widget, &JITWidget::Compare);
|
||||
connect(m_code_widget, &CodeWidget::RequestPPCComparison, m_jit_widget,
|
||||
&JITWidget::OnRequestPPCComparison);
|
||||
connect(m_code_widget, &CodeWidget::ShowMemory, m_memory_widget, &MemoryWidget::SetAddress);
|
||||
connect(m_memory_widget, &MemoryWidget::ShowCode, m_code_widget, [this](u32 address) {
|
||||
m_code_widget->SetAddress(address, CodeViewWidget::SetAddressUpdate::WithDetailedUpdate);
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <QFontDialog>
|
||||
#include <QInputDialog>
|
||||
#include <QMap>
|
||||
#include <QSignalBlocker>
|
||||
#include <QUrl>
|
||||
|
||||
#include <fmt/format.h>
|
||||
@ -95,6 +96,7 @@ MenuBar::MenuBar(QWidget* parent) : QMenuBar(parent)
|
||||
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
|
||||
[=, this](Core::State state) { OnEmulationStateChanged(state); });
|
||||
connect(&Settings::Instance(), &Settings::ConfigChanged, this, &MenuBar::OnConfigChanged);
|
||||
connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this,
|
||||
[this] { OnEmulationStateChanged(Core::GetState(Core::System::GetInstance())); });
|
||||
|
||||
@ -155,7 +157,8 @@ void MenuBar::OnEmulationStateChanged(Core::State state)
|
||||
m_jit_clear_cache->setEnabled(running);
|
||||
m_jit_log_coverage->setEnabled(!running);
|
||||
m_jit_search_instruction->setEnabled(running);
|
||||
m_jit_write_cache_log_dump->setEnabled(running && jit_exists);
|
||||
m_jit_wipe_profiling_data->setEnabled(jit_exists);
|
||||
m_jit_write_cache_log_dump->setEnabled(jit_exists);
|
||||
|
||||
// Symbols
|
||||
m_symbols->setEnabled(running);
|
||||
@ -166,6 +169,12 @@ void MenuBar::OnEmulationStateChanged(Core::State state)
|
||||
OnDebugModeToggled(Settings::Instance().IsDebugModeEnabled());
|
||||
}
|
||||
|
||||
void MenuBar::OnConfigChanged()
|
||||
{
|
||||
const QSignalBlocker blocker(m_jit_profile_blocks);
|
||||
m_jit_profile_blocks->setChecked(Config::Get(Config::MAIN_DEBUG_JIT_ENABLE_PROFILING));
|
||||
}
|
||||
|
||||
void MenuBar::OnDebugModeToggled(bool enabled)
|
||||
{
|
||||
// Options
|
||||
@ -196,6 +205,12 @@ void MenuBar::OnDebugModeToggled(bool enabled)
|
||||
}
|
||||
}
|
||||
|
||||
void MenuBar::OnWipeJitBlockProfilingData()
|
||||
{
|
||||
auto& system = Core::System::GetInstance();
|
||||
system.GetJitInterface().WipeBlockProfilingData(Core::CPUThreadGuard{system});
|
||||
}
|
||||
|
||||
void MenuBar::OnWriteJitBlockLogDump()
|
||||
{
|
||||
const std::string filename = fmt::format("{}{}.txt", File::GetUserPath(D_DUMPDEBUG_JITBLOCKS_IDX),
|
||||
@ -922,6 +937,8 @@ void MenuBar::AddJITMenu()
|
||||
connect(m_jit_profile_blocks, &QAction::toggled, [](bool enabled) {
|
||||
Config::SetBaseOrCurrent(Config::MAIN_DEBUG_JIT_ENABLE_PROFILING, enabled);
|
||||
});
|
||||
m_jit_wipe_profiling_data = m_jit->addAction(tr("Wipe JIT Block Profiling Data"), this,
|
||||
&MenuBar::OnWipeJitBlockProfilingData);
|
||||
m_jit_write_cache_log_dump =
|
||||
m_jit->addAction(tr("Write JIT Block Log Dump"), this, &MenuBar::OnWriteJitBlockLogDump);
|
||||
|
||||
|
@ -127,6 +127,7 @@ signals:
|
||||
|
||||
private:
|
||||
void OnEmulationStateChanged(Core::State state);
|
||||
void OnConfigChanged();
|
||||
|
||||
void AddFileMenu();
|
||||
|
||||
@ -185,6 +186,7 @@ private:
|
||||
void OnRecordingStatusChanged(bool recording);
|
||||
void OnReadOnlyModeChanged(bool read_only);
|
||||
void OnDebugModeToggled(bool enabled);
|
||||
void OnWipeJitBlockProfilingData();
|
||||
void OnWriteJitBlockLogDump();
|
||||
|
||||
QString GetSignatureSelector() const;
|
||||
@ -270,6 +272,7 @@ private:
|
||||
QAction* m_jit_log_coverage;
|
||||
QAction* m_jit_search_instruction;
|
||||
QAction* m_jit_profile_blocks;
|
||||
QAction* m_jit_wipe_profiling_data;
|
||||
QAction* m_jit_write_cache_log_dump;
|
||||
QAction* m_jit_off;
|
||||
QAction* m_jit_loadstore_off;
|
||||
|
22
Source/Core/DolphinQt/QtUtils/ClickableStatusBar.h
Normal file
22
Source/Core/DolphinQt/QtUtils/ClickableStatusBar.h
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QStatusBar>
|
||||
|
||||
// I wanted a QStatusBar that emits a signal when clicked. Qt only provides event overrides.
|
||||
class ClickableStatusBar final : public QStatusBar
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
signals:
|
||||
void pressed();
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent* event) override { emit pressed(); }
|
||||
|
||||
public:
|
||||
explicit ClickableStatusBar(QWidget* parent) : QStatusBar(parent) {}
|
||||
~ClickableStatusBar() override = default;
|
||||
};
|
Reference in New Issue
Block a user