aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2022-02-16 16:34:56 +0100
committerUlf Hermann <ulf.hermann@qt.io>2022-03-09 12:40:06 +0100
commit9ee2df1e3b648d706805ef1e32c853c19d27e1a2 (patch)
tree67795bace9e2ad26d9c4c1f176423e8b313ba794
parente86019ecbddd76d6551a18595ef9dcff8560f1d4 (diff)
QmlCompiler: Add basic block analysis pass
This basic block analysis pass uses the tracked types to determine dead stores. This is better than the one we already have in the code generator because it also tracks dead stores through renames and it allows removal of lookup results. Furthermore, it adjusts each store to write the type most favorable to its readers. This avoids unnecessary conversions at run time. It cannot replace the other dead store elimination, yet, because it doesn't see whether the results of rename operations are read after the rename. This capability will be added in a separate change that also tracks the register numbers. Once this is in place, we can delete the other basic blocks pass. Task-number: QTBUG-100157 Change-Id: I766c919412b6cf43befa7bdb1a6e5e11b41fe55b Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r--src/qmlcompiler/CMakeLists.txt3
-rw-r--r--src/qmlcompiler/qqmljsbasicblocks.cpp397
-rw-r--r--src/qmlcompiler/qqmljsbasicblocks_p.h102
-rw-r--r--src/qmlcompiler/qqmljscodegenerator.cpp196
-rw-r--r--src/qmlcompiler/qqmljscodegenerator_p.h1
-rw-r--r--src/qmlcompiler/qqmljscompiler.cpp4
-rw-r--r--src/qmlcompiler/qqmljsshadowcheck.cpp8
-rw-r--r--src/qmlcompiler/qqmljsstoragegeneralizer.cpp31
-rw-r--r--src/qmlcompiler/qqmljstyperesolver.cpp6
9 files changed, 650 insertions, 98 deletions
diff --git a/src/qmlcompiler/CMakeLists.txt b/src/qmlcompiler/CMakeLists.txt
index 2688f723ac..59d8bf8a98 100644
--- a/src/qmlcompiler/CMakeLists.txt
+++ b/src/qmlcompiler/CMakeLists.txt
@@ -8,7 +8,8 @@ qt_internal_add_module(QmlCompilerPrivate
SOURCES
qcoloroutput_p.h qcoloroutput.cpp
qdeferredpointer_p.h
- qqmljsannotation_p.h qqmljsannotation.cpp
+ qqmljsannotation.cpp qqmljsannotation_p.h
+ qqmljsbasicblocks.cpp qqmljsbasicblocks_p.h
qqmljscodegenerator.cpp qqmljscodegenerator_p.h
qqmljscompilepass_p.h
qqmljscompiler.cpp qqmljscompiler_p.h
diff --git a/src/qmlcompiler/qqmljsbasicblocks.cpp b/src/qmlcompiler/qqmljsbasicblocks.cpp
new file mode 100644
index 0000000000..ebd0458897
--- /dev/null
+++ b/src/qmlcompiler/qqmljsbasicblocks.cpp
@@ -0,0 +1,397 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qqmljsbasicblocks_p.h"
+
+QT_BEGIN_NAMESPACE
+
+template<typename Container>
+void deduplicate(Container &container)
+{
+ std::sort(container.begin(), container.end());
+ auto erase = std::unique(container.begin(), container.end());
+ container.erase(erase, container.end());
+}
+
+static bool instructionManipulatesContext(QV4::Moth::Instr::Type type)
+{
+ using Type = QV4::Moth::Instr::Type;
+ switch (type) {
+ case Type::PopContext:
+ case Type::PopScriptContext:
+ case Type::CreateCallContext:
+ case Type::CreateCallContext_Wide:
+ case Type::PushCatchContext:
+ case Type::PushCatchContext_Wide:
+ case Type::PushWithContext:
+ case Type::PushWithContext_Wide:
+ case Type::PushBlockContext:
+ case Type::PushBlockContext_Wide:
+ case Type::CloneBlockContext:
+ case Type::CloneBlockContext_Wide:
+ case Type::PushScriptContext:
+ case Type::PushScriptContext_Wide:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+QQmlJSCompilePass::InstructionAnnotations QQmlJSBasicBlocks::run(
+ const Function *function,
+ const InstructionAnnotations &annotations)
+{
+ m_annotations = annotations;
+ m_basicBlocks.insert_or_assign(0, BasicBlock());
+
+ const QByteArray byteCode = function->code;
+ decode(byteCode.constData(), static_cast<uint>(byteCode.length()));
+ if (m_hadBackJumps) {
+ // We may have missed some connections between basic blocks if there were back jumps.
+ // Fill them in via a second pass.
+ reset();
+ decode(byteCode.constData(), static_cast<uint>(byteCode.length()));
+ for (auto it = m_basicBlocks.begin(), end = m_basicBlocks.end(); it != end; ++it)
+ deduplicate(it->second.jumpOrigins);
+ }
+
+ populateBasicBlocks();
+ populateReaderLocations();
+ adjustTypes();
+ return std::move(m_annotations);
+}
+
+QV4::Moth::ByteCodeHandler::Verdict QQmlJSBasicBlocks::startInstruction(QV4::Moth::Instr::Type type)
+{
+ auto it = m_basicBlocks.find(currentInstructionOffset());
+ if (it != m_basicBlocks.end()) {
+ if (!m_skipUntilNextLabel && it != m_basicBlocks.begin())
+ it->second.jumpOrigins.append((--it)->first);
+ m_skipUntilNextLabel = false;
+ } else if (m_skipUntilNextLabel && !instructionManipulatesContext(type)) {
+ return SkipInstruction;
+ }
+
+ return ProcessInstruction;
+}
+
+void QQmlJSBasicBlocks::endInstruction(QV4::Moth::Instr::Type)
+{
+}
+
+void QQmlJSBasicBlocks::generate_Jump(int offset)
+{
+ processJump(offset, Unconditional);
+}
+
+void QQmlJSBasicBlocks::generate_JumpTrue(int offset)
+{
+ processJump(offset, Conditional);
+}
+
+void QQmlJSBasicBlocks::generate_JumpFalse(int offset)
+{
+ processJump(offset, Conditional);
+}
+
+void QQmlJSBasicBlocks::generate_JumpNoException(int offset)
+{
+ processJump(offset, Conditional);
+}
+
+void QQmlJSBasicBlocks::generate_JumpNotUndefined(int offset)
+{
+ processJump(offset, Conditional);
+}
+
+void QQmlJSBasicBlocks::generate_Ret()
+{
+ m_skipUntilNextLabel = true;
+}
+
+void QQmlJSBasicBlocks::generate_ThrowException()
+{
+ m_skipUntilNextLabel = true;
+}
+
+void QQmlJSBasicBlocks::processJump(int offset, JumpMode mode)
+{
+ if (offset < 0)
+ 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;
+ currentBlock->second.jumpTarget = jumpTarget;
+ currentBlock->second.jumpIsUnconditional = (mode == Unconditional);
+ m_basicBlocks[jumpTarget].jumpOrigins.append(currentInstructionOffset());
+ if (mode == Unconditional)
+ m_skipUntilNextLabel = true;
+ else
+ m_basicBlocks[nextInstructionOffset()].jumpOrigins.append(currentInstructionOffset());
+}
+
+template<typename ContainerA, typename ContainerB>
+static bool containsAny(const ContainerA &container, const ContainerB &elements)
+{
+ for (const auto &element : elements) {
+ if (container.contains(element))
+ return true;
+ }
+ return false;
+}
+
+template<class Key, class T, class Compare = std::less<Key>,
+ class KeyContainer = QList<Key>, class MappedContainer = QList<T>>
+class NewFlatMap
+{
+public:
+ using OriginalFlatMap = QFlatMap<Key, T, Compare, KeyContainer, MappedContainer>;
+
+ void appendOrdered(const typename OriginalFlatMap::iterator &i) {
+ keys.append(i.key());
+ values.append(i.value());
+ }
+
+ OriginalFlatMap take() {
+ OriginalFlatMap result(Qt::OrderedUniqueRange, std::move(keys), std::move(values));
+ keys.clear();
+ values.clear();
+ return result;
+ }
+
+private:
+ typename OriginalFlatMap::key_container_type keys;
+ typename OriginalFlatMap::mapped_container_type values;
+};
+
+void QQmlJSBasicBlocks::populateReaderLocations()
+{
+ using NewInstructionAnnotations = NewFlatMap<int, InstructionAnnotation>;
+
+ bool erasedReaders = false;
+ auto eraseDeadStore = [&](const InstructionAnnotations::iterator &it) {
+ auto reader = m_readerLocations.find(it.key());
+ if (reader != m_readerLocations.end() && reader->readers.isEmpty()) {
+
+ // void the output, rather than deleting it. We still need its variant.
+ m_typeResolver->adjustTrackedType(
+ it->second.changedRegister.storedType(),
+ m_typeResolver->voidType());
+ m_typeResolver->adjustTrackedType(
+ m_typeResolver->containedType(it->second.changedRegister),
+ m_typeResolver->voidType());
+ m_readerLocations.erase(reader);
+
+ // If it's not a label and has no side effects, we can drop the instruction.
+ if (!it->second.hasSideEffects && m_basicBlocks.find(it.key()) == m_basicBlocks.end()) {
+ if (!it->second.readRegisters.isEmpty()) {
+ it->second.readRegisters.clear();
+ erasedReaders = true;
+ }
+ return true;
+ }
+ }
+ return false;
+ };
+
+ NewInstructionAnnotations newAnnotations;
+ for (auto writeIt = m_annotations.begin(), writeEnd = m_annotations.end();
+ writeIt != writeEnd; ++writeIt) {
+ const int writtenRegister = writeIt->second.changedRegisterIndex;
+ if (writtenRegister == InvalidRegister) {
+ newAnnotations.appendOrdered(writeIt);
+ continue;
+ }
+
+ RegisterAccess &access = m_readerLocations[writeIt.key()];
+ if (writeIt->second.changedRegister.isConversion()) {
+ // If it's a conversion, we have to check for all readers of the conversion origins.
+ // This happens at jump targets where different types are merged. A StoreReg or similar
+ // instruction must be optimized out if none of the types it can hold is read anymore.
+ access.trackedTypes = writeIt->second.changedRegister.conversionOrigins();
+ } else {
+ access.trackedTypes.append(
+ m_typeResolver->trackedContainedType(writeIt->second.changedRegister));
+ }
+
+ auto blockIt = m_basicBlocks.lower_bound(writeIt.key());
+ if (blockIt == m_basicBlocks.end() || blockIt->first != writeIt.key())
+ --blockIt;
+
+ QList<int> blocks = { blockIt->first };
+ QList<int> processedBlocks;
+ bool isFirstBlock = true;
+
+ while (!blocks.isEmpty()) {
+ auto nextBlock = m_basicBlocks.find(blocks.takeLast());
+ auto currentBlock = nextBlock++;
+
+ if (isFirstBlock
+ || containsAny(currentBlock->second.readRegisters, access.trackedTypes)) {
+ const auto blockEnd = (nextBlock == m_basicBlocks.end())
+ ? m_annotations.end()
+ : m_annotations.find(nextBlock->first);
+
+ auto blockInstr = isFirstBlock
+ ? (writeIt + 1)
+ : m_annotations.find(currentBlock->first);
+ for (; blockInstr != blockEnd; ++blockInstr) {
+ for (auto readIt = blockInstr->second.readRegisters.constBegin(),
+ end = blockInstr->second.readRegisters.constEnd();
+ readIt != end; ++readIt) {
+ Q_ASSERT(readIt->second.isConversion());
+ if (containsAny(readIt->second.conversionOrigins(), access.trackedTypes))
+ access.readers[blockInstr.key()] = readIt->second.conversionResult();
+ }
+ }
+ }
+
+ if (!currentBlock->second.jumpIsUnconditional && nextBlock != m_basicBlocks.end()
+ && !processedBlocks.contains(nextBlock->first)) {
+ blocks.append(nextBlock->first);
+ }
+
+ const int jumpTarget = currentBlock->second.jumpTarget;
+ if (jumpTarget != -1 && !processedBlocks.contains(jumpTarget))
+ blocks.append(jumpTarget);
+
+ // We can re-enter the first block from the beginning.
+ // We will then find any reads before the write we're currently examining.
+ if (isFirstBlock)
+ isFirstBlock = false;
+ else
+ processedBlocks.append(currentBlock->first);
+ }
+
+ if (!eraseDeadStore(writeIt))
+ newAnnotations.appendOrdered(writeIt);
+ }
+ m_annotations = newAnnotations.take();
+
+ while (erasedReaders) {
+ erasedReaders = false;
+
+ for (auto it = m_annotations.begin(), end = m_annotations.end(); it != end; ++it) {
+ InstructionAnnotation &instruction = it->second;
+ if (instruction.changedRegisterIndex < InvalidRegister) {
+ newAnnotations.appendOrdered(it);
+ continue;
+ }
+
+ auto readers = m_readerLocations.find(it.key());
+ if (readers != m_readerLocations.end()) {
+ for (auto it = readers->readers.begin(); it != readers->readers.end();) {
+ if (m_annotations.contains(it.key()))
+ ++it;
+ else
+ it = readers->readers.erase(it);
+ }
+ }
+
+ if (!eraseDeadStore(it))
+ newAnnotations.appendOrdered(it);
+ }
+
+ m_annotations = newAnnotations.take();
+ }
+}
+
+void QQmlJSBasicBlocks::adjustTypes()
+{
+ using NewVirtualRegisters = NewFlatMap<int, QQmlJSRegisterContent>;
+
+ for (auto it = m_readerLocations.begin(), end = m_readerLocations.end(); it != end; ++it) {
+ // There is always one first occurrence of any tracked type. Conversions don't change
+ // the type.
+ if (it->trackedTypes.length() != 1)
+ continue;
+
+ m_typeResolver->adjustTrackedType(it->trackedTypes[0], it->readers.values());
+ }
+
+ const auto transformRegister = [&](QQmlJSRegisterContent &content) {
+ m_typeResolver->adjustTrackedType(
+ content.storedType(),
+ m_typeResolver->storedType(m_typeResolver->containedType(content)));
+ };
+
+ NewVirtualRegisters newRegisters;
+ for (auto i = m_annotations.begin(), iEnd = m_annotations.end(); i != iEnd; ++i) {
+ if (i->second.changedRegisterIndex != InvalidRegister)
+ transformRegister(i->second.changedRegister);
+ for (auto content : i->second.typeConversions) {
+ QQmlJSScope::ConstPtr conversionResult = content.second.conversionResult();
+ const auto conversionOrigins = content.second.conversionOrigins();
+ QQmlJSScope::ConstPtr newResult;
+ for (const auto &origin : conversionOrigins)
+ newResult = m_typeResolver->merge(newResult, origin);
+ m_typeResolver->adjustTrackedType(conversionResult, newResult);
+ transformRegister(content.second);
+ }
+ i->second.typeConversions = newRegisters.take();
+ }
+}
+
+void QQmlJSBasicBlocks::populateBasicBlocks()
+{
+ for (auto blockNext = m_basicBlocks.begin(), blockEnd = m_basicBlocks.end();
+ blockNext != blockEnd;) {
+
+ const auto blockIt = blockNext++;
+ BasicBlock &block = blockIt->second;
+ QList<QQmlJSScope::ConstPtr> writtenRegisters;
+
+ const auto instrEnd = (blockNext == blockEnd)
+ ? m_annotations.end()
+ : m_annotations.find(blockNext->first);
+ for (auto instrIt = m_annotations.find(blockIt->first); instrIt != instrEnd; ++instrIt) {
+ const InstructionAnnotation &instruction = instrIt->second;
+ for (auto it = instruction.readRegisters.begin(), end = instruction.readRegisters.end();
+ it != end; ++it) {
+ Q_ASSERT(it->second.isConversion());
+ for (const QQmlJSScope::ConstPtr &origin : it->second.conversionOrigins()) {
+ if (!writtenRegisters.contains(origin))
+ block.readRegisters.append(origin);
+ }
+ }
+
+ // If it's just a renaming, the type has existed in a different register before.
+ if (instruction.changedRegisterIndex != InvalidRegister && !instruction.isRename) {
+ writtenRegisters.append(m_typeResolver->trackedContainedType(
+ instruction.changedRegister));
+ }
+ }
+
+ deduplicate(block.readRegisters);
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljsbasicblocks_p.h b/src/qmlcompiler/qqmljsbasicblocks_p.h
new file mode 100644
index 0000000000..76f3216ce8
--- /dev/null
+++ b/src/qmlcompiler/qqmljsbasicblocks_p.h
@@ -0,0 +1,102 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQMLJSBASICBLOCKS_P_H
+#define QQMLJSBASICBLOCKS_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+
+#include <private/qqmljscompilepass_p.h>
+#include <private/qflatmap_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQmlJSBasicBlocks : public QQmlJSCompilePass
+{
+public:
+ struct BasicBlock {
+ QList<int> jumpOrigins;
+ QList<QQmlJSScope::ConstPtr> readRegisters;
+ int jumpTarget = -1;
+ bool jumpIsUnconditional = false;
+ };
+
+ QQmlJSBasicBlocks(const QV4::Compiler::JSUnitGenerator *unitGenerator,
+ const QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger)
+ : QQmlJSCompilePass(unitGenerator, typeResolver, logger)
+ {
+ }
+
+ ~QQmlJSBasicBlocks() = default;
+
+ InstructionAnnotations run(const Function *function, const InstructionAnnotations &annotations);
+
+private:
+ struct RegisterAccess
+ {
+ QList<QQmlJSScope::ConstPtr> trackedTypes;
+ QHash<int, QQmlJSScope::ConstPtr> readers;
+ };
+
+ QV4::Moth::ByteCodeHandler::Verdict startInstruction(QV4::Moth::Instr::Type type) override;
+ void endInstruction(QV4::Moth::Instr::Type type) override;
+
+ void generate_Jump(int offset) override;
+ void generate_JumpTrue(int offset) override;
+ void generate_JumpFalse(int offset) override;
+ void generate_JumpNoException(int offset) override;
+ void generate_JumpNotUndefined(int offset) override;
+
+ void generate_Ret() override;
+ void generate_ThrowException() override;
+
+ enum JumpMode { Unconditional, Conditional };
+ void processJump(int offset, JumpMode mode);
+ void populateBasicBlocks();
+ void populateReaderLocations();
+ void adjustTypes();
+
+ InstructionAnnotations m_annotations;
+ QFlatMap<int, BasicBlock> m_basicBlocks;
+ QHash<int, RegisterAccess> m_readerLocations;
+ bool m_skipUntilNextLabel = false;
+ bool m_hadBackJumps = false;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQMLJSBASICBLOCKS_P_H
diff --git a/src/qmlcompiler/qqmljscodegenerator.cpp b/src/qmlcompiler/qqmljscodegenerator.cpp
index 874f0918bd..f7fa9ba403 100644
--- a/src/qmlcompiler/qqmljscodegenerator.cpp
+++ b/src/qmlcompiler/qqmljscodegenerator.cpp
@@ -477,9 +477,14 @@ void QQmlJSCodeGenerator::generate_LoadConst(int index)
auto encodedConst = m_jsUnitGenerator->constant(index);
double value = QV4::StaticValue::fromReturnedValue(encodedConst).doubleValue();
m_body += m_state.accumulatorVariableOut;
- m_body += u" = "_qs;
- m_body += toNumericString(value); // ### handle other types
+
+ // ### handle other types?
+ m_body += u" = "_qs + conversion(
+ m_typeResolver->intType(), m_state.accumulatorOut().storedType(),
+ toNumericString(value));
+
m_body += u";\n"_qs;
+ generateOutputVariantConversion(m_typeResolver->intType());
}
void QQmlJSCodeGenerator::generate_LoadZero()
@@ -487,8 +492,10 @@ void QQmlJSCodeGenerator::generate_LoadZero()
INJECT_TRACE_INFO(generate_LoadZero);
m_body += m_state.accumulatorVariableOut;
- m_body += u" = 0"_qs;
+ m_body += u" = "_qs + conversion(
+ m_typeResolver->intType(), m_state.accumulatorOut().storedType(), u"0"_qs);
m_body += u";\n"_qs;
+ generateOutputVariantConversion(m_typeResolver->intType());
}
void QQmlJSCodeGenerator::generate_LoadTrue()
@@ -496,8 +503,10 @@ void QQmlJSCodeGenerator::generate_LoadTrue()
INJECT_TRACE_INFO(generate_LoadTrue);
m_body += m_state.accumulatorVariableOut;
- m_body += u" = true"_qs;
+ m_body += u" = "_qs + conversion(
+ m_typeResolver->boolType(), m_state.accumulatorOut().storedType(), u"true"_qs);
m_body += u";\n"_qs;
+ generateOutputVariantConversion(m_typeResolver->boolType());
}
void QQmlJSCodeGenerator::generate_LoadFalse()
@@ -505,36 +514,32 @@ void QQmlJSCodeGenerator::generate_LoadFalse()
INJECT_TRACE_INFO(generate_LoadFalse);
m_body += m_state.accumulatorVariableOut;
- m_body += u" = false"_qs;
+ m_body += u" = "_qs + conversion(
+ m_typeResolver->boolType(), m_state.accumulatorOut().storedType(), u"false"_qs);
m_body += u";\n"_qs;
+ generateOutputVariantConversion(m_typeResolver->boolType());
}
void QQmlJSCodeGenerator::generate_LoadNull()
{
INJECT_TRACE_INFO(generate_LoadNull);
- // No need to generate code. We don't store std::nullptr_t.
- if (m_typeResolver->equals(m_state.accumulatorOut().storedType(), m_typeResolver->nullType()))
- return;
-
m_body += m_state.accumulatorVariableOut + u" = "_qs;
m_body += conversion(m_typeResolver->nullType(), m_state.accumulatorOut().storedType(),
u"nullptr"_qs);
m_body += u";\n"_qs;
+ generateOutputVariantConversion(m_typeResolver->nullType());
}
void QQmlJSCodeGenerator::generate_LoadUndefined()
{
INJECT_TRACE_INFO(generate_LoadUndefined);
- // No need to generate code. We don't store void.
- if (m_typeResolver->equals(m_state.accumulatorOut().storedType(), m_typeResolver->voidType()))
- return;
-
m_body += m_state.accumulatorVariableOut + u" = "_qs;
m_body += conversion(m_typeResolver->voidType(), m_state.accumulatorOut().storedType(),
QString());
m_body += u";\n"_qs;
+ generateOutputVariantConversion(m_typeResolver->voidType());
}
void QQmlJSCodeGenerator::generate_LoadInt(int value)
@@ -546,6 +551,7 @@ void QQmlJSCodeGenerator::generate_LoadInt(int value)
m_body += conversion(m_typeResolver->intType(), m_state.accumulatorOut().storedType(),
QString::number(value));
m_body += u";\n"_qs;
+ generateOutputVariantConversion(m_typeResolver->intType());
}
void QQmlJSCodeGenerator::generate_MoveConst(int constIndex, int destTemp)
@@ -563,35 +569,34 @@ void QQmlJSCodeGenerator::generate_MoveConst(int constIndex, int destTemp)
m_jsUnitGenerator->constant(constIndex));
const auto changed = m_state.changedRegister().storedType();
+ QQmlJSScope::ConstPtr contained;
+ QString input;
m_body += var + u" = "_qs;
if (v4Value.isNull()) {
- m_body += conversion(m_typeResolver->nullType(), changed, QString());
+ contained = m_typeResolver->nullType();
} else if (v4Value.isUndefined()) {
- m_body += conversion(m_typeResolver->voidType(), changed, QString());
+ contained = m_typeResolver->voidType();
} else if (v4Value.isBoolean()) {
- m_body += conversion(m_typeResolver->boolType(), changed,
- v4Value.booleanValue() ? u"true"_qs : u"false"_qs);
+ contained = m_typeResolver->boolType();
+ input = v4Value.booleanValue() ? u"true"_qs : u"false"_qs;
} else if (v4Value.isInteger()) {
- m_body += conversion(m_typeResolver->intType(), changed,
- QString::number(v4Value.int_32()));
+ contained = m_typeResolver->intType();
+ input = QString::number(v4Value.int_32());
} else if (v4Value.isDouble()) {
- m_body += conversion(m_typeResolver->realType(), changed,
- toNumericString(v4Value.doubleValue()));
+ contained = m_typeResolver->realType();
+ input = toNumericString(v4Value.doubleValue());
} else {
reject(u"unknown const type"_qs);
}
- m_body += u";\n"_qs;
+ m_body += conversion(contained, changed, input) + u";\n"_qs;
+ generateOutputVariantConversion(contained);
}
void QQmlJSCodeGenerator::generate_LoadReg(int reg)
{
INJECT_TRACE_INFO(generate_LoadReg);
- // We won't emit any code for loading undefined.
- // See also generate_LoadUndefined()
- if (m_typeResolver->registerContains(m_state.accumulatorOut(), m_typeResolver->voidType()))
- return;
m_body += m_state.accumulatorVariableOut;
m_body += u" = "_qs;
m_body += conversion(registerType(reg), m_state.accumulatorOut(), use(registerVariable(reg)));
@@ -672,6 +677,8 @@ void QQmlJSCodeGenerator::generate_LoadRuntimeString(int stringId)
m_body += conversion(m_typeResolver->stringType(), m_state.accumulatorOut().storedType(),
QQmlJSUtils::toLiteral(m_jsUnitGenerator->stringForIndex(stringId)));
m_body += u";\n"_qs;
+
+ generateOutputVariantConversion(m_typeResolver->stringType());
}
void QQmlJSCodeGenerator::generate_MoveRegExp(int regExpId, int destReg)
@@ -697,6 +704,8 @@ void QQmlJSCodeGenerator::generate_LoadGlobalLookup(int index)
{
INJECT_TRACE_INFO(generate_LoadGlobalLookup);
+ AccumulatorConverter registers(this);
+
const QString lookup = u"aotContext->loadGlobalLookup("_qs + QString::number(index)
+ u", &"_qs + m_state.accumulatorVariableOut + u", "_qs
+ metaTypeFromType(m_state.accumulatorOut().storedType()) + u')';
@@ -709,9 +718,6 @@ void QQmlJSCodeGenerator::generate_LoadQmlContextPropertyLookup(int index)
{
INJECT_TRACE_INFO(generate_LoadQmlContextPropertyLookup);
- if (m_state.accumulatorVariableOut.isEmpty())
- return;
-
AccumulatorConverter registers(this);
const int nameIndex = m_jsUnitGenerator->lookupNameIndex(index);
@@ -827,6 +833,8 @@ void QQmlJSCodeGenerator::generate_LoadElement(int base)
return;
}
+ AccumulatorConverter registers(this);
+
const QString baseName = use(registerVariable(base));
const QString indexName = use(m_state.accumulatorVariableIn);
@@ -971,6 +979,26 @@ void QQmlJSCodeGenerator::generateTypeLookup(int index)
}
}
+void QQmlJSCodeGenerator::generateOutputVariantConversion(const QQmlJSScope::ConstPtr &containedType)
+{
+ if (changedRegisterVariable().isEmpty())
+ return;
+
+ const QQmlJSRegisterContent changed = m_state.changedRegister();
+ if (!m_typeResolver->equals(changed.storedType(), m_typeResolver->varType()))
+ return;
+
+ const QQmlJSScope::ConstPtr target = m_typeResolver->containedType(changed);
+ if (m_typeResolver->equals(target, containedType)
+ || m_typeResolver->equals(target, m_typeResolver->varType())) {
+ return;
+ }
+
+ // If we could store the type directly, we would not wrap it in a QVariant.
+ // Therefore, our best bet here is metaTypeFromName().
+ m_body += changedRegisterVariable() + u".convert("_qs + metaTypeFromName(target) + u");\n"_qs;
+}
+
void QQmlJSCodeGenerator::generate_GetLookup(int index)
{
INJECT_TRACE_INFO(generate_GetLookup);
@@ -1471,6 +1499,8 @@ void QQmlJSCodeGenerator::generate_CallPropertyLookup(int index, int base, int a
if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::JavaScriptReturnValue)
reject(u"call to untyped JavaScript function"_qs);
+ AccumulatorConverter registers(this);
+
const QQmlJSRegisterContent baseType = registerType(base);
if (baseType.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) {
const QString name = m_jsUnitGenerator->stringForIndex(
@@ -1541,6 +1571,8 @@ void QQmlJSCodeGenerator::generate_CallQmlContextPropertyLookup(int index, int a
if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::JavaScriptReturnValue)
reject(u"call to untyped JavaScript function"_qs);
+ AccumulatorConverter registers(this);
+
m_body.setHasSideEffects(true);
const QString indexString = QString::number(index);
@@ -1761,16 +1793,11 @@ void QQmlJSCodeGenerator::generate_DefineArray(int argc, int args)
if (argc > 0)
reject(u"DefineArray"_qs);
- // No need to generate code. We don't store empty lists.
- if (m_typeResolver->equals(m_state.accumulatorOut().storedType(),
- m_typeResolver->emptyListType())) {
- return;
- }
-
m_body += m_state.accumulatorVariableOut + u" = "_qs;
m_body += conversion(m_typeResolver->emptyListType(), m_state.accumulatorOut().storedType(),
QString());
m_body += u";\n"_qs;
+ generateOutputVariantConversion(m_typeResolver->emptyListType());
}
void QQmlJSCodeGenerator::generate_DefineObjectLiteral(int internalClassId, int argc, int args)
@@ -1893,25 +1920,30 @@ void QQmlJSCodeGenerator::generate_CmpEqNull()
{
INJECT_TRACE_INFO(generate_CmlEqNull);
- m_body += m_state.accumulatorVariableOut;
- m_body += u" = QJSPrimitiveValue(QJSPrimitiveNull()).equals("_qs;
- m_body += conversion(m_state.accumulatorIn().storedType(), m_typeResolver->jsPrimitiveType(),
- use(m_state.accumulatorVariableIn));
- m_body += u')';
+ m_body += m_state.accumulatorVariableOut + u" = "_qs;
+ m_body += conversion(
+ m_typeResolver->boolType(), m_state.accumulatorOut().storedType(),
+ u"QJSPrimitiveValue(QJSPrimitiveNull()).equals("_qs
+ + conversion(
+ m_state.accumulatorIn().storedType(), m_typeResolver->jsPrimitiveType(),
+ use(m_state.accumulatorVariableIn)) + u')');
m_body += u";\n"_qs;
-
+ generateOutputVariantConversion(m_typeResolver->boolType());
}
void QQmlJSCodeGenerator::generate_CmpNeNull()
{
INJECT_TRACE_INFO(generate_CmlNeNull);
- m_body += m_state.accumulatorVariableOut;
- m_body += u" = !QJSPrimitiveValue(QJSPrimitiveNull()).equals("_qs;
- m_body += conversion(m_state.accumulatorIn().storedType(), m_typeResolver->jsPrimitiveType(),
- use(m_state.accumulatorVariableIn));
- m_body += u')';
+ m_body += m_state.accumulatorVariableOut + u" = "_qs;
+ m_body += conversion(
+ m_typeResolver->boolType(), m_state.accumulatorOut().storedType(),
+ u"!QJSPrimitiveValue(QJSPrimitiveNull()).equals("_qs
+ + conversion(
+ m_state.accumulatorIn().storedType(), m_typeResolver->jsPrimitiveType(),
+ use(m_state.accumulatorVariableIn)) + u')');
m_body += u";\n"_qs;
+ generateOutputVariantConversion(m_typeResolver->boolType());
}
QString QQmlJSCodeGenerator::eqIntExpression(int lhsConst)
@@ -1990,16 +2022,20 @@ void QQmlJSCodeGenerator::generate_CmpEqInt(int lhsConst)
{
INJECT_TRACE_INFO(generate_CmpEqInt);
- m_body += m_state.accumulatorVariableOut + u" = "_qs + eqIntExpression(lhsConst)
- + u";\n"_qs;
+ m_body += m_state.accumulatorVariableOut + u" = "_qs;
+ m_body += conversion(m_typeResolver->boolType(), m_state.accumulatorOut().storedType(),
+ eqIntExpression(lhsConst)) + u";\n"_qs;
+ generateOutputVariantConversion(m_typeResolver->boolType());
}
void QQmlJSCodeGenerator::generate_CmpNeInt(int lhsConst)
{
INJECT_TRACE_INFO(generate_CmpNeInt);
- m_body += m_state.accumulatorVariableOut + u" = !("_qs + eqIntExpression(lhsConst)
- + u");\n"_qs;
+ m_body += m_state.accumulatorVariableOut + u" = "_qs;
+ m_body += conversion(m_typeResolver->boolType(), m_state.accumulatorOut().storedType(),
+ u"!("_qs + eqIntExpression(lhsConst) + u')') + u";\n"_qs;
+ generateOutputVariantConversion(m_typeResolver->boolType());
}
void QQmlJSCodeGenerator::generate_CmpEq(int lhs)
@@ -2234,6 +2270,8 @@ void QQmlJSCodeGenerator::generate_Mod(int lhs)
m_body += conversion(m_typeResolver->jsPrimitiveType(), m_state.accumulatorOut().storedType(),
u'(' + lhsVar + u" % "_qs + rhsVar + u')');
m_body += u";\n"_qs;
+
+ generateOutputVariantConversion(m_typeResolver->jsPrimitiveType());
}
void QQmlJSCodeGenerator::generate_Sub(int lhs)
@@ -2341,7 +2379,7 @@ QV4::Moth::ByteCodeHandler::Verdict QQmlJSCodeGenerator::startInstruction(
// If the instruction has no side effects and doesn't write any register, it's dead.
// We might still need the label, though, and the source code comment.
- if (!m_state.hasSideEffects() && m_state.changedRegisterIndex() == InvalidRegister)
+ if (!m_state.hasSideEffects() && changedRegisterVariable().isEmpty())
return SkipInstruction;
return ProcessInstruction;
@@ -2392,40 +2430,42 @@ void QQmlJSCodeGenerator::generateEqualityOperation(int lhs, const QString &func
const auto primitive = m_typeResolver->jsPrimitiveType();
if (m_typeResolver->equals(lhsType, rhsType) && !m_typeResolver->equals(lhsType, primitive)) {
- m_body += use(registerVariable(lhs));
- m_body += (invert ? u" != "_qs : u" == "_qs);
- m_body += use(m_state.accumulatorVariableIn);
+ m_body += conversion(m_typeResolver->boolType(), m_state.accumulatorOut().storedType(),
+ use(registerVariable(lhs)) + (invert ? u" != "_qs : u" == "_qs)
+ + use(m_state.accumulatorVariableIn));
} else {
- if (invert)
- m_body += u'!';
- m_body += conversion(registerType(lhs).storedType(), primitive, use(registerVariable(lhs)));
- m_body += u'.';
- m_body += function;
- m_body += u'(';
- m_body += conversion(m_state.accumulatorIn().storedType(), primitive,
- use(m_state.accumulatorVariableIn));
- m_body += u')';
+ m_body += conversion(
+ m_typeResolver->boolType(), m_state.accumulatorOut().storedType(),
+ (invert ? u"!"_qs : QString())
+ + conversion(registerType(lhs).storedType(), primitive,
+ use(registerVariable(lhs)))
+ + u'.' + function + u'(' + conversion(
+ m_state.accumulatorIn().storedType(), primitive,
+ use(m_state.accumulatorVariableIn))
+ + u')');
}
m_body += u";\n"_qs;
+ generateOutputVariantConversion(m_typeResolver->boolType());
}
void QQmlJSCodeGenerator::generateCompareOperation(int lhs, const QString &cppOperator)
{
- m_body += m_state.accumulatorVariableOut;
+ m_body += m_state.accumulatorVariableOut + u" = "_qs;
const auto lhsType = registerType(lhs);
const QQmlJSScope::ConstPtr compareType =
m_typeResolver->isNumeric(lhsType) && m_typeResolver->isNumeric(m_state.accumulatorIn())
? m_typeResolver->merge(lhsType, m_state.accumulatorIn()).storedType()
: m_typeResolver->jsPrimitiveType();
- m_body += u" = "_qs;
- m_body += conversion(registerType(lhs).storedType(), compareType, use(registerVariable(lhs)));
- m_body += u' ';
- m_body += cppOperator;
- m_body += u' ';
- m_body += conversion(m_state.accumulatorIn().storedType(), compareType,
- use(m_state.accumulatorVariableIn));
+
+ m_body += conversion(
+ m_typeResolver->boolType(), m_state.accumulatorOut().storedType(),
+ conversion(registerType(lhs).storedType(), compareType, use(registerVariable(lhs)))
+ + u' ' + cppOperator + u' '
+ + conversion(m_state.accumulatorIn().storedType(), compareType,
+ use(m_state.accumulatorVariableIn)));
m_body += u";\n"_qs;
+ generateOutputVariantConversion(m_typeResolver->boolType());
}
void QQmlJSCodeGenerator::generateArithmeticOperation(int lhs, const QString &cppOperator)
@@ -2437,13 +2477,14 @@ void QQmlJSCodeGenerator::generateArithmeticOperation(int lhs, const QString &cp
Q_ASSERT(!lhsVar.isEmpty());
Q_ASSERT(!rhsVar.isEmpty());
+ const QQmlJSRegisterContent originalOut = m_typeResolver->original(m_state.accumulatorOut());
m_body += m_state.accumulatorVariableOut;
m_body += u" = "_qs;
m_body += conversion(
- m_typeResolver->original(m_state.accumulatorOut()),
- m_state.accumulatorOut(),
+ originalOut, m_state.accumulatorOut(),
u'(' + lhsVar + u' ' + cppOperator + u' ' + rhsVar + u')');
m_body += u";\n"_qs;
+ generateOutputVariantConversion(m_typeResolver->containedType(originalOut));
}
void QQmlJSCodeGenerator::generateUnaryOperation(const QString &cppOperator)
@@ -2467,6 +2508,8 @@ void QQmlJSCodeGenerator::generateUnaryOperation(const QString &cppOperator)
m_body += m_state.accumulatorVariableOut + u" = "_qs + conversion(
m_typeResolver->original(m_state.accumulatorOut()),
m_state.accumulatorOut(), cppOperator + var) + u";\n"_qs;
+
+ generateOutputVariantConversion(m_typeResolver->containedType(original));
}
void QQmlJSCodeGenerator::generateInPlaceOperation(const QString &cppOperator)
@@ -2492,6 +2535,7 @@ void QQmlJSCodeGenerator::generateInPlaceOperation(const QString &cppOperator)
m_typeResolver->original(m_state.accumulatorOut()),
m_state.accumulatorOut(), cppOperator + u"converted"_qs) + u";\n"_qs;
m_body += u"}\n"_qs;
+ generateOutputVariantConversion(m_typeResolver->containedType(original));
}
void QQmlJSCodeGenerator::generateLookup(const QString &lookup, const QString &initialization,
@@ -2814,6 +2858,9 @@ QQmlJSCodeGenerator::AccumulatorConverter::AccumulatorConverter(QQmlJSCodeGenera
, accumulatorVariableOut(generator->m_state.accumulatorVariableOut)
, generator(generator)
{
+ if (accumulatorVariableOut.isEmpty())
+ return;
+
const QQmlJSTypeResolver *resolver = generator->m_typeResolver;
const QQmlJSScope::ConstPtr origContained = resolver->originalContainedType(accumulatorOut);
const QQmlJSScope::ConstPtr origStored = resolver->originalType(accumulatorOut.storedType());
@@ -2846,9 +2893,12 @@ QQmlJSCodeGenerator::AccumulatorConverter::~AccumulatorConverter()
generator->m_body += accumulatorVariableOut + u" = "_qs + generator->conversion(
generator->m_state.accumulatorOut(), accumulatorOut,
generator->m_state.accumulatorVariableOut) + u";\n"_qs;
+ const auto contained = generator->m_typeResolver->containedType(
+ generator->m_state.accumulatorOut());
generator->m_body += u"}\n"_qs;
generator->m_state.setRegister(Accumulator, accumulatorOut);
generator->m_state.accumulatorVariableOut = accumulatorVariableOut;
+ generator->generateOutputVariantConversion(contained);
} else if (accumulatorVariableIn != generator->m_state.accumulatorVariableIn) {
generator->m_body += u"}\n"_qs;
generator->m_state.accumulatorVariableIn = accumulatorVariableIn;
diff --git a/src/qmlcompiler/qqmljscodegenerator_p.h b/src/qmlcompiler/qqmljscodegenerator_p.h
index 687e22ed19..5128b585d7 100644
--- a/src/qmlcompiler/qqmljscodegenerator_p.h
+++ b/src/qmlcompiler/qqmljscodegenerator_p.h
@@ -347,6 +347,7 @@ private:
void generateInPlaceOperation(const QString &cppOperator);
void generateMoveOutVar(const QString &outVar);
void generateTypeLookup(int index);
+ void generateOutputVariantConversion(const QQmlJSScope::ConstPtr &containedType);
QString eqIntExpression(int lhsConst);
QString argumentsList(int argc, int argv, QString *outVar);
diff --git a/src/qmlcompiler/qqmljscompiler.cpp b/src/qmlcompiler/qqmljscompiler.cpp
index 30b74fe6ee..c765a1c94e 100644
--- a/src/qmlcompiler/qqmljscompiler.cpp
+++ b/src/qmlcompiler/qqmljscompiler.cpp
@@ -29,6 +29,7 @@
#include "qqmljscompiler_p.h"
#include <private/qqmlirbuilder_p.h>
+#include <private/qqmljsbasicblocks_p.h>
#include <private/qqmljscodegenerator_p.h>
#include <private/qqmljsfunctioninitializer_p.h>
#include <private/qqmljsimportvisitor_p.h>
@@ -768,6 +769,9 @@ QQmlJSAotFunction QQmlJSAotCompiler::doCompile(
if (error->isValid())
return compileError();
+ QQmlJSBasicBlocks basicBlocks(m_unitGenerator, &m_typeResolver, m_logger);
+ typePropagationResult = basicBlocks.run(function, typePropagationResult);
+
QQmlJSShadowCheck shadowCheck(m_unitGenerator, &m_typeResolver, m_logger);
shadowCheck.run(&typePropagationResult, function, error);
if (error->isValid())
diff --git a/src/qmlcompiler/qqmljsshadowcheck.cpp b/src/qmlcompiler/qqmljsshadowcheck.cpp
index 79f197750c..283553f758 100644
--- a/src/qmlcompiler/qqmljsshadowcheck.cpp
+++ b/src/qmlcompiler/qqmljsshadowcheck.cpp
@@ -64,12 +64,16 @@ void QQmlJSShadowCheck::run(
void QQmlJSShadowCheck::generate_LoadProperty(int nameIndex)
{
- checkShadowing(m_state.accumulatorIn(), m_jsUnitGenerator->stringForIndex(nameIndex));
+ auto accumulatorIn = m_state.registers.find(Accumulator);
+ if (accumulatorIn != m_state.registers.end())
+ checkShadowing(accumulatorIn.value(), m_jsUnitGenerator->stringForIndex(nameIndex));
}
void QQmlJSShadowCheck::generate_GetLookup(int index)
{
- checkShadowing(m_state.accumulatorIn(), m_jsUnitGenerator->lookupName(index));
+ auto accumulatorIn = m_state.registers.find(Accumulator);
+ if (accumulatorIn != m_state.registers.end())
+ checkShadowing(accumulatorIn.value(), m_jsUnitGenerator->lookupName(index));
}
void QQmlJSShadowCheck::generate_StoreProperty(int nameIndex, int base)
diff --git a/src/qmlcompiler/qqmljsstoragegeneralizer.cpp b/src/qmlcompiler/qqmljsstoragegeneralizer.cpp
index 889150f2dd..ce07a333d5 100644
--- a/src/qmlcompiler/qqmljsstoragegeneralizer.cpp
+++ b/src/qmlcompiler/qqmljsstoragegeneralizer.cpp
@@ -61,38 +61,25 @@ QQmlJSCompilePass::InstructionAnnotations QQmlJSStorageGeneralizer::run(
}
}
- const auto transformRegister = [&](QQmlJSRegisterContent &content, int offset) {
- if (QQmlJSScope::ConstPtr specific = content.storedType()) {
- if (QQmlJSScope::ConstPtr generic = m_typeResolver->genericType(specific)) {
- content = content.storedIn(generic);
- } else {
- setError(QStringLiteral("Cannot store the register type %1.")
- .arg(specific->internalName()), offset);
- return false;
- }
- }
- return true;
+ const auto transformRegister = [&](QQmlJSRegisterContent &content) {
+ if (const QQmlJSScope::ConstPtr &specific = content.storedType())
+ m_typeResolver->generalizeType(specific);
};
const auto transformRegisters
- = [&](QFlatMap<int, QQmlJSRegisterContent> &registers, int offset) {
- for (auto j = registers.begin(), jEnd = registers.end(); j != jEnd; ++j) {
- if (!transformRegister(j.value(), offset))
- return false;
- }
- return true;
+ = [&](QFlatMap<int, QQmlJSRegisterContent> &registers) {
+ for (auto j = registers.begin(), jEnd = registers.end(); j != jEnd; ++j)
+ transformRegister(j.value());
};
for (QQmlJSRegisterContent &argument : function->argumentTypes) {
Q_ASSERT(argument.isValid());
- transformRegister(argument, 0);
+ transformRegister(argument);
}
for (auto i = annotations.begin(), iEnd = annotations.end(); i != iEnd; ++i) {
- if (!transformRegister(i->second.changedRegister, i.key()))
- return InstructionAnnotations();
- if (!transformRegisters(i->second.typeConversions, i.key()))
- return InstructionAnnotations();
+ transformRegister(i->second.changedRegister);
+ transformRegisters(i->second.typeConversions);
}
return annotations;
diff --git a/src/qmlcompiler/qqmljstyperesolver.cpp b/src/qmlcompiler/qqmljstyperesolver.cpp
index 277f45512f..1b0520e70d 100644
--- a/src/qmlcompiler/qqmljstyperesolver.cpp
+++ b/src/qmlcompiler/qqmljstyperesolver.cpp
@@ -587,6 +587,12 @@ QQmlJSRegisterContent QQmlJSTypeResolver::merge(const QQmlJSRegisterContent &a,
QQmlJSScope::ConstPtr QQmlJSTypeResolver::merge(const QQmlJSScope::ConstPtr &a,
const QQmlJSScope::ConstPtr &b) const
{
+ if (a.isNull())
+ return b;
+
+ if (b.isNull())
+ return a;
+
const auto commonBaseType = [this](
const QQmlJSScope::ConstPtr &a, const QQmlJSScope::ConstPtr &b) {
for (QQmlJSScope::ConstPtr aBase = a; aBase; aBase = aBase->baseType()) {