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
43static bool isTypeStorable(const QQmlJSTypeResolver *resolver, const QQmlJSScope::ConstPtr &type)
44{
45 return !type.isNull()
46 && !resolver->equals(a: type, b: resolver->nullType())
47 && !resolver->equals(a: type, b: resolver->voidType());
48}
49
50QString QQmlJSCodeGenerator::castTargetName(const QQmlJSScope::ConstPtr &type) const
51{
52 return type->augmentedInternalName();
53}
54
55QQmlJSCodeGenerator::QQmlJSCodeGenerator(const QV4::Compiler::Context *compilerContext,
56 const QV4::Compiler::JSUnitGenerator *unitGenerator,
57 const QQmlJSTypeResolver *typeResolver,
58 QQmlJSLogger *logger)
59 : QQmlJSCompilePass(unitGenerator, typeResolver, logger)
60 , m_context(compilerContext)
61{}
62
63QString QQmlJSCodeGenerator::metaTypeFromType(const QQmlJSScope::ConstPtr &type) const
64{
65 return u"QMetaType::fromType<"_s + type->augmentedInternalName() + u">()"_s;
66}
67
68QString QQmlJSCodeGenerator::metaTypeFromName(const QQmlJSScope::ConstPtr &type) const
69{
70 return u"[]() { static const auto t = QMetaType::fromName(\""_s
71 + QString::fromUtf8(ba: QMetaObject::normalizedType(type: type->augmentedInternalName().toUtf8()))
72 + u"\"); return t; }()"_s;
73}
74
75QString QQmlJSCodeGenerator::metaObject(const QQmlJSScope::ConstPtr &objectType)
76{
77 if (!objectType->isComposite()) {
78 if (objectType->internalName() == u"QObject"_s
79 || objectType->internalName() == u"QQmlComponent"_s) {
80 return u'&' + objectType->internalName() + u"::staticMetaObject"_s;
81 }
82 return metaTypeFromName(type: objectType) + u".metaObject()"_s;
83 }
84
85 reject(thing: u"retrieving the metaObject of a composite type without using an instance."_s);
86 return QString();
87}
88
89QString QQmlJSCodeGenerator::metaType(const QQmlJSScope::ConstPtr &type)
90{
91 return m_typeResolver->equals(a: m_typeResolver->genericType(type), b: type)
92 ? metaTypeFromType(type)
93 : metaTypeFromName(type);
94}
95
96QQmlJSAotFunction QQmlJSCodeGenerator::run(
97 const Function *function, const InstructionAnnotations *annotations,
98 QQmlJS::DiagnosticMessage *error)
99{
100 m_annotations = annotations;
101 m_function = function;
102 m_error = error;
103
104 QHash<int, int> numRegisterVariablesPerIndex;
105
106 auto addVariable = [&](int registerIndex, const QQmlJSScope::ConstPtr &seenType) {
107 // Don't generate any variables for registers that are initialized with undefined.
108 if (registerIndex == InvalidRegister || !isTypeStorable(resolver: m_typeResolver, type: seenType))
109 return;
110
111 const RegisterVariablesKey key = { .internalName: seenType->internalName(), .registerIndex: registerIndex };
112
113
114 const auto oldSize = m_registerVariables.size();
115 auto &e = m_registerVariables[key];
116 if (m_registerVariables.size() != oldSize) {
117 e.variableName = u"r%1_%2"_s
118 .arg(a: registerIndex)
119 .arg(a: numRegisterVariablesPerIndex[registerIndex]++);
120 e.storedType = m_typeResolver->comparableType(type: seenType);
121 }
122 ++e.numTracked;
123 };
124
125QT_WARNING_PUSH
126QT_WARNING_DISABLE_CLANG("-Wrange-loop-analysis")
127 for (const auto &annotation : *m_annotations) {
128 addVariable(annotation.second.changedRegisterIndex,
129 annotation.second.changedRegister.storedType());
130 for (auto it = annotation.second.typeConversions.begin(),
131 end = annotation.second.typeConversions.end();
132 it != end; ++it) {
133 addVariable(it.key(), it.value().content.storedType());
134 }
135 }
136QT_WARNING_POP
137
138 // ensure we have m_labels for loops
139 for (const auto loopLabel : m_context->labelInfo)
140 m_labels.insert(key: loopLabel, value: u"label_%1"_s.arg(a: m_labels.size()));
141
142 // Initialize the first instruction's state to hold the arguments.
143 // After this, the arguments (or whatever becomes of them) are carried
144 // over into any further basic blocks automatically.
145 m_state.State::operator=(initialState(function: m_function));
146
147 const QByteArray byteCode = function->code;
148 decode(code: byteCode.constData(), len: static_cast<uint>(byteCode.size()));
149
150 QQmlJSAotFunction result;
151 result.includes.swap(other&: m_includes);
152
153 result.code += u"// %1 at line %2, column %3\n"_s
154 .arg(a: m_context->name).arg(a: m_context->line).arg(a: m_context->column);
155
156 for (auto registerIt = m_registerVariables.cbegin(), registerEnd = m_registerVariables.cend();
157 registerIt != registerEnd; ++registerIt) {
158
159 const int registerIndex = registerIt.key().registerIndex;
160 const bool registerIsArgument = isArgument(registerIndex);
161
162 result.code += registerIt.key().internalName;
163
164 const QQmlJSScope::ConstPtr storedType = registerIt->storedType;
165 const bool isPointer
166 = (storedType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference);
167 if (isPointer)
168 result.code += u" *"_s;
169 else
170 result.code += u' ';
171
172 if (!registerIsArgument
173 && registerIndex != Accumulator
174 && registerIndex != This
175 && !m_typeResolver->registerIsStoredIn(
176 reg: function->registerTypes[registerIndex - firstRegisterIndex()],
177 type: m_typeResolver->voidType())) {
178 result.code += registerIt->variableName + u" = "_s;
179 result.code += convertStored(from: m_typeResolver->voidType(), to: storedType, variable: QString());
180 } else if (registerIsArgument && m_typeResolver->registerIsStoredIn(
181 reg: argumentType(registerIndex), type: storedType)) {
182 const int argumentIndex = registerIndex - FirstArgument;
183 const QQmlJSRegisterContent argument
184 = m_function->argumentTypes[argumentIndex];
185 const QQmlJSRegisterContent original
186 = m_typeResolver->original(type: argument);
187
188 const bool needsConversion = argument != original;
189 if (!isPointer && registerIt->numTracked == 1 && !needsConversion) {
190 // Not a pointer, never written to, and doesn't need any initial conversion.
191 // This is a readonly argument.
192 //
193 // We would like to make the variable a const ref if it's a readonly argument,
194 // but due to the various call interfaces accepting non-const values, we can't.
195 // We rely on those calls to still not modify their arguments in place.
196 result.code += u'&';
197 }
198
199 result.code += registerIt->variableName + u" = "_s;
200
201 const QString originalValue = u"*static_cast<"_s + castTargetName(type: original.storedType())
202 + u"*>(argumentsPtr["_s + QString::number(argumentIndex) + u"])"_s;
203
204 if (needsConversion)
205 result.code += conversion(from: original, to: argument, variable: originalValue);
206 else
207 result.code += originalValue;
208 } else {
209 result.code += registerIt->variableName;
210 }
211 result.code += u";\n"_s;
212 }
213
214 result.code += m_body;
215
216 for (const QQmlJSRegisterContent &argType : std::as_const(t: function->argumentTypes)) {
217 if (argType.isValid()) {
218 result.argumentTypes.append(
219 t: m_typeResolver->originalType(type: argType.storedType())
220 ->augmentedInternalName());
221 } else {
222 result.argumentTypes.append(t: u"void"_s);
223 }
224 }
225
226 if (function->returnType) {
227 result.returnType = function->returnType->internalName();
228 if (function->returnType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
229 result.returnType += u'*';
230 } else {
231 result.returnType = u"void"_s;
232 }
233
234 return result;
235}
236
237QString QQmlJSCodeGenerator::errorReturnValue()
238{
239 if (auto ret = m_function->returnType) {
240 return ret->accessSemantics() == QQmlJSScope::AccessSemantics::Reference
241 ? convertStored(from: m_typeResolver->nullType(), to: ret, variable: QString())
242 : ret->internalName() + u"()"_s;
243 }
244 return QString();
245}
246
247void QQmlJSCodeGenerator::generate_Ret()
248{
249 INJECT_TRACE_INFO(generate_Ret);
250
251 if (m_function->returnType) {
252 const QString signalUndefined = u"aotContext->setReturnValueUndefined();\n"_s;
253 if (!m_state.accumulatorVariableIn.isEmpty()) {
254 const QString in = m_state.accumulatorVariableIn;
255 if (m_typeResolver->registerIsStoredIn(
256 reg: m_state.accumulatorIn(), type: m_typeResolver->varType())) {
257 m_body += u"if (!"_s + in + u".isValid())\n"_s;
258 m_body += u" "_s + signalUndefined;
259 } else if (m_typeResolver->registerIsStoredIn(
260 reg: m_state.accumulatorIn(), type: m_typeResolver->jsPrimitiveType())) {
261 m_body += u"if ("_s + in
262 + u".type() == QJSPrimitiveValue::Undefined)\n"_s;
263 m_body += u" "_s + signalUndefined;
264 } else if (m_typeResolver->registerIsStoredIn(
265 reg: m_state.accumulatorIn(), type: m_typeResolver->jsValueType())) {
266 m_body += u"if ("_s + in + u".isUndefined())\n"_s;
267 m_body += u" "_s + signalUndefined;
268 }
269 m_body += u"return "_s
270 + convertStored(from: m_state.accumulatorIn().storedType(), to: m_function->returnType, variable: in);
271 } else {
272 if (m_typeResolver->equals(a: m_state.accumulatorIn().storedType(),
273 b: m_typeResolver->voidType())) {
274 m_body += signalUndefined;
275 }
276 m_body += u"return "_s + convertStored(
277 from: m_state.accumulatorIn().storedType(), to: m_function->returnType, variable: QString());
278 }
279 } else {
280 m_body += u"return"_s;
281 }
282
283 m_body += u";\n"_s;
284 m_skipUntilNextLabel = true;
285 resetState();
286}
287
288void QQmlJSCodeGenerator::generate_Debug()
289{
290 BYTECODE_UNIMPLEMENTED();
291}
292
293static QString toNumericString(double value)
294{
295 if (value >= std::numeric_limits<int>::min() && value <= std::numeric_limits<int>::max()) {
296 const int i = value;
297 if (i == value)
298 return QString::number(i);
299 }
300
301 switch (qFpClassify(val: value)) {
302 case FP_INFINITE: {
303 const QString inf = u"std::numeric_limits<double>::infinity()"_s;
304 return std::signbit(x: value) ? (u'-' + inf) : inf;
305 }
306 case FP_NAN:
307 return u"std::numeric_limits<double>::quiet_NaN()"_s;
308 case FP_ZERO:
309 return std::signbit(x: value) ? u"-0.0"_s : u"0"_s;
310 default:
311 break;
312 }
313
314 return QString::number(value, format: 'f', precision: std::numeric_limits<double>::max_digits10);
315}
316
317void QQmlJSCodeGenerator::generate_LoadConst(int index)
318{
319 INJECT_TRACE_INFO(generate_LoadConst);
320
321 // You cannot actually get it to generate LoadConst for anything but double. We have
322 // a numer of specialized instructions for the other types, after all. However, let's
323 // play it safe.
324
325 const QV4::ReturnedValue encodedConst = m_jsUnitGenerator->constant(idx: index);
326 const QV4::StaticValue value = QV4::StaticValue::fromReturnedValue(val: encodedConst);
327 const QQmlJSScope::ConstPtr type = m_typeResolver->typeForConst(rv: encodedConst);
328
329 m_body += m_state.accumulatorVariableOut + u" = "_s;
330 if (type == m_typeResolver->realType()) {
331 m_body += conversion(
332 from: type, to: m_state.accumulatorOut(),
333 variable: toNumericString(value: value.doubleValue()));
334 } else if (type == m_typeResolver->int32Type()) {
335 m_body += conversion(
336 from: type, to: m_state.accumulatorOut(),
337 variable: QString::number(value.integerValue()));
338 } else if (type == m_typeResolver->boolType()) {
339 m_body += conversion(
340 from: type, to: m_state.accumulatorOut(),
341 variable: value.booleanValue() ? u"true"_s : u"false"_s);
342 } else if (type == m_typeResolver->voidType()) {
343 m_body += conversion(
344 from: type, to: m_state.accumulatorOut(),
345 variable: QString());
346 } else if (type == m_typeResolver->nullType()) {
347 m_body += conversion(
348 from: type, to: m_state.accumulatorOut(),
349 variable: u"nullptr"_s);
350 } else {
351 reject(thing: u"unsupported constant type"_s);
352 }
353
354 m_body += u";\n"_s;
355}
356
357void QQmlJSCodeGenerator::generate_LoadZero()
358{
359 INJECT_TRACE_INFO(generate_LoadZero);
360
361 m_body += m_state.accumulatorVariableOut;
362 m_body += u" = "_s + conversion(
363 from: m_typeResolver->int32Type(), to: m_state.accumulatorOut(), variable: u"0"_s);
364 m_body += u";\n"_s;
365}
366
367void QQmlJSCodeGenerator::generate_LoadTrue()
368{
369 INJECT_TRACE_INFO(generate_LoadTrue);
370
371 m_body += m_state.accumulatorVariableOut;
372 m_body += u" = "_s + conversion(
373 from: m_typeResolver->boolType(), to: m_state.accumulatorOut(), variable: u"true"_s);
374 m_body += u";\n"_s;
375}
376
377void QQmlJSCodeGenerator::generate_LoadFalse()
378{
379 INJECT_TRACE_INFO(generate_LoadFalse);
380
381 m_body += m_state.accumulatorVariableOut;
382 m_body += u" = "_s + conversion(
383 from: m_typeResolver->boolType(), to: m_state.accumulatorOut(), variable: u"false"_s);
384 m_body += u";\n"_s;
385}
386
387void QQmlJSCodeGenerator::generate_LoadNull()
388{
389 INJECT_TRACE_INFO(generate_LoadNull);
390
391 m_body += m_state.accumulatorVariableOut + u" = "_s;
392 m_body += conversion(from: m_typeResolver->nullType(), to: m_state.accumulatorOut(),
393 variable: u"nullptr"_s);
394 m_body += u";\n"_s;
395}
396
397void QQmlJSCodeGenerator::generate_LoadUndefined()
398{
399 INJECT_TRACE_INFO(generate_LoadUndefined);
400
401 m_body += m_state.accumulatorVariableOut + u" = "_s;
402 m_body += conversion(from: m_typeResolver->voidType(), to: m_state.accumulatorOut(),
403 variable: QString());
404 m_body += u";\n"_s;
405}
406
407void QQmlJSCodeGenerator::generate_LoadInt(int value)
408{
409 INJECT_TRACE_INFO(generate_LoadInt);
410
411 m_body += m_state.accumulatorVariableOut;
412 m_body += u" = "_s;
413 m_body += conversion(from: m_typeResolver->int32Type(), to: m_state.accumulatorOut(),
414 variable: QString::number(value));
415 m_body += u";\n"_s;
416}
417
418void QQmlJSCodeGenerator::generate_MoveConst(int constIndex, int destTemp)
419{
420 INJECT_TRACE_INFO(generate_MoveConst);
421
422 Q_ASSERT(destTemp == m_state.changedRegisterIndex());
423
424 auto var = changedRegisterVariable();
425 if (var.isEmpty())
426 return; // Do not load 'undefined'
427
428 const auto v4Value = QV4::StaticValue::fromReturnedValue(
429 val: m_jsUnitGenerator->constant(idx: constIndex));
430
431 const auto changed = m_state.changedRegister();
432 QQmlJSScope::ConstPtr contained;
433 QString input;
434
435 m_body += var + u" = "_s;
436 if (v4Value.isNull()) {
437 contained = m_typeResolver->nullType();
438 } else if (v4Value.isUndefined()) {
439 contained = m_typeResolver->voidType();
440 } else if (v4Value.isBoolean()) {
441 contained = m_typeResolver->boolType();
442 input = v4Value.booleanValue() ? u"true"_s : u"false"_s;
443 } else if (v4Value.isInteger()) {
444 contained = m_typeResolver->int32Type();
445 input = QString::number(v4Value.int_32());
446 } else if (v4Value.isDouble()) {
447 contained = m_typeResolver->realType();
448 input = toNumericString(value: v4Value.doubleValue());
449 } else {
450 reject(thing: u"unknown const type"_s);
451 }
452 m_body += conversion(from: contained, to: changed, variable: input) + u";\n"_s;
453}
454
455void QQmlJSCodeGenerator::generate_LoadReg(int reg)
456{
457 INJECT_TRACE_INFO(generate_LoadReg);
458
459 m_body += m_state.accumulatorVariableOut;
460 m_body += u" = "_s;
461 m_body += conversion(
462 from: registerType(index: reg), to: m_state.accumulatorOut(), variable: consumedRegisterVariable(index: reg));
463 m_body += u";\n"_s;
464}
465
466void QQmlJSCodeGenerator::generate_StoreReg(int reg)
467{
468 INJECT_TRACE_INFO(generate_StoreReg);
469
470 Q_ASSERT(m_state.changedRegisterIndex() == reg);
471 Q_ASSERT(m_state.accumulatorIn().isValid());
472 const QString var = changedRegisterVariable();
473 if (var.isEmpty())
474 return; // don't store "undefined"
475 m_body += var;
476 m_body += u" = "_s;
477 m_body += conversion(from: m_state.accumulatorIn(), to: m_state.changedRegister(),
478 variable: consumedAccumulatorVariableIn());
479 m_body += u";\n"_s;
480}
481
482void QQmlJSCodeGenerator::generate_MoveReg(int srcReg, int destReg)
483{
484 INJECT_TRACE_INFO(generate_MoveReg);
485
486 Q_ASSERT(m_state.changedRegisterIndex() == destReg);
487 const QString destRegName = changedRegisterVariable();
488 if (destRegName.isEmpty())
489 return; // don't store things we cannot store.
490 m_body += destRegName;
491 m_body += u" = "_s;
492 m_body += conversion(
493 from: registerType(index: srcReg), to: m_state.changedRegister(), variable: consumedRegisterVariable(index: srcReg));
494 m_body += u";\n"_s;
495}
496
497void QQmlJSCodeGenerator::generate_LoadImport(int index)
498{
499 Q_UNUSED(index)
500 BYTECODE_UNIMPLEMENTED();
501}
502
503void QQmlJSCodeGenerator::generate_LoadLocal(int index)
504{
505 Q_UNUSED(index);
506 reject(thing: u"LoadLocal"_s);
507}
508
509void QQmlJSCodeGenerator::generate_StoreLocal(int index)
510{
511 Q_UNUSED(index)
512 BYTECODE_UNIMPLEMENTED();
513}
514
515void QQmlJSCodeGenerator::generate_LoadScopedLocal(int scope, int index)
516{
517 Q_UNUSED(scope)
518 Q_UNUSED(index)
519 BYTECODE_UNIMPLEMENTED();
520}
521
522void QQmlJSCodeGenerator::generate_StoreScopedLocal(int scope, int index)
523{
524 Q_UNUSED(scope)
525 Q_UNUSED(index)
526 BYTECODE_UNIMPLEMENTED();
527}
528
529void QQmlJSCodeGenerator::generate_LoadRuntimeString(int stringId)
530{
531 INJECT_TRACE_INFO(generate_LoadRuntimeString);
532
533 m_body += m_state.accumulatorVariableOut;
534 m_body += u" = "_s;
535 m_body += conversion(from: m_typeResolver->stringType(), to: m_state.accumulatorOut(),
536 variable: QQmlJSUtils::toLiteral(s: m_jsUnitGenerator->stringForIndex(index: stringId)));
537 m_body += u";\n"_s;
538}
539
540void QQmlJSCodeGenerator::generate_MoveRegExp(int regExpId, int destReg)
541{
542 Q_UNUSED(regExpId)
543 Q_UNUSED(destReg)
544 BYTECODE_UNIMPLEMENTED();
545}
546
547void QQmlJSCodeGenerator::generate_LoadClosure(int value)
548{
549 Q_UNUSED(value)
550 reject(thing: u"LoadClosure"_s);
551}
552
553void QQmlJSCodeGenerator::generate_LoadName(int nameIndex)
554{
555 Q_UNUSED(nameIndex)
556 reject(thing: u"LoadName"_s);
557}
558
559void QQmlJSCodeGenerator::generate_LoadGlobalLookup(int index)
560{
561 INJECT_TRACE_INFO(generate_LoadGlobalLookup);
562
563 AccumulatorConverter registers(this);
564
565 const QString lookup = u"aotContext->loadGlobalLookup("_s + QString::number(index)
566 + u", &"_s + m_state.accumulatorVariableOut + u", "_s
567 + metaTypeFromType(type: m_state.accumulatorOut().storedType()) + u')';
568 const QString initialization = u"aotContext->initLoadGlobalLookup("_s
569 + QString::number(index) + u')';
570 generateLookup(lookup, initialization);
571}
572
573void QQmlJSCodeGenerator::generate_LoadQmlContextPropertyLookup(int index)
574{
575 INJECT_TRACE_INFO(generate_LoadQmlContextPropertyLookup);
576
577 AccumulatorConverter registers(this);
578
579 const int nameIndex = m_jsUnitGenerator->lookupNameIndex(index);
580 const QString name = m_jsUnitGenerator->stringForIndex(index: nameIndex);
581 if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::JavaScriptGlobal) {
582 m_body += m_state.accumulatorVariableOut + u" = "_s
583 + conversion(
584 from: m_typeResolver->original(type: m_state.accumulatorOut()), to: m_state.accumulatorOut(),
585 variable: u"aotContext->javaScriptGlobalProperty("_s + QString::number(nameIndex) + u")")
586 + u";\n"_s;
587 return;
588 }
589
590 const QString indexString = QString::number(index);
591 if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::ObjectById) {
592 const QString lookup = u"aotContext->loadContextIdLookup("_s
593 + indexString + u", "_s
594 + contentPointer(content: m_state.accumulatorOut(), var: m_state.accumulatorVariableOut) + u')';
595 const QString initialization = u"aotContext->initLoadContextIdLookup("_s
596 + indexString + u')';
597 generateLookup(lookup, initialization);
598 return;
599 }
600
601 const bool isProperty = m_state.accumulatorOut().isProperty();
602 const QQmlJSScope::ConstPtr scope = m_state.accumulatorOut().scopeType();
603 const QQmlJSScope::ConstPtr stored = m_state.accumulatorOut().storedType();
604 if (isProperty) {
605 const auto lookupType = contentType(content: m_state.accumulatorOut(), var: m_state.accumulatorVariableOut);
606
607 const QString lookup = u"aotContext->loadScopeObjectPropertyLookup("_s
608 + indexString + u", "_s
609 + contentPointer(content: m_state.accumulatorOut(), var: m_state.accumulatorVariableOut) + u')';
610 const QString initialization
611 = u"aotContext->initLoadScopeObjectPropertyLookup("_s
612 + indexString + u", "_s
613 + lookupType + u')';
614 const QString preparation = getLookupPreparation(
615 content: m_state.accumulatorOut(), var: m_state.accumulatorVariableOut, lookup: index);
616
617 generateLookup(lookup, initialization, resultPreparation: preparation);
618 } else if (m_state.accumulatorOut().isType() || m_state.accumulatorOut().isImportNamespace()) {
619 generateTypeLookup(index);
620 } else {
621 reject(thing: u"lookup of %1"_s.arg(a: m_state.accumulatorOut().descriptiveName()));
622 }
623}
624
625void QQmlJSCodeGenerator::generate_StoreNameSloppy(int nameIndex)
626{
627 INJECT_TRACE_INFO(generate_StoreNameSloppy);
628
629 const QString name = m_jsUnitGenerator->stringForIndex(index: nameIndex);
630 const QQmlJSRegisterContent type = m_typeResolver->scopedType(scope: m_function->qmlScope, name);
631 Q_ASSERT(type.isProperty());
632
633 switch (type.variant()) {
634 case QQmlJSRegisterContent::ScopeProperty:
635 case QQmlJSRegisterContent::ExtensionScopeProperty: {
636 // Do not convert here. We may intentionally pass the "wrong" type, for example to trigger
637 // a property reset.
638 m_body += u"aotContext->storeNameSloppy("_s + QString::number(nameIndex)
639 + u", "_s
640 + contentPointer(content: m_state.accumulatorIn(), var: m_state.accumulatorVariableIn)
641 + u", "_s
642 + contentType(content: m_state.accumulatorIn(), var: m_state.accumulatorVariableIn) + u')';
643 m_body += u";\n"_s;
644 break;
645 }
646 case QQmlJSRegisterContent::ScopeMethod:
647 case QQmlJSRegisterContent::ExtensionScopeMethod:
648 reject(thing: u"assignment to scope method"_s);
649 break;
650 default:
651 Q_UNREACHABLE();
652 }
653}
654
655void QQmlJSCodeGenerator::generate_StoreNameStrict(int name)
656{
657 Q_UNUSED(name)
658 BYTECODE_UNIMPLEMENTED();
659}
660
661void QQmlJSCodeGenerator::generate_LoadElement(int base)
662{
663 INJECT_TRACE_INFO(generate_LoadElement);
664
665 const QQmlJSRegisterContent baseType = registerType(index: base);
666
667 if (!m_typeResolver->isNumeric(type: m_state.accumulatorIn())
668 || (!baseType.isList()
669 && !m_typeResolver->registerIsStoredIn(reg: baseType, type: m_typeResolver->stringType()))) {
670 reject(thing: u"LoadElement with non-list base type or non-numeric arguments"_s);
671 return;
672 }
673
674 AccumulatorConverter registers(this);
675
676 const QString baseName = registerVariable(index: base);
677 const QString indexName = m_state.accumulatorVariableIn;
678
679 const QString voidAssignment = u" "_s + m_state.accumulatorVariableOut + u" = "_s +
680 conversion(from: m_typeResolver->globalType(type: m_typeResolver->voidType()),
681 to: m_state.accumulatorOut(), variable: QString()) + u";\n"_s;
682
683 if (!m_typeResolver->isIntegral(type: m_state.accumulatorIn())) {
684 m_body += u"if (!QJSNumberCoercion::isArrayIndex("_s + indexName + u"))\n"_s
685 + voidAssignment
686 + u"else "_s;
687 }
688
689 if (!m_typeResolver->isUnsignedInteger(
690 type: m_typeResolver->containedType(container: m_state.accumulatorIn()))) {
691 m_body += u"if ("_s + indexName + u" < 0)\n"_s
692 + voidAssignment
693 + u"else "_s;
694 }
695
696 if (m_typeResolver->registerIsStoredIn(reg: baseType, type: m_typeResolver->listPropertyType())) {
697 // Our QQmlListProperty only keeps plain QObject*.
698 const auto elementType = m_typeResolver->globalType(type: m_typeResolver->qObjectType());
699
700 m_body += u"if ("_s + indexName + u" < "_s + baseName
701 + u".count(&"_s + baseName + u"))\n"_s;
702 m_body += u" "_s + m_state.accumulatorVariableOut + u" = "_s +
703 conversion(from: elementType, to: m_state.accumulatorOut(),
704 variable: baseName + u".at(&"_s + baseName + u", "_s
705 + indexName + u')') + u";\n"_s;
706 m_body += u"else\n"_s
707 + voidAssignment;
708 return;
709 }
710
711 const auto elementType = m_typeResolver->valueType(list: baseType);
712
713 QString access = baseName + u".at("_s + indexName + u')';
714
715 // TODO: Once we get a char type in QML, use it here.
716 if (m_typeResolver->registerIsStoredIn(reg: baseType, type: m_typeResolver->stringType()))
717 access = u"QString("_s + access + u")"_s;
718 else if (m_state.isRegisterAffectedBySideEffects(registerIndex: base))
719 reject(thing: u"LoadElement on a sequence potentially affected by side effects"_s);
720 else if (baseType.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence)
721 reject(thing: u"LoadElement on a sequence wrapped in a non-sequence type"_s);
722
723 m_body += u"if ("_s + indexName + u" < "_s + baseName + u".size())\n"_s;
724 m_body += u" "_s + m_state.accumulatorVariableOut + u" = "_s +
725 conversion(from: elementType, to: m_state.accumulatorOut(), variable: access) + u";\n"_s;
726 m_body += u"else\n"_s
727 + voidAssignment;
728}
729
730void QQmlJSCodeGenerator::generate_StoreElement(int base, int index)
731{
732 INJECT_TRACE_INFO(generate_StoreElement);
733
734 const QQmlJSRegisterContent baseType = registerType(index: base);
735 const QQmlJSRegisterContent indexType = registerType(index);
736
737 if (!m_typeResolver->isNumeric(type: registerType(index)) || !baseType.isList()) {
738 reject(thing: u"StoreElement with non-list base type or non-numeric arguments"_s);
739 return;
740 }
741
742 if (!m_typeResolver->registerIsStoredIn(reg: baseType, type: m_typeResolver->listPropertyType())) {
743 reject(thing: u"indirect StoreElement"_s);
744 return;
745 }
746
747 const QString baseName = registerVariable(index: base);
748 const QString indexName = registerVariable(index);
749
750 const auto valueType = m_typeResolver->valueType(list: baseType);
751 const auto elementType = m_typeResolver->globalType(type: m_typeResolver->genericType(
752 type: m_typeResolver->containedType(container: valueType)));
753
754 m_body += u"if ("_s;
755 if (!m_typeResolver->isIntegral(type: indexType))
756 m_body += u"QJSNumberCoercion::isArrayIndex("_s + indexName + u") && "_s;
757 if (!m_typeResolver->isUnsignedInteger(type: m_typeResolver->containedType(container: indexType)))
758 m_body += indexName + u" >= 0 && "_s;
759 m_body += indexName + u" < "_s + baseName + u".count(&"_s + baseName
760 + u"))\n"_s;
761 m_body += u" "_s + baseName + u".replace(&"_s + baseName
762 + u", "_s + indexName + u", "_s;
763 m_body += conversion(from: m_state.accumulatorIn(), to: elementType, variable: m_state.accumulatorVariableIn)
764 + u");\n"_s;
765}
766
767void QQmlJSCodeGenerator::generate_LoadProperty(int nameIndex)
768{
769 Q_UNUSED(nameIndex)
770 reject(thing: u"LoadProperty"_s);
771}
772
773void QQmlJSCodeGenerator::generate_LoadOptionalProperty(int name, int offset)
774{
775 Q_UNUSED(name)
776 Q_UNUSED(offset)
777 BYTECODE_UNIMPLEMENTED();
778}
779
780void QQmlJSCodeGenerator::generateEnumLookup(int index)
781{
782 const QString enumMember = m_state.accumulatorOut().enumMember();
783
784 // If we're referring to the type, there's nothing to do.
785 if (enumMember.isEmpty())
786 return;
787
788 // If the metaenum has the value, just use it and skip all the rest.
789 const QQmlJSMetaEnum metaEnum = m_state.accumulatorOut().enumeration();
790 if (metaEnum.hasValues()) {
791 m_body += m_state.accumulatorVariableOut + u" = "_s
792 + QString::number(metaEnum.value(key: enumMember));
793 m_body += u";\n"_s;
794 return;
795 }
796
797 const QQmlJSScope::ConstPtr scopeType = m_state.accumulatorOut().scopeType();
798
799 // Otherwise we would have found an enum with values.
800 Q_ASSERT(!scopeType->isComposite());
801
802 const QString enumName = metaEnum.isFlag() ? metaEnum.alias() : metaEnum.name();
803 if (enumName.isEmpty()) {
804 if (metaEnum.isFlag() && !metaEnum.name().isEmpty())
805 reject(thing: u"qmltypes misses name entry for flag; did you pass the enum type to Q_FLAG instead of the QFlag type?"
806 "\nType is %1, enum name is %2"_s.arg(args: scopeType->internalName(), args: metaEnum.name()));
807 reject(thing: u"qmltypes misses name entry for enum"_s);
808 }
809 const QString lookup = u"aotContext->getEnumLookup("_s + QString::number(index)
810 + u", &"_s + m_state.accumulatorVariableOut + u')';
811 const QString initialization = u"aotContext->initGetEnumLookup("_s
812 + QString::number(index) + u", "_s + metaObject(objectType: scopeType)
813 + u", \""_s + enumName + u"\", \""_s + enumMember
814 + u"\")"_s;
815 generateLookup(lookup, initialization);
816}
817
818void QQmlJSCodeGenerator::generateTypeLookup(int index)
819{
820 const QString indexString = QString::number(index);
821 const QQmlJSRegisterContent accumulatorIn = m_state.registers.value(key: Accumulator).content;
822 const QString namespaceString
823 = accumulatorIn.isImportNamespace()
824 ? QString::number(accumulatorIn.importNamespace())
825 : u"QQmlPrivate::AOTCompiledContext::InvalidStringId"_s;
826
827 switch (m_state.accumulatorOut().variant()) {
828 case QQmlJSRegisterContent::Singleton: {
829 rejectIfNonQObjectOut(error: u"non-QObject singleton type"_s);
830 const QString lookup = u"aotContext->loadSingletonLookup("_s + indexString
831 + u", &"_s + m_state.accumulatorVariableOut + u')';
832 const QString initialization = u"aotContext->initLoadSingletonLookup("_s + indexString
833 + u", "_s + namespaceString + u')';
834 generateLookup(lookup, initialization);
835 break;
836 }
837 case QQmlJSRegisterContent::ScopeModulePrefix:
838 break;
839 case QQmlJSRegisterContent::ScopeAttached: {
840 rejectIfNonQObjectOut(error: u"non-QObject attached type"_s);
841 const QString lookup = u"aotContext->loadAttachedLookup("_s + indexString
842 + u", aotContext->qmlScopeObject, &"_s + m_state.accumulatorVariableOut + u')';
843 const QString initialization = u"aotContext->initLoadAttachedLookup("_s + indexString
844 + u", "_s + namespaceString + u", aotContext->qmlScopeObject)"_s;
845 generateLookup(lookup, initialization);
846 break;
847 }
848 case QQmlJSRegisterContent::Script:
849 reject(thing: u"script lookup"_s);
850 break;
851 case QQmlJSRegisterContent::MetaType: {
852 if (!m_typeResolver->registerIsStoredIn(
853 reg: m_state.accumulatorOut(), type: m_typeResolver->metaObjectType())) {
854 // TODO: Can we trigger this somehow?
855 // It might be impossible, but we better be safe here.
856 reject(thing: u"meta-object stored in different type"_s);
857 }
858 const QString lookup = u"aotContext->loadTypeLookup("_s + indexString
859 + u", &"_s + m_state.accumulatorVariableOut + u')';
860 const QString initialization = u"aotContext->initLoadTypeLookup("_s + indexString
861 + u", "_s + namespaceString + u")"_s;
862 generateLookup(lookup, initialization);
863 break;
864 }
865 default:
866 Q_UNREACHABLE();
867 }
868}
869
870void QQmlJSCodeGenerator::generateVariantEqualityComparison(
871 const QQmlJSRegisterContent &nonStorableContent, const QString &registerName, bool invert)
872{
873 const auto nonStorableType = m_typeResolver->containedType(container: nonStorableContent);
874 QQmlJSScope::ConstPtr comparedType =
875 m_typeResolver->equals(a: nonStorableType, b: m_typeResolver->nullType())
876 ? m_typeResolver->nullType()
877 : m_typeResolver->voidType();
878
879 // The common operations for both nulltype and voidtype
880 m_body += u"if ("_s + registerName
881 + u".metaType() == QMetaType::fromType<QJSPrimitiveValue>()) {\n"_s
882 + m_state.accumulatorVariableOut + u" = "_s
883 + conversion(from: m_typeResolver->boolType(), to: m_state.accumulatorOut(),
884 variable: u"static_cast<const QJSPrimitiveValue *>("_s + registerName
885 + u".constData())"_s + u"->type() "_s
886 + (invert ? u"!="_s : u"=="_s)
887 + (m_typeResolver->equals(a: comparedType, b: m_typeResolver->nullType())
888 ? u"QJSPrimitiveValue::Null"_s
889 : u"QJSPrimitiveValue::Undefined"_s))
890 + u";\n} else if ("_s + registerName
891 + u".metaType() == QMetaType::fromType<QJSValue>()) {\n"_s
892 + m_state.accumulatorVariableOut + u" = "_s
893 + conversion(from: m_typeResolver->boolType(), to: m_state.accumulatorOut(),
894 variable: (invert ? u"!"_s : QString()) + u"static_cast<const QJSValue *>("_s
895 + registerName + u".constData())"_s + u"->"_s
896 + (m_typeResolver->equals(a: comparedType, b: m_typeResolver->nullType())
897 ? u"isNull()"_s
898 : u"isUndefined()"_s))
899 + u";\n}"_s;
900
901 // Generate nullType specific operations (the case when variant contains QObject * or
902 // std::nullptr_t)
903 if (m_typeResolver->equals(a: nonStorableType, b: m_typeResolver->nullType())) {
904 m_body += u"else if ("_s + registerName
905 + u".metaType().flags().testFlag(QMetaType::PointerToQObject)) {\n"_s
906 + m_state.accumulatorVariableOut + u" = "_s
907 + conversion(from: m_typeResolver->boolType(), to: m_state.accumulatorOut(),
908 variable: u"*static_cast<QObject *const *>("_s + registerName
909 + u".constData())"_s + (invert ? u"!="_s : u"=="_s)
910 + u" nullptr"_s)
911 + u";\n} else if ("_s + registerName
912 + u".metaType() == QMetaType::fromType<std::nullptr_t>()) {\n"_s
913 + m_state.accumulatorVariableOut + u" = "_s
914 + conversion(from: m_typeResolver->boolType(), to: m_state.accumulatorOut(),
915 variable: (invert ? u"false"_s : u"true"_s))
916 + u";\n}\n"_s;
917 }
918
919 // fallback case (if variant contains a different type, then it is not null or undefined)
920 m_body += u"else {\n"_s + m_state.accumulatorVariableOut + u" = "_s
921 + conversion(from: m_typeResolver->boolType(), to: m_state.accumulatorOut(),
922 variable: (invert ? (registerName + u".isValid() ? true : false"_s)
923 : (registerName + u".isValid() ? false : true"_s)))
924 + u";\n}"_s;
925}
926
927void QQmlJSCodeGenerator::rejectIfNonQObjectOut(const QString &error)
928{
929 if (m_state.accumulatorOut().storedType()->accessSemantics()
930 != QQmlJSScope::AccessSemantics::Reference) {
931 reject(thing: error);
932 }
933}
934
935/*!
936 * \internal
937 *
938 * generates a check for the content pointer to be valid.
939 * Returns true if the content pointer needs to be retrieved from a QVariant, or
940 * false if the variable can be used as-is.
941 */
942bool QQmlJSCodeGenerator::generateContentPointerCheck(
943 const QQmlJSScope::ConstPtr &required, const QQmlJSRegisterContent &actual,
944 const QString &variable, const QString &errorMessage)
945{
946 const QQmlJSScope::ConstPtr scope = required;
947 const QQmlJSScope::ConstPtr input = m_typeResolver->containedType(container: actual);
948 if (QQmlJSUtils::searchBaseAndExtensionTypes(type: input,
949 check: [&](const QQmlJSScope::ConstPtr &base) {
950 return m_typeResolver->equals(a: base, b: scope);
951 })) {
952 return false;
953 }
954
955 if (!m_typeResolver->canHold(container: input, contained: scope)) {
956 reject(thing: u"lookup of members of %1 in %2"_s.arg(
957 args: scope->internalName(), args: input->internalName()));
958 }
959
960 bool needsVarContentConversion = false;
961 QString processedErrorMessage;
962 if (actual.storedType()->isReferenceType()) {
963 // Since we have verified the type in qqmljstypepropagator.cpp we now know
964 // that we can only have either null or the actual type here. Therefore,
965 // it's enough to check the pointer for null.
966 m_body += u"if ("_s + variable + u" == nullptr) {\n "_s;
967 processedErrorMessage = errorMessage.arg(a: u"null");
968 } else if (m_typeResolver->equals(a: actual.storedType(), b: m_typeResolver->varType())) {
969 // Since we have verified the type in qqmljstypepropagator.cpp we now know
970 // that we can only have either undefined or the actual type here. Therefore,
971 // it's enough to check the QVariant for isValid().
972 m_body += u"if (!"_s + variable + u".isValid()) {\n "_s;
973 needsVarContentConversion = true;
974 processedErrorMessage = errorMessage.arg(a: u"undefined");
975 } else {
976 reject(thing: u"retrieving metatype from %1"_s.arg(a: actual.descriptiveName()));
977 }
978
979 generateSetInstructionPointer();
980 m_body += u" aotContext->engine->throwError(QJSValue::TypeError, "_s;
981 m_body += u"QLatin1String(\"%1\"));\n"_s.arg(a: processedErrorMessage);
982 m_body += u" return "_s + errorReturnValue() + u";\n"_s;
983 m_body += u"}\n"_s;
984 return needsVarContentConversion;
985}
986
987QString QQmlJSCodeGenerator::resolveValueTypeContentPointer(
988 const QQmlJSScope::ConstPtr &required, const QQmlJSRegisterContent &actual,
989 const QString &variable, const QString &errorMessage)
990{
991 if (generateContentPointerCheck(required, actual, variable, errorMessage))
992 return variable + u".data()"_s;
993 return contentPointer(content: actual, var: variable);
994}
995
996QString QQmlJSCodeGenerator::resolveQObjectPointer(
997 const QQmlJSScope::ConstPtr &required, const QQmlJSRegisterContent &actual,
998 const QString &variable, const QString &errorMessage)
999{
1000 if (generateContentPointerCheck(required, actual, variable, errorMessage))
1001 return u"*static_cast<QObject *const *>("_s + variable + u".constData())"_s;
1002 return variable;
1003}
1004
1005void QQmlJSCodeGenerator::generate_GetLookup(int index)
1006{
1007 INJECT_TRACE_INFO(generate_GetLookup);
1008
1009 if (m_state.accumulatorOut().isMethod()) {
1010 reject(thing: u"lookup of function property."_s);
1011 return;
1012 }
1013
1014 if (m_state.accumulatorOut().isImportNamespace()) {
1015 Q_ASSERT(m_state.accumulatorOut().variant() == QQmlJSRegisterContent::ObjectModulePrefix);
1016 // If we have an object module prefix, we need to pass through the original object.
1017 if (m_state.accumulatorVariableIn != m_state.accumulatorVariableOut) {
1018 m_body += m_state.accumulatorVariableOut + u" = "_s
1019 + conversion(from: m_state.accumulatorIn(), to: m_state.accumulatorOut(),
1020 variable: m_state.accumulatorVariableIn)
1021 + u";\n"_s;
1022 }
1023 return;
1024 }
1025
1026 AccumulatorConverter registers(this);
1027
1028 if (m_state.accumulatorOut().isEnumeration()) {
1029 generateEnumLookup(index);
1030 return;
1031 }
1032
1033 const QString indexString = QString::number(index);
1034 const QString namespaceString = m_state.accumulatorIn().isImportNamespace()
1035 ? QString::number(m_state.accumulatorIn().importNamespace())
1036 : u"QQmlPrivate::AOTCompiledContext::InvalidStringId"_s;
1037 const auto accumulatorIn = m_state.accumulatorIn();
1038 const QQmlJSScope::ConstPtr scope = m_state.accumulatorOut().scopeType();
1039 const bool isReferenceType = scope->isReferenceType();
1040
1041 switch (m_state.accumulatorOut().variant()) {
1042 case QQmlJSRegisterContent::ObjectAttached: {
1043 if (!isReferenceType) {
1044 // This can happen on incomplete type information. We contextually know that the
1045 // type must be a QObject, but we cannot construct the inheritance chain. Then we
1046 // store it in a generic type. Technically we could even convert it to QObject*, but
1047 // that would be expensive.
1048 reject(thing: u"attached object for non-QObject type"_s);
1049 }
1050
1051 if (!m_state.accumulatorIn().storedType()->isReferenceType()) {
1052 // This can happen if we retroactively determine that the property might not be
1053 // what we think it is (ie, it can be shadowed).
1054 reject(thing: u"attached object of potentially non-QObject base"_s);
1055 }
1056
1057 rejectIfNonQObjectOut(error: u"non-QObject attached type"_s);
1058
1059 const QString lookup = u"aotContext->loadAttachedLookup("_s + indexString
1060 + u", "_s + m_state.accumulatorVariableIn
1061 + u", &"_s + m_state.accumulatorVariableOut + u')';
1062 const QString initialization = u"aotContext->initLoadAttachedLookup("_s
1063 + indexString + u", "_s + namespaceString + u", "_s
1064 + m_state.accumulatorVariableIn + u')';
1065 generateLookup(lookup, initialization);
1066 return;
1067 }
1068 case QQmlJSRegisterContent::ScopeAttached:
1069 case QQmlJSRegisterContent::Singleton:
1070 case QQmlJSRegisterContent::MetaType: {
1071 generateTypeLookup(index);
1072 return;
1073 }
1074 default:
1075 break;
1076 }
1077
1078 Q_ASSERT(m_state.accumulatorOut().isProperty());
1079
1080 if (m_typeResolver->registerIsStoredIn(reg: accumulatorIn, type: m_typeResolver->jsValueType())) {
1081 reject(thing: u"lookup in QJSValue"_s);
1082 } else if (isReferenceType) {
1083 const QString inputPointer = resolveQObjectPointer(
1084 required: scope, actual: accumulatorIn, variable: m_state.accumulatorVariableIn,
1085 errorMessage: u"Cannot read property '%1' of %2"_s.arg(
1086 a: m_jsUnitGenerator->lookupName(index)));
1087 const QString lookup = u"aotContext->getObjectLookup("_s + indexString
1088 + u", "_s + inputPointer + u", "_s
1089 + contentPointer(content: m_state.accumulatorOut(), var: m_state.accumulatorVariableOut) + u')';
1090 const QString initialization = u"aotContext->initGetObjectLookup("_s
1091 + indexString + u", "_s + inputPointer
1092 + u", "_s + contentType(content: m_state.accumulatorOut(), var: m_state.accumulatorVariableOut)
1093 + u')';
1094 const QString preparation = getLookupPreparation(
1095 content: m_state.accumulatorOut(), var: m_state.accumulatorVariableOut, lookup: index);
1096 generateLookup(lookup, initialization, resultPreparation: preparation);
1097 } else if ((scope->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
1098 || m_typeResolver->equals(a: scope, b: m_typeResolver->stringType()))
1099 && m_jsUnitGenerator->lookupName(index) == u"length"_s) {
1100 const QQmlJSScope::ConstPtr stored = accumulatorIn.storedType();
1101 if (stored->isListProperty()) {
1102 m_body += m_state.accumulatorVariableOut + u" = "_s;
1103 m_body += conversion(
1104 from: m_typeResolver->globalType(type: m_typeResolver->int32Type()),
1105 to: m_state.accumulatorOut(),
1106 variable: m_state.accumulatorVariableIn + u".count("_s + u'&'
1107 + m_state.accumulatorVariableIn + u')');
1108 m_body += u";\n"_s;
1109 } else if (stored->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
1110 || m_typeResolver->equals(a: stored, b: m_typeResolver->stringType())) {
1111 m_body += m_state.accumulatorVariableOut + u" = "_s
1112 + conversion(from: m_typeResolver->globalType(type: m_typeResolver->int32Type()),
1113 to: m_state.accumulatorOut(),
1114 variable: m_state.accumulatorVariableIn + u".length()"_s)
1115 + u";\n"_s;
1116 } else {
1117 reject(thing: u"access to 'length' property of sequence wrapped in non-sequence"_s);
1118 }
1119 } else if (m_typeResolver->registerIsStoredIn(reg: accumulatorIn,
1120 type: m_typeResolver->variantMapType())) {
1121 QString mapLookup = m_state.accumulatorVariableIn + u"["_s
1122 + QQmlJSUtils::toLiteral(s: m_jsUnitGenerator->lookupName(index)) + u"]"_s;
1123 m_body += m_state.accumulatorVariableOut + u" = "_s;
1124 m_body += conversion(from: m_typeResolver->globalType(type: m_typeResolver->varType()),
1125 to: m_state.accumulatorOut(), variable: mapLookup);
1126 m_body += u";\n"_s;
1127 } else {
1128 if (m_state.isRegisterAffectedBySideEffects(registerIndex: Accumulator))
1129 reject(thing: u"reading from a value that's potentially affected by side effects"_s);
1130
1131 const QString inputContentPointer = resolveValueTypeContentPointer(
1132 required: scope, actual: accumulatorIn, variable: m_state.accumulatorVariableIn,
1133 errorMessage: u"Cannot read property '%1' of %2"_s.arg(
1134 a: m_jsUnitGenerator->lookupName(index)));
1135
1136 const QString lookup = u"aotContext->getValueLookup("_s + indexString
1137 + u", "_s + inputContentPointer
1138 + u", "_s + contentPointer(content: m_state.accumulatorOut(), var: m_state.accumulatorVariableOut)
1139 + u')';
1140 const QString initialization = u"aotContext->initGetValueLookup("_s
1141 + indexString + u", "_s
1142 + metaObject(objectType: scope) + u", "_s
1143 + contentType(content: m_state.accumulatorOut(), var: m_state.accumulatorVariableOut) + u')';
1144 const QString preparation = getLookupPreparation(
1145 content: m_state.accumulatorOut(), var: m_state.accumulatorVariableOut, lookup: index);
1146 generateLookup(lookup, initialization, resultPreparation: preparation);
1147 }
1148}
1149
1150void QQmlJSCodeGenerator::generate_GetOptionalLookup(int index, int offset)
1151{
1152 Q_UNUSED(index)
1153 Q_UNUSED(offset)
1154 BYTECODE_UNIMPLEMENTED();
1155}
1156
1157void QQmlJSCodeGenerator::generate_StoreProperty(int nameIndex, int baseReg)
1158{
1159 Q_UNUSED(nameIndex)
1160 Q_UNUSED(baseReg)
1161 reject(thing: u"StoreProperty"_s);
1162}
1163
1164QString QQmlJSCodeGenerator::setLookupPreparation(
1165 const QQmlJSRegisterContent &content, const QString &arg, int lookup)
1166{
1167 if (m_typeResolver->registerContains(reg: content, type: content.storedType()))
1168 return QString();
1169
1170 if (m_typeResolver->registerIsStoredIn(reg: content, type: m_typeResolver->varType())) {
1171 return u"const QMetaType argType = aotContext->lookupResultMetaType("_s
1172 + QString::number(lookup) + u");\n"_s
1173 + u"if (argType.isValid())\n "_s + arg + u".convert(argType)";
1174 }
1175 // TODO: We could make sure they're compatible, for example QObject pointers.
1176 return QString();
1177}
1178
1179
1180void QQmlJSCodeGenerator::generate_SetLookup(int index, int baseReg)
1181{
1182 INJECT_TRACE_INFO(generate_SetLookup);
1183
1184 const QString indexString = QString::number(index);
1185 const QQmlJSScope::ConstPtr valueType = m_state.accumulatorIn().storedType();
1186 const QQmlJSRegisterContent callBase = m_typeResolver->original(type: registerType(index: baseReg));
1187 const QQmlJSRegisterContent specific = m_state.readAccumulator();
1188 Q_ASSERT(specific.isConversion());
1189 const QQmlJSScope::ConstPtr conversionResultScope = specific.conversionResultScope();
1190
1191 if (specific.storedType().isNull()) {
1192 reject(thing: u"SetLookup. Could not find property "
1193 + m_jsUnitGenerator->lookupName(index)
1194 + u" on type "
1195 + callBase.storedType()->internalName());
1196 return;
1197 }
1198
1199 // Choose a container that can hold both, the "in" accumulator and what we actually want.
1200 // If the types are all the same because we can all store them as verbatim C++ types,
1201 // the container will also be that type.
1202 const QQmlJSScope::ConstPtr container = m_typeResolver->merge(a: specific.storedType(), b: valueType);
1203 const QQmlJSRegisterContent property = specific.storedIn(newStoredType: container);
1204
1205 const QString object = registerVariable(index: baseReg);
1206 m_body += u"{\n"_s;
1207 QString variableIn;
1208 QString variableInType;
1209 QString preparation;
1210 QString argType;
1211 if (!m_typeResolver->registerContains(
1212 reg: m_state.accumulatorIn(), type: m_typeResolver->containedType(container: property))) {
1213 m_body += u"auto converted = "_s
1214 + conversion(from: m_state.accumulatorIn(), to: property, variable: consumedAccumulatorVariableIn())
1215 + u";\n"_s;
1216 variableIn = contentPointer(content: property, var: u"converted"_s);
1217 variableInType = contentType(content: property, var: u"converted"_s);
1218 preparation = setLookupPreparation(content: property, arg: u"converted"_s, lookup: index);
1219 if (preparation.isEmpty())
1220 argType = contentType(content: property, var: u"converted"_s);
1221 else
1222 argType = u"argType"_s;
1223 } else {
1224 variableIn = contentPointer(content: property, var: m_state.accumulatorVariableIn);
1225 variableInType = contentType(content: property, var: m_state.accumulatorVariableIn);
1226 argType = variableInType;
1227 }
1228
1229 switch (conversionResultScope->accessSemantics()) {
1230 case QQmlJSScope::AccessSemantics::Reference: {
1231 const QString basePointer = resolveQObjectPointer(
1232 required: conversionResultScope, actual: registerType(index: baseReg), variable: object,
1233 errorMessage: u"TypeError: Value is %1 and could not be converted to an object"_s);
1234
1235 const QString lookup = u"aotContext->setObjectLookup("_s + indexString
1236 + u", "_s + basePointer + u", "_s + variableIn + u')';
1237 const QString initialization = u"aotContext->initSetObjectLookup("_s
1238 + indexString + u", "_s + basePointer + u", "_s + argType + u')';
1239 generateLookup(lookup, initialization, resultPreparation: preparation);
1240 break;
1241 }
1242 case QQmlJSScope::AccessSemantics::Sequence: {
1243 const QString propertyName = m_jsUnitGenerator->lookupName(index);
1244 if (propertyName != u"length"_s) {
1245 reject(thing: u"setting non-length property on a sequence type"_s);
1246 break;
1247 }
1248
1249 if (!conversionResultScope->isListProperty()) {
1250 reject(thing: u"resizing sequence types (because of missing write-back)"_s);
1251 break;
1252 }
1253
1254 // We can resize without write back on a list property because it's actually a reference.
1255 m_body += u"const int begin = "_s + object + u".count(&" + object + u");\n"_s;
1256 m_body += u"const int end = "_s
1257 + (variableIn.startsWith(c: u'&') ? variableIn.mid(position: 1) : (u'*' + variableIn))
1258 + u";\n"_s;
1259 m_body += u"for (int i = begin; i < end; ++i)\n"_s;
1260 m_body += u" "_s + object + u".append(&"_s + object + u", nullptr);\n"_s;
1261 m_body += u"for (int i = begin; i > end; --i)\n"_s;
1262 m_body += u" "_s + object + u".removeLast(&"_s + object + u')'
1263 + u";\n"_s;
1264 break;
1265 }
1266 case QQmlJSScope::AccessSemantics::Value: {
1267 const QString baseContentPointer = resolveValueTypeContentPointer(
1268 required: conversionResultScope, actual: registerType(index: baseReg), variable: object,
1269 errorMessage: u"TypeError: Value is %1 and could not be converted to an object"_s);
1270
1271 const QString lookup = u"aotContext->setValueLookup("_s + indexString
1272 + u", "_s + baseContentPointer
1273 + u", "_s + variableIn + u')';
1274 const QString initialization = u"aotContext->initSetValueLookup("_s
1275 + indexString + u", "_s + metaObject(objectType: conversionResultScope)
1276 + u", "_s + argType + u')';
1277
1278 generateLookup(lookup, initialization, resultPreparation: preparation);
1279 reject(thing: u"SetLookup on value types (because of missing write-back)"_s);
1280 break;
1281 }
1282 case QQmlJSScope::AccessSemantics::None:
1283 Q_UNREACHABLE();
1284 break;
1285 }
1286
1287 m_body += u"}\n"_s;
1288}
1289
1290void QQmlJSCodeGenerator::generate_LoadSuperProperty(int property)
1291{
1292 Q_UNUSED(property)
1293 BYTECODE_UNIMPLEMENTED();
1294}
1295
1296void QQmlJSCodeGenerator::generate_StoreSuperProperty(int property)
1297{
1298 Q_UNUSED(property)
1299 BYTECODE_UNIMPLEMENTED();
1300}
1301
1302void QQmlJSCodeGenerator::generate_Yield()
1303{
1304 BYTECODE_UNIMPLEMENTED();
1305}
1306
1307void QQmlJSCodeGenerator::generate_YieldStar()
1308{
1309 BYTECODE_UNIMPLEMENTED();
1310}
1311
1312void QQmlJSCodeGenerator::generate_Resume(int)
1313{
1314 BYTECODE_UNIMPLEMENTED();
1315}
1316
1317QString QQmlJSCodeGenerator::argumentsList(int argc, int argv, QString *outVar)
1318{
1319 QString types;
1320 QString args;
1321
1322 if (m_state.changedRegisterIndex() == InvalidRegister ||
1323 m_typeResolver->registerContains(
1324 reg: m_state.accumulatorOut(), type: m_typeResolver->voidType())) {
1325 types = u"QMetaType()"_s;
1326 args = u"nullptr"_s;
1327 } else {
1328 *outVar = u"callResult"_s;
1329 const QQmlJSScope::ConstPtr outType = m_state.accumulatorOut().storedType();
1330 m_body += outType->augmentedInternalName() + u' ' + *outVar;
1331 if (!m_typeResolver->registerContains(reg: m_state.accumulatorOut(), type: outType)) {
1332 if (m_typeResolver->equals(a: outType, b: m_typeResolver->varType())
1333 || m_typeResolver->equals(a: outType, b: m_typeResolver->jsPrimitiveType())) {
1334 m_body += u'('
1335 + metaType(type: m_typeResolver->containedType(container: m_state.accumulatorOut()))
1336 + u')';
1337 }
1338 }
1339 m_body += u";\n";
1340
1341 args = contentPointer(content: m_state.accumulatorOut(), var: *outVar);
1342 types = contentType(content: m_state.accumulatorOut(), var: *outVar);
1343 }
1344
1345 for (int i = 0; i < argc; ++i) {
1346 const QQmlJSRegisterContent content = registerType(index: argv + i);
1347 const QString var = registerVariable(index: argv + i);
1348 args += u", "_s + contentPointer(content, var);
1349 types += u", "_s + contentType(content, var);
1350 }
1351
1352 return u"void *args[] = { "_s + args + u" };\n"_s
1353 + u"const QMetaType types[] = { "_s + types + u" };\n"_s;
1354}
1355
1356void QQmlJSCodeGenerator::generateMoveOutVar(const QString &outVar)
1357{
1358 if (m_state.accumulatorVariableOut.isEmpty() || outVar.isEmpty())
1359 return;
1360
1361 m_body += m_state.accumulatorVariableOut + u" = "_s;
1362 m_body += u"std::move(" + outVar + u");\n";
1363}
1364
1365void QQmlJSCodeGenerator::generate_CallValue(int name, int argc, int argv)
1366{
1367 Q_UNUSED(name)
1368 Q_UNUSED(argc)
1369 Q_UNUSED(argv)
1370 BYTECODE_UNIMPLEMENTED();
1371}
1372
1373void QQmlJSCodeGenerator::generate_CallWithReceiver(int name, int thisObject, int argc, int argv)
1374{
1375 Q_UNUSED(name)
1376 Q_UNUSED(thisObject)
1377 Q_UNUSED(argc)
1378 Q_UNUSED(argv)
1379 BYTECODE_UNIMPLEMENTED();
1380}
1381
1382void QQmlJSCodeGenerator::generate_CallProperty(int nameIndex, int baseReg, int argc, int argv)
1383{
1384 Q_UNUSED(nameIndex);
1385 Q_UNUSED(baseReg);
1386 Q_UNUSED(argc);
1387 Q_UNUSED(argv);
1388 reject(thing: u"CallProperty"_s);
1389}
1390
1391bool QQmlJSCodeGenerator::inlineStringMethod(const QString &name, int base, int argc, int argv)
1392{
1393 if (name != u"arg"_s || argc != 1)
1394 return false;
1395
1396 const auto arg = [&](const QQmlJSScope::ConstPtr &type) {
1397 return convertStored(from: registerType(index: argv).storedType(), to: type, variable: consumedRegisterVariable(index: argv));
1398 };
1399
1400 const auto ret = [&](const QString &arg) {
1401 const QString expression = convertStored(
1402 from: registerType(index: base).storedType(), to: m_typeResolver->stringType(),
1403 variable: consumedRegisterVariable(index: base)) + u".arg("_s + arg + u')';
1404 return conversion(
1405 from: m_typeResolver->stringType(), to: m_state.accumulatorOut(), variable: expression);
1406 };
1407
1408 const QQmlJSRegisterContent input = m_state.readRegister(registerIndex: argv);
1409 m_body += m_state.accumulatorVariableOut + u" = "_s;
1410
1411 if (m_typeResolver->isNumeric(type: input))
1412 m_body += ret(arg(m_typeResolver->containedType(container: input)));
1413 else if (m_typeResolver->registerContains(reg: input, type: m_typeResolver->boolType()))
1414 m_body += ret(arg(m_typeResolver->boolType()));
1415 else
1416 m_body += ret(arg(m_typeResolver->stringType()));
1417 m_body += u";\n"_s;
1418 return true;
1419}
1420
1421bool QQmlJSCodeGenerator::inlineTranslateMethod(const QString &name, int argc, int argv)
1422{
1423 addInclude(include: u"qcoreapplication.h"_s);
1424
1425 const auto arg = [&](int i, const QQmlJSScope::ConstPtr &type) {
1426 Q_ASSERT(i < argc);
1427 return convertStored(from: registerType(index: argv + i).storedType(), to: type,
1428 variable: consumedRegisterVariable(index: argv + i));
1429 };
1430
1431 const auto stringArg = [&](int i) {
1432 return i < argc
1433 ? (arg(i, m_typeResolver->stringType()) + u".toUtf8().constData()"_s)
1434 : u"\"\""_s;
1435 };
1436
1437 const auto intArg = [&](int i) {
1438 return i < argc ? arg(i, m_typeResolver->int32Type()) : u"-1"_s;
1439 };
1440
1441 const auto stringRet = [&](const QString &expression) {
1442 return conversion(
1443 from: m_typeResolver->stringType(), to: m_state.accumulatorOut(), variable: expression);
1444 };
1445
1446 const auto capture = [&]() {
1447 m_body += u"aotContext->captureTranslation();\n"_s;
1448 };
1449
1450 if (name == u"QT_TRID_NOOP"_s || name == u"QT_TR_NOOP"_s) {
1451 Q_ASSERT(argc > 0);
1452 m_body += m_state.accumulatorVariableOut + u" = "_s
1453 + stringRet(arg(0, m_typeResolver->stringType())) + u";\n"_s;
1454 return true;
1455 }
1456
1457 if (name == u"QT_TRANSLATE_NOOP"_s) {
1458 Q_ASSERT(argc > 1);
1459 m_body += m_state.accumulatorVariableOut + u" = "_s
1460 + stringRet(arg(1, m_typeResolver->stringType())) + u";\n"_s;
1461 return true;
1462 }
1463
1464 if (name == u"qsTrId"_s) {
1465 capture();
1466 // We inline qtTrId() here because in the !QT_CONFIG(translation) case it's unavailable.
1467 // QCoreApplication::translate() is always available in some primitive form.
1468 // Also, this saves a function call.
1469 m_body += m_state.accumulatorVariableOut + u" = "_s
1470 + stringRet(u"QCoreApplication::translate(nullptr, "_s + stringArg(0) +
1471 u", nullptr, "_s + intArg(1) + u")"_s) + u";\n"_s;
1472 return true;
1473 }
1474
1475 if (name == u"qsTr"_s) {
1476 capture();
1477 m_body += m_state.accumulatorVariableOut + u" = "_s
1478 + stringRet(u"QCoreApplication::translate("_s
1479 + u"aotContext->translationContext().toUtf8().constData(), "_s
1480 + stringArg(0) + u", "_s + stringArg(1) + u", "_s
1481 + intArg(2) + u")"_s) + u";\n"_s;
1482 return true;
1483 }
1484
1485 if (name == u"qsTranslate"_s) {
1486 capture();
1487 m_body += m_state.accumulatorVariableOut + u" = "_s
1488 + stringRet(u"QCoreApplication::translate("_s
1489 + stringArg(0) + u", "_s + stringArg(1) + u", "_s
1490 + stringArg(2) + u", "_s + intArg(3) + u")"_s) + u";\n"_s;
1491 return true;
1492 }
1493
1494 return false;
1495}
1496
1497static QString maxExpression(int argc)
1498{
1499 Q_ASSERT_X(argc >= 2, Q_FUNC_INFO, "max() expects at least two arguments.");
1500
1501 QString expression =
1502 u"[&]() { \nauto tmpMax = (qIsNull(arg2) && qIsNull(arg1) && std::copysign(1.0, arg2) == 1) ? arg2 : ((arg2 > arg1 || std::isnan(arg2)) ? arg2 : arg1);\n"_s;
1503 for (int i = 2; i < argc; i++) {
1504 expression +=
1505 "\ttmpMax = (qIsNull(%1) && qIsNull(tmpMax) && std::copysign(1.0, %1) == 1) ? arg2 : ((%1 > tmpMax || std::isnan(%1)) ? %1 : tmpMax);\n"_L1
1506 .arg(args: "arg"_L1 + QString::number(i + 1));
1507 }
1508 expression += "return tmpMax;\n}()"_L1;
1509
1510 return expression;
1511}
1512
1513static QString minExpression(int argc)
1514{
1515 Q_ASSERT_X(argc >= 2, Q_FUNC_INFO, "min() expects at least two arguments.");
1516
1517 QString expression =
1518 u"[&]() { \nauto tmpMin = (qIsNull(arg2) && qIsNull(arg1) && std::copysign(1.0, arg2) == -1) ? arg2 : ((arg2 < arg1 || std::isnan(arg2)) ? arg2 : arg1);\n"_s;
1519 for (int i = 2; i < argc; i++) {
1520 expression +=
1521 "tmpMin = (qIsNull(%1) && qIsNull(tmpMin) && std::copysign(1.0, %1) == -1) ? arg2 : ((%1 < tmpMin || std::isnan(%1)) ? %1 : tmpMin);\n"_L1
1522 .arg(args: "arg"_L1 + QString::number(i + 1));
1523 }
1524 expression += "return tmpMin;\n}()"_L1;
1525
1526 return expression;
1527}
1528
1529bool QQmlJSCodeGenerator::inlineMathMethod(const QString &name, int argc, int argv)
1530{
1531 addInclude(include: u"cmath"_s);
1532 addInclude(include: u"limits"_s);
1533 addInclude(include: u"qalgorithms.h"_s);
1534 addInclude(include: u"qrandom.h"_s);
1535 addInclude(include: u"qjsprimitivevalue.h"_s);
1536
1537 // If the result is not stored, we don't need to generate any code. All the math methods are
1538 // conceptually pure functions.
1539 if (m_state.changedRegisterIndex() != Accumulator)
1540 return true;
1541
1542 m_body += u"{\n"_s;
1543 for (int i = 0; i < argc; ++i) {
1544 m_body += u"const double arg%1 = "_s.arg(a: i + 1) + convertStored(
1545 from: registerType(index: argv + i).storedType(),
1546 to: m_typeResolver->realType(), variable: consumedRegisterVariable(index: argv + i))
1547 + u";\n"_s;
1548 }
1549
1550 const QString qNaN = u"std::numeric_limits<double>::quiet_NaN()"_s;
1551 const QString inf = u"std::numeric_limits<double>::infinity()"_s;
1552 m_body += m_state.accumulatorVariableOut + u" = "_s;
1553
1554 QString expression;
1555
1556 if (name == u"abs" && argc == 1) {
1557 expression = u"(qIsNull(arg1) ? 0 : (arg1 < 0.0 ? -arg1 : arg1))"_s;
1558 } else if (name == u"acos"_s && argc == 1) {
1559 expression = u"arg1 > 1.0 ? %1 : std::acos(arg1)"_s.arg(a: qNaN);
1560 } else if (name == u"acosh"_s && argc == 1) {
1561 expression = u"arg1 < 1.0 ? %1 : std::acosh(arg1)"_s.arg(a: qNaN);
1562 } else if (name == u"asin"_s && argc == 1) {
1563 expression = u"arg1 > 1.0 ? %1 : std::asin(arg1)"_s.arg(a: qNaN);
1564 } else if (name == u"asinh"_s && argc == 1) {
1565 expression = u"qIsNull(arg1) ? arg1 : std::asinh(arg1)"_s;
1566 } else if (name == u"atan"_s && argc == 1) {
1567 expression = u"qIsNull(arg1) ? arg1 : std::atan(arg1)"_s;
1568 } else if (name == u"atanh"_s && argc == 1) {
1569 expression = u"qIsNull(arg1) ? arg1 : std::atanh(arg1)"_s;
1570 } else if (name == u"atan2"_s) {
1571 // TODO: complicated
1572 return false;
1573 } else if (name == u"cbrt"_s && argc == 1) {
1574 expression = u"std::cbrt(arg1)"_s;
1575 } else if (name == u"ceil"_s && argc == 1) {
1576 expression = u"(arg1 < 0.0 && arg1 > -1.0) ? std::copysign(0.0, -1.0) : std::ceil(arg1)"_s;
1577 } else if (name == u"clz32"_s && argc == 1) {
1578 expression = u"qint32(qCountLeadingZeroBits(quint32(QJSNumberCoercion::toInteger(arg1))))"_s;
1579 } else if (name == u"cos"_s && argc == 1) {
1580 expression = u"std::cos(arg1)"_s;
1581 } else if (name == u"cosh"_s && argc == 1) {
1582 expression = u"std::cosh(arg1)"_s;
1583 } else if (name == u"exp"_s && argc == 1) {
1584 expression = u"std::isinf(arg1) "
1585 "? (std::copysign(1.0, arg1) == -1 ? 0.0 : %1) "
1586 ": std::exp(arg1)"_s.arg(a: inf);
1587 } else if (name == u"expm1"_s) {
1588 // TODO: complicated
1589 return false;
1590 } else if (name == u"floor"_s && argc == 1) {
1591 expression = u"std::floor(arg1)"_s;
1592 } else if (name == u"fround"_s && argc == 1) {
1593 expression = u"(std::isnan(arg1) || std::isinf(arg1) || qIsNull(arg1)) "
1594 "? arg1 "
1595 ": double(float(arg1))"_s;
1596 } else if (name == u"hypot"_s) {
1597 // TODO: complicated
1598 return false;
1599 } else if (name == u"imul"_s && argc == 2) {
1600 expression = u"qint32(quint32(QJSNumberCoercion::toInteger(arg1)) "
1601 "* quint32(QJSNumberCoercion::toInteger(arg2)))"_s;
1602 } else if (name == u"log"_s && argc == 1) {
1603 expression = u"arg1 < 0.0 ? %1 : std::log(arg1)"_s.arg(a: qNaN);
1604 } else if (name == u"log10"_s && argc == 1) {
1605 expression = u"arg1 < 0.0 ? %1 : std::log10(arg1)"_s.arg(a: qNaN);
1606 } else if (name == u"log1p"_s && argc == 1) {
1607 expression = u"arg1 < -1.0 ? %1 : std::log1p(arg1)"_s.arg(a: qNaN);
1608 } else if (name == u"log2"_s && argc == 1) {
1609 expression = u"arg1 < -0.0 ? %1 : std::log2(arg1)"_s.arg(a: qNaN);
1610 } else if (name == u"max"_s && argc >= 2) {
1611 expression = maxExpression(argc);
1612 } else if (name == u"min"_s && argc >= 2) {
1613 expression = minExpression(argc);
1614 } else if (name == u"pow"_s) {
1615 expression = u"QQmlPrivate::jsExponentiate(arg1, arg2)"_s;
1616 } else if (name == u"random"_s && argc == 0) {
1617 expression = u"QRandomGenerator::global()->generateDouble()"_s;
1618 } else if (name == u"round"_s && argc == 1) {
1619 expression = u"std::isfinite(arg1) "
1620 "? ((arg1 < 0.5 && arg1 >= -0.5) "
1621 "? std::copysign(0.0, arg1) "
1622 ": std::floor(arg1 + 0.5)) "
1623 ": arg1"_s;
1624 } else if (name == u"sign"_s && argc == 1) {
1625 expression = u"std::isnan(arg1) "
1626 "? %1 "
1627 ": (qIsNull(arg1) "
1628 "? arg1 "
1629 ": (std::signbit(arg1) ? -1.0 : 1.0))"_s.arg(a: qNaN);
1630 } else if (name == u"sin"_s && argc == 1) {
1631 expression = u"qIsNull(arg1) ? arg1 : std::sin(arg1)"_s;
1632 } else if (name == u"sinh"_s && argc == 1) {
1633 expression = u"qIsNull(arg1) ? arg1 : std::sinh(arg1)"_s;
1634 } else if (name == u"sqrt"_s && argc == 1) {
1635 expression = u"std::sqrt(arg1)"_s;
1636 } else if (name == u"tan"_s && argc == 1) {
1637 expression = u"qIsNull(arg1) ? arg1 : std::tan(arg1)"_s;
1638 } else if (name == u"tanh"_s && argc == 1) {
1639 expression = u"qIsNull(arg1) ? arg1 : std::tanh(arg1)"_s;
1640 } else if (name == u"trunc"_s && argc == 1) {
1641 expression = u"std::trunc(arg1)"_s;
1642 } else {
1643 return false;
1644 }
1645
1646 m_body += conversion(from: m_typeResolver->realType(), to: m_state.accumulatorOut(), variable: expression);
1647
1648 m_body += u";\n"_s;
1649 m_body += u"}\n"_s;
1650 return true;
1651}
1652
1653static QString messageTypeForMethod(const QString &method)
1654{
1655 if (method == u"log" || method == u"debug")
1656 return u"QtDebugMsg"_s;
1657 if (method == u"info")
1658 return u"QtInfoMsg"_s;
1659 if (method == u"warn")
1660 return u"QtWarningMsg"_s;
1661 if (method == u"error")
1662 return u"QtCriticalMsg"_s;
1663 return QString();
1664}
1665
1666bool QQmlJSCodeGenerator::inlineConsoleMethod(const QString &name, int argc, int argv)
1667{
1668 const QString type = messageTypeForMethod(method: name);
1669 if (type.isEmpty())
1670 return false;
1671
1672 addInclude(include: u"qloggingcategory.h"_s);
1673
1674 m_body += u"{\n";
1675 m_body += u" bool firstArgIsCategory = false;\n";
1676 const QQmlJSRegisterContent firstArg = argc > 0 ? registerType(index: argv) : QQmlJSRegisterContent();
1677
1678 // We could check for internalName == "QQmlLoggingCategory" here, but we don't want to
1679 // because QQmlLoggingCategory is not a builtin. Tying the specific internal name and
1680 // intheritance hierarchy in here would be fragile.
1681 // TODO: We could drop the check for firstArg in some cases if we made some base class
1682 // of QQmlLoggingCategory a builtin.
1683 const bool firstArgIsReference = argc > 0
1684 && m_typeResolver->containedType(container: firstArg)->isReferenceType();
1685
1686 if (firstArgIsReference) {
1687 m_body += u" QObject *firstArg = ";
1688 m_body += convertStored(
1689 from: firstArg.storedType(),
1690 to: m_typeResolver->genericType(type: firstArg.storedType()),
1691 variable: registerVariable(index: argv));
1692 m_body += u";\n";
1693 }
1694
1695 m_body += u" const QLoggingCategory *category = aotContext->resolveLoggingCategory(";
1696 m_body += firstArgIsReference ? u"firstArg" : u"nullptr";
1697 m_body += u", &firstArgIsCategory);\n";
1698 m_body += u" if (category && category->isEnabled(" + type + u")) {\n";
1699
1700 m_body += u" const QString message = ";
1701 if (argc > 0) {
1702 const QString firstArgStringConversion = convertStored(
1703 from: registerType(index: argv).storedType(),
1704 to: m_typeResolver->stringType(), variable: registerVariable(index: argv));
1705 if (firstArgIsReference) {
1706 m_body += u"(firstArgIsCategory ? QString() : (" + firstArgStringConversion;
1707 if (argc > 1)
1708 m_body += u".append(QLatin1Char(' ')))).append(";
1709 else
1710 m_body += u"))";
1711 } else {
1712 m_body += firstArgStringConversion;
1713 if (argc > 1)
1714 m_body += u".append(QLatin1Char(' ')).append(";
1715 }
1716
1717 for (int i = 1; i < argc; ++i) {
1718 if (i > 1)
1719 m_body += u".append(QLatin1Char(' ')).append("_s;
1720 m_body += convertStored(
1721 from: registerType(index: argv + i).storedType(),
1722 to: m_typeResolver->stringType(), variable: consumedRegisterVariable(index: argv + i)) + u')';
1723 }
1724 } else {
1725 m_body += u"QString()";
1726 }
1727 m_body += u";\n ";
1728 generateSetInstructionPointer();
1729 m_body += u" aotContext->writeToConsole(" + type + u", message, category);\n";
1730 m_body += u" }\n";
1731 m_body += u"}\n";
1732 return true;
1733}
1734
1735bool QQmlJSCodeGenerator::inlineArrayMethod(const QString &name, int base, int argc, int argv)
1736{
1737 const auto intType = m_typeResolver->int32Type();
1738 const auto valueType = registerType(index: base).storedType()->valueType();
1739 const auto boolType = m_typeResolver->boolType();
1740 const auto stringType = m_typeResolver->stringType();
1741 const auto baseType = registerType(index: base);
1742
1743 const QString baseVar = registerVariable(index: base);
1744 const QString qjsListMethod = u"QJSList(&"_s + baseVar + u", aotContext->engine)."
1745 + name + u"(";
1746
1747 addInclude(include: u"qjslist.h"_s);
1748
1749 if (name == u"includes" && argc > 0 && argc < 3) {
1750 QString call = qjsListMethod
1751 + convertStored(from: registerType(index: argv).storedType(), to: valueType,
1752 variable: consumedRegisterVariable(index: argv));
1753 if (argc == 2) {
1754 call += u", " + convertStored(from: registerType(index: argv + 1).storedType(), to: intType,
1755 variable: consumedRegisterVariable(index: argv + 1));
1756 }
1757 call += u")";
1758
1759 m_body += m_state.accumulatorVariableOut + u" = "_s
1760 + conversion(from: boolType, to: m_state.accumulatorOut(), variable: call) + u";\n"_s;
1761 return true;
1762 }
1763
1764 if (name == u"toString" || (name == u"join" && argc < 2)) {
1765 QString call = qjsListMethod;
1766 if (argc == 1) {
1767 call += convertStored(from: registerType(index: argv).storedType(), to: stringType,
1768 variable: consumedRegisterVariable(index: argv));
1769 }
1770 call += u")";
1771
1772 m_body += m_state.accumulatorVariableOut + u" = "_s
1773 + conversion(from: stringType, to: m_state.accumulatorOut(), variable: call) + u";\n"_s;
1774 return true;
1775 }
1776
1777 if (name == u"slice" && argc < 3) {
1778 QString call = qjsListMethod;
1779 for (int i = 0; i < argc; ++i) {
1780 if (i > 0)
1781 call += u", ";
1782 call += convertStored(from: registerType(index: argv + i).storedType(), to: intType,
1783 variable: consumedRegisterVariable(index: argv + i));
1784 }
1785 call += u")";
1786
1787 const auto outType = baseType.storedType()->isListProperty()
1788 ? m_typeResolver->globalType(type: m_typeResolver->qObjectListType())
1789 : baseType;
1790
1791 m_body += m_state.accumulatorVariableOut + u" = "_s
1792 + conversion(from: outType, to: m_state.accumulatorOut(), variable: call) + u";\n"_s;
1793 return true;
1794 }
1795
1796 if ((name == u"indexOf" || name == u"lastIndexOf") && argc > 0 && argc < 3) {
1797 QString call = qjsListMethod
1798 + convertStored(from: registerType(index: argv).storedType(), to: valueType,
1799 variable: consumedRegisterVariable(index: argv));
1800 if (argc == 2) {
1801 call += u", " + convertStored(from: registerType(index: argv + 1).storedType(), to: intType,
1802 variable: consumedRegisterVariable(index: argv + 1));
1803 }
1804 call += u")";
1805
1806 m_body += m_state.accumulatorVariableOut + u" = "_s
1807 + conversion(from: intType, to: m_state.accumulatorOut(), variable: call) + u";\n"_s;
1808 return true;
1809 }
1810
1811 return false;
1812}
1813
1814void QQmlJSCodeGenerator::generate_CallPropertyLookup(int index, int base, int argc, int argv)
1815{
1816 INJECT_TRACE_INFO(generate_CallPropertyLookup);
1817
1818 if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::JavaScriptReturnValue)
1819 reject(thing: u"call to untyped JavaScript function"_s);
1820
1821 AccumulatorConverter registers(this);
1822
1823 const QQmlJSRegisterContent baseType = registerType(index: base);
1824 if (baseType.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) {
1825 const QString name = m_jsUnitGenerator->stringForIndex(
1826 index: m_jsUnitGenerator->lookupNameIndex(index));
1827 if (m_typeResolver->equals(a: m_typeResolver->originalContainedType(container: baseType), b: mathObject())) {
1828 if (inlineMathMethod(name, argc, argv))
1829 return;
1830 }
1831
1832 if (m_typeResolver->equals(a: m_typeResolver->originalContainedType(container: baseType), b: consoleObject())) {
1833 if (inlineConsoleMethod(name, argc, argv))
1834 return;
1835 }
1836
1837 if (m_typeResolver->equals(a: m_typeResolver->originalContainedType(container: baseType),
1838 b: m_typeResolver->stringType())) {
1839 if (inlineStringMethod(name, base, argc, argv))
1840 return;
1841 }
1842
1843 if (baseType.storedType()->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence) {
1844 if (inlineArrayMethod(name, base, argc, argv))
1845 return;
1846 }
1847
1848 // This is possible, once we establish the right kind of lookup for it
1849 reject(thing: u"call to property '%1' of %2"_s.arg(args: name, args: baseType.descriptiveName()));
1850 }
1851
1852 const QString indexString = QString::number(index);
1853
1854 m_body += u"{\n"_s;
1855
1856 QString outVar;
1857 m_body += argumentsList(argc, argv, outVar: &outVar);
1858 const QString lookup = u"aotContext->callObjectPropertyLookup("_s + indexString
1859 + u", "_s + registerVariable(index: base)
1860 + u", args, types, "_s + QString::number(argc) + u')';
1861 const QString initialization = u"aotContext->initCallObjectPropertyLookup("_s
1862 + indexString + u')';
1863 generateLookup(lookup, initialization);
1864 generateMoveOutVar(outVar);
1865
1866 m_body += u"}\n"_s;
1867}
1868
1869void QQmlJSCodeGenerator::generate_CallName(int name, int argc, int argv)
1870{
1871 Q_UNUSED(name);
1872 Q_UNUSED(argc);
1873 Q_UNUSED(argv);
1874 reject(thing: u"CallName"_s);
1875}
1876
1877void QQmlJSCodeGenerator::generate_CallPossiblyDirectEval(int argc, int argv)
1878{
1879 Q_UNUSED(argc)
1880 Q_UNUSED(argv)
1881 BYTECODE_UNIMPLEMENTED();
1882}
1883
1884void QQmlJSCodeGenerator::generate_CallGlobalLookup(int index, int argc, int argv)
1885{
1886 Q_UNUSED(index);
1887 Q_UNUSED(argc);
1888 Q_UNUSED(argv);
1889 reject(thing: u"CallGlobalLookup"_s);
1890}
1891
1892void QQmlJSCodeGenerator::generate_CallQmlContextPropertyLookup(int index, int argc, int argv)
1893{
1894 INJECT_TRACE_INFO(generate_CallQmlContextPropertyLookup);
1895
1896 if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::JavaScriptReturnValue)
1897 reject(thing: u"call to untyped JavaScript function"_s);
1898
1899 if (m_typeResolver->equals(a: m_state.accumulatorOut().scopeType(),
1900 b: m_typeResolver->jsGlobalObject())) {
1901 const QString name = m_jsUnitGenerator->stringForIndex(
1902 index: m_jsUnitGenerator->lookupNameIndex(index));
1903 if (inlineTranslateMethod(name, argc, argv))
1904 return;
1905 }
1906
1907 AccumulatorConverter registers(this);
1908
1909 const QString indexString = QString::number(index);
1910
1911 m_body += u"{\n"_s;
1912 QString outVar;
1913 m_body += argumentsList(argc, argv, outVar: &outVar);
1914 const QString lookup = u"aotContext->callQmlContextPropertyLookup("_s + indexString
1915 + u", args, types, "_s + QString::number(argc) + u')';
1916 const QString initialization = u"aotContext->initCallQmlContextPropertyLookup("_s
1917 + indexString + u')';
1918 generateLookup(lookup, initialization);
1919 generateMoveOutVar(outVar);
1920
1921 m_body += u"}\n"_s;
1922}
1923
1924void QQmlJSCodeGenerator::generate_CallWithSpread(int func, int thisObject, int argc, int argv)
1925{
1926 Q_UNUSED(func)
1927 Q_UNUSED(thisObject)
1928 Q_UNUSED(argc)
1929 Q_UNUSED(argv)
1930 BYTECODE_UNIMPLEMENTED();
1931}
1932
1933void QQmlJSCodeGenerator::generate_TailCall(int func, int thisObject, int argc, int argv)
1934{
1935 Q_UNUSED(func)
1936 Q_UNUSED(thisObject)
1937 Q_UNUSED(argc)
1938 Q_UNUSED(argv)
1939 BYTECODE_UNIMPLEMENTED();
1940}
1941
1942void QQmlJSCodeGenerator::generate_Construct(int func, int argc, int argv)
1943{
1944 Q_UNUSED(func);
1945 Q_UNUSED(argc);
1946 Q_UNUSED(argv);
1947 reject(thing: u"Construct"_s);
1948}
1949
1950void QQmlJSCodeGenerator::generate_ConstructWithSpread(int func, int argc, int argv)
1951{
1952 Q_UNUSED(func)
1953 Q_UNUSED(argc)
1954 Q_UNUSED(argv)
1955 BYTECODE_UNIMPLEMENTED();
1956}
1957
1958void QQmlJSCodeGenerator::generate_SetUnwindHandler(int offset)
1959{
1960 Q_UNUSED(offset)
1961 reject(thing: u"SetUnwindHandlerh"_s);
1962}
1963
1964void QQmlJSCodeGenerator::generate_UnwindDispatch()
1965{
1966 reject(thing: u"UnwindDispatch"_s);
1967}
1968
1969void QQmlJSCodeGenerator::generate_UnwindToLabel(int level, int offset)
1970{
1971 Q_UNUSED(level)
1972 Q_UNUSED(offset)
1973 BYTECODE_UNIMPLEMENTED();
1974}
1975
1976void QQmlJSCodeGenerator::generate_DeadTemporalZoneCheck(int name)
1977{
1978 Q_UNUSED(name)
1979 // Nothing to do here. If we have statically asserted the dtz check in the type propagator
1980 // the value cannot be empty. Otherwise we can't get here.
1981}
1982
1983void QQmlJSCodeGenerator::generate_ThrowException()
1984{
1985 INJECT_TRACE_INFO(generate_ThrowException);
1986
1987 generateSetInstructionPointer();
1988 m_body += u"aotContext->engine->throwError("_s
1989 + conversion(from: m_state.accumulatorIn(), to: m_typeResolver->globalType(
1990 type: m_typeResolver->jsValueType()),
1991 variable: m_state.accumulatorVariableIn) + u");\n"_s;
1992 m_body += u"return "_s + errorReturnValue() + u";\n"_s;
1993 m_skipUntilNextLabel = true;
1994 resetState();
1995}
1996
1997void QQmlJSCodeGenerator::generate_GetException()
1998{
1999 BYTECODE_UNIMPLEMENTED();
2000}
2001
2002void QQmlJSCodeGenerator::generate_SetException()
2003{
2004 BYTECODE_UNIMPLEMENTED();
2005}
2006
2007void QQmlJSCodeGenerator::generate_CreateCallContext()
2008{
2009 INJECT_TRACE_INFO(generate_CreateCallContext);
2010
2011 m_body += u"{\n"_s;
2012}
2013
2014void QQmlJSCodeGenerator::generate_PushCatchContext(int index, int nameIndex)
2015{
2016 Q_UNUSED(index)
2017 Q_UNUSED(nameIndex)
2018 reject(thing: u"PushCatchContext"_s);
2019}
2020
2021void QQmlJSCodeGenerator::generate_PushWithContext()
2022{
2023 BYTECODE_UNIMPLEMENTED();
2024}
2025
2026void QQmlJSCodeGenerator::generate_PushBlockContext(int index)
2027{
2028 Q_UNUSED(index)
2029 BYTECODE_UNIMPLEMENTED();
2030}
2031
2032void QQmlJSCodeGenerator::generate_CloneBlockContext()
2033{
2034 BYTECODE_UNIMPLEMENTED();
2035}
2036
2037void QQmlJSCodeGenerator::generate_PushScriptContext(int index)
2038{
2039 Q_UNUSED(index)
2040 BYTECODE_UNIMPLEMENTED();
2041}
2042
2043void QQmlJSCodeGenerator::generate_PopScriptContext()
2044{
2045 BYTECODE_UNIMPLEMENTED();
2046}
2047
2048void QQmlJSCodeGenerator::generate_PopContext()
2049{
2050 INJECT_TRACE_INFO(generate_PopContext);
2051
2052 // Add a semicolon before the closing brace, in case there was a bare label before it.
2053 m_body += u";}\n"_s;
2054}
2055
2056void QQmlJSCodeGenerator::generate_GetIterator(int iterator)
2057{
2058 Q_UNUSED(iterator)
2059 BYTECODE_UNIMPLEMENTED();
2060}
2061
2062void QQmlJSCodeGenerator::generate_IteratorNext(int value, int done)
2063{
2064 Q_UNUSED(value)
2065 Q_UNUSED(done)
2066 BYTECODE_UNIMPLEMENTED();
2067}
2068
2069void QQmlJSCodeGenerator::generate_IteratorNextForYieldStar(int iterator, int object)
2070{
2071 Q_UNUSED(iterator)
2072 Q_UNUSED(object)
2073 BYTECODE_UNIMPLEMENTED();
2074}
2075
2076void QQmlJSCodeGenerator::generate_IteratorClose(int done)
2077{
2078 Q_UNUSED(done)
2079 BYTECODE_UNIMPLEMENTED();
2080}
2081
2082void QQmlJSCodeGenerator::generate_DestructureRestElement()
2083{
2084 BYTECODE_UNIMPLEMENTED();
2085}
2086
2087void QQmlJSCodeGenerator::generate_DeleteProperty(int base, int index)
2088{
2089 Q_UNUSED(base)
2090 Q_UNUSED(index)
2091 BYTECODE_UNIMPLEMENTED();
2092}
2093
2094void QQmlJSCodeGenerator::generate_DeleteName(int name)
2095{
2096 Q_UNUSED(name)
2097 BYTECODE_UNIMPLEMENTED();
2098}
2099
2100void QQmlJSCodeGenerator::generate_TypeofName(int name)
2101{
2102 Q_UNUSED(name);
2103 reject(thing: u"TypeofName"_s);
2104}
2105
2106void QQmlJSCodeGenerator::generate_TypeofValue()
2107{
2108 reject(thing: u"TypeofValue"_s);
2109}
2110
2111void QQmlJSCodeGenerator::generate_DeclareVar(int varName, int isDeletable)
2112{
2113 Q_UNUSED(varName)
2114 Q_UNUSED(isDeletable)
2115 BYTECODE_UNIMPLEMENTED();
2116}
2117
2118void QQmlJSCodeGenerator::generate_DefineArray(int argc, int args)
2119{
2120 INJECT_TRACE_INFO(generate_DefineArray);
2121
2122 const QQmlJSScope::ConstPtr stored = m_state.accumulatorOut().storedType();
2123 if (stored->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence) {
2124 // This rejects any attempt to store the list into a QVariant.
2125 // Therefore, we don't have to adjust the contained type below.
2126 reject(thing: u"storing an array in a non-sequence type"_s);
2127 return;
2128 }
2129
2130 const QQmlJSScope::ConstPtr value = stored->valueType();
2131 Q_ASSERT(value);
2132
2133 QStringList initializer;
2134 for (int i = 0; i < argc; ++i) {
2135 initializer += convertStored(
2136 from: registerType(index: args + i).storedType(), to: value,
2137 variable: consumedRegisterVariable(index: args + i));
2138 }
2139
2140 if (stored->isListProperty()) {
2141 reject(thing: u"creating a QQmlListProperty not backed by a property"_s);
2142
2143 // We can, technically, generate code for this. But it's dangerous:
2144 //
2145 // const QString storage = m_state.accumulatorVariableOut + u"_storage"_s;
2146 // m_body += stored->internalName() + u"::ListType " + storage
2147 // + u" = {"_s + initializer.join(u", "_s) + u"};\n"_s;
2148 // m_body += m_state.accumulatorVariableOut
2149 // + u" = " + stored->internalName() + u"(nullptr, &"_s + storage + u");\n"_s;
2150 } else {
2151 m_body += m_state.accumulatorVariableOut + u" = "_s + stored->internalName() + u'{';
2152 m_body += initializer.join(sep: u", "_s);
2153 m_body += u"};\n";
2154 }
2155}
2156
2157void QQmlJSCodeGenerator::generate_DefineObjectLiteral(int internalClassId, int argc, int args)
2158{
2159 Q_UNUSED(internalClassId)
2160 Q_UNUSED(argc)
2161 Q_UNUSED(args)
2162 reject(thing: u"DefineObjectLiteral"_s);
2163}
2164
2165void QQmlJSCodeGenerator::generate_CreateClass(int classIndex, int heritage, int computedNames)
2166{
2167 Q_UNUSED(classIndex)
2168 Q_UNUSED(heritage)
2169 Q_UNUSED(computedNames)
2170 BYTECODE_UNIMPLEMENTED();
2171}
2172
2173void QQmlJSCodeGenerator::generate_CreateMappedArgumentsObject()
2174{
2175 BYTECODE_UNIMPLEMENTED();
2176}
2177
2178void QQmlJSCodeGenerator::generate_CreateUnmappedArgumentsObject()
2179{
2180 BYTECODE_UNIMPLEMENTED();
2181}
2182
2183void QQmlJSCodeGenerator::generate_CreateRestParameter(int argIndex)
2184{
2185 Q_UNUSED(argIndex)
2186 BYTECODE_UNIMPLEMENTED();
2187}
2188
2189void QQmlJSCodeGenerator::generate_ConvertThisToObject()
2190{
2191 INJECT_TRACE_INFO(generate_ConvertThisToObject);
2192
2193 m_body += changedRegisterVariable() + u" = "_s
2194 + conversion(from: m_typeResolver->qObjectType(), to: m_state.changedRegister(),
2195 variable: u"aotContext->thisObject()"_s)
2196 + u";\n"_s;
2197}
2198
2199void QQmlJSCodeGenerator::generate_LoadSuperConstructor()
2200{
2201 BYTECODE_UNIMPLEMENTED();
2202}
2203
2204void QQmlJSCodeGenerator::generate_ToObject()
2205{
2206 BYTECODE_UNIMPLEMENTED();
2207}
2208
2209void QQmlJSCodeGenerator::generate_Jump(int offset)
2210{
2211 INJECT_TRACE_INFO(generate_Jump);
2212
2213 generateJumpCodeWithTypeConversions(relativeOffset: offset);
2214 m_body += u";\n"_s;
2215 m_skipUntilNextLabel = true;
2216 resetState();
2217}
2218
2219void QQmlJSCodeGenerator::generate_JumpTrue(int offset)
2220{
2221 INJECT_TRACE_INFO(generate_JumpTrue);
2222
2223 m_body += u"if ("_s;
2224 m_body += convertStored(from: m_state.accumulatorIn().storedType(), to: m_typeResolver->boolType(),
2225 variable: m_state.accumulatorVariableIn);
2226 m_body += u") "_s;
2227 generateJumpCodeWithTypeConversions(relativeOffset: offset);
2228 m_body += u";\n"_s;
2229}
2230
2231void QQmlJSCodeGenerator::generate_JumpFalse(int offset)
2232{
2233 INJECT_TRACE_INFO(generate_JumpFalse);
2234
2235 m_body += u"if (!"_s;
2236 m_body += convertStored(from: m_state.accumulatorIn().storedType(), to: m_typeResolver->boolType(),
2237 variable: m_state.accumulatorVariableIn);
2238 m_body += u") "_s;
2239 generateJumpCodeWithTypeConversions(relativeOffset: offset);
2240 m_body += u";\n"_s;
2241}
2242
2243void QQmlJSCodeGenerator::generate_JumpNoException(int offset)
2244{
2245 INJECT_TRACE_INFO(generate_JumpNoException);
2246
2247 m_body += u"if (!context->engine->hasException()) "_s;
2248 generateJumpCodeWithTypeConversions(relativeOffset: offset);
2249 m_body += u";\n"_s;
2250}
2251
2252void QQmlJSCodeGenerator::generate_JumpNotUndefined(int offset)
2253{
2254 Q_UNUSED(offset)
2255 BYTECODE_UNIMPLEMENTED();
2256}
2257
2258void QQmlJSCodeGenerator::generate_CheckException()
2259{
2260 INJECT_TRACE_INFO(generate_CheckException);
2261
2262 generateExceptionCheck();
2263}
2264
2265void QQmlJSCodeGenerator::generate_CmpEqNull()
2266{
2267 INJECT_TRACE_INFO(generate_CmlEqNull);
2268
2269 m_body += m_state.accumulatorVariableOut + u" = "_s;
2270 m_body += conversion(
2271 from: m_typeResolver->boolType(), to: m_state.accumulatorOut(),
2272 variable: u"QJSPrimitiveValue(QJSPrimitiveNull()).equals("_s
2273 + convertStored(
2274 from: m_state.accumulatorIn().storedType(), to: m_typeResolver->jsPrimitiveType(),
2275 variable: consumedAccumulatorVariableIn()) + u')');
2276 m_body += u";\n"_s;
2277}
2278
2279void QQmlJSCodeGenerator::generate_CmpNeNull()
2280{
2281 INJECT_TRACE_INFO(generate_CmlNeNull);
2282
2283 m_body += m_state.accumulatorVariableOut + u" = "_s;
2284 m_body += conversion(
2285 from: m_typeResolver->boolType(), to: m_state.accumulatorOut(),
2286 variable: u"!QJSPrimitiveValue(QJSPrimitiveNull()).equals("_s
2287 + convertStored(
2288 from: m_state.accumulatorIn().storedType(), to: m_typeResolver->jsPrimitiveType(),
2289 variable: consumedAccumulatorVariableIn()) + u')');
2290 m_body += u";\n"_s;
2291}
2292
2293QString QQmlJSCodeGenerator::eqIntExpression(int lhsConst)
2294{
2295 if (m_typeResolver->isIntegral(type: m_state.accumulatorIn()))
2296 return QString::number(lhsConst) + u" == "_s + m_state.accumulatorVariableIn;
2297
2298 if (m_typeResolver->registerIsStoredIn(reg: m_state.accumulatorIn(), type: m_typeResolver->boolType())) {
2299 return QString::number(lhsConst) + u" == "_s
2300 + convertStored(from: m_state.accumulatorIn().storedType(), to: m_typeResolver->int32Type(),
2301 variable: consumedAccumulatorVariableIn());
2302 }
2303
2304 if (m_typeResolver->isNumeric(type: m_state.accumulatorIn())) {
2305 return convertStored(from: m_typeResolver->int32Type(), to: m_typeResolver->realType(),
2306 variable: QString::number(lhsConst)) + u" == "_s
2307 + convertStored(from: m_state.accumulatorIn().storedType(), to: m_typeResolver->realType(),
2308 variable: consumedAccumulatorVariableIn());
2309 }
2310
2311 QString result;
2312 result += convertStored(from: m_typeResolver->int32Type(), to: m_typeResolver->jsPrimitiveType(),
2313 variable: QString::number(lhsConst));
2314 result += u".equals("_s;
2315 result += convertStored(from: m_state.accumulatorIn().storedType(), to: m_typeResolver->jsPrimitiveType(),
2316 variable: consumedAccumulatorVariableIn());
2317 result += u')';
2318 return result;
2319}
2320
2321QString QQmlJSCodeGenerator::getLookupPreparation(
2322 const QQmlJSRegisterContent &content, const QString &var, int lookup)
2323{
2324 if (m_typeResolver->registerContains(reg: content, type: content.storedType()))
2325 return QString();
2326
2327 if (m_typeResolver->registerIsStoredIn(reg: content, type: m_typeResolver->varType())) {
2328 return var + u" = QVariant(aotContext->lookupResultMetaType("_s
2329 + QString::number(lookup) + u"))"_s;
2330 }
2331 // TODO: We could make sure they're compatible, for example QObject pointers.
2332 return QString();
2333}
2334
2335QString QQmlJSCodeGenerator::contentPointer(const QQmlJSRegisterContent &content, const QString &var)
2336{
2337 const QQmlJSScope::ConstPtr stored = content.storedType();
2338 if (m_typeResolver->registerContains(reg: content, type: stored))
2339 return u'&' + var;
2340
2341 if (m_typeResolver->registerIsStoredIn(reg: content, type: m_typeResolver->varType())
2342 || m_typeResolver->registerIsStoredIn(reg: content, type: m_typeResolver->jsPrimitiveType())) {
2343 return var + u".data()"_s;
2344 }
2345
2346 if (stored->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
2347 return u'&' + var;
2348
2349 if (m_typeResolver->isNumeric(type: content.storedType())
2350 && m_typeResolver->containedType(container: content)->scopeType() == QQmlSA::ScopeType::EnumScope) {
2351 return u'&' + var;
2352 }
2353
2354 if (stored->isListProperty() && m_typeResolver->containedType(container: content)->isListProperty())
2355 return u'&' + var;
2356
2357 reject(thing: u"content pointer of unsupported wrapper type "_s + content.descriptiveName());
2358 return QString();
2359}
2360
2361QString QQmlJSCodeGenerator::contentType(const QQmlJSRegisterContent &content, const QString &var)
2362{
2363 const QQmlJSScope::ConstPtr stored = content.storedType();
2364 const QQmlJSScope::ConstPtr contained = QQmlJSScope::nonCompositeBaseType(
2365 type: m_typeResolver->containedType(container: content));
2366 if (m_typeResolver->equals(a: contained, b: stored))
2367 return metaTypeFromType(type: stored);
2368
2369 if (m_typeResolver->equals(a: stored, b: m_typeResolver->varType())
2370 || m_typeResolver->registerIsStoredIn(reg: content, type: m_typeResolver->jsPrimitiveType())) {
2371 return var + u".metaType()"_s; // We expect the container to be initialized
2372 }
2373
2374 if (stored->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
2375 return metaTypeFromName(type: contained);
2376
2377 if (m_typeResolver->isNumeric(type: stored) && contained->scopeType() == QQmlSA::ScopeType::EnumScope)
2378 return metaTypeFromType(type: contained->baseType());
2379
2380 if (stored->isListProperty() && m_typeResolver->containedType(container: content)->isListProperty())
2381 return metaTypeFromType(type: m_typeResolver->listPropertyType());
2382
2383 reject(thing: u"content type of unsupported wrapper type "_s + content.descriptiveName());
2384 return QString();
2385}
2386
2387void QQmlJSCodeGenerator::generate_CmpEqInt(int lhsConst)
2388{
2389 INJECT_TRACE_INFO(generate_CmpEqInt);
2390
2391 m_body += m_state.accumulatorVariableOut + u" = "_s;
2392 m_body += conversion(from: m_typeResolver->boolType(), to: m_state.accumulatorOut(),
2393 variable: eqIntExpression(lhsConst)) + u";\n"_s;
2394}
2395
2396void QQmlJSCodeGenerator::generate_CmpNeInt(int lhsConst)
2397{
2398 INJECT_TRACE_INFO(generate_CmpNeInt);
2399
2400 m_body += m_state.accumulatorVariableOut + u" = "_s;
2401 m_body += conversion(from: m_typeResolver->boolType(), to: m_state.accumulatorOut(),
2402 variable: u"!("_s + eqIntExpression(lhsConst) + u')') + u";\n"_s;
2403}
2404
2405void QQmlJSCodeGenerator::generate_CmpEq(int lhs)
2406{
2407 INJECT_TRACE_INFO(generate_CmpEq);
2408 generateEqualityOperation(lhs, function: u"equals"_s, invert: false);
2409}
2410
2411void QQmlJSCodeGenerator::generate_CmpNe(int lhs)
2412{
2413 INJECT_TRACE_INFO(generate_CmpNe);
2414 generateEqualityOperation(lhs, function: u"equals"_s, invert: true);
2415}
2416
2417void QQmlJSCodeGenerator::generate_CmpGt(int lhs)
2418{
2419 INJECT_TRACE_INFO(generate_CmpGt);
2420 generateCompareOperation(lhs, cppOperator: u">"_s);
2421}
2422
2423void QQmlJSCodeGenerator::generate_CmpGe(int lhs)
2424{
2425 INJECT_TRACE_INFO(generate_CmpGe);
2426 generateCompareOperation(lhs, cppOperator: u">="_s);
2427}
2428
2429void QQmlJSCodeGenerator::generate_CmpLt(int lhs)
2430{
2431 INJECT_TRACE_INFO(generate_CmpLt);
2432 generateCompareOperation(lhs, cppOperator: u"<"_s);
2433}
2434
2435void QQmlJSCodeGenerator::generate_CmpLe(int lhs)
2436{
2437 INJECT_TRACE_INFO(generate_CmpLe);
2438 generateCompareOperation(lhs, cppOperator: u"<="_s);
2439}
2440
2441void QQmlJSCodeGenerator::generate_CmpStrictEqual(int lhs)
2442{
2443 INJECT_TRACE_INFO(generate_CmpStrictEqual);
2444 generateEqualityOperation(lhs, function: u"strictlyEquals"_s, invert: false);
2445}
2446
2447void QQmlJSCodeGenerator::generate_CmpStrictNotEqual(int lhs)
2448{
2449 INJECT_TRACE_INFO(generate_CmpStrictNotEqual);
2450 generateEqualityOperation(lhs, function: u"strictlyEquals"_s, invert: true);
2451}
2452
2453void QQmlJSCodeGenerator::generate_CmpIn(int lhs)
2454{
2455 Q_UNUSED(lhs)
2456 reject(thing: u"CmpIn"_s);
2457}
2458
2459void QQmlJSCodeGenerator::generate_CmpInstanceOf(int lhs)
2460{
2461 Q_UNUSED(lhs)
2462 BYTECODE_UNIMPLEMENTED();
2463}
2464
2465void QQmlJSCodeGenerator::generate_As(int lhs)
2466{
2467 INJECT_TRACE_INFO(generate_As);
2468
2469 const QString input = registerVariable(index: lhs);
2470 const QQmlJSRegisterContent inputContent = m_state.readRegister(registerIndex: lhs);
2471 const QQmlJSRegisterContent outputContent = m_state.accumulatorOut();
2472
2473 // If the original output is a conversion, we're supposed to check for the contained
2474 // type and if it doesn't match, set the result to null or undefined.
2475 const QQmlJSRegisterContent originalContent = m_typeResolver->original(type: outputContent);
2476
2477 const QQmlJSScope::ConstPtr contained = m_typeResolver->containedType(container: originalContent);
2478
2479 if (contained->isReferenceType()) {
2480 const QQmlJSScope::ConstPtr genericContained = m_typeResolver->genericType(type: contained);
2481 const QString inputConversion = inputContent.storedType()->isReferenceType()
2482 ? input
2483 : convertStored(from: inputContent.storedType(), to: genericContained, variable: input);
2484
2485 m_body += m_state.accumulatorVariableOut + u" = "_s;
2486 if (m_typeResolver->equals(
2487 a: m_state.accumulatorIn().storedType(), b: m_typeResolver->metaObjectType())
2488 && contained->isComposite()) {
2489 m_body += conversion(
2490 from: genericContained, to: outputContent,
2491 variable: m_state.accumulatorVariableIn + u"->cast("_s + inputConversion + u')');
2492 } else {
2493 m_body += conversion(
2494 from: genericContained, to: outputContent,
2495 variable: u'(' + metaObject(objectType: contained) + u")->cast("_s + inputConversion + u')');
2496 }
2497 m_body += u";\n"_s;
2498 return;
2499 } else if (m_typeResolver->equals(a: inputContent.storedType(), b: m_typeResolver->varType())) {
2500 if (originalContent.isConversion()) {
2501 const auto origins = originalContent.conversionOrigins();
2502 Q_ASSERT(origins.size() == 2);
2503
2504 const auto target = m_typeResolver->equals(a: origins[0], b: m_typeResolver->voidType())
2505 ? origins[1]
2506 : origins[0];
2507
2508 Q_ASSERT(!m_typeResolver->equals(target, m_typeResolver->voidType()));
2509
2510 m_body += m_state.accumulatorVariableOut + u" = "_s;
2511 m_body += input + u".metaType() == "_s + metaType(type: target)
2512 + u" ? " + conversion(from: inputContent, to: outputContent, variable: input)
2513 + u" : " + conversion(from: m_typeResolver->globalType(type: m_typeResolver->voidType()),
2514 to: outputContent, variable: QString());
2515 m_body += u";\n"_s;
2516 return;
2517 }
2518 }
2519
2520 reject(thing: u"unsupported type assertion"_s);
2521
2522}
2523
2524void QQmlJSCodeGenerator::generate_UNot()
2525{
2526 INJECT_TRACE_INFO(generate_UNot);
2527 generateUnaryOperation(cppOperator: u"!"_s);
2528}
2529
2530void QQmlJSCodeGenerator::generate_UPlus()
2531{
2532 INJECT_TRACE_INFO(generate_UPlus);
2533 generateUnaryOperation(cppOperator: u"+"_s);
2534}
2535
2536void QQmlJSCodeGenerator::generate_UMinus()
2537{
2538 INJECT_TRACE_INFO(generate_UMinus);
2539 generateUnaryOperation(cppOperator: u"-"_s);
2540}
2541
2542void QQmlJSCodeGenerator::generate_UCompl()
2543{
2544 INJECT_TRACE_INFO(generate_UCompl);
2545 generateUnaryOperation(cppOperator: u"~"_s);
2546}
2547
2548void QQmlJSCodeGenerator::generate_Increment()
2549{
2550 INJECT_TRACE_INFO(generate_Increment);
2551 generateInPlaceOperation(cppOperator: u"++"_s);
2552}
2553
2554void QQmlJSCodeGenerator::generate_Decrement()
2555{
2556 INJECT_TRACE_INFO(generate_Decrement);
2557 generateInPlaceOperation(cppOperator: u"--"_s);
2558}
2559
2560void QQmlJSCodeGenerator::generate_Add(int lhs)
2561{
2562 INJECT_TRACE_INFO(generate_Add);
2563 generateArithmeticOperation(lhs, cppOperator: u"+"_s);
2564}
2565
2566void QQmlJSCodeGenerator::generate_BitAnd(int lhs)
2567{
2568 INJECT_TRACE_INFO(generate_BitAnd);
2569 generateArithmeticOperation(lhs, cppOperator: u"&"_s);
2570}
2571
2572void QQmlJSCodeGenerator::generate_BitOr(int lhs)
2573{
2574 INJECT_TRACE_INFO(generate_BitOr);
2575 generateArithmeticOperation(lhs, cppOperator: u"|"_s);
2576}
2577
2578void QQmlJSCodeGenerator::generate_BitXor(int lhs)
2579{
2580 INJECT_TRACE_INFO(generate_BitXor);
2581 generateArithmeticOperation(lhs, cppOperator: u"^"_s);
2582}
2583
2584void QQmlJSCodeGenerator::generate_UShr(int lhs)
2585{
2586 INJECT_TRACE_INFO(generate_BitUShr);
2587 generateShiftOperation(lhs, cppOperator: u">>"_s);
2588}
2589
2590void QQmlJSCodeGenerator::generate_Shr(int lhs)
2591{
2592 INJECT_TRACE_INFO(generate_Shr);
2593 generateShiftOperation(lhs, cppOperator: u">>"_s);
2594}
2595
2596void QQmlJSCodeGenerator::generate_Shl(int lhs)
2597{
2598 INJECT_TRACE_INFO(generate_Shl);
2599 generateShiftOperation(lhs, cppOperator: u"<<"_s);
2600}
2601
2602void QQmlJSCodeGenerator::generate_BitAndConst(int rhs)
2603{
2604 INJECT_TRACE_INFO(generate_BitAndConst);
2605 generateArithmeticConstOperation(lhsConst: rhs, cppOperator: u"&"_s);
2606}
2607
2608void QQmlJSCodeGenerator::generate_BitOrConst(int rhs)
2609{
2610 INJECT_TRACE_INFO(generate_BitOrConst);
2611 generateArithmeticConstOperation(lhsConst: rhs, cppOperator: u"|"_s);
2612}
2613
2614void QQmlJSCodeGenerator::generate_BitXorConst(int rhs)
2615{
2616 INJECT_TRACE_INFO(generate_BitXorConst);
2617 generateArithmeticConstOperation(lhsConst: rhs, cppOperator: u"^"_s);
2618}
2619
2620void QQmlJSCodeGenerator::generate_UShrConst(int rhs)
2621{
2622 INJECT_TRACE_INFO(generate_UShrConst);
2623 generateArithmeticConstOperation(lhsConst: rhs & 0x1f, cppOperator: u">>"_s);
2624}
2625
2626void QQmlJSCodeGenerator::generate_ShrConst(int rhs)
2627{
2628 INJECT_TRACE_INFO(generate_ShrConst);
2629 generateArithmeticConstOperation(lhsConst: rhs & 0x1f, cppOperator: u">>"_s);
2630}
2631
2632void QQmlJSCodeGenerator::generate_ShlConst(int rhs)
2633{
2634 INJECT_TRACE_INFO(generate_ShlConst);
2635 generateArithmeticConstOperation(lhsConst: rhs & 0x1f, cppOperator: u"<<"_s);
2636}
2637
2638void QQmlJSCodeGenerator::generate_Exp(int lhs)
2639{
2640 INJECT_TRACE_INFO(generate_Exp);
2641
2642 const QString lhsString = conversion(
2643 from: registerType(index: lhs), to: m_state.readRegister(registerIndex: lhs), variable: consumedRegisterVariable(index: lhs));
2644 const QString rhsString = conversion(
2645 from: m_state.accumulatorIn(), to: m_state.readAccumulator(),
2646 variable: consumedAccumulatorVariableIn());
2647
2648 Q_ASSERT(m_error->isValid() || !lhsString.isEmpty());
2649 Q_ASSERT(m_error->isValid() || !rhsString.isEmpty());
2650
2651 const QQmlJSRegisterContent originalOut = m_typeResolver->original(type: m_state.accumulatorOut());
2652 m_body += m_state.accumulatorVariableOut + u" = "_s;
2653 m_body += conversion(
2654 from: originalOut, to: m_state.accumulatorOut(),
2655 variable: u"QQmlPrivate::jsExponentiate("_s + lhsString + u", "_s + rhsString + u')');
2656 m_body += u";\n"_s;
2657}
2658
2659void QQmlJSCodeGenerator::generate_Mul(int lhs)
2660{
2661 INJECT_TRACE_INFO(generate_Mul);
2662 generateArithmeticOperation(lhs, cppOperator: u"*"_s);
2663}
2664
2665void QQmlJSCodeGenerator::generate_Div(int lhs)
2666{
2667 INJECT_TRACE_INFO(generate_Div);
2668 generateArithmeticOperation(lhs, cppOperator: u"/"_s);
2669}
2670
2671void QQmlJSCodeGenerator::generate_Mod(int lhs)
2672{
2673 INJECT_TRACE_INFO(generate_Mod);
2674
2675 const auto lhsVar = convertStored(
2676 from: registerType(index: lhs).storedType(), to: m_typeResolver->jsPrimitiveType(),
2677 variable: consumedRegisterVariable(index: lhs));
2678 const auto rhsVar = convertStored(
2679 from: m_state.accumulatorIn().storedType(), to: m_typeResolver->jsPrimitiveType(),
2680 variable: consumedAccumulatorVariableIn());
2681 Q_ASSERT(m_error->isValid() || !lhsVar.isEmpty());
2682 Q_ASSERT(m_error->isValid() || !rhsVar.isEmpty());
2683
2684 m_body += m_state.accumulatorVariableOut;
2685 m_body += u" = "_s;
2686 m_body += conversion(from: m_typeResolver->jsPrimitiveType(), to: m_state.accumulatorOut(),
2687 variable: u'(' + lhsVar + u" % "_s + rhsVar + u')');
2688 m_body += u";\n"_s;
2689}
2690
2691void QQmlJSCodeGenerator::generate_Sub(int lhs)
2692{
2693 INJECT_TRACE_INFO(generate_Sub);
2694 generateArithmeticOperation(lhs, cppOperator: u"-"_s);
2695}
2696
2697void QQmlJSCodeGenerator::generate_InitializeBlockDeadTemporalZone(int firstReg, int count)
2698{
2699 Q_UNUSED(firstReg)
2700 Q_UNUSED(count)
2701 // Ignore. We reject uninitialized values anyway.
2702}
2703
2704void QQmlJSCodeGenerator::generate_ThrowOnNullOrUndefined()
2705{
2706 BYTECODE_UNIMPLEMENTED();
2707}
2708
2709void QQmlJSCodeGenerator::generate_GetTemplateObject(int index)
2710{
2711 Q_UNUSED(index)
2712 BYTECODE_UNIMPLEMENTED();
2713}
2714
2715QV4::Moth::ByteCodeHandler::Verdict QQmlJSCodeGenerator::startInstruction(
2716 QV4::Moth::Instr::Type type)
2717{
2718 m_state.State::operator=(nextStateFromAnnotations(oldState: m_state, annotations: *m_annotations));
2719 const auto accumulatorIn = m_state.registers.find(key: Accumulator);
2720 if (accumulatorIn != m_state.registers.end()
2721 && isTypeStorable(resolver: m_typeResolver, type: accumulatorIn.value().content.storedType())) {
2722 m_state.accumulatorVariableIn = m_registerVariables.value(key: RegisterVariablesKey {
2723 .internalName: accumulatorIn.value().content.storedType()->internalName(),
2724 .registerIndex: Accumulator
2725 }).variableName;
2726 Q_ASSERT(!m_state.accumulatorVariableIn.isEmpty());
2727 } else {
2728 m_state.accumulatorVariableIn.clear();
2729 }
2730
2731 auto labelIt = m_labels.constFind(key: currentInstructionOffset());
2732 if (labelIt != m_labels.constEnd()) {
2733 m_body += *labelIt + u":;\n"_s;
2734 m_skipUntilNextLabel = false;
2735 } else if (m_skipUntilNextLabel && !instructionManipulatesContext(type)) {
2736 return SkipInstruction;
2737 }
2738
2739 if (m_state.changedRegisterIndex() == Accumulator)
2740 m_state.accumulatorVariableOut = changedRegisterVariable();
2741 else
2742 m_state.accumulatorVariableOut.clear();
2743
2744 // If the accumulator type is valid, we want an accumulator variable.
2745 // If not, we don't want one.
2746 Q_ASSERT(m_state.changedRegisterIndex() == Accumulator
2747 || m_state.accumulatorVariableOut.isEmpty());
2748 Q_ASSERT(m_state.changedRegisterIndex() != Accumulator
2749 || !m_state.accumulatorVariableOut.isEmpty()
2750 || !isTypeStorable(m_typeResolver, m_state.changedRegister().storedType()));
2751
2752 // If the instruction has no side effects and doesn't write any register, it's dead.
2753 // We might still need the label, though, and the source code comment.
2754 if (!m_state.hasSideEffects() && changedRegisterVariable().isEmpty())
2755 return SkipInstruction;
2756
2757 return ProcessInstruction;
2758}
2759
2760void QQmlJSCodeGenerator::endInstruction(QV4::Moth::Instr::Type)
2761{
2762 if (!m_skipUntilNextLabel)
2763 generateJumpCodeWithTypeConversions(relativeOffset: 0);
2764}
2765
2766void QQmlJSCodeGenerator::generateSetInstructionPointer()
2767{
2768 m_body += u"aotContext->setInstructionPointer("_s
2769 + QString::number(nextInstructionOffset()) + u");\n"_s;
2770}
2771
2772void QQmlJSCodeGenerator::generateExceptionCheck()
2773{
2774 m_body += u"if (aotContext->engine->hasError())\n"_s;
2775 m_body += u" return "_s + errorReturnValue() + u";\n"_s;
2776}
2777
2778void QQmlJSCodeGenerator::generateEqualityOperation(int lhs, const QString &function, bool invert)
2779{
2780 const QQmlJSRegisterContent lhsContent = registerType(index: lhs);
2781 const bool strictlyComparableWithVar = function == "strictlyEquals"_L1
2782 && canStrictlyCompareWithVar(typeResolver: m_typeResolver, lhsContent, rhsContent: m_state.accumulatorIn());
2783 auto isComparable = [&]() {
2784 if (m_typeResolver->isPrimitive(type: lhsContent)
2785 && m_typeResolver->isPrimitive(type: m_state.accumulatorIn())) {
2786 return true;
2787 }
2788 if (m_typeResolver->isNumeric(type: lhsContent) && m_state.accumulatorIn().isEnumeration())
2789 return true;
2790 if (m_typeResolver->isNumeric(type: m_state.accumulatorIn()) && lhsContent.isEnumeration())
2791 return true;
2792 if (strictlyComparableWithVar)
2793 return true;
2794 if (canCompareWithQObject(typeResolver: m_typeResolver, lhsContent, rhsContent: m_state.accumulatorIn()))
2795 return true;
2796 if (canCompareWithQUrl(typeResolver: m_typeResolver, lhsContent, rhsContent: m_state.accumulatorIn()))
2797 return true;
2798 return false;
2799 };
2800
2801 if (!isComparable()) {
2802 reject(thing: u"incomparable types %1 and %2"_s.arg(args: m_state.accumulatorIn().descriptiveName(),
2803 args: lhsContent.descriptiveName()));
2804 }
2805
2806 const QQmlJSScope::ConstPtr lhsType = lhsContent.storedType();
2807 const QQmlJSScope::ConstPtr rhsType = m_state.accumulatorIn().storedType();
2808
2809 const auto primitive = m_typeResolver->jsPrimitiveType();
2810 if (m_typeResolver->equals(a: lhsType, b: rhsType) && !m_typeResolver->equals(a: lhsType, b: primitive)) {
2811 m_body += m_state.accumulatorVariableOut + u" = "_s;
2812 if (isTypeStorable(resolver: m_typeResolver, type: lhsType)) {
2813 m_body += conversion(from: m_typeResolver->boolType(), to: m_state.accumulatorOut(),
2814 variable: consumedRegisterVariable(index: lhs) + (invert ? u" != "_s : u" == "_s)
2815 + consumedAccumulatorVariableIn());
2816 } else {
2817 // null === null and undefined === undefined
2818 m_body += invert ? u"false"_s : u"true"_s;
2819 }
2820 } else if (strictlyComparableWithVar) {
2821 // Determine which side is holding a storable type
2822 if (const auto registerVariableName = registerVariable(index: lhs);
2823 !registerVariableName.isEmpty()) {
2824 // lhs register holds var type
2825 generateVariantEqualityComparison(nonStorableContent: m_state.accumulatorIn(), registerName: registerVariableName,
2826 invert);
2827 } else {
2828 // lhs content is not storable and rhs is var type
2829 generateVariantEqualityComparison(nonStorableContent: lhsContent, registerName: m_state.accumulatorVariableIn, invert);
2830 }
2831 } else if (canCompareWithQObject(typeResolver: m_typeResolver, lhsContent, rhsContent: m_state.accumulatorIn())) {
2832 m_body += m_state.accumulatorVariableOut + u" = "_s;
2833 m_body += u'('
2834 + (isTypeStorable(resolver: m_typeResolver, type: lhsContent.storedType())
2835 ? registerVariable(index: lhs)
2836 : m_state.accumulatorVariableIn)
2837 + (invert ? u" != "_s : u" == "_s) + u"nullptr)"_s;
2838 } else {
2839 m_body += m_state.accumulatorVariableOut + u" = "_s;
2840 m_body += conversion(
2841 from: m_typeResolver->boolType(), to: m_state.accumulatorOut(),
2842 variable: (invert ? u"!"_s : QString())
2843 + convertStored(from: registerType(index: lhs).storedType(), to: primitive,
2844 variable: consumedRegisterVariable(index: lhs))
2845 + u'.' + function + u'(' + convertStored(
2846 from: m_state.accumulatorIn().storedType(), to: primitive,
2847 variable: consumedAccumulatorVariableIn())
2848 + u')');
2849 }
2850 m_body += u";\n"_s;
2851}
2852
2853void QQmlJSCodeGenerator::generateCompareOperation(int lhs, const QString &cppOperator)
2854{
2855 m_body += m_state.accumulatorVariableOut + u" = "_s;
2856
2857 const auto lhsType = registerType(index: lhs);
2858 const QQmlJSScope::ConstPtr compareType =
2859 m_typeResolver->isNumeric(type: lhsType) && m_typeResolver->isNumeric(type: m_state.accumulatorIn())
2860 ? m_typeResolver->merge(a: lhsType, b: m_state.accumulatorIn()).storedType()
2861 : m_typeResolver->jsPrimitiveType();
2862
2863 m_body += conversion(
2864 from: m_typeResolver->boolType(), to: m_state.accumulatorOut(),
2865 variable: convertStored(from: registerType(index: lhs).storedType(), to: compareType,
2866 variable: consumedRegisterVariable(index: lhs))
2867 + u' ' + cppOperator + u' '
2868 + convertStored(from: m_state.accumulatorIn().storedType(), to: compareType,
2869 variable: consumedAccumulatorVariableIn()));
2870 m_body += u";\n"_s;
2871}
2872
2873void QQmlJSCodeGenerator::generateArithmeticOperation(int lhs, const QString &cppOperator)
2874{
2875 generateArithmeticOperation(
2876 lhs: conversion(from: registerType(index: lhs), to: m_state.readRegister(registerIndex: lhs),
2877 variable: consumedRegisterVariable(index: lhs)),
2878 rhs: conversion(from: m_state.accumulatorIn(), to: m_state.readAccumulator(),
2879 variable: consumedAccumulatorVariableIn()),
2880 cppOperator);
2881}
2882
2883void QQmlJSCodeGenerator::generateShiftOperation(int lhs, const QString &cppOperator)
2884{
2885 generateArithmeticOperation(
2886 lhs: conversion(from: registerType(index: lhs), to: m_state.readRegister(registerIndex: lhs),
2887 variable: consumedRegisterVariable(index: lhs)),
2888 rhs: u'(' + conversion(from: m_state.accumulatorIn(), to: m_state.readAccumulator(),
2889 variable: consumedAccumulatorVariableIn()) + u" & 0x1f)"_s,
2890 cppOperator);
2891}
2892
2893void QQmlJSCodeGenerator::generateArithmeticOperation(
2894 const QString &lhs, const QString &rhs, const QString &cppOperator)
2895{
2896 Q_ASSERT(m_error->isValid() || !lhs.isEmpty());
2897 Q_ASSERT(m_error->isValid() || !rhs.isEmpty());
2898
2899 const QQmlJSRegisterContent originalOut = m_typeResolver->original(type: m_state.accumulatorOut());
2900 m_body += m_state.accumulatorVariableOut;
2901 m_body += u" = "_s;
2902 m_body += conversion(
2903 from: originalOut, to: m_state.accumulatorOut(),
2904 variable: u'(' + lhs + u' ' + cppOperator + u' ' + rhs + u')');
2905 m_body += u";\n"_s;
2906}
2907
2908void QQmlJSCodeGenerator::generateArithmeticConstOperation(int rhsConst, const QString &cppOperator)
2909{
2910 generateArithmeticOperation(
2911 lhs: conversion(from: m_state.accumulatorIn(), to: m_state.readAccumulator(),
2912 variable: consumedAccumulatorVariableIn()),
2913 rhs: conversion(from: m_typeResolver->globalType(type: m_typeResolver->int32Type()),
2914 to: m_state.readAccumulator(), variable: QString::number(rhsConst)),
2915 cppOperator);
2916}
2917
2918void QQmlJSCodeGenerator::generateUnaryOperation(const QString &cppOperator)
2919{
2920 const auto var = conversion(from: m_state.accumulatorIn(), to: m_state.readAccumulator(),
2921 variable: consumedAccumulatorVariableIn());
2922
2923 if (var == m_state.accumulatorVariableOut) {
2924 m_body += m_state.accumulatorVariableOut + u" = "_s + cppOperator + var + u";\n"_s;
2925 return;
2926 }
2927
2928 const auto original = m_typeResolver->original(type: m_state.accumulatorOut());
2929 if (m_state.accumulatorOut() == original) {
2930 m_body += m_state.accumulatorVariableOut + u" = "_s + var + u";\n"_s;
2931 m_body += m_state.accumulatorVariableOut + u" = "_s
2932 + cppOperator + m_state.accumulatorVariableOut + u";\n"_s;
2933 return;
2934 }
2935
2936 m_body += m_state.accumulatorVariableOut + u" = "_s + conversion(
2937 from: original, to: m_state.accumulatorOut(), variable: cppOperator + var) + u";\n"_s;
2938}
2939
2940void QQmlJSCodeGenerator::generateInPlaceOperation(const QString &cppOperator)
2941{
2942 {
2943 // If actually in place, we cannot consume the variable.
2944 const QString var = conversion(from: m_state.accumulatorIn(), to: m_state.readAccumulator(),
2945 variable: m_state.accumulatorVariableIn);
2946 if (var == m_state.accumulatorVariableOut) {
2947 m_body += cppOperator + var + u";\n"_s;
2948 return;
2949 }
2950 }
2951
2952 const QString var = conversion(from: m_state.accumulatorIn(), to: m_state.readAccumulator(),
2953 variable: consumedAccumulatorVariableIn());
2954
2955 const auto original = m_typeResolver->original(type: m_state.accumulatorOut());
2956 if (m_state.accumulatorOut() == original) {
2957 m_body += m_state.accumulatorVariableOut + u" = "_s + var + u";\n"_s;
2958 m_body += cppOperator + m_state.accumulatorVariableOut + u";\n"_s;
2959 return;
2960 }
2961
2962 m_body += u"{\n"_s;
2963 m_body += u"auto converted = "_s + var + u";\n"_s;
2964 m_body += m_state.accumulatorVariableOut + u" = "_s + conversion(
2965 from: original, to: m_state.accumulatorOut(), variable: u'('
2966 + cppOperator + u"converted)"_s) + u";\n"_s;
2967 m_body += u"}\n"_s;
2968}
2969
2970void QQmlJSCodeGenerator::generateLookup(const QString &lookup, const QString &initialization,
2971 const QString &resultPreparation)
2972{
2973 if (!resultPreparation.isEmpty())
2974 m_body += resultPreparation + u";\n"_s;
2975 m_body += u"while (!"_s + lookup + u") {\n"_s;
2976 generateSetInstructionPointer();
2977 m_body += initialization + u";\n"_s;
2978 generateExceptionCheck();
2979 if (!resultPreparation.isEmpty())
2980 m_body += resultPreparation + u";\n"_s;
2981 m_body += u"}\n"_s;
2982}
2983
2984void QQmlJSCodeGenerator::generateJumpCodeWithTypeConversions(int relativeOffset)
2985{
2986 QString conversionCode;
2987 const int absoluteOffset = nextInstructionOffset() + relativeOffset;
2988 const auto annotation = m_annotations->find(key: absoluteOffset);
2989 if (annotation != m_annotations->constEnd()) {
2990 const auto &conversions = annotation->second.typeConversions;
2991
2992 for (auto regIt = conversions.constBegin(), regEnd = conversions.constEnd();
2993 regIt != regEnd; ++regIt) {
2994 int registerIndex = regIt.key();
2995 const QQmlJSRegisterContent targetType = regIt.value().content;
2996 if (!targetType.isValid())
2997 continue;
2998
2999 QQmlJSRegisterContent currentType;
3000 QString currentVariable;
3001 if (registerIndex == m_state.changedRegisterIndex()) {
3002 currentType = m_state.changedRegister();
3003 currentVariable = changedRegisterVariable();
3004 } else {
3005 auto it = m_state.registers.find(key: registerIndex);
3006 if (it == m_state.registers.end())
3007 continue;
3008 currentType = it.value().content;
3009 currentVariable = consumedRegisterVariable(index: registerIndex);
3010 }
3011
3012 // Actually == here. We want the jump code also for equal types
3013 if (currentType == targetType
3014 || !isTypeStorable(resolver: m_typeResolver, type: targetType.storedType())) {
3015 continue;
3016 }
3017
3018 const auto variable = m_registerVariables.constFind(key: RegisterVariablesKey {
3019 .internalName: targetType.storedType()->internalName(),
3020 .registerIndex: registerIndex
3021 });
3022 if (variable == m_registerVariables.end() || variable->variableName == currentVariable)
3023 continue;
3024
3025 conversionCode += variable->variableName;
3026 conversionCode += u" = "_s;
3027 conversionCode += conversion(from: currentType, to: targetType, variable: currentVariable);
3028 conversionCode += u";\n"_s;
3029 }
3030 }
3031
3032 if (relativeOffset) {
3033 auto labelIt = m_labels.find(key: absoluteOffset);
3034 if (labelIt == m_labels.end())
3035 labelIt = m_labels.insert(key: absoluteOffset, value: u"label_%1"_s.arg(a: m_labels.size()));
3036 conversionCode += u" goto "_s + *labelIt + u";\n"_s;
3037 }
3038
3039 if (!conversionCode.isEmpty())
3040 m_body += u"{\n"_s + conversionCode + u"}\n"_s;
3041}
3042
3043QString QQmlJSCodeGenerator::registerVariable(int index) const
3044{
3045 const auto it = m_registerVariables.constFind(key: RegisterVariablesKey {
3046 .internalName: registerType(index).storedType()->internalName(),
3047 .registerIndex: index
3048 });
3049 if (it != m_registerVariables.constEnd())
3050 return it->variableName;
3051
3052 return QString();
3053}
3054
3055QString QQmlJSCodeGenerator::consumedRegisterVariable(int index) const
3056{
3057 const QString var = registerVariable(index);
3058 if (var.isEmpty() || !m_state.canMoveReadRegister(registerIndex: index))
3059 return var;
3060 return u"std::move(" + var + u")";
3061}
3062
3063QString QQmlJSCodeGenerator::consumedAccumulatorVariableIn() const
3064{
3065 return m_state.canMoveReadRegister(registerIndex: Accumulator)
3066 ? u"std::move(" + m_state.accumulatorVariableIn + u")"
3067 : m_state.accumulatorVariableIn;
3068}
3069
3070QString QQmlJSCodeGenerator::changedRegisterVariable() const
3071{
3072 const QQmlJSScope::ConstPtr storedType = m_state.changedRegister().storedType();
3073 if (storedType.isNull())
3074 return QString();
3075
3076 return m_registerVariables.value(key: RegisterVariablesKey {
3077 .internalName: storedType->internalName(),
3078 .registerIndex: m_state.changedRegisterIndex()
3079 }).variableName;
3080}
3081
3082QQmlJSRegisterContent QQmlJSCodeGenerator::registerType(int index) const
3083{
3084 auto it = m_state.registers.find(key: index);
3085 if (it != m_state.registers.end())
3086 return it.value().content;
3087
3088 return QQmlJSRegisterContent();
3089}
3090
3091QString QQmlJSCodeGenerator::conversion(
3092 const QQmlJSRegisterContent &from, const QQmlJSRegisterContent &to, const QString &variable)
3093{
3094 const QQmlJSScope::ConstPtr contained = m_typeResolver->containedType(container: to);
3095
3096 // If both types are stored in QJSPrimitiveValue we coerce using QJSPrimitiveValue
3097 if (m_typeResolver->registerIsStoredIn(reg: from, type: m_typeResolver->jsPrimitiveType())
3098 && m_typeResolver->registerIsStoredIn(reg: to, type: m_typeResolver->jsPrimitiveType())) {
3099 if (m_typeResolver->equals(a: contained, b: m_typeResolver->jsPrimitiveType()))
3100 return variable;
3101
3102 const QString conversion = variable + u".to<QJSPrimitiveValue::%1>()"_s;
3103 if (m_typeResolver->equals(a: contained, b: m_typeResolver->boolType()))
3104 return conversion.arg(a: u"Boolean"_s);
3105 if (m_typeResolver->isIntegral(type: to))
3106 return conversion.arg(a: u"Integer"_s);
3107 if (m_typeResolver->equals(a: contained, b: m_typeResolver->realType()))
3108 return conversion.arg(a: u"Double"_s);
3109 if (m_typeResolver->equals(a: contained, b: m_typeResolver->stringType()))
3110 return conversion.arg(a: u"String"_s);
3111 reject(thing: u"Conversion of QJSPrimitiveValue to "_s + contained->internalName());
3112 }
3113
3114 if (m_typeResolver->registerIsStoredIn(reg: to, type: contained)
3115 || m_typeResolver->isNumeric(type: to.storedType())
3116 || to.storedType()->isReferenceType()
3117 || m_typeResolver->registerContains(reg: from, type: contained)) {
3118 // If:
3119 // * the output is not actually wrapped at all, or
3120 // * the output is stored in a numeric type (as there are no internals to a number), or
3121 // * the output is a QObject pointer, or
3122 // * we merely wrap the value into a new container,
3123 // we can convert by stored type.
3124 return convertStored(from: from.storedType(), to: to.storedType(), variable);
3125 } else {
3126 return convertContained(from, to, variable);
3127 }
3128}
3129
3130QString QQmlJSCodeGenerator::convertStored(
3131 const QQmlJSScope::ConstPtr &from, const QQmlJSScope::ConstPtr &to, const QString &variable)
3132{
3133 // TODO: most values can be moved, which is much more efficient with the common types.
3134 // add a move(from, to, variable) function that implements the moves.
3135 Q_ASSERT(!to->isComposite()); // We cannot directly convert to composites.
3136
3137 const auto jsValueType = m_typeResolver->jsValueType();
3138 const auto varType = m_typeResolver->varType();
3139 const auto jsPrimitiveType = m_typeResolver->jsPrimitiveType();
3140 const auto boolType = m_typeResolver->boolType();
3141
3142 auto zeroBoolOrInt = [&](const QQmlJSScope::ConstPtr &to) {
3143 if (m_typeResolver->equals(a: to, b: boolType))
3144 return u"false"_s;
3145 if (m_typeResolver->isSignedInteger(type: to))
3146 return u"0"_s;
3147 if (m_typeResolver->isUnsignedInteger(type: to))
3148 return u"0u"_s;
3149 return QString();
3150 };
3151
3152 if (m_typeResolver->equals(a: from, b: m_typeResolver->voidType())) {
3153 if (to->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
3154 return u"static_cast<"_s + to->internalName() + u" *>(nullptr)"_s;
3155 const QString zero = zeroBoolOrInt(to);
3156 if (!zero.isEmpty())
3157 return zero;
3158 if (m_typeResolver->equals(a: to, b: m_typeResolver->floatType()))
3159 return u"std::numeric_limits<float>::quiet_NaN()"_s;
3160 if (m_typeResolver->equals(a: to, b: m_typeResolver->realType()))
3161 return u"std::numeric_limits<double>::quiet_NaN()"_s;
3162 if (m_typeResolver->equals(a: to, b: m_typeResolver->stringType()))
3163 return QQmlJSUtils::toLiteral(s: u"undefined"_s);
3164 if (m_typeResolver->equals(a: to, b: m_typeResolver->varType()))
3165 return u"QVariant()"_s;
3166 if (m_typeResolver->equals(a: to, b: m_typeResolver->jsValueType()))
3167 return u"QJSValue();"_s;
3168 if (m_typeResolver->equals(a: to, b: m_typeResolver->jsPrimitiveType()))
3169 return u"QJSPrimitiveValue()"_s;
3170 if (m_typeResolver->equals(a: from, b: to))
3171 return QString();
3172 }
3173
3174 if (m_typeResolver->equals(a: from, b: m_typeResolver->nullType())) {
3175 if (to->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
3176 return u"static_cast<"_s + to->internalName() + u" *>(nullptr)"_s;
3177 if (m_typeResolver->equals(a: to, b: jsValueType))
3178 return u"QJSValue(QJSValue::NullValue)"_s;
3179 if (m_typeResolver->equals(a: to, b: jsPrimitiveType))
3180 return u"QJSPrimitiveValue(QJSPrimitiveNull())"_s;
3181 if (m_typeResolver->equals(a: to, b: varType))
3182 return u"QVariant::fromValue<std::nullptr_t>(nullptr)"_s;
3183 const QString zero = zeroBoolOrInt(to);
3184 if (!zero.isEmpty())
3185 return zero;
3186 if (m_typeResolver->equals(a: to, b: m_typeResolver->floatType()))
3187 return u"0.0f"_s;
3188 if (m_typeResolver->equals(a: to, b: m_typeResolver->realType()))
3189 return u"0.0"_s;
3190 if (m_typeResolver->equals(a: to, b: m_typeResolver->stringType()))
3191 return QQmlJSUtils::toLiteral(s: u"null"_s);
3192 if (m_typeResolver->equals(a: from, b: to))
3193 return QString();
3194 reject(thing: u"Conversion from null to %1"_s.arg(a: to->internalName()));
3195 }
3196
3197 if (m_typeResolver->equals(a: from, b: to))
3198 return variable;
3199
3200 if (from->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) {
3201 if (to->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) {
3202 // Compare internalName here. The same C++ type can be exposed muliple times in
3203 // different QML types. However, the C++ names have to be unique. We can always
3204 // static_cast to those.
3205
3206 for (QQmlJSScope::ConstPtr base = from; base; base = base->baseType()) {
3207 // We still have to cast as other execution paths may result in different types.
3208 if (base->internalName() == to->internalName())
3209 return u"static_cast<"_s + to->internalName() + u" *>("_s + variable + u')';
3210 }
3211 for (QQmlJSScope::ConstPtr base = to; base; base = base->baseType()) {
3212 if (base->internalName() == from->internalName())
3213 return u"static_cast<"_s + to->internalName() + u" *>("_s + variable + u')';
3214 }
3215 } else if (m_typeResolver->equals(a: to, b: m_typeResolver->boolType())) {
3216 return u'(' + variable + u" != nullptr)"_s;
3217 }
3218 }
3219
3220 auto isJsValue = [&](const QQmlJSScope::ConstPtr &candidate) {
3221 return m_typeResolver->equals(a: candidate, b: jsValueType) || candidate->isScript();
3222 };
3223
3224 if (isJsValue(from) && isJsValue(to))
3225 return variable;
3226
3227 const auto isBoolOrNumber = [&](const QQmlJSScope::ConstPtr &type) {
3228 return m_typeResolver->isNumeric(type: m_typeResolver->globalType(type))
3229 || m_typeResolver->equals(a: type, b: m_typeResolver->boolType())
3230 || type->scopeType() == QQmlSA::ScopeType::EnumScope;
3231 };
3232
3233 if (m_typeResolver->equals(a: from, b: m_typeResolver->realType())
3234 || m_typeResolver->equals(a: from, b: m_typeResolver->floatType())) {
3235 if (m_typeResolver->isSignedInteger(type: to))
3236 return u"QJSNumberCoercion::toInteger("_s + variable + u')';
3237 if (m_typeResolver->isUnsignedInteger(type: to))
3238 return u"uint(QJSNumberCoercion::toInteger("_s + variable + u"))"_s;
3239 if (m_typeResolver->equals(a: to, b: m_typeResolver->boolType()))
3240 return u'(' + variable + u" && !std::isnan("_s + variable + u"))"_s;
3241 }
3242
3243 if (isBoolOrNumber(from) && isBoolOrNumber(to))
3244 return to->internalName() + u'(' + variable + u')';
3245
3246
3247 if (m_typeResolver->equals(a: from, b: jsPrimitiveType)) {
3248 if (m_typeResolver->equals(a: to, b: m_typeResolver->realType()))
3249 return variable + u".toDouble()"_s;
3250 if (m_typeResolver->equals(a: to, b: boolType))
3251 return variable + u".toBoolean()"_s;
3252 if (m_typeResolver->isSignedInteger(type: to))
3253 return variable + u".toInteger()"_s;
3254 if (m_typeResolver->isUnsignedInteger(type: to))
3255 return u"uint("_s + variable + u".toInteger())"_s;
3256 if (m_typeResolver->equals(a: to, b: m_typeResolver->stringType()))
3257 return variable + u".toString()"_s;
3258 if (m_typeResolver->equals(a: to, b: jsValueType))
3259 return u"QJSValue(QJSPrimitiveValue("_s + variable + u"))"_s;
3260 if (m_typeResolver->equals(a: to, b: varType))
3261 return variable + u".toVariant()"_s;
3262 if (to->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
3263 return u"static_cast<"_s + to->internalName() + u" *>(nullptr)"_s;
3264 }
3265
3266 if (isJsValue(from)) {
3267 if (m_typeResolver->equals(a: to, b: jsPrimitiveType))
3268 return variable + u".toPrimitive()"_s;
3269 if (m_typeResolver->equals(a: to, b: varType))
3270 return variable + u".toVariant(QJSValue::RetainJSObjects)"_s;
3271 return u"qjsvalue_cast<"_s + castTargetName(type: to) + u">("_s + variable + u')';
3272 }
3273
3274 if (m_typeResolver->equals(a: to, b: jsPrimitiveType)) {
3275 // null and undefined have been handled above already
3276 Q_ASSERT(!m_typeResolver->equals(from, m_typeResolver->nullType()));
3277 Q_ASSERT(!m_typeResolver->equals(from, m_typeResolver->voidType()));
3278
3279 if (m_typeResolver->equals(a: from, b: m_typeResolver->boolType())
3280 || m_typeResolver->equals(a: from, b: m_typeResolver->int32Type())
3281 || m_typeResolver->equals(a: from, b: m_typeResolver->realType())
3282 || m_typeResolver->equals(a: from, b: m_typeResolver->stringType())) {
3283 return u"QJSPrimitiveValue("_s + variable + u')';
3284 } else if (m_typeResolver->isSignedInteger(type: from)
3285 || m_typeResolver->equals(a: from, b: m_typeResolver->uint16Type())
3286 || m_typeResolver->equals(a: from, b: m_typeResolver->uint8Type())) {
3287 return u"QJSPrimitiveValue(int("_s + variable + u"))"_s;
3288 } else if (m_typeResolver->isNumeric(type: from)) {
3289 return u"QJSPrimitiveValue(double("_s + variable + u"))"_s;
3290 }
3291 }
3292
3293 if (m_typeResolver->equals(a: to, b: jsValueType))
3294 return u"aotContext->engine->toScriptValue("_s + variable + u')';
3295
3296 if (m_typeResolver->equals(a: from, b: varType)) {
3297 if (m_typeResolver->equals(a: to, b: m_typeResolver->listPropertyType()))
3298 return u"QQmlListReference("_s + variable + u", aotContext->qmlEngine())"_s;
3299 return u"aotContext->engine->fromVariant<"_s + castTargetName(type: to) + u">("_s
3300 + variable + u')';
3301 }
3302
3303 if (m_typeResolver->equals(a: to, b: varType))
3304 return u"QVariant::fromValue("_s + variable + u')';
3305
3306 if (m_typeResolver->equals(a: from, b: m_typeResolver->urlType())
3307 && m_typeResolver->equals(a: to, b: m_typeResolver->stringType())) {
3308 return variable + u".toString()"_s;
3309 }
3310
3311 if (m_typeResolver->equals(a: from, b: m_typeResolver->stringType())
3312 && m_typeResolver->equals(a: to, b: m_typeResolver->urlType())) {
3313 return u"QUrl("_s + variable + u')';
3314 }
3315
3316 if (m_typeResolver->equals(a: from, b: m_typeResolver->byteArrayType())
3317 && m_typeResolver->equals(a: to, b: m_typeResolver->stringType())) {
3318 return u"QString::fromUtf8("_s + variable + u')';
3319 }
3320
3321 if (m_typeResolver->equals(a: from, b: m_typeResolver->stringType())
3322 && m_typeResolver->equals(a: to, b: m_typeResolver->byteArrayType())) {
3323 return variable + u".toUtf8()"_s;
3324 }
3325
3326 for (const auto &originType : {
3327 m_typeResolver->dateTimeType(),
3328 m_typeResolver->dateType(),
3329 m_typeResolver->timeType()}) {
3330 if (m_typeResolver->equals(a: from, b: originType)) {
3331 for (const auto &targetType : {
3332 m_typeResolver->dateTimeType(),
3333 m_typeResolver->dateType(),
3334 m_typeResolver->timeType(),
3335 m_typeResolver->stringType()}) {
3336 if (m_typeResolver->equals(a: to, b: targetType)) {
3337 return u"aotContext->engine->coerceValue<%1, %2>(%3)"_s.arg(
3338 args: originType->internalName(), args: targetType->internalName(), args: variable);
3339 }
3340 }
3341 break;
3342 }
3343 }
3344
3345 const auto retrieveFromPrimitive = [&](
3346 const QQmlJSScope::ConstPtr &type, const QString &expression) -> QString
3347 {
3348 if (m_typeResolver->equals(a: type, b: m_typeResolver->boolType()))
3349 return expression + u".toBoolean()"_s;
3350 if (m_typeResolver->isSignedInteger(type))
3351 return expression + u".toInteger()"_s;
3352 if (m_typeResolver->isUnsignedInteger(type))
3353 return u"uint("_s + expression + u".toInteger())"_s;
3354 if (m_typeResolver->equals(a: type, b: m_typeResolver->realType()))
3355 return expression + u".toDouble()"_s;
3356 if (m_typeResolver->equals(a: type, b: m_typeResolver->floatType()))
3357 return u"float("_s + expression + u".toDouble())"_s;
3358 if (m_typeResolver->equals(a: type, b: m_typeResolver->stringType()))
3359 return expression + u".toString()"_s;
3360 return QString();
3361 };
3362
3363 if (!retrieveFromPrimitive(from, u"x"_s).isEmpty()) {
3364 const QString retrieve = retrieveFromPrimitive(
3365 to, convertStored(from, to: m_typeResolver->jsPrimitiveType(), variable));
3366 if (!retrieve.isEmpty())
3367 return retrieve;
3368 }
3369
3370 if (from->isReferenceType() && m_typeResolver->equals(a: to, b: m_typeResolver->stringType())) {
3371 return u"aotContext->engine->coerceValue<"_s + castTargetName(type: from) + u", "
3372 + castTargetName(type: to) + u">("_s + variable + u')';
3373 }
3374
3375 // Any value type is a non-null JS 'object' and therefore coerces to true.
3376 if (m_typeResolver->equals(a: to, b: m_typeResolver->boolType())) {
3377 // All the interesting cases are already handled above:
3378 Q_ASSERT(!m_typeResolver->equals(from, m_typeResolver->nullType()));
3379 Q_ASSERT(!m_typeResolver->equals(from, m_typeResolver->voidType()));
3380 Q_ASSERT(retrieveFromPrimitive(from, u"x"_s).isEmpty());
3381 Q_ASSERT(!isBoolOrNumber(from));
3382
3383 return u"true"_s;
3384 }
3385
3386 if (m_typeResolver->areEquivalentLists(a: from, b: to))
3387 return variable;
3388
3389 if (from->isListProperty()
3390 && to->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
3391 && to->valueType()->isReferenceType()
3392 && !to->isListProperty()) {
3393 return variable + u".toList<"_s + to->internalName() + u">()"_s;
3394 }
3395
3396 bool isExtension = false;
3397 if (const auto ctor = m_typeResolver->selectConstructor(type: to, argument: from, isExtension: &isExtension); ctor.isValid()) {
3398 const auto argumentTypes = ctor.parameters();
3399 return (isExtension ? to->extensionType().scope->internalName() : to->internalName())
3400 + u"("_s + convertStored(from, to: argumentTypes[0].type(), variable) + u")"_s;
3401 }
3402
3403 // TODO: add more conversions
3404
3405 reject(thing: u"conversion from "_s + from->internalName() + u" to "_s + to->internalName());
3406 return QString();
3407}
3408
3409QString QQmlJSCodeGenerator::convertContained(const QQmlJSRegisterContent &from, const QQmlJSRegisterContent &to, const QString &variable)
3410{
3411 const QQmlJSScope::ConstPtr containedFrom = m_typeResolver->containedType(container: from);
3412 const QQmlJSScope::ConstPtr containedTo = m_typeResolver->containedType(container: to);
3413
3414 // Those should be handled before, by convertStored().
3415 Q_ASSERT(!to.storedType()->isReferenceType());
3416 Q_ASSERT(!m_typeResolver->registerIsStoredIn(to, containedTo));
3417 Q_ASSERT(!m_typeResolver->isIntegral(from.storedType()));
3418 Q_ASSERT(!m_typeResolver->equals(containedFrom, containedTo));
3419
3420 if (!m_typeResolver->registerIsStoredIn(reg: to, type: m_typeResolver->varType()) &&
3421 !m_typeResolver->registerIsStoredIn(reg: to, type: m_typeResolver->jsPrimitiveType())) {
3422 reject(thing: u"internal conversion into unsupported wrapper type."_s);
3423 }
3424
3425 bool isExtension = false;
3426 if (const auto ctor = m_typeResolver->selectConstructor(
3427 type: containedTo, argument: containedFrom, isExtension: &isExtension); ctor.isValid()) {
3428 const auto argumentTypes = ctor.parameters();
3429 const QQmlJSScope::ConstPtr argumentType = argumentTypes[0].type();
3430
3431 // We need to store the converted argument in a temporary
3432 // because it might not be an lvalue.
3433
3434 QString input;
3435 QString argPointer;
3436
3437 if (m_typeResolver->equals(a: argumentType, b: containedFrom)) {
3438 input = variable;
3439 argPointer = contentPointer(content: from, var: u"arg"_s);
3440 } else {
3441 const QQmlJSRegisterContent argument
3442 = m_typeResolver->globalType(type: argumentType)
3443 .storedIn(newStoredType: m_typeResolver->genericType(type: argumentType));
3444 input = conversion(from, to: argument, variable);
3445 argPointer = contentPointer(content: argument, var: u"arg"_s);
3446 }
3447
3448 return u"[&](){ auto arg = " + input
3449 + u"; return aotContext->constructValueType("_s + metaType(type: containedTo)
3450 + u", "_s + metaObject(
3451 objectType: isExtension ? containedTo->extensionType().scope : containedTo)
3452 + u", "_s + QString::number(int(ctor.constructorIndex()))
3453 + u", "_s + argPointer + u"); }()"_s;
3454 }
3455
3456 reject(thing: u"internal conversion with incompatible or ambiguous types"_s);
3457 return QString();
3458}
3459
3460void QQmlJSCodeGenerator::reject(const QString &thing)
3461{
3462 setError(u"Cannot generate efficient code for %1"_s.arg(a: thing));
3463}
3464
3465QQmlJSCodeGenerator::AccumulatorConverter::AccumulatorConverter(QQmlJSCodeGenerator *generator)
3466 : accumulatorOut(generator->m_state.accumulatorOut())
3467 , accumulatorVariableIn(generator->m_state.accumulatorVariableIn)
3468 , accumulatorVariableOut(generator->m_state.accumulatorVariableOut)
3469 , generator(generator)
3470{
3471 if (accumulatorVariableOut.isEmpty())
3472 return;
3473
3474 const QQmlJSTypeResolver *resolver = generator->m_typeResolver;
3475 const QQmlJSScope::ConstPtr origContained = resolver->originalContainedType(container: accumulatorOut);
3476 const QQmlJSScope::ConstPtr stored = accumulatorOut.storedType();
3477 const QQmlJSScope::ConstPtr origStored = resolver->originalType(type: stored);
3478
3479 // If the stored type differs or if we store in QVariant and the contained type differs,
3480 // then we have to use a temporary ...
3481 if (!resolver->equals(a: origStored, b: stored)
3482 || (!resolver->equals(a: origContained, b: resolver->containedType(container: accumulatorOut))
3483 && resolver->equals(a: stored, b: resolver->varType()))) {
3484
3485 const bool storable = isTypeStorable(resolver, type: origStored);
3486 generator->m_state.accumulatorVariableOut = storable ? u"retrieved"_s : QString();
3487 generator->m_state.setRegister(registerIndex: Accumulator, content: resolver->original(type: accumulatorOut));
3488 generator->m_body += u"{\n"_s;
3489 if (storable) {
3490 generator->m_body += origStored->augmentedInternalName() + u' '
3491 + generator->m_state.accumulatorVariableOut + u";\n";
3492 }
3493 } else if (generator->m_state.accumulatorVariableIn == generator->m_state.accumulatorVariableOut
3494 && generator->m_state.readsRegister(registerIndex: Accumulator)
3495 && resolver->registerIsStoredIn(
3496 reg: generator->m_state.accumulatorOut(), type: resolver->varType())) {
3497 // If both m_state.accumulatorIn and m_state.accumulatorOut are QVariant, we will need to
3498 // prepare the output QVariant, and afterwards use the input variant. Therefore we need to
3499 // move the input out of the way first.
3500 generator->m_state.accumulatorVariableIn
3501 = generator->m_state.accumulatorVariableIn + u"_moved"_s;
3502 generator->m_body += u"{\n"_s;
3503 generator->m_body += u"QVariant "_s + generator->m_state.accumulatorVariableIn
3504 + u" = std::move("_s + generator->m_state.accumulatorVariableOut + u");\n"_s;
3505 }
3506}
3507
3508QQmlJSCodeGenerator::AccumulatorConverter::~AccumulatorConverter()
3509{
3510 if (accumulatorVariableOut != generator->m_state.accumulatorVariableOut) {
3511 generator->m_body += accumulatorVariableOut + u" = "_s + generator->conversion(
3512 from: generator->m_state.accumulatorOut(), to: accumulatorOut,
3513 variable: generator->m_state.accumulatorVariableOut) + u";\n"_s;
3514 generator->m_body += u"}\n"_s;
3515 generator->m_state.setRegister(registerIndex: Accumulator, content: accumulatorOut);
3516 generator->m_state.accumulatorVariableOut = accumulatorVariableOut;
3517 } else if (accumulatorVariableIn != generator->m_state.accumulatorVariableIn) {
3518 generator->m_body += u"}\n"_s;
3519 generator->m_state.accumulatorVariableIn = accumulatorVariableIn;
3520 }
3521}
3522
3523
3524QT_END_NAMESPACE
3525

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