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 const VirtualRegister &source = registers[registerIndex];
185 VirtualRegister &target = m_readRegisters[registerIndex];
186 target.content = reg;
187 target.canMove = source.canMove;
188 target.affectedBySideEffects = source.affectedBySideEffects;
189 }
190
191 void addReadAccumulator(QQmlJSRegisterContent reg)
192 {
193 addReadRegister(Accumulator, reg);
194 }
195
196 VirtualRegisters takeReadRegisters() const { return std::move(m_readRegisters); }
197 void setReadRegisters(VirtualRegisters readReagisters)
198 {
199 m_readRegisters = std::move(readReagisters);
200 }
201
202 QQmlJSRegisterContent readRegister(int registerIndex) const
203 {
204 Q_ASSERT(m_readRegisters.contains(registerIndex));
205 return m_readRegisters[registerIndex].content;
206 }
207
208 bool canMoveReadRegister(int registerIndex) const
209 {
210 auto it = m_readRegisters.find(registerIndex);
211 return it != m_readRegisters.end() && it->second.canMove;
212 }
213
214 bool isRegisterAffectedBySideEffects(int registerIndex) const
215 {
216 auto it = m_readRegisters.find(registerIndex);
217 return it != m_readRegisters.end() && it->second.affectedBySideEffects;
218 }
219
220 /*!
221 \internal
222 \brief The readAccumulator is the register content expected by the current instruction.
223
224 It may differ from the actual input type of the accumulatorIn register and usage of the
225 value may require a conversion.
226 */
227 QQmlJSRegisterContent readAccumulator() const
228 {
229 return readRegister(Accumulator);
230 }
231
232 bool readsRegister(int registerIndex) const
233 {
234 return m_readRegisters.contains(registerIndex);
235 }
236
237 bool hasInternalSideEffects() const { return m_hasInternalSideEffects; }
238 bool hasExternalSideEffects() const { return m_hasExternalSideEffects; }
239
240 void resetSideEffects()
241 {
242 m_hasInternalSideEffects = false;
243 m_hasExternalSideEffects = false;
244 }
245
246 void applyExternalSideEffects(bool hasExternalSideEffects)
247 {
248 if (!hasExternalSideEffects)
249 return;
250
251 for (auto it = registers.begin(), end = registers.end(); it != end; ++it)
252 it.value().affectedBySideEffects = true;
253
254 for (auto it = lookups.begin(), end = lookups.end(); it != end; ++it)
255 it.value().affectedBySideEffects = true;
256 }
257
258 void setHasInternalSideEffects() { m_hasInternalSideEffects = true; }
259 void setHasExternalSideEffects()
260 {
261 m_hasExternalSideEffects = true;
262 m_hasInternalSideEffects = true;
263 applyExternalSideEffects(true);
264 }
265
266
267 bool isRename() const { return m_isRename; }
268 void setIsRename(bool isRename) { m_isRename = isRename; }
269
270 bool isShadowable() const { return m_isShadowable; }
271 void setIsShadowable(bool isShadowable) { m_isShadowable = isShadowable; }
272
273 int renameSourceRegisterIndex() const
274 {
275 Q_ASSERT(m_isRename);
276 Q_ASSERT(m_readRegisters.size() == 1);
277 return m_readRegisters.begin().key();
278 }
279
280 void applyAnnotation(const InstructionAnnotation &annotation)
281 {
282 m_readRegisters = annotation.readRegisters;
283
284 m_hasInternalSideEffects = annotation.hasInternalSideEffects;
285 m_hasExternalSideEffects = annotation.hasExternalSideEffects;
286 m_isRename = annotation.isRename;
287 m_isShadowable = annotation.isShadowable;
288
289 for (auto it = annotation.typeConversions.constBegin(),
290 end = annotation.typeConversions.constEnd(); it != end; ++it) {
291 Q_ASSERT(it.key() != InvalidRegister);
292 registers[it.key()] = it.value();
293 }
294
295 if (annotation.changedRegisterIndex != InvalidRegister)
296 setRegister(annotation.changedRegisterIndex, annotation.changedRegister);
297 }
298
299 private:
300 VirtualRegisters m_readRegisters;
301 QQmlJSRegisterContent m_changedRegister;
302 int m_changedRegisterIndex = InvalidRegister;
303
304 // If the instruction's value is unused, we still cannot optimize it out.
305 bool m_hasInternalSideEffects = false;
306
307 // Side effect created by calls to other functions or writes to properties,
308 // affects tracked value types and lists. Implies the effects of Internal.
309 bool m_hasExternalSideEffects = false;
310
311 bool m_isRename = false;
312 bool m_isShadowable = false;
313 };
314
315 QQmlJSCompilePass(const QV4::Compiler::JSUnitGenerator *jsUnitGenerator,
316 const QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger,
317 const BasicBlocks &basicBlocks = {},
318 const InstructionAnnotations &annotations = {})
319 : m_jsUnitGenerator(jsUnitGenerator)
320 , m_typeResolver(typeResolver)
321 , m_pool(typeResolver->registerContentPool())
322 , m_logger(logger)
323 , m_basicBlocks(basicBlocks)
324 , m_annotations(annotations)
325 {}
326
327protected:
328 const QV4::Compiler::JSUnitGenerator *m_jsUnitGenerator = nullptr;
329 const QQmlJSTypeResolver *m_typeResolver = nullptr;
330 QQmlJSRegisterContentPool *m_pool = nullptr;
331 QQmlJSLogger *m_logger = nullptr;
332
333 const Function *m_function = nullptr;
334 BasicBlocks m_basicBlocks;
335 InstructionAnnotations m_annotations;
336
337 int firstRegisterIndex() const
338 {
339 return FirstArgument + m_function->argumentTypes.size();
340 }
341
342 bool isArgument(int registerIndex) const
343 {
344 return registerIndex >= FirstArgument && registerIndex < firstRegisterIndex();
345 }
346
347 QQmlJSRegisterContent argumentType(int registerIndex) const
348 {
349 Q_ASSERT(isArgument(registerIndex));
350 return m_function->argumentTypes[registerIndex - FirstArgument];
351 }
352
353 /*!
354 * \internal
355 * Determines whether this is the _QML_ scope object
356 * (in contrast to the JavaScript global or some other scope).
357 *
358 * We omit any module prefixes seen on top of the object.
359 * The module prefixes don't actually add anything unless they
360 * are the prefix to an attachment.
361 */
362 bool isQmlScopeObject(QQmlJSRegisterContent content)
363 {
364 switch (content.variant()) {
365 case QQmlJSRegisterContent::ScopeObject:
366 return content.contains(m_function->qmlScope.containedType());
367 case QQmlJSRegisterContent::ModulePrefix:
368 return content.scope().contains(m_function->qmlScope.containedType());
369 default:
370 break;
371 }
372
373 return false;
374 }
375
376 State initialState(const Function *function)
377 {
378 State state;
379 for (int i = 0, end = function->argumentTypes.size(); i < end; ++i) {
380 state.registers[FirstArgument + i].content = function->argumentTypes.at(i);
381 Q_ASSERT(state.registers[FirstArgument + i].content.isValid());
382 }
383 for (int i = 0, end = function->registerTypes.size(); i != end; ++i)
384 state.registers[firstRegisterIndex() + i].content = function->registerTypes[i];
385 return state;
386 }
387
388 State nextStateFromAnnotations(
389 const State &oldState, const InstructionAnnotations &annotations)
390 {
391 State newState;
392
393 const auto instruction = annotations.find(currentInstructionOffset());
394 newState.registers = oldState.registers;
395 newState.lookups = oldState.lookups;
396
397 // Usually the initial accumulator type is the output of the previous instruction, but ...
398 if (oldState.changedRegisterIndex() != InvalidRegister) {
399 newState.registers[oldState.changedRegisterIndex()].affectedBySideEffects = false;
400 newState.registers[oldState.changedRegisterIndex()].content
401 = oldState.changedRegister();
402 newState.registers[oldState.changedRegisterIndex()].isShadowable
403 = oldState.isShadowable();
404 }
405
406 // Side effects are applied at the end of an instruction: An instruction with side
407 // effects can still read its registers before the side effects happen.
408 newState.applyExternalSideEffects(oldState.hasExternalSideEffects());
409
410 if (instruction != annotations.constEnd())
411 newState.applyAnnotation(instruction->second);
412
413 return newState;
414 }
415
416 QList<SourceLocationTable::Entry>::const_iterator sourceLocationEntry(
417 int instructionOffset) const
418 {
419 Q_ASSERT(m_function);
420 Q_ASSERT(m_function->sourceLocations);
421 const auto &entries = m_function->sourceLocations->entries;
422 const auto entry = std::lower_bound(
423 entries.begin(), entries.end(), instructionOffset,
424 [](auto entry, uint offset) { return entry.offset < offset; });
425 Q_ASSERT(entry != entries.end());
426 return entry;
427 }
428
429 QQmlJS::SourceLocation sourceLocation(int instructionOffset) const
430 {
431 return sourceLocationEntry(instructionOffset)->location;
432 }
433
434 QQmlJS::SourceLocation nonEmptySourceLocation(int instructionOffset) const
435 {
436 auto entry = sourceLocationEntry(instructionOffset);
437
438 // filter out empty locations
439 const auto begin = m_function->sourceLocations->entries.begin();
440 while (entry->location.length == 0 && entry != begin)
441 --entry;
442
443 return entry->location;
444 }
445
446 QQmlJS::SourceLocation currentSourceLocation() const
447 {
448 return sourceLocation(currentInstructionOffset());
449 }
450
451 QQmlJS::SourceLocation currentNonEmptySourceLocation() const
452 {
453 return nonEmptySourceLocation(currentInstructionOffset());
454 }
455
456 QQmlJS::SourceLocation currentFunctionSourceLocation() const
457 {
458 Q_ASSERT(m_function->sourceLocations);
459 const auto &entries = m_function->sourceLocations->entries;
460
461 Q_ASSERT(!entries.isEmpty());
462 return combine(entries.constFirst().location, entries.constLast().location);
463 }
464
465 void addError(const QString &message, int instructionOffset)
466 {
467 m_logger->logCompileError(message, sourceLocation(instructionOffset));
468 }
469
470 void addSkip(const QString &message, int instructionOffset)
471 {
472 m_logger->logCompileSkip(message, sourceLocation(instructionOffset));
473 }
474
475 void addError(const QString &message)
476 {
477 addError(message, currentInstructionOffset());
478 }
479
480 void addSkip(const QString &message)
481 {
482 addSkip(message, currentInstructionOffset());
483 }
484
485 static bool instructionManipulatesContext(QV4::Moth::Instr::Type type)
486 {
487 using Type = QV4::Moth::Instr::Type;
488 switch (type) {
489 case Type::PopContext:
490 case Type::PopScriptContext:
491 case Type::CreateCallContext:
492 case Type::CreateCallContext_Wide:
493 case Type::PushCatchContext:
494 case Type::PushCatchContext_Wide:
495 case Type::PushWithContext:
496 case Type::PushWithContext_Wide:
497 case Type::PushBlockContext:
498 case Type::PushBlockContext_Wide:
499 case Type::CloneBlockContext:
500 case Type::CloneBlockContext_Wide:
501 case Type::PushScriptContext:
502 case Type::PushScriptContext_Wide:
503 return true;
504 default:
505 break;
506 }
507 return false;
508 }
509
510 // Stub out all the methods so that passes can choose to only implement part of them.
511 void generate_Add(int) override {}
512 void generate_As(int) override {}
513 void generate_BitAnd(int) override {}
514 void generate_BitAndConst(int) override {}
515 void generate_BitOr(int) override {}
516 void generate_BitOrConst(int) override {}
517 void generate_BitXor(int) override {}
518 void generate_BitXorConst(int) override {}
519 void generate_CallGlobalLookup(int, int, int) override {}
520 void generate_CallName(int, int, int) override {}
521 void generate_CallPossiblyDirectEval(int, int) override {}
522 void generate_CallProperty(int, int, int, int) override {}
523 void generate_CallPropertyLookup(int, int, int, int) override {}
524 void generate_CallQmlContextPropertyLookup(int, int, int) override {}
525 void generate_CallValue(int, int, int) override {}
526 void generate_CallWithReceiver(int, int, int, int) override {}
527 void generate_CallWithSpread(int, int, int, int) override {}
528 void generate_CheckException() override {}
529 void generate_CloneBlockContext() override {}
530 void generate_CmpEq(int) override {}
531 void generate_CmpEqInt(int) override {}
532 void generate_CmpEqNull() override {}
533 void generate_CmpGe(int) override {}
534 void generate_CmpGt(int) override {}
535 void generate_CmpIn(int) override {}
536 void generate_CmpInstanceOf(int) override {}
537 void generate_CmpLe(int) override {}
538 void generate_CmpLt(int) override {}
539 void generate_CmpNe(int) override {}
540 void generate_CmpNeInt(int) override {}
541 void generate_CmpNeNull() override {}
542 void generate_CmpStrictEqual(int) override {}
543 void generate_CmpStrictNotEqual(int) override {}
544 void generate_Construct(int, int, int) override {}
545 void generate_ConstructWithSpread(int, int, int) override {}
546 void generate_ConvertThisToObject() override {}
547 void generate_CreateCallContext() override {}
548 void generate_CreateClass(int, int, int) override {}
549 void generate_CreateMappedArgumentsObject() override {}
550 void generate_CreateRestParameter(int) override {}
551 void generate_CreateUnmappedArgumentsObject() override {}
552 void generate_DeadTemporalZoneCheck(int) override {}
553 void generate_Debug() override {}
554 void generate_DeclareVar(int, int) override {}
555 void generate_Decrement() override {}
556 void generate_DefineArray(int, int) override {}
557 void generate_DefineObjectLiteral(int, int, int) override {}
558 void generate_DeleteName(int) override {}
559 void generate_DeleteProperty(int, int) override {}
560 void generate_DestructureRestElement() override {}
561 void generate_Div(int) override {}
562 void generate_Exp(int) override {}
563 void generate_GetException() override {}
564 void generate_GetIterator(int) override {}
565 void generate_GetLookup(int) override {}
566 void generate_GetOptionalLookup(int, int) override {}
567 void generate_GetTemplateObject(int) override {}
568 void generate_Increment() override {}
569 void generate_InitializeBlockDeadTemporalZone(int, int) override {}
570 void generate_IteratorClose() override {}
571 void generate_IteratorNext(int, int) override {}
572 void generate_IteratorNextForYieldStar(int, int, int) override {}
573 void generate_Jump(int) override {}
574 void generate_JumpFalse(int) override {}
575 void generate_JumpNoException(int) override {}
576 void generate_JumpNotUndefined(int) override {}
577 void generate_JumpTrue(int) override {}
578 void generate_LoadClosure(int) override {}
579 void generate_LoadConst(int) override {}
580 void generate_LoadElement(int) override {}
581 void generate_LoadFalse() override {}
582 void generate_LoadGlobalLookup(int) override {}
583 void generate_LoadImport(int) override {}
584 void generate_LoadInt(int) override {}
585 void generate_LoadLocal(int) override {}
586 void generate_LoadName(int) override {}
587 void generate_LoadNull() override {}
588 void generate_LoadOptionalProperty(int, int) override {}
589 void generate_LoadProperty(int) override {}
590 void generate_LoadQmlContextPropertyLookup(int) override {}
591 void generate_LoadReg(int) override {}
592 void generate_LoadRuntimeString(int) override {}
593 void generate_LoadScopedLocal(int, int) override {}
594 void generate_LoadSuperConstructor() override {}
595 void generate_LoadSuperProperty(int) override {}
596 void generate_LoadTrue() override {}
597 void generate_LoadUndefined() override {}
598 void generate_LoadZero() override {}
599 void generate_Mod(int) override {}
600 void generate_MoveConst(int, int) override {}
601 void generate_MoveReg(int, int) override {}
602 void generate_MoveRegExp(int, int) override {}
603 void generate_Mul(int) override {}
604 void generate_PopContext() override {}
605 void generate_PopScriptContext() override {}
606 void generate_PushBlockContext(int) override {}
607 void generate_PushCatchContext(int, int) override {}
608 void generate_PushScriptContext(int) override {}
609 void generate_PushWithContext() override {}
610 void generate_Resume(int) override {}
611 void generate_Ret() override {}
612 void generate_SetException() override {}
613 void generate_SetLookup(int, int) override {}
614 void generate_SetUnwindHandler(int) override {}
615 void generate_Shl(int) override {}
616 void generate_ShlConst(int) override {}
617 void generate_Shr(int) override {}
618 void generate_ShrConst(int) override {}
619 void generate_StoreElement(int, int) override {}
620 void generate_StoreLocal(int) override {}
621 void generate_StoreNameSloppy(int) override {}
622 void generate_StoreNameStrict(int) override {}
623 void generate_StoreProperty(int, int) override {}
624 void generate_StoreReg(int) override {}
625 void generate_StoreScopedLocal(int, int) override {}
626 void generate_StoreSuperProperty(int) override {}
627 void generate_Sub(int) override {}
628 void generate_TailCall(int, int, int, int) override {}
629 void generate_ThrowException() override {}
630 void generate_ThrowOnNullOrUndefined() override {}
631 void generate_ToObject() override {}
632 void generate_TypeofName(int) override {}
633 void generate_TypeofValue() override {}
634 void generate_UCompl() override {}
635 void generate_UMinus() override {}
636 void generate_UNot() override {}
637 void generate_UPlus() override {}
638 void generate_UShr(int) override {}
639 void generate_UShrConst(int) override {}
640 void generate_UnwindDispatch() override {}
641 void generate_UnwindToLabel(int, int) override {}
642 void generate_Yield() override {}
643 void generate_YieldStar() override {}
644};
645
646QT_END_NAMESPACE
647
648#endif // QQMLJSCOMPILEPASS_P_H