diff options
| author | Olivier De Cannière <olivier.decanniere@qt.io> | 2023-07-04 09:39:37 +0200 |
|---|---|---|
| committer | Olivier De Cannière <olivier.decanniere@qt.io> | 2023-07-10 10:56:10 +0200 |
| commit | 55ef0833254d4861059950dad9b935af6c27315c (patch) | |
| tree | 70d6f14a88d31061104a432140e63d02be89114e /src/qmlcompiler/qqmljsbasicblocks.cpp | |
| parent | 4e7a83156d47683f777e92d2790cb617f0bb09d5 (diff) | |
Compiler: Allow dumping the basic blocks for visualization and debugging
If the QV4_DUMP_BASIC_BLOCKS environment variable is set, the compiler
will output the details of the basic blocks of the compiled functions to
the console.
It will also generate a control flow graph containing the byte code in
DOT format for easier visualization and debugging of the program
execution and of the structure of the generated code. The value of
QV4_DUMP_BASIC_BLOCKS will be used as the path to the folder in which to
output the DOT files. If the path is any of ["-", "1", "true"] or if
files can't be opened, it will be dumped to stdout instead.
The logic in dumpByteCode has been adapted to use a QTextStream. This
way it can continue to be used to dump the byte code of the whole
program as before and also to construct the CFG.
Change-Id: If398d795e4fc0950b5fa8ee1349e80b1ae262deb
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Diffstat (limited to 'src/qmlcompiler/qqmljsbasicblocks.cpp')
| -rw-r--r-- | src/qmlcompiler/qqmljsbasicblocks.cpp | 103 |
1 files changed, 103 insertions, 0 deletions
diff --git a/src/qmlcompiler/qqmljsbasicblocks.cpp b/src/qmlcompiler/qqmljsbasicblocks.cpp index e819313b54..95e7ca8d55 100644 --- a/src/qmlcompiler/qqmljsbasicblocks.cpp +++ b/src/qmlcompiler/qqmljsbasicblocks.cpp @@ -3,8 +3,105 @@ #include "qqmljsbasicblocks_p.h" +#include <QtQml/private/qv4instr_moth_p.h> + QT_BEGIN_NAMESPACE +using namespace Qt::Literals::StringLiterals; + +DEFINE_BOOL_CONFIG_OPTION(qv4DumpBasicBlocks, QV4_DUMP_BASIC_BLOCKS) + +void QQmlJSBasicBlocks::dumpBasicBlocks() +{ + qDebug().noquote() << "=== Basic Blocks for \"%1\""_L1.arg(m_context->name); + for (const auto &[blockOffset, block] : m_basicBlocks) { + QDebug debug = qDebug().nospace(); + debug << "Block " << blockOffset << ":\n"; + debug << " jumpOrigins[" << block.jumpOrigins.size() << "]: "; + for (auto origin : block.jumpOrigins) { + debug << origin << ", "; + } + debug << "\n readRegisters[" << block.readRegisters.size() << "]: "; + for (auto reg : block.readRegisters) { + debug << reg << ", "; + } + debug << "\n readTypes[" << block.readTypes.size() << "]: "; + for (auto type : block.readTypes) { + debug << type->augmentedInternalName() << ", "; + } + debug << "\n jumpTarget: " << block.jumpTarget; + debug << "\n jumpIsUnConditional: " << block.jumpIsUnconditional; + } + qDebug() << "\n"; +} + +void QQmlJSBasicBlocks::dumpDOTGraph() +{ + auto isBackEdge = [](const BasicBlock &originBlock, int originOffset, int destinationOffset) { + return originOffset > destinationOffset && originBlock.jumpIsUnconditional; + }; + + QString output; + QTextStream s{ &output }; + s << "=== Basic Blocks Graph in DOT format for \"%1\" (spaces are encoded as" + "   to preserve formatting)\n"_L1.arg(m_context->name); + s << "digraph BasicBlocks {\n"_L1; + + std::map<int, BasicBlock> blocks{ m_basicBlocks.begin(), m_basicBlocks.end() }; + for (const auto &[blockOffset, block] : blocks) { + for (int originOffset : block.jumpOrigins) { + int originBlockOffset; + auto originBlockIt = blocks.find(originOffset); + if (originBlockIt != blocks.end()) + originBlockOffset = originOffset; + else + originBlockOffset = std::prev(blocks.lower_bound(originOffset))->first; + s << " %1 -> %2%3\n"_L1.arg(QString::number(originBlockOffset)) + .arg(QString::number(blockOffset)) + .arg(isBackEdge(originBlockIt->second, originOffset, blockOffset) + ? " [color=blue]"_L1 + : ""_L1); + } + } + + for (const auto &[blockOffset, block] : blocks) { + int beginOffset = std::max(0, blockOffset); + auto nextBlockIt = blocks.lower_bound(blockOffset + 1); + int nextBlockOffset = nextBlockIt == blocks.end() ? m_context->code.size() : nextBlockIt->first; + QString dump = QV4::Moth::dumpBytecode( + m_context->code.constData(), m_context->code.size(), m_context->locals.size(), + m_context->formals->length(), beginOffset, nextBlockOffset - 1, + m_context->lineAndStatementNumberMapping); + dump = dump.replace(" "_L1, " "_L1); // prevent collapse of extra whitespace for formatting + dump = dump.replace("\n"_L1, "\\l"_L1); // new line + left aligned + s << " %1 [shape=record, fontname=\"Monospace\", label=\"{Block %1: | %2}\"]\n"_L1 + .arg(QString::number(blockOffset)) + .arg(dump); + } + s << "}\n"_L1; + + // Have unique names to prevent overwriting of functions with the same name (eg. anonymous functions). + static int functionCount = 0; + static const auto dumpFolderPath = qEnvironmentVariable("QV4_DUMP_BASIC_BLOCKS"); + + QString expressionName = m_context->name == ""_L1 + ? "anonymous"_L1 + : QString(m_context->name).replace(" "_L1, "_"_L1); + QString fileName = "function"_L1 + QString::number(functionCount++) + "_"_L1 + expressionName + ".gv"_L1; + QFile dumpFile(dumpFolderPath + (dumpFolderPath.endsWith("/"_L1) ? ""_L1 : "/"_L1) + fileName); + + if (dumpFolderPath == "-"_L1 || dumpFolderPath == "1"_L1 || dumpFolderPath == "true"_L1) { + qDebug().noquote() << output; + } else { + if (!dumpFile.open(QIODeviceBase::Truncate | QIODevice::WriteOnly)) { + qDebug() << "Error: Could not open file to dump the basic blocks into"; + } else { + dumpFile.write(("//"_L1 + output).toLatin1().toStdString().c_str()); + dumpFile.close(); + } + } +} + template<typename Container> void deduplicate(Container &container) { @@ -62,6 +159,12 @@ QQmlJSCompilePass::InstructionAnnotations QQmlJSBasicBlocks::run( populateBasicBlocks(); populateReaderLocations(); adjustTypes(); + + if (qv4DumpBasicBlocks()) { + dumpBasicBlocks(); + dumpDOTGraph(); + } + return std::move(m_annotations); } |
