7#include <private/qqmlglobal_p.h>
8#include <private/qqmljsutils_p.h>
9#include <private/qv4instr_moth_p.h>
13using namespace Qt::Literals::StringLiterals;
15DEFINE_BOOL_CONFIG_OPTION(qv4DumpBasicBlocks, QV4_DUMP_BASIC_BLOCKS)
16DEFINE_BOOL_CONFIG_OPTION(qv4ValidateBasicBlocks, QV4_VALIDATE_BASIC_BLOCKS)
18void QQmlJSBasicBlocks::dumpBasicBlocks()
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))
25 debug <<
" jumpOrigins[" << block.jumpOrigins.size() <<
"]: ";
26 for (
auto origin : std::as_const(block.jumpOrigins)) {
27 debug << origin <<
", ";
29 debug <<
"\n readRegisters[" << block.readRegisters.size() <<
"]: ";
30 for (
auto reg : std::as_const(block.readRegisters)) {
33 debug <<
"\n jumpTarget: " << block.jumpTarget;
34 debug <<
"\n jumpIsUnConditional: " << block.jumpIsUnconditional;
35 debug <<
"\n isReturnBlock: " << block.isReturnBlock;
36 debug <<
"\n isThrowBlock: " << block.isThrowBlock;
41void QQmlJSBasicBlocks::dumpDOTGraph()
44 QTextStream s{ &output };
45 s <<
"=== Basic Blocks Graph in DOT format for \"%1\" (spaces are encoded as"
46 "   to preserve formatting)\n"_L1.arg(m_context->name);
47 s <<
"digraph BasicBlocks {\n"_L1;
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);
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));
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,
" "_L1);
74 dump = dump.replace(
"\n"_L1,
"\\l"_L1);
75 dump = dump.replace(
"<"_L1,
"\<"_L1).replace(
">"_L1,
"\>"_L1);
76 s <<
" %1 [shape=record, fontname=\"Monospace\", label=\"{Block %1: | %2}\"]\n"_L1
77 .arg(QString::number(blockOffset))
83 static int functionCount = 0;
84 static const auto dumpFolderPath = qEnvironmentVariable(
"QV4_DUMP_BASIC_BLOCKS");
86 QString expressionName = m_context->name ==
""_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);
92 if (dumpFolderPath ==
"-"_L1 || dumpFolderPath ==
"1"_L1 || dumpFolderPath ==
"true"_L1) {
93 qDebug().noquote() << output;
95 if (!dumpFile.open(QIODeviceBase::Truncate | QIODevice::WriteOnly)) {
96 qDebug() <<
"Error: Could not open file to dump the basic blocks into";
98 dumpFile.write((
"//"_L1 + output).toLatin1().toStdString().c_str());
104QQmlJSCompilePass::BlocksAndAnnotations
105QQmlJSBasicBlocks::run(
const Function *function, QQmlJSAotCompiler::Flags compileFlags,
106 bool &basicBlocksValidationFailed)
108 basicBlocksValidationFailed =
false;
110 m_function = function;
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;
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;
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);
132 const QByteArray &byteCode = function->code;
133 decode(byteCode.constData(),
static_cast<uint>(byteCode.size()));
134 if (m_hadBackJumps) {
140 for (
auto it = m_basicBlocks.begin(), end = m_basicBlocks.end(); it != end; ++it) {
141 it->second.jumpTarget = -1;
142 it->second.jumpIsUnconditional =
false;
145 m_skipUntilNextLabel =
false;
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);
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;
160 if (qv4DumpBasicBlocks()) {
165 return { std::move(m_basicBlocks), std::move(m_annotations) };
168QV4::Moth::ByteCodeHandler::Verdict QQmlJSBasicBlocks::startInstruction(QV4::Moth::Instr::Type type)
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;
177 return ProcessInstruction;
180void QQmlJSBasicBlocks::endInstruction(QV4::Moth::Instr::Type)
182 if (m_skipUntilNextLabel)
184 auto it = m_basicBlocks.find(nextInstructionOffset());
185 if (it != m_basicBlocks.end())
186 it->second.jumpOrigins.append(currentInstructionOffset());
189void QQmlJSBasicBlocks::generate_Jump(
int offset)
191 processJump(offset, Unconditional);
194void QQmlJSBasicBlocks::generate_JumpTrue(
int offset)
196 processJump(offset, Conditional);
199void QQmlJSBasicBlocks::generate_JumpFalse(
int offset)
201 processJump(offset, Conditional);
204void QQmlJSBasicBlocks::generate_JumpNoException(
int offset)
206 processJump(offset, Conditional);
209void QQmlJSBasicBlocks::generate_JumpNotUndefined(
int offset)
211 processJump(offset, Conditional);
214void QQmlJSBasicBlocks::generate_IteratorNext(
int value,
int offset)
217 processJump(offset, Conditional);
220void QQmlJSBasicBlocks::generate_GetOptionalLookup(
int index,
int offset)
223 processJump(offset, Conditional);
226void QQmlJSBasicBlocks::generate_Ret()
228 auto currentBlock = basicBlockForInstruction(m_basicBlocks, currentInstructionOffset());
229 currentBlock.value().isReturnBlock =
true;
230 m_skipUntilNextLabel =
true;
233void QQmlJSBasicBlocks::generate_ThrowException()
235 auto currentBlock = basicBlockForInstruction(m_basicBlocks, currentInstructionOffset());
236 currentBlock.value().isThrowBlock =
true;
237 m_skipUntilNextLabel =
true;
240void QQmlJSBasicBlocks::generate_DefineArray(
int argc,
int argv)
245 m_objectAndArrayDefinitions.append({
246 currentInstructionOffset(), ObjectOrArrayDefinition::ArrayClassId, argc, argv
250void QQmlJSBasicBlocks::generate_DefineObjectLiteral(
int internalClassId,
int argc,
int argv)
255 m_objectAndArrayDefinitions.append({ currentInstructionOffset(), internalClassId, argc, argv });
258void QQmlJSBasicBlocks::generate_Construct(
int func,
int argc,
int argv)
264 m_objectAndArrayDefinitions.append({
265 currentInstructionOffset(),
267 ? ObjectOrArrayDefinition::ArrayConstruct1ArgId
268 : ObjectOrArrayDefinition::ArrayClassId,
274void QQmlJSBasicBlocks::processJump(
int offset, JumpMode mode)
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;
287 m_basicBlocks.insert(nextInstructionOffset(), BasicBlock());
290QQmlJSCompilePass::BasicBlocks::iterator QQmlJSBasicBlocks::basicBlockForInstruction(
291 QFlatMap<
int, BasicBlock> &container,
int instructionOffset)
293 auto block = container.lower_bound(instructionOffset);
294 if (block == container.end() || block->first != instructionOffset)
299QQmlJSCompilePass::BasicBlocks::const_iterator QQmlJSBasicBlocks::basicBlockForInstruction(
300 const BasicBlocks &container,
int instructionOffset)
302 return basicBlockForInstruction(
const_cast<BasicBlocks &>(container), instructionOffset);
305QQmlJSBasicBlocks::BasicBlocksValidationResult QQmlJSBasicBlocks::basicBlocksValidation()
307 if (m_basicBlocks.empty())
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);
318 for (
const auto &it : returnOrThrowBlocks) {
319 if (it.value().jumpTarget != -1)
320 return {
false,
"Return or throw block jumps to somewhere"_L1 };
324 QSet<
int> visitedBlockOffsets;
325 QList<QFlatMap<
int, BasicBlock>::const_iterator> toVisit;
326 toVisit.append(returnOrThrowBlocks);
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);
339 if (visitedBlockOffsets.size() != blocks.size())
340 return {
false,
"Basic blocks graph is not fully connected"_L1 };
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 };