diff --git a/src/models/CMakeLists.txt b/src/models/CMakeLists.txt index faa31587..8d7caaa2 100644 --- a/src/models/CMakeLists.txt +++ b/src/models/CMakeLists.txt @@ -7,6 +7,7 @@ add_library( codedelegate.cpp costdelegate.cpp data.cpp + disassemblyentry.cpp disassemblymodel.cpp disassemblyoutput.cpp eventmodel.cpp diff --git a/src/models/disassemblyentry.cpp b/src/models/disassemblyentry.cpp new file mode 100644 index 00000000..5cd3a0da --- /dev/null +++ b/src/models/disassemblyentry.cpp @@ -0,0 +1,112 @@ +/* + SPDX-FileCopyrightText: Lieven Hey + SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "disassemblyentry.h" + +DisassemblyEntry::DisassemblyEntry(DisassemblyEntry* parent, const DisassemblyOutput::DisassemblyLine& disassemblyLine, + QTextLine textLine) + : m_parent(parent) + , m_disassemblyLine(disassemblyLine) + , m_textLine(textLine) +{ + if (parent) { + m_row = m_parent->childCount(); + } else { + m_row = 0; + } +} + +DisassemblyEntry* DisassemblyEntry::lastChild() +{ + if (m_lines.isEmpty()) { + return nullptr; + } + + return &m_lines[m_lines.size() - 1]; +} + +void DisassemblyEntry::addChild(const DisassemblyEntry& line) +{ + m_lines.push_back(line); +} + +void DisassemblyEntry::clear() +{ + m_lines.clear(); +} + +bool DisassemblyEntry::operator==(const DisassemblyEntry& other) const +{ + return std::tie(m_parent, m_lines, m_disassemblyLine, m_row) + == std::tie(other.m_parent, other.m_lines, other.m_disassemblyLine, other.m_row); +} + +int DisassemblyEntry::findOffsetOf(const DisassemblyEntry* entry) const +{ + auto found = std::find_if(m_lines.begin(), m_lines.end(), + [needle = entry](const DisassemblyEntry& entry) { return needle == &entry; }); + + if (found != m_lines.end()) { + return std::distance(m_lines.begin(), found); + } + + return -1; +} + +DisassemblyEntry::TreeIterator::TreeIterator(const DisassemblyEntry* entry, int child) + : m_entry(const_cast(entry)) + , m_child(child) +{ +} + +DisassemblyEntry& DisassemblyEntry::TreeIterator::operator*() const +{ + if (m_entry->childCount() == 0) { + return *m_entry; + } + return *m_entry->child(m_child); +} + +DisassemblyEntry* DisassemblyEntry::TreeIterator::operator->() const +{ + if (m_entry->childCount() == 0) { + return m_entry; + } + return m_entry->child(m_child); +} + +DisassemblyEntry::TreeIterator& DisassemblyEntry::TreeIterator::operator++() +{ + if (m_entry->childCount() == 0) { + m_entry++; + return *this; + } + + m_child++; + + if (m_child >= m_entry->childCount()) { + m_entry++; + m_child = 0; + } + + return *this; +} + +bool DisassemblyEntry::TreeIterator::operator==(const DisassemblyEntry::TreeIterator other) const +{ + return this->m_entry == other.m_entry && this->m_child == other.m_child; +} + +DisassemblyEntry::TreeIterator DisassemblyEntry::tree_begin() const +{ + return TreeIterator(&m_lines[0], 0); +} + +DisassemblyEntry::TreeIterator DisassemblyEntry::tree_end() const +{ + return TreeIterator(m_lines.end(), 0); +} diff --git a/src/models/disassemblyentry.h b/src/models/disassemblyentry.h new file mode 100644 index 00000000..17287632 --- /dev/null +++ b/src/models/disassemblyentry.h @@ -0,0 +1,103 @@ +/* + SPDX-FileCopyrightText: Lieven Hey + SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include "disassemblyoutput.h" +#include +#include +#include + +class DisassemblyEntry +{ +public: + DisassemblyEntry(DisassemblyEntry* parent, const DisassemblyOutput::DisassemblyLine& disassemblyLine = {}, + QTextLine textLine = {}); + + ~DisassemblyEntry() = default; + + DisassemblyEntry* parent() const + { + return m_parent; + } + + int row() const + { + return m_row; + } + + DisassemblyEntry* child(int row) + { + return &m_lines[row]; + } + + DisassemblyEntry* child(int row) const + { + return const_cast(&m_lines[row]); + } + + DisassemblyEntry* lastChild(); + + DisassemblyOutput::DisassemblyLine disassemblyLine() const + { + return m_disassemblyLine; + } + + QTextLine textLine() const + { + return m_textLine; + } + + int childCount() const + { + return m_lines.size(); + } + + void clear(); + void addChild(const DisassemblyEntry& line); + + bool operator==(const DisassemblyEntry& other) const; + + int findOffsetOf(const DisassemblyEntry* entry) const; + + // an iterator that will iterate over all children and grandchildren + class TreeIterator : public std::iterator + { + public: + TreeIterator(const DisassemblyEntry* entry, int child); + DisassemblyEntry& operator*() const; + DisassemblyEntry* operator->() const; + + TreeIterator& operator++(); + TreeIterator operator++(int) + { + TreeIterator tmp = *this; + ++(*this); + return tmp; + } + + bool operator==(const TreeIterator other) const; + bool operator!=(const TreeIterator other) const + { + return !(*this == other); + } + + private: + DisassemblyEntry* m_entry = nullptr; + int m_child = -1; + }; + + TreeIterator tree_begin() const; + TreeIterator tree_end() const; + +private: + DisassemblyEntry* m_parent; + QVector m_lines; + DisassemblyOutput::DisassemblyLine m_disassemblyLine; + QTextLine m_textLine; + int m_row; +}; diff --git a/src/models/disassemblymodel.cpp b/src/models/disassemblymodel.cpp index f4999b9c..1c108a39 100644 --- a/src/models/disassemblymodel.cpp +++ b/src/models/disassemblymodel.cpp @@ -17,9 +17,10 @@ #include "sourcecodemodel.h" DisassemblyModel::DisassemblyModel(KSyntaxHighlighting::Repository* repository, QObject* parent) - : QAbstractTableModel(parent) + : QAbstractItemModel(parent) , m_document(new QTextDocument(this)) , m_highlighter(new Highlighter(m_document, repository, this)) + , m_disassemblyLines(nullptr) { m_document->setUndoRedoEnabled(false); } @@ -38,15 +39,58 @@ QModelIndex DisassemblyModel::findIndexWithOffset(int offset) quint64 address = m_data.disassemblyLines[0].addr + offset; const auto& found = - std::find_if(m_data.disassemblyLines.begin(), m_data.disassemblyLines.end(), - [address](const DisassemblyOutput::DisassemblyLine& line) { return line.addr == address; }); + std::find_if(m_disassemblyLines.tree_begin(), m_disassemblyLines.tree_end(), + [address](const DisassemblyEntry& entry) { return entry.disassemblyLine().addr == address; }); - if (found != m_data.disassemblyLines.end()) { - return createIndex(std::distance(m_data.disassemblyLines.begin(), found), DisassemblyColumn); + if (found != m_disassemblyLines.tree_end()) { + return indexFromEntry(&(*found)); } + return {}; } +QModelIndex DisassemblyModel::parent(const QModelIndex& index) const +{ + if (!index.isValid()) { + return QModelIndex(); + } + + DisassemblyEntry* line = static_cast(index.internalPointer()); + + if (line == nullptr) { + return QModelIndex(); + } + + auto parentItem = line->parent(); + + if (parentItem == &m_disassemblyLines) { + return QModelIndex(); + } + + return createIndex(parentItem->row(), 0, parentItem); +} + +QModelIndex DisassemblyModel::index(int row, int column, const QModelIndex& parent) const +{ + if (!hasIndex(row, column, parent)) { + return QModelIndex(); + } + + DisassemblyEntry* parentItem; + + if (!parent.isValid()) { + parentItem = const_cast(&m_disassemblyLines); + } else { + parentItem = static_cast(parent.internalPointer()); + } + + if (row < parentItem->childCount()) { + return createIndex(row, column, parentItem->child(row)); + } else { + return QModelIndex(); + } +} + void DisassemblyModel::setDisassembly(const DisassemblyOutput& disassemblyOutput, const Data::CallerCalleeResults& results) { @@ -57,9 +101,11 @@ void DisassemblyModel::setDisassembly(const DisassemblyOutput& disassemblyOutput m_numTypes = results.selfCosts.numTypes(); m_document->clear(); + m_disassemblyLines.clear(); QTextCursor cursor(m_document); cursor.beginEditBlock(); + DisassemblyEntry* lastChild = nullptr; for (const auto& it : disassemblyOutput.disassemblyLines) { cursor.insertText(it.disassembly); cursor.insertBlock(); @@ -68,6 +114,30 @@ void DisassemblyModel::setDisassembly(const DisassemblyOutput& disassemblyOutput m_document->setTextWidth(m_document->idealWidth()); + int linecounter = 0; + QString function; + for (const auto& it : disassemblyOutput.disassemblyLines) { + auto textLine = m_document->findBlockByLineNumber(linecounter++).layout()->lineAt(0); + + // if symbol in not empty -> inlined function detected + if (!it.symbol.isEmpty()) { + // only collapse the same function + if (lastChild && function == it.symbol) { + lastChild->addChild({lastChild, it, textLine}); + } else { + m_disassemblyLines.addChild( + {&m_disassemblyLines, {0, QLatin1String("%1 [inlined]").arg(it.symbol), {}, {}, {}}}); + lastChild = m_disassemblyLines.lastChild(); + Q_ASSERT(lastChild); + lastChild->addChild({lastChild, it, textLine}); + function = it.symbol; + } + } else { + lastChild = nullptr; + m_disassemblyLines.addChild({&m_disassemblyLines, it, textLine}); + } + } + endResetModel(); } @@ -107,29 +177,30 @@ QVariant DisassemblyModel::data(const QModelIndex& index, int role) const return static_cast(Qt::AlignLeft | Qt::AlignVCenter); } - const auto& data = m_data.disassemblyLines.at(index.row()); + DisassemblyEntry* entry = static_cast(index.internalPointer()); + Q_ASSERT(entry); + auto line = entry->disassemblyLine(); if (role == Qt::DisplayRole || role == CostRole || role == TotalCostRole || role == SyntaxHighlightRole || role == Qt::ToolTipRole) { if (role != Qt::ToolTipRole) { if (index.column() == AddrColumn) { - if (!data.addr) + if (!line.addr) return {}; - return QString::number(data.addr, 16); + return QString::number(line.addr, 16); } else if (index.column() == DisassemblyColumn) { - const auto block = m_document->findBlockByLineNumber(index.row()); if (role == SyntaxHighlightRole) - return QVariant::fromValue(block.layout()->lineAt(0)); - return block.text(); + return QVariant::fromValue(entry->textLine()); + return line.disassembly; } } - if (data.addr == 0) { + if (line.addr == 0) { return {}; } const auto entry = m_results.entries.value(m_data.symbol); - auto it = entry.offsetMap.find(data.addr); + auto it = entry.offsetMap.find(line.addr); if (it != entry.offsetMap.end()) { int event = index.column() - COLUMN_COUNT; @@ -143,7 +214,7 @@ QVariant DisassemblyModel::data(const QModelIndex& index, int role) const return totalCost; } else if (role == Qt::ToolTipRole) { auto tooltip = tr("addr: %1
assembly: %2
disassembly: %3") - .arg(QString::number(data.addr, 16), data.disassembly); + .arg(QString::number(line.addr, 16), line.disassembly); return Util::formatTooltip(tooltip, locationCost, m_results.selfCosts); } @@ -153,18 +224,23 @@ QVariant DisassemblyModel::data(const QModelIndex& index, int role) const } else { if (role == Qt::ToolTipRole) return tr("%1
No samples at this location.
") - .arg(data.disassembly.toHtmlEscaped()); + .arg(line.disassembly.toHtmlEscaped()); else return QString(); } } else if (role == DisassemblyModel::HighlightRole) { - return data.fileLine.line == m_highlightLine; + return line.fileLine.line == m_highlightLine; } else if (role == LinkedFunctionNameRole) { - return data.linkedFunction.name; + return line.linkedFunction.name; } else if (role == LinkedFunctionOffsetRole) { - return data.linkedFunction.offset; - } else if (role == RainbowLineNumberRole && data.addr) { - return data.fileLine.line; + return line.linkedFunction.offset; + } else if (role == RainbowLineNumberRole && line.addr) { + // don't color inlined stuff because that is confusing + // TODO find better solution like coloring in the color of the calling line + if (line.symbol.isEmpty()) { + return line.fileLine.line; + } + return -1; } return {}; @@ -172,12 +248,19 @@ QVariant DisassemblyModel::data(const QModelIndex& index, int role) const int DisassemblyModel::columnCount(const QModelIndex& parent) const { - return parent.isValid() ? 0 : COLUMN_COUNT + m_numTypes; + Q_UNUSED(parent); + return COLUMN_COUNT + m_numTypes; } int DisassemblyModel::rowCount(const QModelIndex& parent) const { - return parent.isValid() ? 0 : m_data.disassemblyLines.count(); + if (parent.column() > 0) + return 0; + if (parent.isValid()) { + auto item = static_cast(parent.internalPointer()); + return item->childCount(); + } + return m_disassemblyLines.childCount(); } void DisassemblyModel::updateHighlighting(int line) @@ -188,37 +271,48 @@ void DisassemblyModel::updateHighlighting(int line) Data::FileLine DisassemblyModel::fileLineForIndex(const QModelIndex& index) const { - return m_data.disassemblyLines[index.row()].fileLine; + auto entry = reinterpret_cast(index.internalPointer()); + return entry->disassemblyLine().fileLine; } QModelIndex DisassemblyModel::indexForFileLine(const Data::FileLine& fileLine) const { - int i = -1; - int bestMatch = -1; + DisassemblyEntry* bestMatch = nullptr; qint64 bestCost = 0; const auto entry = m_results.entries.value(m_data.symbol); - for (const auto& line : m_data.disassemblyLines) { - ++i; - if (line.fileLine != fileLine) { + + for (auto line = m_disassemblyLines.tree_begin(); line != m_disassemblyLines.tree_end(); line++) { + if (line->disassemblyLine().fileLine != fileLine) { continue; } - if (bestMatch == -1) { - bestMatch = i; + if (!bestMatch) { + bestMatch = &(*line); } - auto it = entry.offsetMap.find(line.addr); + auto it = entry.offsetMap.find(line->disassemblyLine().addr); if (it != entry.offsetMap.end()) { const auto& locationCost = it.value(); if (!bestCost || bestCost < locationCost.selfCost[0]) { - bestMatch = i; + bestMatch = &(*line); bestCost = locationCost.selfCost[0]; } } } - if (bestMatch == -1) + if (!bestMatch) return {}; - return index(bestMatch, 0); + + return indexFromEntry(bestMatch); +} + +QModelIndex DisassemblyModel::indexFromEntry(DisassemblyEntry* entry) const +{ + Q_ASSERT(entry); + + int index = entry->parent()->findOffsetOf(entry); + Q_ASSERT(index >= 0); + + return createIndex(index, 0, entry); } diff --git a/src/models/disassemblymodel.h b/src/models/disassemblymodel.h index 45d2d648..503dc500 100644 --- a/src/models/disassemblymodel.h +++ b/src/models/disassemblymodel.h @@ -9,10 +9,11 @@ #pragma once #include -#include +#include #include #include "data.h" +#include "disassemblyentry.h" #include "disassemblyoutput.h" class QTextDocument; @@ -23,7 +24,7 @@ class Definition; class Repository; } -class DisassemblyModel : public QAbstractTableModel +class DisassemblyModel : public QAbstractItemModel { Q_OBJECT public: @@ -35,6 +36,9 @@ class DisassemblyModel : public QAbstractTableModel void clear(); QModelIndex findIndexWithOffset(int offset); + QModelIndex parent(const QModelIndex& index) const override; + QModelIndex index(int row, int column, const QModelIndex& parent) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; int columnCount(const QModelIndex& parent = QModelIndex()) const override; @@ -71,8 +75,11 @@ public slots: void updateHighlighting(int line); private: + QModelIndex indexFromEntry(DisassemblyEntry* entry) const; + QTextDocument* m_document; Highlighter* m_highlighter; + DisassemblyEntry m_disassemblyLines; DisassemblyOutput m_data; Data::CallerCalleeResults m_results; int m_numTypes = 0; diff --git a/src/models/disassemblyoutput.cpp b/src/models/disassemblyoutput.cpp index 68b013ec..694c2353 100644 --- a/src/models/disassemblyoutput.cpp +++ b/src/models/disassemblyoutput.cpp @@ -129,6 +129,8 @@ static ObjectdumpOutput objdumpParse(const QByteArray& output) QString asmLine; QString sourceFileName; QString currentSourceFileName; + QString symbol; + QString function; int sourceCodeLine = 0; while (stream.readLineInto(&asmLine)) { @@ -146,10 +148,18 @@ static ObjectdumpOutput objdumpParse(const QByteArray& output) const int colonIndex = asmLine.indexOf(QLatin1Char(':')); const int angleBracketIndex = asmLine.indexOf(QLatin1Char('<')); if (angleBracketIndex > 0 && colonIndex > angleBracketIndex) { - // -l add a line like: - // main(): - // after 0000000000001090
: - stream.readLine(); + continue; + } + + // inlining create lines like these + // std::ostream::operator<<(std::ostream& (*)(std::ostream&)): + // we want to skip those + if (asmLine.endsWith(QLatin1Char(':'))) { + symbol = asmLine.left(asmLine.length() - 1); + + if (function.isEmpty()) { + function = symbol; + } continue; } @@ -200,8 +210,11 @@ static ObjectdumpOutput objdumpParse(const QByteArray& output) assembly = asmLine; } - disassemblyLines.push_back( - {addr, assembly, extractLinkedFunction(asmLine), {currentSourceFileName, sourceCodeLine}}); + disassemblyLines.push_back({addr, + assembly, + extractLinkedFunction(asmLine), + {currentSourceFileName, sourceCodeLine}, + symbol != function ? symbol : QString {}}); } return {disassemblyLines, sourceFileName}; } diff --git a/src/models/disassemblyoutput.h b/src/models/disassemblyoutput.h index cbd4bbbe..ddcfdb79 100644 --- a/src/models/disassemblyoutput.h +++ b/src/models/disassemblyoutput.h @@ -19,6 +19,10 @@ struct DisassemblyOutput QString name; int offset = 0; // offset from the entrypoint of the function }; + friend bool operator==(const LinkedFunction& a, const LinkedFunction& b) + { + return a.name == b.name && a.offset == b.offset; + } struct DisassemblyLine { @@ -26,7 +30,15 @@ struct DisassemblyOutput QString disassembly; LinkedFunction linkedFunction; Data::FileLine fileLine; + QString symbol; // used to show which function was inlined }; + + friend bool operator==(const DisassemblyLine& a, const DisassemblyLine& b) + { + return std::tie(a.addr, a.disassembly, a.linkedFunction, a.fileLine, a.symbol) + == std::tie(b.addr, b.disassembly, b.linkedFunction, b.fileLine, b.symbol); + } + QVector disassemblyLines; quint64 baseAddress = 0; diff --git a/src/models/sourcecodemodel.cpp b/src/models/sourcecodemodel.cpp index 6ada988a..38c6d7d9 100644 --- a/src/models/sourcecodemodel.cpp +++ b/src/models/sourcecodemodel.cpp @@ -69,15 +69,21 @@ void SourceCodeModel::setDisassembly(const DisassemblyOutput& disassemblyOutput, m_document->setTextWidth(m_document->idealWidth()); for (const auto& line : disassemblyOutput.disassemblyLines) { - if (line.fileLine.line == 0 || line.fileLine.file != disassemblyOutput.mainSourceFileName) { + // symbol not empty -> inlined function + // skip all lines that are not part of the function (happens due to inlining) + if (line.fileLine.line == 0 || !line.symbol.isEmpty()) { continue; } - if (line.fileLine.line > maxLineNumber) { - maxLineNumber = line.fileLine.line; - } - if (line.fileLine.line < minLineNumber) { - minLineNumber = line.fileLine.line; + // maxLineNumber and minLineNumber are used to show the source code so we only update them when we are in the + // current file + if (line.fileLine.file == disassemblyOutput.mainSourceFileName) { + if (line.fileLine.line > maxLineNumber) { + maxLineNumber = line.fileLine.line; + } + if (line.fileLine.line < minLineNumber) { + minLineNumber = line.fileLine.line; + } } if (m_validLineNumbers.contains(line.fileLine.line)) diff --git a/src/resultsdisassemblypage.cpp b/src/resultsdisassemblypage.cpp index 6c16f695..7421f2a7 100644 --- a/src/resultsdisassemblypage.cpp +++ b/src/resultsdisassemblypage.cpp @@ -112,7 +112,8 @@ ResultsDisassemblyPage::ResultsDisassemblyPage(CostContextMenu* costContextMenu, connect(sourceView, &QTreeView::clicked, sourceView, [=](const QModelIndex& index) { const auto fileLine = sourceModel->fileLineForIndex(index); if (fileLine.isValid()) { - destView->scrollTo(destModel->indexForFileLine(fileLine)); + auto index = destModel->indexForFileLine(fileLine); + destView->scrollTo(index); } }); }; @@ -128,8 +129,9 @@ ResultsDisassemblyPage::ResultsDisassemblyPage(CostContextMenu* costContextMenu, int functionOffset = index.data(DisassemblyModel::LinkedFunctionOffsetRole).toInt(); if (m_symbolStack[m_stackIndex].symbol == functionName) { - ui->assemblyView->scrollTo(m_disassemblyModel->findIndexWithOffset(functionOffset), - QAbstractItemView::ScrollHint::PositionAtTop); + auto index = m_disassemblyModel->findIndexWithOffset(functionOffset); + ui->assemblyView->setExpanded(index, true); + ui->assemblyView->scrollTo(index, QAbstractItemView::ScrollHint::PositionAtTop); } else { const auto symbol = std::find_if(m_callerCalleeResults.entries.keyBegin(), m_callerCalleeResults.entries.keyEnd(),