9#include <QtCore/qhash.h>
13using namespace Qt::Literals::StringLiterals;
15QQmlJSCompilePass::BlocksAndAnnotations QQmlJSOptimizations::run(
const Function *function)
17 m_function = function;
19 populateBasicBlocks();
20 populateReaderLocationsTypeReaders();
21 populateReaderLocationsConversions();
22 removeDeadStoresUntilStable();
25 return { std::move(m_basicBlocks), std::move(m_annotations) };
34template<
typename ContainerA,
typename ContainerB>
35static bool containsAny(
const ContainerA &container,
const ContainerB &elements)
37 for (
const auto &element : elements) {
38 if (container.contains(element))
44template<
class Key,
class T,
class Compare = std::less<Key>,
45 class KeyContainer = QList<Key>,
class MappedContainer = QList<T>>
54 values.append(i.value());
59 OriginalFlatMap result(Qt::OrderedUniqueRange, std::move(keys), std::move(values));
66 typename OriginalFlatMap::key_container_type keys;
67 typename OriginalFlatMap::mapped_container_type values;
70void QQmlJSOptimizations::populateReaderLocationsTypeReaders()
73 QMultiHash<QQmlJSRegisterContent,
int> trackedBy;
75 for (
const auto &[offset, annotation] : m_annotations) {
76 const int writtenRegister = annotation.changedRegisterIndex;
79 if (writtenRegister == InvalidRegister)
82 RegisterAccess &access = m_readerLocations[offset];
83 access.trackedRegister = writtenRegister;
84 if (annotation.changedRegister.isConversion()) {
88 const auto &origins = annotation.changedRegister.conversionOrigins();
89 for (QQmlJSRegisterContent origin : origins)
90 access.trackedTypes.append(origin);
92 access.trackedTypes.append(annotation.changedRegister);
93 Q_ASSERT(!access.trackedTypes.last().isNull());
96 for (
const QQmlJSRegisterContent &tracked : std::as_const(access.trackedTypes))
97 trackedBy.insert(tracked, offset);
102 for (
const auto &[offset, annotation] : m_annotations) {
103 if (annotation.isRename)
106 for (
auto readIt = annotation.readRegisters.constBegin(),
107 readEnd = annotation.readRegisters.constEnd(); readIt != readEnd; ++readIt) {
108 const QQmlJSRegisterContent &content = readIt->second.content;
110 if (content.isConversion()) {
111 Q_ASSERT(content.conversionResultType());
112 const QList<QQmlJSRegisterContent> &conversionOrigins = content.conversionOrigins();
113 for (QQmlJSRegisterContent origin : conversionOrigins) {
114 for (
auto [it, last] = trackedBy.equal_range(origin); it != last; ++it)
115 m_readerLocations[it.value()].typeReaders[offset] = content;
118 for (
auto [it, last] = trackedBy.equal_range(content); it != last; ++it)
119 m_readerLocations[it.value()].typeReaders[offset] = content;
125void QQmlJSOptimizations::populateReaderLocationsConversions()
130 for (
auto writeIt = m_annotations.cbegin(), writeEnd = m_annotations.cend();
131 writeIt != writeEnd; ++writeIt) {
132 const int writtenRegister = writeIt->second.changedRegisterIndex;
133 if (writtenRegister == InvalidRegister)
136 RegisterAccess &access = m_readerLocations[writeIt.key()];
138 const auto blockIt = QQmlJSBasicBlocks::constBasicBlockForInstruction(m_basicBlocks,
140 std::vector<PendingBlock> blocks = { { {}, blockIt->first } };
141 std::unordered_map<
int, PendingBlock> processedBlocks;
142 bool isFirstBlock =
true;
144 while (!blocks.empty()) {
145 const PendingBlock &block = blocks.back();
150 processedBlocks.insert_or_assign(block.start, block);
152 auto nextBlock = m_basicBlocks.find(block.start);
153 const auto currentBlock = nextBlock++;
154 bool registerActive =
true;
155 Conversions conversions = block.conversions;
158 const auto blockEnd = (nextBlock == m_basicBlocks.end())
159 ? m_annotations.cend()
160 : m_annotations.find(nextBlock->first);
162 auto blockInstr = isFirstBlock
164 : m_annotations.find(currentBlock->first);
165 for (; registerActive && blockInstr != blockEnd; ++blockInstr) {
166 if (blockInstr->second.typeConversions.contains(writtenRegister))
167 conversions.insert(blockInstr.key());
169 for (
auto readIt = blockInstr->second.readRegisters.constBegin(),
170 end = blockInstr->second.readRegisters.constEnd();
171 readIt != end; ++readIt) {
172 if (readIt->first == writtenRegister)
173 access.registerReadersAndConversions[blockInstr.key()] = conversions;
176 if (blockInstr->second.changedRegisterIndex == writtenRegister) {
178 registerActive =
false;
183 isFirstBlock =
false;
190 auto scheduleBlock = [&](
int blockStart) {
191 const auto processed = processedBlocks.find(blockStart);
192 if (processed == processedBlocks.end()) {
193 blocks.push_back({ conversions, blockStart });
197 Conversions merged = processed->second.conversions;
198 merged.unite(conversions);
199 if (merged.size() > processed->second.conversions.size())
200 blocks.push_back({ std::move(merged), blockStart });
203 if (!currentBlock->second.jumpIsUnconditional && nextBlock != m_basicBlocks.end())
204 scheduleBlock(nextBlock->first);
206 const int jumpTarget = currentBlock->second.jumpTarget;
207 if (jumpTarget != -1)
208 scheduleBlock(jumpTarget);
213bool QQmlJSOptimizations::eraseDeadStore(
const InstructionAnnotations::iterator &it,
216 auto reader = m_readerLocations.find(it.key());
217 if (reader != m_readerLocations.end()
218 && (reader->typeReaders.isEmpty() || reader->registerReadersAndConversions.isEmpty())) {
220 if (it->second.isRename) {
225 it->second.changedRegisterIndex = InvalidRegister;
226 it->second.changedRegister = QQmlJSRegisterContent();
235 const QQmlJSScope::ConstPtr contained = it->second.changedRegister.containedType();
236 if (!it->second.hasExternalSideEffects
237 || (!contained->isReferenceType()
238 && !m_typeResolver->canHold(contained, m_typeResolver->qObjectType()))) {
240 const bool adjusted = m_typeResolver->adjustTrackedType(
241 it->second.changedRegister, m_typeResolver->voidType());
245 m_readerLocations.erase(reader);
248 if (!it->second.hasInternalSideEffects) {
249 if (!it->second.readRegisters.isEmpty()) {
250 it->second.readRegisters.clear();
251 erasedReaders =
true;
253 if (m_basicBlocks.find(it.key()) == m_basicBlocks.end())
260void QQmlJSOptimizations::removeDeadStoresUntilStable()
262 using NewInstructionAnnotations = NewFlatMap<
int, InstructionAnnotation>;
263 NewInstructionAnnotations newAnnotations;
265 bool erasedReaders =
true;
266 while (erasedReaders) {
267 erasedReaders =
false;
269 for (
auto it = m_annotations.begin(), end = m_annotations.end(); it != end; ++it) {
270 InstructionAnnotation &instruction = it->second;
273 if (instruction.changedRegisterIndex < InvalidRegister) {
274 newAnnotations.appendOrdered(it);
278 removeReadsFromErasedInstructions(it);
280 if (!eraseDeadStore(it, erasedReaders))
281 newAnnotations.appendOrdered(it);
284 m_annotations = newAnnotations.take();
288void QQmlJSOptimizations::removeReadsFromErasedInstructions(
289 const QFlatMap<
int, InstructionAnnotation>::const_iterator &it)
291 auto readers = m_readerLocations.find(it.key());
292 if (readers == m_readerLocations.end())
295 for (
auto typeIt = readers->typeReaders.begin(); typeIt != readers->typeReaders.end();) {
296 if (m_annotations.contains(typeIt.key()))
299 typeIt = readers->typeReaders.erase(typeIt);
302 for (
auto registerIt = readers->registerReadersAndConversions.begin();
303 registerIt != readers->registerReadersAndConversions.end();) {
304 if (m_annotations.contains(registerIt.key()))
307 registerIt = readers->registerReadersAndConversions.erase(registerIt);
311bool QQmlJSOptimizations::canMove(
int instructionOffset,
312 const QQmlJSOptimizations::RegisterAccess &access)
const
314 if (access.typeReaders.size() != 1)
316 return QQmlJSBasicBlocks::constBasicBlockForInstruction(m_basicBlocks, instructionOffset)
317 == QQmlJSBasicBlocks::constBasicBlockForInstruction(m_basicBlocks, access.typeReaders.begin().key());
320QList<QQmlJSCompilePass::ObjectOrArrayDefinition>
321QQmlJSBasicBlocks::objectAndArrayDefinitions()
const
323 return m_objectAndArrayDefinitions;
327 QQmlJSRegisterContent origin,
const QQmlJSScope::ConstPtr &conversion) {
328 return QLatin1String(
"Cannot convert from ")
329 + origin.containedType()->internalName() + QLatin1String(
" to ")
330 + conversion->internalName();
334 QQmlJSRegisterContent origin, QQmlJSRegisterContent conversion) {
335 return adjustErrorMessage(origin, conversion.containedType());
339 QQmlJSRegisterContent origin,
const QList<QQmlJSRegisterContent> &conversions) {
340 if (conversions.size() == 1)
341 return adjustErrorMessage(origin, conversions[0]);
344 for (QQmlJSRegisterContent type : conversions) {
345 if (!types.isEmpty())
346 types += QLatin1String(
", ");
347 types += type.containedType()->internalName();
349 return QLatin1String(
"Cannot convert from ")
350 + origin.containedType()->internalName() + QLatin1String(
" to union of ") + types;
353void QQmlJSOptimizations::adjustTypes()
355 using NewVirtualRegisters = NewFlatMap<
int, VirtualRegister>;
357 QHash<
int, QList<
int>> liveConversions;
358 QHash<
int, QList<
int>> movableReads;
360 const auto handleRegisterReadersAndConversions
361 = [&](QHash<
int, RegisterAccess>::const_iterator it) {
362 for (
auto conversions = it->registerReadersAndConversions.constBegin(),
363 end = it->registerReadersAndConversions.constEnd(); conversions != end;
365 if (conversions->isEmpty() && canMove(it.key(), it.value()))
366 movableReads[conversions.key()].append(it->trackedRegister);
367 for (
int conversion : *conversions)
368 liveConversions[conversion].append(it->trackedRegister);
374 auto adjustArray = [&](
int instructionOffset,
int mode) {
375 auto it = m_readerLocations.constFind(instructionOffset);
376 if (it == m_readerLocations.cend())
379 const InstructionAnnotation &annotation = m_annotations[instructionOffset];
380 if (annotation.readRegisters.isEmpty())
383 Q_ASSERT(it->trackedTypes.size() == 1);
384 Q_ASSERT(it->trackedTypes[0] == annotation.changedRegister);
386 if (it->trackedTypes[0].containedType()->accessSemantics()
387 != QQmlJSScope::AccessSemantics::Sequence) {
391 if (!m_typeResolver->adjustTrackedType(it->trackedTypes[0], it->typeReaders.values()))
392 addError(adjustErrorMessage(it->trackedTypes[0], it->typeReaders.values()));
397 if (
const QQmlJSScope::ConstPtr elementType
398 = it->trackedTypes[0].containedType()->elementType()) {
399 const auto adjust = [&](
const auto it) {
400 const QQmlJSRegisterContent content = it.value().content;
401 const QQmlJSScope::ConstPtr contained = content.containedType();
402 if (!m_typeResolver->adjustTrackedType(content, elementType)) {
403 addError(adjustErrorMessage(content, elementType));
409 const auto &readRegisters = annotation.readRegisters;
410 if (mode == ObjectOrArrayDefinition::ArrayConstruct1ArgId) {
411 Q_ASSERT(readRegisters.size() == 1);
412 const auto it = readRegisters.cbegin();
413 if (it.value().content.containedType() != m_typeResolver->realType())
416 for (
auto it = readRegisters.cbegin(); it != readRegisters.cend(); ++it) {
423 handleRegisterReadersAndConversions(it);
424 m_readerLocations.erase(it);
429 const auto adjustObject = [&](
const ObjectOrArrayDefinition &object) {
430 auto it = m_readerLocations.find(object.instructionOffset);
431 if (it == m_readerLocations.end())
434 const InstructionAnnotation &annotation = m_annotations[object.instructionOffset];
436 Q_ASSERT(it->trackedTypes.size() == 1);
437 const QQmlJSRegisterContent resultType = it->trackedTypes[0];
439 Q_ASSERT(resultType == annotation.changedRegister);
440 Q_ASSERT(!annotation.readRegisters.isEmpty());
442 if (!m_typeResolver->adjustTrackedType(resultType, it->typeReaders.values()))
443 addError(adjustErrorMessage(resultType, it->typeReaders.values()));
445 m_readerLocations.erase(it);
447 if (resultType.contains(m_typeResolver->varType())
448 || resultType.contains(m_typeResolver->variantMapType())
449 || resultType.contains(m_typeResolver->jsValueType())) {
454 const int classSize = m_jsUnitGenerator->jsClassSize(object.internalClassId);
455 Q_ASSERT(object.argc >= classSize);
457 for (
int i = 0; i < classSize; ++i) {
462 const QString propName = m_jsUnitGenerator->jsClassMember(object.internalClassId, i);
463 const QQmlJSMetaProperty property = resultType.containedType()->property(propName);
464 if (!property.isValid()) {
465 addError(resultType.containedType()->internalName()
466 + QLatin1String(
" has no property called ") + propName);
469 const QQmlJSScope::ConstPtr propType = property.type();
470 if (propType.isNull()) {
471 addError(QLatin1String(
"Cannot resolve type of property ") + propName);
474 const QQmlJSRegisterContent content = annotation.readRegisters[object.argv + i].content;
475 if (!m_typeResolver->adjustTrackedType(content, propType))
476 addError(adjustErrorMessage(content, propType));
485 for (
auto it = m_objectAndArrayDefinitions.crbegin(), end = m_objectAndArrayDefinitions.crend();
487 switch (it->internalClassId) {
488 case ObjectOrArrayDefinition::ArrayClassId:
489 case ObjectOrArrayDefinition::ArrayConstruct1ArgId:
490 adjustArray(it->instructionOffset, it->internalClassId);
498 for (
auto it = m_readerLocations.cbegin(), end = m_readerLocations.cend(); it != end; ++it) {
499 handleRegisterReadersAndConversions(it);
503 if (it->trackedTypes.size() != 1)
507 const int writeLocation = it.key();
508 if (writeLocation >= 0 && m_annotations[writeLocation].isRename)
511 if (!m_typeResolver->adjustTrackedType(it->trackedTypes[0], it->typeReaders.values()))
512 addError(adjustErrorMessage(it->trackedTypes[0], it->typeReaders.values()));
516 NewVirtualRegisters newRegisters;
517 for (
auto i = m_annotations.begin(), iEnd = m_annotations.end(); i != iEnd; ++i) {
518 for (
auto conversion = i->second.typeConversions.begin(),
519 conversionEnd = i->second.typeConversions.end(); conversion != conversionEnd;
521 if (!liveConversions[i.key()].contains(conversion.key()))
524 QQmlJSScope::ConstPtr newResult;
525 const auto content = conversion->second.content;
526 if (content.isConversion() && !content.original().isValid()) {
527 const auto &conversionOrigins = content.conversionOrigins();
528 for (
const auto &origin : conversionOrigins)
529 newResult = m_typeResolver->merge(newResult, origin.containedType());
530 if (!m_typeResolver->adjustTrackedType(content, newResult))
531 addError(adjustErrorMessage(content, newResult));
533 newRegisters.appendOrdered(conversion);
535 i->second.typeConversions = newRegisters.take();
537 for (
int movable : std::as_const(movableReads[i.key()]))
538 i->second.readRegisters[movable].canMove =
true;
542void QQmlJSOptimizations::populateBasicBlocks()
544 for (
auto blockNext = m_basicBlocks.begin(), blockEnd = m_basicBlocks.end();
545 blockNext != blockEnd;) {
547 const auto blockIt = blockNext++;
548 BasicBlock &block = blockIt->second;
549 QList<QQmlJSScope::ConstPtr> writtenTypes;
550 QList<
int> writtenRegisters;
552 const auto instrEnd = (blockNext == blockEnd) ? m_annotations.end()
553 : m_annotations.find(blockNext->first);
554 for (
auto instrIt = m_annotations.find(blockIt->first); instrIt != instrEnd; ++instrIt) {
555 const InstructionAnnotation &instruction = instrIt->second;
556 for (
auto it = instruction.readRegisters.begin(), end = instruction.readRegisters.end();
558 if (!writtenRegisters.contains(it->first))
559 block.readRegisters.append(it->first);
563 if (instruction.changedRegisterIndex != InvalidRegister) {
564 if (!instruction.isRename)
565 writtenTypes.append(instruction.changedRegister.containedType());
566 writtenRegisters.append(instruction.changedRegisterIndex);
570 QQmlJSUtils::deduplicate(block.readRegisters);
void appendOrdered(const typename OriginalFlatMap::iterator &i)
Combined button and popup list for selecting options.
static QString adjustErrorMessage(QQmlJSRegisterContent origin, const QQmlJSScope::ConstPtr &conversion)
static bool containsAny(const ContainerA &container, const ContainerB &elements)
static QString adjustErrorMessage(QQmlJSRegisterContent origin, QQmlJSRegisterContent conversion)