1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "qqmljscompiler_p.h"
5
6#include <private/qqmlirbuilder_p.h>
7#include <private/qqmljsbasicblocks_p.h>
8#include <private/qqmljscodegenerator_p.h>
9#include <private/qqmljscompilerstats_p.h>
10#include <private/qqmljsfunctioninitializer_p.h>
11#include <private/qqmljsimportvisitor_p.h>
12#include <private/qqmljslexer_p.h>
13#include <private/qqmljsloadergenerator_p.h>
14#include <private/qqmljsoptimizations_p.h>
15#include <private/qqmljsparser_p.h>
16#include <private/qqmljsshadowcheck_p.h>
17#include <private/qqmljsstoragegeneralizer_p.h>
18#include <private/qqmljsstorageinitializer_p.h>
19#include <private/qqmljstypepropagator_p.h>
20
21#include <QtCore/qfile.h>
22#include <QtCore/qfileinfo.h>
23#include <QtCore/qloggingcategory.h>
24
25#include <QtQml/private/qqmlsignalnames_p.h>
26
27#include <limits>
28
29QT_BEGIN_NAMESPACE
30
31using namespace Qt::StringLiterals;
32
33Q_LOGGING_CATEGORY(lcAotCompiler, "qt.qml.compiler.aot", QtFatalMsg);
34
35static const int FileScopeCodeIndex = -1;
36
37void QQmlJSCompileError::print()
38{
39 fprintf(stderr, format: "%s\n", qPrintable(message));
40}
41
42QQmlJSCompileError QQmlJSCompileError::augment(const QString &contextErrorMessage) const
43{
44 QQmlJSCompileError augmented;
45 augmented.message = contextErrorMessage + message;
46 return augmented;
47}
48
49static QString diagnosticErrorMessage(const QString &fileName, const QQmlJS::DiagnosticMessage &m)
50{
51 QString message;
52 message = fileName + QLatin1Char(':') + QString::number(m.loc.startLine) + QLatin1Char(':');
53 if (m.loc.startColumn > 0)
54 message += QString::number(m.loc.startColumn) + QLatin1Char(':');
55
56 if (m.isError())
57 message += QLatin1String(" error: ");
58 else
59 message += QLatin1String(" warning: ");
60 message += m.message;
61 return message;
62}
63
64void QQmlJSCompileError::appendDiagnostic(const QString &inputFileName,
65 const QQmlJS::DiagnosticMessage &diagnostic)
66{
67 if (!message.isEmpty())
68 message += QLatin1Char('\n');
69 message += diagnosticErrorMessage(fileName: inputFileName, m: diagnostic);
70}
71
72void QQmlJSCompileError::appendDiagnostics(const QString &inputFileName,
73 const QList<QQmlJS::DiagnosticMessage> &diagnostics)
74{
75 for (const QQmlJS::DiagnosticMessage &diagnostic: diagnostics)
76 appendDiagnostic(inputFileName, diagnostic);
77}
78
79// Ensure that ListElement objects keep all property assignments in their string form
80static void annotateListElements(QmlIR::Document *document)
81{
82 QStringList listElementNames;
83
84 for (const QV4::CompiledData::Import *import : std::as_const(t&: document->imports)) {
85 const QString uri = document->stringAt(index: import->uriIndex);
86 if (uri != QStringLiteral("QtQml.Models") && uri != QStringLiteral("QtQuick"))
87 continue;
88
89 QString listElementName = QStringLiteral("ListElement");
90 const QString qualifier = document->stringAt(index: import->qualifierIndex);
91 if (!qualifier.isEmpty()) {
92 listElementName.prepend(c: QLatin1Char('.'));
93 listElementName.prepend(s: qualifier);
94 }
95 listElementNames.append(t: listElementName);
96 }
97
98 if (listElementNames.isEmpty())
99 return;
100
101 for (QmlIR::Object *object : std::as_const(t&: document->objects)) {
102 if (!listElementNames.contains(str: document->stringAt(index: object->inheritedTypeNameIndex)))
103 continue;
104 for (QmlIR::Binding *binding = object->firstBinding(); binding; binding = binding->next) {
105 if (binding->type() != QV4::CompiledData::Binding::Type_Script)
106 continue;
107 binding->stringIndex = document->registerString(str: object->bindingAsString(doc: document, scriptIndex: binding->value.compiledScriptIndex));
108 }
109 }
110}
111
112static bool checkArgumentsObjectUseInSignalHandlers(const QmlIR::Document &doc,
113 QQmlJSCompileError *error)
114{
115 for (QmlIR::Object *object: std::as_const(t: doc.objects)) {
116 for (auto binding = object->bindingsBegin(); binding != object->bindingsEnd(); ++binding) {
117 if (binding->type() != QV4::CompiledData::Binding::Type_Script)
118 continue;
119 const QString propName = doc.stringAt(index: binding->propertyNameIndex);
120 if (!QQmlSignalNames::isHandlerName(signalName: propName))
121 continue;
122 auto compiledFunction = doc.jsModule.functions.value(i: object->runtimeFunctionIndices.at(index: binding->value.compiledScriptIndex));
123 if (!compiledFunction)
124 continue;
125 if (compiledFunction->usesArgumentsObject == QV4::Compiler::Context::UsesArgumentsObject::Used) {
126 error->message = QLatin1Char(':') + QString::number(compiledFunction->line) + QLatin1Char(':');
127 if (compiledFunction->column > 0)
128 error->message += QString::number(compiledFunction->column) + QLatin1Char(':');
129
130 error->message += QLatin1String(" error: The use of eval() or the use of the arguments object in signal handlers is\n"
131 "not supported when compiling qml files ahead of time. That is because it's ambiguous if \n"
132 "any signal parameter is called \"arguments\". Similarly the string passed to eval might use\n"
133 "\"arguments\". Unfortunately we cannot distinguish between it being a parameter or the\n"
134 "JavaScript arguments object at this point.\n"
135 "Consider renaming the parameter of the signal if applicable or moving the code into a\n"
136 "helper function.");
137 return false;
138 }
139 }
140 }
141 return true;
142}
143
144class BindingOrFunction
145{
146public:
147 BindingOrFunction(const QmlIR::Binding &b) : m_binding(&b) {}
148 BindingOrFunction(const QmlIR::Function &f) : m_function(&f) {}
149
150 friend bool operator<(const BindingOrFunction &lhs, const BindingOrFunction &rhs)
151 {
152 return lhs.index() < rhs.index();
153 }
154
155 const QmlIR::Binding *binding() const { return m_binding; }
156 const QmlIR::Function *function() const { return m_function; }
157
158 quint32 index() const
159 {
160 return m_binding
161 ? m_binding->value.compiledScriptIndex
162 : (m_function
163 ? m_function->index
164 : std::numeric_limits<quint32>::max());
165 }
166
167private:
168 const QmlIR::Binding *m_binding = nullptr;
169 const QmlIR::Function *m_function = nullptr;
170};
171
172bool qCompileQmlFile(const QString &inputFileName, QQmlJSSaveFunction saveFunction,
173 QQmlJSAotCompiler *aotCompiler, QQmlJSCompileError *error,
174 bool storeSourceLocation, QV4::Compiler::CodegenWarningInterface *wInterface,
175 const QString *fileContents)
176{
177 QmlIR::Document irDocument(QString(), QString(), /*debugMode*/false);
178 return qCompileQmlFile(irDocument, inputFileName, saveFunction, aotCompiler, error,
179 storeSourceLocation, wInterface, fileContents);
180}
181
182bool qCompileQmlFile(QmlIR::Document &irDocument, const QString &inputFileName,
183 QQmlJSSaveFunction saveFunction, QQmlJSAotCompiler *aotCompiler,
184 QQmlJSCompileError *error, bool storeSourceLocation,
185 QV4::Compiler::CodegenWarningInterface *wInterface, const QString *fileContents)
186{
187 QString sourceCode;
188
189 if (fileContents != nullptr) {
190 sourceCode = *fileContents;
191 } else {
192 QFile f(inputFileName);
193 if (!f.open(flags: QIODevice::ReadOnly)) {
194 error->message = QLatin1String("Error opening ") + inputFileName + QLatin1Char(':') + f.errorString();
195 return false;
196 }
197 sourceCode = QString::fromUtf8(ba: f.readAll());
198 if (f.error() != QFileDevice::NoError) {
199 error->message = QLatin1String("Error reading from ") + inputFileName + QLatin1Char(':') + f.errorString();
200 return false;
201 }
202 }
203
204 {
205 QmlIR::IRBuilder irBuilder;
206 if (!irBuilder.generateFromQml(code: sourceCode, url: inputFileName, output: &irDocument)) {
207 error->appendDiagnostics(inputFileName, diagnostics: irBuilder.errors);
208 return false;
209 }
210 }
211
212 annotateListElements(document: &irDocument);
213 QQmlJSAotFunctionMap aotFunctionsByIndex;
214
215 {
216 QmlIR::JSCodeGen v4CodeGen(&irDocument, wInterface, storeSourceLocation);
217
218 if (aotCompiler)
219 aotCompiler->setDocument(codegen: &v4CodeGen, document: &irDocument);
220
221 QHash<QmlIR::Object *, QmlIR::Object *> effectiveScopes;
222 for (QmlIR::Object *object: std::as_const(t&: irDocument.objects)) {
223 if (object->functionsAndExpressions->count == 0 && object->bindingCount() == 0)
224 continue;
225
226 if (!v4CodeGen.generateRuntimeFunctions(object)) {
227 Q_ASSERT(v4CodeGen.hasError());
228 error->appendDiagnostic(inputFileName, diagnostic: v4CodeGen.error());
229 return false;
230 }
231
232 if (!aotCompiler)
233 continue;
234
235 QmlIR::Object *scope = object;
236 for (auto it = effectiveScopes.constFind(key: scope), end = effectiveScopes.constEnd();
237 it != end; it = effectiveScopes.constFind(key: scope)) {
238 scope = *it;
239 }
240
241 aotCompiler->setScope(object, scope);
242 aotFunctionsByIndex[FileScopeCodeIndex] = aotCompiler->globalCode();
243
244 std::vector<BindingOrFunction> bindingsAndFunctions;
245 bindingsAndFunctions.reserve(n: object->bindingCount() + object->functionCount());
246
247 std::copy(first: object->bindingsBegin(), last: object->bindingsEnd(),
248 result: std::back_inserter(x&: bindingsAndFunctions));
249 std::copy(first: object->functionsBegin(), last: object->functionsEnd(),
250 result: std::back_inserter(x&: bindingsAndFunctions));
251
252 QList<QmlIR::CompiledFunctionOrExpression> functionsToCompile;
253 for (QmlIR::CompiledFunctionOrExpression *foe = object->functionsAndExpressions->first;
254 foe; foe = foe->next) {
255 functionsToCompile << *foe;
256 }
257
258 // AOT-compile bindings and functions in the same order as above so that the runtime
259 // class indices match
260 auto contextMap = v4CodeGen.module()->contextMap;
261 std::sort(first: bindingsAndFunctions.begin(), last: bindingsAndFunctions.end());
262 std::for_each(first: bindingsAndFunctions.begin(), last: bindingsAndFunctions.end(),
263 f: [&](const BindingOrFunction &bindingOrFunction) {
264 std::variant<QQmlJSAotFunction, QList<QQmlJS::DiagnosticMessage>> result;
265 if (const auto *binding = bindingOrFunction.binding()) {
266 switch (binding->type()) {
267 case QmlIR::Binding::Type_AttachedProperty:
268 case QmlIR::Binding::Type_GroupProperty:
269 effectiveScopes.insert(
270 key: irDocument.objects.at(i: binding->value.objectIndex), value: scope);
271 return;
272 case QmlIR::Binding::Type_Boolean:
273 case QmlIR::Binding::Type_Number:
274 case QmlIR::Binding::Type_String:
275 case QmlIR::Binding::Type_Null:
276 case QmlIR::Binding::Type_Object:
277 case QmlIR::Binding::Type_Translation:
278 case QmlIR::Binding::Type_TranslationById:
279 return;
280 default:
281 break;
282 }
283
284 Q_ASSERT(quint32(functionsToCompile.size()) > binding->value.compiledScriptIndex);
285 const auto &functionToCompile
286 = functionsToCompile[binding->value.compiledScriptIndex];
287 auto *parentNode = functionToCompile.parentNode;
288 Q_ASSERT(parentNode);
289 Q_ASSERT(contextMap.contains(parentNode));
290 QV4::Compiler::Context *context = contextMap.take(key: parentNode);
291 Q_ASSERT(context);
292
293 auto *node = functionToCompile.node;
294 Q_ASSERT(node);
295
296 if (context->returnsClosure) {
297 QQmlJS::AST::Node *inner
298 = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement *>(
299 ast: node)->expression;
300 Q_ASSERT(inner);
301 QV4::Compiler::Context *innerContext = contextMap.take(key: inner);
302 Q_ASSERT(innerContext);
303 qCDebug(lcAotCompiler) << "Compiling signal handler for"
304 << irDocument.stringAt(index: binding->propertyNameIndex);
305 std::variant<QQmlJSAotFunction, QList<QQmlJS::DiagnosticMessage>> innerResult
306 = aotCompiler->compileBinding(context: innerContext, irBinding: *binding, astNode: inner);
307 if (auto *errors = std::get_if<QList<QQmlJS::DiagnosticMessage>>(ptr: &innerResult)) {
308 for (const auto &error : std::as_const(t&: *errors)) {
309 qCDebug(lcAotCompiler) << "Compilation failed:"
310 << diagnosticErrorMessage(fileName: inputFileName, m: error);
311 }
312 } else if (auto *func = std::get_if<QQmlJSAotFunction>(ptr: &innerResult)) {
313 qCDebug(lcAotCompiler) << "Generated code:" << func->code;
314 aotFunctionsByIndex[innerContext->functionIndex] = *func;
315 }
316 }
317
318 qCDebug(lcAotCompiler) << "Compiling binding for property"
319 << irDocument.stringAt(index: binding->propertyNameIndex);
320 result = aotCompiler->compileBinding(context, irBinding: *binding, astNode: node);
321 } else if (const auto *function = bindingOrFunction.function()) {
322 Q_ASSERT(quint32(functionsToCompile.size()) > function->index);
323 auto *node = functionsToCompile[function->index].node;
324 Q_ASSERT(node);
325 Q_ASSERT(contextMap.contains(node));
326 QV4::Compiler::Context *context = contextMap.take(key: node);
327 Q_ASSERT(context);
328
329 const QString functionName = irDocument.stringAt(index: function->nameIndex);
330 qCDebug(lcAotCompiler) << "Compiling function" << functionName;
331 result = aotCompiler->compileFunction(context, name: functionName, astNode: node);
332 } else {
333 Q_UNREACHABLE();
334 }
335
336 if (auto *errors = std::get_if<QList<QQmlJS::DiagnosticMessage>>(ptr: &result)) {
337 for (const auto &error : std::as_const(t&: *errors)) {
338 qCDebug(lcAotCompiler) << "Compilation failed:"
339 << diagnosticErrorMessage(fileName: inputFileName, m: error);
340 }
341 } else if (auto *func = std::get_if<QQmlJSAotFunction>(ptr: &result)) {
342 if (func->skipReason.has_value()) {
343 qCDebug(lcAotCompiler) << "Compilation skipped:" << func->skipReason.value();
344 } else {
345 qCDebug(lcAotCompiler) << "Generated code:" << func->code;
346 auto index = object->runtimeFunctionIndices[bindingOrFunction.index()];
347 aotFunctionsByIndex[index] = *func;
348 }
349 }
350 });
351 }
352
353 if (!checkArgumentsObjectUseInSignalHandlers(doc: irDocument, error)) {
354 *error = error->augment(contextErrorMessage: inputFileName);
355 return false;
356 }
357
358 QmlIR::QmlUnitGenerator generator;
359 irDocument.javaScriptCompilationUnit = v4CodeGen.generateCompilationUnit(/*generate unit*/generateUnitData: false);
360 generator.generate(output&: irDocument);
361
362 const quint32 saveFlags
363 = QV4::CompiledData::Unit::StaticData
364 | QV4::CompiledData::Unit::PendingTypeCompilation;
365 QV4::CompiledData::SaveableUnitPointer saveable(
366 irDocument.javaScriptCompilationUnit->unitData(), saveFlags);
367 if (!saveFunction(saveable, aotFunctionsByIndex, &error->message))
368 return false;
369 }
370 return true;
371}
372
373bool qCompileJSFile(
374 const QString &inputFileName, const QString &inputFileUrl, QQmlJSSaveFunction saveFunction,
375 QQmlJSCompileError *error)
376{
377 Q_UNUSED(inputFileUrl);
378
379 QQmlRefPointer<QV4::CompiledData::CompilationUnit> unit;
380
381 QString sourceCode;
382 {
383 QFile f(inputFileName);
384 if (!f.open(flags: QIODevice::ReadOnly)) {
385 error->message = QLatin1String("Error opening ") + inputFileName + QLatin1Char(':') + f.errorString();
386 return false;
387 }
388 sourceCode = QString::fromUtf8(ba: f.readAll());
389 if (f.error() != QFileDevice::NoError) {
390 error->message = QLatin1String("Error reading from ") + inputFileName + QLatin1Char(':') + f.errorString();
391 return false;
392 }
393 }
394
395 const bool isModule = inputFileName.endsWith(s: QLatin1String(".mjs"));
396 if (isModule) {
397 QList<QQmlJS::DiagnosticMessage> diagnostics;
398 // Precompiled files are relocatable and the final location will be set when loading.
399 QString url;
400 unit = QV4::Compiler::Codegen::compileModule(/*debugMode*/false, url, sourceCode,
401 sourceTimeStamp: QDateTime(), diagnostics: &diagnostics);
402 error->appendDiagnostics(inputFileName, diagnostics);
403 if (!unit || !unit->unitData())
404 return false;
405 } else {
406 QmlIR::Document irDocument(QString(), QString(), /*debugMode*/false);
407
408 QQmlJS::Engine *engine = &irDocument.jsParserEngine;
409 QmlIR::ScriptDirectivesCollector directivesCollector(&irDocument);
410 QQmlJS::Directives *oldDirs = engine->directives();
411 engine->setDirectives(&directivesCollector);
412 auto directivesGuard = qScopeGuard(f: [engine, oldDirs]{
413 engine->setDirectives(oldDirs);
414 });
415
416 QQmlJS::AST::Program *program = nullptr;
417
418 {
419 QQmlJS::Lexer lexer(engine);
420 lexer.setCode(code: sourceCode, /*line*/lineno: 1, /*parseAsBinding*/qmlMode: false);
421 QQmlJS::Parser parser(engine);
422
423 bool parsed = parser.parseProgram();
424
425 error->appendDiagnostics(inputFileName, diagnostics: parser.diagnosticMessages());
426
427 if (!parsed)
428 return false;
429
430 program = QQmlJS::AST::cast<QQmlJS::AST::Program*>(parser.rootNode());
431 if (!program) {
432 lexer.setCode(QStringLiteral("undefined;"), lineno: 1, qmlMode: false);
433 parsed = parser.parseProgram();
434 Q_ASSERT(parsed);
435 program = QQmlJS::AST::cast<QQmlJS::AST::Program*>(parser.rootNode());
436 Q_ASSERT(program);
437 }
438 }
439
440 {
441 QmlIR::JSCodeGen v4CodeGen(&irDocument);
442 v4CodeGen.generateFromProgram(
443 sourceCode, ast: program, module: &irDocument.jsModule,
444 contextType: QV4::Compiler::ContextType::ScriptImportedByQML);
445 if (v4CodeGen.hasError()) {
446 error->appendDiagnostic(inputFileName, diagnostic: v4CodeGen.error());
447 return false;
448 }
449
450 // Precompiled files are relocatable and the final location will be set when loading.
451 Q_ASSERT(irDocument.jsModule.fileName.isEmpty());
452 Q_ASSERT(irDocument.jsModule.finalUrl.isEmpty());
453
454 irDocument.javaScriptCompilationUnit = v4CodeGen.generateCompilationUnit(/*generate unit*/generateUnitData: false);
455 QmlIR::QmlUnitGenerator generator;
456 generator.generate(output&: irDocument);
457 unit = std::move(irDocument.javaScriptCompilationUnit);
458 }
459 }
460
461 QQmlJSAotFunctionMap empty;
462 return saveFunction(
463 QV4::CompiledData::SaveableUnitPointer(unit->unitData()), empty, &error->message);
464}
465
466static const char *funcHeaderCode = R"(
467 [](const QQmlPrivate::AOTCompiledContext *aotContext, void **argv) {
468Q_UNUSED(aotContext)
469Q_UNUSED(argv)
470)";
471
472bool qSaveQmlJSUnitAsCpp(const QString &inputFileName, const QString &outputFileName, const QV4::CompiledData::SaveableUnitPointer &unit, const QQmlJSAotFunctionMap &aotFunctions, QString *errorString)
473{
474#if QT_CONFIG(temporaryfile)
475 QSaveFile f(outputFileName);
476#else
477 QFile f(outputFileName);
478#endif
479 if (!f.open(flags: QIODevice::WriteOnly | QIODevice::Truncate)) {
480 *errorString = f.errorString();
481 return false;
482 }
483
484 auto writeStr = [&f, errorString](const QByteArray &data) {
485 if (f.write(data) != data.size()) {
486 *errorString = f.errorString();
487 return false;
488 }
489 return true;
490 };
491
492 if (!writeStr("// "))
493 return false;
494
495 if (!writeStr(inputFileName.toUtf8()))
496 return false;
497
498 if (!writeStr("\n"))
499 return false;
500
501 if (!writeStr("#include <QtQml/qqmlprivate.h>\n"))
502 return false;
503
504 if (!aotFunctions.isEmpty()) {
505 QStringList includes;
506
507 for (const auto &function : aotFunctions)
508 includes.append(l: function.includes);
509
510 std::sort(first: includes.begin(), last: includes.end());
511 const auto end = std::unique(first: includes.begin(), last: includes.end());
512 for (auto it = includes.begin(); it != end; ++it) {
513 if (!writeStr(QStringLiteral("#include <%1>\n").arg(a: *it).toUtf8()))
514 return false;
515 }
516 }
517
518 if (!writeStr(QByteArrayLiteral("namespace QmlCacheGeneratedCode {\nnamespace ")))
519 return false;
520
521 if (!writeStr(qQmlJSSymbolNamespaceForPath(relativePath: inputFileName).toUtf8()))
522 return false;
523
524 if (!writeStr(QByteArrayLiteral(" {\nextern const unsigned char qmlData alignas(16) [];\n"
525 "extern const unsigned char qmlData alignas(16) [] = {\n")))
526 return false;
527
528 unit.saveToDisk<uchar>(writer: [&writeStr](const uchar *begin, quint32 size) {
529 QByteArray hexifiedData;
530 {
531 QTextStream stream(&hexifiedData);
532 const uchar *end = begin + size;
533 stream << Qt::hex;
534 int col = 0;
535 for (const uchar *data = begin; data < end; ++data, ++col) {
536 if (data > begin)
537 stream << ',';
538 if (col % 8 == 0) {
539 stream << '\n';
540 col = 0;
541 }
542 stream << "0x" << *data;
543 }
544 stream << '\n';
545 }
546 return writeStr(hexifiedData);
547 });
548
549
550
551 if (!writeStr("};\n"))
552 return false;
553
554 // Suppress the following warning generated by MSVC 2019:
555 // "the usage of 'QJSNumberCoercion::toInteger' requires the compiler to capture 'this'
556 // but the current default capture mode does not allow it"
557 // You clearly don't have to capture 'this' in order to call 'QJSNumberCoercion::toInteger'.
558 // TODO: Remove when we don't have to support MSVC 2019 anymore. Mind the QT_WARNING_POP below.
559 if (!writeStr("QT_WARNING_PUSH\nQT_WARNING_DISABLE_MSVC(4573)\n"))
560 return false;
561
562 writeStr(aotFunctions[FileScopeCodeIndex].code.toUtf8().constData());
563 if (aotFunctions.size() <= 1) {
564 // FileScopeCodeIndex is always there, but it may be the only one.
565 writeStr("extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[];\n"
566 "extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[] = { { 0, 0, nullptr, nullptr } };\n");
567 } else {
568 writeStr("extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[];\n"
569 "extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[] = {\n");
570
571 QString footer = QStringLiteral("}\n");
572
573 for (QQmlJSAotFunctionMap::ConstIterator func = aotFunctions.constBegin(),
574 end = aotFunctions.constEnd();
575 func != end; ++func) {
576
577 if (func.key() == FileScopeCodeIndex)
578 continue;
579
580 const QString function = QString::fromUtf8(utf8: funcHeaderCode) + func.value().code + footer;
581
582 writeStr(QStringLiteral("{ %1, %2, [](QV4::ExecutableCompilationUnit *contextUnit, "
583 "QMetaType *argTypes) {\n%3}, %4 },")
584 .arg(a: func.key())
585 .arg(a: func->numArguments)
586 .arg(args: func->signature, args: function)
587 .toUtf8().constData());
588 }
589
590 // Conclude the list with a nullptr
591 writeStr("{ 0, 0, nullptr, nullptr }");
592 writeStr("};\n");
593 }
594
595 if (!writeStr("QT_WARNING_POP\n"))
596 return false;
597
598 if (!writeStr("}\n}\n"))
599 return false;
600
601#if QT_CONFIG(temporaryfile)
602 if (!f.commit()) {
603 *errorString = f.errorString();
604 return false;
605 }
606#endif
607
608 return true;
609}
610
611QQmlJSAotCompiler::QQmlJSAotCompiler(
612 QQmlJSImporter *importer, const QString &resourcePath, const QStringList &qmldirFiles,
613 QQmlJSLogger *logger)
614 : m_typeResolver(importer)
615 , m_resourcePath(resourcePath)
616 , m_qmldirFiles(qmldirFiles)
617 , m_importer(importer)
618 , m_logger(logger)
619{
620}
621
622void QQmlJSAotCompiler::setDocument(
623 const QmlIR::JSCodeGen *codegen, const QmlIR::Document *irDocument)
624{
625 Q_UNUSED(codegen);
626 m_document = irDocument;
627 const QFileInfo resourcePathInfo(m_resourcePath);
628 if (m_logger->filePath().isEmpty())
629 m_logger->setFilePath(resourcePathInfo.fileName());
630 m_logger->setCode(irDocument->code);
631 m_unitGenerator = &irDocument->jsGenerator;
632 QQmlJSScope::Ptr target = QQmlJSScope::create();
633 QQmlJSImportVisitor visitor(target, m_importer, m_logger,
634 resourcePathInfo.canonicalPath() + u'/',
635 m_qmldirFiles);
636 m_typeResolver.init(visitor: &visitor, program: irDocument->program);
637}
638
639void QQmlJSAotCompiler::setScope(const QmlIR::Object *object, const QmlIR::Object *scope)
640{
641 m_currentObject = object;
642 m_currentScope = scope;
643}
644
645static bool isStrict(const QmlIR::Document *doc)
646{
647 for (const QmlIR::Pragma *pragma : doc->pragmas) {
648 if (pragma->type == QmlIR::Pragma::Strict)
649 return true;
650 }
651 return false;
652}
653
654QQmlJS::DiagnosticMessage QQmlJSAotCompiler::diagnose(
655 const QString &message, QtMsgType type, const QQmlJS::SourceLocation &location) const
656{
657 if (isStrict(doc: m_document)
658 && (type == QtWarningMsg || type == QtCriticalMsg || type == QtFatalMsg)
659 && m_logger->isCategoryFatal(id: qmlCompiler)) {
660 qFatal(msg: "%s:%d: (strict mode) %s",
661 qPrintable(QFileInfo(m_resourcePath).fileName()),
662 location.startLine, qPrintable(message));
663 }
664
665 return QQmlJS::DiagnosticMessage {
666 .message: message,
667 .type: type,
668 .loc: location
669 };
670}
671
672std::variant<QQmlJSAotFunction, QList<QQmlJS::DiagnosticMessage>> QQmlJSAotCompiler::compileBinding(
673 const QV4::Compiler::Context *context, const QmlIR::Binding &irBinding,
674 QQmlJS::AST::Node *astNode)
675{
676 QQmlJSFunctionInitializer initializer(
677 &m_typeResolver, m_currentObject->location, m_currentScope->location, m_logger);
678
679 const QString name = m_document->stringAt(index: irBinding.propertyNameIndex);
680 QQmlJSCompilePass::Function function = initializer.run( context, propertyName: name, astNode, irBinding);
681
682 const QQmlJSAotFunction aotFunction = doCompileAndRecordAotStats(
683 context, function: &function, name, location: astNode->firstSourceLocation());
684
685 if (const auto errors = finalizeBindingOrFunction())
686 return *errors;
687
688 qCDebug(lcAotCompiler()) << "includes:" << aotFunction.includes;
689 qCDebug(lcAotCompiler()) << "binding code:" << aotFunction.code;
690 return aotFunction;
691}
692
693std::variant<QQmlJSAotFunction, QList<QQmlJS::DiagnosticMessage>> QQmlJSAotCompiler::compileFunction(
694 const QV4::Compiler::Context *context, const QString &name, QQmlJS::AST::Node *astNode)
695{
696 QQmlJSFunctionInitializer initializer(
697 &m_typeResolver, m_currentObject->location, m_currentScope->location, m_logger);
698 QQmlJSCompilePass::Function function = initializer.run(context, functionName: name, astNode);
699
700 const QQmlJSAotFunction aotFunction = doCompileAndRecordAotStats(
701 context, function: &function, name, location: astNode->firstSourceLocation());
702
703 if (const auto errors = finalizeBindingOrFunction())
704 return *errors;
705
706 qCDebug(lcAotCompiler()) << "includes:" << aotFunction.includes;
707 qCDebug(lcAotCompiler()) << "binding code:" << aotFunction.code;
708 return aotFunction;
709}
710
711QQmlJSAotFunction QQmlJSAotCompiler::globalCode() const
712{
713 QQmlJSAotFunction global;
714 global.includes = {
715 u"QtQml/qjsengine.h"_s,
716 u"QtQml/qjsprimitivevalue.h"_s,
717 u"QtQml/qjsvalue.h"_s,
718 u"QtQml/qqmlcomponent.h"_s,
719 u"QtQml/qqmlcontext.h"_s,
720 u"QtQml/qqmlengine.h"_s,
721 u"QtQml/qqmllist.h"_s,
722
723 u"QtCore/qdatetime.h"_s,
724 u"QtCore/qtimezone.h"_s,
725 u"QtCore/qobject.h"_s,
726 u"QtCore/qstring.h"_s,
727 u"QtCore/qstringlist.h"_s,
728 u"QtCore/qurl.h"_s,
729 u"QtCore/qvariant.h"_s,
730
731 u"type_traits"_s
732 };
733 return global;
734}
735
736std::optional<QList<QQmlJS::DiagnosticMessage>> QQmlJSAotCompiler::finalizeBindingOrFunction()
737{
738 const auto archiveMessages = qScopeGuard(f: [this]() { m_logger->finalizeFuction(); });
739
740 if (!m_logger->currentFunctionHasCompileError())
741 return {};
742
743 QList<QQmlJS::DiagnosticMessage> errors;
744 m_logger->iterateCurrentFunctionMessages(f: [&](const Message &msg) {
745 if (msg.compilationStatus == Message::CompilationStatus::Error)
746 errors.append(t: diagnose(message: msg.message, type: msg.type, location: msg.loc));
747 });
748 return errors;
749}
750
751QQmlJSAotFunction QQmlJSAotCompiler::doCompile(
752 const QV4::Compiler::Context *context, QQmlJSCompilePass::Function *function)
753{
754 if (m_logger->currentFunctionHasErrorOrSkip())
755 return QQmlJSAotFunction();
756
757 bool basicBlocksValidationFailed = false;
758 QQmlJSBasicBlocks basicBlocks(context, m_unitGenerator, &m_typeResolver, m_logger);
759 auto passResult = basicBlocks.run(function, compileFlags: m_flags, basicBlocksValidationFailed);
760 auto &[blocks, annotations] = passResult;
761
762 QQmlJSTypePropagator propagator(
763 m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations);
764 passResult = propagator.run(m_function: function);
765 if (m_logger->currentFunctionHasErrorOrSkip())
766 return QQmlJSAotFunction();
767
768 QQmlJSShadowCheck shadowCheck(
769 m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations);
770 passResult = shadowCheck.run(function);
771 if (m_logger->currentFunctionHasErrorOrSkip())
772 return QQmlJSAotFunction();
773
774 QQmlJSOptimizations optimizer(
775 m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations,
776 basicBlocks.objectAndArrayDefinitions());
777 passResult = optimizer.run(function);
778 if (m_logger->currentFunctionHasErrorOrSkip())
779 return QQmlJSAotFunction();
780
781 QQmlJSStorageInitializer initializer(
782 m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations);
783 passResult = initializer.run(function);
784
785 // Generalize all arguments, registers, and the return type.
786 QQmlJSStorageGeneralizer generalizer(
787 m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations);
788 passResult = generalizer.run(function);
789 if (m_logger->currentFunctionHasErrorOrSkip())
790 return QQmlJSAotFunction();
791
792 QQmlJSCodeGenerator codegen(
793 context, m_unitGenerator, &m_typeResolver, m_logger, blocks, annotations);
794 QQmlJSAotFunction result = codegen.run(function, basicBlocksValidationFailed);
795 if (m_logger->currentFunctionHasErrorOrSkip())
796 return QQmlJSAotFunction();
797
798 return result;
799}
800
801QQmlJSAotFunction QQmlJSAotCompiler::doCompileAndRecordAotStats(
802 const QV4::Compiler::Context *context, QQmlJSCompilePass::Function *function,
803 const QString &name, QQmlJS::SourceLocation location)
804{
805 auto t1 = std::chrono::high_resolution_clock::now();
806 QQmlJSAotFunction result;
807 if (!m_logger->currentFunctionHasCompileError())
808 result = doCompile(context, function);
809 auto t2 = std::chrono::high_resolution_clock::now();
810
811 if (QQmlJS::QQmlJSAotCompilerStats::recordAotStats()) {
812 QQmlJS::AotStatsEntry entry;
813 entry.codegenDuration = std::chrono::duration_cast<std::chrono::microseconds>(d: t2 - t1);
814 entry.functionName = name;
815 entry.message = m_logger->currentFunctionWasSkipped()
816 ? m_logger->currentFunctionCompileSkipMessage()
817 : m_logger->currentFunctionCompileErrorMessage();
818 entry.line = location.startLine;
819 entry.column = location.startColumn;
820 if (m_logger->currentFunctionWasSkipped())
821 entry.codegenResult = QQmlJS::CodegenResult::Skip;
822 else if (m_logger->currentFunctionHasCompileError())
823 entry.codegenResult = QQmlJS::CodegenResult::Failure;
824 else
825 entry.codegenResult = QQmlJS::CodegenResult::Success;
826 QQmlJS::QQmlJSAotCompilerStats::addEntry(
827 filepath: function->qmlScope.containedType()->filePath(), entry);
828 }
829
830 if (m_logger->currentFunctionWasSkipped())
831 result.skipReason = m_logger->currentFunctionCompileSkipMessage();
832
833 return result;
834}
835
836QT_END_NAMESPACE
837

source code of qtdeclarative/src/qmlcompiler/qqmljscompiler.cpp