Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
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// Qt-Security score:significant
4
6
7#include <private/qqmlglobal_p.h>
8#include <private/qqmljsutils_p.h>
9#include <private/qv4instr_moth_p.h>
10
12
13using namespace Qt::Literals::StringLiterals;
14
15DEFINE_BOOL_CONFIG_OPTION(qv4DumpBasicBlocks, QV4_DUMP_BASIC_BLOCKS)
16DEFINE_BOOL_CONFIG_OPTION(qv4ValidateBasicBlocks, QV4_VALIDATE_BASIC_BLOCKS)
17
18void QQmlJSBasicBlocks::dumpBasicBlocks()
19{
20 qDebug().noquote() << "=== Basic Blocks for \"%1\""_L1.arg(m_context->name);
21 for (const auto &[blockOffset, block] : m_basicBlocks) {
22 QDebug debug = qDebug().nospace();
23 debug << "Block " << (blockOffset < 0 ? "Function prolog"_L1 : QString::number(blockOffset))
24 << ":\n";
25 debug << " jumpOrigins[" << block.jumpOrigins.size() << "]: ";
26 for (auto origin : std::as_const(block.jumpOrigins)) {
27 debug << origin << ", ";
28 }
29 debug << "\n readRegisters[" << block.readRegisters.size() << "]: ";
30 for (auto reg : std::as_const(block.readRegisters)) {
31 debug << reg << ", ";
32 }
33 debug << "\n jumpTarget: " << block.jumpTarget;
34 debug << "\n jumpIsUnConditional: " << block.jumpIsUnconditional;
35 debug << "\n isReturnBlock: " << block.isReturnBlock;
36 debug << "\n isThrowBlock: " << block.isThrowBlock;
37 }
38 qDebug() << "\n";
39}
40
41void QQmlJSBasicBlocks::dumpDOTGraph()
42{
43 QString output;
44 QTextStream s{ &output };
45 s << "=== Basic Blocks Graph in DOT format for \"%1\" (spaces are encoded as"
46 " &#160; to preserve formatting)\n"_L1.arg(m_context->name);
47 s << "digraph BasicBlocks {\n"_L1;
48
49 QFlatMap<int, BasicBlock> blocks{ m_basicBlocks };
50 for (const auto &[blockOffset, block] : blocks) {
51 for (int originOffset : std::as_const(block.jumpOrigins)) {
52 const auto originBlockIt = basicBlockForInstruction(blocks, originOffset);
53 const auto isBackEdge = originOffset > blockOffset && originBlockIt->second.jumpIsUnconditional;
54 s << " %1 -> %2%3\n"_L1.arg(QString::number(originBlockIt.key()))
55 .arg(QString::number(blockOffset))
56 .arg(isBackEdge ? " [color=blue]"_L1 : ""_L1);
57 }
58 }
59
60 for (const auto &[blockOffset, block] : blocks) {
61 if (blockOffset < 0) {
62 s << " %1 [shape=record, fontname=\"Monospace\", label=\"Function Prolog\"]\n"_L1
63 .arg(QString::number(blockOffset));
64 continue;
65 }
66
67 auto nextBlockIt = blocks.lower_bound(blockOffset + 1);
68 int nextBlockOffset = nextBlockIt == blocks.end() ? m_context->code.size() : nextBlockIt->first;
69 QString dump = QV4::Moth::dumpBytecode(
70 m_context->code.constData(), m_context->code.size(), m_context->locals.size(),
71 m_context->formals->length(), blockOffset, nextBlockOffset - 1,
72 m_context->lineAndStatementNumberMapping);
73 dump = dump.replace(" "_L1, "&#160;"_L1); // prevent collapse of extra whitespace for formatting
74 dump = dump.replace("\n"_L1, "\\l"_L1); // new line + left aligned
75 dump = dump.replace("<"_L1, "\<"_L1).replace(">"_L1, "\>"_L1); // escape < and > for "SetUnwindHandler <null>" instruction
76 s << " %1 [shape=record, fontname=\"Monospace\", label=\"{Block %1: | %2}\"]\n"_L1
77 .arg(QString::number(blockOffset))
78 .arg(dump);
79 }
80 s << "}\n"_L1;
81
82 // Have unique names to prevent overwriting of functions with the same name (eg. anonymous functions).
83 static int functionCount = 0;
84 static const auto dumpFolderPath = qEnvironmentVariable("QV4_DUMP_BASIC_BLOCKS");
85
86 QString expressionName = m_context->name == ""_L1
87 ? "anonymous"_L1
88 : QString(m_context->name).replace(" "_L1, "_"_L1);
89 QString fileName = "function"_L1 + QString::number(functionCount++) + "_"_L1 + expressionName + ".gv"_L1;
90 QFile dumpFile(dumpFolderPath + (dumpFolderPath.endsWith("/"_L1) ? ""_L1 : "/"_L1) + fileName);
91
92 if (dumpFolderPath == "-"_L1 || dumpFolderPath == "1"_L1 || dumpFolderPath == "true"_L1) {
93 qDebug().noquote() << output;
94 } else {
95 if (!dumpFile.open(QIODeviceBase::Truncate | QIODevice::WriteOnly)) {
96 qDebug() << "Error: Could not open file to dump the basic blocks into";
97 } else {
98 dumpFile.write(("//"_L1 + output).toLatin1().toStdString().c_str());
99 dumpFile.close();
100 }
101 }
102}
103
104QQmlJSCompilePass::BlocksAndAnnotations
105QQmlJSBasicBlocks::run(const Function *function, QQmlJSAotCompiler::Flags compileFlags,
106 bool &basicBlocksValidationFailed)
107{
108 basicBlocksValidationFailed = false;
109
110 m_function = function;
111
112 for (int i = 0, end = function->argumentTypes.size(); i != end; ++i) {
113 InstructionAnnotation annotation;
114 annotation.changedRegisterIndex = FirstArgument + i;
115 annotation.changedRegister = function->argumentTypes[i];
116 m_annotations[-annotation.changedRegisterIndex] = annotation;
117 }
118
119 for (int i = 0, end = function->registerTypes.size(); i != end; ++i) {
120 InstructionAnnotation annotation;
121 annotation.changedRegisterIndex = firstRegisterIndex() + i;
122 annotation.changedRegister = function->registerTypes[i];
123 m_annotations[-annotation.changedRegisterIndex] = annotation;
124 }
125
126 // Insert the function prolog block followed by the first "real" block.
127 m_basicBlocks.insert_or_assign(m_annotations.begin().key(), BasicBlock());
128 BasicBlock zeroBlock;
129 zeroBlock.jumpOrigins.append(m_basicBlocks.begin().key());
130 m_basicBlocks.insert(0, zeroBlock);
131
132 const QByteArray &byteCode = function->code;
133 decode(byteCode.constData(), static_cast<uint>(byteCode.size()));
134 if (m_hadBackJumps) {
135 // We may have missed some connections between basic blocks if there were back jumps.
136 // Fill them in via a second pass.
137
138 // We also need to re-calculate the jump targets then because the basic block boundaries
139 // may have shifted.
140 for (auto it = m_basicBlocks.begin(), end = m_basicBlocks.end(); it != end; ++it) {
141 it->second.jumpTarget = -1;
142 it->second.jumpIsUnconditional = false;
143 }
144
145 m_skipUntilNextLabel = false;
146
147 reset();
148 decode(byteCode.constData(), static_cast<uint>(byteCode.size()));
149 for (auto it = m_basicBlocks.begin(), end = m_basicBlocks.end(); it != end; ++it)
150 QQmlJSUtils::deduplicate(it->second.jumpOrigins);
151 }
152
153 if (compileFlags.testFlag(QQmlJSAotCompiler::ValidateBasicBlocks) || qv4ValidateBasicBlocks()) {
154 if (auto validationResult = basicBlocksValidation(); !validationResult.success) {
155 qDebug() << "Basic blocks validation failed: %1."_L1.arg(validationResult.errorMessage);
156 basicBlocksValidationFailed = true;
157 }
158 }
159
160 if (qv4DumpBasicBlocks()) {
161 dumpBasicBlocks();
162 dumpDOTGraph();
163 }
164
165 return { std::move(m_basicBlocks), std::move(m_annotations) };
166}
167
168QV4::Moth::ByteCodeHandler::Verdict QQmlJSBasicBlocks::startInstruction(QV4::Moth::Instr::Type type)
169{
170 auto it = m_basicBlocks.find(currentInstructionOffset());
171 if (it != m_basicBlocks.end()) {
172 m_skipUntilNextLabel = false;
173 } else if (m_skipUntilNextLabel && !instructionManipulatesContext(type)) {
174 return SkipInstruction;
175 }
176
177 return ProcessInstruction;
178}
179
180void QQmlJSBasicBlocks::endInstruction(QV4::Moth::Instr::Type)
181{
182 if (m_skipUntilNextLabel)
183 return;
184 auto it = m_basicBlocks.find(nextInstructionOffset());
185 if (it != m_basicBlocks.end())
186 it->second.jumpOrigins.append(currentInstructionOffset());
187}
188
189void QQmlJSBasicBlocks::generate_Jump(int offset)
190{
191 processJump(offset, Unconditional);
192}
193
194void QQmlJSBasicBlocks::generate_JumpTrue(int offset)
195{
196 processJump(offset, Conditional);
197}
198
199void QQmlJSBasicBlocks::generate_JumpFalse(int offset)
200{
201 processJump(offset, Conditional);
202}
203
204void QQmlJSBasicBlocks::generate_JumpNoException(int offset)
205{
206 processJump(offset, Conditional);
207}
208
209void QQmlJSBasicBlocks::generate_JumpNotUndefined(int offset)
210{
211 processJump(offset, Conditional);
212}
213
214void QQmlJSBasicBlocks::generate_IteratorNext(int value, int offset)
215{
216 Q_UNUSED(value);
217 processJump(offset, Conditional);
218}
219
220void QQmlJSBasicBlocks::generate_GetOptionalLookup(int index, int offset)
221{
222 Q_UNUSED(index);
223 processJump(offset, Conditional);
224}
225
226void QQmlJSBasicBlocks::generate_Ret()
227{
228 auto currentBlock = basicBlockForInstruction(m_basicBlocks, currentInstructionOffset());
229 currentBlock.value().isReturnBlock = true;
230 m_skipUntilNextLabel = true;
231}
232
233void QQmlJSBasicBlocks::generate_ThrowException()
234{
235 auto currentBlock = basicBlockForInstruction(m_basicBlocks, currentInstructionOffset());
236 currentBlock.value().isThrowBlock = true;
237 m_skipUntilNextLabel = true;
238}
239
240void QQmlJSBasicBlocks::generate_DefineArray(int argc, int argv)
241{
242 if (argc == 0)
243 return; // empty array/list, nothing to do
244
245 m_objectAndArrayDefinitions.append({
246 currentInstructionOffset(), ObjectOrArrayDefinition::ArrayClassId, argc, argv
247 });
248}
249
250void QQmlJSBasicBlocks::generate_DefineObjectLiteral(int internalClassId, int argc, int argv)
251{
252 if (argc == 0)
253 return;
254
255 m_objectAndArrayDefinitions.append({ currentInstructionOffset(), internalClassId, argc, argv });
256}
257
258void QQmlJSBasicBlocks::generate_Construct(int func, int argc, int argv)
259{
260 Q_UNUSED(func)
261 if (argc == 0)
262 return; // empty array/list, nothing to do
263
264 m_objectAndArrayDefinitions.append({
265 currentInstructionOffset(),
266 argc == 1
267 ? ObjectOrArrayDefinition::ArrayConstruct1ArgId
268 : ObjectOrArrayDefinition::ArrayClassId,
269 argc,
270 argv
271 });
272}
273
274void QQmlJSBasicBlocks::processJump(int offset, JumpMode mode)
275{
276 if (offset < 0)
277 m_hadBackJumps = true;
278 const int jumpTarget = absoluteOffset(offset);
279 Q_ASSERT(!m_basicBlocks.isEmpty());
280 auto currentBlock = basicBlockForInstruction(m_basicBlocks, currentInstructionOffset());
281 currentBlock->second.jumpTarget = jumpTarget;
282 currentBlock->second.jumpIsUnconditional = (mode == Unconditional);
283 m_basicBlocks[jumpTarget].jumpOrigins.append(currentInstructionOffset());
284 if (mode == Unconditional)
285 m_skipUntilNextLabel = true;
286 else
287 m_basicBlocks.insert(nextInstructionOffset(), BasicBlock());
288}
289
290QQmlJSCompilePass::BasicBlocks::iterator QQmlJSBasicBlocks::basicBlockForInstruction(
291 QFlatMap<int, BasicBlock> &container, int instructionOffset)
292{
293 auto block = container.lower_bound(instructionOffset);
294 if (block == container.end() || block->first != instructionOffset)
295 --block;
296 return block;
297}
298
299QQmlJSCompilePass::BasicBlocks::const_iterator QQmlJSBasicBlocks::basicBlockForInstruction(
300 const BasicBlocks &container, int instructionOffset)
301{
302 return basicBlockForInstruction(const_cast<BasicBlocks &>(container), instructionOffset);
303}
304
305QQmlJSBasicBlocks::BasicBlocksValidationResult QQmlJSBasicBlocks::basicBlocksValidation()
306{
307 if (m_basicBlocks.empty())
308 return {};
309
310 const QFlatMap<int, BasicBlock> blocks{ m_basicBlocks };
311 QList<QFlatMap<int, BasicBlock>::const_iterator> returnOrThrowBlocks;
312 for (auto it = blocks.cbegin(); it != blocks.cend(); ++it) {
313 if (it.value().isReturnBlock || it.value().isThrowBlock)
314 returnOrThrowBlocks.append(it);
315 }
316
317 // 1. Return blocks and throw blocks must not have a jump target
318 for (const auto &it : returnOrThrowBlocks) {
319 if (it.value().jumpTarget != -1)
320 return { false, "Return or throw block jumps to somewhere"_L1 };
321 }
322
323 // 2. The basic blocks graph must be connected.
324 QSet<int> visitedBlockOffsets;
325 QList<QFlatMap<int, BasicBlock>::const_iterator> toVisit;
326 toVisit.append(returnOrThrowBlocks);
327
328 while (!toVisit.empty()) {
329 const auto &[offset, block] = *toVisit.takeLast();
330 visitedBlockOffsets.insert(offset);
331 for (int originOffset : block.jumpOrigins) {
332 const auto originBlock = basicBlockForInstruction(blocks, originOffset);
333 if (visitedBlockOffsets.find(originBlock.key()) == visitedBlockOffsets.end()
334 && !toVisit.contains(originBlock))
335 toVisit.append(originBlock);
336 }
337 }
338
339 if (visitedBlockOffsets.size() != blocks.size())
340 return { false, "Basic blocks graph is not fully connected"_L1 };
341
342 // 3. A block's jump target must be the first offset of a block.
343 for (const auto &[blockOffset, block] : blocks) {
344 auto target = block.jumpTarget;
345 if (target != -1 && blocks.find(target) == blocks.end())
346 return { false, "Invalid jump; target is not the start of a block"_L1 };
347 }
348
349 return {};
350}
351
352QT_END_NAMESPACE