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