diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2022-02-16 16:34:56 +0100 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2022-03-09 12:40:06 +0100 |
commit | 9ee2df1e3b648d706805ef1e32c853c19d27e1a2 (patch) | |
tree | 67795bace9e2ad26d9c4c1f176423e8b313ba794 | |
parent | e86019ecbddd76d6551a18595ef9dcff8560f1d4 (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.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()) { |