Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmljscodegenerator.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:critical reason:code-generation
4
10
11#include <private/qqmljstypepropagator_p.h>
12
13#include <private/qqmlirbuilder_p.h>
14#include <private/qqmljsscope_p.h>
15#include <private/qqmljsutils_p.h>
16#include <private/qv4compilerscanfunctions_p.h>
17#include <private/qduplicatetracker_p.h>
18
19#include <QtCore/qdir.h>
20#include <QtCore/qfileinfo.h>
21
23
24using namespace Qt::StringLiterals;
25
26/*!
27 * \internal
28 * \class QQmlJSCodeGenerator
29 *
30 * This is a final compile pass that generates C++ code from a function and the
31 * annotations produced by previous passes. Such annotations are produced by
32 * QQmlJSTypePropagator, and possibly amended by other passes.
33 */
34
35#define BYTECODE_UNIMPLEMENTED() Q_ASSERT_X(false, Q_FUNC_INFO, "not implemented");
36
37#define INJECT_TRACE_INFO(function)
38 static const bool injectTraceInfo = true;
39 if (injectTraceInfo) {
40 m_body += u"// "_s + QStringLiteral(#function) + u'\n';
41 }
42
43#define REJECT
44 return reject
45
46static bool isTypeStorable(const QQmlJSTypeResolver *resolver, const QQmlJSScope::ConstPtr &type)
47{
48 return !type.isNull() && type != resolver->nullType() && type != resolver->voidType();
49}
50
51QString QQmlJSCodeGenerator::castTargetName(const QQmlJSScope::ConstPtr &type) const
52{
53 return type->augmentedInternalName();
54}
55
56QQmlJSCodeGenerator::QQmlJSCodeGenerator(
57 const QV4::Compiler::Context *compilerContext,
58 const QV4::Compiler::JSUnitGenerator *unitGenerator, const QQmlJSTypeResolver *typeResolver,
59 QQmlJSLogger *logger, const BasicBlocks &basicBlocks,
60 const InstructionAnnotations &annotations, bool noAotValidation)
61 : QQmlJSCompilePass(unitGenerator, typeResolver, logger, basicBlocks, annotations)
62 , m_context(compilerContext)
63 , m_lookupSignaturesRecorder(logger->filePath(), typeResolver)
64 , m_noAotValidation(noAotValidation)
65{}
66
67QString QQmlJSCodeGenerator::metaTypeFromType(const QQmlJSScope::ConstPtr &type) const
68{
69 return u"QMetaType::fromType<"_s + type->augmentedInternalName() + u">()"_s;
70}
71
72QString QQmlJSCodeGenerator::metaTypeFromName(const QQmlJSScope::ConstPtr &type) const
73{
74 return u"[]() { static constexpr QByteArrayView name(\""_s
75 + QString::fromUtf8(QMetaObject::normalizedType(type->augmentedInternalName().toUtf8()))
76 + u"\"); static const auto t = QMetaType::fromName(name); return t; }()"_s;
77}
78
79QString QQmlJSCodeGenerator::compositeListMetaType(const QString &elementName) const
80{
81 return u"QQmlPrivate::compositeListMetaType(aotContext->compilationUnit, "_s
82 + (m_jsUnitGenerator->hasStringId(elementName)
83 ? QString::number(m_jsUnitGenerator->getStringId(elementName)) + u')'
84 : u"QStringLiteral(\"%1\"))"_s.arg(elementName));
85}
86
87QString QQmlJSCodeGenerator::compositeMetaType(const QString &elementName) const
88{
89 return u"QQmlPrivate::compositeMetaType(aotContext->compilationUnit, "_s
90 + (m_jsUnitGenerator->hasStringId(elementName)
91 ? QString::number(m_jsUnitGenerator->getStringId(elementName)) + u')'
92 : u"QStringLiteral(\"%1\"))"_s.arg(elementName));
93}
94
95QString QQmlJSCodeGenerator::metaObject(const QQmlJSScope::ConstPtr &objectType)
96{
97 if (objectType->isComposite()) {
98 const QString name = m_typeResolver->nameForType(objectType);
99 if (name.isEmpty()) {
100 REJECT<QString>(
101 u"retrieving the metaObject of a composite type without an element name."_s);
102 }
103 return compositeMetaType(name) + u".metaObject()"_s;
104 }
105
106 if (objectType->internalName() == u"QObject"_s
107 || objectType->internalName() == u"QQmlComponent"_s) {
108 return u'&' + objectType->internalName() + u"::staticMetaObject"_s;
109 }
110 return metaTypeFromName(objectType) + u".metaObject()"_s;
111}
112
113QString QQmlJSCodeGenerator::metaType(const QQmlJSScope::ConstPtr &type)
114{
115 if (type->isOpaqueType()) {
116 REJECT<QString>("Retrieving the metatype of an unregistered type"_L1);
117 }
118 if (type->isComposite()) {
119 const QString name = m_typeResolver->nameForType(type);
120 if (name.isEmpty()) {
121 REJECT<QString>(
122 u"retrieving the metaType of a composite type without an element name."_s);
123 }
124 return compositeMetaType(name);
125 }
126
127 if (type->isListProperty() && type->elementType()->isComposite()) {
128 const QString name = m_typeResolver->nameForType(type->elementType());
129 Q_ASSERT(!name.isEmpty()); // There can't be a list with anonymous composite value type
130 return compositeListMetaType(name);
131 }
132
133 return (m_typeResolver->genericType(type) == type)
134 ? metaTypeFromType(type)
135 : metaTypeFromName(type);
136}
137
138static QString registerName(int registerIndex, int offset)
139{
140 // 'a<n>': argument number n
141 // 'v<n>_<m>': value produced by instruction for register n at byte code offset m
142 // 'c<n>_<m>': value produced by conversion for register n at byte code offset m
143 // The output of QV4_SHOW_BYTECODE=1 corresponds to the 'm' values.
144 // We can convert a value and then store a new value in the same register with one instruction.
145 // That's why we need both 'v' and 'c'.
146
147 if (offset < 0)
148 return u"s.a%1"_s.arg(registerIndex - QQmlJSCompilePass::Argc);
149 if (registerIndex < 0)
150 return u"s.c%1_%2"_s.arg(-registerIndex).arg(offset);
151 return u"s.v%1_%2"_s.arg(registerIndex).arg(offset);
152}
153
154QQmlJSAotFunction QQmlJSCodeGenerator::run(const Function *function, bool basicBlocksValidationFailed)
155{
156 m_function = function;
157
158 if (m_context->contextType == QV4::Compiler::ContextType::Binding
159 && m_function->returnType.contains(m_typeResolver->qQmlScriptStringType())) {
160 const QString reason = u"binding for property of type QQmlScriptString; nothing to do."_s;
161 skip(reason);
162 QQmlJSAotFunction result;
163 result.skipReason = reason;
164 return result;
165 }
166
167 const auto addVariable = [&](int registerIndex, int offset, QQmlJSRegisterContent content) {
168 const QQmlJSScope::ConstPtr seenType = content.storedType();
169 // Don't generate any variables for registers that are initialized with undefined.
170 if (!isTypeStorable(m_typeResolver, seenType))
171 return;
172
173 const auto oldSize = m_registerVariables.size();
174 auto &e = m_registerVariables[content];
175 if (m_registerVariables.size() != oldSize) {
176 e.variableName = registerName(registerIndex, offset);
177 e.storedType = seenType;
178 e.initialRegisterIndex = std::abs(registerIndex);
179 } else {
180 e.variableName += u'_' + registerName(registerIndex, offset).mid(2); // skip "s."
181 }
182 ++e.numTracked;
183 };
184
185QT_WARNING_PUSH
186QT_WARNING_DISABLE_CLANG("-Wrange-loop-analysis")
187 for (auto it = m_annotations.constBegin(), iend = m_annotations.constEnd(); it != iend; ++it) {
188 const int registerIndex = it->second.changedRegisterIndex;
189 if (registerIndex != InvalidRegister)
190 addVariable(registerIndex, it.key(), it->second.changedRegister);
191 for (auto jt = it->second.typeConversions.constBegin(),
192 jend = it->second.typeConversions.constEnd();
193 jt != jend; ++jt) {
194 // Conversions get a negative register number to discern them.
195 const int registerIndex = jt.key();
196 if (registerIndex != InvalidRegister)
197 addVariable(-registerIndex, it.key(), jt.value().content);
198 }
199 }
200QT_WARNING_POP
201
202 // ensure we have m_labels for loops
203 for (const auto loopLabel : m_context->labelInfo)
204 m_labels.insert(loopLabel, u"label_%1"_s.arg(m_labels.size()));
205
206 // Initialize the first instruction's state to hold the arguments.
207 // After this, the arguments (or whatever becomes of them) are carried
208 // over into any further basic blocks automatically.
209 m_state.State::operator=(initialState(m_function));
210
211 m_pool->setAllocationMode(QQmlJSRegisterContentPool::Temporary);
212 const QByteArray byteCode = function->code;
213 decode(byteCode.constData(), static_cast<uint>(byteCode.size()));
214 m_pool->setAllocationMode(QQmlJSRegisterContentPool::Permanent);
215
216 QQmlJSAotFunction result;
217 result.includes.swap(m_includes);
218
219 if (basicBlocksValidationFailed) {
220 result.code += "// QV4_BASIC_BLOCK_VALIDATION_FAILED: This file failed compilation "_L1
221 "with basic blocks validation but compiled without it.\n"_L1;
222 }
223
224 result.code += u"// %1 at line %2, column %3\n"_s
225 .arg(m_context->name).arg(m_context->line).arg(m_context->column);
226
227 QStringList initializations;
228 QStringList markings;
229 for (auto registerIt = m_registerVariables.cbegin(), registerEnd = m_registerVariables.cend();
230 registerIt != registerEnd; ++registerIt) {
231
232 // Remove the "s.". Inside the struct we need the plain name.
233 QString declarationName = registerIt->variableName.mid(2);
234
235 const int registerIndex = registerIt->initialRegisterIndex;
236 const bool registerIsArgument = isArgument(registerIndex);
237
238 QString code = registerIt->storedType->internalName();
239
240 const QQmlJSScope::ConstPtr storedType = registerIt->storedType;
241 const bool isPointer
242 = (storedType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference);
243 if (isPointer)
244 code += u" *"_s;
245 else
246 code += u' ';
247
248 if (!registerIsArgument
249 && registerIndex != Accumulator
250 && registerIndex != This
251 && !function->registerTypes[registerIndex - firstRegisterIndex()].contains(
252 m_typeResolver->voidType())) {
253 code += declarationName + u" = "_s;
254 code += convertStored(m_typeResolver->voidType(), storedType, QString());
255 } else if (registerIsArgument && argumentType(registerIndex).isStoredIn(storedType)) {
256 const int argumentIndex = registerIndex - FirstArgument;
257 const QQmlJSRegisterContent argument
258 = m_function->argumentTypes[argumentIndex];
259 const QQmlJSRegisterContent originalArgument = originalType(argument);
260
261 const bool needsConversion = argument != originalArgument;
262 if (!isPointer && registerIt->numTracked == 1 && !needsConversion) {
263 // Not a pointer, never written to, and doesn't need any initial conversion.
264 // This is a readonly argument.
265 //
266 // We would like to make the variable a const ref if it's a readonly argument,
267 // but due to the various call interfaces accepting non-const values, we can't.
268 // We rely on those calls to still not modify their arguments in place.
269 code += u'&';
270 }
271
272 code += declarationName + u" = "_s;
273
274 const auto originalContained = m_typeResolver->originalContainedType(argument);
275 QString originalValue;
276 const bool needsQVariantWrapping =
277 storedType->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence
278 && !originalContained->isReferenceType()
279 && storedType == m_typeResolver->varType()
280 && originalContained != m_typeResolver->varType();
281 if (needsQVariantWrapping) {
282 originalValue = u"QVariant(%1, argv[%2])"_s.arg(metaTypeFromName(originalContained))
283 .arg(QString::number(argumentIndex + 1));
284 } else {
285 originalValue = u"(*static_cast<"_s + castTargetName(originalArgument.storedType())
286 + u"*>(argv["_s + QString::number(argumentIndex + 1) + u"]))"_s;
287 }
288
289 if (needsConversion)
290 code += conversion(originalArgument, argument, originalValue);
291 else
292 code += originalValue;
293 } else if (isPointer) {
294 code += declarationName + u" = nullptr"_s;
295 } else {
296 code += declarationName;
297 }
298 code += u";\n"_s;
299
300 initializations.push_back(std::move(code));
301
302 if (isPointer) {
303 markings.append(u" aotContext->mark("_s + declarationName + u", markStack);\n");
304 } else if (storedType == m_typeResolver->varType()) {
305 markings.append(u" aotContext->mark("_s + declarationName + u", markStack);\n");
306 } else if (storedType == m_typeResolver->listPropertyType()) {
307 // No need to mark that since it's always backed by a property
308 } else if (storedType == m_typeResolver->variantMapType()
309 || storedType->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence) {
310 QString marking = u" for (const auto &v : std::as_const(" + declarationName + u"))\n"
311 + u" aotContext->mark(v, markStack);\n";
312 markings.append(std::move(marking));
313 }
314 }
315
316 result.code += u"struct Storage : QQmlPrivate::AOTTrackedLocalsStorage {\n"_s;
317 result.code += u"Storage(const QQmlPrivate::AOTCompiledContext *ctxt, void **a)"_s;
318 result.code += u" : aotContext(ctxt), argv(a) {}\n"_s;
319 result.code += u"void markObjects(QV4::MarkStack *markStack) const final {"_s;
320 result.code += u" Q_UNUSED(markStack);\n"_s;
321
322 markings.sort();
323 for (const QString &marking : std::as_const(markings))
324 result.code += marking;
325
326 result.code += u"}\n"_s;
327 result.code += u"const QQmlPrivate::AOTCompiledContext *aotContext;\n"_s;
328 result.code += u"void **argv;\n"_s;
329
330 // Sort them to obtain stable output.
331 initializations.sort();
332 for (const QString &initialization : std::as_const(initializations))
333 result.code += initialization;
334
335 result.code += u"};\nStorage s(aotContext, argv);\n"_s;
336 result.code += u"aotContext->setLocals(&s);\n"_s;
337
338 result.code += m_body;
339
340
341 QString signature
342 = u" struct { QV4::ExecutableCompilationUnit *compilationUnit; } c { contextUnit };\n"
343 " const auto *aotContext = &c;\n"
344 " Q_UNUSED(aotContext);\n"_s;
345
346 if (function->returnType.isValid()) {
347 signature += u" argTypes[0] = %1;\n"_s.arg(
348 metaType(function->returnType.containedType()));
349 } else {
350 signature += u" argTypes[0] = QMetaType();\n"_s;
351 }
352 result.numArguments = function->argumentTypes.length();
353 for (qsizetype i = 0; i != result.numArguments; ++i) {
354 signature += u" argTypes[%1] = %2;\n"_s.arg(
355 QString::number(i + 1),
356 metaType(m_typeResolver->originalContainedType(function->argumentTypes[i])));
357 }
358
359 result.signature = std::move(signature);
360 return result;
361}
362
363void QQmlJSCodeGenerator::generateReturnError()
364{
365 const auto finalizeReturn = qScopeGuard([this]() { m_body += u"return;\n"_s; });
366
367 m_body += u"aotContext->setReturnValueUndefined();\n"_s;
368 const auto ret = m_function->returnType;
369 if (!ret.isValid() || ret.contains(m_typeResolver->voidType()))
370 return;
371
372 m_body += u"if (argv[0]) {\n"_s;
373
374 const auto contained = ret.containedType();
375 const auto stored = ret.storedType();
376 if (contained->isReferenceType() && stored->isReferenceType()) {
377 m_body += u" *static_cast<"_s
378 + stored->augmentedInternalName()
379 + u" *>(argv[0]) = nullptr;\n"_s;
380 } else if (contained == stored) {
381 m_body += u" *static_cast<"_s + stored->internalName() + u" *>(argv[0]) = "_s
382 + stored->internalName() + u"();\n"_s;
383 } else {
384 m_body += u" const QMetaType returnType = "_s
385 + metaType(ret.containedType()) + u";\n"_s;
386 m_body += u" returnType.destruct(argv[0]);\n"_s;
387 m_body += u" returnType.construct(argv[0]);\n "_s;
388 }
389
390 m_body += u"}\n"_s;
391}
392
393void QQmlJSCodeGenerator::generate_Ret()
394{
395 INJECT_TRACE_INFO(generate_Ret);
396
397 const auto finalizeReturn = qScopeGuard([this]() {
398 m_body += u"return;\n"_s;
399 m_skipUntilNextLabel = true;
400 resetState();
401 });
402
403 if (!m_function->returnType.isValid())
404 return;
405
406 m_body += u"if (argv[0]) {\n"_s;
407
408 const QString signalUndefined = u"aotContext->setReturnValueUndefined();\n"_s;
409 const QString in = m_state.accumulatorVariableIn;
410
411 const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
412
413 if (in.isEmpty()) {
414 if (accumulatorIn.isStoredIn(m_typeResolver->voidType()))
415 m_body += signalUndefined;
416 } else if (accumulatorIn.isStoredIn(m_typeResolver->varType())) {
417 m_body += u" if (!"_s + in + u".isValid())\n"_s;
418 m_body += u" "_s + signalUndefined;
419 } else if (accumulatorIn.isStoredIn(m_typeResolver->jsPrimitiveType())) {
420 m_body += u" if ("_s + in + u".type() == QJSPrimitiveValue::Undefined)\n"_s;
421 m_body += u" "_s + signalUndefined;
422 } else if (accumulatorIn.isStoredIn(m_typeResolver->jsValueType())) {
423 m_body += u" if ("_s + in + u".isUndefined())\n"_s;
424 m_body += u" "_s + signalUndefined;
425 }
426
427 if (m_function->returnType.contains(m_typeResolver->voidType())) {
428 m_body += u"}\n"_s;
429 return;
430 }
431
432 const auto contained = m_function->returnType.containedType();
433 const auto stored = m_function->returnType.storedType();
434 if (contained == stored || (contained->isReferenceType() && stored->isReferenceType())) {
435 // We can always std::move here, no matter what the optimization pass has detected. The
436 // function returns and nothing can access the accumulator register anymore afterwards.
437 m_body += u" *static_cast<"_s
438 + stored->augmentedInternalName()
439 + u" *>(argv[0]) = "_s
440 + conversion(
441 accumulatorIn, m_function->returnType,
442 m_typeResolver->isTriviallyCopyable(accumulatorIn.storedType())
443 ? in
444 : u"std::move("_s + in + u')')
445 + u";\n"_s;
446 } else if (accumulatorIn.contains(contained)) {
447 m_body += u" const QMetaType returnType = "_s + contentType(accumulatorIn, in)
448 + u";\n"_s;
449 m_body += u" returnType.destruct(argv[0]);\n"_s;
450 m_body += u" returnType.construct(argv[0], "_s
451 + contentPointer(accumulatorIn, in) + u");\n"_s;
452 } else {
453 m_body += u" const auto converted = "_s
454 + conversion(accumulatorIn, m_function->returnType,
455 consumedAccumulatorVariableIn()) + u";\n"_s;
456 m_body += u" const QMetaType returnType = "_s
457 + contentType(m_function->returnType, u"converted"_s)
458 + u";\n"_s;
459 m_body += u" returnType.destruct(argv[0]);\n"_s;
460 m_body += u" returnType.construct(argv[0], "_s
461 + contentPointer(m_function->returnType, u"converted"_s) + u");\n"_s;
462 }
463
464 m_body += u"}\n"_s;
465}
466
467void QQmlJSCodeGenerator::generate_Debug()
468{
470}
471
472static QString toNumericString(double value)
473{
474 if (value >= std::numeric_limits<int>::min() && value <= std::numeric_limits<int>::max()) {
475 const int i = value;
476 if (i == value)
477 return QString::number(i);
478 }
479
480 switch (qFpClassify(value)) {
481 case FP_INFINITE: {
482 const QString inf = u"std::numeric_limits<double>::infinity()"_s;
483 return std::signbit(value) ? (u'-' + inf) : inf;
484 }
485 case FP_NAN:
486 return u"std::numeric_limits<double>::quiet_NaN()"_s;
487 case FP_ZERO:
488 return std::signbit(value) ? u"-0.0"_s : u"0"_s;
489 default:
490 break;
491 }
492
493 return QString::number(value, 'f', std::numeric_limits<double>::max_digits10);
494}
495
496void QQmlJSCodeGenerator::generate_LoadConst(int index)
497{
498 INJECT_TRACE_INFO(generate_LoadConst);
499
500 // You cannot actually get it to generate LoadConst for anything but double. We have
501 // a numer of specialized instructions for the other types, after all. However, let's
502 // play it safe.
503
504 const QV4::ReturnedValue encodedConst = m_jsUnitGenerator->constant(index);
505 const QV4::StaticValue value = QV4::StaticValue::fromReturnedValue(encodedConst);
506 const QQmlJSScope::ConstPtr type = m_typeResolver->typeForConst(encodedConst);
507
508 m_body += m_state.accumulatorVariableOut + u" = "_s;
509 if (type == m_typeResolver->realType()) {
510 m_body += conversion(
511 type, m_state.accumulatorOut(),
512 toNumericString(value.doubleValue()));
513 } else if (type == m_typeResolver->int32Type()) {
514 m_body += conversion(
515 type, m_state.accumulatorOut(),
516 QString::number(value.integerValue()));
517 } else if (type == m_typeResolver->boolType()) {
518 m_body += conversion(
519 type, m_state.accumulatorOut(),
520 value.booleanValue() ? u"true"_s : u"false"_s);
521 } else if (type == m_typeResolver->voidType()) {
522 m_body += conversion(
523 type, m_state.accumulatorOut(),
524 QString());
525 } else if (type == m_typeResolver->nullType()) {
526 m_body += conversion(
527 type, m_state.accumulatorOut(),
528 u"nullptr"_s);
529 } else {
530 REJECT(u"unsupported constant type"_s);
531 }
532
533 m_body += u";\n"_s;
534}
535
536void QQmlJSCodeGenerator::generate_LoadZero()
537{
538 INJECT_TRACE_INFO(generate_LoadZero);
539
540 m_body += m_state.accumulatorVariableOut;
541 m_body += u" = "_s + conversion(
542 m_typeResolver->int32Type(), m_state.accumulatorOut(), u"0"_s);
543 m_body += u";\n"_s;
544}
545
546void QQmlJSCodeGenerator::generate_LoadTrue()
547{
548 INJECT_TRACE_INFO(generate_LoadTrue);
549
550 m_body += m_state.accumulatorVariableOut;
551 m_body += u" = "_s + conversion(
552 m_typeResolver->boolType(), m_state.accumulatorOut(), u"true"_s);
553 m_body += u";\n"_s;
554}
555
556void QQmlJSCodeGenerator::generate_LoadFalse()
557{
558 INJECT_TRACE_INFO(generate_LoadFalse);
559
560 m_body += m_state.accumulatorVariableOut;
561 m_body += u" = "_s + conversion(
562 m_typeResolver->boolType(), m_state.accumulatorOut(), u"false"_s);
563 m_body += u";\n"_s;
564}
565
566void QQmlJSCodeGenerator::generate_LoadNull()
567{
568 INJECT_TRACE_INFO(generate_LoadNull);
569
570 m_body += m_state.accumulatorVariableOut + u" = "_s;
571 m_body += conversion(m_typeResolver->nullType(), m_state.accumulatorOut(),
572 u"nullptr"_s);
573 m_body += u";\n"_s;
574}
575
576void QQmlJSCodeGenerator::generate_LoadUndefined()
577{
578 INJECT_TRACE_INFO(generate_LoadUndefined);
579
580 m_body += m_state.accumulatorVariableOut + u" = "_s;
581 m_body += conversion(m_typeResolver->voidType(), m_state.accumulatorOut(),
582 QString());
583 m_body += u";\n"_s;
584}
585
586void QQmlJSCodeGenerator::generate_LoadInt(int value)
587{
588 INJECT_TRACE_INFO(generate_LoadInt);
589
590 m_body += m_state.accumulatorVariableOut;
591 m_body += u" = "_s;
592 m_body += conversion(m_typeResolver->int32Type(), m_state.accumulatorOut(),
593 QString::number(value));
594 m_body += u";\n"_s;
595}
596
597void QQmlJSCodeGenerator::generate_MoveConst(int constIndex, int destTemp)
598{
599 INJECT_TRACE_INFO(generate_MoveConst);
600
601 Q_ASSERT(destTemp == m_state.changedRegisterIndex());
602
603 auto var = changedRegisterVariable();
604 if (var.isEmpty())
605 return; // Do not load 'undefined'
606
607 const auto v4Value = QV4::StaticValue::fromReturnedValue(
608 m_jsUnitGenerator->constant(constIndex));
609
610 const auto changed = m_state.changedRegister();
611 QQmlJSScope::ConstPtr contained;
612 QString input;
613
614 m_body += var + u" = "_s;
615 if (v4Value.isNull()) {
616 contained = m_typeResolver->nullType();
617 } else if (v4Value.isUndefined()) {
618 contained = m_typeResolver->voidType();
619 } else if (v4Value.isBoolean()) {
620 contained = m_typeResolver->boolType();
621 input = v4Value.booleanValue() ? u"true"_s : u"false"_s;
622 } else if (v4Value.isInteger()) {
623 contained = m_typeResolver->int32Type();
624 input = QString::number(v4Value.int_32());
625 } else if (v4Value.isDouble()) {
626 contained = m_typeResolver->realType();
627 input = toNumericString(v4Value.doubleValue());
628 } else {
629 REJECT(u"unknown const type"_s);
630 }
631 m_body += conversion(contained, changed, input) + u";\n"_s;
632}
633
634void QQmlJSCodeGenerator::generate_LoadReg(int reg)
635{
636 INJECT_TRACE_INFO(generate_LoadReg);
637
638 if (registerVariable(reg) == m_state.accumulatorVariableOut)
639 return;
640
641 m_body += m_state.accumulatorVariableOut;
642 m_body += u" = "_s;
643 m_body += conversion(
644 registerType(reg), m_state.accumulatorOut(), consumedRegisterVariable(reg));
645 m_body += u";\n"_s;
646}
647
648void QQmlJSCodeGenerator::generate_StoreReg(int reg)
649{
650 INJECT_TRACE_INFO(generate_StoreReg);
651
652 Q_ASSERT(m_state.changedRegisterIndex() == reg);
653 Q_ASSERT(m_state.accumulatorIn().isValid());
654 const QString var = changedRegisterVariable();
655 if (var.isEmpty())
656 return; // don't store "undefined"
657 if (var == m_state.accumulatorVariableIn)
658 return;
659 m_body += var;
660 m_body += u" = "_s;
661 m_body += conversion(m_state.accumulatorIn(), m_state.changedRegister(),
662 consumedAccumulatorVariableIn());
663 m_body += u";\n"_s;
664}
665
666void QQmlJSCodeGenerator::generate_MoveReg(int srcReg, int destReg)
667{
668 INJECT_TRACE_INFO(generate_MoveReg);
669
670 Q_ASSERT(m_state.changedRegisterIndex() == destReg);
671 const QString destRegName = changedRegisterVariable();
672 if (destRegName.isEmpty())
673 return; // don't store things we cannot store.
674 if (destRegName == registerVariable(srcReg))
675 return; // don't emit redundant copy statements
676 m_body += destRegName;
677 m_body += u" = "_s;
678 m_body += conversion(
679 registerType(srcReg), m_state.changedRegister(), consumedRegisterVariable(srcReg));
680 m_body += u";\n"_s;
681}
682
683void QQmlJSCodeGenerator::generate_LoadImport(int index)
684{
685 Q_UNUSED(index)
687}
688
689void QQmlJSCodeGenerator::generate_LoadLocal(int index)
690{
691 Q_UNUSED(index);
692 REJECT(u"LoadLocal"_s);
693}
694
695void QQmlJSCodeGenerator::generate_StoreLocal(int index)
696{
697 Q_UNUSED(index)
699}
700
701void QQmlJSCodeGenerator::generate_LoadScopedLocal(int scope, int index)
702{
703 Q_UNUSED(scope)
704 Q_UNUSED(index)
706}
707
708void QQmlJSCodeGenerator::generate_StoreScopedLocal(int scope, int index)
709{
710 Q_UNUSED(scope)
711 Q_UNUSED(index)
713}
714
715void QQmlJSCodeGenerator::generate_LoadRuntimeString(int stringId)
716{
717 INJECT_TRACE_INFO(generate_LoadRuntimeString);
718
719 m_body += m_state.accumulatorVariableOut;
720 m_body += u" = "_s;
721 m_body += conversion(m_typeResolver->stringType(), m_state.accumulatorOut(),
722 QQmlJSUtils::toLiteral(m_jsUnitGenerator->stringForIndex(stringId)));
723 m_body += u";\n"_s;
724}
725
726void QQmlJSCodeGenerator::generate_MoveRegExp(int regExpId, int destReg)
727{
728 Q_UNUSED(regExpId)
729 Q_UNUSED(destReg)
730 REJECT(u"MoveRegExp"_s);
731}
732
733void QQmlJSCodeGenerator::generate_LoadClosure(int value)
734{
735 Q_UNUSED(value)
736 REJECT(u"LoadClosure"_s);
737}
738
739void QQmlJSCodeGenerator::generate_LoadName(int nameIndex)
740{
741 Q_UNUSED(nameIndex)
742 REJECT(u"LoadName"_s);
743}
744
745void QQmlJSCodeGenerator::generate_LoadGlobalLookup(int index)
746{
747 INJECT_TRACE_INFO(generate_LoadGlobalLookup);
748
749 AccumulatorConverter registers(this);
750
751 const QString lookup = u"aotContext->loadGlobalLookup("_s + QString::number(index) + u", "_s
752 + contentPointer(m_state.accumulatorOut(), m_state.accumulatorVariableOut) + u')';
753 const QString initialization = u"aotContext->initLoadGlobalLookup("_s
754 + QString::number(index) + u", "_s
755 + contentType(m_state.accumulatorOut(), m_state.accumulatorVariableOut) + u')';
756 const QString preparation = getLookupPreparation(
757 m_state.accumulatorOut(), m_state.accumulatorVariableOut, index);
758 generateLookup(lookup, initialization, preparation);
759}
760
761void QQmlJSCodeGenerator::generate_LoadQmlContextPropertyLookup(int index)
762{
763 INJECT_TRACE_INFO(generate_LoadQmlContextPropertyLookup);
764
765 AccumulatorConverter registers(this);
766
767 const QQmlJSScope::ConstPtr stored = m_state.accumulatorOut().storedType();
768
769 const int nameIndex = m_jsUnitGenerator->lookupNameIndex(index);
770 if (m_state.accumulatorOut().scope().contains(m_typeResolver->jsGlobalObject())) {
771 // This produces a QJSValue. The QQmlJSMetaProperty used to analyze it may have more details
772 // but the QQmlJSAotContext API does not reflect them.
773 m_body += m_state.accumulatorVariableOut + u" = "_s
774 + conversion(
775 m_typeResolver->jsValueType(), m_state.accumulatorOut(),
776 u"aotContext->javaScriptGlobalProperty("_s + QString::number(nameIndex) + u")")
777 + u";\n"_s;
778 return;
779 }
780
781 const QString indexString = QString::number(index);
782 if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::ObjectById) {
783 const QString lookup = u"aotContext->loadContextIdLookup("_s
784 + indexString + u", "_s
785 + contentPointer(m_state.accumulatorOut(), m_state.accumulatorVariableOut) + u')';
786 const QString initialization = u"aotContext->initLoadContextIdLookup("_s
787 + indexString + u')';
788 generateLookup(lookup, initialization);
789 return;
790 }
791
792 const bool isProperty = m_state.accumulatorOut().isProperty();
793 if (isProperty) {
794 const QString lookup = u"aotContext->loadScopeObjectPropertyLookup("_s
795 + indexString + u", "_s
796 + contentPointer(m_state.accumulatorOut(), m_state.accumulatorVariableOut) + u')';
797 const QString initialization = u"aotContext->initLoadScopeObjectPropertyLookup("_s
798 + indexString + u')';
799 const QString preparation = getLookupPreparation(
800 m_state.accumulatorOut(), m_state.accumulatorVariableOut, index);
801
802 recordPropertyLookup(m_typeResolver->original(m_state.accumulatorOut()).scopeType(),
803 originalProperty(m_state.accumulatorOut()));
804 generateLookup(lookup, initialization, preparation);
805 } else if (m_state.accumulatorOut().isType() || m_state.accumulatorOut().isImportNamespace()) {
806 generateTypeLookup(index);
807 } else {
808 REJECT(u"lookup of %1"_s.arg(m_state.accumulatorOut().descriptiveName()));
809 }
810}
811
812void QQmlJSCodeGenerator::generate_StoreNameSloppy(int nameIndex)
813{
814 INJECT_TRACE_INFO(generate_StoreNameSloppy);
815
816 const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
817 const QQmlJSRegisterContent type = m_typeResolver->scopedType(m_function->qmlScope, name);
818 Q_ASSERT(type.isProperty());
819
820 switch (type.variant()) {
821 case QQmlJSRegisterContent::Property: {
822 // Do not convert here. We may intentionally pass the "wrong" type, for example to trigger
823 // a property reset.
824 m_body += u"aotContext->storeNameSloppy("_s + QString::number(nameIndex)
825 + u", "_s
826 + contentPointer(m_state.accumulatorIn(), m_state.accumulatorVariableIn)
827 + u", "_s
828 + contentType(m_state.accumulatorIn(), m_state.accumulatorVariableIn) + u')';
829 m_body += u";\n"_s;
830 break;
831 }
832 case QQmlJSRegisterContent::Method:
833 REJECT(u"assignment to scope method"_s);
834 default:
835 Q_UNREACHABLE();
836 }
837}
838
839void QQmlJSCodeGenerator::generate_StoreNameStrict(int name)
840{
841 Q_UNUSED(name)
843}
844
845void QQmlJSCodeGenerator::generate_LoadElement(int base)
846{
847 INJECT_TRACE_INFO(generate_LoadElement);
848
849 const QQmlJSRegisterContent baseType = registerType(base);
850
851 if (!baseType.isList() && !baseType.isStoredIn(m_typeResolver->stringType()))
852 REJECT(u"LoadElement with non-list base type "_s + baseType.descriptiveName());
853
854 const QString voidAssignment = u" "_s + m_state.accumulatorVariableOut + u" = "_s +
855 conversion(literalType(m_typeResolver->voidType()),
856 m_state.accumulatorOut(), QString()) + u";\n"_s;
857
858 AccumulatorConverter registers(this);
859
860 QString indexName = m_state.accumulatorVariableIn;
861 QQmlJSScope::ConstPtr indexType;
862 if (m_typeResolver->isNumeric(m_state.accumulatorIn())) {
863 indexType = m_state.accumulatorIn().containedType();
864 } else if (m_state.accumulatorIn().isConversion()) {
865 const auto target = m_typeResolver->extractNonVoidFromOptionalType(m_state.accumulatorIn());
866 if (m_typeResolver->isNumeric(target)) {
867 indexType = target.containedType();
868 m_body += u"if (!" + indexName + u".metaType().isValid())\n"
869 + voidAssignment
870 + u"else ";
871 indexName = convertStored(
872 m_state.accumulatorIn().storedType(), indexType, indexName);
873 } else {
874 REJECT(u"LoadElement with non-numeric argument"_s);
875 }
876 }
877
878 const QString baseName = registerVariable(base);
879
880 if (!m_typeResolver->isNativeArrayIndex(indexType)) {
881 m_body += u"if (!QJSNumberCoercion::isArrayIndex("_s + indexName + u"))\n"_s
882 + voidAssignment
883 + u"else "_s;
884 } else if (!m_typeResolver->isUnsignedInteger(indexType)) {
885 m_body += u"if ("_s + indexName + u" < 0)\n"_s
886 + voidAssignment
887 + u"else "_s;
888 }
889
890 if (baseType.isStoredIn(m_typeResolver->listPropertyType())) {
891 // Our QQmlListProperty only keeps plain QObject*.
892 m_body += u"if ("_s + indexName + u" < "_s + baseName
893 + u".count(&"_s + baseName + u"))\n"_s;
894 m_body += u" "_s + m_state.accumulatorVariableOut + u" = "_s +
895 conversion(m_typeResolver->qObjectType(), m_state.accumulatorOut(),
896 baseName + u".at(&"_s + baseName + u", "_s
897 + indexName + u')') + u";\n"_s;
898 m_body += u"else\n"_s
899 + voidAssignment;
900 return;
901 }
902
903 // Since we can do .at() below, we know that we can natively store the element type.
904 QQmlJSRegisterContent elementType = m_typeResolver->elementType(baseType);
905 elementType = m_pool->storedIn(
906 elementType, m_typeResolver->storedType(elementType.containedType()));
907
908 QString access = baseName + u".at("_s + indexName + u')';
909
910 // TODO: Once we get a char type in QML, use it here.
911 if (baseType.isStoredIn(m_typeResolver->stringType()))
912 access = u"QString("_s + access + u")"_s;
913 else if (isRegisterAffectedBySideEffects(base))
914 REJECT(u"LoadElement on a sequence potentially affected by side effects"_s);
915 else if (baseType.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence)
916 REJECT(u"LoadElement on a sequence wrapped in a non-sequence type"_s);
917
918 m_body += u"if ("_s + indexName + u" < "_s + baseName + u".size())\n"_s;
919 m_body += u" "_s + m_state.accumulatorVariableOut + u" = "_s +
920 conversion(elementType, m_state.accumulatorOut(), access) + u";\n"_s;
921 m_body += u"else\n"_s
922 + voidAssignment;
923}
924
925void QQmlJSCodeGenerator::generate_StoreElement(int base, int index)
926{
927 INJECT_TRACE_INFO(generate_StoreElement);
928
929 const QQmlJSRegisterContent baseType = registerType(base);
930 const QQmlJSScope::ConstPtr indexType = registerType(index).containedType();
931
932 if (!m_typeResolver->isNumeric(indexType) || !baseType.isList())
933 REJECT(u"StoreElement with non-list base type or non-numeric arguments"_s);
934
935 if (baseType.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence)
936 REJECT(u"indirect StoreElement"_s);
937
938 const QString baseName = registerVariable(base);
939 const QString indexName = registerVariable(index);
940
941 const auto elementType = m_typeResolver->genericType(
942 m_typeResolver->elementType(baseType).containedType());
943
944 addInclude(u"QtQml/qjslist.h"_s);
945 if (!m_typeResolver->isNativeArrayIndex(indexType))
946 m_body += u"if (QJSNumberCoercion::isArrayIndex("_s + indexName + u")) {\n"_s;
947 else if (!m_typeResolver->isUnsignedInteger(indexType))
948 m_body += u"if ("_s + indexName + u" >= 0) {\n"_s;
949 else
950 m_body += u"{\n"_s;
951
952 if (baseType.isStoredIn(m_typeResolver->listPropertyType())) {
953 m_body += u" if ("_s + indexName + u" < "_s + baseName + u".count(&"_s + baseName
954 + u"))\n"_s;
955 m_body += u" "_s + baseName + u".replace(&"_s + baseName
956 + u", "_s + indexName + u", "_s;
957 m_body += conversion(m_state.accumulatorIn(), elementType, m_state.accumulatorVariableIn)
958 + u");\n"_s;
959 m_body += u"}\n"_s;
960 return;
961 }
962
963 if (isRegisterAffectedBySideEffects(base))
964 REJECT(u"StoreElement on a sequence potentially affected by side effects"_s);
965 if (isRegisterAffectedBySideEffects(Accumulator))
966 REJECT(u"StoreElement of a value potentially affected by side effects"_s);
967
968 m_body += u" if ("_s + indexName + u" >= " + baseName + u".size())\n"_s;
969 m_body += u" QJSList(&"_s + baseName + u", aotContext->engine).resize("_s
970 + indexName + u" + 1);\n"_s;
971 m_body += u" "_s + baseName + u'[' + indexName + u"] = "_s;
972 m_body += conversion(m_state.accumulatorIn(), elementType, m_state.accumulatorVariableIn)
973 + u";\n"_s;
974 m_body += u"}\n"_s;
975
976 generateWriteBack(base);
977}
978
979void QQmlJSCodeGenerator::generate_LoadProperty(int nameIndex)
980{
981 Q_UNUSED(nameIndex)
982 REJECT(u"LoadProperty"_s);
983}
984
985void QQmlJSCodeGenerator::generate_LoadOptionalProperty(int name, int offset)
986{
987 Q_UNUSED(name)
988 Q_UNUSED(offset)
990}
991
992void QQmlJSCodeGenerator::generateEnumLookup(int index)
993{
994 const QString enumMember = m_state.accumulatorOut().enumMember();
995
996 // If we're referring to the type, there's nothing to do.
997 // However, we should not get here since no one can ever use the enum metatype.
998 // The lookup is dead code and should be optimized away.
999 // ... unless you are actually trying to store the metatype itself in a property.
1000 // We cannot compile such code.
1001 if (enumMember.isEmpty())
1002 REJECT(u"Lookup of enum metatype"_s);
1003
1004 // If the metaenum has the value, just use it and skip all the rest.
1005 const QQmlJSMetaEnum metaEnum = m_state.accumulatorOut().enumeration();
1006 if (metaEnum.hasValues()) {
1007 m_body += m_state.accumulatorVariableOut + u" = "_s
1008 + QString::number(metaEnum.value(enumMember));
1009 m_body += u";\n"_s;
1010 const QQmlJSRegisterContent &scope = m_state.accumulatorOut().scope();
1011 recordEnumKeyLookup(m_typeResolver->originalContainedType(scope), metaEnum, enumMember);
1012 return;
1013 }
1014
1015 const QQmlJSScope::ConstPtr scopeType = m_state.accumulatorOut().scopeType();
1016
1017 // Otherwise we would have found an enum with values.
1018 Q_ASSERT(!scopeType->isComposite());
1019
1020 const QString enumName = metaEnum.isFlag() ? metaEnum.alias() : metaEnum.name();
1021 if (enumName.isEmpty()) {
1022 if (metaEnum.isFlag() && !metaEnum.name().isEmpty()) {
1023 REJECT(u"qmltypes misses name entry for flag; "
1024 "did you pass the enum type to Q_FLAG instead of the QFlag type?"
1025 "\nType is %1, enum name is %2"_s.arg(scopeType->internalName(), metaEnum.name()));
1026 }
1027 REJECT(u"qmltypes misses name entry for enum"_s);
1028 }
1029 const QString lookup = u"aotContext->getEnumLookup("_s + QString::number(index)
1030 + u", &"_s + m_state.accumulatorVariableOut + u')';
1031 const QString initialization = u"aotContext->initGetEnumLookup("_s
1032 + QString::number(index) + u", "_s + metaObject(scopeType)
1033 + u", \""_s + enumName + u"\", \""_s + enumMember
1034 + u"\")"_s;
1035
1036 // No need to record C++ enums lookup as they are resolved at runtime
1037 generateLookup(lookup, initialization);
1038}
1039
1040void QQmlJSCodeGenerator::generateTypeLookup(int index)
1041{
1042 const QString indexString = QString::number(index);
1043 const QQmlJSRegisterContent accumulatorIn = m_state.registers.value(Accumulator).content;
1044 const QString namespaceString
1045 = accumulatorIn.isImportNamespace()
1046 ? QString::number(accumulatorIn.importNamespace())
1047 : u"QQmlPrivate::AOTCompiledContext::InvalidStringId"_s;
1048
1049 switch (m_state.accumulatorOut().variant()) {
1050 case QQmlJSRegisterContent::Singleton: {
1051 rejectIfNonQObjectOut(u"non-QObject singleton type"_s);
1052 const QString lookup = u"aotContext->loadSingletonLookup("_s + indexString
1053 + u", &"_s + m_state.accumulatorVariableOut + u')';
1054 const QString initialization = u"aotContext->initLoadSingletonLookup("_s + indexString
1055 + u", "_s + namespaceString + u')';
1056 generateLookup(lookup, initialization);
1057 break;
1058 }
1059 case QQmlJSRegisterContent::ModulePrefix:
1060 break;
1061 case QQmlJSRegisterContent::Attachment: {
1062 rejectIfNonQObjectOut(u"non-QObject attached type"_s);
1063 const QString lookup = u"aotContext->loadAttachedLookup("_s + indexString
1064 + u", aotContext->qmlScopeObject, &"_s + m_state.accumulatorVariableOut + u')';
1065 const QString initialization = u"aotContext->initLoadAttachedLookup("_s + indexString
1066 + u", "_s + namespaceString + u", aotContext->qmlScopeObject)"_s;
1067 generateLookup(lookup, initialization);
1068 break;
1069 }
1070 case QQmlJSRegisterContent::Script:
1071 REJECT(u"script lookup"_s);
1072 case QQmlJSRegisterContent::MetaType: {
1073 if (!m_state.accumulatorOut().isStoredIn(m_typeResolver->metaObjectType())) {
1074 // TODO: Can we trigger this somehow?
1075 // It might be impossible, but we better be safe here.
1076 REJECT(u"meta-object stored in different type"_s);
1077 }
1078 const QString lookup = u"aotContext->loadTypeLookup("_s + indexString
1079 + u", &"_s + m_state.accumulatorVariableOut + u')';
1080 const QString initialization = u"aotContext->initLoadTypeLookup("_s + indexString
1081 + u", "_s + namespaceString + u")"_s;
1082 generateLookup(lookup, initialization);
1083 break;
1084 }
1085 default:
1086 Q_UNREACHABLE();
1087 }
1088}
1089
1090void QQmlJSCodeGenerator::generateVariantEqualityComparison(
1091 QQmlJSRegisterContent nonStorableContent, const QString &registerName, bool invert)
1092{
1093 const auto nonStorableType = nonStorableContent.containedType();
1094 QQmlJSScope::ConstPtr comparedType = (nonStorableType == m_typeResolver->nullType())
1095 ? m_typeResolver->nullType()
1096 : m_typeResolver->voidType();
1097
1098 // The common operations for both nulltype and voidtype
1099 m_body += u"if ("_s + registerName
1100 + u".metaType() == QMetaType::fromType<QJSPrimitiveValue>()) {\n"_s
1101 + m_state.accumulatorVariableOut + u" = "_s
1102 + conversion(m_typeResolver->boolType(), m_state.accumulatorOut(),
1103 u"static_cast<const QJSPrimitiveValue *>("_s + registerName
1104 + u".constData())"_s + u"->type() "_s
1105 + (invert ? u"!="_s : u"=="_s)
1106 + (comparedType == m_typeResolver->nullType()
1107 ? u"QJSPrimitiveValue::Null"_s
1108 : u"QJSPrimitiveValue::Undefined"_s))
1109 + u";\n} else if ("_s + registerName
1110 + u".metaType() == QMetaType::fromType<QJSValue>()) {\n"_s
1111 + m_state.accumulatorVariableOut + u" = "_s
1112 + conversion(m_typeResolver->boolType(), m_state.accumulatorOut(),
1113 (invert ? u"!"_s : QString()) + u"static_cast<const QJSValue *>("_s
1114 + registerName + u".constData())"_s + u"->"_s
1115 + (comparedType == m_typeResolver->nullType()
1116 ? u"isNull()"_s
1117 : u"isUndefined()"_s))
1118 + u";\n}"_s;
1119
1120 // Generate nullType specific operations (the case when variant contains QObject * or
1121 // std::nullptr_t)
1122 if (nonStorableType == m_typeResolver->nullType()) {
1123 m_body += u"else if ("_s + registerName
1124 + u".metaType().flags().testFlag(QMetaType::PointerToQObject)) {\n"_s
1125 + m_state.accumulatorVariableOut + u" = "_s
1126 + conversion(m_typeResolver->boolType(), m_state.accumulatorOut(),
1127 u"*static_cast<QObject *const *>("_s + registerName
1128 + u".constData())"_s + (invert ? u"!="_s : u"=="_s)
1129 + u" nullptr"_s)
1130 + u";\n} else if ("_s + registerName
1131 + u".metaType() == QMetaType::fromType<std::nullptr_t>()) {\n"_s
1132 + m_state.accumulatorVariableOut + u" = "_s
1133 + conversion(m_typeResolver->boolType(), m_state.accumulatorOut(),
1134 (invert ? u"false"_s : u"true"_s))
1135 + u";\n}\n"_s;
1136 }
1137
1138 // fallback case (if variant contains a different type, then it is not null or undefined)
1139 m_body += u"else {\n"_s + m_state.accumulatorVariableOut + u" = "_s
1140 + conversion(m_typeResolver->boolType(), m_state.accumulatorOut(),
1141 (invert ? (registerName + u".isValid() ? true : false"_s)
1142 : (registerName + u".isValid() ? false : true"_s)))
1143 + u";\n}"_s;
1144}
1145
1146void QQmlJSCodeGenerator::generateVariantEqualityComparison(
1147 QQmlJSRegisterContent storableContent, const QString &typedRegisterName,
1148 const QString &varRegisterName, bool invert)
1149{
1150 // enumerations are ===-equal to their underlying type and they are stored as such.
1151 // Therefore, use the underlying type right away.
1152 const QQmlJSScope::ConstPtr contained = storableContent.isEnumeration()
1153 ? storableContent.storedType()
1154 : storableContent.containedType();
1155
1156 const QQmlJSScope::ConstPtr boolType = m_typeResolver->boolType();
1157 if (contained->isReferenceType()) {
1158 const QQmlJSScope::ConstPtr comparable = m_typeResolver->qObjectType();
1159 const QString cmpExpr = (invert ? u"!"_s : QString()) + u"(("
1160 + varRegisterName + u".metaType().flags() & QMetaType::PointerToQObject) "_s
1161 + u" && "_s + conversion(storableContent, comparable, typedRegisterName) + u" == "_s
1162 + conversion(m_typeResolver->varType(), comparable, varRegisterName) + u')';
1163
1164 m_body += m_state.accumulatorVariableOut + u" = "_s
1165 + conversion(boolType, m_state.accumulatorOut(), cmpExpr) + u";\n";
1166 return;
1167 }
1168
1169 if (m_typeResolver->isPrimitive(contained)) {
1170 const QQmlJSScope::ConstPtr comparable = m_typeResolver->jsPrimitiveType();
1171 const QString cmpExpr = (invert ? u"!"_s : QString())
1172 + conversion(storableContent, comparable, typedRegisterName)
1173 + u".strictlyEquals("_s
1174 + conversion(m_typeResolver->varType(), comparable, varRegisterName) + u')';
1175
1176 m_body += m_state.accumulatorVariableOut + u" = "_s
1177 + conversion(boolType, m_state.accumulatorOut(), cmpExpr) + u";\n"_s;
1178 return;
1179 }
1180
1181 REJECT(u"comparison of non-primitive, non-object type to var"_s);
1182}
1183
1184void QQmlJSCodeGenerator::generateArrayInitializer(int argc, int argv)
1185{
1186 const QQmlJSScope::ConstPtr stored = m_state.accumulatorOut().storedType();
1187 const QQmlJSScope::ConstPtr element = stored->elementType();
1188 Q_ASSERT(element);
1189
1190 QStringList initializer;
1191 for (int i = 0; i < argc; ++i) {
1192 initializer += convertStored(
1193 registerType(argv + i).storedType(), element,
1194 consumedRegisterVariable(argv + i));
1195 }
1196
1197 m_body += m_state.accumulatorVariableOut + u" = "_s + stored->internalName() + u'{';
1198 m_body += initializer.join(u", "_s);
1199 m_body += u"};\n";
1200}
1201
1202void QQmlJSCodeGenerator::generateWriteBack(int registerIndex)
1203{
1204 QString writeBackRegister = registerVariable(registerIndex);
1205 bool writeBackAffectedBySideEffects = isRegisterAffectedBySideEffects(registerIndex);
1206
1207 for (QQmlJSRegisterContent writeBack = registerType(registerIndex);
1208 !writeBack.storedType()->isReferenceType();) {
1209
1210 switch (writeBack.variant()) {
1211 case QQmlJSRegisterContent::Literal:
1212 case QQmlJSRegisterContent::Operation:
1213 // If the value type or list was produced as a literal or as result
1214 // of an operation (like DefineArray ...), we don't have to write back.
1215 return;
1216 default:
1217 break;
1218 }
1219
1220 if (writeBackAffectedBySideEffects)
1221 REJECT(u"write-back of value affected by side effects"_s);
1222
1223 if (writeBack.isConversion())
1224 REJECT(u"write-back of converted value"_s);
1225
1226 const int lookupIndex = writeBack.resultLookupIndex();
1227
1228 // This is essential for the soundness of the type system.
1229 //
1230 // If a value or a list is returned from a function, we cannot know
1231 // whether it is a copy or a reference. Therefore, we cannot know whether
1232 // we have to write it back and so we have to REJECT any write on it.
1233 //
1234 // Only if we are sure that the value is locally created we can be sure
1235 // we don't have to write it back. In this latter case we could allow
1236 // a modification that doesn't write back.
1237 if (lookupIndex == -1)
1238 REJECT(u"write-back of non-lookup"_s);
1239
1240 const QString writeBackIndexString = QString::number(lookupIndex);
1241
1242 const QQmlJSRegisterContent::ContentVariant variant = writeBack.variant();
1243 if (variant == QQmlJSRegisterContent::Property && isQmlScopeObject(writeBack.scope())) {
1244 const QString lookup = u"aotContext->writeBackScopeObjectPropertyLookup("_s
1245 + writeBackIndexString
1246 + u", "_s + contentPointer(writeBack, writeBackRegister) + u')';
1247 const QString initialization = u"aotContext->initLoadScopeObjectPropertyLookup("_s
1248 + writeBackIndexString + u')';
1249 recordPropertyLookup(m_typeResolver->originalContainedType(writeBack.scope()),
1250 originalProperty(writeBack));
1251 generateLookup(lookup, initialization);
1252 break;
1253 }
1254
1255
1256 QQmlJSRegisterContent outerContent;
1257 QString outerRegister;
1258 bool outerAffectedBySideEffects = false;
1259 for (auto it = m_state.lookups.constBegin(), end = m_state.lookups.constEnd();
1260 it != end; ++it) {
1261 if (it.value().content.resultLookupIndex() == writeBack.baseLookupIndex()) {
1262 outerContent = it.value().content;
1263 outerRegister = lookupVariable(outerContent.resultLookupIndex());
1264 outerAffectedBySideEffects = it.value().affectedBySideEffects;
1265 break;
1266 }
1267 }
1268
1269 // If the lookup doesn't exist, it was killed by state merge.
1270 if (!outerContent.isValid())
1271 REJECT(u"write-back of lookup across jumps or merges."_s);
1272
1273 Q_ASSERT(!outerRegister.isEmpty());
1274
1275 switch (writeBack.variant()) {
1276 case QQmlJSRegisterContent::Property: {
1277 const QQmlJSMetaProperty property = originalProperty(writeBack);
1278 if (writeBack.scopeType()->isReferenceType()) {
1279 const QString lookup = u"aotContext->writeBackObjectLookup("_s
1280 + writeBackIndexString
1281 + u", "_s + outerRegister
1282 + u", "_s + contentPointer(writeBack, writeBackRegister) + u')';
1283
1284 const QString initialization = (m_state.registers[registerIndex].isShadowable
1285 ? u"aotContext->initGetObjectLookupAsVariant("_s
1286 : u"aotContext->initGetObjectLookup("_s)
1287 + writeBackIndexString + u", "_s + outerRegister + u')';
1288 recordPropertyLookup(m_typeResolver->originalContainedType(writeBack.scope()), property);
1289 generateLookup(lookup, initialization);
1290 } else {
1291 const QString valuePointer = contentPointer(outerContent, outerRegister);
1292 const QString lookup = u"aotContext->writeBackValueLookup("_s
1293 + writeBackIndexString
1294 + u", "_s + valuePointer
1295 + u", "_s + contentPointer(writeBack, writeBackRegister) + u')';
1296 const QString initialization = u"aotContext->initGetValueLookup("_s
1297 + writeBackIndexString
1298 + u", "_s + metaObject(writeBack.scopeType()) + u')';
1299 recordPropertyLookup(m_typeResolver->originalContainedType(writeBack.scope()), property);
1300 generateLookup(lookup, initialization);
1301 }
1302 break;
1303 }
1304 default:
1305 REJECT(u"SetLookup on value types (because of missing write-back)"_s);
1306 }
1307
1308 writeBackRegister = outerRegister;
1309 writeBack = outerContent;
1310 writeBackAffectedBySideEffects = outerAffectedBySideEffects;
1311 }
1312}
1313
1314void QQmlJSCodeGenerator::rejectIfNonQObjectOut(const QString &error)
1315{
1316 if (m_state.accumulatorOut().storedType()->accessSemantics()
1317 != QQmlJSScope::AccessSemantics::Reference) {
1318 REJECT(error);
1319 }
1320}
1321
1322void QQmlJSCodeGenerator::rejectIfBadArray()
1323{
1324 const QQmlJSScope::ConstPtr stored = m_state.accumulatorOut().storedType();
1325 if (stored->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence) {
1326 // This rejects any attempt to store the list into a QVariant.
1327 // Therefore, we don't have to adjust the contained type below.
1328
1329 REJECT(u"storing an array in a non-sequence type"_s);
1330 } else if (stored->isListProperty()) {
1331 // We can, technically, generate code for this. But it's dangerous:
1332 //
1333 // const QString storage = m_state.accumulatorVariableOut + u"_storage"_s;
1334 // m_body += stored->internalName() + u"::ListType " + storage
1335 // + u" = {"_s + initializer.join(u", "_s) + u"};\n"_s;
1336 // m_body += m_state.accumulatorVariableOut
1337 // + u" = " + stored->internalName() + u"(nullptr, &"_s + storage + u");\n"_s;
1338
1339 REJECT(u"creating a QQmlListProperty not backed by a property"_s);
1340
1341 }
1342}
1343
1344/*!
1345 * \internal
1346 *
1347 * generates a check for the content pointer to be valid.
1348 * Returns true if the content pointer needs to be retrieved from a QVariant, or
1349 * false if the variable can be used as-is.
1350 */
1351bool QQmlJSCodeGenerator::generateContentPointerCheck(
1352 const QQmlJSScope::ConstPtr &required, QQmlJSRegisterContent actual,
1353 const QString &variable, const QString &errorMessage)
1354{
1355 const QQmlJSScope::ConstPtr scope = required;
1356 const QQmlJSScope::ConstPtr input = actual.containedType();
1357 if (QQmlJSUtils::searchBaseAndExtensionTypes(input,
1358 [&](const QQmlJSScope::ConstPtr &base) { return base == scope; })) {
1359 return false;
1360 }
1361
1362 if (!m_typeResolver->canHold(input, scope)) {
1363 REJECT<bool>(u"lookup of members of %1 in %2"_s
1364 .arg(scope->internalName(), input->internalName()));
1365 }
1366
1367 bool needsVarContentConversion = false;
1368 QString processedErrorMessage;
1369 if (actual.storedType()->isReferenceType()) {
1370 // Since we have verified the type in qqmljstypepropagator.cpp we now know
1371 // that we can only have either null or the actual type here. Therefore,
1372 // it's enough to check the pointer for null.
1373 m_body += u"if ("_s + variable + u" == nullptr) {\n "_s;
1374 processedErrorMessage = errorMessage.arg(u"null");
1375 } else if (actual.isStoredIn(m_typeResolver->varType())) {
1376 // Since we have verified the type in qqmljstypepropagator.cpp we now know
1377 // that we can only have either undefined or the actual type here. Therefore,
1378 // it's enough to check the QVariant for isValid().
1379 m_body += u"if (!"_s + variable + u".isValid()) {\n "_s;
1380 needsVarContentConversion = true;
1381 processedErrorMessage = errorMessage.arg(u"undefined");
1382 } else {
1383 REJECT<bool>(u"retrieving metatype from %1"_s.arg(actual.descriptiveName()));
1384 }
1385
1386 generateSetInstructionPointer();
1387 m_body += u" aotContext->engine->throwError(QJSValue::TypeError, "_s;
1388 m_body += u"QLatin1String(\"%1\"));\n"_s.arg(processedErrorMessage);
1389 generateReturnError();
1390 m_body += u"}\n"_s;
1391 return needsVarContentConversion;
1392}
1393
1394QString QQmlJSCodeGenerator::generateCallConstructor(
1395 const QQmlJSMetaMethod &ctor, const QList<QQmlJSRegisterContent> &argumentTypes,
1396 const QStringList &arguments, const QString &metaType, const QString &metaObject)
1397{
1398 const auto parameterTypes = ctor.parameters();
1399 Q_ASSERT(parameterTypes.length() == argumentTypes.length());
1400
1401 // We need to store the converted arguments in a temporaries because they might not be lvalues.
1402 QStringList argPointers;
1403
1404 QString result = u"[&](){\n"_s;
1405 for (qsizetype i = 0, end = parameterTypes.length(); i < end; ++i) {
1406 const QQmlJSRegisterContent argumentType = argumentTypes[i];
1407 const QQmlJSScope::ConstPtr parameterType = parameterTypes[i].type();
1408 const QString argument = arguments[i];
1409 const QString arg = u"arg"_s + QString::number(i);
1410
1411 result += u" auto "_s + arg + u" = "_s;
1412 if (argumentType.contains(parameterType)) {
1413 result += argument;
1414 argPointers.append(contentPointer(argumentType, arg));
1415 } else {
1416 const QQmlJSRegisterContent parameterTypeConversion
1417 = m_pool->storedIn(
1418 m_typeResolver->convert(argumentType, parameterType),
1419 m_typeResolver->genericType(parameterType));
1420 result += conversion(argumentType, parameterTypeConversion, argument);
1421 argPointers.append(contentPointer(parameterTypeConversion, arg));
1422 }
1423 result += u";\n"_s;
1424 }
1425
1426 result += u" void *args[] = {"_s + argPointers.join(u',') + u"};\n"_s;
1427 result += u" return aotContext->constructValueType("_s + metaType + u", "_s + metaObject
1428 + u", "_s + QString::number(int(ctor.constructorIndex())) + u", args);\n"_s;
1429
1430 return result + u"}()"_s;
1431}
1432
1434 const QQmlJSTypeResolver *typeResolver, const QQmlJSRegisterContent &baseType)
1435{
1436 const QQmlJSScope::ConstPtr contained = baseType.containedType();
1437 switch (contained->accessSemantics()) {
1440 // References and namespaces can't be affected by side effects.
1441 return false;
1443 // Value types can have inner objects, and we may have pre-created them where the
1444 // interpreter keeps them in JavaScript object form for longer.
1445 // TODO: We can probably improve here.
1446 return !typeResolver->isPrimitive(contained);
1448 // List properties are never affected by side effects
1449 if (contained->isListProperty())
1450 return false;
1451
1452 switch (baseType.variant()) {
1453 case QQmlJSRegisterContent::Operation:
1454 case QQmlJSRegisterContent::Literal: {
1455 // Stack-created lists of primitives and pointers can't be affected by side effects
1456 const QQmlJSScope::ConstPtr elementContained = contained->elementType();
1457 return !elementContained->isReferenceType()
1458 && !typeResolver->isPrimitive(elementContained);
1459 }
1460 default:
1461 return true;
1462 }
1463 }
1464 }
1465 return true;
1466}
1467
1468bool QQmlJSCodeGenerator::isRegisterAffectedBySideEffects(int registerIndex)
1469{
1470 if (!m_state.isRegisterAffectedBySideEffects(registerIndex))
1471 return false;
1472
1473 QQmlJSRegisterContent baseType = registerType(registerIndex);
1474 if (baseType.isConversion()) {
1475 // A conversion can be affected by side effects if any of its origins can.
1476 // Conversions are unrolled on creation, so we don't have to recurse.
1477
1478 const auto &origins = baseType.conversionOrigins();
1479 for (QQmlJSRegisterContent origin : origins) {
1480 if (canTypeBeAffectedBySideEffects(m_typeResolver, m_typeResolver->original(origin)))
1481 return true;
1482 }
1483
1484 return false;
1485 }
1486
1487 return canTypeBeAffectedBySideEffects(m_typeResolver, m_typeResolver->original(baseType));
1488}
1489
1490QString QQmlJSCodeGenerator::resolveValueTypeContentPointer(
1491 const QQmlJSScope::ConstPtr &required, QQmlJSRegisterContent actual,
1492 const QString &variable, const QString &errorMessage)
1493{
1494 if (generateContentPointerCheck(required, actual, variable, errorMessage))
1495 return variable + u".data()"_s;
1496 return contentPointer(actual, variable);
1497}
1498
1499QString QQmlJSCodeGenerator::resolveQObjectPointer(
1500 const QQmlJSScope::ConstPtr &required, QQmlJSRegisterContent actual,
1501 const QString &variable, const QString &errorMessage)
1502{
1503 if (generateContentPointerCheck(required, actual, variable, errorMessage))
1504 return u"*static_cast<QObject *const *>("_s + variable + u".constData())"_s;
1505 return variable;
1506}
1507
1508void QQmlJSCodeGenerator::generate_GetLookup(int index)
1509{
1510 INJECT_TRACE_INFO(generate_GetLookup);
1511 generate_GetLookupHelper(index);
1512}
1513
1514QString QQmlJSCodeGenerator::generateVariantMapGetLookup(
1515 const QString &map, const int nameIndex)
1516{
1517 const QString mapLookup = map
1518 + u"["_s + QQmlJSUtils::toLiteral(m_jsUnitGenerator->lookupName(nameIndex)) + u"]"_s;
1519
1520 return m_state.accumulatorVariableOut + u" = "_s
1521 + conversion(m_typeResolver->varType(), m_state.accumulatorOut(), mapLookup)
1522 + u";\n"_s;
1523}
1524
1525QString QQmlJSCodeGenerator::generateVariantMapSetLookup(
1526 const QString &map, const int nameIndex,
1527 const QQmlJSScope::ConstPtr &property, const QString &variableIn)
1528{
1529 const QString mapLookup = map
1530 + u"["_s + QQmlJSUtils::toLiteral(m_jsUnitGenerator->lookupName(nameIndex)) + u"]"_s;
1531
1532 return mapLookup + u" = "_s
1533 + conversion(property, m_typeResolver->varType(), variableIn)
1534 + u";\n"_s;
1535}
1536
1537void QQmlJSCodeGenerator::generate_GetLookupHelper(int index)
1538{
1539 if (m_state.accumulatorOut().isMethod())
1540 REJECT(u"lookup of function property."_s);
1541
1542 if (m_state.accumulatorOut().scope().contains(m_typeResolver->mathObject())) {
1543 QString name = m_jsUnitGenerator->lookupName(index);
1544
1545 double value{};
1546 if (name == u"E") {
1547 value = std::exp(1.0);
1548 } else if (name == u"LN10") {
1549 value = log(10.0);
1550 } else if (name == u"LN2") {
1551 value = log(2.0);
1552 } else if (name == u"LOG10E") {
1553 value = log10(std::exp(1.0));
1554 } else if (name == u"LOG2E") {
1555 value = log2(std::exp(1.0));
1556 } else if (name == u"PI") {
1557 value = 3.14159265358979323846;
1558 } else if (name == u"SQRT1_2") {
1559 value = std::sqrt(0.5);
1560 } else if (name == u"SQRT2") {
1561 value = std::sqrt(2.0);
1562 } else {
1563 Q_UNREACHABLE();
1564 }
1565
1566 m_body += m_state.accumulatorVariableOut + u" = "_s
1567 + conversion(m_typeResolver->realType(), m_state.accumulatorOut(), toNumericString(value))
1568 + u";\n"_s;
1569 return;
1570 }
1571
1572 if (m_state.accumulatorOut().isImportNamespace()) {
1573 Q_ASSERT(m_state.accumulatorOut().variant() == QQmlJSRegisterContent::ModulePrefix);
1574 // If we have an object module prefix, we need to pass through the original object.
1575 if (m_state.accumulatorVariableIn != m_state.accumulatorVariableOut) {
1576 m_body += m_state.accumulatorVariableOut + u" = "_s
1577 + conversion(m_state.accumulatorIn(), m_state.accumulatorOut(),
1578 m_state.accumulatorVariableIn)
1579 + u";\n"_s;
1580 }
1581 return;
1582 }
1583
1584 AccumulatorConverter registers(this);
1585
1586 if (m_state.accumulatorOut().isEnumeration()) {
1587 generateEnumLookup(index);
1588 return;
1589 }
1590
1591 const QString indexString = QString::number(index);
1592 const QString namespaceString = m_state.accumulatorIn().isImportNamespace()
1593 ? QString::number(m_state.accumulatorIn().importNamespace())
1594 : u"QQmlPrivate::AOTCompiledContext::InvalidStringId"_s;
1595 const auto accumulatorIn = m_state.accumulatorIn();
1596 const QQmlJSRegisterContent scope = m_state.accumulatorOut().scope();
1597 const QQmlJSRegisterContent originalScope
1598 = scope.original().isValid() ? scope.original() : scope;
1599 const bool isReferenceType = originalScope.containedType()->isReferenceType();
1600
1601 switch (m_state.accumulatorOut().variant()) {
1602 case QQmlJSRegisterContent::Attachment: {
1603 if (isQmlScopeObject(m_state.accumulatorOut().attachee())) {
1604 generateTypeLookup(index);
1605 return;
1606 }
1607 if (!isReferenceType) {
1608 // This can happen on incomplete type information. We contextually know that the
1609 // type must be a QObject, but we cannot construct the inheritance chain. Then we
1610 // store it in a generic type. Technically we could even convert it to QObject*, but
1611 // that would be expensive.
1612 REJECT(u"attached object for non-QObject type"_s);
1613 }
1614
1615 if (!m_state.accumulatorIn().storedType()->isReferenceType()) {
1616 // This can happen if we retroactively determine that the property might not be
1617 // what we think it is (ie, it can be shadowed).
1618 REJECT(u"attached object of potentially non-QObject base"_s);
1619 }
1620
1621 rejectIfNonQObjectOut(u"non-QObject attached type"_s);
1622
1623 const QString lookup = u"aotContext->loadAttachedLookup("_s + indexString
1624 + u", "_s + m_state.accumulatorVariableIn
1625 + u", &"_s + m_state.accumulatorVariableOut + u')';
1626 const QString initialization = u"aotContext->initLoadAttachedLookup("_s
1627 + indexString + u", "_s + namespaceString + u", "_s
1628 + m_state.accumulatorVariableIn + u')';
1629 generateLookup(lookup, initialization);
1630 return;
1631 }
1632 case QQmlJSRegisterContent::Singleton:
1633 case QQmlJSRegisterContent::Script:
1634 case QQmlJSRegisterContent::MetaType: {
1635 generateTypeLookup(index);
1636 return;
1637 }
1638 default:
1639 break;
1640 }
1641
1642 Q_ASSERT(m_state.accumulatorOut().isProperty());
1643
1644 if (accumulatorIn.isStoredIn(m_typeResolver->jsValueType())) {
1645 REJECT(u"lookup in QJSValue"_s);
1646 } else if (isReferenceType) {
1647 const QString inputPointer = resolveQObjectPointer(
1648 scope.containedType(), accumulatorIn, m_state.accumulatorVariableIn,
1649 u"Cannot read property '%1' of %2"_s.arg(
1650 m_jsUnitGenerator->lookupName(index)));
1651 const QString lookup = u"aotContext->getObjectLookup("_s + indexString
1652 + u", "_s + inputPointer + u", "_s
1653 + contentPointer(m_state.accumulatorOut(), m_state.accumulatorVariableOut) + u')';
1654 const QString initialization = (m_state.isShadowable()
1655 ? u"aotContext->initGetObjectLookupAsVariant("_s
1656 : u"aotContext->initGetObjectLookup("_s)
1657 + indexString + u", "_s + inputPointer + u')';
1658 const QString preparation = getLookupPreparation(
1659 m_state.accumulatorOut(), m_state.accumulatorVariableOut, index);
1660 recordPropertyLookup(m_typeResolver->originalContainedType(m_state.accumulatorOut().scope()),
1661 originalProperty(m_state.accumulatorOut()));
1662 generateLookup(lookup, initialization, preparation);
1663 } else if ((originalScope.containedType()->accessSemantics()
1664 == QQmlJSScope::AccessSemantics::Sequence
1665 || originalScope.contains(m_typeResolver->stringType()))
1666 && m_jsUnitGenerator->lookupName(index) == u"length"_s) {
1667 const QQmlJSScope::ConstPtr stored = accumulatorIn.storedType();
1668 if (stored->isListProperty()) {
1669 m_body += m_state.accumulatorVariableOut + u" = "_s;
1670 m_body += conversion(
1671 originalType(m_state.accumulatorOut()),
1672 m_state.accumulatorOut(),
1673 m_state.accumulatorVariableIn + u".count("_s + u'&'
1674 + m_state.accumulatorVariableIn + u')');
1675 m_body += u";\n"_s;
1676 } else if (stored->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
1677 || stored == m_typeResolver->stringType()) {
1678 m_body += m_state.accumulatorVariableOut + u" = "_s
1679 + conversion(originalType(m_state.accumulatorOut()),
1680 m_state.accumulatorOut(),
1681 m_state.accumulatorVariableIn + u".length()"_s)
1682 + u";\n"_s;
1683 } else if (originalScope.contains(m_typeResolver->stringType())) {
1684 m_body += m_state.accumulatorVariableOut + u" = "_s
1685 + conversion(
1686 m_typeResolver->sizeType(), m_state.accumulatorOut(),
1687 conversion(m_state.accumulatorIn(), m_typeResolver->stringType(),
1688 m_state.accumulatorVariableIn)
1689 + u".length()"_s)
1690 + u";\n"_s;
1691 } else {
1692 REJECT(u"access to 'length' property of sequence wrapped in non-sequence"_s);
1693 }
1694 } else if (accumulatorIn.isStoredIn(m_typeResolver->variantMapType())) {
1695 m_body += generateVariantMapGetLookup(m_state.accumulatorVariableIn, index);
1696 } else {
1697 if (m_state.isRegisterAffectedBySideEffects(Accumulator))
1698 REJECT(u"reading from a value that's potentially affected by side effects"_s);
1699
1700 const QString inputContentPointer = resolveValueTypeContentPointer(
1701 scope.containedType(), accumulatorIn, m_state.accumulatorVariableIn,
1702 u"Cannot read property '%1' of %2"_s.arg(
1703 m_jsUnitGenerator->lookupName(index)));
1704
1705 if (scope.contains(m_typeResolver->variantMapType())) {
1706 m_body += generateVariantMapGetLookup(
1707 u"(*static_cast<const QVariantMap *>("_s
1708 + inputContentPointer + u"))"_s, index);
1709 return;
1710 }
1711
1712 const QString lookup = u"aotContext->getValueLookup("_s + indexString
1713 + u", "_s + inputContentPointer
1714 + u", "_s + contentPointer(m_state.accumulatorOut(), m_state.accumulatorVariableOut)
1715 + u')';
1716 const QString initialization = u"aotContext->initGetValueLookup("_s
1717 + indexString + u", "_s
1718 + metaObject(scope.containedType()) + u')';
1719 const QString preparation = getLookupPreparation(
1720 m_state.accumulatorOut(), m_state.accumulatorVariableOut, index);
1721 recordPropertyLookup(m_typeResolver->originalContainedType(scope),
1722 originalProperty(m_state.accumulatorOut()));
1723 generateLookup(lookup, initialization, preparation);
1724 }
1725}
1726
1727void QQmlJSCodeGenerator::generate_GetOptionalLookup(int index, int offset)
1728{
1729 INJECT_TRACE_INFO(generate_GetOptionalLookup);
1730
1731 const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
1732 QString accumulatorVarIn = m_state.accumulatorVariableIn;
1733
1734 const auto &annotation = m_annotations[currentInstructionOffset()];
1735 if (accumulatorIn.storedType()->isReferenceType()) {
1736 m_body += u"if (!%1)\n"_s.arg(accumulatorVarIn);
1737 generateJumpCodeWithTypeConversions(offset);
1738 } else if (accumulatorIn.isStoredIn(m_typeResolver->varType())) {
1739 m_body += u"if (!%1.isValid() || ((%1.metaType().flags() & QMetaType::PointerToQObject) "
1740 "&& %1.value<QObject *>() == nullptr))\n"_s.arg(accumulatorVarIn);
1741 generateJumpCodeWithTypeConversions(offset);
1742 } else if (accumulatorIn.isStoredIn(m_typeResolver->jsPrimitiveType())) {
1743 m_body += u"if (%1.equals(QJSPrimitiveUndefined()) "
1744 "|| %1.equals(QJSPrimitiveNull()))\n"_s.arg(accumulatorVarIn);
1745 generateJumpCodeWithTypeConversions(offset);
1746 } else if (annotation.changedRegisterIndex == Accumulator
1747 && annotation.changedRegister.variant() == QQmlJSRegisterContent::Enum) {
1748 // Nothing
1749 } else if (accumulatorIn.isStoredIn(m_typeResolver->jsValueType())) {
1750 m_body += u"if (%1.isNull() || %1.isUndefined())\n"_s.arg(accumulatorVarIn);
1751 generateJumpCodeWithTypeConversions(offset);
1752 } else if (!m_typeResolver->canHoldUndefined(accumulatorIn.storage())) {
1753 // The base cannot hold undefined and isn't a reference type, generate a regular get lookup
1754 } else {
1755 Q_UNREACHABLE(); // No other accumulatorIn stored types should be possible
1756 }
1757
1758 generate_GetLookupHelper(index);
1759}
1760
1761void QQmlJSCodeGenerator::generate_StoreProperty(int nameIndex, int baseReg)
1762{
1763 Q_UNUSED(nameIndex)
1764 Q_UNUSED(baseReg)
1765 REJECT(u"StoreProperty"_s);
1766}
1767
1768// TODO: This shouldn't be necessary. If the content can be stored directly, then it should
1769// be stored and used directly. If it cannot be stored directly, it should be stored
1770// as QVariant, but then we cannot dereference the content pointer either.
1771static QString derefContentPointer(const QString &contentPointer)
1772{
1773 Q_ASSERT(contentPointer.startsWith(u'&') || contentPointer[0].isLetterOrNumber());
1774 return contentPointer.startsWith(u'&') ? contentPointer.mid(1) : (u'*' + contentPointer);
1775}
1776
1777void QQmlJSCodeGenerator::generate_SetLookup(int index, int baseReg)
1778{
1779 INJECT_TRACE_INFO(generate_SetLookup);
1780
1781 const QString indexString = QString::number(index);
1782 const QQmlJSScope::ConstPtr valueType = m_state.accumulatorIn().storedType();
1783 const QQmlJSRegisterContent property = m_state.readAccumulator();
1784 Q_ASSERT(property.isConversion());
1785 const QQmlJSRegisterContent original
1786 = m_typeResolver->original(property.conversionResultScope());
1787 const QQmlJSScope::ConstPtr originalScope = original.containedType();
1788
1789 const QString &propertyName = m_jsUnitGenerator->lookupName(index);
1790 if (property.storedType().isNull()) {
1791 REJECT(u"SetLookup. Could not find property " + propertyName + u" on type "
1792 + originalScope->internalName());
1793 }
1794
1795 const QString object = registerVariable(baseReg);
1796 m_body += u"{\n"_s;
1797 QString variableIn;
1798 if (!m_state.accumulatorIn().contains(property.containedType())) {
1799 m_body += property.storedType()->augmentedInternalName() + u" converted = "_s
1800 + conversion(m_state.accumulatorIn(), property, consumedAccumulatorVariableIn())
1801 + u";\n"_s;
1802 variableIn = contentPointer(property, u"converted"_s);
1803 } else {
1804 variableIn = contentPointer(property, m_state.accumulatorVariableIn);
1805 }
1806
1807 switch (originalScope->accessSemantics()) {
1808 case QQmlJSScope::AccessSemantics::Reference: {
1809 const QString basePointer = resolveQObjectPointer(
1810 originalScope, registerType(baseReg), object,
1811 u"TypeError: Value is %1 and could not be converted to an object"_s);
1812
1813 const QString lookup = u"aotContext->setObjectLookup("_s + indexString
1814 + u", "_s + basePointer + u", "_s + variableIn + u')';
1815
1816 // We use the asVariant lookup also for non-shadowable properties if the input can hold
1817 // undefined since that may be a reset. See QQmlJSTypePropagator::generate_StoreProperty().
1818 const QString initialization
1819 = (property.contains(m_typeResolver->varType())
1820 ? u"aotContext->initSetObjectLookupAsVariant("_s
1821 : u"aotContext->initSetObjectLookup("_s)
1822 + indexString + u", "_s + basePointer + u')';
1823 recordPropertyLookup(originalScope, originalScope->property(propertyName));
1824 generateLookup(lookup, initialization);
1825 break;
1826 }
1827 case QQmlJSScope::AccessSemantics::Sequence: {
1828 if (propertyName != u"length"_s)
1829 REJECT(u"setting non-length property on a sequence type"_s);
1830
1831 if (!originalScope->isListProperty())
1832 REJECT(u"resizing sequence types (because of missing write-back)"_s);
1833
1834 // We can resize without write back on a list property because it's actually a reference.
1835 m_body += u"const int begin = "_s + object + u".count(&" + object + u");\n"_s;
1836 m_body += u"const int end = "_s + derefContentPointer(variableIn) + u";\n"_s;
1837 m_body += u"for (int i = begin; i < end; ++i)\n"_s;
1838 m_body += u" "_s + object + u".append(&"_s + object + u", nullptr);\n"_s;
1839 m_body += u"for (int i = begin; i > end; --i)\n"_s;
1840 m_body += u" "_s + object + u".removeLast(&"_s + object + u')'
1841 + u";\n"_s;
1842 break;
1843 }
1844 case QQmlJSScope::AccessSemantics::Value: {
1845 const QQmlJSRegisterContent base = registerType(baseReg);
1846 if (base.isStoredIn(m_typeResolver->variantMapType())) {
1847 m_body += generateVariantMapSetLookup(
1848 registerVariable(baseReg), index, property.storedType(),
1849 derefContentPointer(variableIn));
1850 generateWriteBack(baseReg);
1851 break;
1852 }
1853 const QString baseContentPointer = resolveValueTypeContentPointer(
1854 originalScope, base, object,
1855 u"TypeError: Value is %1 and could not be converted to an object"_s);
1856
1857 if (original.contains(m_typeResolver->variantMapType())) {
1858 m_body += generateVariantMapSetLookup(
1859 u"(*static_cast<const QVariantMap *>("_s
1860 + baseContentPointer + u"))"_s, index, property.storedType(),
1861 derefContentPointer(variableIn));
1862 generateWriteBack(baseReg);
1863 break;
1864 }
1865
1866 const QString lookup = u"aotContext->setValueLookup("_s + indexString
1867 + u", "_s + baseContentPointer
1868 + u", "_s + variableIn + u')';
1869
1870 // We use the asVariant lookup also for non-shadowable properties if the input can hold
1871 // undefined since that may be a reset. See QQmlJSTypePropagator::generate_StoreProperty().
1872 const QString initialization
1873 = (property.contains(m_typeResolver->varType())
1874 ? u"aotContext->initSetValueLookupAsVariant("_s
1875 : u"aotContext->initSetValueLookup("_s)
1876 + indexString + u", "_s + metaObject(originalScope) + u')';
1877 recordPropertyLookup(m_typeResolver->originalContainedType(base),
1878 originalScope->property(propertyName));
1879 generateLookup(lookup, initialization);
1880 generateWriteBack(baseReg);
1881
1882 break;
1883 }
1884 case QQmlJSScope::AccessSemantics::None:
1885 Q_UNREACHABLE();
1886 break;
1887 }
1888
1889 m_body += u"}\n"_s;
1890}
1891
1892void QQmlJSCodeGenerator::generate_LoadSuperProperty(int property)
1893{
1894 Q_UNUSED(property)
1896}
1897
1898void QQmlJSCodeGenerator::generate_StoreSuperProperty(int property)
1899{
1900 Q_UNUSED(property)
1902}
1903
1904void QQmlJSCodeGenerator::generate_Yield()
1905{
1907}
1908
1909void QQmlJSCodeGenerator::generate_YieldStar()
1910{
1912}
1913
1914void QQmlJSCodeGenerator::generate_Resume(int)
1915{
1917}
1918
1919QString QQmlJSCodeGenerator::initAndCall(
1920 int argc, int argv, const QString &callMethodTemplate, const QString &initMethodTemplate,
1921 QString *outVar)
1922{
1923 QString args;
1924
1925 if (m_state.changedRegisterIndex() == InvalidRegister ||
1926 m_state.accumulatorOut().contains(m_typeResolver->voidType())) {
1927 args = u"nullptr"_s;
1928 } else {
1929 *outVar = u"callResult"_s;
1930 const QQmlJSScope::ConstPtr outType = m_state.accumulatorOut().storedType();
1931 m_body += outType->augmentedInternalName() + u' ' + *outVar;
1932 m_body += u";\n";
1933
1934 args = contentPointer(m_state.accumulatorOut(), *outVar);
1935 }
1936
1937 // We may need to convert the arguments to the function call so that they match what the
1938 // function expects. They are passed as void* after all. We try to convert them where they
1939 // are created, but if they are read as different types in multiple places, we can't.
1940 QString argumentPreparation;
1941 for (int i = 0; i < argc; ++i) {
1942 if (isRegisterAffectedBySideEffects(argv + i))
1943 REJECT<QString>(u"calling method with argument affected by side effects"_s);
1944 const QQmlJSRegisterContent content = registerType(argv + i);
1945 const QQmlJSRegisterContent read = m_state.readRegister(argv + i);
1946 if (read.contains(content.containedType())) {
1947 args += u", "_s + contentPointer(read, registerVariable(argv + i));
1948 } else {
1949 const QString var = u"arg"_s + QString::number(i);
1950 argumentPreparation +=
1951 u" "_s + read.storedType()->augmentedInternalName() + u' ' + var + u" = "_s
1952 + conversion(content, read, consumedRegisterVariable(argv + i)) + u";\n";
1953 args += u", "_s + contentPointer(read, var);
1954 }
1955 }
1956
1957 QString initMethod;
1958
1959 if (m_state.isShadowable()) {
1960 initMethod = initMethodTemplate;
1961 } else {
1962 const QQmlJSMetaMethod method = m_state.accumulatorOut().methodCall();
1963 Q_ASSERT(!method.isConstructor());
1964
1965 initMethod = initMethodTemplate.arg(int(method.methodIndex()));
1966 }
1967
1968 return u"const auto doCall = [&]() {\n"_s
1969 + argumentPreparation
1970 + u" void *args[] = {" + args + u"};\n"_s
1971 + u" return aotContext->"_s + callMethodTemplate.arg(u"args"_s).arg(argc) + u";\n"
1972 + u"};\n"_s
1973 + u"const auto doInit = [&]() {\n"_s
1974 + u" aotContext->"_s + initMethod + u";\n"
1975 + u"};\n"_s;
1976}
1977
1978void QQmlJSCodeGenerator::generateMoveOutVarAfterCall(const QQmlJSScope::ConstPtr &scope,
1979 const QString &outVar)
1980{
1981 if (m_state.accumulatorVariableOut.isEmpty() || outVar.isEmpty())
1982 return;
1983
1984 m_body += m_state.accumulatorVariableOut + u" = "_s + u"std::move(" + outVar + u");\n";
1985
1986 // Don't transfer ownership if we've called a JavaScript function.
1987 if (scope->isComposite())
1988 return;
1989
1990 m_body += u"aotContext->setImplicitDestructible("_s
1991 + m_state.accumulatorVariableOut + u");\n"_s;
1992}
1993
1994void QQmlJSCodeGenerator::generate_CallValue(int name, int argc, int argv)
1995{
1996 Q_UNUSED(name)
1997 Q_UNUSED(argc)
1998 Q_UNUSED(argv)
2000}
2001
2002void QQmlJSCodeGenerator::generate_CallWithReceiver(int name, int thisObject, int argc, int argv)
2003{
2004 Q_UNUSED(name)
2005 Q_UNUSED(thisObject)
2006 Q_UNUSED(argc)
2007 Q_UNUSED(argv)
2009}
2010
2011void QQmlJSCodeGenerator::generate_CallProperty(int nameIndex, int baseReg, int argc, int argv)
2012{
2013 Q_UNUSED(nameIndex);
2014 Q_UNUSED(baseReg);
2015 Q_UNUSED(argc);
2016 Q_UNUSED(argv);
2017 REJECT(u"CallProperty"_s);
2018}
2019
2020bool QQmlJSCodeGenerator::inlineStringMethod(const QString &name, int base, int argc, int argv)
2021{
2022 if (name != u"arg"_s || argc != 1)
2023 return false;
2024
2025 const auto arg = [&](const QQmlJSScope::ConstPtr &type) {
2026 return convertStored(registerType(argv).storedType(), type, consumedRegisterVariable(argv));
2027 };
2028
2029 const auto ret = [&](const QString &arg) {
2030 const QString expression = convertStored(
2031 registerType(base).storedType(), m_typeResolver->stringType(),
2032 consumedRegisterVariable(base)) + u".arg("_s + arg + u')';
2033 return conversion(
2034 m_typeResolver->stringType(), m_state.accumulatorOut(), expression);
2035 };
2036
2037 const QQmlJSRegisterContent input = m_state.readRegister(argv);
2038 m_body += m_state.accumulatorVariableOut + u" = "_s;
2039
2040 if (m_typeResolver->isNumeric(input))
2041 m_body += ret(arg(input.containedType()));
2042 else if (input.contains(m_typeResolver->boolType()))
2043 m_body += ret(arg(m_typeResolver->boolType()));
2044 else
2045 m_body += ret(arg(m_typeResolver->stringType()));
2046 m_body += u";\n"_s;
2047 return true;
2048}
2049
2050bool QQmlJSCodeGenerator::inlineTranslateMethod(const QString &name, int argc, int argv)
2051{
2052 addInclude(u"QtCore/qcoreapplication.h"_s);
2053
2054 const auto arg = [&](int i, const QQmlJSScope::ConstPtr &type) {
2055 Q_ASSERT(i < argc);
2056 return convertStored(registerType(argv + i).storedType(), type,
2057 consumedRegisterVariable(argv + i));
2058 };
2059
2060 const auto stringArg = [&](int i) {
2061 return i < argc
2062 ? (arg(i, m_typeResolver->stringType()) + u".toUtf8().constData()"_s)
2063 : u"\"\""_s;
2064 };
2065
2066 const auto intArg = [&](int i) {
2067 return i < argc ? arg(i, m_typeResolver->int32Type()) : u"-1"_s;
2068 };
2069
2070 const auto stringRet = [&](const QString &expression) {
2071 return conversion(
2072 m_typeResolver->stringType(), m_state.accumulatorOut(), expression);
2073 };
2074
2075 const auto capture = [&]() {
2076 m_body += u"aotContext->captureTranslation();\n"_s;
2077 };
2078
2079 if (name == u"QT_TRID_NOOP"_s || name == u"QT_TR_NOOP"_s) {
2080 Q_ASSERT(argc > 0);
2081 m_body += m_state.accumulatorVariableOut + u" = "_s
2082 + stringRet(arg(0, m_typeResolver->stringType())) + u";\n"_s;
2083 return true;
2084 }
2085
2086 if (name == u"QT_TRANSLATE_NOOP"_s) {
2087 Q_ASSERT(argc > 1);
2088 m_body += m_state.accumulatorVariableOut + u" = "_s
2089 + stringRet(arg(1, m_typeResolver->stringType())) + u";\n"_s;
2090 return true;
2091 }
2092
2093 if (name == u"qsTrId"_s) {
2094 capture();
2095 // We inline qtTrId() here because in the !QT_CONFIG(translation) case it's unavailable.
2096 // QCoreApplication::translate() is always available in some primitive form.
2097 // Also, this saves a function call.
2098 m_body += m_state.accumulatorVariableOut + u" = "_s
2099 + stringRet(u"QCoreApplication::translate(nullptr, "_s + stringArg(0) +
2100 u", nullptr, "_s + intArg(1) + u")"_s) + u";\n"_s;
2101 return true;
2102 }
2103
2104 if (name == u"qsTr"_s) {
2105 capture();
2106 m_body += m_state.accumulatorVariableOut + u" = "_s
2107 + stringRet(u"QCoreApplication::translate("_s
2108 + u"aotContext->translationContext().toUtf8().constData(), "_s
2109 + stringArg(0) + u", "_s + stringArg(1) + u", "_s
2110 + intArg(2) + u")"_s) + u";\n"_s;
2111 return true;
2112 }
2113
2114 if (name == u"qsTranslate"_s) {
2115 capture();
2116 m_body += m_state.accumulatorVariableOut + u" = "_s
2117 + stringRet(u"QCoreApplication::translate("_s
2118 + stringArg(0) + u", "_s + stringArg(1) + u", "_s
2119 + stringArg(2) + u", "_s + intArg(3) + u")"_s) + u";\n"_s;
2120 return true;
2121 }
2122
2123 return false;
2124}
2125
2126static QString maxExpression(int argc)
2127{
2128 Q_ASSERT_X(argc >= 2, Q_FUNC_INFO, "max() expects at least two arguments.");
2129
2130 QString expression =
2131 u"[&]() { \nauto tmpMax = (qIsNull(arg2) && qIsNull(arg1) && std::copysign(1.0, arg2) == 1) ? arg2 : ((arg2 > arg1 || std::isnan(arg2)) ? arg2 : arg1);\n"_s;
2132 for (int i = 2; i < argc; i++) {
2133 expression +=
2134 "\ttmpMax = (qIsNull(%1) && qIsNull(tmpMax) && std::copysign(1.0, %1) == 1) ? arg2 : ((%1 > tmpMax || std::isnan(%1)) ? %1 : tmpMax);\n"_L1
2135 .arg("arg"_L1 + QString::number(i + 1));
2136 }
2137 expression += "return tmpMax;\n}()"_L1;
2138
2139 return expression;
2140}
2141
2142static QString minExpression(int argc)
2143{
2144 Q_ASSERT_X(argc >= 2, Q_FUNC_INFO, "min() expects at least two arguments.");
2145
2146 QString expression =
2147 u"[&]() { \nauto tmpMin = (qIsNull(arg2) && qIsNull(arg1) && std::copysign(1.0, arg2) == -1) ? arg2 : ((arg2 < arg1 || std::isnan(arg2)) ? arg2 : arg1);\n"_s;
2148 for (int i = 2; i < argc; i++) {
2149 expression +=
2150 "tmpMin = (qIsNull(%1) && qIsNull(tmpMin) && std::copysign(1.0, %1) == -1) ? arg2 : ((%1 < tmpMin || std::isnan(%1)) ? %1 : tmpMin);\n"_L1
2151 .arg("arg"_L1 + QString::number(i + 1));
2152 }
2153 expression += "return tmpMin;\n}()"_L1;
2154
2155 return expression;
2156}
2157
2158bool QQmlJSCodeGenerator::inlineMathMethod(const QString &name, int argc, int argv)
2159{
2160 addInclude(u"cmath"_s);
2161 addInclude(u"limits"_s);
2162 addInclude(u"QtCore/qalgorithms.h"_s);
2163 addInclude(u"QtCore/qrandom.h"_s);
2164 addInclude(u"QtQml/qjsprimitivevalue.h"_s);
2165
2166 // If the result is not stored, we don't need to generate any code. All the math methods are
2167 // conceptually pure functions.
2168 if (m_state.changedRegisterIndex() != Accumulator)
2169 return true;
2170
2171 m_body += u"{\n"_s;
2172 for (int i = 0; i < argc; ++i) {
2173 m_body += u"const double arg%1 = "_s.arg(i + 1) + convertStored(
2174 registerType(argv + i).storedType(),
2175 m_typeResolver->realType(), consumedRegisterVariable(argv + i))
2176 + u";\n"_s;
2177 }
2178
2179 const QString qNaN = u"std::numeric_limits<double>::quiet_NaN()"_s;
2180 const QString inf = u"std::numeric_limits<double>::infinity()"_s;
2181 m_body += m_state.accumulatorVariableOut + u" = "_s;
2182
2183 QString expression;
2184
2185 if (name == u"abs" && argc == 1) {
2186 expression = u"(qIsNull(arg1) ? 0 : (arg1 < 0.0 ? -arg1 : arg1))"_s;
2187 } else if (name == u"acos"_s && argc == 1) {
2188 expression = u"arg1 > 1.0 ? %1 : std::acos(arg1)"_s.arg(qNaN);
2189 } else if (name == u"acosh"_s && argc == 1) {
2190 expression = u"arg1 < 1.0 ? %1 : std::acosh(arg1)"_s.arg(qNaN);
2191 } else if (name == u"asin"_s && argc == 1) {
2192 expression = u"arg1 > 1.0 ? %1 : std::asin(arg1)"_s.arg(qNaN);
2193 } else if (name == u"asinh"_s && argc == 1) {
2194 expression = u"qIsNull(arg1) ? arg1 : std::asinh(arg1)"_s;
2195 } else if (name == u"atan"_s && argc == 1) {
2196 expression = u"qIsNull(arg1) ? arg1 : std::atan(arg1)"_s;
2197 } else if (name == u"atanh"_s && argc == 1) {
2198 expression = u"qIsNull(arg1) ? arg1 : std::atanh(arg1)"_s;
2199 } else if (name == u"atan2"_s) {
2200 // TODO: complicated
2201 return false;
2202 } else if (name == u"cbrt"_s && argc == 1) {
2203 expression = u"std::cbrt(arg1)"_s;
2204 } else if (name == u"ceil"_s && argc == 1) {
2205 expression = u"(arg1 < 0.0 && arg1 > -1.0) ? std::copysign(0.0, -1.0) : std::ceil(arg1)"_s;
2206 } else if (name == u"clz32"_s && argc == 1) {
2207 expression = u"qint32(qCountLeadingZeroBits(quint32(QJSNumberCoercion::toInteger(arg1))))"_s;
2208 } else if (name == u"cos"_s && argc == 1) {
2209 expression = u"std::cos(arg1)"_s;
2210 } else if (name == u"cosh"_s && argc == 1) {
2211 expression = u"std::cosh(arg1)"_s;
2212 } else if (name == u"exp"_s && argc == 1) {
2213 expression = u"std::isinf(arg1) "
2214 "? (std::copysign(1.0, arg1) == -1 ? 0.0 : %1) "
2215 ": std::exp(arg1)"_s.arg(inf);
2216 } else if (name == u"expm1"_s) {
2217 // TODO: complicated
2218 return false;
2219 } else if (name == u"floor"_s && argc == 1) {
2220 expression = u"std::floor(arg1)"_s;
2221 } else if (name == u"fround"_s && argc == 1) {
2222 expression = u"(std::isnan(arg1) || std::isinf(arg1) || qIsNull(arg1)) "
2223 "? arg1 "
2224 ": double(float(arg1))"_s;
2225 } else if (name == u"hypot"_s) {
2226 // TODO: complicated
2227 return false;
2228 } else if (name == u"imul"_s && argc == 2) {
2229 expression = u"qint32(quint32(QJSNumberCoercion::toInteger(arg1)) "
2230 "* quint32(QJSNumberCoercion::toInteger(arg2)))"_s;
2231 } else if (name == u"log"_s && argc == 1) {
2232 expression = u"arg1 < 0.0 ? %1 : std::log(arg1)"_s.arg(qNaN);
2233 } else if (name == u"log10"_s && argc == 1) {
2234 expression = u"arg1 < 0.0 ? %1 : std::log10(arg1)"_s.arg(qNaN);
2235 } else if (name == u"log1p"_s && argc == 1) {
2236 expression = u"arg1 < -1.0 ? %1 : std::log1p(arg1)"_s.arg(qNaN);
2237 } else if (name == u"log2"_s && argc == 1) {
2238 expression = u"arg1 < -0.0 ? %1 : std::log2(arg1)"_s.arg(qNaN);
2239 } else if (name == u"max"_s && argc >= 2) {
2240 expression = maxExpression(argc);
2241 } else if (name == u"min"_s && argc >= 2) {
2242 expression = minExpression(argc);
2243 } else if (name == u"pow"_s) {
2244 expression = u"QQmlPrivate::jsExponentiate(arg1, arg2)"_s;
2245 } else if (name == u"random"_s && argc == 0) {
2246 expression = u"QRandomGenerator::global()->generateDouble()"_s;
2247 } else if (name == u"round"_s && argc == 1) {
2248 expression = u"std::isfinite(arg1) "
2249 "? ((arg1 < 0.5 && arg1 >= -0.5) "
2250 "? std::copysign(0.0, arg1) "
2251 ": std::floor(arg1 + 0.5)) "
2252 ": arg1"_s;
2253 } else if (name == u"sign"_s && argc == 1) {
2254 expression = u"std::isnan(arg1) "
2255 "? %1 "
2256 ": (qIsNull(arg1) "
2257 "? arg1 "
2258 ": (std::signbit(arg1) ? -1.0 : 1.0))"_s.arg(qNaN);
2259 } else if (name == u"sin"_s && argc == 1) {
2260 expression = u"qIsNull(arg1) ? arg1 : std::sin(arg1)"_s;
2261 } else if (name == u"sinh"_s && argc == 1) {
2262 expression = u"qIsNull(arg1) ? arg1 : std::sinh(arg1)"_s;
2263 } else if (name == u"sqrt"_s && argc == 1) {
2264 expression = u"std::sqrt(arg1)"_s;
2265 } else if (name == u"tan"_s && argc == 1) {
2266 expression = u"qIsNull(arg1) ? arg1 : std::tan(arg1)"_s;
2267 } else if (name == u"tanh"_s && argc == 1) {
2268 expression = u"qIsNull(arg1) ? arg1 : std::tanh(arg1)"_s;
2269 } else if (name == u"trunc"_s && argc == 1) {
2270 expression = u"std::trunc(arg1)"_s;
2271 } else {
2272 return false;
2273 }
2274
2275 m_body += conversion(m_typeResolver->realType(), m_state.accumulatorOut(), expression);
2276
2277 m_body += u";\n"_s;
2278 m_body += u"}\n"_s;
2279 return true;
2280}
2281
2282static QString messageTypeForMethod(const QString &method)
2283{
2284 if (method == u"log" || method == u"debug")
2285 return u"QtDebugMsg"_s;
2286 if (method == u"info")
2287 return u"QtInfoMsg"_s;
2288 if (method == u"warn")
2289 return u"QtWarningMsg"_s;
2290 if (method == u"error")
2291 return u"QtCriticalMsg"_s;
2292 return QString();
2293}
2294
2295bool QQmlJSCodeGenerator::inlineConsoleMethod(const QString &name, int argc, int argv)
2296{
2297 const QString type = messageTypeForMethod(name);
2298 if (type.isEmpty())
2299 return false;
2300
2301 addInclude(u"QtCore/qloggingcategory.h"_s);
2302
2303 m_body += u"{\n";
2304 m_body += u" bool firstArgIsCategory = false;\n";
2305 const QQmlJSRegisterContent firstArg = argc > 0 ? registerType(argv) : QQmlJSRegisterContent();
2306
2307 // We could check whether the first argument is a QQmlLoggingCategoryBase here, and we should
2308 // because QQmlLoggingCategoryBase is now a builtin.
2309 // TODO: The run time check for firstArg is obsolete.
2310 const bool firstArgIsReference = argc > 0
2311 && firstArg.containedType()->isReferenceType();
2312
2313 if (firstArgIsReference) {
2314 m_body += u" QObject *firstArg = ";
2315 m_body += convertStored(
2316 firstArg.storedType(),
2317 m_typeResolver->genericType(firstArg.storedType()),
2318 registerVariable(argv));
2319 m_body += u";\n";
2320 }
2321
2322 m_body += u" const QLoggingCategory *category = aotContext->resolveLoggingCategory(";
2323 m_body += firstArgIsReference ? u"firstArg"_sv : u"nullptr"_sv;
2324 m_body += u", &firstArgIsCategory);\n";
2325 m_body += u" if (category && category->isEnabled(" + type + u")) {\n";
2326
2327 m_body += u" const QString message = ";
2328
2329 const auto stringConversion = [&](int i) -> QString {
2330 const QQmlJSScope::ConstPtr read = m_state.readRegister(argv + i).storedType();
2331 const QQmlJSScope::ConstPtr actual = registerType(argv + i).storedType();
2332 if (read == m_typeResolver->stringType()) {
2333 return convertStored(actual, read, consumedRegisterVariable(argv + i));
2334 } else if (actual->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence) {
2335 addInclude(u"QtQml/qjslist.h"_s);
2336 return u"(u'[' + QJSList(&"_s + registerVariable(argv + i)
2337 + u", aotContext->engine).toString() + u']')"_s;
2338 } else {
2339 REJECT<QString>(u"converting arguments for console method to string"_s);
2340 }
2341 };
2342
2343 if (argc > 0) {
2344 if (firstArgIsReference) {
2345 const QString firstArgStringConversion = convertStored(
2346 registerType(argv).storedType(),
2347 m_typeResolver->stringType(), registerVariable(argv));
2348 m_body += u"(firstArgIsCategory ? QString() : (" + firstArgStringConversion;
2349 if (argc > 1)
2350 m_body += u".append(QLatin1Char(' ')))).append(";
2351 else
2352 m_body += u"))";
2353 } else {
2354 m_body += stringConversion(0);
2355 if (argc > 1)
2356 m_body += u".append(QLatin1Char(' ')).append(";
2357 }
2358
2359 for (int i = 1; i < argc; ++i) {
2360 if (i > 1)
2361 m_body += u".append(QLatin1Char(' ')).append("_s;
2362 m_body += stringConversion(i) + u')';
2363 }
2364 } else {
2365 m_body += u"QString()";
2366 }
2367 m_body += u";\n ";
2368 generateSetInstructionPointer();
2369 m_body += u" aotContext->writeToConsole(" + type + u", message, category);\n";
2370 m_body += u" }\n";
2371 m_body += u"}\n";
2372 return true;
2373}
2374
2375bool QQmlJSCodeGenerator::inlineArrayMethod(const QString &name, int base, int argc, int argv)
2376{
2377 const auto intType = m_typeResolver->int32Type();
2378 const auto elementType = registerType(base).storedType()->elementType();
2379 const auto boolType = m_typeResolver->boolType();
2380 const auto stringType = m_typeResolver->stringType();
2381 const auto baseType = registerType(base);
2382
2383 const QString baseVar = registerVariable(base);
2384 const QString qjsListMethod = u"QJSList(&"_s + baseVar + u", aotContext->engine)."
2385 + name + u"(";
2386
2387 addInclude(u"QtQml/qjslist.h"_s);
2388
2389 if (name == u"includes" && argc > 0 && argc < 3) {
2390 QString call = qjsListMethod
2391 + convertStored(registerType(argv).storedType(), elementType,
2392 consumedRegisterVariable(argv));
2393 if (argc == 2) {
2394 call += u", " + convertStored(registerType(argv + 1).storedType(), intType,
2395 consumedRegisterVariable(argv + 1));
2396 }
2397 call += u")";
2398
2399 m_body += m_state.accumulatorVariableOut + u" = "_s
2400 + conversion(boolType, m_state.accumulatorOut(), call) + u";\n"_s;
2401 return true;
2402 }
2403
2404 if (name == u"toString" || (name == u"join" && argc < 2)) {
2405 QString call = qjsListMethod;
2406 if (argc == 1) {
2407 call += convertStored(registerType(argv).storedType(), stringType,
2408 consumedRegisterVariable(argv));
2409 }
2410 call += u")";
2411
2412 m_body += m_state.accumulatorVariableOut + u" = "_s
2413 + conversion(stringType, m_state.accumulatorOut(), call) + u";\n"_s;
2414 return true;
2415 }
2416
2417 if (name == u"slice" && argc < 3) {
2418 QString call = qjsListMethod;
2419 for (int i = 0; i < argc; ++i) {
2420 if (i > 0)
2421 call += u", ";
2422 call += convertStored(registerType(argv + i).storedType(), intType,
2423 consumedRegisterVariable(argv + i));
2424 }
2425 call += u")";
2426
2427 m_body += m_state.accumulatorVariableOut + u" = "_s;
2428 if (baseType.storedType()->isListProperty())
2429 m_body += conversion(m_typeResolver->qObjectListType(), m_state.accumulatorOut(), call);
2430 else
2431 m_body += conversion(baseType, m_state.accumulatorOut(), call);
2432 m_body += u";\n"_s;
2433
2434 return true;
2435 }
2436
2437 if ((name == u"indexOf" || name == u"lastIndexOf") && argc > 0 && argc < 3) {
2438 QString call = qjsListMethod
2439 + convertStored(registerType(argv).storedType(), elementType,
2440 consumedRegisterVariable(argv));
2441 if (argc == 2) {
2442 call += u", " + convertStored(registerType(argv + 1).storedType(), intType,
2443 consumedRegisterVariable(argv + 1));
2444 }
2445 call += u")";
2446
2447 m_body += m_state.accumulatorVariableOut + u" = "_s
2448 + conversion(intType, m_state.accumulatorOut(), call) + u";\n"_s;
2449 return true;
2450 }
2451
2452 return false;
2453}
2454
2455void QQmlJSCodeGenerator::generate_CallPropertyLookup(int index, int base, int argc, int argv)
2456{
2457 INJECT_TRACE_INFO(generate_CallPropertyLookup);
2458
2459 const QQmlJSRegisterContent scopeContent = m_state.accumulatorOut().scope();
2460 const QQmlJSScope::ConstPtr scope = scopeContent.containedType();
2461
2462 AccumulatorConverter registers(this);
2463
2464 const QQmlJSRegisterContent baseType = registerType(base);
2465 const QString name = m_jsUnitGenerator->lookupName(index);
2466
2467 if (scope == m_typeResolver->mathObject()) {
2468 if (inlineMathMethod(name, argc, argv))
2469 return;
2470 } else if (scope == m_typeResolver->consoleObject()) {
2471 if (inlineConsoleMethod(name, argc, argv))
2472 return;
2473 } else if (scope == m_typeResolver->stringType()) {
2474 if (inlineStringMethod(name, base, argc, argv))
2475 return;
2476 } else if (baseType.storedType()->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence) {
2477 if (inlineArrayMethod(name, base, argc, argv))
2478 return;
2479 }
2480
2481 if (m_state.accumulatorOut().isJavaScriptReturnValue())
2482 REJECT(u"call to untyped JavaScript function"_s);
2483
2484 m_body += u"{\n"_s;
2485 QString outVar;
2486
2487 if (scope->isReferenceType()) {
2488 const QString inputPointer = resolveQObjectPointer(
2489 scope, baseType, registerVariable(base),
2490 u"Cannot call method '%1' of %2"_s.arg(name));
2491
2492 const QString initMethodTemplate = m_state.isShadowable()
2493 ? u"initCallObjectPropertyLookupAsVariant(%1, %2)"_s
2494 : u"initCallObjectPropertyLookup(%1, %2, %3)"_s;
2495
2496 m_body += initAndCall(
2497 argc, argv,
2498 u"callObjectPropertyLookup(%1, %2, %3, %4)"_s.arg(index).arg(inputPointer),
2499 initMethodTemplate.arg(index).arg(inputPointer), &outVar);
2500 } else {
2501 const QQmlJSScope::ConstPtr originalScope
2502 = m_typeResolver->original(scopeContent).containedType();
2503 const QString inputPointer = resolveValueTypeContentPointer(
2504 originalScope, baseType, registerVariable(base),
2505 u"Cannot call method '%1' of %2"_s.arg(name));
2506
2507 m_body += initAndCall(
2508 argc, argv,
2509 u"callValueLookup(%1, %2, %3, %4)"_s.arg(index).arg(inputPointer),
2510 u"initCallValueLookup(%1, %2, %3)"_s
2511 .arg(index).arg(metaObject(originalScope)),
2512 &outVar);
2513 }
2514
2515 const QString lookup = u"doCall()"_s;
2516 const QString initialization = u"doInit()"_s;
2517 const QString preparation = getLookupPreparation(m_state.accumulatorOut(), outVar, index);
2518 recordMethodLookup(m_typeResolver->originalContainedType(scopeContent),
2519 originalMethod(m_state.accumulatorOut()));
2520 generateLookup(lookup, initialization, preparation);
2521 generateMoveOutVarAfterCall(scope, outVar);
2522
2523 m_body += u"}\n"_s;
2524
2525 if (scope->isReferenceType())
2526 return;
2527
2528 const QQmlJSMetaMethod method = m_state.accumulatorOut().methodCall();
2529 if (!method.isConst())
2530 generateWriteBack(base);
2531}
2532
2533void QQmlJSCodeGenerator::generate_CallName(int name, int argc, int argv)
2534{
2535 Q_UNUSED(name);
2536 Q_UNUSED(argc);
2537 Q_UNUSED(argv);
2538 REJECT(u"CallName"_s);
2539}
2540
2541void QQmlJSCodeGenerator::generate_CallPossiblyDirectEval(int argc, int argv)
2542{
2543 Q_UNUSED(argc)
2544 Q_UNUSED(argv)
2546}
2547
2548void QQmlJSCodeGenerator::generate_CallGlobalLookup(int index, int argc, int argv)
2549{
2550 Q_UNUSED(index);
2551 Q_UNUSED(argc);
2552 Q_UNUSED(argv);
2553 REJECT(u"CallGlobalLookup"_s);
2554}
2555
2556void QQmlJSCodeGenerator::generate_CallQmlContextPropertyLookup(int index, int argc, int argv)
2557{
2558 INJECT_TRACE_INFO(generate_CallQmlContextPropertyLookup);
2559
2560 const QQmlJSRegisterContent scope = m_state.accumulatorOut().scope();
2561 if (scope.contains(m_typeResolver->jsGlobalObject())) {
2562 const QString name = m_jsUnitGenerator->stringForIndex(
2563 m_jsUnitGenerator->lookupNameIndex(index));
2564 if (inlineTranslateMethod(name, argc, argv))
2565 return;
2566 }
2567
2568 if (m_state.accumulatorOut().isJavaScriptReturnValue())
2569 REJECT(u"call to untyped JavaScript function"_s);
2570
2571 AccumulatorConverter registers(this);
2572
2573 m_body += u"{\n"_s;
2574 QString outVar;
2575 m_body += initAndCall(
2576 argc, argv, u"callQmlContextPropertyLookup(%1, %2, %3)"_s.arg(index),
2577 u"initCallQmlContextPropertyLookup(%1, %2)"_s.arg(index), &outVar);
2578
2579 const QString lookup = u"doCall()"_s;
2580 const QString initialization = u"doInit()"_s;
2581 const QString preparation = getLookupPreparation(m_state.accumulatorOut(), outVar, index);
2582 recordMethodLookup(m_typeResolver->originalContainedType(scope),
2583 originalMethod(m_state.accumulatorOut()));
2584 generateLookup(lookup, initialization, preparation);
2585 generateMoveOutVarAfterCall(scope.containedType(), outVar);
2586
2587 m_body += u"}\n"_s;
2588}
2589
2590void QQmlJSCodeGenerator::generate_CallWithSpread(int func, int thisObject, int argc, int argv)
2591{
2592 Q_UNUSED(func)
2593 Q_UNUSED(thisObject)
2594 Q_UNUSED(argc)
2595 Q_UNUSED(argv)
2597}
2598
2599void QQmlJSCodeGenerator::generate_TailCall(int func, int thisObject, int argc, int argv)
2600{
2601 Q_UNUSED(func)
2602 Q_UNUSED(thisObject)
2603 Q_UNUSED(argc)
2604 Q_UNUSED(argv)
2606}
2607
2608void QQmlJSCodeGenerator::generate_Construct(int func, int argc, int argv)
2609{
2610 INJECT_TRACE_INFO(generate_Construct);
2611 Q_UNUSED(func);
2612
2613 const auto originalResult = originalType(m_state.accumulatorOut());
2614
2615 if (originalResult.contains(m_typeResolver->dateTimeType())) {
2616 m_body += m_state.accumulatorVariableOut + u" = ";
2617 if (argc == 0) {
2618 m_body += conversion(
2619 m_typeResolver->dateTimeType(), m_state.accumulatorOut(),
2620 u"QDateTime::currentDateTime()"_s) + u";\n";
2621 return;
2622 }
2623
2624 if (argc == 1 && m_state.readRegister(argv).contains(m_typeResolver->dateTimeType())) {
2625 m_body += conversion(
2626 registerType(argv), m_state.readRegister(argv), registerVariable(argv))
2627 + u";\n";
2628 return;
2629 }
2630
2631 QString ctorArgs;
2632 constexpr int maxArgc = 7; // year, month, day, hours, minutes, seconds, milliseconds
2633 for (int i = 0; i < std::min(argc, maxArgc); ++i) {
2634 if (i > 0)
2635 ctorArgs += u", ";
2636 ctorArgs += conversion(
2637 registerType(argv + i), m_state.readRegister(argv + i),
2638 registerVariable(argv + i));
2639 }
2640 m_body += conversion(
2641 m_typeResolver->dateTimeType(), m_state.accumulatorOut(),
2642 u"aotContext->constructDateTime("_s + ctorArgs + u')') + u";\n";
2643 return;
2644 }
2645
2646 if (originalResult.contains(m_typeResolver->variantListType())) {
2647 rejectIfBadArray();
2648
2649 if (argc == 1 && m_state.readRegister(argv).contains(m_typeResolver->realType())) {
2650 addInclude(u"QtQml/qjslist.h"_s);
2651
2652 const QString error = u" aotContext->engine->throwError(QJSValue::RangeError, "_s
2653 + u"QLatin1String(\"Invalid array length\"));\n"_s;
2654
2655 const QString indexName = registerVariable(argv);
2656 const auto indexType = registerType(argv).containedType();
2657 if (!m_typeResolver->isNativeArrayIndex(indexType)) {
2658 m_body += u"if (!QJSNumberCoercion::isArrayIndex("_s + indexName + u")) {\n"_s
2659 + error;
2660 generateReturnError();
2661 m_body += u"}\n"_s;
2662 } else if (!m_typeResolver->isUnsignedInteger(indexType)) {
2663 m_body += u"if ("_s + indexName + u" < 0) {\n"_s
2664 + error;
2665 generateReturnError();
2666 m_body += u"}\n"_s;
2667 }
2668
2669 m_body += m_state.accumulatorVariableOut + u" = "_s
2670 + m_state.accumulatorOut().storedType()->internalName() + u"();\n"_s;
2671 m_body += u"QJSList(&"_s + m_state.accumulatorVariableOut
2672 + u", aotContext->engine).resize("_s
2673 + convertStored(
2674 registerType(argv).storedType(), m_typeResolver->sizeType(),
2675 consumedRegisterVariable(argv))
2676 + u");\n"_s;
2677 } else if (!m_logger->currentFunctionHasCompileError()) {
2678 generateArrayInitializer(argc, argv);
2679 }
2680 return;
2681 }
2682
2683 const QQmlJSScope::ConstPtr originalContained = originalResult.containedType();
2684 if (originalContained->isValueType() && originalResult.isMethodCall()) {
2685 const QQmlJSMetaMethod ctor = originalResult.methodCall();
2686 if (ctor.isJavaScriptFunction())
2687 REJECT(u"calling JavaScript constructor "_s + ctor.methodName());
2688
2689 QList<QQmlJSRegisterContent> argumentTypes;
2690 QStringList arguments;
2691 for (int i = 0; i < argc; ++i) {
2692 argumentTypes.append(registerType(argv + i));
2693 arguments.append(consumedRegisterVariable(argv + i));
2694 }
2695
2696 const QQmlJSScope::ConstPtr extension = originalContained->extensionType().scope;
2697 const QString result = generateCallConstructor(
2698 ctor, argumentTypes, arguments, metaType(originalContained),
2699 metaObject(extension ? extension : originalContained));
2700
2701 m_body += m_state.accumulatorVariableOut + u" = "_s
2702 + conversion(m_pool->storedIn(originalResult, m_typeResolver->varType()),
2703 m_state.accumulatorOut(), result)
2704 + u";\n"_s;
2705
2706 return;
2707 }
2708
2709
2710 REJECT(u"Construct"_s);
2711}
2712
2713void QQmlJSCodeGenerator::generate_ConstructWithSpread(int func, int argc, int argv)
2714{
2715 Q_UNUSED(func)
2716 Q_UNUSED(argc)
2717 Q_UNUSED(argv)
2719}
2720
2721void QQmlJSCodeGenerator::generate_SetUnwindHandler(int offset)
2722{
2723 Q_UNUSED(offset)
2724 REJECT(u"SetUnwindHandler"_s);
2725}
2726
2727void QQmlJSCodeGenerator::generate_UnwindDispatch()
2728{
2729 REJECT(u"UnwindDispatch"_s);
2730}
2731
2732void QQmlJSCodeGenerator::generate_UnwindToLabel(int level, int offset)
2733{
2734 Q_UNUSED(level)
2735 Q_UNUSED(offset)
2737}
2738
2739void QQmlJSCodeGenerator::generate_DeadTemporalZoneCheck(int name)
2740{
2741 Q_UNUSED(name)
2742 INJECT_TRACE_INFO(generate_DeadTemporalZoneCheck);
2743 // Nothing to do here. If we have statically asserted the dtz check in the type propagator
2744 // the value cannot be empty. Otherwise we can't get here.
2745}
2746
2747void QQmlJSCodeGenerator::generate_ThrowException()
2748{
2749 INJECT_TRACE_INFO(generate_ThrowException);
2750
2751 generateSetInstructionPointer();
2752 m_body += u"aotContext->engine->throwError("_s + conversion(
2753 m_state.accumulatorIn(),
2754 m_typeResolver->jsValueType(),
2755 m_state.accumulatorVariableIn) + u");\n"_s;
2756 generateReturnError();
2757 m_skipUntilNextLabel = true;
2758 resetState();
2759}
2760
2761void QQmlJSCodeGenerator::generate_GetException()
2762{
2764}
2765
2766void QQmlJSCodeGenerator::generate_SetException()
2767{
2769}
2770
2771void QQmlJSCodeGenerator::generate_CreateCallContext()
2772{
2773 INJECT_TRACE_INFO(generate_CreateCallContext);
2774
2775 m_body += u"{\n"_s;
2776}
2777
2778void QQmlJSCodeGenerator::generate_PushCatchContext(int index, int nameIndex)
2779{
2780 Q_UNUSED(index)
2781 Q_UNUSED(nameIndex)
2782 REJECT(u"PushCatchContext"_s);
2783}
2784
2785void QQmlJSCodeGenerator::generate_PushWithContext()
2786{
2788}
2789
2790void QQmlJSCodeGenerator::generate_PushBlockContext(int index)
2791{
2792 Q_UNUSED(index)
2794}
2795
2796void QQmlJSCodeGenerator::generate_CloneBlockContext()
2797{
2799}
2800
2801void QQmlJSCodeGenerator::generate_PushScriptContext(int index)
2802{
2803 Q_UNUSED(index)
2805}
2806
2807void QQmlJSCodeGenerator::generate_PopScriptContext()
2808{
2810}
2811
2812void QQmlJSCodeGenerator::generate_PopContext()
2813{
2814 INJECT_TRACE_INFO(generate_PopContext);
2815
2816 // Add an empty block before the closing brace, in case there was a bare label before it.
2817 m_body += u"{}\n}\n"_s;
2818}
2819
2820void QQmlJSCodeGenerator::generate_GetIterator(int iterator)
2821{
2822 INJECT_TRACE_INFO(generate_GetIterator);
2823
2824 addInclude(u"QtQml/qjslist.h"_s);
2825 const QQmlJSRegisterContent listType = m_state.accumulatorIn();
2826 if (!listType.isList())
2827 REJECT(u"iterator on non-list type"_s);
2828
2829 const QQmlJSRegisterContent iteratorType = m_state.accumulatorOut();
2830 if (!iteratorType.isProperty())
2831 REJECT(u"using non-iterator as iterator"_s);
2832
2833 const QString identifier = QString::number(iteratorType.baseLookupIndex());
2834 QString baseName = m_state.accumulatorVariableOut.mid(2); // remove "s."
2835 const QString iteratorName = baseName + u"Iterator" + identifier;
2836 const QString listName = baseName + u"List" + identifier;
2837
2838 m_body += u"QJSListFor"_s
2839 + (iterator == int(QQmlJS::AST::ForEachType::In) ? u"In"_s : u"Of"_s)
2840 + u"Iterator "_s + iteratorName + u";\n";
2841 m_body += m_state.accumulatorVariableOut + u" = &" + iteratorName + u";\n";
2842
2843 m_body += m_state.accumulatorVariableOut + u"->init(";
2844 if (iterator == int(QQmlJS::AST::ForEachType::In)) {
2845 if (!iteratorType.isStoredIn(m_typeResolver->forInIteratorPtr()))
2846 REJECT(u"using non-iterator as iterator"_s);
2847 m_body += u"QJSList(&" + m_state.accumulatorVariableIn + u", aotContext->engine)";
2848 }
2849 m_body += u");\n";
2850
2851 if (iterator == int(QQmlJS::AST::ForEachType::Of)) {
2852 if (!iteratorType.isStoredIn(m_typeResolver->forOfIteratorPtr()))
2853 REJECT(u"using non-iterator as iterator"_s);
2854 m_body += u"const auto &" // Rely on life time extension for const refs
2855 + listName + u" = " + consumedAccumulatorVariableIn();
2856 }
2857}
2858
2859void QQmlJSCodeGenerator::generate_IteratorNext(int value, int offset)
2860{
2861 INJECT_TRACE_INFO(generate_IteratorNext);
2862
2863 Q_ASSERT(value == m_state.changedRegisterIndex());
2864 const QQmlJSRegisterContent iteratorContent = m_state.accumulatorIn();
2865 if (!iteratorContent.isProperty())
2866 REJECT(u"using non-iterator as iterator"_s);
2867
2868 const QQmlJSScope::ConstPtr iteratorType = iteratorContent.storedType();
2869 const QString listName = m_state.accumulatorVariableIn
2870 + u"List" + QString::number(iteratorContent.baseLookupIndex());
2871 QString qjsList;
2872 if (iteratorType == m_typeResolver->forOfIteratorPtr())
2873 qjsList = u"QJSList(&" + listName + u", aotContext->engine)";
2874 else if (iteratorType != m_typeResolver->forInIteratorPtr())
2875 REJECT(u"using non-iterator as iterator"_s);
2876
2877 m_body += u"if (" + m_state.accumulatorVariableIn + u"->hasNext(" + qjsList + u")) {\n ";
2878
2879 // We know that this works because we can do ->next() below.
2880 QQmlJSRegisterContent iteratorValue = m_typeResolver->extractNonVoidFromOptionalType(
2881 m_typeResolver->original(m_state.changedRegister()));
2882 iteratorValue = m_pool->storedIn(iteratorValue, iteratorValue.containedType());
2883
2884 m_body += changedRegisterVariable() + u" = "
2885 + conversion(
2886 iteratorValue, m_state.changedRegister(),
2887 m_state.accumulatorVariableIn + u"->next(" + qjsList + u')')
2888 + u";\n";
2889 m_body += u"} else {\n ";
2890 m_body += changedRegisterVariable() + u" = "
2891 + conversion(m_typeResolver->voidType(), m_state.changedRegister(), QString());
2892 m_body += u";\n ";
2893 generateJumpCodeWithTypeConversions(offset);
2894 m_body += u"\n}"_s;
2895}
2896
2897void QQmlJSCodeGenerator::generate_IteratorNextForYieldStar(int iterator, int object, int offset)
2898{
2899 Q_UNUSED(iterator)
2900 Q_UNUSED(object)
2901 Q_UNUSED(offset)
2903}
2904
2905void QQmlJSCodeGenerator::generate_IteratorClose()
2906{
2908}
2909
2910void QQmlJSCodeGenerator::generate_DestructureRestElement()
2911{
2913}
2914
2915void QQmlJSCodeGenerator::generate_DeleteProperty(int base, int index)
2916{
2917 Q_UNUSED(base)
2918 Q_UNUSED(index)
2920}
2921
2922void QQmlJSCodeGenerator::generate_DeleteName(int name)
2923{
2924 Q_UNUSED(name)
2926}
2927
2928void QQmlJSCodeGenerator::generate_TypeofName(int name)
2929{
2930 Q_UNUSED(name);
2931 REJECT(u"TypeofName"_s);
2932}
2933
2934void QQmlJSCodeGenerator::generate_TypeofValue()
2935{
2936 REJECT(u"TypeofValue"_s);
2937}
2938
2939void QQmlJSCodeGenerator::generate_DeclareVar(int varName, int isDeletable)
2940{
2941 Q_UNUSED(varName)
2942 Q_UNUSED(isDeletable)
2944}
2945
2946void QQmlJSCodeGenerator::generate_DefineArray(int argc, int args)
2947{
2948 INJECT_TRACE_INFO(generate_DefineArray);
2949
2950 rejectIfBadArray();
2951 if (!m_logger->currentFunctionHasCompileError())
2952 generateArrayInitializer(argc, args);
2953}
2954
2955void QQmlJSCodeGenerator::generate_DefineObjectLiteral(int internalClassId, int argc, int args)
2956{
2957 INJECT_TRACE_INFO(generate_DefineObjectLiteral);
2958
2959 const QQmlJSScope::ConstPtr stored = m_state.accumulatorOut().storedType();
2960 if (stored->accessSemantics() != QQmlJSScope::AccessSemantics::Value)
2961 REJECT(u"storing an object literal in a non-value type"_s);
2962
2963 const QQmlJSScope::ConstPtr contained = m_state.accumulatorOut().containedType();
2964
2965 const int classSize = m_jsUnitGenerator->jsClassSize(internalClassId);
2966
2967 // This is not implemented because we cannot statically determine the type of the value and we
2968 // don't want to rely on QVariant::convert() since that may give different results than
2969 // the JavaScript coercion. We might still make it work by querying the QMetaProperty
2970 // for its type at run time and runtime coercing to that, but we don't know whether that
2971 // still pays off.
2972 if (argc > classSize)
2973 REJECT(u"non-literal keys of object literals"_s);
2974
2975 Q_ASSERT(argc == classSize);
2976
2977 const auto createVariantMap = [&]() {
2978 QString result;
2979 result += u"QVariantMap {\n";
2980 const QQmlJSScope::ConstPtr propType = m_typeResolver->varType();
2981 for (int i = 0; i < classSize; ++i) {
2982 result += u"{ "_s
2983 + QQmlJSUtils::toLiteral(m_jsUnitGenerator->jsClassMember(internalClassId, i))
2984 + u", "_s;
2985 const int currentArg = args + i;
2986 const QQmlJSScope::ConstPtr argType = registerType(currentArg).storedType();
2987 const QString consumedArg = consumedRegisterVariable(currentArg);
2988 result += convertStored(argType, propType, consumedArg) + u" },\n";
2989 }
2990
2991
2992 result += u"}";
2993 return result;
2994 };
2995
2996 if (contained == m_typeResolver->varType() || contained == m_typeResolver->variantMapType()) {
2997 m_body += m_state.accumulatorVariableOut + u" = "_s + createVariantMap() + u";\n"_s;
2998 return;
2999 }
3000
3001 if (contained == m_typeResolver->jsValueType()) {
3002 m_body += m_state.accumulatorVariableOut + u" = aotContext->engine->toScriptValue("_s
3003 + createVariantMap() + u");\n"_s;
3004 return;
3005 }
3006
3007 m_body += m_state.accumulatorVariableOut + u" = "_s + stored->augmentedInternalName();
3008 const bool isVariantOrPrimitive = (stored == m_typeResolver->varType())
3009 || (stored == m_typeResolver->jsPrimitiveType());
3010
3011 if (m_state.accumulatorOut().contains(stored)) {
3012 m_body += u"()";
3013 } else if (isVariantOrPrimitive) {
3014 m_body += u'(' + metaType(m_state.accumulatorOut().containedType()) + u')';
3015 } else {
3016 REJECT(u"storing an object literal in an unsupported container %1"_s
3017 .arg(stored->internalName()));
3018 }
3019 m_body += u";\n";
3020
3021 if (argc == 0)
3022 return;
3023
3024 bool isExtension = false;
3025 if (!m_typeResolver->canPopulate(contained, m_typeResolver->variantMapType(), &isExtension))
3026 REJECT(u"storing an object literal in a non-structured value type"_s);
3027
3028 const QQmlJSScope::ConstPtr accessor = isExtension
3029 ? contained->extensionType().scope
3030 : contained;
3031
3032 m_body += u"{\n";
3033 m_body += u" const QMetaObject *meta = ";
3034 if (!isExtension && isVariantOrPrimitive)
3035 m_body += m_state.accumulatorVariableOut + u".metaType().metaObject()";
3036 else
3037 m_body += metaObject(accessor);
3038 m_body += u";\n";
3039
3040 for (int i = 0; i < classSize; ++i) {
3041 m_body += u" {\n";
3042 const QString propName = m_jsUnitGenerator->jsClassMember(internalClassId, i);
3043 const int currentArg = args + i;
3044 const QQmlJSRegisterContent propType = m_state.readRegister(currentArg);
3045 const QQmlJSRegisterContent argType = registerType(currentArg);
3046 const QQmlJSMetaProperty property = contained->property(propName);
3047 const QString consumedArg = consumedRegisterVariable(currentArg);
3048 QString argument = conversion(argType, propType, consumedArg);
3049
3050 if (argument == consumedArg) {
3051 argument = registerVariable(currentArg);
3052 } else {
3053 m_body += u" "_s + propType.storedType()->augmentedInternalName()
3054 + u" arg = "_s + argument + u";\n";
3055 argument = u"arg"_s;
3056 }
3057
3058 int index = property.index();
3059 if (index == -1)
3060 continue;
3061
3062 const QString indexString = QString::number(index);
3063 m_body += u" void *argv[] = { %1, nullptr };\n"_s
3064 .arg(contentPointer(propType, argument));
3065 m_body += u" meta->property("_s + indexString;
3066 m_body += u").enclosingMetaObject()->d.static_metacall(reinterpret_cast<QObject *>(";
3067 m_body += contentPointer(m_state.accumulatorOut(), m_state.accumulatorVariableOut);
3068 m_body += u"), QMetaObject::WriteProperty, " + indexString + u", argv);\n";
3069 m_body += u" }\n";
3070 }
3071
3072 m_body += u"}\n";
3073
3074}
3075
3076void QQmlJSCodeGenerator::generate_CreateClass(int classIndex, int heritage, int computedNames)
3077{
3078 Q_UNUSED(classIndex)
3079 Q_UNUSED(heritage)
3080 Q_UNUSED(computedNames)
3082}
3083
3084void QQmlJSCodeGenerator::generate_CreateMappedArgumentsObject()
3085{
3087}
3088
3089void QQmlJSCodeGenerator::generate_CreateUnmappedArgumentsObject()
3090{
3092}
3093
3094void QQmlJSCodeGenerator::generate_CreateRestParameter(int argIndex)
3095{
3096 Q_UNUSED(argIndex)
3098}
3099
3100void QQmlJSCodeGenerator::generate_ConvertThisToObject()
3101{
3102 INJECT_TRACE_INFO(generate_ConvertThisToObject);
3103
3104 m_body += changedRegisterVariable() + u" = "_s
3105 + conversion(m_typeResolver->qObjectType(), m_state.changedRegister(),
3106 u"aotContext->thisObject()"_s)
3107 + u";\n"_s;
3108}
3109
3110void QQmlJSCodeGenerator::generate_LoadSuperConstructor()
3111{
3113}
3114
3115void QQmlJSCodeGenerator::generate_ToObject()
3116{
3118}
3119
3120void QQmlJSCodeGenerator::generate_Jump(int offset)
3121{
3122 INJECT_TRACE_INFO(generate_Jump);
3123
3124 generateJumpCodeWithTypeConversions(offset);
3125 m_skipUntilNextLabel = true;
3126 resetState();
3127}
3128
3129void QQmlJSCodeGenerator::generate_JumpTrue(int offset)
3130{
3131 INJECT_TRACE_INFO(generate_JumpTrue);
3132
3133 m_body += u"if ("_s;
3134 m_body += convertStored(m_state.accumulatorIn().storedType(), m_typeResolver->boolType(),
3135 m_state.accumulatorVariableIn);
3136 m_body += u") "_s;
3137 generateJumpCodeWithTypeConversions(offset);
3138}
3139
3140void QQmlJSCodeGenerator::generate_JumpFalse(int offset)
3141{
3142 INJECT_TRACE_INFO(generate_JumpFalse);
3143
3144 m_body += u"if (!"_s;
3145 m_body += convertStored(m_state.accumulatorIn().storedType(), m_typeResolver->boolType(),
3146 m_state.accumulatorVariableIn);
3147 m_body += u") "_s;
3148 generateJumpCodeWithTypeConversions(offset);
3149}
3150
3151void QQmlJSCodeGenerator::generate_JumpNoException(int offset)
3152{
3153 INJECT_TRACE_INFO(generate_JumpNoException);
3154
3155 m_body += u"if (!context->engine->hasException()) "_s;
3156 generateJumpCodeWithTypeConversions(offset);
3157}
3158
3159void QQmlJSCodeGenerator::generate_JumpNotUndefined(int offset)
3160{
3161 Q_UNUSED(offset)
3163}
3164
3165void QQmlJSCodeGenerator::generate_CheckException()
3166{
3167 INJECT_TRACE_INFO(generate_CheckException);
3168
3169 generateExceptionCheck();
3170}
3171
3172void QQmlJSCodeGenerator::generate_CmpEqNull()
3173{
3174 INJECT_TRACE_INFO(generate_CmpEqNull);
3175 generateEqualityOperation(literalType(m_typeResolver->nullType()), QString(), u"equals"_s, false);
3176}
3177
3178void QQmlJSCodeGenerator::generate_CmpNeNull()
3179{
3180 INJECT_TRACE_INFO(generate_CmlNeNull);
3181 generateEqualityOperation(literalType(m_typeResolver->nullType()), QString(), u"equals"_s, true);
3182}
3183
3184QString QQmlJSCodeGenerator::getLookupPreparation(
3185 QQmlJSRegisterContent content, const QString &var, int lookup)
3186{
3187 if (content.contains(content.storedType()))
3188 return QString();
3189
3190 if (content.storedType()->isOpaqueType()) {
3191 // Can't do meta-type lookups if the type is potentially unregistered,
3192 // which can be the case for mehods
3193 REJECT<QString>("Can't prepare lookup involving non-registered types"_L1);
3194 }
3195
3196 if (content.isStoredIn(m_typeResolver->varType())) {
3197 return var + u" = QVariant(aotContext->lookupResultMetaType("_s
3198 + QString::number(lookup) + u"))"_s;
3199 }
3200
3201 if (content.isStoredIn(m_typeResolver->jsPrimitiveType())) {
3202 return var + u" = QJSPrimitiveValue(aotContext->lookupResultMetaType("_s
3203 + QString::number(lookup) + u"))"_s;
3204 }
3205
3206 // TODO: We could make sure they're compatible, for example QObject pointers.
3207 return QString();
3208}
3209
3210QString QQmlJSCodeGenerator::contentPointer(QQmlJSRegisterContent content, const QString &var)
3211{
3212 const QQmlJSScope::ConstPtr stored = content.storedType();
3213 if (content.contains(stored))
3214 return u'&' + var;
3215
3216 if (content.isStoredIn(m_typeResolver->varType())
3217 || content.isStoredIn(m_typeResolver->jsPrimitiveType())) {
3218 return var + u".data()"_s;
3219 }
3220
3221 if (stored->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
3222 return u'&' + var;
3223
3224 if (m_typeResolver->isNumeric(content.storedType())
3225 && content.containedType()->scopeType() == QQmlSA::ScopeType::EnumScope) {
3226 return u'&' + var;
3227 }
3228
3229 if (stored->isListProperty() && content.containedType()->isListProperty())
3230 return u'&' + var;
3231
3232 REJECT<QString>(
3233 u"content pointer of unsupported wrapper type "_s + content.descriptiveName());
3234}
3235
3236QString QQmlJSCodeGenerator::contentType(QQmlJSRegisterContent content, const QString &var)
3237{
3238 const QQmlJSScope::ConstPtr stored = content.storedType();
3239 const QQmlJSScope::ConstPtr contained = content.containedType();
3240 if (contained == stored)
3241 return metaTypeFromType(stored);
3242
3243 if (stored == m_typeResolver->varType() || stored == m_typeResolver->jsPrimitiveType())
3244 return var + u".metaType()"_s; // We expect the container to be initialized
3245
3246 if (stored->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
3247 return metaType(contained);
3248
3249 const QQmlJSScope::ConstPtr nonComposite = QQmlJSScope::nonCompositeBaseType(contained);
3250 if (m_typeResolver->isNumeric(stored) && nonComposite->scopeType() == QQmlSA::ScopeType::EnumScope)
3251 return metaTypeFromType(nonComposite->baseType());
3252
3253 if (stored->isListProperty() && contained->isListProperty())
3254 return metaType(contained);
3255
3256 REJECT<QString>(
3257 u"content type of unsupported wrapper type "_s + content.descriptiveName());
3258}
3259
3260void QQmlJSCodeGenerator::generate_CmpEqInt(int lhsConst)
3261{
3262 INJECT_TRACE_INFO(generate_CmpEqInt);
3263
3264 generateEqualityOperation(
3265 literalType(m_typeResolver->int32Type()), QString::number(lhsConst), u"equals"_s, false);
3266}
3267
3268void QQmlJSCodeGenerator::generate_CmpNeInt(int lhsConst)
3269{
3270 INJECT_TRACE_INFO(generate_CmpNeInt);
3271
3272 generateEqualityOperation(
3273 literalType(m_typeResolver->int32Type()), QString::number(lhsConst), u"equals"_s, true);
3274}
3275
3276void QQmlJSCodeGenerator::generate_CmpEq(int lhs)
3277{
3278 INJECT_TRACE_INFO(generate_CmpEq);
3279 generateEqualityOperation(registerType(lhs), registerVariable(lhs), u"equals"_s, false);
3280}
3281
3282void QQmlJSCodeGenerator::generate_CmpNe(int lhs)
3283{
3284 INJECT_TRACE_INFO(generate_CmpNe);
3285 generateEqualityOperation(registerType(lhs), registerVariable(lhs), u"equals"_s, true);
3286}
3287
3288void QQmlJSCodeGenerator::generate_CmpGt(int lhs)
3289{
3290 INJECT_TRACE_INFO(generate_CmpGt);
3291 generateCompareOperation(lhs, u">"_s);
3292}
3293
3294void QQmlJSCodeGenerator::generate_CmpGe(int lhs)
3295{
3296 INJECT_TRACE_INFO(generate_CmpGe);
3297 generateCompareOperation(lhs, u">="_s);
3298}
3299
3300void QQmlJSCodeGenerator::generate_CmpLt(int lhs)
3301{
3302 INJECT_TRACE_INFO(generate_CmpLt);
3303 generateCompareOperation(lhs, u"<"_s);
3304}
3305
3306void QQmlJSCodeGenerator::generate_CmpLe(int lhs)
3307{
3308 INJECT_TRACE_INFO(generate_CmpLe);
3309 generateCompareOperation(lhs, u"<="_s);
3310}
3311
3312void QQmlJSCodeGenerator::generate_CmpStrictEqual(int lhs)
3313{
3314 INJECT_TRACE_INFO(generate_CmpStrictEqual);
3315 generateEqualityOperation(registerType(lhs), registerVariable(lhs), u"strictlyEquals"_s, false);
3316}
3317
3318void QQmlJSCodeGenerator::generate_CmpStrictNotEqual(int lhs)
3319{
3320 INJECT_TRACE_INFO(generate_CmpStrictNotEqual);
3321 generateEqualityOperation(registerType(lhs), registerVariable(lhs), u"strictlyEquals"_s, true);
3322}
3323
3324void QQmlJSCodeGenerator::generate_CmpIn(int lhs)
3325{
3326 Q_UNUSED(lhs)
3327 REJECT(u"CmpIn"_s);
3328}
3329
3330void QQmlJSCodeGenerator::generate_CmpInstanceOf(int lhs)
3331{
3332 Q_UNUSED(lhs)
3334}
3335
3336void QQmlJSCodeGenerator::generate_As(int lhs)
3337{
3338 INJECT_TRACE_INFO(generate_As);
3339
3340 const QString input = registerVariable(lhs);
3341 const QQmlJSRegisterContent inputContent = m_state.readRegister(lhs);
3342 const QQmlJSRegisterContent outputContent = m_state.accumulatorOut();
3343
3344 // If the originalType output is a conversion, we're supposed to check for the contained
3345 // type and if it doesn't match, set the result to null or undefined.
3346 const QQmlJSRegisterContent originalContent = originalType(outputContent);
3347 QQmlJSScope::ConstPtr target;
3348 if (originalContent.containedType()->isReferenceType())
3349 target = originalContent.containedType();
3350 else if (originalContent.isConversion())
3351 target = m_typeResolver->extractNonVoidFromOptionalType(originalContent).containedType();
3352 else if (originalContent.variant() == QQmlJSRegisterContent::Cast)
3353 target = originalContent.containedType();
3354
3355 if (!target)
3356 REJECT(u"type assertion to unknown type"_s);
3357
3358 const bool isTrivial = m_typeResolver->inherits(
3359 m_typeResolver->originalContainedType(inputContent), target);
3360
3361 m_body += m_state.accumulatorVariableOut + u" = "_s;
3362
3363 if (!isTrivial && target->isReferenceType()) {
3364 const QQmlJSScope::ConstPtr genericContained = m_typeResolver->genericType(target);
3365 const QString inputConversion = inputContent.storedType()->isReferenceType()
3366 ? input
3367 : convertStored(inputContent.storedType(), genericContained, input);
3368
3369 if (target->isComposite()
3370 && m_state.accumulatorIn().isStoredIn(m_typeResolver->metaObjectType())) {
3371 m_body += conversion(
3372 genericContained, outputContent,
3373 m_state.accumulatorVariableIn + u"->cast("_s + inputConversion + u')');
3374 } else {
3375 m_body += conversion(
3376 genericContained, outputContent,
3377 u'(' + metaObject(target) + u")->cast("_s + inputConversion + u')');
3378 }
3379 m_body += u";\n"_s;
3380 return;
3381 }
3382
3383 if (inputContent.isStoredIn(m_typeResolver->varType())
3384 || inputContent.isStoredIn(m_typeResolver->jsPrimitiveType())) {
3385
3386 const auto source = m_typeResolver->extractNonVoidFromOptionalType(
3387 originalType(inputContent)).containedType();
3388
3389 if (source && source == target) {
3390 m_body += input + u".metaType() == "_s + metaType(target)
3391 + u" ? " + conversion(inputContent, outputContent, input)
3392 + u" : " + conversion(
3393 literalType(m_typeResolver->voidType()), outputContent, QString());
3394 m_body += u";\n"_s;
3395 return;
3396 }
3397 }
3398
3399 if (isTrivial) {
3400 // No actual conversion necessary. The 'as' is a no-op
3401 m_body += conversion(inputContent, m_state.accumulatorOut(), input) + u";\n"_s;
3402 return;
3403 }
3404
3405 REJECT(u"non-trivial value type assertion"_s);
3406}
3407
3408void QQmlJSCodeGenerator::generate_UNot()
3409{
3410 INJECT_TRACE_INFO(generate_UNot);
3411 generateUnaryOperation(u"!"_s);
3412}
3413
3414void QQmlJSCodeGenerator::generate_UPlus()
3415{
3416 INJECT_TRACE_INFO(generate_UPlus);
3417 generateUnaryOperation(u"+"_s);
3418}
3419
3420void QQmlJSCodeGenerator::generate_UMinus()
3421{
3422 INJECT_TRACE_INFO(generate_UMinus);
3423 generateUnaryOperation(u"-"_s);
3424}
3425
3426void QQmlJSCodeGenerator::generate_UCompl()
3427{
3428 INJECT_TRACE_INFO(generate_UCompl);
3429 generateUnaryOperation(u"~"_s);
3430}
3431
3432void QQmlJSCodeGenerator::generate_Increment()
3433{
3434 INJECT_TRACE_INFO(generate_Increment);
3435 generateInPlaceOperation(u"++"_s);
3436}
3437
3438void QQmlJSCodeGenerator::generate_Decrement()
3439{
3440 INJECT_TRACE_INFO(generate_Decrement);
3441 generateInPlaceOperation(u"--"_s);
3442}
3443
3444void QQmlJSCodeGenerator::generate_Add(int lhs)
3445{
3446 INJECT_TRACE_INFO(generate_Add);
3447 generateArithmeticOperation(lhs, u"+"_s);
3448}
3449
3450void QQmlJSCodeGenerator::generate_BitAnd(int lhs)
3451{
3452 INJECT_TRACE_INFO(generate_BitAnd);
3453 generateArithmeticOperation(lhs, u"&"_s);
3454}
3455
3456void QQmlJSCodeGenerator::generate_BitOr(int lhs)
3457{
3458 INJECT_TRACE_INFO(generate_BitOr);
3459 generateArithmeticOperation(lhs, u"|"_s);
3460}
3461
3462void QQmlJSCodeGenerator::generate_BitXor(int lhs)
3463{
3464 INJECT_TRACE_INFO(generate_BitXor);
3465 generateArithmeticOperation(lhs, u"^"_s);
3466}
3467
3468void QQmlJSCodeGenerator::generate_UShr(int lhs)
3469{
3470 INJECT_TRACE_INFO(generate_BitUShr);
3471 generateShiftOperation(lhs, u">>"_s);
3472}
3473
3474void QQmlJSCodeGenerator::generate_Shr(int lhs)
3475{
3476 INJECT_TRACE_INFO(generate_Shr);
3477 generateShiftOperation(lhs, u">>"_s);
3478}
3479
3480void QQmlJSCodeGenerator::generate_Shl(int lhs)
3481{
3482 INJECT_TRACE_INFO(generate_Shl);
3483 generateShiftOperation(lhs, u"<<"_s);
3484}
3485
3486void QQmlJSCodeGenerator::generate_BitAndConst(int rhs)
3487{
3488 INJECT_TRACE_INFO(generate_BitAndConst);
3489 generateArithmeticConstOperation(rhs, u"&"_s);
3490}
3491
3492void QQmlJSCodeGenerator::generate_BitOrConst(int rhs)
3493{
3494 INJECT_TRACE_INFO(generate_BitOrConst);
3495 generateArithmeticConstOperation(rhs, u"|"_s);
3496}
3497
3498void QQmlJSCodeGenerator::generate_BitXorConst(int rhs)
3499{
3500 INJECT_TRACE_INFO(generate_BitXorConst);
3501 generateArithmeticConstOperation(rhs, u"^"_s);
3502}
3503
3504void QQmlJSCodeGenerator::generate_UShrConst(int rhs)
3505{
3506 INJECT_TRACE_INFO(generate_UShrConst);
3507 generateArithmeticConstOperation(rhs & 0x1f, u">>"_s);
3508}
3509
3510void QQmlJSCodeGenerator::generate_ShrConst(int rhs)
3511{
3512 INJECT_TRACE_INFO(generate_ShrConst);
3513 generateArithmeticConstOperation(rhs & 0x1f, u">>"_s);
3514}
3515
3516void QQmlJSCodeGenerator::generate_ShlConst(int rhs)
3517{
3518 INJECT_TRACE_INFO(generate_ShlConst);
3519 generateArithmeticConstOperation(rhs & 0x1f, u"<<"_s);
3520}
3521
3522void QQmlJSCodeGenerator::generate_Exp(int lhs)
3523{
3524 INJECT_TRACE_INFO(generate_Exp);
3525
3526 const QString lhsString = conversion(
3527 registerType(lhs), m_state.readRegister(lhs), consumedRegisterVariable(lhs));
3528 const QString rhsString = conversion(
3529 m_state.accumulatorIn(), m_state.readAccumulator(),
3530 consumedAccumulatorVariableIn());
3531
3532 Q_ASSERT(m_logger->currentFunctionHasCompileError() || !lhsString.isEmpty());
3533 Q_ASSERT(m_logger->currentFunctionHasCompileError() || !rhsString.isEmpty());
3534
3535 const QQmlJSRegisterContent originalOut = originalType(m_state.accumulatorOut());
3536 m_body += m_state.accumulatorVariableOut + u" = "_s;
3537 m_body += conversion(
3538 originalOut, m_state.accumulatorOut(),
3539 u"QQmlPrivate::jsExponentiate("_s + lhsString + u", "_s + rhsString + u')');
3540 m_body += u";\n"_s;
3541}
3542
3543void QQmlJSCodeGenerator::generate_Mul(int lhs)
3544{
3545 INJECT_TRACE_INFO(generate_Mul);
3546 generateArithmeticOperation(lhs, u"*"_s);
3547}
3548
3549void QQmlJSCodeGenerator::generate_Div(int lhs)
3550{
3551 INJECT_TRACE_INFO(generate_Div);
3552 GeneratePragmaWarningBlock warningBlock(this);
3553 warningBlock.silenceDivideByZero();
3554 generateArithmeticOperation(lhs, u"/"_s);
3555}
3556
3557void QQmlJSCodeGenerator::generate_Mod(int lhs)
3558{
3559 INJECT_TRACE_INFO(generate_Mod);
3560
3561 const auto lhsVar = convertStored(
3562 registerType(lhs).storedType(), m_typeResolver->jsPrimitiveType(),
3563 consumedRegisterVariable(lhs));
3564 const auto rhsVar = convertStored(
3565 m_state.accumulatorIn().storedType(), m_typeResolver->jsPrimitiveType(),
3566 consumedAccumulatorVariableIn());
3567 Q_ASSERT(m_logger->currentFunctionHasCompileError() || !lhsVar.isEmpty());
3568 Q_ASSERT(m_logger->currentFunctionHasCompileError() || !rhsVar.isEmpty());
3569
3570 m_body += m_state.accumulatorVariableOut;
3571 m_body += u" = "_s;
3572 m_body += conversion(m_typeResolver->jsPrimitiveType(), m_state.accumulatorOut(),
3573 u'(' + lhsVar + u" % "_s + rhsVar + u')');
3574 m_body += u";\n"_s;
3575}
3576
3577void QQmlJSCodeGenerator::generate_Sub(int lhs)
3578{
3579 INJECT_TRACE_INFO(generate_Sub);
3580 generateArithmeticOperation(lhs, u"-"_s);
3581}
3582
3583void QQmlJSCodeGenerator::generate_InitializeBlockDeadTemporalZone(int firstReg, int count)
3584{
3585 Q_UNUSED(firstReg)
3586 Q_UNUSED(count)
3587 // Ignore. We reject uninitialized values anyway.
3588}
3589
3590void QQmlJSCodeGenerator::generate_ThrowOnNullOrUndefined()
3591{
3593}
3594
3595void QQmlJSCodeGenerator::generate_GetTemplateObject(int index)
3596{
3597 Q_UNUSED(index)
3599}
3600
3601QV4::Moth::ByteCodeHandler::Verdict QQmlJSCodeGenerator::startInstruction(
3602 QV4::Moth::Instr::Type type)
3603{
3604 m_state.State::operator=(nextStateFromAnnotations(m_state, m_annotations));
3605 const auto accumulatorIn = m_state.registers.find(Accumulator);
3606 if (accumulatorIn != m_state.registers.end()
3607 && isTypeStorable(m_typeResolver, accumulatorIn.value().content.storedType())) {
3608 QQmlJSRegisterContent content = accumulatorIn.value().content;
3609 m_state.accumulatorVariableIn = m_registerVariables.value(content).variableName;
3610 Q_ASSERT(!m_state.accumulatorVariableIn.isEmpty());
3611 } else {
3612 m_state.accumulatorVariableIn.clear();
3613 }
3614
3615 auto labelIt = m_labels.constFind(currentInstructionOffset());
3616 if (labelIt != m_labels.constEnd()) {
3617 m_body += *labelIt + u":;\n"_s;
3618 m_skipUntilNextLabel = false;
3619 } else if (m_skipUntilNextLabel && !instructionManipulatesContext(type)) {
3620 return SkipInstruction;
3621 }
3622
3623 if (m_state.changedRegisterIndex() == Accumulator)
3624 m_state.accumulatorVariableOut = changedRegisterVariable();
3625 else
3626 m_state.accumulatorVariableOut.clear();
3627
3628 // If the accumulator type is valid, we want an accumulator variable.
3629 // If not, we don't want one.
3630 Q_ASSERT(m_state.changedRegisterIndex() == Accumulator
3631 || m_state.accumulatorVariableOut.isEmpty());
3632 Q_ASSERT(m_state.changedRegisterIndex() != Accumulator
3633 || !m_state.accumulatorVariableOut.isEmpty()
3634 || !isTypeStorable(m_typeResolver, m_state.changedRegister().storedType()));
3635
3636 // If the instruction has no side effects and doesn't write any register, it's dead.
3637 // We might still need the label, though, and the source code comment.
3638 if (!m_state.hasInternalSideEffects() && changedRegisterVariable().isEmpty()) {
3639 generateJumpCodeWithTypeConversions(0);
3640 return SkipInstruction;
3641 }
3642
3643 return ProcessInstruction;
3644}
3645
3646void QQmlJSCodeGenerator::endInstruction(QV4::Moth::Instr::Type)
3647{
3648 if (!m_skipUntilNextLabel)
3649 generateJumpCodeWithTypeConversions(0);
3650 m_pool->clearTemporaries();
3651}
3652
3653void QQmlJSCodeGenerator::generateSetInstructionPointer()
3654{
3655 m_body += u"aotContext->setInstructionPointer("_s
3656 + QString::number(nextInstructionOffset()) + u");\n"_s;
3657}
3658
3659void QQmlJSCodeGenerator::generateExceptionCheck()
3660{
3661 m_body += u"if (aotContext->engine->hasError()) {\n"_s;
3662 generateReturnError();
3663 m_body += u"}\n"_s;
3664}
3665
3666void QQmlJSCodeGenerator::generateEqualityOperation(
3667 QQmlJSRegisterContent lhsContent, QQmlJSRegisterContent rhsContent,
3668 const QString &lhsName, const QString &rhsName, const QString &function, bool invert)
3669{
3670 const bool lhsIsOptional = m_typeResolver->isOptionalType(lhsContent);
3671 const bool rhsIsOptional = m_typeResolver->isOptionalType(rhsContent);
3672
3673 const QQmlJSScope::ConstPtr rhsContained = rhsIsOptional
3674 ? m_typeResolver->extractNonVoidFromOptionalType(rhsContent).containedType()
3675 : rhsContent.containedType();
3676
3677 const QQmlJSScope::ConstPtr lhsContained = lhsIsOptional
3678 ? m_typeResolver->extractNonVoidFromOptionalType(lhsContent).containedType()
3679 : lhsContent.containedType();
3680
3681 const bool isStrict = function == "strictlyEquals"_L1;
3682 const bool strictlyComparableWithVar = isStrict
3683 && QQmlJSUtils::canStrictlyCompareWithVar(m_typeResolver, lhsContained, rhsContained);
3684 auto isComparable = [&]() {
3685 if (m_typeResolver->isPrimitive(lhsContent) && m_typeResolver->isPrimitive(rhsContent))
3686 return true;
3687 if (m_typeResolver->isNumeric(lhsContent) && m_typeResolver->isNumeric(rhsContent))
3688 return true;
3689 if (m_typeResolver->isNumeric(lhsContent) && rhsContent.isEnumeration())
3690 return true;
3691 if (m_typeResolver->isNumeric(rhsContent) && lhsContent.isEnumeration())
3692 return true;
3693 if (strictlyComparableWithVar)
3694 return true;
3695 if (QQmlJSUtils::canCompareWithQObject(m_typeResolver, lhsContained, rhsContained))
3696 return true;
3697 if (QQmlJSUtils::canCompareWithQUrl(m_typeResolver, lhsContained, rhsContained))
3698 return true;
3699 return false;
3700 };
3701
3702 const auto retrieveOriginal = [this](QQmlJSRegisterContent content) {
3703 const auto contained = content.containedType();
3704 const auto originalContent = originalType(content);
3705 const auto containedOriginal = originalContent.containedType();
3706
3707 if (originalContent.isStoredIn(m_typeResolver->genericType(containedOriginal))) {
3708 // The original type doesn't need any wrapping.
3709 return originalContent;
3710 } else if (contained == containedOriginal) {
3711 if (originalContent.isConversion()) {
3712 // The original conversion origins are more accurate
3713 return m_pool->storedIn(originalContent, content.storedType());
3714 }
3715 } else if (m_typeResolver->canHold(contained, containedOriginal)) {
3716 return m_pool->storedIn(originalContent, content.storedType());
3717 }
3718
3719 return content;
3720 };
3721
3722 const QQmlJSScope::ConstPtr lhsType = lhsContent.storedType();
3723 const QQmlJSScope::ConstPtr rhsType = rhsContent.storedType();
3724
3725 if (!isComparable()) {
3726 QQmlJSRegisterContent lhsOriginal = retrieveOriginal(lhsContent);
3727 QQmlJSRegisterContent rhsOriginal = retrieveOriginal(rhsContent);
3728 if (lhsOriginal.containedType() != lhsContent.containedType()
3729 || lhsOriginal.storedType() != lhsType
3730 || rhsOriginal.containedType() != rhsContent.containedType()
3731 || rhsOriginal.storedType() != rhsType) {
3732 // If either side is simply a wrapping of a specific type into a more general one, we
3733 // can compare the original types instead. You can't nest wrappings after all.
3734 generateEqualityOperation(lhsOriginal, rhsOriginal,
3735 conversion(lhsType, lhsOriginal, lhsName),
3736 conversion(rhsType, rhsOriginal, rhsName),
3737 function, invert);
3738 return;
3739 }
3740
3741 REJECT(u"incomparable types %1 and %2"_s.arg(
3742 rhsContent.descriptiveName(), lhsContent.descriptiveName()));
3743 }
3744
3745 if (strictlyComparableWithVar) {
3746 // Determine which side is holding a storable type
3747 if (!lhsName.isEmpty() && rhsName.isEmpty()) {
3748 // lhs register holds var type and rhs is not storable
3749 generateVariantEqualityComparison(rhsContent, lhsName, invert);
3750 return;
3751 }
3752
3753 if (!rhsName.isEmpty() && lhsName.isEmpty()) {
3754 // lhs content is not storable and rhs is var type
3755 generateVariantEqualityComparison(lhsContent, rhsName, invert);
3756 return;
3757 }
3758
3759 if (lhsContent.contains(m_typeResolver->varType())) {
3760 generateVariantEqualityComparison(rhsContent, rhsName, lhsName, invert);
3761 return;
3762 }
3763
3764 if (rhsContent.contains(m_typeResolver->varType())) {
3765 generateVariantEqualityComparison(lhsContent, lhsName, rhsName, invert);
3766 return;
3767 }
3768
3769 // It shouldn't be possible to get here because optional null should be stored in
3770 // QJSPrimitiveValue, not in QVariant. But let's rather be safe than sorry.
3771 REJECT(u"comparison of optional null"_s);
3772 }
3773
3774 const auto comparison = [&]() -> QString {
3775 const auto primitive = m_typeResolver->jsPrimitiveType();
3776 const QString sign = invert ? u" != "_s : u" == "_s;
3777
3778 if (lhsType == rhsType && lhsType != primitive && lhsType != m_typeResolver->varType()) {
3779
3780 // Straight forward comparison of equal types,
3781 // except QJSPrimitiveValue which has two comparison functions.
3782
3783 if (isTypeStorable(m_typeResolver, lhsType))
3784 return lhsName + sign + rhsName;
3785
3786 // null === null and undefined === undefined
3787 return invert ? u"false"_s : u"true"_s;
3788 }
3789
3790 if (QQmlJSUtils::canCompareWithQObject(m_typeResolver, lhsType, rhsType)) {
3791 // Comparison of QObject-derived with nullptr or different QObject-derived.
3792 return (isTypeStorable(m_typeResolver, lhsType) ? lhsName : u"nullptr"_s)
3793 + sign
3794 + (isTypeStorable(m_typeResolver, rhsType) ? rhsName : u"nullptr"_s);
3795 }
3796
3797 if (QQmlJSUtils::canCompareWithQObject(m_typeResolver, lhsContained, rhsContained)) {
3798 // Comparison of optional QObject-derived with nullptr or different QObject-derived.
3799 // Mind that null == undefined but null !== undefined
3800 // Therefore the isStrict dance.
3801
3802 QString result;
3803 if (isStrict) {
3804 if (lhsIsOptional) {
3805 if (rhsIsOptional) {
3806 // If both are invalid we're fine
3807 result += u"(!"_s
3808 + lhsName + u".isValid() && !"_s
3809 + rhsName + u".isValid()) || "_s;
3810 }
3811
3812 result += u'(' + lhsName + u".isValid() && "_s;
3813 } else {
3814 result += u'(';
3815 }
3816
3817 if (rhsIsOptional) {
3818 result += rhsName + u".isValid() && "_s;
3819 }
3820 } else {
3821 result += u'(';
3822 }
3823
3824 // We do not implement comparison with explicit undefined, yet. Only with null.
3825 Q_ASSERT(lhsType != m_typeResolver->voidType());
3826 Q_ASSERT(rhsType != m_typeResolver->voidType());
3827
3828 const auto resolvedName = [&](const QString name) -> QString {
3829 // If isStrict we check validity already before.
3830 const QString content = u"*static_cast<QObject **>("_s + name + u".data())"_s;
3831 return isStrict
3832 ? content
3833 : u'(' + name + u".isValid() ? "_s + content + u" : nullptr)"_s;
3834 };
3835
3836 const QString lhsResolved = lhsIsOptional ? resolvedName(lhsName) : lhsName;
3837 const QString rhsResolved = rhsIsOptional ? resolvedName(rhsName) : rhsName;
3838
3839 return (invert ? u"!("_s : u"("_s) + result
3840 + (isTypeStorable(m_typeResolver, lhsType) ? lhsResolved : u"nullptr"_s)
3841 + u" == "_s
3842 + (isTypeStorable(m_typeResolver, rhsType) ? rhsResolved : u"nullptr"_s)
3843 + u"))"_s;
3844 }
3845
3846 if ((m_typeResolver->isUnsignedInteger(rhsType)
3847 && m_typeResolver->isUnsignedInteger(lhsType))
3848 || (m_typeResolver->isSignedInteger(rhsType)
3849 && m_typeResolver->isSignedInteger(lhsType))) {
3850 // Both integers of same signedness: Let the C++ compiler perform the type promotion
3851 return lhsName + sign + rhsName;
3852 }
3853
3854 if (rhsType == m_typeResolver->boolType() && m_typeResolver->isIntegral(lhsType)) {
3855 // Integral and bool: We can promote the bool to the integral type
3856 return lhsName + sign + convertStored(rhsType, lhsType, rhsName);
3857 }
3858
3859 if (lhsType == m_typeResolver->boolType() && m_typeResolver->isIntegral(rhsType)) {
3860 // Integral and bool: We can promote the bool to the integral type
3861 return convertStored(lhsType, rhsType, lhsName) + sign + rhsName;
3862 }
3863
3864 if (m_typeResolver->isNumeric(lhsType) && m_typeResolver->isNumeric(rhsType)) {
3865 // Both numbers: promote them to double
3866 return convertStored(lhsType, m_typeResolver->realType(), lhsName)
3867 + sign
3868 + convertStored(rhsType, m_typeResolver->realType(), rhsName);
3869 }
3870
3871 // If none of the above matches, we have to use QJSPrimitiveValue
3872 return (invert ? u"!"_s : QString())
3873 + convertStored(lhsType, primitive, lhsName)
3874 + u'.' + function + u'(' + convertStored(rhsType, primitive, rhsName) + u')';
3875 };
3876
3877 m_body += m_state.accumulatorVariableOut + u" = "_s;
3878 m_body += conversion(m_typeResolver->boolType(), m_state.accumulatorOut(), comparison());
3879 m_body += u";\n"_s;
3880}
3881
3882void QQmlJSCodeGenerator::generateCompareOperation(int lhs, const QString &cppOperator)
3883{
3884 m_body += m_state.accumulatorVariableOut + u" = "_s;
3885
3886 const auto lhsType = registerType(lhs);
3887 const QQmlJSScope::ConstPtr compareType =
3888 m_typeResolver->isNumeric(lhsType) && m_typeResolver->isNumeric(m_state.accumulatorIn())
3889 ? m_typeResolver->merge(lhsType.storedType(), m_state.accumulatorIn().storedType())
3890 : m_typeResolver->jsPrimitiveType();
3891
3892 m_body += conversion(
3893 m_typeResolver->boolType(), m_state.accumulatorOut(),
3894 convertStored(registerType(lhs).storedType(), compareType,
3895 consumedRegisterVariable(lhs))
3896 + u' ' + cppOperator + u' '
3897 + convertStored(m_state.accumulatorIn().storedType(), compareType,
3898 consumedAccumulatorVariableIn()));
3899 m_body += u";\n"_s;
3900}
3901
3902void QQmlJSCodeGenerator::generateArithmeticOperation(int lhs, const QString &cppOperator)
3903{
3904 generateArithmeticOperation(
3905 conversion(registerType(lhs), m_state.readRegister(lhs),
3906 consumedRegisterVariable(lhs)),
3907 conversion(m_state.accumulatorIn(), m_state.readAccumulator(),
3908 consumedAccumulatorVariableIn()),
3909 cppOperator);
3910}
3911
3912void QQmlJSCodeGenerator::generateShiftOperation(int lhs, const QString &cppOperator)
3913{
3914 generateArithmeticOperation(
3915 conversion(registerType(lhs), m_state.readRegister(lhs),
3916 consumedRegisterVariable(lhs)),
3917 u'(' + conversion(m_state.accumulatorIn(), m_state.readAccumulator(),
3918 consumedAccumulatorVariableIn()) + u" & 0x1f)"_s,
3919 cppOperator);
3920}
3921
3922void QQmlJSCodeGenerator::generateArithmeticOperation(
3923 const QString &lhs, const QString &rhs, const QString &cppOperator)
3924{
3925 Q_ASSERT(m_logger->currentFunctionHasCompileError() || !lhs.isEmpty());
3926 Q_ASSERT(m_logger->currentFunctionHasCompileError() || !rhs.isEmpty());
3927
3928 const QQmlJSRegisterContent originalOut = originalType(m_state.accumulatorOut());
3929 m_body += m_state.accumulatorVariableOut;
3930 m_body += u" = "_s;
3931 const QString explicitCast
3932 = originalOut.isStoredIn(m_typeResolver->stringType())
3933 ? originalOut.storedType()->internalName()
3934 : QString();
3935 m_body += conversion(
3936 originalOut, m_state.accumulatorOut(),
3937 explicitCast + u'(' + lhs + u' ' + cppOperator + u' ' + rhs + u')');
3938 m_body += u";\n"_s;
3939}
3940
3941void QQmlJSCodeGenerator::generateArithmeticConstOperation(int rhsConst, const QString &cppOperator)
3942{
3943 generateArithmeticOperation(
3944 conversion(m_state.accumulatorIn(), m_state.readAccumulator(),
3945 consumedAccumulatorVariableIn()),
3946 conversion(literalType(m_typeResolver->int32Type()),
3947 m_state.readAccumulator(), QString::number(rhsConst)),
3948 cppOperator);
3949}
3950
3951void QQmlJSCodeGenerator::generateUnaryOperation(const QString &cppOperator)
3952{
3953 const auto var = conversion(m_state.accumulatorIn(),
3954 originalType(m_state.readAccumulator()),
3955 consumedAccumulatorVariableIn());
3956
3957 if (var == m_state.accumulatorVariableOut) {
3958 m_body += m_state.accumulatorVariableOut + u" = "_s + cppOperator + var + u";\n"_s;
3959 return;
3960 }
3961
3962 const auto originalResult = originalType(m_state.accumulatorOut());
3963 if (m_state.accumulatorOut() == originalResult) {
3964 m_body += m_state.accumulatorVariableOut + u" = "_s + var + u";\n"_s;
3965 m_body += m_state.accumulatorVariableOut + u" = "_s
3966 + cppOperator + m_state.accumulatorVariableOut + u";\n"_s;
3967 return;
3968 }
3969
3970 m_body += m_state.accumulatorVariableOut + u" = "_s + conversion(
3971 originalResult, m_state.accumulatorOut(), cppOperator + var) + u";\n"_s;
3972}
3973
3974void QQmlJSCodeGenerator::generateInPlaceOperation(const QString &cppOperator)
3975{
3976 {
3977 // If actually in place, we cannot consume the variable.
3978 const QString var = conversion(m_state.accumulatorIn(), m_state.readAccumulator(),
3979 m_state.accumulatorVariableIn);
3980 if (var == m_state.accumulatorVariableOut) {
3981 m_body += cppOperator + var + u";\n"_s;
3982 return;
3983 }
3984 }
3985
3986 const QString var = conversion(m_state.accumulatorIn(), m_state.readAccumulator(),
3987 consumedAccumulatorVariableIn());
3988
3989 const auto originalResult = originalType(m_state.accumulatorOut());
3990 if (m_state.accumulatorOut() == originalResult) {
3991 m_body += m_state.accumulatorVariableOut + u" = "_s + var + u";\n"_s;
3992 m_body += cppOperator + m_state.accumulatorVariableOut + u";\n"_s;
3993 return;
3994 }
3995
3996 m_body += u"{\n"_s;
3997 m_body += u"auto converted = "_s + var + u";\n"_s;
3998 m_body += m_state.accumulatorVariableOut + u" = "_s + conversion(
3999 originalResult, m_state.accumulatorOut(), u'('
4000 + cppOperator + u"converted)"_s) + u";\n"_s;
4001 m_body += u"}\n"_s;
4002}
4003
4004void QQmlJSCodeGenerator::generateLookup(const QString &lookup, const QString &initialization,
4005 const QString &resultPreparation)
4006{
4007 m_body += u"#ifndef QT_NO_DEBUG\n"_s;
4008 generateSetInstructionPointer();
4009 m_body += u"#endif\n"_s;
4010
4011 if (!resultPreparation.isEmpty())
4012 m_body += resultPreparation + u";\n"_s;
4013 m_body += u"while (!"_s + lookup + u") {\n"_s;
4014
4015 m_body += u"#ifdef QT_NO_DEBUG\n"_s;
4016 generateSetInstructionPointer();
4017 m_body += u"#endif\n"_s;
4018
4019 m_body += initialization + u";\n"_s;
4020 generateExceptionCheck();
4021 if (!resultPreparation.isEmpty())
4022 m_body += resultPreparation + u";\n"_s;
4023 m_body += u"}\n"_s;
4024}
4025
4026void QQmlJSCodeGenerator::generateJumpCodeWithTypeConversions(int relativeOffset)
4027{
4028 QString conversionCode;
4029 const int absoluteOffset = nextInstructionOffset() + relativeOffset;
4030 const auto annotation = m_annotations.find(absoluteOffset);
4031 if (static_cast<InstructionAnnotations::const_iterator>(annotation) != m_annotations.constEnd()) {
4032 const auto &conversions = annotation->second.typeConversions;
4033
4034 for (auto regIt = conversions.constBegin(), regEnd = conversions.constEnd();
4035 regIt != regEnd; ++regIt) {
4036 const QQmlJSRegisterContent targetType = regIt.value().content;
4037 if (!targetType.isValid() || !isTypeStorable(m_typeResolver, targetType.storedType()))
4038 continue;
4039
4040 const int registerIndex = regIt.key();
4041 const auto variable = m_registerVariables.constFind(targetType);
4042
4043 if (variable == m_registerVariables.constEnd())
4044 continue;
4045
4046 QQmlJSRegisterContent currentType;
4047 QString currentVariable;
4048 if (registerIndex == m_state.changedRegisterIndex()) {
4049 currentVariable = changedRegisterVariable();
4050 if (variable->variableName == currentVariable)
4051 continue;
4052
4053 currentType = m_state.changedRegister();
4054
4055 // TODO: We can std::move the changed register in some cases here but it needs be
4056 // done carefully (QTBUG-141920).
4057 } else {
4058 const auto it = m_state.registers.find(registerIndex);
4059 if (it == m_state.registers.end()
4060 || variable->variableName == registerVariable(registerIndex)) {
4061 continue;
4062 }
4063
4064 currentType = it.value().content;
4065 currentVariable = consumedRegisterVariable(registerIndex);
4066 }
4067
4068 // Actually == here. We want the jump code also for equal types
4069 if (currentType == targetType)
4070 continue;
4071
4072 conversionCode += variable->variableName;
4073 conversionCode += u" = "_s;
4074 conversionCode += conversion(currentType, targetType, currentVariable);
4075 conversionCode += u";\n"_s;
4076 }
4077 }
4078
4079 if (relativeOffset) {
4080 auto labelIt = m_labels.find(absoluteOffset);
4081 if (labelIt == m_labels.end())
4082 labelIt = m_labels.insert(absoluteOffset, u"label_%1"_s.arg(m_labels.size()));
4083 conversionCode += u" goto "_s + *labelIt + u";\n"_s;
4084 }
4085
4086 m_body += u"{\n"_s + conversionCode + u"}\n"_s;
4087}
4088
4089QString QQmlJSCodeGenerator::registerVariable(int index) const
4090{
4091 QQmlJSRegisterContent content = registerType(index);
4092 const auto it = m_registerVariables.constFind(content);
4093 if (it != m_registerVariables.constEnd())
4094 return it->variableName;
4095
4096 return QString();
4097}
4098
4099QString QQmlJSCodeGenerator::lookupVariable(int lookupIndex) const
4100{
4101 for (auto it = m_registerVariables.constBegin(), end = m_registerVariables.constEnd(); it != end; ++it) {
4102 if (it.key().resultLookupIndex() == lookupIndex)
4103 return it->variableName;
4104 }
4105 return QString();
4106}
4107
4108QString QQmlJSCodeGenerator::consumedRegisterVariable(int index) const
4109{
4110 const QString var = registerVariable(index);
4111 if (var.isEmpty() || !shouldMoveRegister(index))
4112 return var;
4113 return u"std::move(" + var + u")";
4114}
4115
4116QString QQmlJSCodeGenerator::consumedAccumulatorVariableIn() const
4117{
4118 return shouldMoveRegister(Accumulator)
4119 ? u"std::move(" + m_state.accumulatorVariableIn + u")"
4120 : m_state.accumulatorVariableIn;
4121}
4122
4123QString QQmlJSCodeGenerator::changedRegisterVariable() const
4124{
4125 QQmlJSRegisterContent changedRegister = m_state.changedRegister();
4126
4127 const QQmlJSScope::ConstPtr storedType = changedRegister.storedType();
4128 if (storedType.isNull())
4129 return QString();
4130
4131 return m_registerVariables.value(changedRegister).variableName;
4132}
4133
4134QQmlJSRegisterContent QQmlJSCodeGenerator::registerType(int index) const
4135{
4136 auto it = m_state.registers.find(index);
4137 if (it != m_state.registers.end())
4138 return it.value().content;
4139
4140 return QQmlJSRegisterContent();
4141}
4142
4143QQmlJSRegisterContent QQmlJSCodeGenerator::lookupType(int lookupIndex) const
4144{
4145 auto it = m_state.lookups.find(lookupIndex);
4146 if (it != m_state.lookups.end())
4147 return it.value().content;
4148
4149 return QQmlJSRegisterContent();
4150}
4151
4152bool QQmlJSCodeGenerator::shouldMoveRegister(int index) const
4153{
4154 return m_state.canMoveReadRegister(index)
4155 && !m_typeResolver->isTriviallyCopyable(m_state.readRegister(index).storedType());
4156}
4157
4158QString QQmlJSCodeGenerator::conversion(
4159 QQmlJSRegisterContent from, QQmlJSRegisterContent to, const QString &variable)
4160{
4161 const QQmlJSScope::ConstPtr contained = to.containedType();
4162
4163 // If from is QJSPrimitiveValue and to contains a primitive we coerce using QJSPrimitiveValue
4164 if (from.isStoredIn(m_typeResolver->jsPrimitiveType()) && m_typeResolver->isPrimitive(to)) {
4165
4166 QString primitive = [&]() -> QString {
4167 if (contained == m_typeResolver->jsPrimitiveType())
4168 return variable;
4169
4170 const QString conversion = variable + u".to<QJSPrimitiveValue::%1>()"_s;
4171 if (contained == m_typeResolver->boolType())
4172 return conversion.arg(u"Boolean"_s);
4173 if (m_typeResolver->isIntegral(to))
4174 return conversion.arg(u"Integer"_s);
4175 if (m_typeResolver->isNumeric(to))
4176 return conversion.arg(u"Double"_s);
4177 if (contained == m_typeResolver->stringType())
4178 return conversion.arg(u"String"_s);
4179 REJECT<QString>(
4180 u"Conversion of QJSPrimitiveValue to "_s + contained->internalName());
4181 }();
4182
4183 if (primitive.isEmpty())
4184 return primitive;
4185
4186 return convertStored(m_typeResolver->jsPrimitiveType(), to.storedType(), primitive);
4187 }
4188
4189 if (to.isStoredIn(contained)
4190 || m_typeResolver->isNumeric(to.storedType())
4191 || to.storedType()->isReferenceType()
4192 || from.contains(contained)) {
4193 // If:
4194 // * the output is not actually wrapped at all, or
4195 // * the output is stored in a numeric type (as there are no internals to a number), or
4196 // * the output is a QObject pointer, or
4197 // * we merely wrap the value into a new container,
4198 // we can convert by stored type.
4199 return convertStored(from.storedType(), to.storedType(), variable);
4200 } else {
4201 return convertContained(from, to, variable);
4202 }
4203}
4204
4205QString QQmlJSCodeGenerator::convertStored(
4206 const QQmlJSScope::ConstPtr &from, const QQmlJSScope::ConstPtr &to, const QString &variable)
4207{
4208 // TODO: most values can be moved, which is much more efficient with the common types.
4209 // add a move(from, to, variable) function that implements the moves.
4210 Q_ASSERT(!to->isComposite()); // We cannot directly convert to composites.
4211
4212 const auto jsValueType = m_typeResolver->jsValueType();
4213 const auto varType = m_typeResolver->varType();
4214 const auto jsPrimitiveType = m_typeResolver->jsPrimitiveType();
4215 const auto boolType = m_typeResolver->boolType();
4216
4217 auto zeroBoolOrInt = [&](const QQmlJSScope::ConstPtr &to) {
4218 if (to == boolType)
4219 return u"false"_s;
4220 if (m_typeResolver->isSignedInteger(to))
4221 return u"0"_s;
4222 if (m_typeResolver->isUnsignedInteger(to))
4223 return u"0u"_s;
4224 return QString();
4225 };
4226
4227 if (from == m_typeResolver->voidType()) {
4228 if (to->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
4229 return u"static_cast<"_s + to->internalName() + u" *>(nullptr)"_s;
4230 const QString zero = zeroBoolOrInt(to);
4231 if (!zero.isEmpty())
4232 return zero;
4233 if (to == m_typeResolver->floatType())
4234 return u"std::numeric_limits<float>::quiet_NaN()"_s;
4235 if (to == m_typeResolver->realType())
4236 return u"std::numeric_limits<double>::quiet_NaN()"_s;
4237 if (to == m_typeResolver->stringType())
4238 return QQmlJSUtils::toLiteral(u"undefined"_s);
4239 if (to == m_typeResolver->varType())
4240 return u"QVariant()"_s;
4241 if (to == m_typeResolver->jsValueType())
4242 return u"QJSValue();"_s;
4243 if (to == m_typeResolver->jsPrimitiveType())
4244 return u"QJSPrimitiveValue()"_s;
4245 if (from == to)
4246 return QString();
4247 }
4248
4249 if (from == m_typeResolver->nullType()) {
4250 if (to->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
4251 return u"static_cast<"_s + to->internalName() + u" *>(nullptr)"_s;
4252 if (to == jsValueType)
4253 return u"QJSValue(QJSValue::NullValue)"_s;
4254 if (to == jsPrimitiveType)
4255 return u"QJSPrimitiveValue(QJSPrimitiveNull())"_s;
4256 if (to == varType)
4257 return u"QVariant::fromValue<std::nullptr_t>(nullptr)"_s;
4258 const QString zero = zeroBoolOrInt(to);
4259 if (!zero.isEmpty())
4260 return zero;
4261 if (to == m_typeResolver->floatType())
4262 return u"0.0f"_s;
4263 if (to == m_typeResolver->realType())
4264 return u"0.0"_s;
4265 if (to == m_typeResolver->stringType())
4266 return QQmlJSUtils::toLiteral(u"null"_s);
4267 if (from == to)
4268 return QString();
4269 REJECT<QString>(u"Conversion from null to %1"_s.arg(to->internalName()));
4270 }
4271
4272 if (from == to)
4273 return variable;
4274
4275 if (from->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) {
4276 if (to->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) {
4277 // Compare internalName here. The same C++ type can be exposed muliple times in
4278 // different QML types. However, the C++ names have to be unique. We can always
4279 // static_cast to those.
4280
4281 for (QQmlJSScope::ConstPtr base = from; base; base = base->baseType()) {
4282 // We still have to cast as other execution paths may result in different types.
4283 if (base->internalName() == to->internalName())
4284 return u"static_cast<"_s + to->internalName() + u" *>("_s + variable + u')';
4285 }
4286 for (QQmlJSScope::ConstPtr base = to; base; base = base->baseType()) {
4287 if (base->internalName() == from->internalName())
4288 return u"static_cast<"_s + to->internalName() + u" *>("_s + variable + u')';
4289 }
4290 } else if (to == m_typeResolver->boolType()) {
4291 return u'(' + variable + u" != nullptr)"_s;
4292 }
4293 }
4294
4295 auto isJsValue = [&](const QQmlJSScope::ConstPtr &candidate) {
4296 return candidate == jsValueType || candidate->isScript();
4297 };
4298
4299 if (isJsValue(from) && isJsValue(to))
4300 return variable;
4301
4302 const auto isBoolOrNumber = [&](const QQmlJSScope::ConstPtr &type) {
4303 return m_typeResolver->isNumeric(type)
4304 || type == m_typeResolver->boolType()
4305 || type->scopeType() == QQmlSA::ScopeType::EnumScope;
4306 };
4307
4308 if (from == m_typeResolver->realType() || from == m_typeResolver->floatType()) {
4309 if (to == m_typeResolver->int64Type() || to == m_typeResolver->uint64Type()) {
4310 return to->internalName() + u"(QJSNumberCoercion::roundTowards0("_s
4311 + variable + u"))"_s;
4312 }
4313
4314 if (m_typeResolver->isSignedInteger(to))
4315 return u"QJSNumberCoercion::toInteger("_s + variable + u')';
4316 if (m_typeResolver->isUnsignedInteger(to))
4317 return u"uint(QJSNumberCoercion::toInteger("_s + variable + u"))"_s;
4318 if (to == m_typeResolver->boolType())
4319 return u"[](double moved){ return moved && !std::isnan(moved); }("_s + variable + u')';
4320 }
4321
4322 if (isBoolOrNumber(from) && isBoolOrNumber(to))
4323 return to->internalName() + u'(' + variable + u')';
4324
4325
4326 if (from == jsPrimitiveType) {
4327 if (to == boolType)
4328 return variable + u".toBoolean()"_s;
4329 if (to == m_typeResolver->int64Type() || to == m_typeResolver->uint64Type())
4330 return u"%1(%2.toDouble())"_s.arg(to->internalName(), variable);
4331 if (m_typeResolver->isIntegral(to))
4332 return u"%1(%2.toInteger())"_s.arg(to->internalName(), variable);
4333 if (m_typeResolver->isNumeric(to))
4334 return u"%1(%2.toDouble())"_s.arg(to->internalName(), variable);
4335 if (to == m_typeResolver->stringType())
4336 return variable + u".toString()"_s;
4337 if (to == jsValueType)
4338 return u"QJSValue(QJSPrimitiveValue("_s + variable + u"))"_s;
4339 if (to == varType)
4340 return variable + u".toVariant()"_s;
4341 if (to->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
4342 return u"static_cast<"_s + to->internalName() + u" *>(nullptr)"_s;
4343 }
4344
4345 if (isJsValue(from)) {
4346 if (to == jsPrimitiveType)
4347 return variable + u".toPrimitive()"_s;
4348 if (to == varType)
4349 return variable + u".toVariant(QJSValue::RetainJSObjects)"_s;
4350 return u"qjsvalue_cast<"_s + castTargetName(to) + u">("_s + variable + u')';
4351 }
4352
4353 if (to == jsPrimitiveType) {
4354 // null and undefined have been handled above already
4355 Q_ASSERT(from != m_typeResolver->nullType());
4356 Q_ASSERT(from != m_typeResolver->voidType());
4357
4358 if (from == m_typeResolver->boolType()
4359 || from == m_typeResolver->int32Type()
4360 || from == m_typeResolver->realType()
4361 || from == m_typeResolver->stringType()) {
4362 return u"QJSPrimitiveValue("_s + variable + u')';
4363 } else if (from == m_typeResolver->int16Type()
4364 || from == m_typeResolver->int8Type()
4365 || from == m_typeResolver->uint16Type()
4366 || from == m_typeResolver->uint8Type()) {
4367 return u"QJSPrimitiveValue(int("_s + variable + u"))"_s;
4368 } else if (m_typeResolver->isNumeric(from)) {
4369 return u"QJSPrimitiveValue(double("_s + variable + u"))"_s;
4370 }
4371 }
4372
4373 if (to == jsValueType)
4374 return u"aotContext->engine->toScriptValue("_s + variable + u')';
4375
4376 if (from == varType) {
4377 if (to == m_typeResolver->listPropertyType())
4378 return u"QQmlListReference("_s + variable + u", aotContext->qmlEngine())"_s;
4379 return u"aotContext->engine->fromVariant<"_s + castTargetName(to) + u">("_s
4380 + variable + u')';
4381 }
4382
4383 if (to == varType)
4384 return u"QVariant::fromValue("_s + variable + u')';
4385
4386 if (from == m_typeResolver->urlType() && to == m_typeResolver->stringType())
4387 return variable + u".toString()"_s;
4388
4389 if (from == m_typeResolver->stringType() && to == m_typeResolver->urlType())
4390 return u"QUrl("_s + variable + u')';
4391
4392 if (from == m_typeResolver->byteArrayType() && to == m_typeResolver->stringType())
4393 return u"QString::fromUtf8("_s + variable + u')';
4394
4395 if (from == m_typeResolver->stringType() && to == m_typeResolver->byteArrayType())
4396 return variable + u".toUtf8()"_s;
4397
4398 for (const auto &originType : {
4399 m_typeResolver->dateTimeType(),
4400 m_typeResolver->dateType(),
4401 m_typeResolver->timeType()}) {
4402 if (from == originType) {
4403 for (const auto &targetType : {
4404 m_typeResolver->dateTimeType(),
4405 m_typeResolver->dateType(),
4406 m_typeResolver->timeType(),
4407 m_typeResolver->stringType(),
4408 m_typeResolver->realType()}) {
4409 if (to == targetType) {
4410 return u"aotContext->engine->coerceValue<%1, %2>(%3)"_s.arg(
4411 originType->internalName(), targetType->internalName(), variable);
4412 }
4413 }
4414 break;
4415 }
4416 }
4417
4418 const auto retrieveFromPrimitive = [&](
4419 const QQmlJSScope::ConstPtr &type, const QString &expression) -> QString
4420 {
4421 if (type == m_typeResolver->boolType())
4422 return expression + u".toBoolean()"_s;
4423 if (m_typeResolver->isSignedInteger(type))
4424 return expression + u".toInteger()"_s;
4425 if (m_typeResolver->isUnsignedInteger(type))
4426 return u"uint("_s + expression + u".toInteger())"_s;
4427 if (type == m_typeResolver->realType())
4428 return expression + u".toDouble()"_s;
4429 if (type == m_typeResolver->floatType())
4430 return u"float("_s + expression + u".toDouble())"_s;
4431 if (type == m_typeResolver->stringType())
4432 return expression + u".toString()"_s;
4433 return QString();
4434 };
4435
4436 if (!retrieveFromPrimitive(from, u"x"_s).isEmpty()) {
4437 const QString retrieve = retrieveFromPrimitive(
4438 to, convertStored(from, m_typeResolver->jsPrimitiveType(), variable));
4439 if (!retrieve.isEmpty())
4440 return retrieve;
4441 }
4442
4443 if (from->isReferenceType() && to == m_typeResolver->stringType()) {
4444 return u"aotContext->engine->coerceValue<"_s + castTargetName(from) + u", "
4445 + castTargetName(to) + u">("_s + variable + u')';
4446 }
4447
4448 // Any value type is a non-null JS 'object' and therefore coerces to true.
4449 if (to == m_typeResolver->boolType()) {
4450 // All the interesting cases are already handled above:
4451 Q_ASSERT(from != m_typeResolver->nullType());
4452 Q_ASSERT(from != m_typeResolver->voidType());
4453 Q_ASSERT(retrieveFromPrimitive(from, u"x"_s).isEmpty());
4454 Q_ASSERT(!isBoolOrNumber(from));
4455
4456 return u"true"_s;
4457 }
4458
4459 if (m_typeResolver->areEquivalentLists(from, to))
4460 return variable;
4461
4462 if (from->isListProperty()
4463 && to->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
4464 && to->elementType()->isReferenceType()
4465 && !to->isListProperty()) {
4466 return variable + u".toList<"_s + to->internalName() + u">()"_s;
4467 }
4468
4469 bool isExtension = false;
4470 if (m_typeResolver->canPopulate(to, from, &isExtension)) {
4471 REJECT<QString>(
4472 u"populating "_s + to->internalName() + u" from "_s + from->internalName());
4473 } else if (const auto ctor = m_typeResolver->selectConstructor(to, from, &isExtension);
4474 ctor.isValid()) {
4475 const auto argumentTypes = ctor.parameters();
4476 const QString argument = convertStored(from, argumentTypes[0].type(), variable);
4477 if (isExtension) {
4478 // We typically use private inheritance for the foreign/extension trick.
4479 // Therefore we need to jump through some hoops to extract the actual value here.
4480 return u"[](auto &&arg) { "
4481 "%1 wrapper(std::forward<decltype(arg)>(arg)); "
4482 "return %2(reinterpret_cast<%2 &>(wrapper)); "
4483 "}(%3)"_s.arg(
4484 to->extensionType().scope->internalName(), to->internalName(), argument);
4485 }
4486
4487 return u"%1(%2)"_s.arg(to->internalName(), argument);
4488 }
4489
4490 if (to == m_typeResolver->stringType()
4491 && from->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence) {
4492 addInclude(u"QtQml/qjslist.h"_s);
4493
4494 // Extend the life time of whatever variable is across the call to toString().
4495 // variable may be an rvalue.
4496 return u"[&](auto &&l){ return QJSList(&l, aotContext->engine).toString(); }("_s
4497 + variable + u')';
4498 }
4499
4500 // TODO: add more conversions
4501
4502 REJECT<QString>(
4503 u"conversion from "_s + from->internalName() + u" to "_s + to->internalName());
4504}
4505
4506QString QQmlJSCodeGenerator::convertContained(QQmlJSRegisterContent from, QQmlJSRegisterContent to, const QString &variable)
4507{
4508 const QQmlJSScope::ConstPtr containedFrom = from.containedType();
4509 const QQmlJSScope::ConstPtr containedTo = to.containedType();
4510
4511 // Those should be handled before, by convertStored().
4512 Q_ASSERT(!to.storedType()->isReferenceType());
4513 Q_ASSERT(!to.isStoredIn(containedTo));
4514 Q_ASSERT(containedFrom != containedTo);
4515
4516 if (!to.isStoredIn(m_typeResolver->varType())
4517 && !to.isStoredIn(m_typeResolver->jsPrimitiveType())) {
4518 REJECT<QString>(u"internal conversion into unsupported wrapper type."_s);
4519 }
4520
4521 bool isExtension = false;
4522 if (m_typeResolver->canPopulate(containedTo, containedFrom, &isExtension)) {
4523 REJECT<QString>(u"populating "_s + containedTo->internalName()
4524 + u" from "_s + containedFrom->internalName());
4525 } else if (const auto ctor = m_typeResolver->selectConstructor(
4526 containedTo, containedFrom, &isExtension); ctor.isValid()) {
4527 return generateCallConstructor(
4528 ctor, {from}, {variable}, metaType(containedTo),
4529 metaObject(isExtension ? containedTo->extensionType().scope : containedTo));
4530 }
4531
4532 const auto originalFrom = originalType(from);
4533 const auto containedOriginalFrom = originalFrom.containedType();
4534 if (containedFrom != containedOriginalFrom
4535 && m_typeResolver->canHold(containedFrom, containedOriginalFrom)) {
4536 // If from is simply a wrapping of a specific type into a more general one, we can convert
4537 // the original type instead. You can't nest wrappings after all.
4538 return conversion(m_pool->storedIn(originalFrom, from.storedType()), to, variable);
4539 }
4540
4541 if (m_typeResolver->isPrimitive(containedFrom) && m_typeResolver->isPrimitive(containedTo)) {
4542 const QQmlJSRegisterContent intermediate
4543 = m_pool->storedIn(from, m_typeResolver->jsPrimitiveType());
4544 return conversion(intermediate, to, conversion(from, intermediate, variable));
4545 }
4546
4547 REJECT<QString>(
4548 u"internal conversion with incompatible or ambiguous types: %1 -> %2"_s
4549 .arg(from.descriptiveName(), to.descriptiveName()));
4550}
4551
4552void QQmlJSCodeGenerator::reject(const QString &thing)
4553{
4554 addError(u"Cannot generate efficient code for %1"_s.arg(thing));
4555}
4556
4557
4558void QQmlJSCodeGenerator::skip(const QString &thing)
4559{
4560 addSkip(u"Skipped code generation for %1"_s.arg(thing));
4561}
4562
4563QQmlJSCodeGenerator::AccumulatorConverter::AccumulatorConverter(QQmlJSCodeGenerator *generator)
4564 : accumulatorOut(generator->m_state.accumulatorOut())
4565 , accumulatorVariableIn(generator->m_state.accumulatorVariableIn)
4566 , accumulatorVariableOut(generator->m_state.accumulatorVariableOut)
4567 , generator(generator)
4568{
4569 if (accumulatorVariableOut.isEmpty())
4570 return;
4571
4572 const QQmlJSTypeResolver *resolver = generator->m_typeResolver;
4573 const QQmlJSScope::ConstPtr origContained = resolver->originalContainedType(accumulatorOut);
4574 const QQmlJSRegisterContent storage = accumulatorOut.storage();
4575 const QQmlJSScope::ConstPtr stored = storage.containedType();
4576 const QQmlJSScope::ConstPtr origStored = resolver->original(storage).containedType();
4577
4578
4579 // If the stored type differs or if we store in QVariant and the contained type differs,
4580 // then we have to use a temporary ...
4581 if (origStored != stored
4582 || (origContained != accumulatorOut.containedType() && stored == resolver->varType())) {
4583
4584 const bool storable = isTypeStorable(resolver, origStored);
4585 generator->m_state.accumulatorVariableOut = storable ? u"retrieved"_s : QString();
4586 generator->m_state.setRegister(Accumulator, generator->originalType(accumulatorOut));
4587 generator->m_body += u"{\n"_s;
4588 if (storable) {
4589 generator->m_body += origStored->augmentedInternalName() + u' '
4590 + generator->m_state.accumulatorVariableOut + u";\n";
4591 }
4592 } else if (generator->m_state.accumulatorVariableIn == generator->m_state.accumulatorVariableOut
4593 && generator->m_state.readsRegister(Accumulator)
4594 && generator->m_state.accumulatorOut().isStoredIn(resolver->varType())) {
4595 // If both m_state.accumulatorIn and m_state.accumulatorOut are QVariant, we will need to
4596 // prepare the output QVariant, and afterwards use the input variant. Therefore we need to
4597 // move the input out of the way first.
4598 generator->m_state.accumulatorVariableIn
4599 = generator->m_state.accumulatorVariableIn + u"_moved"_s;
4600 generator->m_body += u"{\n"_s;
4601 generator->m_body += u"QVariant "_s + generator->m_state.accumulatorVariableIn
4602 + u" = std::move("_s + generator->m_state.accumulatorVariableOut + u");\n"_s;
4603 }
4604}
4605
4606QQmlJSCodeGenerator::AccumulatorConverter::~AccumulatorConverter()
4607{
4608 if (accumulatorVariableOut != generator->m_state.accumulatorVariableOut) {
4609 generator->m_body += accumulatorVariableOut + u" = "_s + generator->conversion(
4610 generator->m_state.accumulatorOut(), accumulatorOut,
4611 u"std::move("_s + generator->m_state.accumulatorVariableOut + u')') + u";\n"_s;
4612 generator->m_body += u"}\n"_s;
4613 generator->m_state.setRegister(Accumulator, accumulatorOut);
4614 generator->m_state.accumulatorVariableOut = accumulatorVariableOut;
4615 } else if (accumulatorVariableIn != generator->m_state.accumulatorVariableIn) {
4616 generator->m_body += u"}\n"_s;
4617 generator->m_state.accumulatorVariableIn = accumulatorVariableIn;
4618 }
4619}
4620
4621
4622QT_END_NAMESPACE
4623
4624QQmlJSCodeGenerator::GeneratePragmaWarningBlock::GeneratePragmaWarningBlock(QQmlJSCodeGenerator *generator)
4625 : m_generator(generator)
4626{
4627 m_generator->m_body += u"QT_WARNING_PUSH\n"_s;
4628}
4629
4630QQmlJSCodeGenerator::GeneratePragmaWarningBlock::~GeneratePragmaWarningBlock()
4631{
4632 m_generator->m_body += u"QT_WARNING_POP\n"_s;
4633}
4634
4635void QQmlJSCodeGenerator::GeneratePragmaWarningBlock::silenceDivideByZero()
4636{
4637 // currently only needed with MSVC, if we need it on more compilers, we
4638 // should add a proper macro in Qt itself
4639 m_generator->m_body += u"QT_WARNING_DISABLE_MSVC(4723) // potential divide by 0\n"_s;
4640}
4641
4642void QQmlJSCodeGenerator::recordPropertyLookup(const QQmlJSScope::ConstPtr &base,
4643 const QQmlJSMetaProperty &property)
4644{
4645 if (!m_noAotValidation) {
4646 if (!m_lookupSignaturesRecorder.recordPropertyLookup(base, property))
4647 REJECT(m_lookupSignaturesRecorder.rejectMessage());
4648 }
4649}
4650
4651void QQmlJSCodeGenerator::recordMethodLookup(const QQmlJSScope::ConstPtr &base,
4652 const QQmlJSMetaMethod &method)
4653{
4654 if (!m_noAotValidation) {
4655 if (!m_lookupSignaturesRecorder.recordMethodLookup(base, method))
4656 REJECT(m_lookupSignaturesRecorder.rejectMessage());
4657 }
4658}
4659
4660void QQmlJSCodeGenerator::recordEnumKeyLookup(const QQmlJSScope::ConstPtr &base,
4661 const QQmlJSMetaEnum &metaEnum,
4662 const QString &keyName)
4663{
4664 if (!m_noAotValidation) {
4665 if (!m_lookupSignaturesRecorder.recordEnumKeyLookup(base, metaEnum, keyName))
4666 REJECT(m_lookupSignaturesRecorder.rejectMessage());
4667 }
4668}
\inmodule QtQmlCompiler
AccessSemantics
Definition qqmlsa.h:50
Combined button and popup list for selecting options.
static QString toNumericString(double value)
static QString messageTypeForMethod(const QString &method)
static QString derefContentPointer(const QString &contentPointer)
static bool canTypeBeAffectedBySideEffects(const QQmlJSTypeResolver *typeResolver, const QQmlJSRegisterContent &baseType)
#define BYTECODE_UNIMPLEMENTED()
#define INJECT_TRACE_INFO(function)
#define REJECT
static QString registerName(int registerIndex, int offset)
static QString minExpression(int argc)
static QString maxExpression(int argc)
static bool isTypeStorable(const QQmlJSTypeResolver *resolver, const QQmlJSScope::ConstPtr &type)