diff options
-rw-r--r-- | src/qmlcompiler/CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsbasicblocks.cpp | 397 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsbasicblocks_p.h | 102 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljscodegenerator.cpp | 196 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljscodegenerator_p.h | 1 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljscompiler.cpp | 4 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsshadowcheck.cpp | 8 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsstoragegeneralizer.cpp | 31 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljstyperesolver.cpp | 6 |
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> ®isters, 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> ®isters) { + 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()) { |