aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmlcompiler/qqmljsbasicblocks.cpp
diff options
context:
space:
mode:
authorOlivier De Cannière <olivier.decanniere@qt.io>2023-07-11 15:01:10 +0200
committerOlivier De Cannière <olivier.decanniere@qt.io>2023-08-04 09:34:09 +0200
commit2cf9aeccddbd06a66df94bd27916714c4a5c7e24 (patch)
treef147cabe8e7f8d3dfcf1b02cf8d730e851fcc0a6 /src/qmlcompiler/qqmljsbasicblocks.cpp
parent0667e68e7715f60916ee91189925855e1dcf5f0e (diff)
Compiler: Separate function prolog block and add validation of blocks
The function prolog logic is now separated in its own basic block. The first "real" block with user code starts at offset 0. Having the function prolog as a hidden part of the first block caused some inconsistencies in block generation and would create empty blocks. This happened for example when a back edge of a loop would target offset 0 in code where a loop condition is the very first set of instructions that are run. This is because the target block offset didn't exist due to it being part of the hidden prolog block. Validation for the basic blocks was also added. This checks for three things at the moment: 1. That return and throw blocks don't have jump targets. 2. That the basic blocks graph is connected. 3. That jump targets are the first offset of a block. Test tst_QmlCppCodegen::basicBlocksWithBackJump_infinite() is expected to fail because it contains an infinite loop and the basic blocks that are generated for it are inconsistent due to dead-code elimination happening earlier in compilation. Debug outputs for dumping basic blocks were also adapted to reflect these changes. Change-Id: I513f73856412d488d443c2b47a052b0023d45496 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'src/qmlcompiler/qqmljsbasicblocks.cpp')
-rw-r--r--src/qmlcompiler/qqmljsbasicblocks.cpp143
1 files changed, 106 insertions, 37 deletions
diff --git a/src/qmlcompiler/qqmljsbasicblocks.cpp b/src/qmlcompiler/qqmljsbasicblocks.cpp
index 073e294f45..028f1df1c5 100644
--- a/src/qmlcompiler/qqmljsbasicblocks.cpp
+++ b/src/qmlcompiler/qqmljsbasicblocks.cpp
@@ -10,13 +10,15 @@ QT_BEGIN_NAMESPACE
using namespace Qt::Literals::StringLiterals;
DEFINE_BOOL_CONFIG_OPTION(qv4DumpBasicBlocks, QV4_DUMP_BASIC_BLOCKS)
+DEFINE_BOOL_CONFIG_OPTION(qv4ValidateBasicBlocks, QV4_VALIDATE_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 << "Block " << (blockOffset < 0 ? "Function prolog"_L1 : QString::number(blockOffset))
+ << ":\n";
debug << " jumpOrigins[" << block.jumpOrigins.size() << "]: ";
for (auto origin : block.jumpOrigins) {
debug << origin << ", ";
@@ -31,6 +33,8 @@ void QQmlJSBasicBlocks::dumpBasicBlocks()
}
debug << "\n jumpTarget: " << block.jumpTarget;
debug << "\n jumpIsUnConditional: " << block.jumpIsUnconditional;
+ debug << "\n isReturnBlock: " << block.isReturnBlock;
+ debug << "\n isThrowBlock: " << block.isThrowBlock;
}
qDebug() << "\n";
}
@@ -43,32 +47,29 @@ void QQmlJSBasicBlocks::dumpDOTGraph()
" &#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() };
+ QFlatMap<int, BasicBlock> blocks{ m_basicBlocks };
for (const auto &[blockOffset, block] : blocks) {
for (int originOffset : block.jumpOrigins) {
- int originBlockOffset;
- auto originBlockIt = blocks.find(originOffset);
- bool isBackEdge = false;
- if (originBlockIt != blocks.end()) {
- originBlockOffset = originOffset;
- isBackEdge = originOffset > blockOffset
- && originBlockIt->second.jumpIsUnconditional;
- } else {
- originBlockOffset = std::prev(blocks.lower_bound(originOffset))->first;
- }
- s << " %1 -> %2%3\n"_L1.arg(QString::number(originBlockOffset))
+ const auto originBlockIt = basicBlockForInstruction(blocks, originOffset);
+ const auto isBackEdge = originOffset > blockOffset && originBlockIt->second.jumpIsUnconditional;
+ s << " %1 -> %2%3\n"_L1.arg(QString::number(originBlockIt.key()))
.arg(QString::number(blockOffset))
.arg(isBackEdge ? " [color=blue]"_L1 : ""_L1);
}
}
for (const auto &[blockOffset, block] : blocks) {
- int beginOffset = std::max(0, blockOffset);
+ if (blockOffset < 0) {
+ s << " %1 [shape=record, fontname=\"Monospace\", label=\"Function Prolog\"]\n"_L1
+ .arg(QString::number(blockOffset));
+ continue;
+ }
+
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->formals->length(), blockOffset, 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
@@ -108,14 +109,15 @@ void deduplicate(Container &container)
container.erase(erase, container.end());
}
-QQmlJSCompilePass::InstructionAnnotations QQmlJSBasicBlocks::run(
- const Function *function,
- const InstructionAnnotations &annotations,
- QQmlJS::DiagnosticMessage *error)
+QQmlJSCompilePass::InstructionAnnotations
+QQmlJSBasicBlocks::run(const Function *function, const InstructionAnnotations &annotations,
+ QQmlJS::DiagnosticMessage *error, QQmlJSAotCompiler::Flags compileFlags,
+ bool &basicBlocksValidationFailed)
{
m_function = function;
m_annotations = annotations;
m_error = error;
+ basicBlocksValidationFailed = false;
for (int i = 0, end = function->argumentTypes.size(); i != end; ++i) {
InstructionAnnotation annotation;
@@ -131,7 +133,11 @@ QQmlJSCompilePass::InstructionAnnotations QQmlJSBasicBlocks::run(
m_annotations[-annotation.changedRegisterIndex] = annotation;
}
+ // Insert the function prolog block followed by the first "real" block.
m_basicBlocks.insert_or_assign(m_annotations.begin().key(), BasicBlock());
+ BasicBlock zeroBlock;
+ zeroBlock.jumpOrigins.append(m_basicBlocks.begin().key());
+ m_basicBlocks.insert(0, zeroBlock);
const QByteArray byteCode = function->code;
decode(byteCode.constData(), static_cast<uint>(byteCode.size()));
@@ -154,6 +160,13 @@ QQmlJSCompilePass::InstructionAnnotations QQmlJSBasicBlocks::run(
deduplicate(it->second.jumpOrigins);
}
+ if (compileFlags.testFlag(QQmlJSAotCompiler::ValidateBasicBlocks) || qv4ValidateBasicBlocks()) {
+ if (auto validationResult = basicBlocksValidation(); !validationResult.success) {
+ qDebug() << "Basic blocks validation failed: %1."_L1.arg(validationResult.errorMessage);
+ basicBlocksValidationFailed = true;
+ }
+ }
+
populateBasicBlocks();
populateReaderLocations();
adjustTypes();
@@ -214,11 +227,15 @@ void QQmlJSBasicBlocks::generate_JumpNotUndefined(int offset)
void QQmlJSBasicBlocks::generate_Ret()
{
+ auto currentBlock = basicBlockForInstruction(m_basicBlocks, currentInstructionOffset());
+ currentBlock.value().isReturnBlock = true;
m_skipUntilNextLabel = true;
}
void QQmlJSBasicBlocks::generate_ThrowException()
{
+ auto currentBlock = basicBlockForInstruction(m_basicBlocks, currentInstructionOffset());
+ currentBlock.value().isThrowBlock = true;
m_skipUntilNextLabel = true;
}
@@ -246,9 +263,7 @@ void QQmlJSBasicBlocks::processJump(int offset, JumpMode mode)
m_hadBackJumps = true;
const int jumpTarget = absoluteOffset(offset);
Q_ASSERT(!m_basicBlocks.isEmpty());
- auto currentBlock = m_basicBlocks.lower_bound(currentInstructionOffset());
- if (currentBlock == m_basicBlocks.end() || currentBlock->first != currentInstructionOffset())
- --currentBlock;
+ auto currentBlock = basicBlockForInstruction(m_basicBlocks, currentInstructionOffset());
currentBlock->second.jumpTarget = jumpTarget;
currentBlock->second.jumpIsUnconditional = (mode == Unconditional);
m_basicBlocks[jumpTarget].jumpOrigins.append(currentInstructionOffset());
@@ -375,10 +390,7 @@ void QQmlJSBasicBlocks::populateReaderLocations()
m_typeResolver->trackedContainedType(writeIt->second.changedRegister));
}
- auto blockIt = m_basicBlocks.lower_bound(writeIt.key());
- if (blockIt == m_basicBlocks.end() || blockIt->first != writeIt.key())
- --blockIt;
-
+ auto blockIt = basicBlockForInstruction(m_basicBlocks, writeIt.key());
QList<PendingBlock> blocks = { { {}, blockIt->first, true } };
QHash<int, PendingBlock> processedBlocks;
bool isFirstBlock = true;
@@ -494,21 +506,31 @@ void QQmlJSBasicBlocks::populateReaderLocations()
}
}
+QFlatMap<int, QQmlJSBasicBlocks::BasicBlock>::iterator
+QQmlJSBasicBlocks::basicBlockForInstruction(QFlatMap<int, BasicBlock> &container,
+ int instructionOffset)
+{
+ auto block = container.lower_bound(instructionOffset);
+ if (block == container.end() || block->first != instructionOffset)
+ --block;
+ return block;
+}
+
+QFlatMap<int, QQmlJSBasicBlocks::BasicBlock>::const_iterator
+QQmlJSBasicBlocks::basicBlockForInstruction(const QFlatMap<int, BasicBlock> &container,
+ int instructionOffset) const
+{
+ auto *nonConstThis = const_cast<QQmlJSBasicBlocks *>(this);
+ return nonConstThis->basicBlockForInstruction(
+ const_cast<QFlatMap<int, BasicBlock> &>(container), instructionOffset);
+}
+
bool QQmlJSBasicBlocks::canMove(int instructionOffset, const RegisterAccess &access) const
{
if (access.registerReadersAndConversions.size() != 1)
return false;
- const auto basicBlockForInstruction = [this](int instruction) {
- auto block = m_basicBlocks.lower_bound(instruction);
- if (block == m_basicBlocks.end() || block.key() == instruction)
- return block;
- Q_ASSERT(block.key() > instruction);
- if (block == m_basicBlocks.begin())
- return m_basicBlocks.end();
- return --block;
- };
- return basicBlockForInstruction(instructionOffset)
- == basicBlockForInstruction(access.registerReadersAndConversions.begin().key());
+ return basicBlockForInstruction(m_basicBlocks, instructionOffset)
+ == basicBlockForInstruction(m_basicBlocks, access.registerReadersAndConversions.begin().key());
}
static QString adjustErrorMessage(
@@ -743,4 +765,51 @@ void QQmlJSBasicBlocks::populateBasicBlocks()
}
}
+QQmlJSBasicBlocks::BasicBlocksValidationResult QQmlJSBasicBlocks::basicBlocksValidation()
+{
+ if (m_basicBlocks.empty())
+ return {};
+
+ const QFlatMap<int, BasicBlock> blocks{ m_basicBlocks };
+ QList<QFlatMap<int, BasicBlock>::const_iterator> returnOrThrowBlocks;
+ for (auto it = blocks.cbegin(); it != blocks.cend(); ++it) {
+ if (it.value().isReturnBlock || it.value().isThrowBlock)
+ returnOrThrowBlocks.append(it);
+ }
+
+ // 1. Return blocks and throw blocks must not have a jump target
+ for (const auto it : returnOrThrowBlocks) {
+ if (it.value().jumpTarget != -1)
+ return { false, "Return or throw block jumps to somewhere"_L1 };
+ }
+
+ // 2. The basic blocks graph must be connected.
+ QSet<int> visitedBlockOffsets;
+ QList<QFlatMap<int, BasicBlock>::const_iterator> toVisit;
+ toVisit.append(returnOrThrowBlocks);
+
+ while (!toVisit.empty()) {
+ const auto &[offset, block] = *toVisit.takeLast();
+ visitedBlockOffsets.insert(offset);
+ for (int originOffset : block.jumpOrigins) {
+ const auto originBlock = basicBlockForInstruction(blocks, originOffset);
+ if (visitedBlockOffsets.find(originBlock.key()) == visitedBlockOffsets.end()
+ && !toVisit.contains(originBlock))
+ toVisit.append(originBlock);
+ }
+ }
+
+ if (visitedBlockOffsets.size() != blocks.size())
+ return { false, "Basic blocks graph is not fully connected"_L1 };
+
+ // 3. A block's jump target must be the first offset of a block.
+ for (const auto &[blockOffset, block] : blocks) {
+ auto target = block.jumpTarget;
+ if (target != -1 && blocks.find(target) == blocks.end())
+ return { false, "Invalid jump; target is not the start of a block"_L1 };
+ }
+
+ return {};
+}
+
QT_END_NAMESPACE