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

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