diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2021-11-16 16:49:49 +0100 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2021-11-26 11:58:07 +0100 |
commit | 6d5c2cecdd9694f6ecf9679ea2d46c04b51ca771 (patch) | |
tree | f2694763464d4ace9cc0bb568a1b0116ff2ff443 /src/qmlcompiler/qqmljscodegenerator.cpp | |
parent | 66a7ab6ac1ebde82f6f81d43a5ecb1e693a41fab (diff) |
QmlCompiler: Add a code generator
We're going to use this in qmlcachegen.
Task-number: QTBUG-98305
Change-Id: Ie2c945751041bf99af9fbc22dfa5665cd0201446
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'src/qmlcompiler/qqmljscodegenerator.cpp')
-rw-r--r-- | src/qmlcompiler/qqmljscodegenerator.cpp | 2577 |
1 files changed, 2577 insertions, 0 deletions
diff --git a/src/qmlcompiler/qqmljscodegenerator.cpp b/src/qmlcompiler/qqmljscodegenerator.cpp new file mode 100644 index 0000000000..785f959a31 --- /dev/null +++ b/src/qmlcompiler/qqmljscodegenerator.cpp @@ -0,0 +1,2577 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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 "qqmljscodegenerator_p.h" + +#include <private/qqmljstypepropagator_p.h> + +#include <private/qqmlirbuilder_p.h> +#include <private/qqmljsscope_p.h> +#include <private/qv4compilerscanfunctions_p.h> + +#include <QtCore/qdir.h> +#include <QtCore/qfileinfo.h> + +QT_BEGIN_NAMESPACE + +/*! + * \internal + * \class QQmlJSCodeGenerator + * + * This is a final compile pass that generates C++ code from a function and the + * annotations produced by previous passes. Such annotations are produced by + * QQmlJSTypePropagator, and possibly amended by other passes. + */ + +#define BYTECODE_UNIMPLEMENTED() Q_ASSERT_X(false, Q_FUNC_INFO, "not implemented"); + +#define INJECT_TRACE_INFO(function) \ + static const bool injectTraceInfo = true; \ + if (injectTraceInfo) { \ + m_body += u"// "_qs + QStringLiteral(#function) + u'\n'; \ + } + +static QString escapeString(QString s) +{ + return s.replace(u'\\', u"\\\\"_qs) + .replace(u'"', u"\\\""_qs) + .replace(u'\n', u"\\n"_qs); +} + +QString QQmlJSCodeGenerator::castTargetName(const QQmlJSScope::ConstPtr &type) const +{ + return (type->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) + ? type->internalName() + u" *"_qs + : type->internalName(); +} + +QQmlJSCodeGenerator::QQmlJSCodeGenerator(const QV4::Compiler::Context *compilerContext, + const QV4::Compiler::JSUnitGenerator *unitGenerator, + const QQmlJSTypeResolver *typeResolver, + QQmlJSLogger *logger, const QStringList &sourceCodeLines) + : QQmlJSCompilePass(unitGenerator, typeResolver, logger) + , m_sourceCodeLines(sourceCodeLines) + , m_context(compilerContext) +{} + +QString QQmlJSCodeGenerator::metaTypeFromType(const QQmlJSScope::ConstPtr &type) const +{ + return u"QMetaType::fromType<"_qs + type->internalName() + + (type->accessSemantics() == QQmlJSScope::AccessSemantics::Reference + ? u" *"_qs + : QString()) + + u">()"_qs; +} + +QString QQmlJSCodeGenerator::metaTypeFromName(const QQmlJSScope::ConstPtr &type) const +{ + return u"QMetaType::fromName(\""_qs + type->internalName() + + ((type->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) + ? u" *"_qs + : QString()) + + u"\")"_qs; +} + +QString QQmlJSCodeGenerator::metaObject(const QQmlJSScope::ConstPtr &objectType) +{ + if (!objectType->isComposite()) + return metaTypeFromName(objectType) + u".metaObject()"_qs; + + reject(u"retrieving the metaObject of a composite type without using an instance."_qs); + return QString(); +} + +QQmlJSAotFunction QQmlJSCodeGenerator::run( + const Function *function, const InstructionAnnotations *annotations, + QQmlJS::DiagnosticMessage *error) +{ + m_annotations = annotations; + m_function = function; + m_error = error; + + QSet<QString> registerNames; + for (const InstructionAnnotation &annotation : *m_annotations) { + for (auto regIt = annotation.registers.constBegin(), + regEnd = annotation.registers.constEnd(); + regIt != regEnd; + ++regIt) { + int registerIndex = regIt.key(); + + const QQmlJSScope::ConstPtr seenType = regIt.value().storedType(); + // Don't generate any variables for registers that are initialized with undefined. + if (seenType.isNull() || seenType == m_typeResolver->voidType()) + continue; + + auto &typesForRegisters = m_registerVariables[registerIndex]; + if (!typesForRegisters.contains(seenType)) { + QString variableName = u"r%1"_qs.arg(registerIndex); + if (registerNames.contains(variableName)) + variableName += u'_' + QString::number(typesForRegisters.count()); + registerNames.insert(variableName); + typesForRegisters[seenType] = variableName; + } + } + } + + // ensure we have m_labels for loops + for (const auto loopLabel : m_context->labelInfo) + m_labels.insert(loopLabel, u"label_%1"_qs.arg(m_labels.count())); + + const QByteArray byteCode = function->code; + decode(byteCode.constData(), static_cast<uint>(byteCode.length())); + eliminateDeadStores(); + + QQmlJSAotFunction result; + result.includes.swap(m_includes); + + for (const auto ®isterTypes : qAsConst(m_registerVariables)) { + for (auto registerTypeIt = registerTypes.constBegin(), end = registerTypes.constEnd(); + registerTypeIt != end; ++registerTypeIt) { + + const QQmlJSScope::ConstPtr storedType = registerTypeIt.key(); + result.code += storedType->internalName(); + + if (storedType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) + result.code += u" *"_qs; + else + result.code += u' '; + + result.code += registerTypeIt.value(); + result.code += u";\n"_qs; + } + } + + for (const Section §ion : m_sections) + result.code += section.code(); + + for (const QQmlJSScope::ConstPtr &argType : qAsConst(function->argumentTypes)) { + if (argType) { + result.argumentTypes.append( + argType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference + ? (argType->internalName() + u'*') + : argType->internalName()); + } else { + result.argumentTypes.append(u"void"_qs); + } + } + + if (function->returnType) { + result.returnType = function->returnType->internalName(); + if (function->returnType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) + result.returnType += u'*'; + } else { + result.returnType = u"void"_qs; + } + + return result; +} + +QList<QQmlJSCodeGenerator::BasicBlock> QQmlJSCodeGenerator::findBasicBlocks( + const QList<QQmlJSCodeGenerator::Section> §ions) +{ + using JumpMode = QQmlJSCodeGenerator::JumpMode; + + QList<BasicBlock> basicBlocks; + BasicBlock currentBlock; + currentBlock.beginSection = 0; + for (int i = 0, end = sections.length(); i != end; ++i) { + const QQmlJSCodeGenerator::Section §ion = sections[i]; + const QString label = section.label(); + if (!label.isEmpty() || currentBlock.jumpMode != JumpMode::None) { + currentBlock.endSection = i; + basicBlocks.append(currentBlock); + currentBlock.beginSection = i; + currentBlock.label = label; + } + currentBlock.jumpMode = section.jumpMode(); + currentBlock.jumpTarget = section.jumpTarget(); + } + currentBlock.endSection = sections.length(); + basicBlocks.append(currentBlock); + + BasicBlock *prevBlock = nullptr; + for (int i = 0, end = basicBlocks.length(); i != end; ++i) { + BasicBlock *block = &basicBlocks[i]; + if (prevBlock && prevBlock->jumpMode != JumpMode::Unconditional) + block->previousBlocks.append(i); + if (!block->label.isEmpty()) { + for (int j = 0, end = basicBlocks.length(); j != end; ++j) { + BasicBlock *jumpFrom = &basicBlocks[j]; + if (jumpFrom->jumpMode == JumpMode::None || jumpFrom->jumpTarget != block->label) + continue; + jumpFrom->jumpTargetBlock = i; + block->previousBlocks.append(j); + } + } + } + + return basicBlocks; +} + +QQmlJSCodeGenerator::RequiredRegisters QQmlJSCodeGenerator::dropPreserveCycles( + const QList<QQmlJSCodeGenerator::BasicBlock> &basicBlocks, + const QQmlJSCodeGenerator::RequiredRegisters &requiredRegisters) +{ + RequiredRegisters result(requiredRegisters.length()); + for (int i = 0, blocksEnd = basicBlocks.length(); i != blocksEnd; ++i) { + const BasicBlock &block = basicBlocks[i]; + const QHash<QString, ReadMode> ®isters = requiredRegisters[i]; + QHash<QString, ReadMode> &resultRegisters = result[i]; + for (auto r = registers.begin(), registersEnd = registers.end(); r != registersEnd; ++r) { + const QString variable = r.key(); + switch (*r) { + case ReadMode::NoRead: + resultRegisters[variable] = ReadMode::NoRead; + continue; + case ReadMode::Preserve: + // Convert Preserve into NoRead and update below + if (!resultRegisters.contains(variable)) + resultRegisters[variable] = ReadMode::NoRead; + continue; + case ReadMode::SelfRead: + resultRegisters[variable] = ReadMode::SelfRead; + break; + } + + QList<int> blocksToCheck; + blocksToCheck.append(block.previousBlocks); + + while (!blocksToCheck.isEmpty()) { + const int currentIndex = blocksToCheck.takeFirst(); + if (requiredRegisters[currentIndex][variable] == ReadMode::Preserve) { + result[currentIndex][variable] = ReadMode::Preserve; + blocksToCheck.append(basicBlocks[currentIndex].previousBlocks); + } + } + } + } + return result; +} + +void QQmlJSCodeGenerator::eliminateDeadStores() +{ + const QList<BasicBlock> basicBlocks = findBasicBlocks(m_sections); + + QList<int> toErase; + bool foundUnknownBlock = false; + RequiredRegisters requiredRegisters(basicBlocks.length()); + do { + toErase.clear(); + foundUnknownBlock = false; + for (auto ®isterTypes : m_registerVariables) { + for (auto registerTypeIt = registerTypes.constBegin(); + registerTypeIt != registerTypes.constEnd();) { + + const QString variable = registerTypeIt.value(); + + // Don't declare any variables for registers that contain undefined. + if (variable.isEmpty()) { + registerTypeIt = registerTypes.erase(registerTypeIt); + continue; + } + + bool usedOnce = false; + ReadMode inUse = ReadMode::NoRead; + int basicBlockIndex = basicBlocks.length() - 1; + for (int i = m_sections.length() - 1; i >= 0; --i) { + Section §ion = m_sections[i]; + const BasicBlock *block = &basicBlocks[basicBlockIndex]; + if (block->beginSection > i) { + requiredRegisters[basicBlockIndex][variable] = inUse; + block = &basicBlocks[--basicBlockIndex]; + if (block->jumpMode == JumpMode::Unconditional) + inUse = ReadMode::NoRead; + else if (inUse == ReadMode::SelfRead) + inUse = ReadMode::Preserve; + + if (block->jumpMode != JumpMode::None) { + QHash<QString, ReadMode> *blockRegisters + = &requiredRegisters[block->jumpTargetBlock]; + auto req = blockRegisters->find(variable); + if (req == blockRegisters->end()) { + foundUnknownBlock = true; + inUse = ReadMode::Preserve; + } else if (*req != ReadMode::NoRead) { + inUse = ReadMode::Preserve; + } + } + } + + if (section.writeRegister() == variable) { + if (inUse == ReadMode::NoRead && !section.hasSideEffects()) { + toErase.append(i); + } else { + usedOnce = true; + + // We can read and write the same register in one instruction. + // See Increment and Decrement + inUse = section.readsRegister(variable) + ? ReadMode::SelfRead + : ReadMode::NoRead; + } + } else if (section.readsRegister(variable)) { + inUse = ReadMode::SelfRead; + usedOnce = true; + } + } + + if (!usedOnce) { + registerTypeIt = registerTypes.erase(registerTypeIt); + continue; + } + + ++registerTypeIt; + } + } + + // Sort the offsets in reverse order so that we can pop from the back + std::sort(toErase.begin(), toErase.end()); + int eraseIndex = toErase.length() - 1; + for (int i = m_sections.length() - 1; i >= 0 && eraseIndex >= 0; --i) { + if (i != toErase[eraseIndex]) + continue; + Section §ion = m_sections[i]; + QString code = section.code(); + section = Section(); + + // Comment out the section. + code.replace(u'\n', u"\n// "_qs); + if (!code.startsWith(u"// "_qs) && !code.startsWith(u'\n')) + code.prepend(u"// "_qs); + + // Make sure we end with a newline so that we don't comment out the next section. + if (code.endsWith(u"\n// ")) + code.chop(3); + else if (!code.endsWith(u'\n')) + code.append(u'\n'); + + section += code; + --eraseIndex; + } + + if (foundUnknownBlock) + requiredRegisters = dropPreserveCycles(basicBlocks, requiredRegisters); + + } while (!toErase.isEmpty() || foundUnknownBlock); +} + +QString QQmlJSCodeGenerator::errorReturnValue() const +{ + if (m_function->returnType) { + return conversion(m_typeResolver->jsPrimitiveType(), m_function->returnType, + u"QJSPrimitiveValue(QJSPrimitiveUndefined())"_qs); + } + return QString(); +} + +void QQmlJSCodeGenerator::generate_Ret() +{ + INJECT_TRACE_INFO(generate_Ret); + + m_body.setWriteRegister(QString()); + m_body.setHasSideEffects(true); + + if (m_function->returnType) { + const QString signalUndefined = u"aotContext->setReturnValueUndefined();\n"_qs; + if (!m_state.accumulatorVariableIn.isEmpty()) { + const QString in = use(m_state.accumulatorVariableIn); + if (m_state.accumulatorIn.storedType() == m_typeResolver->varType()) { + m_body += u"if (!"_qs + in + u".isValid())\n"_qs; + m_body += u" "_qs + signalUndefined; + } else if (m_state.accumulatorIn.storedType() == m_typeResolver->jsPrimitiveType()) { + m_body += u"if ("_qs + in + + u".type() == QJSPrimitiveValue::Undefined)\n"_qs; + m_body += u" "_qs + signalUndefined; + } else if (m_state.accumulatorIn.storedType() == m_typeResolver->jsValueType()) { + m_body += u"if ("_qs + in + u".isUndefined())\n"_qs; + m_body += u" "_qs + signalUndefined; + } + m_body += u"return "_qs + + conversion(m_state.accumulatorIn.storedType(), m_function->returnType, in); + } else if (m_function->returnType != m_typeResolver->voidType() + && m_function->returnType->internalName() != u"void"_qs) { + if (m_function->returnType->internalName().trimmed().endsWith(u'*') + || m_function->returnType->internalName().trimmed().endsWith(u'&')) { + setError(u"Not all paths return a value"_qs); + return; + } + + m_body += signalUndefined; + m_body += u"return "_qs + m_function->returnType->internalName() + u"()"_qs; + } + } else { + m_body += u"return"_qs; + } + + m_body += u";\n"_qs; + m_skipUntilNextLabel = true; +} + +void QQmlJSCodeGenerator::generate_Debug() +{ + BYTECODE_UNIMPLEMENTED(); +} + +static QString toNumericString(double value) +{ + if (value >= std::numeric_limits<int>::min() && value <= std::numeric_limits<int>::max()) { + const int i = value; + if (i == value) + return QString::number(i); + } + + return QString::number(value, 'f', std::numeric_limits<double>::max_digits10); +} + +void QQmlJSCodeGenerator::generate_LoadConst(int index) +{ + INJECT_TRACE_INFO(generate_LoadConst); + + 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 + m_body += u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_LoadZero() +{ + INJECT_TRACE_INFO(generate_LoadZero); + + m_body += m_state.accumulatorVariableOut; + m_body += u" = 0"_qs; + m_body += u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_LoadTrue() +{ + INJECT_TRACE_INFO(generate_LoadTrue); + + m_body += m_state.accumulatorVariableOut; + m_body += u" = true"_qs; + m_body += u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_LoadFalse() +{ + INJECT_TRACE_INFO(generate_LoadFalse); + + m_body += m_state.accumulatorVariableOut; + m_body += u" = false"_qs; + m_body += u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_LoadNull() +{ + INJECT_TRACE_INFO(generate_LoadNull); + + m_body += m_state.accumulatorVariableOut; + m_body += u" = QJSPrimitiveNull()"_qs; + m_body += u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_LoadUndefined() +{ + INJECT_TRACE_INFO(generate_LoadUndefined); + m_body += m_state.accumulatorVariableOut + u" = "_qs + conversion( + m_typeResolver->jsPrimitiveType(), m_state.accumulatorOut.storedType(), + u"QJSPrimitiveValue()"_qs) + u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_LoadInt(int value) +{ + INJECT_TRACE_INFO(generate_LoadInt); + + Q_ASSERT(m_typeResolver->registerContains(m_state.accumulatorOut, m_typeResolver->intType())); + m_body += m_state.accumulatorVariableOut; + m_body += u" = "_qs; + m_body += QString::number(value); + m_body += u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_MoveConst(int constIndex, int destTemp) +{ + INJECT_TRACE_INFO(generate_MoveConst); + + auto var = registerVariable(destTemp); + if (var.isEmpty()) + return; // Do not load 'undefined' + + m_body.setWriteRegister(var); + const auto v4Value = QV4::StaticValue::fromReturnedValue( + m_jsUnitGenerator->constant(constIndex)); + + if (v4Value.isNull()) { + const auto type = registerType(destTemp).storedType(); + m_body += var + u" = "_qs; + if (type == m_typeResolver->jsPrimitiveType()) { + m_body += u"QJSPrimitiveNull()"_qs; + } else if (type == m_typeResolver->jsValueType()) { + m_body += u"QJSValue(QJSValue::NullValue)"_qs; + } else if (type == m_typeResolver->varType()) { + m_body += u"QVariant::fromValue<std::nullptr_t>(nullptr)"_qs; + } else if (type->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) { + m_body += u"nullptr"_qs; + } else { + setError(u"Cannot load null into %1"_qs.arg(m_state.accumulatorOut.descriptiveName())); + } + + m_body += u";\n"_qs; + return; + } + + double value = 0.0f; + if (v4Value.isInteger() || v4Value.isBoolean()) + value = v4Value.int_32(); + else if (v4Value.isDouble()) + value = v4Value.doubleValue(); + + m_body += var; + m_body += u" = "_qs; + m_body += toNumericString(value); + m_body += u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_LoadReg(int reg) +{ + INJECT_TRACE_INFO(generate_LoadReg); + + // We can't emit any code yet 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 += use(registerVariable(reg)); + m_body += u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_StoreReg(int reg) +{ + INJECT_TRACE_INFO(generate_StoreReg); + + Q_ASSERT(m_state.accumulatorIn.isValid()); + const QString var = registerVariable(reg); + m_body.setWriteRegister(var); + if (var.isEmpty()) + return; // don't store "undefined" + m_body += var; + m_body += u" = "_qs; + m_body += conversion(m_state.accumulatorIn, registerType(reg), + use(m_state.accumulatorVariableIn)); + m_body += u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_MoveReg(int srcReg, int destReg) +{ + INJECT_TRACE_INFO(generate_MoveReg); + + const QString destRegName = registerVariable(destReg); + m_body.setWriteRegister(destRegName); + m_body += destRegName; + m_body += u" = "_qs; + m_body += use(registerVariable(srcReg)); + m_body += u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_LoadImport(int index) +{ + Q_UNUSED(index) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_LoadLocal(int index) +{ + Q_UNUSED(index); + reject(u"LoadLocal"_qs); +} + +void QQmlJSCodeGenerator::generate_StoreLocal(int index) +{ + Q_UNUSED(index) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_LoadScopedLocal(int scope, int index) +{ + Q_UNUSED(scope) + Q_UNUSED(index) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_StoreScopedLocal(int scope, int index) +{ + Q_UNUSED(scope) + Q_UNUSED(index) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_LoadRuntimeString(int stringId) +{ + INJECT_TRACE_INFO(generate_LoadRuntimeString); + + const QString string = escapeString(m_jsUnitGenerator->stringForIndex(stringId)); + + m_body += m_state.accumulatorVariableOut; + m_body += u" = QStringLiteral(\""_qs; + m_body += string; + m_body += u"\")"_qs; + m_body += u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_MoveRegExp(int regExpId, int destReg) +{ + Q_UNUSED(regExpId) + Q_UNUSED(destReg) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_LoadClosure(int value) +{ + Q_UNUSED(value) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_LoadName(int nameIndex) +{ + Q_UNUSED(nameIndex) + reject(u"LoadName"_qs); +} + +void QQmlJSCodeGenerator::generate_LoadGlobalLookup(int index) +{ + INJECT_TRACE_INFO(generate_LoadGlobalLookup); + + const QString lookup = u"aotContext->loadGlobalLookup("_qs + QString::number(index) + + u", &"_qs + m_state.accumulatorVariableOut + u", "_qs + + metaTypeFromType(m_state.accumulatorOut.storedType()) + u')'; + const QString initialization = u"aotContext->initLoadGlobalLookup("_qs + + QString::number(index) + u')'; + generateLookup(lookup, initialization); +} + +void QQmlJSCodeGenerator::generate_LoadQmlContextPropertyLookup(int index) +{ + INJECT_TRACE_INFO(generate_LoadQmlContextPropertyLookup); + + if (m_state.accumulatorVariableOut.isEmpty()) + return; + + const int nameIndex = m_jsUnitGenerator->lookupNameIndex(index); + const QString name = m_jsUnitGenerator->stringForIndex(nameIndex); + if (m_state.accumulatorOut.variant() == QQmlJSRegisterContent::JavaScriptGlobal) { + m_body += m_state.accumulatorVariableOut + u" = "_qs + + conversion( + m_typeResolver->jsValueType(), m_state.accumulatorOut.storedType(), + u"aotContext->javaScriptGlobalProperty("_qs + QString::number(nameIndex) + u")") + + u";\n"_qs; + return; + } + + const QString indexString = QString::number(index); + const QQmlJSScope::ConstPtr scopeForId = m_typeResolver->scopeForId(name); + if (!scopeForId.isNull()) { + const QString lookup = u"aotContext->loadContextIdLookup("_qs + + indexString + u", &"_qs + m_state.accumulatorVariableOut + u')'; + const QString initialization = u"aotContext->initLoadContextIdLookup("_qs + + indexString + u')'; + generateLookup(lookup, initialization); + return; + } + + const bool isProperty = m_state.accumulatorOut.isProperty(); + QQmlJSScope::ConstPtr qmlScope = QQmlJSScope::findCurrentQMLScope(m_function->qmlScope); + + const QQmlJSScope::ConstPtr scope = m_state.accumulatorOut.scopeType(); + const QQmlJSScope::ConstPtr stored = m_state.accumulatorOut.storedType(); + if (isProperty) { + m_body += u"{\n"_qs; + + const auto lookupType = contentType(m_state.accumulatorOut, m_state.accumulatorVariableOut); + + const QString lookup = u"aotContext->loadScopeObjectPropertyLookup("_qs + + indexString + u", "_qs + + contentPointer(m_state.accumulatorOut, m_state.accumulatorVariableOut) + u')'; + const QString initialization + = u"aotContext->initLoadScopeObjectPropertyLookup("_qs + + indexString + u", "_qs + + lookupType + u')'; + const QString preparation = getLookupPreparation( + m_state.accumulatorOut, m_state.accumulatorVariableOut, index); + + generateLookup(lookup, initialization, preparation); + m_body += u"}\n"_qs; + } else if (m_state.accumulatorOut.isType() || m_state.accumulatorOut.isImportNamespace()) { + generateTypeLookup(index); + } else { + Q_UNREACHABLE(); + } +} + +void QQmlJSCodeGenerator::generate_StoreNameSloppy(int nameIndex) +{ + INJECT_TRACE_INFO(generate_StoreNameSloppy); + + m_body.setHasSideEffects(true); + m_body.setWriteRegister(QString()); + + const QString name = m_jsUnitGenerator->stringForIndex(nameIndex); + const QQmlJSRegisterContent specific = m_typeResolver->scopedType(m_function->qmlScope, name); + const QQmlJSRegisterContent type = specific.storedIn( + m_typeResolver->genericType(specific.storedType())); + Q_ASSERT(type.isProperty()); + + switch (type.variant()) { + case QQmlJSRegisterContent::ScopeProperty: + case QQmlJSRegisterContent::ExtensionScopeProperty: { + if (type.property().type() != m_typeResolver->containedType(m_state.accumulatorIn)) { + m_body += u"{\n"_qs; + m_body += u"auto converted = "_qs + + conversion(m_state.accumulatorIn, type, use(m_state.accumulatorVariableIn)) + + u";\n"_qs; + m_body += u"aotContext->storeNameSloppy("_qs + QString::number(nameIndex) + + u", "_qs + contentPointer(type, u"converted"_qs) + + u", "_qs + contentType(type, u"converted"_qs) + u')'; + m_body += u";\n"_qs; + m_body += u"}\n"_qs; + } else { + m_body += u"aotContext->storeNameSloppy("_qs + QString::number(nameIndex) + + u", "_qs + + contentPointer(m_state.accumulatorIn, use(m_state.accumulatorVariableIn)) + + u", "_qs + + contentType(m_state.accumulatorIn, use(m_state.accumulatorVariableIn)) + u')'; + m_body += u";\n"_qs; + } + break; + } + case QQmlJSRegisterContent::ScopeMethod: + case QQmlJSRegisterContent::ExtensionScopeMethod: + reject(u"assignment to scope method"_qs); + break; + default: + Q_UNREACHABLE(); + } +} + +void QQmlJSCodeGenerator::generate_StoreNameStrict(int name) +{ + Q_UNUSED(name) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_LoadElement(int base) +{ + INJECT_TRACE_INFO(generate_LoadElement); + + const QQmlJSRegisterContent baseType = registerType(base); + + if (!m_typeResolver->isNumeric(m_state.accumulatorIn) || !baseType.isList()) { + reject(u"LoadElement with non-list base type or non-numeric arguments"_qs); + return; + } + + if (baseType.storedType() != m_typeResolver->listPropertyType()) { + reject(u"indirect LoadElement"_qs); + return; + } + + const QString baseName = use(registerVariable(base)); + const QString indexName = use(m_state.accumulatorVariableIn); + + // Our QQmlListProperty only keeps plain QObject*. + const auto valueType = m_typeResolver->valueType(baseType); + const auto elementType = m_typeResolver->globalType( + m_typeResolver->genericType(m_typeResolver->containedType(valueType))); + + m_body += u"if ("_qs + indexName + u" >= 0 && "_qs + indexName + + u" < "_qs + baseName + u".count(&"_qs + baseName + + u"))\n"_qs; + m_body += u" "_qs + m_state.accumulatorVariableOut + u" = "_qs + + conversion(elementType, m_state.accumulatorOut, + baseName + u".at(&"_qs + baseName + u", "_qs + + indexName + u')') + u";\n"_qs; + m_body += u"else\n"_qs; + m_body += u" "_qs + m_state.accumulatorVariableOut + u" = {}"_qs; + m_body += u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_StoreElement(int base, int index) +{ + INJECT_TRACE_INFO(generate_StoreElement); + + m_body.setHasSideEffects(true); + m_body.setWriteRegister(QString()); + + const QQmlJSRegisterContent baseType = registerType(base); + + if (!m_typeResolver->isNumeric(registerType(index)) || !baseType.isList()) { + reject(u"StoreElement with non-list base type or non-numeric arguments"_qs); + return; + } + + if (baseType.storedType() != m_typeResolver->listPropertyType()) { + reject(u"indirect StoreElement"_qs); + return; + } + + const QString baseName = use(registerVariable(base)); + const QString indexName = use(registerVariable(index)); + + const auto valueType = m_typeResolver->valueType(baseType); + const auto elementType = m_typeResolver->globalType(m_typeResolver->genericType( + m_typeResolver->containedType(valueType))); + + m_body += u"if ("_qs + indexName + u" >= 0 && "_qs + indexName + + u" < "_qs + baseName + u".count(&"_qs + baseName + + u"))\n"_qs; + m_body += u" "_qs + baseName + u".replace(&"_qs + baseName + + u", "_qs + indexName + u", "_qs; + m_body += conversion(m_state.accumulatorIn, elementType, use(m_state.accumulatorVariableIn)) + + u");\n"_qs; +} + +void QQmlJSCodeGenerator::generate_LoadProperty(int nameIndex) +{ + Q_UNUSED(nameIndex) + reject(u"LoadProperty"_qs); +} + +void QQmlJSCodeGenerator::generate_LoadOptionalProperty(int name, int offset) +{ + Q_UNUSED(name) + Q_UNUSED(offset) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generateEnumLookup(int index) +{ + const QString enumMember = m_state.accumulatorOut.enumMember(); + + // If we're referring to the type, there's nothing to do. + if (enumMember.isEmpty()) + return; + + // If the metaenum has the value, just use it and skip all the rest. + const QQmlJSMetaEnum metaEnum = m_state.accumulatorOut.enumeration(); + if (metaEnum.hasValues()) { + m_body += m_state.accumulatorVariableOut + u" = "_qs + + QString::number(metaEnum.value(enumMember)); + m_body += u";\n"_qs; + return; + } + + const QQmlJSScope::ConstPtr scopeType = m_state.accumulatorOut.scopeType(); + + // Otherwise we would have found an enum with values. + Q_ASSERT(!scopeType->isComposite()); + + const QString enumName = metaEnum.isFlag() ? metaEnum.alias() : metaEnum.name(); + const QString lookup = u"aotContext->getEnumLookup("_qs + QString::number(index) + + u", &"_qs + m_state.accumulatorVariableOut + u')'; + const QString initialization = u"aotContext->initGetEnumLookup("_qs + + QString::number(index) + u", "_qs + metaObject(scopeType) + + u", \""_qs + enumName + u"\", \""_qs + enumMember + + u"\")"_qs; + generateLookup(lookup, initialization); +} + +void QQmlJSCodeGenerator::generateTypeLookup(int index) +{ + const QString indexString = QString::number(index); + const QString namespaceString + = m_state.accumulatorIn.isImportNamespace() + ? QString::number(m_state.accumulatorIn.importNamespace()) + : u"QQmlPrivate::AOTCompiledContext::InvalidStringId"_qs; + + switch (m_state.accumulatorOut.variant()) { + case QQmlJSRegisterContent::Singleton: { + const QString lookup = u"aotContext->loadSingletonLookup("_qs + indexString + + u", &"_qs + m_state.accumulatorVariableOut + u')'; + const QString initialization = u"aotContext->initLoadSingletonLookup("_qs + indexString + + u", "_qs + namespaceString + u')'; + generateLookup(lookup, initialization); + break; + } + case QQmlJSRegisterContent::ScopeModulePrefix: + m_body += m_state.accumulatorVariableOut + u" = aotContext->qmlScopeObject;\n"_qs; + break; + case QQmlJSRegisterContent::ScopeAttached: { + const QString lookup = u"aotContext->loadAttachedLookup("_qs + indexString + + u", aotContext->qmlScopeObject, &"_qs + m_state.accumulatorVariableOut + u')'; + const QString initialization = u"aotContext->initLoadAttachedLookup("_qs + indexString + + u", "_qs + namespaceString + u", aotContext->qmlScopeObject)"_qs; + generateLookup(lookup, initialization); + break; + } + case QQmlJSRegisterContent::Script: + reject(u"script lookup"_qs); + break; + case QQmlJSRegisterContent::MetaType: { + const QString lookup = u"aotContext->loadTypeLookup("_qs + indexString + + u", &"_qs + m_state.accumulatorVariableOut + u')'; + const QString initialization = u"aotContext->initLoadTypeLookup("_qs + indexString + + u", "_qs + namespaceString + u")"_qs; + generateLookup(lookup, initialization); + break; + } + default: + Q_UNREACHABLE(); + } +} + +void QQmlJSCodeGenerator::generate_GetLookup(int index) +{ + INJECT_TRACE_INFO(generate_GetLookup); + + if (m_state.accumulatorOut.isMethod()) { + reject(u"lookup of function property."_qs); + return; + } + + if (m_state.accumulatorOut.isList()) { + reject(u"lookup of list property."_qs); + return; + } + + if (m_state.accumulatorOut.isEnumeration()) { + generateEnumLookup(index); + return; + } + + if (m_state.accumulatorOut.isImportNamespace()) { + m_body.setWriteRegister(QString()); + return; // Nothing to do. We've resolved the prefix already. + } + + const QString indexString = QString::number(index); + const QString namespaceString = m_state.accumulatorIn.isImportNamespace() + ? QString::number(m_state.accumulatorIn.importNamespace()) + : u"QQmlPrivate::AOTCompiledContext::InvalidStringId"_qs; + if (m_state.accumulatorOut.variant() == QQmlJSRegisterContent::ObjectAttached) { + const QString lookup = u"aotContext->loadAttachedLookup("_qs + indexString + + u", "_qs + use(m_state.accumulatorVariableIn) + + u", &"_qs + m_state.accumulatorVariableOut + u')'; + const QString initialization = u"aotContext->initLoadAttachedLookup("_qs + + indexString + u", "_qs + namespaceString + u", "_qs + + use(m_state.accumulatorVariableIn) + u')'; + generateLookup(lookup, initialization); + return; + } else if (m_state.accumulatorOut.variant() == QQmlJSRegisterContent::Singleton) { + const QString lookup = u"aotContext->loadSingletonLookup("_qs + indexString + + u", &"_qs + m_state.accumulatorVariableOut + u')'; + const QString initialization = u"aotContext->initLoadSingletonLookup("_qs + + indexString + u", "_qs + namespaceString + u')'; + generateLookup(lookup, initialization); + return; + } + + Q_ASSERT(m_state.accumulatorOut.isProperty()); + + const auto storedType = m_state.accumulatorIn.storedType(); + const bool isReferenceType + = (storedType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference); + + const QQmlJSScope::ConstPtr out = m_state.accumulatorOut.storedType(); + if (isReferenceType) { + m_body += u"{\n"_qs; + protectAccumulator(); + const QString lookup = u"aotContext->getObjectLookup("_qs + indexString + + u", "_qs + use(m_state.accumulatorVariableIn) + u", "_qs + + contentPointer(m_state.accumulatorOut, m_state.accumulatorVariableOut) + u')'; + const QString initialization = u"aotContext->initGetObjectLookup("_qs + + indexString + u", "_qs + use(m_state.accumulatorVariableIn) + + u", "_qs + contentType(m_state.accumulatorOut, m_state.accumulatorVariableOut) + + u')'; + const QString preparation = getLookupPreparation( + m_state.accumulatorOut, m_state.accumulatorVariableOut, index); + generateLookup(lookup, initialization, preparation); + m_body += u"}\n"_qs; + } else if ((storedType == m_typeResolver->stringType() + || storedType->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence) + && m_jsUnitGenerator->lookupName(index) == u"length"_qs) { + // Special-cased the same way as in QQmlJSTypeResolver::memberType() + m_body += m_state.accumulatorVariableOut + u" = "_qs + use(m_state.accumulatorVariableIn) + + u".count("_qs; + if (storedType == m_typeResolver->listPropertyType()) + m_body += u'&' + use(m_state.accumulatorVariableIn); + m_body += u')' + u";\n"_qs; + } else if (storedType == m_typeResolver->jsValueType()) { + reject(u"lookup in QJSValue"_qs); + } else { + m_body += u"{\n"_qs; + protectAccumulator(); + const QString lookup = u"aotContext->getValueLookup("_qs + indexString + + u", "_qs + contentPointer(m_state.accumulatorIn, + use(m_state.accumulatorVariableIn)) + + u", "_qs + contentPointer(m_state.accumulatorOut, m_state.accumulatorVariableOut) + + u')'; + const QString initialization = u"aotContext->initGetValueLookup("_qs + + indexString + u", "_qs + + metaObject(m_state.accumulatorOut.scopeType()) + u", "_qs + + contentType(m_state.accumulatorOut, m_state.accumulatorVariableOut) + u')'; + const QString preparation = getLookupPreparation( + m_state.accumulatorOut, m_state.accumulatorVariableOut, index); + generateLookup(lookup, initialization, preparation); + m_body += u"}\n"_qs; + } +} + +void QQmlJSCodeGenerator::generate_GetOptionalLookup(int index, int offset) +{ + Q_UNUSED(index) + Q_UNUSED(offset) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_StoreProperty(int nameIndex, int baseReg) +{ + Q_UNUSED(nameIndex) + Q_UNUSED(baseReg) + reject(u"StoreProperty"_qs); +} + +QString QQmlJSCodeGenerator::setLookupPreparation( + const QQmlJSRegisterContent &content, const QString &arg, int lookup) +{ + const QQmlJSScope::ConstPtr stored = content.storedType(); + if (m_typeResolver->containedType(content) == stored) { + return QString(); + } else if (stored == m_typeResolver->varType()) { + return u"const QMetaType argType = aotContext->lookupResultMetaType("_qs + + QString::number(lookup) + u");\n"_qs + + u"if (argType.isValid())\n "_qs + arg + u".convert(argType)"; + } + // TODO: We could make sure they're compatible, for example QObject pointers. + return QString(); +} + + +void QQmlJSCodeGenerator::generate_SetLookup(int index, int baseReg) +{ + INJECT_TRACE_INFO(generate_SetLookup); + + m_body.setHasSideEffects(true); + m_body.setWriteRegister(QString()); + + const QString indexString = QString::number(index); + const QQmlJSScope::ConstPtr valueType = m_state.accumulatorIn.storedType(); + const QQmlJSRegisterContent callBase = registerType(baseReg); + const QQmlJSScope::ConstPtr objectType = callBase.storedType(); + const QQmlJSRegisterContent specific = m_typeResolver->memberType( + callBase, m_jsUnitGenerator->lookupName(index)); + const QQmlJSRegisterContent property = specific.storedIn( + m_typeResolver->genericType(specific.storedType())); + + const QString object = use(registerVariable(baseReg)); + m_body += u"{\n"_qs; + QString variableIn; + QString variableInType; + QString preparation; + QString argType; + if (property != m_state.accumulatorIn) { + m_body += u"auto converted = "_qs + + conversion(m_state.accumulatorIn, property, use(m_state.accumulatorVariableIn)) + + u";\n"_qs; + variableIn = contentPointer(property, u"converted"_qs); + variableInType = contentType(property, u"converted"_qs); + preparation = setLookupPreparation(property, u"converted"_qs, index); + if (preparation.isEmpty()) + argType = contentType(property, u"converted"_qs); + else + argType = u"argType"_qs; + } else { + variableIn = contentPointer(property, use(m_state.accumulatorVariableIn)); + variableInType = contentType(property, use(m_state.accumulatorVariableIn)); + argType = variableInType; + } + + switch (objectType->accessSemantics()) { + case QQmlJSScope::AccessSemantics::Reference: { + const QString lookup = u"aotContext->setObjectLookup("_qs + indexString + + u", "_qs + object + u", "_qs + variableIn + u')'; + const QString initialization = u"aotContext->initSetObjectLookup("_qs + + indexString + u", "_qs + object + u", "_qs + argType + u')'; + generateLookup(lookup, initialization, preparation); + break; + } + case QQmlJSScope::AccessSemantics::Sequence: { + const QString propertyName = m_jsUnitGenerator->lookupName(index); + if (propertyName != u"length"_qs) { + reject(u"setting non-length property on a sequence type"_qs); + break; + } + + if (objectType != m_typeResolver->listPropertyType()) { + reject(u"SetLookup on sequence types (because of missing write-back)"_qs); + break; + } + + // We can resize without write back on a list property because it's actually a reference. + m_body += u"const int begin = "_qs + object + u".count(&" + object + u");\n"_qs; + m_body += u"const int end = "_qs + + (variableIn.startsWith(u'&') ? variableIn.mid(1) : (u'*' + variableIn)) + + u";\n"_qs; + m_body += u"for (int i = begin; i < end; ++i)\n"_qs; + m_body += u" "_qs + object + u".append(&"_qs + object + u", nullptr);\n"_qs; + m_body += u"for (int i = begin; i > end; --i)\n"_qs; + m_body += u" "_qs + object + u".removeLast(&"_qs + object + u')' + + u";\n"_qs; + break; + } + case QQmlJSScope::AccessSemantics::Value: { + const QString propertyName = m_jsUnitGenerator->lookupName(index); + const QQmlJSRegisterContent specific = m_typeResolver->memberType(callBase, propertyName); + Q_ASSERT(specific.isProperty()); + const QQmlJSRegisterContent property = specific.storedIn( + m_typeResolver->genericType(specific.storedType())); + + const QString lookup = u"aotContext->setValueLookup("_qs + indexString + + u", "_qs + contentPointer(registerType(baseReg), object) + + u", "_qs + variableIn + u')'; + const QString initialization = u"aotContext->initSetValueLookup("_qs + + indexString + u", "_qs + metaObject(property.scopeType()) + + u", "_qs + contentType(registerType(baseReg), object) + u')'; + + generateLookup(lookup, initialization, preparation); + reject(u"SetLookup on value types (because of missing write-back)"_qs); + break; + } + case QQmlJSScope::AccessSemantics::None: + Q_UNREACHABLE(); + break; + } + + m_body += u"}\n"_qs; +} + +void QQmlJSCodeGenerator::generate_LoadSuperProperty(int property) +{ + Q_UNUSED(property) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_StoreSuperProperty(int property) +{ + Q_UNUSED(property) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_Yield() +{ + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_YieldStar() +{ + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_Resume(int) +{ + BYTECODE_UNIMPLEMENTED(); +} + +QString QQmlJSCodeGenerator::argumentsList(int argc, int argv, QString *outVar) +{ + QString types; + QString args; + QString conversions; + + if (m_typeResolver->containedType(m_state.accumulatorOut) == m_typeResolver->voidType()) { + types = u"QMetaType()"_qs; + args = u"nullptr"_qs; + } else { + *outVar = u"callResult"_qs; + const QQmlJSScope::ConstPtr outType = m_state.accumulatorOut.storedType(); + m_body += outType->internalName(); + if (outType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) + m_body += u" *"_qs; + else + m_body += u' '; + m_body += *outVar + u";\n"; + + types = metaTypeFromType(m_state.accumulatorOut.storedType()); + args = u'&' + *outVar; + } + + for (int i = 0; i < argc; ++i) { + const QQmlJSScope::ConstPtr type = registerType(argv + i).storedType(); + const QString var = use(registerVariable(argv + i)); + if (type == m_typeResolver->jsPrimitiveType()) { + QString argName = u"arg"_qs + QString::number(i); + conversions += u"QVariant "_qs + argName + u" = "_qs + + conversion(type, m_typeResolver->varType(), var) + u";\n"_qs; + args += u", "_qs + argName + u".data()"_qs; + types += u", "_qs + argName + u".metaType()"_qs; + } else if (type == m_typeResolver->varType()) { + args += u", "_qs + var + u".data()"_qs; + types += u", "_qs + var + u".metaType()"_qs; + } else { + args += u", &"_qs + var; + types += u", "_qs + metaTypeFromType(type); + } + } + return conversions + + u"void *args[] = { "_qs + args + u" };\n"_qs + + u"const QMetaType types[] = { "_qs + types + u" };\n"_qs; +} + +void QQmlJSCodeGenerator::generateMoveOutVar(const QString &outVar) +{ + // Generate a new section to set m_state.accumulatorVariableOut, + // so that m_state.accumulatorVariableOut can be optimized away. + + nextSection(); + m_body.setWriteRegister(m_state.accumulatorVariableOut); + m_body += m_state.accumulatorVariableOut + u" = "_qs; + + if (outVar.isEmpty()) + m_body += u"{};\n"_qs; + else + m_body += u"std::move(" + outVar + u");\n"; + + nextSection(); + m_body.setHasSideEffects(true); + m_body.setWriteRegister(QString()); +} + +void QQmlJSCodeGenerator::generate_CallValue(int name, int argc, int argv) +{ + Q_UNUSED(name) + Q_UNUSED(argc) + Q_UNUSED(argv) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_CallWithReceiver(int name, int thisObject, int argc, int argv) +{ + Q_UNUSED(name) + Q_UNUSED(thisObject) + Q_UNUSED(argc) + Q_UNUSED(argv) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_CallProperty(int nameIndex, int baseReg, int argc, int argv) +{ + Q_UNUSED(nameIndex); + Q_UNUSED(baseReg); + Q_UNUSED(argc); + Q_UNUSED(argv); + reject(u"CallProperty"_qs); +} + +bool QQmlJSCodeGenerator::inlineMathMethod(const QString &name, int argc, int argv) +{ + addInclude(u"cmath"_qs); + addInclude(u"limits"_qs); + addInclude(u"qalgorithms.h"_qs); + addInclude(u"qrandom.h"_qs); + + if (m_state.accumulatorOut.storedType() != m_typeResolver->realType()) + return false; + + m_body += u"{\n"_qs; + for (int i = 0; i < argc; ++i) { + m_body += u"const double arg%1 = "_qs.arg(i + 1) + conversion( + registerType(argv + i).storedType(), + m_typeResolver->realType(), use(registerVariable(argv + i))) + + u";\n"_qs; + } + + const QString qNaN = u"std::numeric_limits<double>::quiet_NaN()"_qs; + const QString inf = u"std::numeric_limits<double>::infinity()"_qs; + m_body += m_state.accumulatorVariableOut + u" = "_qs; + + if (name == u"abs" && argc == 1) { + m_body += u"(qIsNull(arg1) ? 0 : (arg1 < 0.0 ? -arg1 : arg1))"_qs; + } else if (name == u"acos"_qs && argc == 1) { + m_body += u"arg1 > 1.0 ? %1 : std::acos(arg1)"_qs.arg(qNaN); + } else if (name == u"acosh"_qs && argc == 1) { + m_body += u"arg1 < 1.0 ? %1 : std::acosh(arg1)"_qs.arg(qNaN); + } else if (name == u"asin"_qs && argc == 1) { + m_body += u"arg1 > 1.0 ? %1 : std::asin(arg1)"_qs.arg(qNaN); + } else if (name == u"asinh"_qs && argc == 1) { + m_body += u"qIsNull(arg1) ? arg1 : std::asinh(arg1)"_qs; + } else if (name == u"atan"_qs && argc == 1) { + m_body += u"qIsNull(arg1) ? arg1 : std::atan(arg1)"_qs; + } else if (name == u"atanh"_qs && argc == 1) { + m_body += u"qIsNull(arg1) ? arg1 : std::atanh(arg1)"_qs; + } else if (name == u"atan2"_qs) { + // TODO: complicated + return false; + } else if (name == u"cbrt"_qs && argc == 1) { + m_body += u"std::cbrt(arg1)"_qs; + } else if (name == u"ceil"_qs && argc == 1) { + m_body += u"(arg1 < 0.0 && arg1 > -1.0) ? std::copysign(0.0, -1.0) : std::ceil(arg1)"_qs; + } else if (name == u"clz32"_qs && argc == 1) { + m_body += u"qint32(qCountLeadingZeroBits(quint32(QJSNumberCoercion::toInteger(arg1))))"_qs; + } else if (name == u"cos"_qs && argc == 1) { + m_body += u"std::cos(arg1)"_qs; + } else if (name == u"cosh"_qs && argc == 1) { + m_body += u"std::cosh(arg1)"_qs; + } else if (name == u"exp"_qs && argc == 1) { + m_body += u"std::isinf(arg1) " + "? (std::copysign(1.0, arg1) == -1 ? 0.0 : %1) " + ": std::exp(arg1)"_qs.arg(inf); + } else if (name == u"expm1"_qs) { + // TODO: complicated + return false; + } else if (name == u"floor"_qs && argc == 1) { + m_body += u"std::floor(arg1)"_qs; + } else if (name == u"fround"_qs && argc == 1) { + m_body += u"(std::isnan(arg1) || std::isinf(arg1) || qIsNull(arg1)) " + "? arg1 " + ": double(float(arg1))"_qs; + } else if (name == u"hypot"_qs) { + // TODO: complicated + return false; + } else if (name == u"imul"_qs && argc == 2) { + m_body += u"qint32(quint32(QJSNumberCoercion::toInteger(arg1)) " + "* quint32(QJSNumberCoercion::toInteger(arg2)))"_qs; + } else if (name == u"log"_qs && argc == 1) { + m_body += u"arg1 < 0.0 ? %1 : std::log(arg1)"_qs.arg(qNaN); + } else if (name == u"log10"_qs && argc == 1) { + m_body += u"arg1 < 0.0 ? %1 : std::log10(arg1)"_qs.arg(qNaN); + } else if (name == u"log1p"_qs && argc == 1) { + m_body += u"arg1 < -1.0 ? %1 : std::log1p(arg1)"_qs.arg(qNaN); + } else if (name == u"log2"_qs && argc == 1) { + m_body += u"arg1 < -0.0 ? %1 : std::log2(arg1)"_qs.arg(qNaN); + } else if (name == u"max"_qs && argc == 2) { + m_body += u"(qIsNull(arg2) && qIsNull(arg1) && std::copysign(1.0, arg2) == 1) " + "? arg2 " + ": ((arg2 > arg1 || std::isnan(arg2)) ? arg2 : arg1)"_qs; + } else if (name == u"min"_qs && argc == 2) { + m_body += u"(qIsNull(arg2) && qIsNull(arg1) && std::copysign(1.0, arg2) == -1) " + "? arg2 " + ": ((arg2 < arg1 || std::isnan(arg2)) ? arg2 : arg1)"_qs; + } else if (name == u"pow"_qs) { + // TODO: complicated + return false; + } else if (name == u"random"_qs && argc == 0) { + m_body += u"QRandomGenerator::global()->generateDouble()"_qs; + } else if (name == u"round"_qs && argc == 1) { + m_body += u"std::isfinite(arg1) " + "? ((arg1 < 0.5 && arg1 >= -0.5) " + "? std::copysign(0.0, arg1) " + ": std::floor(arg1 + 0.5)) " + ": arg1"_qs; + } else if (name == u"sign"_qs && argc == 1) { + m_body += u"std::isnan(arg1) " + "? %1 " + ": (qIsNull(arg1) " + "? arg1 " + ": (std::signbit(arg1) ? -1.0 : 1.0))"_qs.arg(qNaN); + } else if (name == u"sin"_qs && argc == 1) { + m_body += u"qIsNull(arg1) ? arg1 : std::sin(arg1)"_qs; + } else if (name == u"sinh"_qs && argc == 1) { + m_body += u"qIsNull(arg1) ? arg1 : std::sinh(arg1)"_qs; + } else if (name == u"sqrt"_qs && argc == 1) { + m_body += u"std::sqrt(arg1)"_qs; + } else if (name == u"tan"_qs && argc == 1) { + m_body += u"qIsNull(arg1) ? arg1 : std::tan(arg1)"_qs; + } else if (name == u"tanh"_qs && argc == 1) { + m_body += u"qIsNull(arg1) ? arg1 : std::tanh(arg1)"_qs; + } else if (name == u"trunc"_qs && argc == 1) { + m_body += u"std::trunc(arg1)"_qs; + } else { + return false; + } + + m_body += u";\n"_qs; + m_body += u"}\n"_qs; + return true; +} + +void QQmlJSCodeGenerator::generate_CallPropertyLookup(int index, int base, int argc, int argv) +{ + INJECT_TRACE_INFO(generate_CallPropertyLookup); + + const QQmlJSRegisterContent baseType = registerType(base); + if (baseType.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) { + const QString name = m_jsUnitGenerator->stringForIndex( + m_jsUnitGenerator->lookupNameIndex(index)); + if (m_typeResolver->containedType(baseType) == mathObject()) { + if (inlineMathMethod(name, argc, argv)) + return; + } + + reject(u"call to property '%1' of %2"_qs.arg(name, baseType.descriptiveName())); + } + + m_body.setHasSideEffects(true); + m_body.setWriteRegister(QString()); + const QString indexString = QString::number(index); + + m_body += u"{\n"_qs; + + QString outVar; + m_body += argumentsList(argc, argv, &outVar); + const QString lookup = u"aotContext->callObjectPropertyLookup("_qs + indexString + + u", "_qs + use(registerVariable(base)) + + u", args, types, "_qs + QString::number(argc) + u')'; + const QString initialization = u"aotContext->initCallObjectPropertyLookup("_qs + + indexString + u')'; + generateLookup(lookup, initialization); + generateMoveOutVar(outVar); + + m_body += u"}\n"_qs; +} + +void QQmlJSCodeGenerator::generate_CallElement(int base, int index, int argc, int argv) +{ + Q_UNUSED(base) + Q_UNUSED(index) + Q_UNUSED(argc) + Q_UNUSED(argv) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_CallName(int name, int argc, int argv) +{ + Q_UNUSED(name); + Q_UNUSED(argc); + Q_UNUSED(argv); + reject(u"CallName"_qs); +} + +void QQmlJSCodeGenerator::generate_CallPossiblyDirectEval(int argc, int argv) +{ + Q_UNUSED(argc) + Q_UNUSED(argv) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_CallGlobalLookup(int index, int argc, int argv) +{ + Q_UNUSED(index); + Q_UNUSED(argc); + Q_UNUSED(argv); + reject(u"CallGlobalLookup"_qs); +} + +void QQmlJSCodeGenerator::generate_CallQmlContextPropertyLookup(int index, int argc, int argv) +{ + INJECT_TRACE_INFO(generate_CallQmlContextPropertyLookup); + + m_body.setHasSideEffects(true); + const QString indexString = QString::number(index); + + m_body += u"{\n"_qs; + QString outVar; + m_body += argumentsList(argc, argv, &outVar); + const QString lookup = u"aotContext->callQmlContextPropertyLookup("_qs + indexString + + u", args, types, "_qs + QString::number(argc) + u')'; + const QString initialization = u"aotContext->initCallQmlContextPropertyLookup("_qs + + indexString + u')'; + generateLookup(lookup, initialization); + generateMoveOutVar(outVar); + + m_body += u"}\n"_qs; +} + +void QQmlJSCodeGenerator::generate_CallWithSpread(int func, int thisObject, int argc, int argv) +{ + Q_UNUSED(func) + Q_UNUSED(thisObject) + Q_UNUSED(argc) + Q_UNUSED(argv) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_TailCall(int func, int thisObject, int argc, int argv) +{ + Q_UNUSED(func) + Q_UNUSED(thisObject) + Q_UNUSED(argc) + Q_UNUSED(argv) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_Construct(int func, int argc, int argv) +{ + Q_UNUSED(func); + Q_UNUSED(argc); + Q_UNUSED(argv); + reject(u"Construct"_qs); +} + +void QQmlJSCodeGenerator::generate_ConstructWithSpread(int func, int argc, int argv) +{ + Q_UNUSED(func) + Q_UNUSED(argc) + Q_UNUSED(argv) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_SetUnwindHandler(int offset) +{ + Q_UNUSED(offset) + reject(u"SetUnwindHandlerh"_qs); +} + +void QQmlJSCodeGenerator::generate_UnwindDispatch() +{ + reject(u"UnwindDispatch"_qs); +} + +void QQmlJSCodeGenerator::generate_UnwindToLabel(int level, int offset) +{ + Q_UNUSED(level) + Q_UNUSED(offset) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_DeadTemporalZoneCheck(int name) +{ + Q_UNUSED(name) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_ThrowException() +{ + INJECT_TRACE_INFO(generate_ThrowException); + + m_body.setHasSideEffects(true); + m_body.setWriteRegister(QString()); + + generateSetInstructionPointer(); + m_body += u"aotContext->engine->throwError("_qs + + conversion(m_state.accumulatorIn, m_typeResolver->globalType( + m_typeResolver->jsValueType()), + use(m_state.accumulatorVariableIn)) + u");\n"_qs; + m_body += u"return "_qs + errorReturnValue() + u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_GetException() +{ + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_SetException() +{ + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_CreateCallContext() +{ + INJECT_TRACE_INFO(generate_CreateCallContext); + + m_body.setHasSideEffects(true); + m_body.setWriteRegister(QString()); + m_body += u"{\n"_qs; +} + +void QQmlJSCodeGenerator::generate_PushCatchContext(int index, int nameIndex) +{ + Q_UNUSED(index) + Q_UNUSED(nameIndex) + reject(u"PushCatchContext"_qs); +} + +void QQmlJSCodeGenerator::generate_PushWithContext() +{ + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_PushBlockContext(int index) +{ + Q_UNUSED(index) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_CloneBlockContext() +{ + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_PushScriptContext(int index) +{ + Q_UNUSED(index) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_PopScriptContext() +{ + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_PopContext() +{ + INJECT_TRACE_INFO(generate_PopContext); + + m_body.setHasSideEffects(true); + m_body.setWriteRegister(QString()); + // Add a semicolon before the closing brace, in case there was a bare label before it. + m_body += u";}\n"_qs; +} + +void QQmlJSCodeGenerator::generate_GetIterator(int iterator) +{ + Q_UNUSED(iterator) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_IteratorNext(int value, int done) +{ + Q_UNUSED(value) + Q_UNUSED(done) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_IteratorNextForYieldStar(int iterator, int object) +{ + Q_UNUSED(iterator) + Q_UNUSED(object) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_IteratorClose(int done) +{ + Q_UNUSED(done) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_DestructureRestElement() +{ + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_DeleteProperty(int base, int index) +{ + Q_UNUSED(base) + Q_UNUSED(index) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_DeleteName(int name) +{ + Q_UNUSED(name) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_TypeofName(int name) +{ + Q_UNUSED(name); + reject(u"TypeofName"_qs); +} + +void QQmlJSCodeGenerator::generate_TypeofValue() +{ + reject(u"TypeofValue"_qs); +} + +void QQmlJSCodeGenerator::generate_DeclareVar(int varName, int isDeletable) +{ + Q_UNUSED(varName) + Q_UNUSED(isDeletable) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_DefineArray(int argc, int args) +{ + Q_UNUSED(argc); + Q_UNUSED(args); + reject(u"DefineArray"_qs); +} + +void QQmlJSCodeGenerator::generate_DefineObjectLiteral(int internalClassId, int argc, int args) +{ + Q_UNUSED(internalClassId) + Q_UNUSED(argc) + Q_UNUSED(args) + reject(u"DefineObjectLiteral"_qs); +} + +void QQmlJSCodeGenerator::generate_CreateClass(int classIndex, int heritage, int computedNames) +{ + Q_UNUSED(classIndex) + Q_UNUSED(heritage) + Q_UNUSED(computedNames) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_CreateMappedArgumentsObject() +{ + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_CreateUnmappedArgumentsObject() +{ + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_CreateRestParameter(int argIndex) +{ + Q_UNUSED(argIndex) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_ConvertThisToObject() +{ + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_LoadSuperConstructor() +{ + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_ToObject() +{ + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_Jump(int offset) +{ + INJECT_TRACE_INFO(generate_Jump); + + m_body.setHasSideEffects(true); + m_body.setWriteRegister(QString()); + generateJumpCodeWithTypeConversions(offset, JumpMode::Unconditional); + m_body += u";\n"_qs; + m_skipUntilNextLabel = true; +} + +void QQmlJSCodeGenerator::generate_JumpTrue(int offset) +{ + INJECT_TRACE_INFO(generate_JumpTrue); + + m_body.setHasSideEffects(true); + m_body.setWriteRegister(QString()); + + m_body += u"if ("_qs; + m_body += conversion(m_state.accumulatorIn.storedType(), m_typeResolver->boolType(), + use(m_state.accumulatorVariableIn)); + m_body += u") "_qs; + generateJumpCodeWithTypeConversions(offset, JumpMode::Conditional); + m_body += u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_JumpFalse(int offset) +{ + INJECT_TRACE_INFO(generate_JumpFalse); + + m_body.setHasSideEffects(true); + m_body.setWriteRegister(QString()); + + m_body += u"if (!"_qs; + m_body += conversion(m_state.accumulatorIn.storedType(), m_typeResolver->boolType(), + use(m_state.accumulatorVariableIn)); + m_body += u") "_qs; + generateJumpCodeWithTypeConversions(offset, JumpMode::Conditional); + m_body += u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_JumpNoException(int offset) +{ + INJECT_TRACE_INFO(generate_JumpNoException); + + m_body.setHasSideEffects(true); + m_body.setWriteRegister(QString()); + + m_body += u"if (!context->engine->hasException()) "_qs; + generateJumpCodeWithTypeConversions(offset, JumpMode::Conditional); + m_body += u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_JumpNotUndefined(int offset) +{ + Q_UNUSED(offset) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_CheckException() +{ + INJECT_TRACE_INFO(generate_CheckException); + + m_body.setHasSideEffects(true); + m_body.setWriteRegister(QString()); + + generateExceptionCheck(); +} + +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 += u";\n"_qs; + +} + +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 += u";\n"_qs; +} + +QString QQmlJSCodeGenerator::eqIntExpression(int lhsConst) +{ + if (m_state.accumulatorIn.storedType() == m_typeResolver->intType()) + return QString::number(lhsConst) + u" == "_qs + use(m_state.accumulatorVariableIn); + + if (m_state.accumulatorIn.storedType() == m_typeResolver->boolType()) { + return QString::number(lhsConst) + u" == "_qs + + conversion(m_state.accumulatorIn.storedType(), m_typeResolver->intType(), + use(m_state.accumulatorVariableIn)); + } + + if (m_typeResolver->isNumeric(m_state.accumulatorIn)) { + return conversion(m_typeResolver->intType(), m_typeResolver->realType(), + QString::number(lhsConst)) + u" == "_qs + + conversion(m_state.accumulatorIn.storedType(), m_typeResolver->realType(), + use(m_state.accumulatorVariableIn)); + } + + QString result; + result += conversion(m_typeResolver->intType(), m_typeResolver->jsPrimitiveType(), + QString::number(lhsConst)); + result += u".equals("_qs; + result += conversion(m_state.accumulatorIn.storedType(), m_typeResolver->jsPrimitiveType(), + use(m_state.accumulatorVariableIn)); + result += u')'; + return result; +} + +QString QQmlJSCodeGenerator::getLookupPreparation( + const QQmlJSRegisterContent &content, const QString &var, int lookup) +{ + const QQmlJSScope::ConstPtr stored = content.storedType(); + if (m_typeResolver->containedType(content) == stored) { + return QString(); + } else if (stored == m_typeResolver->varType()) { + return var + u" = QVariant(aotContext->lookupResultMetaType("_qs + + QString::number(lookup) + u"))"_qs; + } + // TODO: We could make sure they're compatible, for example QObject pointers. + return QString(); +} + +QString QQmlJSCodeGenerator::contentPointer(const QQmlJSRegisterContent &content, const QString &var) +{ + const QQmlJSScope::ConstPtr stored = content.storedType(); + if (m_typeResolver->containedType(content) == stored) + return u'&' + var; + else if (stored == m_typeResolver->varType()) + return var + u".data()"_qs; + else if (stored->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) + return u'&' + var; + else + reject(u"content pointer of non-QVariant wrapper type "_qs + content.descriptiveName()); + return QString(); +} + +QString QQmlJSCodeGenerator::contentType(const QQmlJSRegisterContent &content, const QString &var) +{ + const QQmlJSScope::ConstPtr stored = content.storedType(); + const QQmlJSScope::ConstPtr contained = QQmlJSScope::nonCompositeBaseType( + m_typeResolver->containedType(content)); + if (contained == stored) + return metaTypeFromType(stored); + else if (stored == m_typeResolver->varType()) + return var + u".metaType()"_qs; // We expect the QVariant to be initialized + else if (stored->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) + return metaTypeFromName(contained); + else + reject(u"content type of non-QVariant wrapper type "_qs + content.descriptiveName()); + return QString(); +} + +void QQmlJSCodeGenerator::generate_CmpEqInt(int lhsConst) +{ + INJECT_TRACE_INFO(generate_CmpEqInt); + + m_body += m_state.accumulatorVariableOut + u" = "_qs + eqIntExpression(lhsConst) + + u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_CmpNeInt(int lhsConst) +{ + INJECT_TRACE_INFO(generate_CmpNeInt); + + m_body += m_state.accumulatorVariableOut + u" = !"_qs + eqIntExpression(lhsConst) + + u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_CmpEq(int lhs) +{ + INJECT_TRACE_INFO(generate_CmpEq); + generateEqualityOperation(lhs, u"equals"_qs, false); +} + +void QQmlJSCodeGenerator::generate_CmpNe(int lhs) +{ + INJECT_TRACE_INFO(generate_CmpNe); + generateEqualityOperation(lhs, u"equals"_qs, true); +} + +void QQmlJSCodeGenerator::generate_CmpGt(int lhs) +{ + INJECT_TRACE_INFO(generate_CmpGt); + generateCompareOperation(lhs, u">"_qs); +} + +void QQmlJSCodeGenerator::generate_CmpGe(int lhs) +{ + INJECT_TRACE_INFO(generate_CmpGe); + generateCompareOperation(lhs, u">="_qs); +} + +void QQmlJSCodeGenerator::generate_CmpLt(int lhs) +{ + INJECT_TRACE_INFO(generate_CmpLt); + generateCompareOperation(lhs, u"<"_qs); +} + +void QQmlJSCodeGenerator::generate_CmpLe(int lhs) +{ + INJECT_TRACE_INFO(generate_CmpLe); + generateCompareOperation(lhs, u"<="_qs); +} + +void QQmlJSCodeGenerator::generate_CmpStrictEqual(int lhs) +{ + INJECT_TRACE_INFO(generate_CmpStrictEqual); + generateEqualityOperation(lhs, u"strictlyEquals"_qs, false); +} + +void QQmlJSCodeGenerator::generate_CmpStrictNotEqual(int lhs) +{ + INJECT_TRACE_INFO(generate_CmpStrictNotEqual); + generateEqualityOperation(lhs, u"strictlyEquals"_qs, true); +} + +void QQmlJSCodeGenerator::generate_CmpIn(int lhs) +{ + Q_UNUSED(lhs) + reject(u"CmpIn"_qs); +} + +void QQmlJSCodeGenerator::generate_CmpInstanceOf(int lhs) +{ + Q_UNUSED(lhs) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_As(int lhs) +{ + INJECT_TRACE_INFO(generate_As); + + const QString input = use(registerVariable(lhs)); + const QQmlJSScope::ConstPtr contained = m_typeResolver->containedType(m_state.accumulatorOut); + + m_body += m_state.accumulatorVariableOut + u" = "_qs; + if (m_state.accumulatorIn.storedType() == m_typeResolver->metaObjectType() + && contained->isComposite()) { + m_body += conversion( + m_typeResolver->genericType(contained), m_state.accumulatorOut.storedType(), + use(m_state.accumulatorVariableIn) + u"->cast("_qs + input + u')'); + } else { + m_body += conversion( + m_typeResolver->genericType(contained), m_state.accumulatorOut.storedType(), + u'(' + metaObject(contained) + u")->cast("_qs + input + u')'); + } + m_body += u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_UNot() +{ + INJECT_TRACE_INFO(generate_UNot); + m_body += m_state.accumulatorVariableOut; + m_body += u" = !"_qs; + m_body += conversion(m_state.accumulatorIn.storedType(), m_typeResolver->boolType(), + use(m_state.accumulatorVariableIn)); + m_body += u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_UPlus() +{ + INJECT_TRACE_INFO(generate_UPlus); + m_body += m_state.accumulatorVariableOut; + m_body += u"= +"_qs; + m_body += conversion(m_state.accumulatorIn, m_state.accumulatorOut, + use(m_state.accumulatorVariableIn)); + m_body += u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_UMinus() +{ + INJECT_TRACE_INFO(generate_UMinus); + m_body += m_state.accumulatorVariableOut; + m_body += u"= -"_qs; + m_body += conversion(m_state.accumulatorIn, m_state.accumulatorOut, + use(m_state.accumulatorVariableIn)); + m_body += u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_UCompl() +{ + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_Increment() +{ + INJECT_TRACE_INFO(generate_Increment); + if (m_state.accumulatorVariableIn != m_state.accumulatorVariableOut) { + m_body += m_state.accumulatorVariableOut + u" = "_qs + + conversion(m_state.accumulatorIn, m_state.accumulatorOut, + use(m_state.accumulatorVariableIn)) + u"; "_qs; + // No line break, to allow removal of the whole thing + } + m_body += u"++"_qs + use(m_state.accumulatorVariableOut) + u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_Decrement() +{ + INJECT_TRACE_INFO(generate_Decrement); + if (m_state.accumulatorVariableIn != m_state.accumulatorVariableOut) { + m_body += m_state.accumulatorVariableOut + u" = "_qs + + conversion(m_state.accumulatorIn, m_state.accumulatorOut, + use(m_state.accumulatorVariableIn)) + u"; "_qs; + // No line break, to allow removal of the whole thing + } + m_body += u"--"_qs + use(m_state.accumulatorVariableOut) + u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_Add(int lhs) +{ + INJECT_TRACE_INFO(generate_Add); + generateArithmeticOperation(lhs, u"+"_qs); +} + +void QQmlJSCodeGenerator::generate_BitAnd(int lhs) +{ + Q_UNUSED(lhs) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_BitOr(int lhs) +{ + Q_UNUSED(lhs) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_BitXor(int lhs) +{ + Q_UNUSED(lhs) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_UShr(int lhs) +{ + Q_UNUSED(lhs) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_Shr(int lhs) +{ + Q_UNUSED(lhs); + reject(u"Shr"_qs); +} + +void QQmlJSCodeGenerator::generate_Shl(int lhs) +{ + Q_UNUSED(lhs); + reject(u"Shl"_qs); +} + +void QQmlJSCodeGenerator::generate_BitAndConst(int rhs) +{ + Q_UNUSED(rhs) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_BitOrConst(int rhs) +{ + Q_UNUSED(rhs) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_BitXorConst(int rhs) +{ + Q_UNUSED(rhs) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_UShrConst(int rhs) +{ + Q_UNUSED(rhs) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_ShrConst(int value) +{ + Q_UNUSED(value); + reject(u"ShrConst"_qs); +} + +void QQmlJSCodeGenerator::generate_ShlConst(int value) +{ + Q_UNUSED(value); + reject(u"ShlConst"_qs); +} + +void QQmlJSCodeGenerator::generate_Exp(int lhs) +{ + Q_UNUSED(lhs) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_Mul(int lhs) +{ + INJECT_TRACE_INFO(generate_Mul); + generateArithmeticOperation(lhs, u"*"_qs); +} + +void QQmlJSCodeGenerator::generate_Div(int lhs) +{ + INJECT_TRACE_INFO(generate_Div); + generateArithmeticOperation(lhs, u"/"_qs); +} + +void QQmlJSCodeGenerator::generate_Mod(int lhs) +{ + INJECT_TRACE_INFO(generate_Mod); + + const auto lhsVar = conversion( + registerType(lhs).storedType(), m_typeResolver->jsPrimitiveType(), + use(registerVariable(lhs))); + const auto rhsVar = conversion( + m_state.accumulatorIn.storedType(), m_typeResolver->jsPrimitiveType(), + use(m_state.accumulatorVariableIn)); + Q_ASSERT(!lhsVar.isEmpty()); + Q_ASSERT(!rhsVar.isEmpty()); + + m_body += m_state.accumulatorVariableOut; + m_body += u" = "_qs; + m_body += conversion(m_typeResolver->jsPrimitiveType(), m_state.accumulatorOut.storedType(), + u'(' + lhsVar + u" % "_qs + rhsVar + u')'); + m_body += u";\n"_qs; +} + +void QQmlJSCodeGenerator::generate_Sub(int lhs) +{ + INJECT_TRACE_INFO(generate_Sub); + generateArithmeticOperation(lhs, u"-"_qs); +} + +void QQmlJSCodeGenerator::generate_InitializeBlockDeadTemporalZone(int firstReg, int count) +{ + Q_UNUSED(firstReg) + Q_UNUSED(count) + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_ThrowOnNullOrUndefined() +{ + BYTECODE_UNIMPLEMENTED(); +} + +void QQmlJSCodeGenerator::generate_GetTemplateObject(int index) +{ + Q_UNUSED(index) + BYTECODE_UNIMPLEMENTED(); +} + +QV4::Moth::ByteCodeHandler::Verdict QQmlJSCodeGenerator::startInstruction(QV4::Moth::Instr::Type) +{ + m_state.State::operator=(nextStateFromAnnotations(m_state, *m_annotations)); + m_state.accumulatorVariableIn = m_registerVariables.value(Accumulator) + .value(m_state.accumulatorIn.storedType()); + Q_ASSERT(!m_state.accumulatorIn.isValid() || !m_state.accumulatorVariableIn.isEmpty()); + + auto labelIt = m_labels.constFind(currentInstructionOffset()); + if (labelIt != m_labels.constEnd()) { + nextSection(); + m_body.setHasSideEffects(true); + m_body.setLabel(*labelIt); + m_body += *labelIt + u":;\n"_qs; + m_skipUntilNextLabel = false; + } else if (m_skipUntilNextLabel) { + return SkipInstruction; + } + + nextSection(); + + m_state.accumulatorVariableOut = registerVariable(QQmlJSTypePropagator::Accumulator); + if (!m_state.accumulatorVariableOut.isEmpty()) + m_body.setWriteRegister(m_state.accumulatorVariableOut); + + // If the accumulator type is valid, we want an accumulator variable. + // If not, we don't want one. + // If the stored type is void, we don't need a variable, but we want to transport the type + // information for any enum access or similar. + Q_ASSERT((m_state.accumulatorOut.isValid() + && m_state.accumulatorOut.storedType() != m_typeResolver->voidType()) + || m_state.accumulatorVariableOut.isEmpty()); + Q_ASSERT(!m_state.accumulatorOut.isValid() + || m_state.accumulatorOut.storedType() == m_typeResolver->voidType() + || !m_state.accumulatorVariableOut.isEmpty()); + + const int currentLine = currentSourceLocation().startLine; + if (currentLine != m_lastLineNumberUsed) { + const int nextLine = nextJSLine(currentLine); + if (nextLine == currentLine + 1 || nextLine < 1) { + m_body += u"// "_qs; + m_body += m_sourceCodeLines.value(currentLine - 1).trimmed(); + m_body += u'\n'; + } else { + m_body += u"/*\n"_qs; + for (auto line = currentLine - 1; line < nextLine - 1; ++line) { + m_body += m_sourceCodeLines.at(line).trimmed(); + m_body += u'\n'; + } + m_body += u"*/\n"_qs; + } + m_lastLineNumberUsed = currentLine; + } + return ProcessInstruction; +} + +void QQmlJSCodeGenerator::endInstruction(QV4::Moth::Instr::Type) +{ + generateJumpCodeWithTypeConversions(0, JumpMode::None); + nextSection(); +} + +void QQmlJSCodeGenerator::generateSetInstructionPointer() +{ + m_body += u"aotContext->setInstructionPointer("_qs + + QString::number(nextInstructionOffset()) + u");\n"_qs; +} + +void QQmlJSCodeGenerator::generateExceptionCheck() +{ + m_body += u"if (aotContext->engine->hasError())\n"_qs; + m_body += u" return "_qs + errorReturnValue() + u";\n"_qs; +} + +void QQmlJSCodeGenerator::generateEqualityOperation(int lhs, const QString &function, bool invert) +{ + const QQmlJSRegisterContent lhsContent = registerType(lhs); + auto isComparable = [&]() { + if (m_typeResolver->isPrimitive(lhsContent) + && m_typeResolver->isPrimitive(m_state.accumulatorIn)) { + return true; + } + if (m_typeResolver->isNumeric(lhsContent) && m_state.accumulatorIn.isEnumeration()) + return true; + if (m_typeResolver->isNumeric(m_state.accumulatorIn) && lhsContent.isEnumeration()) + return true; + return false; + }; + + if (!isComparable()) + reject(u"equality comparison on non-primitive types"_qs); + + const QQmlJSScope::ConstPtr lhsType = lhsContent.storedType(); + const QQmlJSScope::ConstPtr rhsType = m_state.accumulatorIn.storedType(); + + m_body += m_state.accumulatorVariableOut + u" = "_qs; + + const auto primitive = m_typeResolver->jsPrimitiveType(); + if (lhsType == rhsType && lhsType != primitive) { + m_body += use(registerVariable(lhs)); + m_body += (invert ? u" != "_qs : u" == "_qs); + m_body += 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 += u";\n"_qs; +} + +void QQmlJSCodeGenerator::generateCompareOperation(int lhs, const QString &cppOperator) +{ + m_body += m_state.accumulatorVariableOut; + + 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 += u";\n"_qs; +} + +void QQmlJSCodeGenerator::generateArithmeticOperation(int lhs, const QString &cppOperator) +{ + const auto lhsVar = conversion(registerType(lhs), m_state.accumulatorOut, + use(registerVariable(lhs))); + const auto rhsVar = conversion(m_state.accumulatorIn, m_state.accumulatorOut, + use(m_state.accumulatorVariableIn)); + Q_ASSERT(!lhsVar.isEmpty()); + Q_ASSERT(!rhsVar.isEmpty()); + + m_body += m_state.accumulatorVariableOut; + m_body += u" = "_qs; + m_body += lhsVar; + m_body += u' '; + m_body += cppOperator; + m_body += u' '; + m_body += rhsVar; + m_body += u";\n"_qs; +} + +void QQmlJSCodeGenerator::generateLookup(const QString &lookup, const QString &initialization, + const QString &resultPreparation) +{ + if (!resultPreparation.isEmpty()) + m_body += resultPreparation + u";\n"_qs; + m_body += u"while (!"_qs + lookup + u") {\n"_qs; + generateSetInstructionPointer(); + m_body += initialization + u";\n"_qs; + generateExceptionCheck(); + if (!resultPreparation.isEmpty()) + m_body += resultPreparation + u";\n"_qs; + m_body += u"}\n"_qs; +} + +void QQmlJSCodeGenerator::protectAccumulator() +{ + // If both m_state.accumulatorIn and m_state.accumulatorOut are QVariant, we will need to + // prepare the output QVariant, and afterwards use the input variant. Therefore we need to move + // the input out of the way first. + if (m_state.accumulatorVariableIn == m_state.accumulatorVariableOut + && m_state.accumulatorOut.storedType() == m_typeResolver->varType()) { + m_state.accumulatorVariableIn = use(m_state.accumulatorVariableIn) + u"_moved"_qs; + m_body += u"QVariant "_qs + m_state.accumulatorVariableIn + + u" = std::move("_qs + m_state.accumulatorVariableOut + u");\n"_qs; + } +} + +void QQmlJSCodeGenerator::generateJumpCodeWithTypeConversions( + int relativeOffset, JumpMode mode) +{ + nextSection(); + m_body.setHasSideEffects(true); + m_body += u"{\n"_qs; + int absoluteOffset =nextInstructionOffset() + relativeOffset; + + const auto annotation = m_annotations->constFind(absoluteOffset); + if (annotation != m_annotations->constEnd()) { + const auto ¤tTypes = (*m_annotations)[currentInstructionOffset()].registers; + const auto &conversions = annotation->expectedTargetTypesBeforeJump; + + for (auto regIt = conversions.constBegin(), regEnd = conversions.constEnd(); + regIt != regEnd; ++regIt) { + int registerIndex = regIt.key(); + const QQmlJSRegisterContent targetType = regIt.value(); + if (!targetType.isValid() || !currentTypes.contains(registerIndex)) + continue; + const QQmlJSRegisterContent currentType = currentTypes.value(registerIndex); + if (!currentType.isValid() || currentType == targetType) + continue; + Q_ASSERT(m_registerVariables.contains(registerIndex)); + const auto ¤tRegisterVariables = m_registerVariables[registerIndex]; + const auto variable = currentRegisterVariables.constFind(targetType.storedType()); + const QString oldVar = registerVariable(registerIndex); + if (variable == currentRegisterVariables.end() || *variable == oldVar) + continue; + + nextSection(); + m_body.setWriteRegister(*variable); + m_body += *variable; + m_body += u" = "_qs; + m_body += conversion(currentTypes.value(registerIndex), targetType, use(oldVar)); + m_body += u";\n"_qs; + } + } + + nextSection(); + m_body.setHasSideEffects(true); + if (relativeOffset) { + auto labelIt = m_labels.find(absoluteOffset); + if (labelIt == m_labels.end()) + labelIt = m_labels.insert(absoluteOffset, u"label_%1"_qs.arg(m_labels.count())); + m_body.setJump(*labelIt, mode); + m_body += u" goto "_qs + *labelIt + u";\n"_qs; + } + m_body += u"}\n"_qs; +} + +QString QQmlJSCodeGenerator::registerVariable(int index) const +{ + if (index >= QV4::CallData::OffsetCount && index < firstRegisterIndex()) { + const int argumentIndex = index - QV4::CallData::OffsetCount; + return u"*static_cast<"_qs + + castTargetName(m_function->argumentTypes[argumentIndex]) + + u"*>(argumentsPtr["_qs + QString::number(argumentIndex) + u"])"_qs; + } + return m_registerVariables.value(index).value(registerType(index).storedType()); +} + +QQmlJSRegisterContent QQmlJSCodeGenerator::registerType(int index) const +{ + if (index >= QV4::CallData::OffsetCount && index < firstRegisterIndex()) { + return m_typeResolver->globalType( + m_function->argumentTypes[index - QV4::CallData::OffsetCount]); + } + return m_state.registers[index]; +} + +QString QQmlJSCodeGenerator::conversion(const QQmlJSScope::ConstPtr &from, + const QQmlJSScope::ConstPtr &to, + const QString &variable) const +{ + // TODO: most values can be moved, which is much more efficient with the common types. + // add a move(from, to, variable) function that implements the moves. + Q_ASSERT(!to->isComposite()); // We cannot directly convert to composites. + + if (from == to) + return variable; + + if (from->accessSemantics() == QQmlJSScope::AccessSemantics::Reference + && to->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) { + for (QQmlJSScope::ConstPtr base = from; base; base = base->baseType()) { + // We still have to cast as other execution paths may result in different types. + if (base == to) + return u"static_cast<"_qs + to->internalName() + u" *>("_qs + variable + u')'; + } + for (QQmlJSScope::ConstPtr base = to; base; base = base->baseType()) { + if (base == from) + return u"static_cast<"_qs + to->internalName() + u" *>("_qs + variable + u')'; + } + } + + const auto jsValueType = m_typeResolver->jsValueType(); + + auto isJsValue = [&](const QQmlJSScope::ConstPtr &candidate) { + return candidate == jsValueType || candidate->isScript(); + }; + + if (isJsValue(from) && isJsValue(to)) + return variable; + + const auto boolType = m_typeResolver->boolType(); + const auto isBoolOrNumber = [&](const QQmlJSScope::ConstPtr &type) { + return m_typeResolver->isNumeric(m_typeResolver->globalType(type)) + || type == m_typeResolver->boolType() + || type->scopeType() == QQmlJSScope::EnumScope; + }; + + if (from == m_typeResolver->realType() && to == m_typeResolver->intType()) + return u"QJSNumberCoercion::toInteger("_qs + variable + u')'; + + if (isBoolOrNumber(from) && isBoolOrNumber(to)) + return to->internalName() + u'(' + variable + u')'; + + const auto varType = m_typeResolver->varType(); + const auto jsPrimitiveType = m_typeResolver->jsPrimitiveType(); + if (from == jsPrimitiveType) { + if (to == m_typeResolver->realType()) + return variable + u".toDouble()"_qs; + if (to == boolType) + return variable + u".toBoolean()"_qs; + if (to == m_typeResolver->intType()) + return variable + u".toInteger()"_qs; + if (to == m_typeResolver->stringType()) + return variable + u".toString()"_qs; + if (to == jsValueType) + return u"QJSValue(QJSPrimitiveValue("_qs + variable + u"))"_qs; + if (to == varType) + return variable + u".toVariant()"_qs; + if (to->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) + return u"static_cast<"_qs + to->internalName() + u" *>(nullptr)"_qs; + } + + if (isJsValue(from)) { + if (to == jsPrimitiveType) + return variable + u".toPrimitive()"_qs; + if (to == varType) + return variable + u".toVariant(QJSValue::RetainJSObjects)"_qs; + return u"qjsvalue_cast<"_qs + castTargetName(to) + u">("_qs + variable + u')'; + } + + if (to == jsPrimitiveType) + return u"QJSPrimitiveValue("_qs + variable + u')'; + + if (to == jsValueType) + return u"aotContext->engine->toScriptValue("_qs + variable + u')'; + + if (from == varType) { + if (to == m_typeResolver->listPropertyType()) + return u"QQmlListReference("_qs + variable + u", aotContext->qmlEngine())"_qs; + return u"qvariant_cast<"_qs + castTargetName(to) + u">("_qs + variable + u')'; + } + + if (to == varType) + return u"QVariant::fromValue("_qs + variable + u')'; + + // TODO: more efficient string conversions, possibly others + + return u"aotContext->engine->fromScriptValue<"_qs + castTargetName(to) + + u">(aotContext->engine->toScriptValue("_qs + variable + u"))"_qs; +} + +int QQmlJSCodeGenerator::nextJSLine(uint line) const +{ + auto findLine = [](uint line, const QV4::CompiledData::CodeOffsetToLine &entry) { + return entry.line > line; + }; + const auto codeToLine + = std::upper_bound(m_context->lineNumberMapping.constBegin(), + m_context->lineNumberMapping.constEnd(), + line, + findLine); + bool bNoNextLine = m_context->lineNumberMapping.constEnd() == codeToLine; + + return static_cast<int>(bNoNextLine ? -1 : codeToLine->line); +} + +void QQmlJSCodeGenerator::reject(const QString &thing) +{ + setError(u"Cannot generate efficient code for %1"_qs.arg(thing)); +} + +QT_END_NAMESPACE |