11using namespace Qt::Literals::StringLiterals;
13QQmlJSCompilePass::BlocksAndAnnotations QQmlJSOptimizations::run(
const Function *function)
15 m_function = function;
17 populateBasicBlocks();
18 populateReaderLocations();
19 removeDeadStoresUntilStable();
22 return { std::move(m_basicBlocks), std::move(m_annotations) };
32template<
typename ContainerA,
typename ContainerB>
33static bool containsAny(
const ContainerA &container,
const ContainerB &elements)
35 for (
const auto &element : elements) {
36 if (container.contains(element))
42template<
class Key,
class T,
class Compare = std::less<Key>,
43 class KeyContainer = QList<Key>,
class MappedContainer = QList<T>>
52 values.append(i.value());
57 OriginalFlatMap result(Qt::OrderedUniqueRange, std::move(keys), std::move(values));
64 typename OriginalFlatMap::key_container_type keys;
65 typename OriginalFlatMap::mapped_container_type values;
68void QQmlJSOptimizations::populateReaderLocations()
70 for (
auto writeIt = m_annotations.begin(), writeEnd = m_annotations.end();
71 writeIt != writeEnd; ++writeIt) {
72 const int writtenRegister = writeIt->second.changedRegisterIndex;
75 if (writtenRegister == InvalidRegister)
78 RegisterAccess &access = m_readerLocations[writeIt.key()];
79 access.trackedRegister = writtenRegister;
80 if (writeIt->second.changedRegister.isConversion()) {
84 access.trackedTypes.clear();
85 const auto origins = writeIt->second.changedRegister.conversionOrigins();
86 for (QQmlJSRegisterContent origin : origins)
87 access.trackedTypes.append(origin);
89 access.trackedTypes.append(writeIt->second.changedRegister);
90 Q_ASSERT(!access.trackedTypes.last().isNull());
93 auto blockIt = QQmlJSBasicBlocks::basicBlockForInstruction(m_basicBlocks, writeIt.key());
94 QList<PendingBlock> blocks = { { {}, blockIt->first,
true } };
95 QHash<
int, PendingBlock> processedBlocks;
96 bool isFirstBlock =
true;
98 while (!blocks.isEmpty()) {
99 const PendingBlock block = blocks.takeLast();
104 processedBlocks.insert(block.start, block);
106 auto nextBlock = m_basicBlocks.find(block.start);
107 auto currentBlock = nextBlock++;
108 bool registerActive = block.registerActive;
109 Conversions conversions = block.conversions;
111 const auto blockEnd = (nextBlock == m_basicBlocks.end())
112 ? m_annotations.end()
113 : m_annotations.find(nextBlock->first);
115 auto blockInstr = isFirstBlock
117 : m_annotations.find(currentBlock->first);
118 for (; blockInstr != blockEnd; ++blockInstr) {
120 && blockInstr->second.typeConversions.contains(writtenRegister)) {
121 conversions.insert(blockInstr.key());
124 for (
auto readIt = blockInstr->second.readRegisters.constBegin(),
125 end = blockInstr->second.readRegisters.constEnd();
126 readIt != end; ++readIt) {
127 if (blockInstr->second.isRename) {
129 }
else if (readIt->second.content.isConversion()) {
130 const QList<QQmlJSRegisterContent> conversionOrigins
131 = readIt->second.content.conversionOrigins();
132 for (QQmlJSRegisterContent origin : conversionOrigins) {
133 if (!access.trackedTypes.contains(origin))
136 Q_ASSERT(readIt->second.content.conversionResultType());
137 access.typeReaders[blockInstr.key()] = readIt->second.content;
140 }
else if (access.trackedTypes.contains(readIt->second.content)) {
142 access.typeReaders[blockInstr.key()] = readIt->second.content;
144 if (registerActive && readIt->first == writtenRegister)
145 access.registerReadersAndConversions[blockInstr.key()] = conversions;
148 if (blockInstr->second.changedRegisterIndex == writtenRegister) {
150 registerActive =
false;
154 auto scheduleBlock = [&](
int blockStart) {
157 const auto processed = processedBlocks.find(blockStart);
158 if (processed == processedBlocks.end()) {
159 blocks.append({conversions, blockStart, registerActive});
160 }
else if (registerActive && !processed->registerActive) {
161 blocks.append({conversions, blockStart, registerActive});
163 Conversions merged = processed->conversions;
164 merged.unite(conversions);
166 if (merged.size() > processed->conversions.size())
167 blocks.append({std::move(merged), blockStart, registerActive});
171 if (!currentBlock->second.jumpIsUnconditional && nextBlock != m_basicBlocks.end())
172 scheduleBlock(nextBlock->first);
174 const int jumpTarget = currentBlock->second.jumpTarget;
175 if (jumpTarget != -1)
176 scheduleBlock(jumpTarget);
179 isFirstBlock =
false;
184bool QQmlJSOptimizations::eraseDeadStore(
const InstructionAnnotations::iterator &it,
187 auto reader = m_readerLocations.find(it.key());
188 if (reader != m_readerLocations.end()
189 && (reader->typeReaders.isEmpty() || reader->registerReadersAndConversions.isEmpty())) {
191 if (it->second.isRename) {
196 it->second.changedRegisterIndex = InvalidRegister;
197 it->second.changedRegister = QQmlJSRegisterContent();
206 const QQmlJSScope::ConstPtr contained = it->second.changedRegister.containedType();
207 if (!it->second.hasExternalSideEffects
208 || (!contained->isReferenceType()
209 && !m_typeResolver->canHold(contained, m_typeResolver->qObjectType()))) {
211 const bool adjusted = m_typeResolver->adjustTrackedType(
212 it->second.changedRegister, m_typeResolver->voidType());
216 m_readerLocations.erase(reader);
219 if (!it->second.hasInternalSideEffects) {
220 if (!it->second.readRegisters.isEmpty()) {
221 it->second.readRegisters.clear();
222 erasedReaders =
true;
224 if (m_basicBlocks.find(it.key()) == m_basicBlocks.end())
231void QQmlJSOptimizations::removeDeadStoresUntilStable()
233 using NewInstructionAnnotations = NewFlatMap<
int, InstructionAnnotation>;
234 NewInstructionAnnotations newAnnotations;
236 bool erasedReaders =
true;
237 while (erasedReaders) {
238 erasedReaders =
false;
240 for (
auto it = m_annotations.begin(), end = m_annotations.end(); it != end; ++it) {
241 InstructionAnnotation &instruction = it->second;
244 if (instruction.changedRegisterIndex < InvalidRegister) {
245 newAnnotations.appendOrdered(it);
249 removeReadsFromErasedInstructions(it);
251 if (!eraseDeadStore(it, erasedReaders))
252 newAnnotations.appendOrdered(it);
255 m_annotations = newAnnotations.take();
259void QQmlJSOptimizations::removeReadsFromErasedInstructions(
260 const QFlatMap<
int, InstructionAnnotation>::const_iterator &it)
262 auto readers = m_readerLocations.find(it.key());
263 if (readers == m_readerLocations.end())
266 for (
auto typeIt = readers->typeReaders.begin(); typeIt != readers->typeReaders.end();) {
267 if (m_annotations.contains(typeIt.key()))
270 typeIt = readers->typeReaders.erase(typeIt);
273 for (
auto registerIt = readers->registerReadersAndConversions.begin();
274 registerIt != readers->registerReadersAndConversions.end();) {
275 if (m_annotations.contains(registerIt.key()))
278 registerIt = readers->registerReadersAndConversions.erase(registerIt);
282bool QQmlJSOptimizations::canMove(
int instructionOffset,
283 const QQmlJSOptimizations::RegisterAccess &access)
const
285 if (access.typeReaders.size() != 1)
287 return QQmlJSBasicBlocks::basicBlockForInstruction(m_basicBlocks, instructionOffset)
288 == QQmlJSBasicBlocks::basicBlockForInstruction(m_basicBlocks, access.typeReaders.begin().key());
291QList<QQmlJSCompilePass::ObjectOrArrayDefinition>
292QQmlJSBasicBlocks::objectAndArrayDefinitions()
const
294 return m_objectAndArrayDefinitions;
298 QQmlJSRegisterContent origin,
const QQmlJSScope::ConstPtr &conversion) {
299 return QLatin1String(
"Cannot convert from ")
300 + origin.containedType()->internalName() + QLatin1String(
" to ")
301 + conversion->internalName();
305 QQmlJSRegisterContent origin, QQmlJSRegisterContent conversion) {
306 return adjustErrorMessage(origin, conversion.containedType());
310 QQmlJSRegisterContent origin,
const QList<QQmlJSRegisterContent> &conversions) {
311 if (conversions.size() == 1)
312 return adjustErrorMessage(origin, conversions[0]);
315 for (QQmlJSRegisterContent type : conversions) {
316 if (!types.isEmpty())
317 types += QLatin1String(
", ");
318 types += type.containedType()->internalName();
320 return QLatin1String(
"Cannot convert from ")
321 + origin.containedType()->internalName() + QLatin1String(
" to union of ") + types;
324void QQmlJSOptimizations::adjustTypes()
326 using NewVirtualRegisters = NewFlatMap<
int, VirtualRegister>;
328 QHash<
int, QList<
int>> liveConversions;
329 QHash<
int, QList<
int>> movableReads;
331 const auto handleRegisterReadersAndConversions
332 = [&](QHash<
int, RegisterAccess>::const_iterator it) {
333 for (
auto conversions = it->registerReadersAndConversions.constBegin(),
334 end = it->registerReadersAndConversions.constEnd(); conversions != end;
336 if (conversions->isEmpty() && canMove(it.key(), it.value()))
337 movableReads[conversions.key()].append(it->trackedRegister);
338 for (
int conversion : *conversions)
339 liveConversions[conversion].append(it->trackedRegister);
345 auto adjustArray = [&](
int instructionOffset,
int mode) {
346 auto it = m_readerLocations.constFind(instructionOffset);
347 if (it == m_readerLocations.cend())
350 const InstructionAnnotation &annotation = m_annotations[instructionOffset];
351 if (annotation.readRegisters.isEmpty())
354 Q_ASSERT(it->trackedTypes.size() == 1);
355 Q_ASSERT(it->trackedTypes[0] == annotation.changedRegister);
357 if (it->trackedTypes[0].containedType()->accessSemantics()
358 != QQmlJSScope::AccessSemantics::Sequence) {
362 if (!m_typeResolver->adjustTrackedType(it->trackedTypes[0], it->typeReaders.values()))
363 addError(adjustErrorMessage(it->trackedTypes[0], it->typeReaders.values()));
368 if (
const QQmlJSScope::ConstPtr elementType
369 = it->trackedTypes[0].containedType()->elementType()) {
370 const QQmlJSRegisterContent content = annotation.readRegisters.begin().value().content;
371 const QQmlJSScope::ConstPtr contained = content.containedType();
374 if (mode != ObjectOrArrayDefinition::ArrayConstruct1ArgId
375 || contained != m_typeResolver->realType()) {
376 if (!m_typeResolver->adjustTrackedType(content, elementType))
377 addError(adjustErrorMessage(content, elementType));
381 handleRegisterReadersAndConversions(it);
382 m_readerLocations.erase(it);
387 const auto adjustObject = [&](
const ObjectOrArrayDefinition &object) {
388 auto it = m_readerLocations.find(object.instructionOffset);
389 if (it == m_readerLocations.end())
392 const InstructionAnnotation &annotation = m_annotations[object.instructionOffset];
394 Q_ASSERT(it->trackedTypes.size() == 1);
395 const QQmlJSRegisterContent resultType = it->trackedTypes[0];
397 Q_ASSERT(resultType == annotation.changedRegister);
398 Q_ASSERT(!annotation.readRegisters.isEmpty());
400 if (!m_typeResolver->adjustTrackedType(resultType, it->typeReaders.values()))
401 addError(adjustErrorMessage(resultType, it->typeReaders.values()));
403 m_readerLocations.erase(it);
405 if (resultType.contains(m_typeResolver->varType())
406 || resultType.contains(m_typeResolver->variantMapType())
407 || resultType.contains(m_typeResolver->jsValueType())) {
412 const int classSize = m_jsUnitGenerator->jsClassSize(object.internalClassId);
413 Q_ASSERT(object.argc >= classSize);
415 for (
int i = 0; i < classSize; ++i) {
420 const QString propName = m_jsUnitGenerator->jsClassMember(object.internalClassId, i);
421 const QQmlJSMetaProperty property = resultType.containedType()->property(propName);
422 if (!property.isValid()) {
423 addError(resultType.containedType()->internalName()
424 + QLatin1String(
" has no property called ") + propName);
427 const QQmlJSScope::ConstPtr propType = property.type();
428 if (propType.isNull()) {
429 addError(QLatin1String(
"Cannot resolve type of property ") + propName);
432 const QQmlJSRegisterContent content = annotation.readRegisters[object.argv + i].content;
433 if (!m_typeResolver->adjustTrackedType(content, propType))
434 addError(adjustErrorMessage(content, propType));
443 for (
auto it = m_objectAndArrayDefinitions.crbegin(), end = m_objectAndArrayDefinitions.crend();
445 switch (it->internalClassId) {
446 case ObjectOrArrayDefinition::ArrayClassId:
447 case ObjectOrArrayDefinition::ArrayConstruct1ArgId:
448 adjustArray(it->instructionOffset, it->internalClassId);
456 for (
auto it = m_readerLocations.cbegin(), end = m_readerLocations.cend(); it != end; ++it) {
457 handleRegisterReadersAndConversions(it);
461 if (it->trackedTypes.size() != 1)
465 const int writeLocation = it.key();
466 if (writeLocation >= 0 && m_annotations[writeLocation].isRename)
469 if (!m_typeResolver->adjustTrackedType(it->trackedTypes[0], it->typeReaders.values()))
470 addError(adjustErrorMessage(it->trackedTypes[0], it->typeReaders.values()));
474 NewVirtualRegisters newRegisters;
475 for (
auto i = m_annotations.begin(), iEnd = m_annotations.end(); i != iEnd; ++i) {
476 for (
auto conversion = i->second.typeConversions.begin(),
477 conversionEnd = i->second.typeConversions.end(); conversion != conversionEnd;
479 if (!liveConversions[i.key()].contains(conversion.key()))
482 QQmlJSScope::ConstPtr newResult;
483 const auto content = conversion->second.content;
484 if (content.isConversion() && !content.original().isValid()) {
485 const auto conversionOrigins = content.conversionOrigins();
486 for (
const auto &origin : conversionOrigins)
487 newResult = m_typeResolver->merge(newResult, origin.containedType());
488 if (!m_typeResolver->adjustTrackedType(content, newResult))
489 addError(adjustErrorMessage(content, newResult));
491 newRegisters.appendOrdered(conversion);
493 i->second.typeConversions = newRegisters.take();
495 for (
int movable : std::as_const(movableReads[i.key()]))
496 i->second.readRegisters[movable].canMove =
true;
500void QQmlJSOptimizations::populateBasicBlocks()
502 for (
auto blockNext = m_basicBlocks.begin(), blockEnd = m_basicBlocks.end();
503 blockNext != blockEnd;) {
505 const auto blockIt = blockNext++;
506 BasicBlock &block = blockIt->second;
507 QList<QQmlJSScope::ConstPtr> writtenTypes;
508 QList<
int> writtenRegisters;
510 const auto instrEnd = (blockNext == blockEnd) ? m_annotations.end()
511 : m_annotations.find(blockNext->first);
512 for (
auto instrIt = m_annotations.find(blockIt->first); instrIt != instrEnd; ++instrIt) {
513 const InstructionAnnotation &instruction = instrIt->second;
514 for (
auto it = instruction.readRegisters.begin(), end = instruction.readRegisters.end();
516 if (!writtenRegisters.contains(it->first))
517 block.readRegisters.append(it->first);
521 if (instruction.changedRegisterIndex != InvalidRegister) {
522 if (!instruction.isRename)
523 writtenTypes.append(instruction.changedRegister.containedType());
524 writtenRegisters.append(instruction.changedRegisterIndex);
528 QQmlJSUtils::deduplicate(block.readRegisters);
void appendOrdered(const typename OriginalFlatMap::iterator &i)
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)