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#ifndef QQMLJSCOMPILEPASS_P_H
5#define QQMLJSCOMPILEPASS_P_H
6
7//
8// W A R N I N G
9// -------------
10//
11// This file is not part of the Qt API. It exists purely as an
12// implementation detail. This header file may change from version to
13// version without notice, or even be removed.
14//
15// We mean it.
16
17
18#include <private/qqmljslogger_p.h>
19#include <private/qqmljsregistercontent_p.h>
20#include <private/qqmljsscope_p.h>
21#include <private/qqmljstyperesolver_p.h>
22#include <private/qv4bytecodehandler_p.h>
23#include <private/qv4compiler_p.h>
24#include <private/qflatmap_p.h>
25
26QT_BEGIN_NAMESPACE
27
28class QQmlJSCompilePass : public QV4::Moth::ByteCodeHandler
29{
30 Q_DISABLE_COPY_MOVE(QQmlJSCompilePass)
31public:
32 enum RegisterShortcuts {
33 InvalidRegister = -1,
34
35 // TODO: Should be called "Function", requires refactoring
36 CurrentFunction = QV4::CallData::Function,
37
38 Context = QV4::CallData::Context,
39 Accumulator = QV4::CallData::Accumulator,
40 This = QV4::CallData::This,
41 NewTarget = QV4::CallData::NewTarget,
42 Argc = QV4::CallData::Argc,
43 FirstArgument = QV4::CallData::OffsetCount
44 };
45
46 using SourceLocationTable = QV4::Compiler::Context::SourceLocationTable;
47
48 struct VirtualRegister
49 {
50 QQmlJSRegisterContent content;
51 bool canMove = false;
52 bool affectedBySideEffects = false;
53 bool isShadowable = false;
54
55 private:
56 friend bool operator==(const VirtualRegister &a, const VirtualRegister &b)
57 {
58 return a.content == b.content && a.canMove == b.canMove
59 && a.affectedBySideEffects == b.affectedBySideEffects;
60 }
61 };
62
63 // map from register index to expected type
64 using VirtualRegisters = QFlatMap<int, VirtualRegister>;
65
66 struct BasicBlock
67 {
68 QList<int> jumpOrigins;
69 QList<int> readRegisters;
70 int jumpTarget = -1;
71 bool jumpIsUnconditional = false;
72 bool isReturnBlock = false;
73 bool isThrowBlock = false;
74 };
75
76 using BasicBlocks = QFlatMap<int, BasicBlock>;
77
78 struct InstructionAnnotation
79 {
80 // Registers explicit read as part of the instruction.
81 VirtualRegisters readRegisters;
82
83 // Registers that have to be converted for future instructions after a jump.
84 VirtualRegisters typeConversions;
85
86 QQmlJSRegisterContent changedRegister;
87 int changedRegisterIndex = InvalidRegister;
88 bool hasInternalSideEffects = false;
89 bool hasExternalSideEffects = false;
90 bool isRename = false;
91 bool isShadowable = false;
92 };
93
94 using InstructionAnnotations = QFlatMap<int, InstructionAnnotation>;
95 struct BlocksAndAnnotations
96 {
97 BasicBlocks basicBlocks;
98 InstructionAnnotations annotations;
99 };
100
101 struct Function
102 {
103 QQmlJSScopesById addressableScopes;
104 QList<QQmlJSRegisterContent> argumentTypes;
105 QList<QQmlJSRegisterContent> registerTypes;
106 QQmlJSRegisterContent returnType;
107 QQmlJSRegisterContent qmlScope;
108 QByteArray code;
109 const SourceLocationTable *sourceLocations = nullptr;
110 bool isSignalHandler = false;
111 bool isQPropertyBinding = false;
112 bool isProperty = false;
113 bool isFullyTyped = false;
114 };
115
116 struct ObjectOrArrayDefinition
117 {
118 enum {
119 ArrayClassId = -1,
120 ArrayConstruct1ArgId = -2,
121 };
122
123 int instructionOffset = -1;
124 int internalClassId = ArrayClassId;
125 int argc = 0;
126 int argv = -1;
127 };
128
129 struct State
130 {
131 VirtualRegisters registers;
132 VirtualRegisters lookups;
133
134 /*!
135 \internal
136 \brief The accumulatorIn is the input register of the current instruction.
137
138 It holds a content, a type that content is acctually stored in, and an enclosing type
139 of the stored type called the scope. Note that passes after the original type
140 propagation may change the type of this register to a different type that the original
141 one can be coerced to. Therefore, when analyzing the same instruction in a later pass,
142 the type may differ from what was seen or requested ealier. See \l {readAccumulator()}.
143 The input type may then need to be converted to the expected type.
144 */
145 QQmlJSRegisterContent accumulatorIn() const
146 {
147 auto it = registers.find(key: Accumulator);
148 Q_ASSERT(it != registers.end());
149 return it.value().content;
150 };
151
152 /*!
153 \internal
154 \brief The accumulatorOut is the output register of the current instruction.
155 */
156 QQmlJSRegisterContent accumulatorOut() const
157 {
158 Q_ASSERT(m_changedRegisterIndex == Accumulator);
159 return m_changedRegister;
160 };
161
162 void setRegister(int registerIndex, QQmlJSRegisterContent content)
163 {
164 const int lookupIndex = content.resultLookupIndex();
165 if (lookupIndex != QQmlJSRegisterContent::InvalidLookupIndex)
166 lookups[lookupIndex] = { .content: content, .canMove: false, .affectedBySideEffects: false };
167
168 m_changedRegister = std::move(content);
169 m_changedRegisterIndex = registerIndex;
170 }
171
172 void clearChangedRegister()
173 {
174 m_changedRegisterIndex = InvalidRegister;
175 m_changedRegister = QQmlJSRegisterContent();
176 }
177
178 int changedRegisterIndex() const { return m_changedRegisterIndex; }
179 QQmlJSRegisterContent changedRegister() const { return m_changedRegister; }
180
181 void addReadRegister(int registerIndex, QQmlJSRegisterContent reg)
182 {
183 const VirtualRegister &source = registers[registerIndex];
184 VirtualRegister &target = m_readRegisters[registerIndex];
185 target.content = reg;
186 target.canMove = source.canMove;
187 target.affectedBySideEffects = source.affectedBySideEffects;
188 }
189
190 void addReadAccumulator(QQmlJSRegisterContent reg)
191 {
192 addReadRegister(registerIndex: Accumulator, reg);
193 }
194
195 VirtualRegisters takeReadRegisters() const { return std::move(m_readRegisters); }
196 void setReadRegisters(VirtualRegisters readReagisters)
197 {
198 m_readRegisters = std::move(readReagisters);
199 }
200
201 QQmlJSRegisterContent readRegister(int registerIndex) const
202 {
203 Q_ASSERT(m_readRegisters.contains(registerIndex));
204 return m_readRegisters[registerIndex].content;
205 }
206
207 bool canMoveReadRegister(int registerIndex) const
208 {
209 auto it = m_readRegisters.find(key: registerIndex);
210 return it != m_readRegisters.end() && it->second.canMove;
211 }
212
213 bool isRegisterAffectedBySideEffects(int registerIndex) const
214 {
215 auto it = m_readRegisters.find(key: registerIndex);
216 return it != m_readRegisters.end() && it->second.affectedBySideEffects;
217 }
218
219 /*!
220 \internal
221 \brief The readAccumulator is the register content expected by the current instruction.
222
223 It may differ from the actual input type of the accumulatorIn register and usage of the
224 value may require a conversion.
225 */
226 QQmlJSRegisterContent readAccumulator() const
227 {
228 return readRegister(registerIndex: Accumulator);
229 }
230
231 bool readsRegister(int registerIndex) const
232 {
233 return m_readRegisters.contains(key: registerIndex);
234 }
235
236 bool hasInternalSideEffects() const { return m_hasInternalSideEffects; }
237 bool hasExternalSideEffects() const { return m_hasExternalSideEffects; }
238
239 void resetSideEffects()
240 {
241 m_hasInternalSideEffects = false;
242 m_hasExternalSideEffects = false;
243 }
244
245 void applyExternalSideEffects(bool hasExternalSideEffects)
246 {
247 if (!hasExternalSideEffects)
248 return;
249
250 for (auto it = registers.begin(), end = registers.end(); it != end; ++it)
251 it.value().affectedBySideEffects = true;
252
253 for (auto it = lookups.begin(), end = lookups.end(); it != end; ++it)
254 it.value().affectedBySideEffects = true;
255 }
256
257 void setHasInternalSideEffects() { m_hasInternalSideEffects = true; }
258 void setHasExternalSideEffects()
259 {
260 m_hasExternalSideEffects = true;
261 m_hasInternalSideEffects = true;
262 applyExternalSideEffects(hasExternalSideEffects: true);
263 }
264
265
266 bool isRename() const { return m_isRename; }
267 void setIsRename(bool isRename) { m_isRename = isRename; }
268
269 bool isShadowable() const { return m_isShadowable; }
270 void setIsShadowable(bool isShadowable) { m_isShadowable = isShadowable; }
271
272 int renameSourceRegisterIndex() const
273 {
274 Q_ASSERT(m_isRename);
275 Q_ASSERT(m_readRegisters.size() == 1);
276 return m_readRegisters.begin().key();
277 }
278
279 void applyAnnotation(const InstructionAnnotation &annotation)
280 {
281 m_readRegisters = annotation.readRegisters;
282
283 m_hasInternalSideEffects = annotation.hasInternalSideEffects;
284 m_hasExternalSideEffects = annotation.hasExternalSideEffects;
285 m_isRename = annotation.isRename;
286 m_isShadowable = annotation.isShadowable;
287
288 for (auto it = annotation.typeConversions.constBegin(),
289 end = annotation.typeConversions.constEnd(); it != end; ++it) {
290 Q_ASSERT(it.key() != InvalidRegister);
291 registers[it.key()] = it.value();
292 }
293
294 if (annotation.changedRegisterIndex != InvalidRegister)
295 setRegister(registerIndex: annotation.changedRegisterIndex, content: annotation.changedRegister);
296 }
297
298 private:
299 VirtualRegisters m_readRegisters;
300 QQmlJSRegisterContent m_changedRegister;
301 int m_changedRegisterIndex = InvalidRegister;
302
303 // If the instruction's value is unused, we still cannot optimize it out.
304 bool m_hasInternalSideEffects = false;
305
306 // Side effect created by calls to other functions or writes to properties,
307 // affects tracked value types and lists. Implies the effects of Internal.
308 bool m_hasExternalSideEffects = false;
309
310 bool m_isRename = false;
311 bool m_isShadowable = false;
312 };
313
314 QQmlJSCompilePass(const QV4::Compiler::JSUnitGenerator *jsUnitGenerator,
315 const QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger,
316 const BasicBlocks &basicBlocks = {},
317 const InstructionAnnotations &annotations = {})
318 : m_jsUnitGenerator(jsUnitGenerator)
319 , m_typeResolver(typeResolver)
320 , m_pool(typeResolver->registerContentPool())
321 , m_logger(logger)
322 , m_basicBlocks(basicBlocks)
323 , m_annotations(annotations)
324 {}
325
326protected:
327 const QV4::Compiler::JSUnitGenerator *m_jsUnitGenerator = nullptr;
328 const QQmlJSTypeResolver *m_typeResolver = nullptr;
329 QQmlJSRegisterContentPool *m_pool = nullptr;
330 QQmlJSLogger *m_logger = nullptr;
331
332 const Function *m_function = nullptr;
333 BasicBlocks m_basicBlocks;
334 InstructionAnnotations m_annotations;
335
336 int firstRegisterIndex() const
337 {
338 return FirstArgument + m_function->argumentTypes.size();
339 }
340
341 bool isArgument(int registerIndex) const
342 {
343 return registerIndex >= FirstArgument && registerIndex < firstRegisterIndex();
344 }
345
346 QQmlJSRegisterContent argumentType(int registerIndex) const
347 {
348 Q_ASSERT(isArgument(registerIndex));
349 return m_function->argumentTypes[registerIndex - FirstArgument];
350 }
351
352 /*!
353 * \internal
354 * Determines whether this is the _QML_ scope object
355 * (in contrast to the JavaScript global or some other scope).
356 *
357 * We omit any module prefixes seen on top of the object.
358 * The module prefixes don't actually add anything unless they
359 * are the prefix to an attachment.
360 */
361 bool isQmlScopeObject(QQmlJSRegisterContent content)
362 {
363 switch (content.variant()) {
364 case QQmlJSRegisterContent::ScopeObject:
365 return content.contains(type: m_function->qmlScope.containedType());
366 case QQmlJSRegisterContent::ModulePrefix:
367 return content.scope().contains(type: m_function->qmlScope.containedType());
368 default:
369 break;
370 }
371
372 return false;
373 }
374
375 State initialState(const Function *function)
376 {
377 State state;
378 for (int i = 0, end = function->argumentTypes.size(); i < end; ++i) {
379 state.registers[FirstArgument + i].content = function->argumentTypes.at(i);
380 Q_ASSERT(state.registers[FirstArgument + i].content.isValid());
381 }
382 for (int i = 0, end = function->registerTypes.size(); i != end; ++i)
383 state.registers[firstRegisterIndex() + i].content = function->registerTypes[i];
384 return state;
385 }
386
387 State nextStateFromAnnotations(
388 const State &oldState, const InstructionAnnotations &annotations)
389 {
390 State newState;
391
392 const auto instruction = annotations.find(key: currentInstructionOffset());
393 newState.registers = oldState.registers;
394 newState.lookups = oldState.lookups;
395
396 // Usually the initial accumulator type is the output of the previous instruction, but ...
397 if (oldState.changedRegisterIndex() != InvalidRegister) {
398 newState.registers[oldState.changedRegisterIndex()].affectedBySideEffects = false;
399 newState.registers[oldState.changedRegisterIndex()].content
400 = oldState.changedRegister();
401 newState.registers[oldState.changedRegisterIndex()].isShadowable
402 = oldState.isShadowable();
403 }
404
405 // Side effects are applied at the end of an instruction: An instruction with side
406 // effects can still read its registers before the side effects happen.
407 newState.applyExternalSideEffects(hasExternalSideEffects: oldState.hasExternalSideEffects());
408
409 if (instruction != annotations.constEnd())
410 newState.applyAnnotation(annotation: instruction->second);
411
412 return newState;
413 }
414
415 QList<SourceLocationTable::Entry>::const_iterator sourceLocationEntry(
416 int instructionOffset) const
417 {
418 Q_ASSERT(m_function);
419 Q_ASSERT(m_function->sourceLocations);
420 const auto &entries = m_function->sourceLocations->entries;
421 const auto entry = std::lower_bound(
422 first: entries.begin(), last: entries.end(), val: instructionOffset,
423 comp: [](auto entry, uint offset) { return entry.offset < offset; });
424 Q_ASSERT(entry != entries.end());
425 return entry;
426 }
427
428 QQmlJS::SourceLocation sourceLocation(int instructionOffset) const
429 {
430 return sourceLocationEntry(instructionOffset)->location;
431 }
432
433 QQmlJS::SourceLocation nonEmptySourceLocation(int instructionOffset) const
434 {
435 auto entry = sourceLocationEntry(instructionOffset);
436
437 // filter out empty locations
438 const auto begin = m_function->sourceLocations->entries.begin();
439 while (entry->location.length == 0 && entry != begin)
440 --entry;
441
442 return entry->location;
443 }
444
445 QQmlJS::SourceLocation currentSourceLocation() const
446 {
447 return sourceLocation(instructionOffset: currentInstructionOffset());
448 }
449
450 QQmlJS::SourceLocation currentNonEmptySourceLocation() const
451 {
452 return nonEmptySourceLocation(instructionOffset: currentInstructionOffset());
453 }
454
455 QQmlJS::SourceLocation currentFunctionSourceLocation() const
456 {
457 Q_ASSERT(m_function->sourceLocations);
458 const auto &entries = m_function->sourceLocations->entries;
459
460 Q_ASSERT(!entries.isEmpty());
461 return combine(l1: entries.constFirst().location, l2: entries.constLast().location);
462 }
463
464 void addError(const QString &message, int instructionOffset)
465 {
466 m_logger->logCompileError(message, srcLocation: sourceLocation(instructionOffset));
467 }
468
469 void addSkip(const QString &message, int instructionOffset)
470 {
471 m_logger->logCompileSkip(message, srcLocation: sourceLocation(instructionOffset));
472 }
473
474 void addError(const QString &message)
475 {
476 addError(message, instructionOffset: currentInstructionOffset());
477 }
478
479 void addSkip(const QString &message)
480 {
481 addSkip(message, instructionOffset: currentInstructionOffset());
482 }
483
484 static bool instructionManipulatesContext(QV4::Moth::Instr::Type type)
485 {
486 using Type = QV4::Moth::Instr::Type;
487 switch (type) {
488 case Type::PopContext:
489 case Type::PopScriptContext:
490 case Type::CreateCallContext:
491 case Type::CreateCallContext_Wide:
492 case Type::PushCatchContext:
493 case Type::PushCatchContext_Wide:
494 case Type::PushWithContext:
495 case Type::PushWithContext_Wide:
496 case Type::PushBlockContext:
497 case Type::PushBlockContext_Wide:
498 case Type::CloneBlockContext:
499 case Type::CloneBlockContext_Wide:
500 case Type::PushScriptContext:
501 case Type::PushScriptContext_Wide:
502 return true;
503 default:
504 break;
505 }
506 return false;
507 }
508
509 // Stub out all the methods so that passes can choose to only implement part of them.
510 void generate_Add(int) override {}
511 void generate_As(int) override {}
512 void generate_BitAnd(int) override {}
513 void generate_BitAndConst(int) override {}
514 void generate_BitOr(int) override {}
515 void generate_BitOrConst(int) override {}
516 void generate_BitXor(int) override {}
517 void generate_BitXorConst(int) override {}
518 void generate_CallGlobalLookup(int, int, int) override {}
519 void generate_CallName(int, int, int) override {}
520 void generate_CallPossiblyDirectEval(int, int) override {}
521 void generate_CallProperty(int, int, int, int) override {}
522 void generate_CallPropertyLookup(int, int, int, int) override {}
523 void generate_CallQmlContextPropertyLookup(int, int, int) override {}
524 void generate_CallValue(int, int, int) override {}
525 void generate_CallWithReceiver(int, int, int, int) override {}
526 void generate_CallWithSpread(int, int, int, int) override {}
527 void generate_CheckException() override {}
528 void generate_CloneBlockContext() override {}
529 void generate_CmpEq(int) override {}
530 void generate_CmpEqInt(int) override {}
531 void generate_CmpEqNull() override {}
532 void generate_CmpGe(int) override {}
533 void generate_CmpGt(int) override {}
534 void generate_CmpIn(int) override {}
535 void generate_CmpInstanceOf(int) override {}
536 void generate_CmpLe(int) override {}
537 void generate_CmpLt(int) override {}
538 void generate_CmpNe(int) override {}
539 void generate_CmpNeInt(int) override {}
540 void generate_CmpNeNull() override {}
541 void generate_CmpStrictEqual(int) override {}
542 void generate_CmpStrictNotEqual(int) override {}
543 void generate_Construct(int, int, int) override {}
544 void generate_ConstructWithSpread(int, int, int) override {}
545 void generate_ConvertThisToObject() override {}
546 void generate_CreateCallContext() override {}
547 void generate_CreateClass(int, int, int) override {}
548 void generate_CreateMappedArgumentsObject() override {}
549 void generate_CreateRestParameter(int) override {}
550 void generate_CreateUnmappedArgumentsObject() override {}
551 void generate_DeadTemporalZoneCheck(int) override {}
552 void generate_Debug() override {}
553 void generate_DeclareVar(int, int) override {}
554 void generate_Decrement() override {}
555 void generate_DefineArray(int, int) override {}
556 void generate_DefineObjectLiteral(int, int, int) override {}
557 void generate_DeleteName(int) override {}
558 void generate_DeleteProperty(int, int) override {}
559 void generate_DestructureRestElement() override {}
560 void generate_Div(int) override {}
561 void generate_Exp(int) override {}
562 void generate_GetException() override {}
563 void generate_GetIterator(int) override {}
564 void generate_GetLookup(int) override {}
565 void generate_GetOptionalLookup(int, int) override {}
566 void generate_GetTemplateObject(int) override {}
567 void generate_Increment() override {}
568 void generate_InitializeBlockDeadTemporalZone(int, int) override {}
569 void generate_IteratorClose() override {}
570 void generate_IteratorNext(int, int) override {}
571 void generate_IteratorNextForYieldStar(int, int, int) override {}
572 void generate_Jump(int) override {}
573 void generate_JumpFalse(int) override {}
574 void generate_JumpNoException(int) override {}
575 void generate_JumpNotUndefined(int) override {}
576 void generate_JumpTrue(int) override {}
577 void generate_LoadClosure(int) override {}
578 void generate_LoadConst(int) override {}
579 void generate_LoadElement(int) override {}
580 void generate_LoadFalse() override {}
581 void generate_LoadGlobalLookup(int) override {}
582 void generate_LoadImport(int) override {}
583 void generate_LoadInt(int) override {}
584 void generate_LoadLocal(int) override {}
585 void generate_LoadName(int) override {}
586 void generate_LoadNull() override {}
587 void generate_LoadOptionalProperty(int, int) override {}
588 void generate_LoadProperty(int) override {}
589 void generate_LoadQmlContextPropertyLookup(int) override {}
590 void generate_LoadReg(int) override {}
591 void generate_LoadRuntimeString(int) override {}
592 void generate_LoadScopedLocal(int, int) override {}
593 void generate_LoadSuperConstructor() override {}
594 void generate_LoadSuperProperty(int) override {}
595 void generate_LoadTrue() override {}
596 void generate_LoadUndefined() override {}
597 void generate_LoadZero() override {}
598 void generate_Mod(int) override {}
599 void generate_MoveConst(int, int) override {}
600 void generate_MoveReg(int, int) override {}
601 void generate_MoveRegExp(int, int) override {}
602 void generate_Mul(int) override {}
603 void generate_PopContext() override {}
604 void generate_PopScriptContext() override {}
605 void generate_PushBlockContext(int) override {}
606 void generate_PushCatchContext(int, int) override {}
607 void generate_PushScriptContext(int) override {}
608 void generate_PushWithContext() override {}
609 void generate_Resume(int) override {}
610 void generate_Ret() override {}
611 void generate_SetException() override {}
612 void generate_SetLookup(int, int) override {}
613 void generate_SetUnwindHandler(int) override {}
614 void generate_Shl(int) override {}
615 void generate_ShlConst(int) override {}
616 void generate_Shr(int) override {}
617 void generate_ShrConst(int) override {}
618 void generate_StoreElement(int, int) override {}
619 void generate_StoreLocal(int) override {}
620 void generate_StoreNameSloppy(int) override {}
621 void generate_StoreNameStrict(int) override {}
622 void generate_StoreProperty(int, int) override {}
623 void generate_StoreReg(int) override {}
624 void generate_StoreScopedLocal(int, int) override {}
625 void generate_StoreSuperProperty(int) override {}
626 void generate_Sub(int) override {}
627 void generate_TailCall(int, int, int, int) override {}
628 void generate_ThrowException() override {}
629 void generate_ThrowOnNullOrUndefined() override {}
630 void generate_ToObject() override {}
631 void generate_TypeofName(int) override {}
632 void generate_TypeofValue() override {}
633 void generate_UCompl() override {}
634 void generate_UMinus() override {}
635 void generate_UNot() override {}
636 void generate_UPlus() override {}
637 void generate_UShr(int) override {}
638 void generate_UShrConst(int) override {}
639 void generate_UnwindDispatch() override {}
640 void generate_UnwindToLabel(int, int) override {}
641 void generate_Yield() override {}
642 void generate_YieldStar() override {}
643};
644
645QT_END_NAMESPACE
646
647#endif // QQMLJSCOMPILEPASS_P_H
648

source code of qtdeclarative/src/qmlcompiler/qqmljscompilepass_p.h