2018-04-09 13:31:20 +00:00
|
|
|
// Copyright 2018 Dolphin Emulator Project
|
|
|
|
// Licensed under GPLv2+
|
|
|
|
// Refer to the license.txt file included.
|
|
|
|
|
2018-07-06 22:40:15 +00:00
|
|
|
#include "DolphinQt/Debugger/JITWidget.h"
|
2018-04-09 13:31:20 +00:00
|
|
|
|
|
|
|
#include <QPushButton>
|
|
|
|
#include <QSplitter>
|
|
|
|
#include <QTableWidget>
|
|
|
|
#include <QTextBrowser>
|
|
|
|
#include <QVBoxLayout>
|
|
|
|
|
|
|
|
#include "Common/GekkoDisassembler.h"
|
|
|
|
#include "Common/StringUtil.h"
|
|
|
|
#include "Core/PowerPC/PPCAnalyst.h"
|
|
|
|
#include "UICommon/Disassembler.h"
|
|
|
|
|
2020-04-14 22:12:35 +00:00
|
|
|
#include "DolphinQt/Host.h"
|
2018-07-06 22:40:15 +00:00
|
|
|
#include "DolphinQt/Settings.h"
|
2018-04-09 13:31:20 +00:00
|
|
|
|
|
|
|
JITWidget::JITWidget(QWidget* parent) : QDockWidget(parent)
|
|
|
|
{
|
|
|
|
setWindowTitle(tr("JIT Blocks"));
|
2018-05-20 01:58:54 +00:00
|
|
|
setObjectName(QStringLiteral("jitwidget"));
|
|
|
|
|
2019-02-28 20:19:04 +00:00
|
|
|
setHidden(!Settings::Instance().IsJITVisible() || !Settings::Instance().IsDebugModeEnabled());
|
|
|
|
|
2018-04-09 13:31:20 +00:00
|
|
|
setAllowedAreas(Qt::AllDockWidgetAreas);
|
|
|
|
|
|
|
|
auto& settings = Settings::GetQSettings();
|
|
|
|
|
|
|
|
CreateWidgets();
|
|
|
|
|
|
|
|
restoreGeometry(settings.value(QStringLiteral("jitwidget/geometry")).toByteArray());
|
2019-02-28 20:19:04 +00:00
|
|
|
// macOS: setHidden() needs to be evaluated before setFloating() for proper window presentation
|
|
|
|
// according to Settings
|
2018-04-09 13:31:20 +00:00
|
|
|
setFloating(settings.value(QStringLiteral("jitwidget/floating")).toBool());
|
|
|
|
|
|
|
|
m_table_splitter->restoreState(
|
|
|
|
settings.value(QStringLiteral("jitwidget/tablesplitter")).toByteArray());
|
|
|
|
m_asm_splitter->restoreState(
|
|
|
|
settings.value(QStringLiteral("jitwidget/asmsplitter")).toByteArray());
|
|
|
|
|
|
|
|
connect(&Settings::Instance(), &Settings::JITVisibilityChanged,
|
|
|
|
[this](bool visible) { setHidden(!visible); });
|
|
|
|
|
|
|
|
connect(&Settings::Instance(), &Settings::DebugModeToggled,
|
|
|
|
[this](bool enabled) { setHidden(!enabled || !Settings::Instance().IsJITVisible()); });
|
|
|
|
|
|
|
|
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, &JITWidget::Update);
|
2020-04-14 22:12:35 +00:00
|
|
|
connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this, &JITWidget::Update);
|
2018-04-09 13:31:20 +00:00
|
|
|
|
|
|
|
ConnectWidgets();
|
|
|
|
|
|
|
|
#if defined(_M_X86)
|
|
|
|
m_disassembler = GetNewDisassembler("x86");
|
|
|
|
#elif defined(_M_ARM_64)
|
|
|
|
m_disassembler = GetNewDisassembler("aarch64");
|
|
|
|
#else
|
|
|
|
m_disassembler = GetNewDisassembler("UNK");
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
JITWidget::~JITWidget()
|
|
|
|
{
|
|
|
|
auto& settings = Settings::GetQSettings();
|
|
|
|
|
|
|
|
settings.setValue(QStringLiteral("jitwidget/geometry"), saveGeometry());
|
|
|
|
settings.setValue(QStringLiteral("jitwidget/floating"), isFloating());
|
|
|
|
settings.setValue(QStringLiteral("jitwidget/tablesplitter"), m_table_splitter->saveState());
|
|
|
|
settings.setValue(QStringLiteral("jitwidget/asmsplitter"), m_asm_splitter->saveState());
|
|
|
|
}
|
|
|
|
|
|
|
|
void JITWidget::CreateWidgets()
|
|
|
|
{
|
|
|
|
m_table_widget = new QTableWidget;
|
|
|
|
|
2020-02-07 02:34:27 +00:00
|
|
|
m_table_widget->setTabKeyNavigation(false);
|
2018-04-09 13:31:20 +00:00
|
|
|
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;
|
2018-08-02 19:50:23 +00:00
|
|
|
layout->setContentsMargins(2, 2, 2, 2);
|
2018-04-09 13:31:20 +00:00
|
|
|
widget->setLayout(layout);
|
|
|
|
|
|
|
|
layout->addWidget(m_table_splitter);
|
|
|
|
layout->addWidget(m_refresh_button);
|
|
|
|
|
|
|
|
setWidget(widget);
|
|
|
|
}
|
|
|
|
|
|
|
|
void JITWidget::ConnectWidgets()
|
|
|
|
{
|
2019-07-23 22:18:58 +00:00
|
|
|
connect(m_refresh_button, &QPushButton::clicked, this, &JITWidget::Update);
|
2018-04-09 13:31:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void JITWidget::Compare(u32 address)
|
|
|
|
{
|
|
|
|
m_address = address;
|
|
|
|
Update();
|
|
|
|
}
|
|
|
|
|
|
|
|
void JITWidget::Update()
|
|
|
|
{
|
2019-07-06 08:50:11 +00:00
|
|
|
if (!isVisible())
|
|
|
|
return;
|
|
|
|
|
2018-04-09 13:31:20 +00:00
|
|
|
if (!m_address)
|
|
|
|
{
|
|
|
|
m_ppc_asm_widget->setHtml(QStringLiteral("<i>%1</i>").arg(tr("(ppc)")));
|
|
|
|
m_host_asm_widget->setHtml(QStringLiteral("<i>%1</i>").arg(tr("(host)")));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Actually do something with the table (Wx doesn't)
|
|
|
|
|
|
|
|
// Get host side code disassembly
|
|
|
|
u32 host_instructions_count = 0;
|
|
|
|
u32 host_code_size = 0;
|
|
|
|
std::string host_instructions_disasm;
|
|
|
|
host_instructions_disasm =
|
|
|
|
DisassembleBlock(m_disassembler.get(), &m_address, &host_instructions_count, &host_code_size);
|
|
|
|
|
|
|
|
m_host_asm_widget->setHtml(
|
|
|
|
QStringLiteral("<pre>%1</pre>").arg(QString::fromStdString(host_instructions_disasm)));
|
|
|
|
|
|
|
|
// == 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.SetOption(PPCAnalyst::PPCAnalyzer::OPTION_CONDITIONAL_CONTINUE);
|
|
|
|
analyzer.SetOption(PPCAnalyst::PPCAnalyzer::OPTION_BRANCH_FOLLOW);
|
|
|
|
|
|
|
|
code_block.m_stats = &st;
|
|
|
|
code_block.m_gpa = &gpa;
|
|
|
|
code_block.m_fpa = &fpa;
|
|
|
|
|
2018-05-18 20:14:31 +00:00
|
|
|
if (analyzer.Analyze(ppc_addr, &code_block, &code_buffer, code_buffer.size()) != 0xFFFFFFFF)
|
2018-04-09 13:31:20 +00:00
|
|
|
{
|
|
|
|
std::ostringstream ppc_disasm;
|
|
|
|
for (u32 i = 0; i < code_block.m_num_instructions; i++)
|
|
|
|
{
|
2018-05-18 20:14:31 +00:00
|
|
|
const PPCAnalyst::CodeOp& op = code_buffer[i];
|
2018-05-25 20:29:56 +00:00
|
|
|
const std::string opcode = Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address);
|
2018-04-09 13:31:20 +00:00
|
|
|
ppc_disasm << std::setfill('0') << std::setw(8) << std::hex << op.address;
|
|
|
|
ppc_disasm << " " << opcode << std::endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add stats to the end of the ppc box since it's generally the shortest.
|
|
|
|
ppc_disasm << std::dec << std::endl;
|
|
|
|
|
|
|
|
// Add some generic analysis
|
|
|
|
if (st.isFirstBlockOfFunction)
|
|
|
|
ppc_disasm << "(first block of function)" << std::endl;
|
|
|
|
if (st.isLastBlockOfFunction)
|
|
|
|
ppc_disasm << "(last block of function)" << std::endl;
|
|
|
|
|
|
|
|
ppc_disasm << st.numCycles << " estimated cycles" << std::endl;
|
|
|
|
|
|
|
|
ppc_disasm << "Num instr: PPC: " << code_block.m_num_instructions
|
|
|
|
<< " Host: " << host_instructions_count << " (blowup: "
|
|
|
|
<< 100 * host_instructions_count / code_block.m_num_instructions - 100 << "%)"
|
|
|
|
<< std::endl;
|
|
|
|
|
|
|
|
ppc_disasm << "Num bytes: PPC: " << code_block.m_num_instructions * 4
|
|
|
|
<< " Host: " << host_code_size
|
|
|
|
<< " (blowup: " << 100 * host_code_size / (4 * code_block.m_num_instructions) - 100
|
|
|
|
<< "%)" << std::endl;
|
|
|
|
|
|
|
|
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(StringFromFormat("(non-code address: %08x)", m_address))));
|
|
|
|
m_ppc_asm_widget->setHtml(QStringLiteral("<i>---</i>"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void JITWidget::closeEvent(QCloseEvent*)
|
|
|
|
{
|
|
|
|
Settings::Instance().SetJITVisible(false);
|
|
|
|
}
|
2019-07-06 08:50:11 +00:00
|
|
|
|
|
|
|
void JITWidget::showEvent(QShowEvent* event)
|
|
|
|
{
|
|
|
|
Update();
|
|
|
|
}
|