QmlCompiler: Preserve external side effects across jumps

Now that we discern internal and external side effects, we cannot
implicitly rely on every jump to generate a side effect anymore. We need
to actually update the virtual registers and also merge the side effects
(along with other flags).

Amends commit 6b14ba5c2f

Pick-to: 6.10 6.9 6.8 6.5
Task-number: QTBUG-137540
Change-Id: I6b46c7a4773759c8f6f30308ba72082555ce3e61
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Reviewed-by: Olivier De Cannière <olivier.decanniere@qt.io>
This commit is contained in:
Ulf Hermann 2025-06-26 12:37:56 +02:00
parent b1bdad8096
commit b9974d82cd
5 changed files with 67 additions and 17 deletions

View File

@ -1535,19 +1535,25 @@ void QQmlJSTypePropagator::setRegister(int index, QQmlJSRegisterContent content)
}
void QQmlJSTypePropagator::mergeRegister(
int index, QQmlJSRegisterContent a, QQmlJSRegisterContent b)
int index, const VirtualRegister &a, const VirtualRegister &b)
{
const QQmlJSRegisterContent merged = (a == b) ? a : m_typeResolver->merge(a, b);
Q_ASSERT(merged.isValid());
const VirtualRegister merged = {
(a.content == b.content) ? a.content : m_typeResolver->merge(a.content, b.content),
a.canMove && b.canMove,
a.affectedBySideEffects || b.affectedBySideEffects,
a.isShadowable || b.isShadowable,
};
if (!merged.isConversion()) {
Q_ASSERT(merged.content.isValid());
if (!merged.content.isConversion()) {
// The registers were the same. We're already tracking them.
m_state.annotations[currentInstructionOffset()].typeConversions[index].content = merged;
m_state.registers[index].content = merged;
m_state.annotations[currentInstructionOffset()].typeConversions[index] = merged;
m_state.registers[index] = merged;
return;
}
auto tryPrevStateConversion = [this](int index, QQmlJSRegisterContent merged) -> bool {
auto tryPrevStateConversion = [this](int index, const VirtualRegister &merged) -> bool {
auto it = m_prevStateAnnotations.find(currentInstructionOffset());
if (it == m_prevStateAnnotations.end())
return false;
@ -1562,23 +1568,35 @@ void QQmlJSTypePropagator::mergeRegister(
if (!lastTry.content.isConversion())
return false;
if (lastTry.content.conversionResultType() != merged.conversionResultType()
|| lastTry.content.conversionOrigins() != merged.conversionOrigins()) {
if (lastTry.content.conversionResultType() != merged.content.conversionResultType()
|| lastTry.content.conversionOrigins() != merged.content.conversionOrigins()
|| lastTry.canMove != merged.canMove
|| lastTry.affectedBySideEffects != merged.affectedBySideEffects
|| lastTry.isShadowable != merged.isShadowable) {
return false;
}
// We don't need to track it again if we've come to the same conclusion before.
m_state.annotations[currentInstructionOffset()].typeConversions[index] = lastTry;
// Do not reset the side effects
Q_ASSERT(!m_state.registers[index].affectedBySideEffects || lastTry.affectedBySideEffects);
m_state.registers[index] = lastTry;
return true;
};
if (!tryPrevStateConversion(index, merged)) {
// if a != b, we have already re-tracked it.
QQmlJSRegisterContent cloned = (a == b) ? m_pool->clone(merged) : merged;
Q_ASSERT(cloned.isValid());
m_state.annotations[currentInstructionOffset()].typeConversions[index].content = cloned;
m_state.registers[index].content = cloned;
const VirtualRegister cloned = {
(a == b) ? m_pool->clone(merged.content) : merged.content,
merged.canMove,
merged.affectedBySideEffects,
merged.isShadowable,
};
Q_ASSERT(cloned.content.isValid());
m_state.annotations[currentInstructionOffset()].typeConversions[index] = cloned;
m_state.registers[index] = cloned;
}
}
@ -3019,8 +3037,8 @@ QQmlJSTypePropagator::startInstruction(QV4::Moth::Instr::Type type)
registerIt != end; ++registerIt) {
const int registerIndex = registerIt.key();
auto newType = registerIt.value().content;
if (!newType.isValid()) {
const VirtualRegister &newType = registerIt.value();
if (!newType.content.isValid()) {
addError(u"When reached from offset %1, %2 is undefined"_s
.arg(stateToMerge.originatingOffset)
.arg(registerName(registerIndex)));
@ -3029,7 +3047,7 @@ QQmlJSTypePropagator::startInstruction(QV4::Moth::Instr::Type type)
auto currentRegister = m_state.registers.find(registerIndex);
if (currentRegister != m_state.registers.end())
mergeRegister(registerIndex, newType, currentRegister.value().content);
mergeRegister(registerIndex, newType, currentRegister.value());
else
mergeRegister(registerIndex, newType, newType);
}

View File

@ -234,7 +234,7 @@ private:
void setAccumulator(QQmlJSRegisterContent content);
void setRegister(int index, QQmlJSRegisterContent content);
void mergeRegister(int index, QQmlJSRegisterContent a, QQmlJSRegisterContent b);
void mergeRegister(int index, const VirtualRegister &a, const VirtualRegister &b);
void addReadRegister(int index);
void addReadRegister(int index, QQmlJSRegisterContent convertTo);

View File

@ -223,6 +223,7 @@ set(qml_files
mathMinMax.qml
mathOperations.qml
mathStaticProperties.qml
mergeSideEffects.qml
mergedObjectRead.qml
mergedObjectWrite.qml
methodOnListLookup.qml

View File

@ -0,0 +1,20 @@
import QtQml
QtObject {
property bool no: false
property list<int> a: [1]
property list<int> b: [2]
property int c: {
let numbers = a;
a = [3]; // create side effect affecting "numbers"
if (no) {
// Force two branches to be merged on "numbers"
numbers = b
}
// Side effect is still in effect
return numbers[0];
}
}

View File

@ -184,6 +184,7 @@ private slots:
void mathMinMax();
void mathOperations();
void mathStaticProperties();
void mergeSideEffects();
void mergedObjectReadWrite();
void methodOnListLookup();
void methods();
@ -3712,6 +3713,16 @@ void tst_QmlCppCodegen::mathStaticProperties()
QCOMPARE(object->property("sqrt2").toDouble(), 1.4142135623730951);
}
void tst_QmlCppCodegen::mergeSideEffects()
{
QQmlEngine engine;
QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/mergeSideEffects.qml"_s));
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
QScopedPointer<QObject> o(c.create());
QVERIFY(!o.isNull());
QCOMPARE(o->property("c").toInt(), 3);
}
void tst_QmlCppCodegen::mergedObjectReadWrite()
{
QQmlEngine e;