Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qqmljsbasicblocks.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
5#include "qqmljsutils_p.h"
6
7#include <QtQml/private/qv4instr_moth_p.h>
8
10
11using namespace Qt::Literals::StringLiterals;
12
13DEFINE_BOOL_CONFIG_OPTION(qv4DumpBasicBlocks, QV4_DUMP_BASIC_BLOCKS)
14DEFINE_BOOL_CONFIG_OPTION(qv4ValidateBasicBlocks, QV4_VALIDATE_BASIC_BLOCKS)
15
16void QQmlJSBasicBlocks::dumpBasicBlocks()
17{
18 qDebug().noquote() << "=== Basic Blocks for \"%1\""_L1.arg(m_context->name);
19 for (const auto &[blockOffset, block] : m_basicBlocks) {
20 QDebug debug = qDebug().nospace();
21 debug << "Block " << (blockOffset < 0 ? "Function prolog"_L1 : QString::number(blockOffset))
22 << ":\n";
23 debug << " jumpOrigins[" << block.jumpOrigins.size() << "]: ";
24 for (auto origin : block.jumpOrigins) {
25 debug << origin << ", ";
26 }
27 debug << "\n readRegisters[" << block.readRegisters.size() << "]: ";
28 for (auto reg : block.readRegisters) {
29 debug << reg << ", ";
30 }
31 debug << "\n jumpTarget: " << block.jumpTarget;
32 debug << "\n jumpIsUnConditional: " << block.jumpIsUnconditional;
33 debug << "\n isReturnBlock: " << block.isReturnBlock;
34 debug << "\n isThrowBlock: " << block.isThrowBlock;
35 }
36 qDebug() << "\n";
37}
38
39void QQmlJSBasicBlocks::dumpDOTGraph()
40{
43 s << "=== Basic Blocks Graph in DOT format for \"%1\" (spaces are encoded as"
44 " &#160; to preserve formatting)\n"_L1.arg(m_context->name);
45 s << "digraph BasicBlocks {\n"_L1;
46
47 QFlatMap<int, BasicBlock> blocks{ m_basicBlocks };
48 for (const auto &[blockOffset, block] : blocks) {
49 for (int originOffset : block.jumpOrigins) {
50 const auto originBlockIt = basicBlockForInstruction(blocks, originOffset);
51 const auto isBackEdge = originOffset > blockOffset && originBlockIt->second.jumpIsUnconditional;
52 s << " %1 -> %2%3\n"_L1.arg(QString::number(originBlockIt.key()))
53 .arg(QString::number(blockOffset))
54 .arg(isBackEdge ? " [color=blue]"_L1 : ""_L1);
55 }
56 }
57
58 for (const auto &[blockOffset, block] : blocks) {
59 if (blockOffset < 0) {
60 s << " %1 [shape=record, fontname=\"Monospace\", label=\"Function Prolog\"]\n"_L1
61 .arg(QString::number(blockOffset));
62 continue;
63 }
64
65 auto nextBlockIt = blocks.lower_bound(blockOffset + 1);
66 int nextBlockOffset = nextBlockIt == blocks.end() ? m_context->code.size() : nextBlockIt->first;
68 m_context->code.constData(), m_context->code.size(), m_context->locals.size(),
69 m_context->formals->length(), blockOffset, nextBlockOffset - 1,
71 dump = dump.replace(" "_L1, "&#160;"_L1); // prevent collapse of extra whitespace for formatting
72 dump = dump.replace("\n"_L1, "\\l"_L1); // new line + left aligned
73 s << " %1 [shape=record, fontname=\"Monospace\", label=\"{Block %1: | %2}\"]\n"_L1
74 .arg(QString::number(blockOffset))
75 .arg(dump);
76 }
77 s << "}\n"_L1;
78
79 // Have unique names to prevent overwriting of functions with the same name (eg. anonymous functions).
80 static int functionCount = 0;
81 static const auto dumpFolderPath = qEnvironmentVariable("QV4_DUMP_BASIC_BLOCKS");
82
83 QString expressionName = m_context->name == ""_L1
84 ? "anonymous"_L1
85 : QString(m_context->name).replace(" "_L1, "_"_L1);
86 QString fileName = "function"_L1 + QString::number(functionCount++) + "_"_L1 + expressionName + ".gv"_L1;
87 QFile dumpFile(dumpFolderPath + (dumpFolderPath.endsWith("/"_L1) ? ""_L1 : "/"_L1) + fileName);
88
89 if (dumpFolderPath == "-"_L1 || dumpFolderPath == "1"_L1 || dumpFolderPath == "true"_L1) {
90 qDebug().noquote() << output;
91 } else {
92 if (!dumpFile.open(QIODeviceBase::Truncate | QIODevice::WriteOnly)) {
93 qDebug() << "Error: Could not open file to dump the basic blocks into";
94 } else {
95 dumpFile.write(("//"_L1 + output).toLatin1().toStdString().c_str());
96 dumpFile.close();
97 }
98 }
99}
100
102QQmlJSBasicBlocks::run(const Function *function, QQmlJSAotCompiler::Flags compileFlags,
103 bool &basicBlocksValidationFailed)
104{
105 basicBlocksValidationFailed = false;
106
108
109 for (int i = 0, end = function->argumentTypes.size(); i != end; ++i) {
110 InstructionAnnotation annotation;
111 annotation.changedRegisterIndex = FirstArgument + i;
112 annotation.changedRegister = function->argumentTypes[i];
113 m_annotations[-annotation.changedRegisterIndex] = annotation;
114 }
115
116 for (int i = 0, end = function->registerTypes.size(); i != end; ++i) {
117 InstructionAnnotation annotation;
119 annotation.changedRegister = function->registerTypes[i];
120 m_annotations[-annotation.changedRegisterIndex] = annotation;
121 }
122
123 // Insert the function prolog block followed by the first "real" block.
125 BasicBlock zeroBlock;
126 zeroBlock.jumpOrigins.append(m_basicBlocks.begin().key());
127 m_basicBlocks.insert(0, zeroBlock);
128
129 const QByteArray byteCode = function->code;
130 decode(byteCode.constData(), static_cast<uint>(byteCode.size()));
131 if (m_hadBackJumps) {
132 // We may have missed some connections between basic blocks if there were back jumps.
133 // Fill them in via a second pass.
134
135 // We also need to re-calculate the jump targets then because the basic block boundaries
136 // may have shifted.
137 for (auto it = m_basicBlocks.begin(), end = m_basicBlocks.end(); it != end; ++it) {
138 it->second.jumpTarget = -1;
139 it->second.jumpIsUnconditional = false;
140 }
141
142 m_skipUntilNextLabel = false;
143
144 reset();
145 decode(byteCode.constData(), static_cast<uint>(byteCode.size()));
146 for (auto it = m_basicBlocks.begin(), end = m_basicBlocks.end(); it != end; ++it)
147 QQmlJSUtils::deduplicate(it->second.jumpOrigins);
148 }
149
150 if (compileFlags.testFlag(QQmlJSAotCompiler::ValidateBasicBlocks) || qv4ValidateBasicBlocks()) {
151 if (auto validationResult = basicBlocksValidation(); !validationResult.success) {
152 qDebug() << "Basic blocks validation failed: %1."_L1.arg(validationResult.errorMessage);
153 basicBlocksValidationFailed = true;
154 }
155 }
156
157 if (qv4DumpBasicBlocks()) {
158 dumpBasicBlocks();
159 dumpDOTGraph();
160 }
161
162 return { std::move(m_basicBlocks), std::move(m_annotations) };
163}
164
166{
168 if (it != m_basicBlocks.end()) {
169 m_skipUntilNextLabel = false;
170 } else if (m_skipUntilNextLabel && !instructionManipulatesContext(type)) {
171 return SkipInstruction;
172 }
173
174 return ProcessInstruction;
175}
176
178{
179 if (m_skipUntilNextLabel)
180 return;
182 if (it != m_basicBlocks.end())
183 it->second.jumpOrigins.append(currentInstructionOffset());
184}
185
187{
188 processJump(offset, Unconditional);
189}
190
192{
193 processJump(offset, Conditional);
194}
195
197{
198 processJump(offset, Conditional);
199}
200
202{
203 processJump(offset, Conditional);
204}
205
207{
208 processJump(offset, Conditional);
209}
210
212{
214 processJump(offset, Conditional);
215}
216
218{
220 processJump(offset, Conditional);
221}
222
224{
226 currentBlock.value().isReturnBlock = true;
227 m_skipUntilNextLabel = true;
228}
229
231{
233 currentBlock.value().isThrowBlock = true;
234 m_skipUntilNextLabel = true;
235}
236
238{
239 if (argc == 0)
240 return; // empty array/list, nothing to do
241
242 m_objectAndArrayDefinitions.append({
243 currentInstructionOffset(), ObjectOrArrayDefinition::ArrayClassId, argc, argv
244 });
245}
246
247void QQmlJSBasicBlocks::generate_DefineObjectLiteral(int internalClassId, int argc, int argv)
248{
249 if (argc == 0)
250 return;
251
252 m_objectAndArrayDefinitions.append({ currentInstructionOffset(), internalClassId, argc, argv });
253}
254
255void QQmlJSBasicBlocks::generate_Construct(int func, int argc, int argv)
256{
258 if (argc == 0)
259 return; // empty array/list, nothing to do
260
261 m_objectAndArrayDefinitions.append({
263 argc == 1
264 ? ObjectOrArrayDefinition::ArrayConstruct1ArgId
265 : ObjectOrArrayDefinition::ArrayClassId,
266 argc,
267 argv
268 });
269}
270
271void QQmlJSBasicBlocks::processJump(int offset, JumpMode mode)
272{
273 if (offset < 0)
274 m_hadBackJumps = true;
275 const int jumpTarget = absoluteOffset(offset);
278 currentBlock->second.jumpTarget = jumpTarget;
279 currentBlock->second.jumpIsUnconditional = (mode == Unconditional);
280 m_basicBlocks[jumpTarget].jumpOrigins.append(currentInstructionOffset());
281 if (mode == Unconditional)
282 m_skipUntilNextLabel = true;
283 else
285}
286
287QQmlJSCompilePass::BasicBlocks::iterator QQmlJSBasicBlocks::basicBlockForInstruction(
288 QFlatMap<int, BasicBlock> &container, int instructionOffset)
289{
290 auto block = container.lower_bound(instructionOffset);
291 if (block == container.end() || block->first != instructionOffset)
292 --block;
293 return block;
294}
295
296QQmlJSCompilePass::BasicBlocks::const_iterator QQmlJSBasicBlocks::basicBlockForInstruction(
297 const BasicBlocks &container, int instructionOffset)
298{
299 return basicBlockForInstruction(const_cast<BasicBlocks &>(container), instructionOffset);
300}
301
303{
304 if (m_basicBlocks.empty())
305 return {};
306
307 const QFlatMap<int, BasicBlock> blocks{ m_basicBlocks };
308 QList<QFlatMap<int, BasicBlock>::const_iterator> returnOrThrowBlocks;
309 for (auto it = blocks.cbegin(); it != blocks.cend(); ++it) {
310 if (it.value().isReturnBlock || it.value().isThrowBlock)
311 returnOrThrowBlocks.append(it);
312 }
313
314 // 1. Return blocks and throw blocks must not have a jump target
315 for (const auto &it : returnOrThrowBlocks) {
316 if (it.value().jumpTarget != -1)
317 return { false, "Return or throw block jumps to somewhere"_L1 };
318 }
319
320 // 2. The basic blocks graph must be connected.
321 QSet<int> visitedBlockOffsets;
322 QList<QFlatMap<int, BasicBlock>::const_iterator> toVisit;
323 toVisit.append(returnOrThrowBlocks);
324
325 while (!toVisit.empty()) {
326 const auto &[offset, block] = *toVisit.takeLast();
327 visitedBlockOffsets.insert(offset);
328 for (int originOffset : block.jumpOrigins) {
329 const auto originBlock = basicBlockForInstruction(blocks, originOffset);
330 if (visitedBlockOffsets.find(originBlock.key()) == visitedBlockOffsets.end()
331 && !toVisit.contains(originBlock))
332 toVisit.append(originBlock);
333 }
334 }
335
336 if (visitedBlockOffsets.size() != blocks.size())
337 return { false, "Basic blocks graph is not fully connected"_L1 };
338
339 // 3. A block's jump target must be the first offset of a block.
340 for (const auto &[blockOffset, block] : blocks) {
341 auto target = block.jumpTarget;
342 if (target != -1 && blocks.find(target) == blocks.end())
343 return { false, "Invalid jump; target is not the start of a block"_L1 };
344 }
345
346 return {};
347}
348
\inmodule QtCore
Definition qbytearray.h:57
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:495
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
\inmodule QtCore
\inmodule QtCore
Definition qfile.h:93
bool isEmpty() const noexcept
Definition qflatmap_p.h:579
bool empty() const noexcept
Definition qflatmap_p.h:580
std::pair< iterator, bool > insert_or_assign(const Key &key, M &&obj)
Definition qflatmap_p.h:724
iterator begin()
Definition qflatmap_p.h:769
iterator end()
Definition qflatmap_p.h:773
iterator find(const Key &key)
Definition qflatmap_p.h:816
std::pair< iterator, bool > insert(const Key &key, const T &value)
Definition qflatmap_p.h:678
void append(parameter_type t)
Definition qlist.h:459
void endInstruction(QV4::Moth::Instr::Type type) override
void generate_JumpNoException(int offset) override
BasicBlocksValidationResult basicBlocksValidation()
QV4::Moth::ByteCodeHandler::Verdict startInstruction(QV4::Moth::Instr::Type type) override
void generate_GetOptionalLookup(int index, int offset) override
void generate_DefineObjectLiteral(int internalClassId, int argc, int args) override
QQmlJSCompilePass::BlocksAndAnnotations run(const Function *function, QQmlJSAotCompiler::Flags compileFlags, bool &basicBlocksValidationFailed)
void generate_Ret() override
void generate_JumpNotUndefined(int offset) override
void generate_Jump(int offset) override
static BasicBlocks::iterator basicBlockForInstruction(QFlatMap< int, BasicBlock > &container, int instructionOffset)
void generate_JumpFalse(int offset) override
void generate_IteratorNext(int value, int offset) override
void generate_ThrowException() override
void generate_DefineArray(int argc, int argv) override
void generate_JumpTrue(int offset) override
void generate_Construct(int func, int argc, int argv) override
static bool instructionManipulatesContext(QV4::Moth::Instr::Type type)
const Function * m_function
InstructionAnnotations m_annotations
QFlatMap< int, BasicBlock > BasicBlocks
const_iterator cend() const noexcept
Definition qset.h:143
const_iterator cbegin() const noexcept
Definition qset.h:139
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
QString & replace(qsizetype i, qsizetype len, QChar after)
Definition qstring.cpp:3829
static QString number(int, int base=10)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:8095
\inmodule QtCore
int absoluteOffset(int relativeOffset) const
QSet< QString >::iterator it
Combined button and popup list for selecting options.
QString dumpBytecode(const char *code, int len, int nLocals, int nFormals, int, const QVector< CompiledData::CodeOffsetToLineAndStatement > &lineAndStatementNumberMapping)
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction function
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define qDebug
[1]
Definition qlogging.h:165
GLenum mode
GLuint index
[2]
GLuint GLuint end
GLenum type
GLenum target
GLenum GLuint GLintptr offset
GLdouble s
[6]
Definition qopenglext.h:235
GLenum func
Definition qopenglext.h:663
#define DEFINE_BOOL_CONFIG_OPTION(name, var)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
static QString dump(const QByteArray &)
QString qEnvironmentVariable(const char *varName, const QString &defaultValue)
#define Q_UNUSED(x)
unsigned int uint
Definition qtypes.h:34
#define decode(x)
QT_BEGIN_NAMESPACE typedef uchar * output
static void deduplicate(Container &container)
QQmlJS::AST::FormalParameterList * formals
QVector< CompiledData::CodeOffsetToLineAndStatement > lineAndStatementNumberMapping