diff options
Diffstat (limited to 'src/qmlcompiler/qqmljscompiler.cpp')
-rw-r--r-- | src/qmlcompiler/qqmljscompiler.cpp | 818 |
1 files changed, 818 insertions, 0 deletions
diff --git a/src/qmlcompiler/qqmljscompiler.cpp b/src/qmlcompiler/qqmljscompiler.cpp new file mode 100644 index 0000000000..d55140f964 --- /dev/null +++ b/src/qmlcompiler/qqmljscompiler.cpp @@ -0,0 +1,818 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qqmljscompiler_p.h" + +#include <private/qqmlirbuilder_p.h> +#include <private/qqmljsbasicblocks_p.h> +#include <private/qqmljscodegenerator_p.h> +#include <private/qqmljscompilerstats_p.h> +#include <private/qqmljsfunctioninitializer_p.h> +#include <private/qqmljsimportvisitor_p.h> +#include <private/qqmljslexer_p.h> +#include <private/qqmljsloadergenerator_p.h> +#include <private/qqmljsoptimizations_p.h> +#include <private/qqmljsparser_p.h> +#include <private/qqmljsshadowcheck_p.h> +#include <private/qqmljsstoragegeneralizer_p.h> +#include <private/qqmljsstorageinitializer_p.h> +#include <private/qqmljstypepropagator_p.h> + +#include <QtCore/qfile.h> +#include <QtCore/qfileinfo.h> +#include <QtCore/qloggingcategory.h> + +#include <QtQml/private/qqmlsignalnames_p.h> + +#include <limits> + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +Q_LOGGING_CATEGORY(lcAotCompiler, "qt.qml.compiler.aot", QtFatalMsg); + +static const int FileScopeCodeIndex = -1; + +static QSet<QString> getIllegalNames() +{ + QSet<QString> illegalNames; + for (const char **g = QV4::Compiler::Codegen::s_globalNames; *g != nullptr; ++g) + illegalNames.insert(QString::fromLatin1(*g)); + return illegalNames; +} + +Q_GLOBAL_STATIC_WITH_ARGS(QSet<QString>, illegalNames, (getIllegalNames())); + + +void QQmlJSCompileError::print() +{ + fprintf(stderr, "%s\n", qPrintable(message)); +} + +QQmlJSCompileError QQmlJSCompileError::augment(const QString &contextErrorMessage) const +{ + QQmlJSCompileError augmented; + augmented.message = contextErrorMessage + message; + return augmented; +} + +static QString diagnosticErrorMessage(const QString &fileName, const QQmlJS::DiagnosticMessage &m) +{ + QString message; + message = fileName + QLatin1Char(':') + QString::number(m.loc.startLine) + QLatin1Char(':'); + if (m.loc.startColumn > 0) + message += QString::number(m.loc.startColumn) + QLatin1Char(':'); + + if (m.isError()) + message += QLatin1String(" error: "); + else + message += QLatin1String(" warning: "); + message += m.message; + return message; +} + +void QQmlJSCompileError::appendDiagnostic(const QString &inputFileName, + const QQmlJS::DiagnosticMessage &diagnostic) +{ + if (!message.isEmpty()) + message += QLatin1Char('\n'); + message += diagnosticErrorMessage(inputFileName, diagnostic); +} + +void QQmlJSCompileError::appendDiagnostics(const QString &inputFileName, + const QList<QQmlJS::DiagnosticMessage> &diagnostics) +{ + for (const QQmlJS::DiagnosticMessage &diagnostic: diagnostics) + appendDiagnostic(inputFileName, diagnostic); +} + +// Ensure that ListElement objects keep all property assignments in their string form +static void annotateListElements(QmlIR::Document *document) +{ + QStringList listElementNames; + + for (const QV4::CompiledData::Import *import : std::as_const(document->imports)) { + const QString uri = document->stringAt(import->uriIndex); + if (uri != QStringLiteral("QtQml.Models") && uri != QStringLiteral("QtQuick")) + continue; + + QString listElementName = QStringLiteral("ListElement"); + const QString qualifier = document->stringAt(import->qualifierIndex); + if (!qualifier.isEmpty()) { + listElementName.prepend(QLatin1Char('.')); + listElementName.prepend(qualifier); + } + listElementNames.append(listElementName); + } + + if (listElementNames.isEmpty()) + return; + + for (QmlIR::Object *object : std::as_const(document->objects)) { + if (!listElementNames.contains(document->stringAt(object->inheritedTypeNameIndex))) + continue; + for (QmlIR::Binding *binding = object->firstBinding(); binding; binding = binding->next) { + if (binding->type() != QV4::CompiledData::Binding::Type_Script) + continue; + binding->stringIndex = document->registerString(object->bindingAsString(document, binding->value.compiledScriptIndex)); + } + } +} + +static bool checkArgumentsObjectUseInSignalHandlers(const QmlIR::Document &doc, + QQmlJSCompileError *error) +{ + for (QmlIR::Object *object: std::as_const(doc.objects)) { + for (auto binding = object->bindingsBegin(); binding != object->bindingsEnd(); ++binding) { + if (binding->type() != QV4::CompiledData::Binding::Type_Script) + continue; + const QString propName = doc.stringAt(binding->propertyNameIndex); + if (!QQmlSignalNames::isHandlerName(propName)) + continue; + auto compiledFunction = doc.jsModule.functions.value(object->runtimeFunctionIndices.at(binding->value.compiledScriptIndex)); + if (!compiledFunction) + continue; + if (compiledFunction->usesArgumentsObject == QV4::Compiler::Context::ArgumentsObjectUsed) { + error->message = QLatin1Char(':') + QString::number(compiledFunction->line) + QLatin1Char(':'); + if (compiledFunction->column > 0) + error->message += QString::number(compiledFunction->column) + QLatin1Char(':'); + + error->message += QLatin1String(" error: The use of eval() or the use of the arguments object in signal handlers is\n" + "not supported when compiling qml files ahead of time. That is because it's ambiguous if \n" + "any signal parameter is called \"arguments\". Similarly the string passed to eval might use\n" + "\"arguments\". Unfortunately we cannot distinguish between it being a parameter or the\n" + "JavaScript arguments object at this point.\n" + "Consider renaming the parameter of the signal if applicable or moving the code into a\n" + "helper function."); + return false; + } + } + } + return true; +} + +class BindingOrFunction +{ +public: + BindingOrFunction(const QmlIR::Binding &b) : m_binding(&b) {} + BindingOrFunction(const QmlIR::Function &f) : m_function(&f) {} + + friend bool operator<(const BindingOrFunction &lhs, const BindingOrFunction &rhs) + { + return lhs.index() < rhs.index(); + } + + const QmlIR::Binding *binding() const { return m_binding; } + const QmlIR::Function *function() const { return m_function; } + + quint32 index() const + { + return m_binding + ? m_binding->value.compiledScriptIndex + : (m_function + ? m_function->index + : std::numeric_limits<quint32>::max()); + } + +private: + const QmlIR::Binding *m_binding = nullptr; + const QmlIR::Function *m_function = nullptr; +}; + +bool qCompileQmlFile(const QString &inputFileName, QQmlJSSaveFunction saveFunction, + QQmlJSAotCompiler *aotCompiler, QQmlJSCompileError *error, + bool storeSourceLocation, QV4::Compiler::CodegenWarningInterface *interface, + const QString *fileContents) +{ + QmlIR::Document irDocument(/*debugMode*/false); + return qCompileQmlFile(irDocument, inputFileName, saveFunction, aotCompiler, error, + storeSourceLocation, interface, fileContents); +} + +bool qCompileQmlFile(QmlIR::Document &irDocument, const QString &inputFileName, + QQmlJSSaveFunction saveFunction, QQmlJSAotCompiler *aotCompiler, + QQmlJSCompileError *error, bool storeSourceLocation, + QV4::Compiler::CodegenWarningInterface *interface, const QString *fileContents) +{ + QString sourceCode; + + if (fileContents != nullptr) { + sourceCode = *fileContents; + } else { + QFile f(inputFileName); + if (!f.open(QIODevice::ReadOnly)) { + error->message = QLatin1String("Error opening ") + inputFileName + QLatin1Char(':') + f.errorString(); + return false; + } + sourceCode = QString::fromUtf8(f.readAll()); + if (f.error() != QFileDevice::NoError) { + error->message = QLatin1String("Error reading from ") + inputFileName + QLatin1Char(':') + f.errorString(); + return false; + } + } + + { + QmlIR::IRBuilder irBuilder(*illegalNames()); + if (!irBuilder.generateFromQml(sourceCode, inputFileName, &irDocument)) { + error->appendDiagnostics(inputFileName, irBuilder.errors); + return false; + } + } + + annotateListElements(&irDocument); + QQmlJSAotFunctionMap aotFunctionsByIndex; + + { + QmlIR::JSCodeGen v4CodeGen(&irDocument, *illegalNames(), interface, storeSourceLocation); + + if (aotCompiler) + aotCompiler->setDocument(&v4CodeGen, &irDocument); + + QHash<QmlIR::Object *, QmlIR::Object *> effectiveScopes; + for (QmlIR::Object *object: std::as_const(irDocument.objects)) { + if (object->functionsAndExpressions->count == 0 && object->bindingCount() == 0) + continue; + + if (!v4CodeGen.generateRuntimeFunctions(object)) { + Q_ASSERT(v4CodeGen.hasError()); + error->appendDiagnostic(inputFileName, v4CodeGen.error()); + return false; + } + + if (!aotCompiler) + continue; + + QmlIR::Object *scope = object; + for (auto it = effectiveScopes.constFind(scope), end = effectiveScopes.constEnd(); + it != end; it = effectiveScopes.constFind(scope)) { + scope = *it; + } + + aotCompiler->setScope(object, scope); + aotFunctionsByIndex[FileScopeCodeIndex] = aotCompiler->globalCode(); + + std::vector<BindingOrFunction> bindingsAndFunctions; + bindingsAndFunctions.reserve(object->bindingCount() + object->functionCount()); + + std::copy(object->bindingsBegin(), object->bindingsEnd(), + std::back_inserter(bindingsAndFunctions)); + std::copy(object->functionsBegin(), object->functionsEnd(), + std::back_inserter(bindingsAndFunctions)); + + QList<QmlIR::CompiledFunctionOrExpression> functionsToCompile; + for (QmlIR::CompiledFunctionOrExpression *foe = object->functionsAndExpressions->first; + foe; foe = foe->next) { + functionsToCompile << *foe; + } + + // AOT-compile bindings and functions in the same order as above so that the runtime + // class indices match + auto contextMap = v4CodeGen.module()->contextMap; + std::sort(bindingsAndFunctions.begin(), bindingsAndFunctions.end()); + std::for_each(bindingsAndFunctions.begin(), bindingsAndFunctions.end(), + [&](const BindingOrFunction &bindingOrFunction) { + std::variant<QQmlJSAotFunction, QQmlJS::DiagnosticMessage> result; + if (const auto *binding = bindingOrFunction.binding()) { + switch (binding->type()) { + case QmlIR::Binding::Type_AttachedProperty: + case QmlIR::Binding::Type_GroupProperty: + effectiveScopes.insert( + irDocument.objects.at(binding->value.objectIndex), scope); + return; + case QmlIR::Binding::Type_Boolean: + case QmlIR::Binding::Type_Number: + case QmlIR::Binding::Type_String: + case QmlIR::Binding::Type_Null: + case QmlIR::Binding::Type_Object: + case QmlIR::Binding::Type_Translation: + case QmlIR::Binding::Type_TranslationById: + return; + default: + break; + } + + Q_ASSERT(quint32(functionsToCompile.size()) > binding->value.compiledScriptIndex); + const auto &functionToCompile + = functionsToCompile[binding->value.compiledScriptIndex]; + auto *parentNode = functionToCompile.parentNode; + Q_ASSERT(parentNode); + Q_ASSERT(contextMap.contains(parentNode)); + QV4::Compiler::Context *context = contextMap.take(parentNode); + Q_ASSERT(context); + + auto *node = functionToCompile.node; + Q_ASSERT(node); + + if (context->returnsClosure) { + QQmlJS::AST::Node *inner + = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement *>( + node)->expression; + Q_ASSERT(inner); + QV4::Compiler::Context *innerContext = contextMap.take(inner); + Q_ASSERT(innerContext); + qCDebug(lcAotCompiler) << "Compiling signal handler for" + << irDocument.stringAt(binding->propertyNameIndex); + std::variant<QQmlJSAotFunction, QQmlJS::DiagnosticMessage> innerResult + = aotCompiler->compileBinding(innerContext, *binding, inner); + + if (auto *error = std::get_if<QQmlJS::DiagnosticMessage>(&innerResult)) { + qCDebug(lcAotCompiler) << "Compilation failed:" + << diagnosticErrorMessage(inputFileName, *error); + } else if (auto *func = std::get_if<QQmlJSAotFunction>(&innerResult)) { + qCDebug(lcAotCompiler) << "Generated code:" << func->code; + aotFunctionsByIndex[innerContext->functionIndex] = *func; + } + } + + qCDebug(lcAotCompiler) << "Compiling binding for property" + << irDocument.stringAt(binding->propertyNameIndex); + result = aotCompiler->compileBinding(context, *binding, node); + } else if (const auto *function = bindingOrFunction.function()) { + Q_ASSERT(quint32(functionsToCompile.size()) > function->index); + auto *node = functionsToCompile[function->index].node; + Q_ASSERT(node); + Q_ASSERT(contextMap.contains(node)); + QV4::Compiler::Context *context = contextMap.take(node); + Q_ASSERT(context); + + const QString functionName = irDocument.stringAt(function->nameIndex); + qCDebug(lcAotCompiler) << "Compiling function" << functionName; + result = aotCompiler->compileFunction(context, functionName, node); + } else { + Q_UNREACHABLE(); + } + + if (auto *error = std::get_if<QQmlJS::DiagnosticMessage>(&result)) { + qCDebug(lcAotCompiler) << "Compilation failed:" + << diagnosticErrorMessage(inputFileName, *error); + } else if (auto *func = std::get_if<QQmlJSAotFunction>(&result)) { + qCDebug(lcAotCompiler) << "Generated code:" << func->code; + aotFunctionsByIndex[object->runtimeFunctionIndices[bindingOrFunction.index()]] = + *func; + } + }); + } + + if (!checkArgumentsObjectUseInSignalHandlers(irDocument, error)) { + *error = error->augment(inputFileName); + return false; + } + + QmlIR::QmlUnitGenerator generator; + irDocument.javaScriptCompilationUnit = v4CodeGen.generateCompilationUnit(/*generate unit*/false); + generator.generate(irDocument); + + const quint32 saveFlags + = QV4::CompiledData::Unit::StaticData + | QV4::CompiledData::Unit::PendingTypeCompilation; + QV4::CompiledData::SaveableUnitPointer saveable( + irDocument.javaScriptCompilationUnit->unitData(), saveFlags); + if (!saveFunction(saveable, aotFunctionsByIndex, &error->message)) + return false; + } + return true; +} + +bool qCompileJSFile(const QString &inputFileName, const QString &inputFileUrl, QQmlJSSaveFunction saveFunction, QQmlJSCompileError *error) +{ + QQmlRefPointer<QV4::CompiledData::CompilationUnit> unit; + + QString sourceCode; + { + QFile f(inputFileName); + if (!f.open(QIODevice::ReadOnly)) { + error->message = QLatin1String("Error opening ") + inputFileName + QLatin1Char(':') + f.errorString(); + return false; + } + sourceCode = QString::fromUtf8(f.readAll()); + if (f.error() != QFileDevice::NoError) { + error->message = QLatin1String("Error reading from ") + inputFileName + QLatin1Char(':') + f.errorString(); + return false; + } + } + + const bool isModule = inputFileName.endsWith(QLatin1String(".mjs")); + if (isModule) { + QList<QQmlJS::DiagnosticMessage> diagnostics; + // Precompiled files are relocatable and the final location will be set when loading. + QString url; + unit = QV4::Compiler::Codegen::compileModule(/*debugMode*/false, url, sourceCode, + QDateTime(), &diagnostics); + error->appendDiagnostics(inputFileName, diagnostics); + if (!unit || !unit->unitData()) + return false; + } else { + QmlIR::Document irDocument(/*debugMode*/false); + + QQmlJS::Engine *engine = &irDocument.jsParserEngine; + QmlIR::ScriptDirectivesCollector directivesCollector(&irDocument); + QQmlJS::Directives *oldDirs = engine->directives(); + engine->setDirectives(&directivesCollector); + auto directivesGuard = qScopeGuard([engine, oldDirs]{ + engine->setDirectives(oldDirs); + }); + + QQmlJS::AST::Program *program = nullptr; + + { + QQmlJS::Lexer lexer(engine); + lexer.setCode(sourceCode, /*line*/1, /*parseAsBinding*/false); + QQmlJS::Parser parser(engine); + + bool parsed = parser.parseProgram(); + + error->appendDiagnostics(inputFileName, parser.diagnosticMessages()); + + if (!parsed) + return false; + + program = QQmlJS::AST::cast<QQmlJS::AST::Program*>(parser.rootNode()); + if (!program) { + lexer.setCode(QStringLiteral("undefined;"), 1, false); + parsed = parser.parseProgram(); + Q_ASSERT(parsed); + program = QQmlJS::AST::cast<QQmlJS::AST::Program*>(parser.rootNode()); + Q_ASSERT(program); + } + } + + { + QmlIR::JSCodeGen v4CodeGen(&irDocument, *illegalNames()); + v4CodeGen.generateFromProgram(inputFileName, inputFileUrl, sourceCode, program, + &irDocument.jsModule, QV4::Compiler::ContextType::ScriptImportedByQML); + if (v4CodeGen.hasError()) { + error->appendDiagnostic(inputFileName, v4CodeGen.error()); + return false; + } + + // Precompiled files are relocatable and the final location will be set when loading. + irDocument.jsModule.fileName.clear(); + irDocument.jsModule.finalUrl.clear(); + + irDocument.javaScriptCompilationUnit = v4CodeGen.generateCompilationUnit(/*generate unit*/false); + QmlIR::QmlUnitGenerator generator; + generator.generate(irDocument); + unit = std::move(irDocument.javaScriptCompilationUnit); + } + } + + QQmlJSAotFunctionMap empty; + return saveFunction( + QV4::CompiledData::SaveableUnitPointer(unit->unitData()), empty, &error->message); +} + +static const char *funcHeaderCode = R"( + [](const QQmlPrivate::AOTCompiledContext *aotContext, void **argv) { +Q_UNUSED(aotContext) +Q_UNUSED(argv) +)"; + +bool qSaveQmlJSUnitAsCpp(const QString &inputFileName, const QString &outputFileName, const QV4::CompiledData::SaveableUnitPointer &unit, const QQmlJSAotFunctionMap &aotFunctions, QString *errorString) +{ +#if QT_CONFIG(temporaryfile) + QSaveFile f(outputFileName); +#else + QFile f(outputFileName); +#endif + if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + *errorString = f.errorString(); + return false; + } + + auto writeStr = [&f, errorString](const QByteArray &data) { + if (f.write(data) != data.size()) { + *errorString = f.errorString(); + return false; + } + return true; + }; + + if (!writeStr("// ")) + return false; + + if (!writeStr(inputFileName.toUtf8())) + return false; + + if (!writeStr("\n")) + return false; + + if (!writeStr("#include <QtQml/qqmlprivate.h>\n")) + return false; + + if (!aotFunctions.isEmpty()) { + QStringList includes; + + for (const auto &function : aotFunctions) + includes.append(function.includes); + + std::sort(includes.begin(), includes.end()); + const auto end = std::unique(includes.begin(), includes.end()); + for (auto it = includes.begin(); it != end; ++it) { + if (!writeStr(QStringLiteral("#include <%1>\n").arg(*it).toUtf8())) + return false; + } + } + + if (!writeStr(QByteArrayLiteral("namespace QmlCacheGeneratedCode {\nnamespace "))) + return false; + + if (!writeStr(qQmlJSSymbolNamespaceForPath(inputFileName).toUtf8())) + return false; + + if (!writeStr(QByteArrayLiteral(" {\nextern const unsigned char qmlData alignas(16) [];\n" + "extern const unsigned char qmlData alignas(16) [] = {\n"))) + return false; + + unit.saveToDisk<uchar>([&writeStr](const uchar *begin, quint32 size) { + QByteArray hexifiedData; + { + QTextStream stream(&hexifiedData); + const uchar *end = begin + size; + stream << Qt::hex; + int col = 0; + for (const uchar *data = begin; data < end; ++data, ++col) { + if (data > begin) + stream << ','; + if (col % 8 == 0) { + stream << '\n'; + col = 0; + } + stream << "0x" << *data; + } + stream << '\n'; + } + return writeStr(hexifiedData); + }); + + + + if (!writeStr("};\n")) + return false; + + // Suppress the following warning generated by MSVC 2019: + // "the usage of 'QJSNumberCoercion::toInteger' requires the compiler to capture 'this' + // but the current default capture mode does not allow it" + // You clearly don't have to capture 'this' in order to call 'QJSNumberCoercion::toInteger'. + // TODO: Remove when we don't have to support MSVC 2019 anymore. Mind the QT_WARNING_POP below. + if (!writeStr("QT_WARNING_PUSH\nQT_WARNING_DISABLE_MSVC(4573)\n")) + return false; + + writeStr(aotFunctions[FileScopeCodeIndex].code.toUtf8().constData()); + if (aotFunctions.size() <= 1) { + // FileScopeCodeIndex is always there, but it may be the only one. + writeStr("extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[];\n" + "extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[] = { { 0, 0, nullptr, nullptr } };\n"); + } else { + writeStr("extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[];\n" + "extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[] = {\n"); + + QString footer = QStringLiteral("}\n"); + + for (QQmlJSAotFunctionMap::ConstIterator func = aotFunctions.constBegin(), + end = aotFunctions.constEnd(); + func != end; ++func) { + + if (func.key() == FileScopeCodeIndex) + continue; + + const QString function = QString::fromUtf8(funcHeaderCode) + func.value().code + footer; + + writeStr(QStringLiteral("{ %1, %2, [](QV4::ExecutableCompilationUnit *unit, " + "QMetaType *argTypes) {\n%3}, %4 },") + .arg(func.key()) + .arg(func->numArguments) + .arg(func->signature, function) + .toUtf8().constData()); + } + + // Conclude the list with a nullptr + writeStr("{ 0, 0, nullptr, nullptr }"); + writeStr("};\n"); + } + + if (!writeStr("QT_WARNING_POP\n")) + return false; + + if (!writeStr("}\n}\n")) + return false; + +#if QT_CONFIG(temporaryfile) + if (!f.commit()) { + *errorString = f.errorString(); + return false; + } +#endif + + return true; +} + +QQmlJSAotCompiler::QQmlJSAotCompiler( + QQmlJSImporter *importer, const QString &resourcePath, const QStringList &qmldirFiles, + QQmlJSLogger *logger) + : m_typeResolver(importer) + , m_resourcePath(resourcePath) + , m_qmldirFiles(qmldirFiles) + , m_importer(importer) + , m_logger(logger) +{ +} + +void QQmlJSAotCompiler::setDocument( + const QmlIR::JSCodeGen *codegen, const QmlIR::Document *irDocument) +{ + Q_UNUSED(codegen); + m_document = irDocument; + const QFileInfo resourcePathInfo(m_resourcePath); + m_logger->setFileName(resourcePathInfo.fileName()); + m_logger->setCode(irDocument->code); + m_unitGenerator = &irDocument->jsGenerator; + QQmlJSScope::Ptr target = QQmlJSScope::create(); + QQmlJSImportVisitor visitor(target, m_importer, m_logger, + resourcePathInfo.canonicalPath() + u'/', + m_qmldirFiles); + m_typeResolver.init(&visitor, irDocument->program); +} + +void QQmlJSAotCompiler::setScope(const QmlIR::Object *object, const QmlIR::Object *scope) +{ + m_currentObject = object; + m_currentScope = scope; +} + +static bool isStrict(const QmlIR::Document *doc) +{ + for (const QmlIR::Pragma *pragma : doc->pragmas) { + if (pragma->type == QmlIR::Pragma::Strict) + return true; + } + return false; +} + +QQmlJS::DiagnosticMessage QQmlJSAotCompiler::diagnose( + const QString &message, QtMsgType type, const QQmlJS::SourceLocation &location) const +{ + if (isStrict(m_document) + && (type == QtWarningMsg || type == QtCriticalMsg || type == QtFatalMsg) + && m_logger->isCategoryFatal(qmlCompiler)) { + qFatal("%s:%d: (strict mode) %s", + qPrintable(QFileInfo(m_resourcePath).fileName()), + location.startLine, qPrintable(message)); + } + + // TODO: this is a special place that explicitly sets the severity through + // logger's private function + m_logger->log(message, qmlCompiler, location, type); + + return QQmlJS::DiagnosticMessage { + message, + type, + location + }; +} + +std::variant<QQmlJSAotFunction, QQmlJS::DiagnosticMessage> QQmlJSAotCompiler::compileBinding( + const QV4::Compiler::Context *context, const QmlIR::Binding &irBinding, + QQmlJS::AST::Node *astNode) +{ + QQmlJSFunctionInitializer initializer( + &m_typeResolver, m_currentObject->location, m_currentScope->location); + QQmlJS::DiagnosticMessage error; + const QString name = m_document->stringAt(irBinding.propertyNameIndex); + QQmlJSCompilePass::Function function = initializer.run( + context, name, astNode, irBinding, &error); + const QQmlJSAotFunction aotFunction = doCompileAndRecordAotStats( + context, &function, &error, name, astNode->firstSourceLocation()); + + if (error.isValid()) { + // If it's a signal and the function just returns a closure, it's harmless. + // Otherwise promote the message to warning level. + return diagnose(error.message, + (function.isSignalHandler && error.type == QtDebugMsg) + ? QtDebugMsg + : QtWarningMsg, + error.loc); + } + + qCDebug(lcAotCompiler()) << "includes:" << aotFunction.includes; + qCDebug(lcAotCompiler()) << "binding code:" << aotFunction.code; + return aotFunction; +} + +std::variant<QQmlJSAotFunction, QQmlJS::DiagnosticMessage> QQmlJSAotCompiler::compileFunction( + const QV4::Compiler::Context *context, const QString &name, QQmlJS::AST::Node *astNode) +{ + QQmlJSFunctionInitializer initializer( + &m_typeResolver, m_currentObject->location, m_currentScope->location); + QQmlJS::DiagnosticMessage error; + QQmlJSCompilePass::Function function = initializer.run(context, name, astNode, &error); + const QQmlJSAotFunction aotFunction = doCompileAndRecordAotStats( + context, &function, &error, name, astNode->firstSourceLocation()); + + if (error.isValid()) + return diagnose(error.message, QtWarningMsg, error.loc); + + qCDebug(lcAotCompiler()) << "includes:" << aotFunction.includes; + qCDebug(lcAotCompiler()) << "binding code:" << aotFunction.code; + return aotFunction; +} + +QQmlJSAotFunction QQmlJSAotCompiler::globalCode() const +{ + QQmlJSAotFunction global; + global.includes = { + u"QtQml/qjsengine.h"_s, + u"QtQml/qjsprimitivevalue.h"_s, + u"QtQml/qjsvalue.h"_s, + u"QtQml/qqmlcomponent.h"_s, + u"QtQml/qqmlcontext.h"_s, + u"QtQml/qqmlengine.h"_s, + u"QtQml/qqmllist.h"_s, + + u"QtCore/qdatetime.h"_s, + u"QtCore/qtimezone.h"_s, + u"QtCore/qobject.h"_s, + u"QtCore/qstring.h"_s, + u"QtCore/qstringlist.h"_s, + u"QtCore/qurl.h"_s, + u"QtCore/qvariant.h"_s, + + u"type_traits"_s + }; + return global; +} + +QQmlJSAotFunction QQmlJSAotCompiler::doCompile( + const QV4::Compiler::Context *context, QQmlJSCompilePass::Function *function, + QQmlJS::DiagnosticMessage *error) +{ + const auto compileError = [&]() { + Q_ASSERT(error->isValid()); + error->type = context->returnsClosure ? QtDebugMsg : QtWarningMsg; + return QQmlJSAotFunction(); + }; + + if (error->isValid()) + return compileError(); + + bool basicBlocksValidationFailed = false; + QQmlJSBasicBlocks basicBlocks(context, m_unitGenerator, &m_typeResolver, m_logger); + auto passResult = basicBlocks.run(function, m_flags, basicBlocksValidationFailed); + auto &[blocks, annotations] = passResult; + + QQmlJSTypePropagator propagator(m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations); + passResult = propagator.run(function, error); + if (error->isValid()) + return compileError(); + + QQmlJSShadowCheck shadowCheck(m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations); + passResult = shadowCheck.run(function, error); + if (error->isValid()) + return compileError(); + + QQmlJSOptimizations optimizer(m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations, + basicBlocks.objectAndArrayDefinitions()); + passResult = optimizer.run(function, error); + if (error->isValid()) + return compileError(); + + QQmlJSStorageInitializer initializer( + m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations); + passResult = initializer.run(function, error); + + // Generalize all arguments, registers, and the return type. + QQmlJSStorageGeneralizer generalizer( + m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations); + passResult = generalizer.run(function, error); + if (error->isValid()) + return compileError(); + + QQmlJSCodeGenerator codegen(context, m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations); + QQmlJSAotFunction result = codegen.run(function, error, basicBlocksValidationFailed); + return error->isValid() ? compileError() : result; +} + +QQmlJSAotFunction QQmlJSAotCompiler::doCompileAndRecordAotStats( + const QV4::Compiler::Context *context, QQmlJSCompilePass::Function *function, + QQmlJS::DiagnosticMessage *error, const QString &name, QQmlJS::SourceLocation location) +{ + auto t1 = std::chrono::high_resolution_clock::now(); + QQmlJSAotFunction result = doCompile(context, function, error); + auto t2 = std::chrono::high_resolution_clock::now(); + + if (QQmlJS::QQmlJSAotCompilerStats::recordAotStats()) { + QQmlJS::AotStatsEntry entry; + entry.codegenDuration = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1); + entry.functionName = name; + entry.errorMessage = error->message; + entry.line = location.startLine; + entry.column = location.startColumn; + entry.codegenSuccessful = !error->isValid(); + QQmlJS::QQmlJSAotCompilerStats::addEntry( + function->qmlScope.containedType()->filePath(), entry); + } + + return result; +} + +QT_END_NAMESPACE |