9#include <QtCore/qhash.h>
10#include <QtCore/qset.h>
14using namespace Qt::Literals::StringLiterals;
16QQmlJSCompilePass::BlocksAndAnnotations QQmlJSOptimizations::run(
const Function *function)
18 m_function = function;
20 populateBasicBlocks();
21 populateReaderLocationsTrackedTypes();
22 populateReaderLocationsReadersAndConversions();
23 removeDeadStoresUntilStable();
26 return { std::move(m_basicBlocks), std::move(m_annotations) };
29template<
typename ContainerA,
typename ContainerB>
30static bool containsAny(
const ContainerA &container,
const ContainerB &elements)
32 for (
const auto &element : elements) {
33 if (container.contains(element))
39template<
class Key,
class T,
class Compare = std::less<Key>,
40 class KeyContainer = QList<Key>,
class MappedContainer = QList<T>>
49 values.append(i.value());
54 OriginalFlatMap result(Qt::OrderedUniqueRange, std::move(keys), std::move(values));
61 typename OriginalFlatMap::key_container_type keys;
62 typename OriginalFlatMap::mapped_container_type values;
65void QQmlJSOptimizations::populateReaderLocationsTrackedTypes()
67 for (
const auto &[offset, annotation] : m_annotations) {
68 const int writtenRegister = annotation.changedRegisterIndex;
71 if (writtenRegister == InvalidRegister)
74 RegisterAccess &access = m_readerLocations[offset];
75 access.trackedRegister = writtenRegister;
76 if (annotation.changedRegister.isConversion()) {
80 const auto &origins = annotation.changedRegister.conversionOrigins();
81 for (QQmlJSRegisterContent origin : origins)
82 access.trackedTypes.append(origin);
84 access.trackedTypes.append(annotation.changedRegister);
85 Q_ASSERT(!access.trackedTypes.last().isNull());
93using RegisterState = QHash<
int, QQmlJSOptimizations::Conversions>;
103 bool changed =
false;
105 for (
auto contentIt = from.availableContent.constBegin(),
106 contentEnd = from.availableContent.constEnd(); contentIt != contentEnd; ++contentIt) {
107 QSet<
int> &target = to.availableContent[contentIt.key()];
108 const qsizetype before = target.size();
109 target.unite(contentIt.value());
110 if (target.size() > before)
114 for (
const auto &[index, registerState] : from.registers.asKeyValueRange()) {
115 RegisterState &target = to.registers[index];
116 for (
const auto &[writeOffset, conversions] : registerState.asKeyValueRange()) {
117 const auto existing = target.find(writeOffset);
118 if (existing == target.end()) {
119 target.insert(writeOffset, conversions);
123 const qsizetype before = existing.value().size();
124 existing.value().unite(conversions);
125 if (existing.value().size() > before)
133void QQmlJSOptimizations::populateReaderLocationsReadersAndConversions()
135 QHash<
int, BlockState> entryState;
136 std::vector<
int> pending;
137 pending.reserve(m_basicBlocks.size());
138 for (
const auto &[blockStart, block] : m_basicBlocks)
139 pending.push_back(blockStart);
141 while (!pending.empty()) {
142 const int blockStart = pending.back();
145 const auto blockIt = m_basicBlocks.find(blockStart);
146 auto nextBlockIt = blockIt;
149 BlockState state = entryState.value(blockStart);
151 auto instrIt = m_annotations.find(blockStart);
152 const auto blockEnd = (nextBlockIt == m_basicBlocks.end())
153 ? m_annotations.end()
154 : m_annotations.find(nextBlockIt->first);
156 for (; instrIt != blockEnd; ++instrIt) {
157 const int key = instrIt.key();
158 if (!instrIt->second.isRename) {
159 for (
const auto &read : instrIt->second.readRegisters.values()) {
160 const QQmlJSRegisterContent &content = read.content;
161 const auto recordRead = [&](
const QQmlJSRegisterContent &r) {
162 const auto found = state.availableContent.constFind(r);
163 if (found != state.availableContent.constEnd()) {
164 for (
int writerKey : found.value())
165 m_readerLocations[writerKey].typeReaders[key] = content;
169 if (content.isConversion()) {
170 Q_ASSERT(content.conversionResultType());
171 for (QQmlJSRegisterContent origin : content.conversionOrigins())
179 for (
const int convRegister : instrIt->second.typeConversions.keys()) {
180 const auto tracked = state.registers.find(convRegister);
181 if (tracked != state.registers.end()) {
182 for (
auto writerIt = tracked.value().begin(), writerEnd = tracked.value().end();
183 writerIt != writerEnd; ++writerIt) {
184 writerIt.value().insert(key);
189 for (
const int readRegister : instrIt->second.readRegisters.keys()) {
190 const auto tracked = state.registers.constFind(readRegister);
191 if (tracked != state.registers.constEnd()) {
192 for (
const auto &[writerKey, value] : tracked.value().asKeyValueRange())
193 m_readerLocations[writerKey].registerReadersAndConversions[key] = value;
198 if (instrIt->second.changedRegisterIndex != InvalidRegister) {
199 const auto access = m_readerLocations.constFind(key);
200 if (access != m_readerLocations.constEnd()) {
201 for (
const QQmlJSRegisterContent &tracked : std::as_const(access->trackedTypes))
202 state.availableContent[tracked].insert(key);
207 fresh.insert(key, Conversions{});
208 state.registers[instrIt->second.changedRegisterIndex] = std::move(fresh);
212 auto scheduleSuccessor = [&](
int successorStart) {
213 if (mergeBlockState(entryState[successorStart], state))
214 pending.push_back(successorStart);
217 if (!blockIt->second.jumpIsUnconditional && nextBlockIt != m_basicBlocks.end())
218 scheduleSuccessor(nextBlockIt->first);
220 const int jumpTarget = blockIt->second.jumpTarget;
221 if (jumpTarget != -1)
222 scheduleSuccessor(jumpTarget);
226bool QQmlJSOptimizations::eraseDeadStore(
const InstructionAnnotations::iterator &it,
229 auto reader = m_readerLocations.find(it.key());
230 if (reader != m_readerLocations.end()
231 && (reader->typeReaders.isEmpty() || reader->registerReadersAndConversions.isEmpty())) {
233 if (it->second.isRename) {
238 it->second.changedRegisterIndex = InvalidRegister;
239 it->second.changedRegister = QQmlJSRegisterContent();
248 const QQmlJSScope::ConstPtr contained = it->second.changedRegister.containedType();
249 if (!it->second.hasExternalSideEffects
250 || (!contained->isReferenceType()
251 && !m_typeResolver->canHold(contained, m_typeResolver->qObjectType()))) {
253 const bool adjusted = m_typeResolver->adjustTrackedType(
254 it->second.changedRegister, m_typeResolver->voidType());
258 m_readerLocations.erase(reader);
261 if (!it->second.hasInternalSideEffects) {
262 if (!it->second.readRegisters.isEmpty()) {
263 it->second.readRegisters.clear();
264 erasedReaders =
true;
266 if (m_basicBlocks.find(it.key()) == m_basicBlocks.end())
273void QQmlJSOptimizations::removeDeadStoresUntilStable()
275 using NewInstructionAnnotations = NewFlatMap<
int, InstructionAnnotation>;
276 NewInstructionAnnotations newAnnotations;
278 bool erasedReaders =
true;
279 while (erasedReaders) {
280 erasedReaders =
false;
282 for (
auto it = m_annotations.begin(), end = m_annotations.end(); it != end; ++it) {
283 InstructionAnnotation &instruction = it->second;
286 if (instruction.changedRegisterIndex < InvalidRegister) {
287 newAnnotations.appendOrdered(it);
291 removeReadsFromErasedInstructions(it);
293 if (!eraseDeadStore(it, erasedReaders))
294 newAnnotations.appendOrdered(it);
297 m_annotations = newAnnotations.take();
301void QQmlJSOptimizations::removeReadsFromErasedInstructions(
302 const QFlatMap<
int, InstructionAnnotation>::const_iterator &it)
304 auto readers = m_readerLocations.find(it.key());
305 if (readers == m_readerLocations.end())
308 for (
auto typeIt = readers->typeReaders.begin(); typeIt != readers->typeReaders.end();) {
309 if (m_annotations.contains(typeIt.key()))
312 typeIt = readers->typeReaders.erase(typeIt);
315 for (
auto registerIt = readers->registerReadersAndConversions.begin();
316 registerIt != readers->registerReadersAndConversions.end();) {
317 if (m_annotations.contains(registerIt.key()))
320 registerIt = readers->registerReadersAndConversions.erase(registerIt);
324bool QQmlJSOptimizations::canMove(
int instructionOffset,
325 const QQmlJSOptimizations::RegisterAccess &access)
const
327 if (access.typeReaders.size() != 1)
329 return QQmlJSBasicBlocks::constBasicBlockForInstruction(m_basicBlocks, instructionOffset)
330 == QQmlJSBasicBlocks::constBasicBlockForInstruction(m_basicBlocks, access.typeReaders.begin().key());
333QList<QQmlJSCompilePass::ObjectOrArrayDefinition>
334QQmlJSBasicBlocks::objectAndArrayDefinitions()
const
336 return m_objectAndArrayDefinitions;
340 QQmlJSRegisterContent origin,
const QQmlJSScope::ConstPtr &conversion) {
341 return QLatin1String(
"Cannot convert from ")
342 + origin.containedType()->internalName() + QLatin1String(
" to ")
343 + conversion->internalName();
347 QQmlJSRegisterContent origin, QQmlJSRegisterContent conversion) {
348 return adjustErrorMessage(origin, conversion.containedType());
352 QQmlJSRegisterContent origin,
const QList<QQmlJSRegisterContent> &conversions) {
353 if (conversions.size() == 1)
354 return adjustErrorMessage(origin, conversions[0]);
357 for (QQmlJSRegisterContent type : conversions) {
358 if (!types.isEmpty())
359 types += QLatin1String(
", ");
360 types += type.containedType()->internalName();
362 return QLatin1String(
"Cannot convert from ")
363 + origin.containedType()->internalName() + QLatin1String(
" to union of ") + types;
366void QQmlJSOptimizations::adjustTypes()
368 using NewVirtualRegisters = NewFlatMap<
int, VirtualRegister>;
370 QHash<
int, QList<
int>> liveConversions;
371 QHash<
int, QList<
int>> movableReads;
373 const auto handleRegisterReadersAndConversions
374 = [&](QHash<
int, RegisterAccess>::const_iterator it) {
375 for (
auto conversions = it->registerReadersAndConversions.constBegin(),
376 end = it->registerReadersAndConversions.constEnd(); conversions != end;
378 if (conversions->isEmpty() && canMove(it.key(), it.value()))
379 movableReads[conversions.key()].append(it->trackedRegister);
380 for (
int conversion : *conversions)
381 liveConversions[conversion].append(it->trackedRegister);
387 auto adjustArray = [&](
int instructionOffset,
int mode) {
388 auto it = m_readerLocations.constFind(instructionOffset);
389 if (it == m_readerLocations.cend())
392 const InstructionAnnotation &annotation = m_annotations[instructionOffset];
393 if (annotation.readRegisters.isEmpty())
396 Q_ASSERT(it->trackedTypes.size() == 1);
397 Q_ASSERT(it->trackedTypes[0] == annotation.changedRegister);
399 if (it->trackedTypes[0].containedType()->accessSemantics()
400 != QQmlJSScope::AccessSemantics::Sequence) {
404 if (!m_typeResolver->adjustTrackedType(it->trackedTypes[0], it->typeReaders.values()))
405 addError(adjustErrorMessage(it->trackedTypes[0], it->typeReaders.values()));
410 if (
const QQmlJSScope::ConstPtr elementType
411 = it->trackedTypes[0].containedType()->elementType()) {
412 const auto adjust = [&](
const auto it) {
413 const QQmlJSRegisterContent content = it.value().content;
414 const QQmlJSScope::ConstPtr contained = content.containedType();
415 if (!m_typeResolver->adjustTrackedType(content, elementType)) {
416 addError(adjustErrorMessage(content, elementType));
422 const auto &readRegisters = annotation.readRegisters;
423 if (mode == ObjectOrArrayDefinition::ArrayConstruct1ArgId) {
424 Q_ASSERT(readRegisters.size() == 1);
425 const auto it = readRegisters.cbegin();
426 if (it.value().content.containedType() != m_typeResolver->realType())
429 for (
auto it = readRegisters.cbegin(); it != readRegisters.cend(); ++it) {
436 handleRegisterReadersAndConversions(it);
437 m_readerLocations.erase(it);
442 const auto adjustObject = [&](
const ObjectOrArrayDefinition &object) {
443 auto it = m_readerLocations.find(object.instructionOffset);
444 if (it == m_readerLocations.end())
447 const InstructionAnnotation &annotation = m_annotations[object.instructionOffset];
449 Q_ASSERT(it->trackedTypes.size() == 1);
450 const QQmlJSRegisterContent resultType = it->trackedTypes[0];
452 Q_ASSERT(resultType == annotation.changedRegister);
453 Q_ASSERT(!annotation.readRegisters.isEmpty());
455 if (!m_typeResolver->adjustTrackedType(resultType, it->typeReaders.values()))
456 addError(adjustErrorMessage(resultType, it->typeReaders.values()));
458 m_readerLocations.erase(it);
460 if (resultType.contains(m_typeResolver->varType())
461 || resultType.contains(m_typeResolver->variantMapType())
462 || resultType.contains(m_typeResolver->jsValueType())) {
467 const int classSize = m_jsUnitGenerator->jsClassSize(object.internalClassId);
468 Q_ASSERT(object.argc >= classSize);
470 for (
int i = 0; i < classSize; ++i) {
475 const QString propName = m_jsUnitGenerator->jsClassMember(object.internalClassId, i);
476 const QQmlJSMetaProperty property = resultType.containedType()->property(propName);
477 if (!property.isValid()) {
478 addError(resultType.containedType()->internalName()
479 + QLatin1String(
" has no property called ") + propName);
482 const QQmlJSScope::ConstPtr propType = property.type();
483 if (propType.isNull()) {
484 addError(QLatin1String(
"Cannot resolve type of property ") + propName);
487 const QQmlJSRegisterContent content = annotation.readRegisters[object.argv + i].content;
488 if (!m_typeResolver->adjustTrackedType(content, propType))
489 addError(adjustErrorMessage(content, propType));
498 for (
auto it = m_objectAndArrayDefinitions.crbegin(), end = m_objectAndArrayDefinitions.crend();
500 switch (it->internalClassId) {
501 case ObjectOrArrayDefinition::ArrayClassId:
502 case ObjectOrArrayDefinition::ArrayConstruct1ArgId:
503 adjustArray(it->instructionOffset, it->internalClassId);
511 for (
auto it = m_readerLocations.cbegin(), end = m_readerLocations.cend(); it != end; ++it) {
512 handleRegisterReadersAndConversions(it);
516 if (it->trackedTypes.size() != 1)
520 const int writeLocation = it.key();
521 if (writeLocation >= 0 && m_annotations[writeLocation].isRename)
524 if (!m_typeResolver->adjustTrackedType(it->trackedTypes[0], it->typeReaders.values()))
525 addError(adjustErrorMessage(it->trackedTypes[0], it->typeReaders.values()));
529 NewVirtualRegisters newRegisters;
530 for (
auto i = m_annotations.begin(), iEnd = m_annotations.end(); i != iEnd; ++i) {
531 for (
auto conversion = i->second.typeConversions.begin(),
532 conversionEnd = i->second.typeConversions.end(); conversion != conversionEnd;
534 if (!liveConversions[i.key()].contains(conversion.key()))
537 QQmlJSScope::ConstPtr newResult;
538 const auto content = conversion->second.content;
539 if (content.isConversion() && !content.original().isValid()) {
540 const auto &conversionOrigins = content.conversionOrigins();
541 for (
const auto &origin : conversionOrigins)
542 newResult = m_typeResolver->merge(newResult, origin.containedType());
543 if (!m_typeResolver->adjustTrackedType(content, newResult))
544 addError(adjustErrorMessage(content, newResult));
546 newRegisters.appendOrdered(conversion);
548 i->second.typeConversions = newRegisters.take();
550 for (
int movable : std::as_const(movableReads[i.key()]))
551 i->second.readRegisters[movable].canMove =
true;
555void QQmlJSOptimizations::populateBasicBlocks()
557 for (
auto blockNext = m_basicBlocks.begin(), blockEnd = m_basicBlocks.end();
558 blockNext != blockEnd;) {
560 const auto blockIt = blockNext++;
561 BasicBlock &block = blockIt->second;
562 QList<QQmlJSScope::ConstPtr> writtenTypes;
563 QList<
int> writtenRegisters;
565 const auto instrEnd = (blockNext == blockEnd) ? m_annotations.end()
566 : m_annotations.find(blockNext->first);
567 for (
auto instrIt = m_annotations.find(blockIt->first); instrIt != instrEnd; ++instrIt) {
568 const InstructionAnnotation &instruction = instrIt->second;
569 for (
auto it = instruction.readRegisters.begin(), end = instruction.readRegisters.end();
571 if (!writtenRegisters.contains(it->first))
572 block.readRegisters.append(it->first);
576 if (instruction.changedRegisterIndex != InvalidRegister) {
577 if (!instruction.isRename)
578 writtenTypes.append(instruction.changedRegister.containedType());
579 writtenRegisters.append(instruction.changedRegisterIndex);
583 QQmlJSUtils::deduplicate(block.readRegisters);
void appendOrdered(const typename OriginalFlatMap::iterator &i)
Combined button and popup list for selecting options.
static bool mergeBlockState(BlockState &to, const BlockState &from)
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)
QHash< int, RegisterState > registers