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