Skip to content

Commit

Permalink
Merge pull request #189 from mortbopet/c_highlighting
Browse files Browse the repository at this point in the history
C highlighting
  • Loading branch information
mortbopet authored Dec 12, 2021
2 parents 73d7568 + a1d79c4 commit 913d0d0
Show file tree
Hide file tree
Showing 12 changed files with 147 additions and 42 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "external/ELFIO"]
path = external/ELFIO
url = https://github.com/serge1/ELFIO
[submodule "external/libelfin"]
path = external/libelfin
url = https://github.com/mortbopet/libelfin
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ endif()
if(MSVC)
add_definitions(/bigobj) # Allow big object
elseif(MINGW)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wa,-mbig-obj") # Allow big object
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mbig-obj") # Allow big object
endif()

######################################################################
Expand Down Expand Up @@ -70,6 +70,7 @@ include_directories(${RIPES_LIB} SYSTEM PUBLIC external/VSRTL/external/cereal/in
include_directories(${RIPES_LIB} PUBLIC external/VSRTL/external)
include_directories(SYSTEM external/ELFIO)
include_directories(SYSTEM external/VSRTL/external/Signals)
include_directories(external/libelfin_cmake)
include_directories(external)

option(RIPES_BUILD_VERILATOR_PROCESSORS "Build verilator processors" OFF)
Expand Down
6 changes: 6 additions & 0 deletions external/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,9 @@ add_subdirectory(fancytabbar)
add_subdirectory(VSRTL)
set(VSRTL_BUILD_APP OFF)
set(VSRTL_BUILD_TESTS OFF)

add_subdirectory(libelfin)
# Make autoMOC and autoUIC happy about the generated libelfin files
set(libelfin_src "${CMAKE_CURRENT_BINARY_DIR}/libelfin")
set_property(SOURCE "${libelfin_src}/elf_to_string.cc" PROPERTY SKIP_AUTOGEN ON)
set_property(SOURCE "${libelfin_src}/dwarf_to_string.cc" PROPERTY SKIP_AUTOGEN ON)
1 change: 1 addition & 0 deletions external/libelfin
Submodule libelfin added at 49d165
11 changes: 10 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ function(create_ripes_lib NAME)
endif()
endfunction()

# Error flags on everything but MSVC
if(NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wextra -Wall \
-Werror=switch -Werror=return-type \
-Werror=unreachable-code")
elseif(MINGW)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wa")
endif()

# Create the parent library. This will include everything in the current directory.
create_ripes_lib(${RIPES_LIB} FIXED_NAME EXCLUDE_SRC_INC)
Expand All @@ -62,5 +70,6 @@ add_subdirectory(version)
target_link_libraries(${RIPES_LIB} PUBLIC
fancytabbar_lib
${VSRTL_GRAPHICS_LIB}
Qt5::Charts)
Qt5::Charts
dwarf++)

2 changes: 1 addition & 1 deletion src/assembler/assembler.h
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ class Assembler : public AssemblerBase {
std::shared_ptr<_Instruction> assembledWith;
runOperation(machineCode, _InstrRes, assembleInstruction, line, assembledWith);
assert(assembledWith && "Expected the assembler instruction to be set");
program.sourceMapping[addr_offset] = line.sourceLine;
program.sourceMapping[addr_offset].insert(line.sourceLine);

if (!machineCode.linksWithSymbol.symbol.isEmpty()) {
LinkRequest req;
Expand Down
5 changes: 3 additions & 2 deletions src/assembler/program.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <QMetaType>
#include <QString>
#include <optional>
#include <set>
#include <vector>

#include "ripes_types.h"
Expand Down Expand Up @@ -100,8 +101,8 @@ class DisassembledProgram {
*/
class Program {
public:
// A source mapping is a mapping from {instruction address : source code line}
using SourceMapping = std::map<VInt, unsigned>;
// A source mapping is a mapping from {instruction address : source code lines}
using SourceMapping = std::map<VInt, std::set<unsigned>>;

AInt entryPoint = 0;
std::map<QString, ProgramSection> sections;
Expand Down
27 changes: 15 additions & 12 deletions src/editor/codeeditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -419,23 +419,26 @@ void CodeEditor::updateHighlighting() {

for (unsigned sid = 0; sid < stages; sid++) {
const auto stageInfo = proc->stageInfo(sid);
QColor stageColor = colorGenerator();
if (stageInfo.stage_valid) {
auto sourceLine = sourceMapping.find(stageInfo.pc);
if (sourceLine == sourceMapping.end()) {
auto mappingIt = sourceMapping.find(stageInfo.pc);
if (mappingIt == sourceMapping.end()) {
// No source line registerred for this PC.
continue;
}

// Find block
QTextBlock block = document()->findBlockByLineNumber(sourceLine->second);
if (!block.isValid())
continue;

// Record the stage name for the highlighted block for later painting
QString stageString = ProcessorHandler::getProcessor()->stageName(sid);
if (!stageInfo.namedState.isEmpty())
stageString += " (" + stageInfo.namedState + ")";
highlightBlock(block, colorGenerator(), stageString);
for (auto sourceLine : mappingIt->second) {
// Find block
QTextBlock block = document()->findBlockByLineNumber(sourceLine);
if (!block.isValid())
continue;

// Record the stage name for the highlighted block for later painting
QString stageString = ProcessorHandler::getProcessor()->stageName(sid);
if (!stageInfo.namedState.isEmpty())
stageString += " (" + stageInfo.namedState + ")";
highlightBlock(block, stageColor, stageString);
}
}
}
}
Expand Down
52 changes: 30 additions & 22 deletions src/editor/highlightabletextedit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@
namespace Ripes {

HighlightableTextEdit::HighlightableTextEdit(QWidget* parent) : QPlainTextEdit(parent) {
connect(this, &QPlainTextEdit::textChanged, this, &HighlightableTextEdit::clearBlockHighlights);
// Clear block highlighting on text inserted or removed. This avoids clearing block highlightins on formatting
// changes.
connect(this->document(), &QTextDocument::contentsChange, this, [&](int /*pos*/, int charsRemoved, int charsAdded) {
if ((charsRemoved == 0 && charsAdded == 0) || (charsAdded == charsRemoved))
return;
clearBlockHighlights();
});
}

void HighlightableTextEdit::paintEvent(QPaintEvent* event) {
Expand All @@ -31,12 +37,16 @@ void HighlightableTextEdit::paintEvent(QPaintEvent* event) {
painter.drawText(QRectF(drawAt.x(), drawAt.y(), stageStringRect.width(), stageStringRect.height()),
stageString);
}
viewport()->update();
painter.end();
}

void HighlightableTextEdit::applyHighlighting() {
QList<QTextEdit::ExtraSelection> selections;
llvm::transform(m_blockHighlights, std::back_inserter(selections), [](const auto& bh) { return bh.selection; });
for (auto& bh : m_blockHighlights) {
if (auto selection = getExtraSelection(bh); selection.has_value())
selections.push_back(selection.value());
}
setExtraSelections(selections);

// The block text is drawn on the viewport itself, which is not automatically redrawn when the extra selections
Expand All @@ -57,24 +67,8 @@ void HighlightableTextEdit::clearBlockHighlights() {
update();
}

void HighlightableTextEdit::setBlockGradient(BlockHighlight& highlight) {
auto block = highlight.selection.cursor.block();
if (!block.isValid())
return;
const auto bbr = blockBoundingRect(block);
QLinearGradient grad(bbr.topLeft(), bbr.bottomRight());
grad.setColorAt(0, palette().base().color());
grad.setColorAt(1, highlight.color);
highlight.selection.format.setBackground(grad);
}

void HighlightableTextEdit::resizeEvent(QResizeEvent* e) {
QPlainTextEdit::resizeEvent(e);

// we need to update the highlighted lines whenever resizing the window to recalculate the highlighting gradient,
// reflecting the new widget size
for (auto& highlight : m_blockHighlights)
setBlockGradient(highlight);
applyHighlighting();
}

Expand All @@ -87,14 +81,28 @@ void HighlightableTextEdit::highlightBlock(const QTextBlock& block, const QColor
// Check if we're already highlighting the block. If this is the case, do not set an additional highlight on it.
if (m_highlightedBlocks.count(block))
return;

m_blockHighlights.push_back({});
auto& highlight = m_blockHighlights.back();
highlight.selection.cursor = QTextCursor(block);
highlight.selection.format.setProperty(QTextFormat::FullWidthSelection, true);
highlight.blockNumber = block.blockNumber();
highlight.color = color;
setBlockGradient(highlight);
applyHighlighting();
}

std::optional<QTextEdit::ExtraSelection>
HighlightableTextEdit::getExtraSelection(const HighlightableTextEdit::BlockHighlight& highlighting) {
auto block = document()->findBlockByNumber(highlighting.blockNumber);
if (!block.isValid())
return {};

QTextEdit::ExtraSelection selection;
selection.format.setProperty(QTextFormat::FullWidthSelection, true);
selection.cursor = QTextCursor(block);
const auto bbr = blockBoundingRect(block);
QLinearGradient grad(bbr.topLeft(), bbr.bottomRight());
grad.setColorAt(0, palette().base().color());
grad.setColorAt(1, highlighting.color);
selection.format.setBackground(grad);
return {selection};
}

} // namespace Ripes
10 changes: 8 additions & 2 deletions src/editor/highlightabletextedit.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <QScrollBar>
#include <QTimer>

#include <optional>
#include <set>

namespace Ripes {
Expand All @@ -18,7 +19,10 @@ class HighlightableTextEdit : public QPlainTextEdit {
struct BlockHighlight {
// Store the color which the block is highlighted with so that we can rehighlight a selection.
QColor color;
QTextEdit::ExtraSelection selection;
// Only store the block number, not the block itself nor a ExtraSelection. After countless of hours of
// debugging, it's deemed that all of this is very unsafe to store outside of the QPlainTextEdit itself, and so
// we just rebuild all of this info whenever needed.
int blockNumber;
};

public:
Expand All @@ -34,7 +38,9 @@ class HighlightableTextEdit : public QPlainTextEdit {
void resizeEvent(QResizeEvent* event) override;

private:
void setBlockGradient(BlockHighlight& highlight);
/// Creates a new ExtraSelection formatting from the information stored in BlockHighlighting.
std::optional<QTextEdit::ExtraSelection>
getExtraSelection(const HighlightableTextEdit::BlockHighlight& highlighting);
void applyHighlighting();

/// A list of strings which will be printed at the right-hand side of each block
Expand Down
67 changes: 67 additions & 0 deletions src/edittab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "ui_edittab.h"

#include "elfio/elfio.hpp"
#include "libelfin/dwarf/dwarf++.hh"

#include <QCheckBox>
#include <QLabel>
Expand Down Expand Up @@ -367,6 +368,34 @@ bool EditTab::loadSourceFile(Program&, QFile& file) {
return true;
}

using namespace ELFIO;
class ELFIODwarfLoader : public ::dwarf::loader {
public:
ELFIODwarfLoader(elfio& reader) : reader(reader) {}

const void* load(::dwarf::section_type section, size_t* size_out) override {
auto sec = reader.sections[::dwarf::elf::section_type_to_name(section)];
if (sec == nullptr)
return nullptr;
*size_out = sec->get_size();
return sec->get_data();
}

private:
elfio& reader;
};

std::shared_ptr<ELFIODwarfLoader> createDwarfLoader(elfio& reader) {
return std::make_shared<ELFIODwarfLoader>(reader);
}

static bool isInternalSourceFile(const QString& filename) {
// Returns true if we have reason to believe that this file originated from within the Ripes editor. These will be
// temporary files like /.../Ripes.abc123.c
static QRegularExpression re("Ripes.[a-zA-Z0-9]+.c");
return re.match(filename).hasMatch();
}

bool EditTab::loadElfFile(Program& program, QFile& file) {
ELFIO::elfio reader;

Expand Down Expand Up @@ -407,6 +436,44 @@ bool EditTab::loadElfFile(Program& program, QFile& file) {
}
}

// Load DWARF information into the source mapping of the program.
// We'll only load information from compilation units which originated from a source file that plausibly arrived
// from within the Ripes editor.
QString editorSrcFile;
try {
::dwarf::dwarf dw(createDwarfLoader(reader));
for (auto& cu : dw.compilation_units()) {
for (auto& line : cu.get_line_table()) {
if (!line.file)
continue;
QString filePath = QString::fromStdString(line.file->path);
if (editorSrcFile.isEmpty()) {
// Try to see if this compilation unit is from the Ripes editor:
if (isInternalSourceFile(filePath))
editorSrcFile = filePath;
}
if (editorSrcFile != filePath)
continue;
program.sourceMapping[line.address].insert(line.line - 1);
}
}
if (!editorSrcFile.isEmpty()) {
// Finally, we need to generate a hash of the source file that we've loaded source mappings from, so the
// editor knows what editor contents applies to this program.
QFile srcFile(editorSrcFile);
if (srcFile.open(QFile::ReadOnly))
program.sourceHash = Program::calculateHash(srcFile.readAll());
else
throw ::dwarf::format_error("Could not find source file " + editorSrcFile.toStdString());
}
} catch (::dwarf::format_error& e) {
std::string msg = "Could not load debug information: ";
msg += e.what();
GeneralStatusManager::setStatusTimed(QString::fromStdString(msg), 2500);
} catch (...) {
// Something else went wrong.
}

program.entryPoint = reader.get_entry();

m_ui->curInputSrcLabel->setText("Executable (ELF)");
Expand Down
2 changes: 1 addition & 1 deletion src/ripessettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const std::map<QString, QVariant> s_defaultSettings = {
{RIPES_SETTING_FORMATTER_PATH, "clang-format"},
{RIPES_SETTING_FORMAT_ON_SAVE, false},
{RIPES_SETTING_FORMATTER_ARGS, "--style=file --fallback-style=LLVM"},
{RIPES_SETTING_CCARGS, "-O0"},
{RIPES_SETTING_CCARGS, "-O0 -g"},
{RIPES_SETTING_LDARGS, "-static -lm"}, // Ensure statically linked executable + link with math library
{RIPES_SETTING_CONSOLEECHO, "true"},
{RIPES_SETTING_CONSOLEBG, QColorConstants::White},
Expand Down

0 comments on commit 913d0d0

Please sign in to comment.