aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOlivier De Cannière <olivier.decanniere@qt.io>2023-07-04 09:39:37 +0200
committerOlivier De Cannière <olivier.decanniere@qt.io>2023-07-10 10:56:10 +0200
commit55ef0833254d4861059950dad9b935af6c27315c (patch)
tree70d6f14a88d31061104a432140e63d02be89114e
parent4e7a83156d47683f777e92d2790cb617f0bb09d5 (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>
-rw-r--r--src/qml/compiler/qv4codegen.cpp5
-rw-r--r--src/qml/compiler/qv4instr_moth.cpp234
-rw-r--r--src/qml/compiler/qv4instr_moth_p.h24
-rw-r--r--src/qml/doc/src/javascript/finetuning.qdoc8
-rw-r--r--src/qmlcompiler/qqmljsbasicblocks.cpp103
-rw-r--r--src/qmlcompiler/qqmljsbasicblocks_p.h9
-rw-r--r--src/qmlcompiler/qqmljscompiler.cpp2
7 files changed, 261 insertions, 124 deletions
diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp
index d6f2716bbc..f9c4398490 100644
--- a/src/qml/compiler/qv4codegen.cpp
+++ b/src/qml/compiler/qv4codegen.cpp
@@ -3414,8 +3414,9 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, AST::FormalPara
if (showCode) {
qDebug() << "=== Bytecode for" << _context->name << "strict mode" << _context->isStrict
<< "register count" << _context->registerCountInFunction << "implicit return" << requiresReturnValue;
- QV4::Moth::dumpBytecode(_context->code, _context->locals.size(), _context->arguments.size(),
- _context->line, _context->lineAndStatementNumberMapping);
+ qDebug().noquote() << QV4::Moth::dumpBytecode(
+ _context->code, _context->locals.size(), _context->arguments.size(),
+ _context->line, _context->lineAndStatementNumberMapping);
qDebug();
}
}
diff --git a/src/qml/compiler/qv4instr_moth.cpp b/src/qml/compiler/qv4instr_moth.cpp
index eae076012a..4fda5b1b19 100644
--- a/src/qml/compiler/qv4instr_moth.cpp
+++ b/src/qml/compiler/qv4instr_moth.cpp
@@ -48,20 +48,18 @@ static QByteArray rawBytes(const char *data, int n)
}
#define ABSOLUTE_OFFSET() \
- (code - start + offset)
+ (code + beginOffset - start + offset)
#define MOTH_BEGIN_INSTR(instr) \
{ \
INSTR_##instr(MOTH_DECODE_WITH_BASE) \
- QDebug d = qDebug(); \
- d.noquote(); \
- d.nospace(); \
if (static_cast<int>(Instr::Type::instr) >= 0x100) \
--base_ptr; \
- d << alignedLineNumber(line) << alignedNumber(codeOffset).constData() << ": " \
+ s << alignedLineNumber(line) << alignedNumber(beginOffset + codeOffset).constData() << ": " \
<< rawBytes(base_ptr, int(code - base_ptr)) << #instr << " ";
#define MOTH_END_INSTR(instr) \
+ s << "\n"; \
continue; \
}
@@ -106,24 +104,37 @@ QString dumpArguments(int argc, int argv, int nFormals)
return QStringLiteral("(") + dumpRegister(argv, nFormals) + QStringLiteral(", ") + QString::number(argc) + QStringLiteral(")");
}
-void dumpBytecode(
+QString dumpBytecode(
const char *code, int len, int nLocals, int nFormals, int /*startLine*/,
const QVector<CompiledData::CodeOffsetToLineAndStatement> &lineAndStatementNumberMapping)
{
+ return dumpBytecode(code, len, nLocals, nFormals, 0, len - 1, lineAndStatementNumberMapping);
+}
+
+QString dumpBytecode(
+ const char *code, int len, int nLocals, int nFormals, int beginOffset, int endOffset,
+ const QVector<CompiledData::CodeOffsetToLineAndStatement> &lineAndStatementNumberMapping)
+{
+ Q_ASSERT(beginOffset <= endOffset && 0 <= beginOffset && endOffset <= len);
+
MOTH_JUMP_TABLE;
auto findLine = [](const CompiledData::CodeOffsetToLineAndStatement &entry, uint offset) {
return entry.codeOffset < offset;
};
+ QString output;
+ QTextStream s{ &output };
+
int lastLine = -1;
+ code += beginOffset;
const char *start = code;
- const char *end = code + len;
+ const char *end = code + (endOffset - beginOffset) + 1;
while (code < end) {
const auto codeToLine = std::lower_bound(
lineAndStatementNumberMapping.constBegin(),
lineAndStatementNumberMapping.constEnd(),
- static_cast<uint>(code - start) + 1, findLine) - 1;
+ static_cast<uint>(code - start + beginOffset) + 1, findLine) - 1;
int line = int(codeToLine->line);
if (line != lastLine)
lastLine = line;
@@ -135,23 +146,23 @@ void dumpBytecode(
MOTH_DISPATCH()
MOTH_BEGIN_INSTR(LoadReg)
- d << dumpRegister(reg, nFormals);
+ s << dumpRegister(reg, nFormals);
MOTH_END_INSTR(LoadReg)
MOTH_BEGIN_INSTR(StoreReg)
- d << dumpRegister(reg, nFormals);
+ s << dumpRegister(reg, nFormals);
MOTH_END_INSTR(StoreReg)
MOTH_BEGIN_INSTR(MoveReg)
- d << dumpRegister(destReg, nFormals) << ", " << dumpRegister(srcReg, nFormals);
+ s << dumpRegister(destReg, nFormals) << ", " << dumpRegister(srcReg, nFormals);
MOTH_END_INSTR(MoveReg)
MOTH_BEGIN_INSTR(LoadImport)
- d << "i" << index;
+ s << "i" << index;
MOTH_END_INSTR(LoadImport)
MOTH_BEGIN_INSTR(LoadConst)
- d << "C" << index;
+ s << "C" << index;
MOTH_END_INSTR(LoadConst)
MOTH_BEGIN_INSTR(LoadNull)
@@ -170,111 +181,111 @@ void dumpBytecode(
MOTH_END_INSTR(LoadUndefined)
MOTH_BEGIN_INSTR(LoadInt)
- d << value;
+ s << value;
MOTH_END_INSTR(LoadInt)
MOTH_BEGIN_INSTR(MoveConst)
- d << dumpRegister(destTemp, nFormals) << ", C" << constIndex;
+ s << dumpRegister(destTemp, nFormals) << ", C" << constIndex;
MOTH_END_INSTR(MoveConst)
MOTH_BEGIN_INSTR(LoadLocal)
if (index < nLocals)
- d << "l" << index;
+ s << "l" << index;
else
- d << "a" << (index - nLocals);
+ s << "a" << (index - nLocals);
MOTH_END_INSTR(LoadLocal)
MOTH_BEGIN_INSTR(StoreLocal)
if (index < nLocals)
- d << "l" << index;
+ s << "l" << index;
else
- d << "a" << (index - nLocals);
+ s << "a" << (index - nLocals);
MOTH_END_INSTR(StoreLocal)
MOTH_BEGIN_INSTR(LoadScopedLocal)
if (index < nLocals)
- d << "l" << index << "@" << scope;
+ s << "l" << index << "@" << scope;
else
- d << "a" << (index - nLocals) << "@" << scope;
+ s << "a" << (index - nLocals) << "@" << scope;
MOTH_END_INSTR(LoadScopedLocal)
MOTH_BEGIN_INSTR(StoreScopedLocal)
if (index < nLocals)
- d << ", " << "l" << index << "@" << scope;
+ s << ", " << "l" << index << "@" << scope;
else
- d << ", " << "a" << (index - nLocals) << "@" << scope;
+ s << ", " << "a" << (index - nLocals) << "@" << scope;
MOTH_END_INSTR(StoreScopedLocal)
MOTH_BEGIN_INSTR(LoadRuntimeString)
- d << stringId;
+ s << stringId;
MOTH_END_INSTR(LoadRuntimeString)
MOTH_BEGIN_INSTR(MoveRegExp)
- d << dumpRegister(destReg, nFormals) << ", " <<regExpId;
+ s << dumpRegister(destReg, nFormals) << ", " <<regExpId;
MOTH_END_INSTR(MoveRegExp)
MOTH_BEGIN_INSTR(LoadClosure)
- d << value;
+ s << value;
MOTH_END_INSTR(LoadClosure)
MOTH_BEGIN_INSTR(LoadName)
- d << name;
+ s << name;
MOTH_END_INSTR(LoadName)
MOTH_BEGIN_INSTR(LoadGlobalLookup)
- d << index;
+ s << index;
MOTH_END_INSTR(LoadGlobalLookup)
MOTH_BEGIN_INSTR(LoadQmlContextPropertyLookup)
- d << index;
+ s << index;
MOTH_END_INSTR(LoadQmlContextPropertyLookup)
MOTH_BEGIN_INSTR(StoreNameSloppy)
- d << name;
+ s << name;
MOTH_END_INSTR(StoreNameSloppy)
MOTH_BEGIN_INSTR(StoreNameStrict)
- d << name;
+ s << name;
MOTH_END_INSTR(StoreNameStrict)
MOTH_BEGIN_INSTR(LoadElement)
- d << dumpRegister(base, nFormals) << "[acc]";
+ s << dumpRegister(base, nFormals) << "[acc]";
MOTH_END_INSTR(LoadElement)
MOTH_BEGIN_INSTR(StoreElement)
- d << dumpRegister(base, nFormals) << "[" << dumpRegister(index, nFormals) << "]";
+ s << dumpRegister(base, nFormals) << "[" << dumpRegister(index, nFormals) << "]";
MOTH_END_INSTR(StoreElement)
MOTH_BEGIN_INSTR(LoadProperty)
- d << "acc[" << name << "]";
+ s << "acc[" << name << "]";
MOTH_END_INSTR(LoadProperty)
MOTH_BEGIN_INSTR(LoadOptionalProperty)
- d << "acc[" << name << "], jump(" << ABSOLUTE_OFFSET() << ")";
+ s << "acc[" << name << "], jump(" << ABSOLUTE_OFFSET() << ")";
MOTH_END_INSTR(LoadOptionalProperty)
MOTH_BEGIN_INSTR(GetLookup)
- d << "acc(" << index << ")";
+ s << "acc(" << index << ")";
MOTH_END_INSTR(GetLookup)
MOTH_BEGIN_INSTR(GetOptionalLookup)
- d << "acc(" << index << "), jump(" << ABSOLUTE_OFFSET() << ")";
+ s << "acc(" << index << "), jump(" << ABSOLUTE_OFFSET() << ")";
MOTH_END_INSTR(GetOptionalLookup)
MOTH_BEGIN_INSTR(StoreProperty)
- d << dumpRegister(base, nFormals) << "[" << name<< "]";
+ s << dumpRegister(base, nFormals) << "[" << name<< "]";
MOTH_END_INSTR(StoreProperty)
MOTH_BEGIN_INSTR(SetLookup)
- d << dumpRegister(base, nFormals) << "(" << index << ")";
+ s << dumpRegister(base, nFormals) << "(" << index << ")";
MOTH_END_INSTR(SetLookup)
MOTH_BEGIN_INSTR(LoadSuperProperty)
- d << dumpRegister(property, nFormals);
+ s << dumpRegister(property, nFormals);
MOTH_END_INSTR(LoadSuperProperty)
MOTH_BEGIN_INSTR(StoreSuperProperty)
- d << dumpRegister(property, nFormals);
+ s << dumpRegister(property, nFormals);
MOTH_END_INSTR(StoreSuperProperty)
MOTH_BEGIN_INSTR(Yield)
@@ -284,73 +295,73 @@ void dumpBytecode(
MOTH_END_INSTR(YieldStar)
MOTH_BEGIN_INSTR(Resume)
- d << ABSOLUTE_OFFSET();
+ s << ABSOLUTE_OFFSET();
MOTH_END_INSTR(Resume)
MOTH_BEGIN_INSTR(CallValue)
- d << dumpRegister(name, nFormals) << dumpArguments(argc, argv, nFormals);
+ s << dumpRegister(name, nFormals) << dumpArguments(argc, argv, nFormals);
MOTH_END_INSTR(CallValue)
MOTH_BEGIN_INSTR(CallWithReceiver)
- d << dumpRegister(name, nFormals) << dumpRegister(thisObject, nFormals)
+ s << dumpRegister(name, nFormals) << dumpRegister(thisObject, nFormals)
<< dumpArguments(argc, argv, nFormals);
MOTH_END_INSTR(CallWithReceiver)
MOTH_BEGIN_INSTR(CallProperty)
- d << dumpRegister(base, nFormals) << "." << name << dumpArguments(argc, argv, nFormals)
+ s << dumpRegister(base, nFormals) << "." << name << dumpArguments(argc, argv, nFormals)
;
MOTH_END_INSTR(CallProperty)
MOTH_BEGIN_INSTR(CallPropertyLookup)
- d << dumpRegister(base, nFormals) << "." << lookupIndex
+ s << dumpRegister(base, nFormals) << "." << lookupIndex
<< dumpArguments(argc, argv, nFormals);
MOTH_END_INSTR(CallPropertyLookup)
MOTH_BEGIN_INSTR(CallName)
- d << name << dumpArguments(argc, argv, nFormals);
+ s << name << dumpArguments(argc, argv, nFormals);
MOTH_END_INSTR(CallName)
MOTH_BEGIN_INSTR(CallPossiblyDirectEval)
- d << dumpArguments(argc, argv, nFormals);
+ s << dumpArguments(argc, argv, nFormals);
MOTH_END_INSTR(CallPossiblyDirectEval)
MOTH_BEGIN_INSTR(CallGlobalLookup)
- d << index << dumpArguments(argc, argv, nFormals);
+ s << index << dumpArguments(argc, argv, nFormals);
MOTH_END_INSTR(CallGlobalLookup)
MOTH_BEGIN_INSTR(CallQmlContextPropertyLookup)
- d << index << dumpArguments(argc, argv, nFormals);
+ s << index << dumpArguments(argc, argv, nFormals);
MOTH_END_INSTR(CallQmlContextPropertyLookup)
MOTH_BEGIN_INSTR(CallWithSpread)
- d << "new " << dumpRegister(func, nFormals) << dumpRegister(thisObject, nFormals)
+ s << "new " << dumpRegister(func, nFormals) << dumpRegister(thisObject, nFormals)
<< dumpArguments(argc, argv, nFormals);
MOTH_END_INSTR(CallWithSpread)
MOTH_BEGIN_INSTR(Construct)
- d << "new " << dumpRegister(func, nFormals) << dumpArguments(argc, argv, nFormals);
+ s << "new " << dumpRegister(func, nFormals) << dumpArguments(argc, argv, nFormals);
MOTH_END_INSTR(Construct)
MOTH_BEGIN_INSTR(ConstructWithSpread)
- d << "new " << dumpRegister(func, nFormals) << dumpArguments(argc, argv, nFormals);
+ s << "new " << dumpRegister(func, nFormals) << dumpArguments(argc, argv, nFormals);
MOTH_END_INSTR(ConstructWithSpread)
MOTH_BEGIN_INSTR(SetUnwindHandler)
if (offset)
- d << ABSOLUTE_OFFSET();
+ s << ABSOLUTE_OFFSET();
else
- d << "<null>";
+ s << "<null>";
MOTH_END_INSTR(SetUnwindHandler)
MOTH_BEGIN_INSTR(UnwindDispatch)
MOTH_END_INSTR(UnwindDispatch)
MOTH_BEGIN_INSTR(UnwindToLabel)
- d << "(" << level << ") " << ABSOLUTE_OFFSET();
+ s << "(" << level << ") " << ABSOLUTE_OFFSET();
MOTH_END_INSTR(UnwindToLabel)
MOTH_BEGIN_INSTR(DeadTemporalZoneCheck)
- d << name;
+ s << name;
MOTH_END_INSTR(DeadTemporalZoneCheck)
MOTH_BEGIN_INSTR(ThrowException)
@@ -366,21 +377,21 @@ void dumpBytecode(
MOTH_END_INSTR(CreateCallContext)
MOTH_BEGIN_INSTR(PushCatchContext)
- d << index << ", " << name;
+ s << index << ", " << name;
MOTH_END_INSTR(PushCatchContext)
MOTH_BEGIN_INSTR(PushWithContext)
MOTH_END_INSTR(PushWithContext)
MOTH_BEGIN_INSTR(PushBlockContext)
- d << index;
+ s << index;
MOTH_END_INSTR(PushBlockContext)
MOTH_BEGIN_INSTR(CloneBlockContext)
MOTH_END_INSTR(CloneBlockContext)
MOTH_BEGIN_INSTR(PushScriptContext)
- d << index;
+ s << index;
MOTH_END_INSTR(PushScriptContext)
MOTH_BEGIN_INSTR(PopScriptContext)
@@ -390,55 +401,55 @@ void dumpBytecode(
MOTH_END_INSTR(PopContext)
MOTH_BEGIN_INSTR(GetIterator)
- d << iterator;
+ s << iterator;
MOTH_END_INSTR(GetIterator)
MOTH_BEGIN_INSTR(IteratorNext)
- d << dumpRegister(value, nFormals) << ", " << dumpRegister(done, nFormals);
+ s << dumpRegister(value, nFormals) << ", " << dumpRegister(done, nFormals);
MOTH_END_INSTR(IteratorNext)
MOTH_BEGIN_INSTR(IteratorNextForYieldStar)
- d << dumpRegister(iterator, nFormals) << ", " << dumpRegister(object, nFormals);
+ s << dumpRegister(iterator, nFormals) << ", " << dumpRegister(object, nFormals);
MOTH_END_INSTR(IteratorNextForYieldStar)
MOTH_BEGIN_INSTR(IteratorClose)
- d << dumpRegister(done, nFormals);
+ s << dumpRegister(done, nFormals);
MOTH_END_INSTR(IteratorClose)
MOTH_BEGIN_INSTR(DestructureRestElement)
MOTH_END_INSTR(DestructureRestElement)
MOTH_BEGIN_INSTR(DeleteProperty)
- d << dumpRegister(base, nFormals) << "[" << dumpRegister(index, nFormals) << "]";
+ s << dumpRegister(base, nFormals) << "[" << dumpRegister(index, nFormals) << "]";
MOTH_END_INSTR(DeleteProperty)
MOTH_BEGIN_INSTR(DeleteName)
- d << name;
+ s << name;
MOTH_END_INSTR(DeleteName)
MOTH_BEGIN_INSTR(TypeofName)
- d << name;
+ s << name;
MOTH_END_INSTR(TypeofName)
MOTH_BEGIN_INSTR(TypeofValue)
MOTH_END_INSTR(TypeofValue)
MOTH_BEGIN_INSTR(DeclareVar)
- d << isDeletable << ", " << varName;
+ s << isDeletable << ", " << varName;
MOTH_END_INSTR(DeclareVar)
MOTH_BEGIN_INSTR(DefineArray)
- d << dumpRegister(args, nFormals) << ", " << argc;
+ s << dumpRegister(args, nFormals) << ", " << argc;
MOTH_END_INSTR(DefineArray)
MOTH_BEGIN_INSTR(DefineObjectLiteral)
- d << internalClassId
+ s << internalClassId
<< ", " << argc
<< ", " << dumpRegister(args, nFormals);
MOTH_END_INSTR(DefineObjectLiteral)
MOTH_BEGIN_INSTR(CreateClass)
- d << classIndex
+ s << classIndex
<< ", " << dumpRegister(heritage, nFormals)
<< ", " << dumpRegister(computedNames, nFormals);
MOTH_END_INSTR(CreateClass)
@@ -450,7 +461,7 @@ void dumpBytecode(
MOTH_END_INSTR(CreateUnmappedArgumentsObject)
MOTH_BEGIN_INSTR(CreateRestParameter)
- d << argIndex;
+ s << argIndex;
MOTH_END_INSTR(CreateRestParameter)
MOTH_BEGIN_INSTR(ConvertThisToObject)
@@ -463,23 +474,23 @@ void dumpBytecode(
MOTH_END_INSTR(ToObject)
MOTH_BEGIN_INSTR(Jump)
- d << ABSOLUTE_OFFSET();
+ s << ABSOLUTE_OFFSET();
MOTH_END_INSTR(Jump)
MOTH_BEGIN_INSTR(JumpTrue)
- d << ABSOLUTE_OFFSET();
+ s << ABSOLUTE_OFFSET();
MOTH_END_INSTR(JumpTrue)
MOTH_BEGIN_INSTR(JumpFalse)
- d << ABSOLUTE_OFFSET();
+ s << ABSOLUTE_OFFSET();
MOTH_END_INSTR(JumpFalse)
MOTH_BEGIN_INSTR(JumpNotUndefined)
- d << ABSOLUTE_OFFSET();
+ s << ABSOLUTE_OFFSET();
MOTH_END_INSTR(JumpNotUndefined)
MOTH_BEGIN_INSTR(JumpNoException)
- d << ABSOLUTE_OFFSET();
+ s << ABSOLUTE_OFFSET();
MOTH_END_INSTR(JumpNoException)
MOTH_BEGIN_INSTR(CheckException)
@@ -492,43 +503,43 @@ void dumpBytecode(
MOTH_END_INSTR(CmpNeNull)
MOTH_BEGIN_INSTR(CmpEqInt)
- d << lhs;
+ s << lhs;
MOTH_END_INSTR(CmpEq)
MOTH_BEGIN_INSTR(CmpNeInt)
- d << lhs;
+ s << lhs;
MOTH_END_INSTR(CmpNeInt)
MOTH_BEGIN_INSTR(CmpEq)
- d << dumpRegister(lhs, nFormals);
+ s << dumpRegister(lhs, nFormals);
MOTH_END_INSTR(CmpEq)
MOTH_BEGIN_INSTR(CmpNe)
- d << dumpRegister(lhs, nFormals);
+ s << dumpRegister(lhs, nFormals);
MOTH_END_INSTR(CmpNe)
MOTH_BEGIN_INSTR(CmpGt)
- d << dumpRegister(lhs, nFormals);
+ s << dumpRegister(lhs, nFormals);
MOTH_END_INSTR(CmpGt)
MOTH_BEGIN_INSTR(CmpGe)
- d << dumpRegister(lhs, nFormals);
+ s << dumpRegister(lhs, nFormals);
MOTH_END_INSTR(CmpGe)
MOTH_BEGIN_INSTR(CmpLt)
- d << dumpRegister(lhs, nFormals);
+ s << dumpRegister(lhs, nFormals);
MOTH_END_INSTR(CmpLt)
MOTH_BEGIN_INSTR(CmpLe)
- d << dumpRegister(lhs, nFormals);
+ s << dumpRegister(lhs, nFormals);
MOTH_END_INSTR(CmpLe)
MOTH_BEGIN_INSTR(CmpStrictEqual)
- d << dumpRegister(lhs, nFormals);
+ s << dumpRegister(lhs, nFormals);
MOTH_END_INSTR(CmpStrictEqual)
MOTH_BEGIN_INSTR(CmpStrictNotEqual)
- d << dumpRegister(lhs, nFormals);
+ s << dumpRegister(lhs, nFormals);
MOTH_END_INSTR(CmpStrictNotEqual)
MOTH_BEGIN_INSTR(UNot)
@@ -550,87 +561,87 @@ void dumpBytecode(
MOTH_END_INSTR(Decrement)
MOTH_BEGIN_INSTR(Add)
- d << dumpRegister(lhs, nFormals) << ", acc";
+ s << dumpRegister(lhs, nFormals) << ", acc";
MOTH_END_INSTR(Add)
MOTH_BEGIN_INSTR(BitAnd)
- d << dumpRegister(lhs, nFormals) << ", acc";
+ s << dumpRegister(lhs, nFormals) << ", acc";
MOTH_END_INSTR(BitAnd)
MOTH_BEGIN_INSTR(BitOr)
- d << dumpRegister(lhs, nFormals) << ", acc";
+ s << dumpRegister(lhs, nFormals) << ", acc";
MOTH_END_INSTR(BitOr)
MOTH_BEGIN_INSTR(BitXor)
- d << dumpRegister(lhs, nFormals) << ", acc";
+ s << dumpRegister(lhs, nFormals) << ", acc";
MOTH_END_INSTR(BitXor)
MOTH_BEGIN_INSTR(UShr)
- d << dumpRegister(lhs, nFormals) << ", acc";
+ s << dumpRegister(lhs, nFormals) << ", acc";
MOTH_END_INSTR(UShr)
MOTH_BEGIN_INSTR(Shr)
- d << dumpRegister(lhs, nFormals) << ", acc";
+ s << dumpRegister(lhs, nFormals) << ", acc";
MOTH_END_INSTR(Shr)
MOTH_BEGIN_INSTR(Shl)
- d << dumpRegister(lhs, nFormals) << ", acc";
+ s << dumpRegister(lhs, nFormals) << ", acc";
MOTH_END_INSTR(Shl)
MOTH_BEGIN_INSTR(BitAndConst)
- d << "acc, " << rhs;
+ s << "acc, " << rhs;
MOTH_END_INSTR(BitAndConst)
MOTH_BEGIN_INSTR(BitOrConst)
- d << "acc, " << rhs;
+ s << "acc, " << rhs;
MOTH_END_INSTR(BitOr)
MOTH_BEGIN_INSTR(BitXorConst)
- d << "acc, " << rhs;
+ s << "acc, " << rhs;
MOTH_END_INSTR(BitXor)
MOTH_BEGIN_INSTR(UShrConst)
- d << "acc, " << rhs;
+ s << "acc, " << rhs;
MOTH_END_INSTR(UShrConst)
MOTH_BEGIN_INSTR(ShrConst)
- d << "acc, " << rhs;
+ s << "acc, " << rhs;
MOTH_END_INSTR(ShrConst)
MOTH_BEGIN_INSTR(ShlConst)
- d << "acc, " << rhs;
+ s << "acc, " << rhs;
MOTH_END_INSTR(ShlConst)
MOTH_BEGIN_INSTR(Exp)
- d << dumpRegister(lhs, nFormals) << ", acc";
+ s << dumpRegister(lhs, nFormals) << ", acc";
MOTH_END_INSTR(Exp)
MOTH_BEGIN_INSTR(Mul)
- d << dumpRegister(lhs, nFormals) << ", acc";
+ s << dumpRegister(lhs, nFormals) << ", acc";
MOTH_END_INSTR(Mul)
MOTH_BEGIN_INSTR(Div)
- d << dumpRegister(lhs, nFormals) << ", acc";
+ s << dumpRegister(lhs, nFormals) << ", acc";
MOTH_END_INSTR(Div)
MOTH_BEGIN_INSTR(Mod)
- d << dumpRegister(lhs, nFormals) << ", acc";
+ s << dumpRegister(lhs, nFormals) << ", acc";
MOTH_END_INSTR(Mod)
MOTH_BEGIN_INSTR(Sub)
- d << dumpRegister(lhs, nFormals) << ", acc";
+ s << dumpRegister(lhs, nFormals) << ", acc";
MOTH_END_INSTR(Sub)
MOTH_BEGIN_INSTR(As)
- d << dumpRegister(lhs, nFormals) << ", acc";
+ s << dumpRegister(lhs, nFormals) << ", acc";
MOTH_END_INSTR(Sub)
MOTH_BEGIN_INSTR(CmpIn)
- d << dumpRegister(lhs, nFormals) << ", acc";
+ s << dumpRegister(lhs, nFormals) << ", acc";
MOTH_END_INSTR(CmpIn)
MOTH_BEGIN_INSTR(CmpInstanceOf)
- d << dumpRegister(lhs, nFormals) << ", acc";
+ s << dumpRegister(lhs, nFormals) << ", acc";
MOTH_END_INSTR(CmpInstanceOf)
MOTH_BEGIN_INSTR(Ret)
@@ -640,20 +651,21 @@ void dumpBytecode(
MOTH_END_INSTR(Debug)
MOTH_BEGIN_INSTR(InitializeBlockDeadTemporalZone)
- d << dumpRegister(firstReg, nFormals) << ", " << count;
+ s << dumpRegister(firstReg, nFormals) << ", " << count;
MOTH_END_INSTR(InitializeBlockDeadTemporalZone)
MOTH_BEGIN_INSTR(ThrowOnNullOrUndefined)
MOTH_END_INSTR(ThrowOnNullOrUndefined)
MOTH_BEGIN_INSTR(GetTemplateObject)
- d << index;
+ s << index;
MOTH_END_INSTR(GetTemplateObject)
MOTH_BEGIN_INSTR(TailCall)
- d << dumpRegister(func, nFormals) << dumpRegister(thisObject, nFormals) << dumpArguments(argc, argv, nFormals);
+ s << dumpRegister(func, nFormals) << dumpRegister(thisObject, nFormals) << dumpArguments(argc, argv, nFormals);
MOTH_END_INSTR(TailCall)
}
+ return output;
}
}
diff --git a/src/qml/compiler/qv4instr_moth_p.h b/src/qml/compiler/qv4instr_moth_p.h
index c61db9a6ce..7ebf849f3e 100644
--- a/src/qml/compiler/qv4instr_moth_p.h
+++ b/src/qml/compiler/qv4instr_moth_p.h
@@ -497,14 +497,22 @@ inline bool operator!=(const StackSlot &l, const StackSlot &r) { return l.stackS
// When making changes to the instructions, make sure to bump QV4_DATA_STRUCTURE_VERSION in qv4compileddata_p.h
-void dumpBytecode(const char *bytecode, int len, int nLocals, int nFormals, int startLine = 1,
- const QVector<CompiledData::CodeOffsetToLineAndStatement> &lineAndStatementNumberMapping
- = QVector<CompiledData::CodeOffsetToLineAndStatement>());
-inline void dumpBytecode(const QByteArray &bytecode, int nLocals, int nFormals, int startLine = 1,
- const QVector<CompiledData::CodeOffsetToLineAndStatement> &lineAndStatementNumberMapping
- = QVector<CompiledData::CodeOffsetToLineAndStatement>()) {
- dumpBytecode(bytecode.constData(), bytecode.size(), nLocals, nFormals, startLine,
- lineAndStatementNumberMapping);
+Q_QML_PRIVATE_EXPORT
+QString dumpBytecode(
+ const char *bytecode, int len, int nLocals, int nFormals, int beginOffset, int endOffset,
+ const QVector<CompiledData::CodeOffsetToLineAndStatement> &lineAndStatementNumberMapping =
+ QVector<CompiledData::CodeOffsetToLineAndStatement>());
+QString dumpBytecode(
+ const char *bytecode, int len, int nLocals, int nFormals, int startLine = 1,
+ const QVector<CompiledData::CodeOffsetToLineAndStatement> &lineAndStatementNumberMapping =
+ QVector<CompiledData::CodeOffsetToLineAndStatement>());
+inline QString dumpBytecode(
+ const QByteArray &bytecode, int nLocals, int nFormals, int startLine = 1,
+ const QVector<CompiledData::CodeOffsetToLineAndStatement> &lineAndStatementNumberMapping =
+ QVector<CompiledData::CodeOffsetToLineAndStatement>())
+{
+ return dumpBytecode(bytecode.constData(), bytecode.size(), nLocals, nFormals, startLine,
+ lineAndStatementNumberMapping);
}
union Instr
diff --git a/src/qml/doc/src/javascript/finetuning.qdoc b/src/qml/doc/src/javascript/finetuning.qdoc
index 14b4c9538d..e2d754eb83 100644
--- a/src/qml/doc/src/javascript/finetuning.qdoc
+++ b/src/qml/doc/src/javascript/finetuning.qdoc
@@ -82,6 +82,14 @@ Running JavaScript code can be influenced by a few environment variables, partic
\li Outputs the IR bytecode generated by Qt to the console.
Has to be combined with \c{QML_DISABLE_DISK_CACHE} or already cached bytecode will not
be shown.
+ \row
+ \li \c{QV4_DUMP_BASIC_BLOCKS}
+ \li Outputs the basic blocks of each function compiled ahead of time. The details of the
+ blocks are printed to the console. Additionally, control flow graphs with the byte code
+ for each block are generated in the DOT format for each compiled function. The value of
+ \c {QV4_DUMP_BASIC_BLOCKS} is used as the path to the folder where the DOT files should
+ be generated. If the path is any of ["-", "1", "true"] or if files can't be opened,
+ the graphs are dumped to stdout instead.
\endtable
\l{The QML Disk Cache} accepts further environment variables that allow fine tuning its behavior.
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"
+ " &#160; 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, "&#160;"_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);
}
diff --git a/src/qmlcompiler/qqmljsbasicblocks_p.h b/src/qmlcompiler/qqmljsbasicblocks_p.h
index 2b4fc108fc..b641928f32 100644
--- a/src/qmlcompiler/qqmljsbasicblocks_p.h
+++ b/src/qmlcompiler/qqmljsbasicblocks_p.h
@@ -31,9 +31,10 @@ public:
bool jumpIsUnconditional = false;
};
- QQmlJSBasicBlocks(const QV4::Compiler::JSUnitGenerator *unitGenerator,
+ QQmlJSBasicBlocks(const QV4::Compiler::Context *context,
+ const QV4::Compiler::JSUnitGenerator *unitGenerator,
const QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger)
- : QQmlJSCompilePass(unitGenerator, typeResolver, logger)
+ : QQmlJSCompilePass(unitGenerator, typeResolver, logger), m_context{ context }
{
}
@@ -84,6 +85,10 @@ private:
void adjustTypes();
bool canMove(int instructionOffset, const RegisterAccess &access) const;
+ void dumpBasicBlocks();
+ void dumpDOTGraph();
+
+ const QV4::Compiler::Context *m_context;
InstructionAnnotations m_annotations;
QFlatMap<int, BasicBlock> m_basicBlocks;
QHash<int, RegisterAccess> m_readerLocations;
diff --git a/src/qmlcompiler/qqmljscompiler.cpp b/src/qmlcompiler/qqmljscompiler.cpp
index 0a0c97926a..314e241520 100644
--- a/src/qmlcompiler/qqmljscompiler.cpp
+++ b/src/qmlcompiler/qqmljscompiler.cpp
@@ -784,7 +784,7 @@ QQmlJSAotFunction QQmlJSAotCompiler::doCompile(
if (error->isValid())
return compileError();
- QQmlJSBasicBlocks basicBlocks(m_unitGenerator, &m_typeResolver, m_logger);
+ QQmlJSBasicBlocks basicBlocks(context, m_unitGenerator, &m_typeResolver, m_logger);
typePropagationResult = basicBlocks.run(function, typePropagationResult, error);
if (error->isValid())
return compileError();