aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/compiler/qv4codegen.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qml/compiler/qv4codegen.cpp')
-rw-r--r--src/qml/compiler/qv4codegen.cpp288
1 files changed, 139 insertions, 149 deletions
diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp
index 1fca7bd75a..281924fe2c 100644
--- a/src/qml/compiler/qv4codegen.cpp
+++ b/src/qml/compiler/qv4codegen.cpp
@@ -21,7 +21,6 @@
#include <private/qqmljsdiagnosticmessage_p.h>
#include <cmath>
-#include <iostream>
#ifdef CONST
#undef CONST
@@ -1262,35 +1261,26 @@ bool Codegen::visit(ArrayPattern *ast)
bool Codegen::visit(ArrayMemberExpression *ast)
{
- auto label = traverseOptionalChain(ast);
- auto targetLabel = label.has_value() ? label.value() : Moth::BytecodeGenerator::Label();
-
if (hasError())
return false;
- if (ast->isOptional)
- Q_ASSERT(m_optionalChainLabels.contains(ast));
-
+ const bool isTailOfChain = traverseOptionalChain(ast);
TailCallBlocker blockTailCalls(this);
Reference base = expression(ast->base);
auto writeSkip = [&]() {
- auto acc = Reference::fromAccumulator(this).storeOnStack();
- base.loadInAccumulator();
- bytecodeGenerator->addInstruction(Instruction::CmpEqNull());
- auto jumpFalse = bytecodeGenerator->jumpFalse();
- bytecodeGenerator->addInstruction(Instruction::LoadUndefined());
- bytecodeGenerator->jump().link(m_optionalChainLabels.take(ast));
- jumpFalse.link();
- acc.loadInAccumulator();
+ base.loadInAccumulator();
+ bytecodeGenerator->addInstruction(Instruction::CmpEqNull());
+ auto jumpToUndefined = bytecodeGenerator->jumpTrue();
+ m_optionalChainsStates.top().jumpsToPatch.emplace_back(std::move(jumpToUndefined));
};
if (hasError())
return false;
if (base.isSuper()) {
Reference index = expression(ast->expression).storeOnStack();
- setExprResult(Reference::fromSuperProperty(index));
+ optionalChainFinalizer(Reference::fromSuperProperty(index), isTailOfChain);
return false;
}
base = base.storeOnStack();
@@ -1300,11 +1290,10 @@ bool Codegen::visit(ArrayMemberExpression *ast)
QString s = str->value.toString();
uint arrayIndex = stringToArrayIndex(s);
if (arrayIndex == UINT_MAX) {
- auto jumpLabel = ast->isOptional ? m_optionalChainLabels.take(ast) : Moth::BytecodeGenerator::Label();
-
- setExprResult(Reference::fromMember(base, str->value.toString(),
- ast->expression->firstSourceLocation(), jumpLabel,
- targetLabel));
+ auto ref = Reference::fromMember(base, s, ast->expression->firstSourceLocation(),
+ ast->isOptional,
+ &m_optionalChainsStates.top().jumpsToPatch);
+ setExprResult(ref);
return false;
}
@@ -1312,7 +1301,7 @@ bool Codegen::visit(ArrayMemberExpression *ast)
writeSkip();
Reference index = Reference::fromConst(this, QV4::Encode(arrayIndex));
- setExprResult(Reference::fromSubscript(base, index, targetLabel));
+ optionalChainFinalizer(Reference::fromSubscript(base, index), isTailOfChain);
return false;
}
@@ -1325,8 +1314,7 @@ bool Codegen::visit(ArrayMemberExpression *ast)
if (hasError())
return false;
- setExprResult(Reference::fromSubscript(base, index, targetLabel));
-
+ optionalChainFinalizer(Reference::fromSubscript(base, index), isTailOfChain);
return false;
}
@@ -1972,12 +1960,13 @@ bool Codegen::visit(CallExpression *ast)
if (hasError())
return false;
- auto label = traverseOptionalChain(ast);
+ const bool isTailOfChain = traverseOptionalChain(ast);
RegisterScope scope(this);
TailCallBlocker blockTailCalls(this);
- Reference base = expression(ast->base);
+ Reference expr = expression(ast->base);
+ Reference base = expr;
if (hasError())
return false;
@@ -2001,21 +1990,21 @@ bool Codegen::visit(CallExpression *ast)
break;
}
+ if (expr.hasSavedCallBaseSlot) {
+ // Hack to preserve `this` context in optional chain calls. See optionalChainFinalizer().
+ base.hasSavedCallBaseSlot = true;
+ base.savedCallBaseSlot = expr.savedCallBaseSlot;
+ base.savedCallPropertyNameIndex = expr.savedCallPropertyNameIndex;
+ }
+
int thisObject = bytecodeGenerator->newRegister();
int functionObject = bytecodeGenerator->newRegister();
- if (ast->isOptional || (!base.optionalChainJumpLabel.isNull() && base.optionalChainJumpLabel->isValid())) {
- if (ast->isOptional)
- Q_ASSERT(m_optionalChainLabels.contains(ast));
-
- auto jumpLabel = ast->isOptional ? m_optionalChainLabels.take(ast) : *base.optionalChainJumpLabel.get();
-
+ if (ast->isOptional) {
base.loadInAccumulator();
bytecodeGenerator->addInstruction(Instruction::CmpEqNull());
- auto jumpFalse = bytecodeGenerator->jumpFalse();
- bytecodeGenerator->addInstruction(Instruction::LoadUndefined());
- bytecodeGenerator->jump().link(jumpLabel);
- jumpFalse.link();
+ auto jumpToUndefined = bytecodeGenerator->jumpTrue();
+ m_optionalChainsStates.top().jumpsToPatch.emplace_back(std::move(jumpToUndefined));
}
auto calldata = pushArgs(ast->arguments);
@@ -2058,20 +2047,12 @@ bool Codegen::visit(CallExpression *ast)
bytecodeGenerator->addInstruction(call);
}
- setExprResult(Reference::fromAccumulator(this));
-
- if (label.has_value())
- label->link();
-
+ optionalChainFinalizer(Reference::fromAccumulator(this), isTailOfChain);
return false;
-
}
handleCall(base, calldata, functionObject, thisObject, ast->isOptional);
-
- if (label.has_value())
- label->link();
-
+ optionalChainFinalizer(Reference::fromAccumulator(this), isTailOfChain);
return false;
}
@@ -2086,19 +2067,30 @@ void Codegen::handleCall(Reference &base, Arguments calldata, int slotForFunctio
bytecodeGenerator->setLocation(base.sourceLocation);
//### Do we really need all these call instructions? can's we load the callee in a temp?
- if (base.type == Reference::Member) {
+ if (base.type == Reference::Member || base.hasSavedCallBaseSlot) {
if (useFastLookups) {
Instruction::CallPropertyLookup call;
- call.base = base.propertyBase.stackSlot();
- call.lookupIndex = registerGetterLookup(
+ if (base.hasSavedCallBaseSlot) {
+ call.base = base.savedCallBaseSlot;
+ call.lookupIndex = registerGetterLookup(
+ base.savedCallPropertyNameIndex, JSUnitGenerator::LookupForCall);
+ } else {
+ call.base = base.propertyBase.stackSlot();
+ call.lookupIndex = registerGetterLookup(
base.propertyNameIndex, JSUnitGenerator::LookupForCall);
+ }
call.argc = calldata.argc;
call.argv = calldata.argv;
bytecodeGenerator->addInstruction(call);
} else {
Instruction::CallProperty call;
- call.base = base.propertyBase.stackSlot();
- call.name = base.propertyNameIndex;
+ if (base.hasSavedCallBaseSlot) {
+ call.base = base.savedCallBaseSlot;
+ call.name = base.savedCallPropertyNameIndex;
+ } else {
+ call.base = base.propertyBase.stackSlot();
+ call.name = base.propertyNameIndex;
+ }
call.argc = calldata.argc;
call.argv = calldata.argv;
bytecodeGenerator->addInstruction(call);
@@ -2163,8 +2155,6 @@ void Codegen::handleCall(Reference &base, Arguments calldata, int slotForFunctio
call.argv = calldata.argv;
bytecodeGenerator->addInstruction(call);
}
-
- setExprResult(Reference::fromAccumulator(this));
}
Codegen::Arguments Codegen::pushArgs(ArgumentList *args)
@@ -2274,7 +2264,7 @@ bool Codegen::visit(DeleteExpression *ast)
if (hasError())
return false;
- auto label = traverseOptionalChain(ast);
+ const bool isTailOfChain = traverseOptionalChain(ast);
RegisterScope scope(this);
TailCallBlocker blockTailCalls(this);
@@ -2282,8 +2272,8 @@ bool Codegen::visit(DeleteExpression *ast)
if (hasError())
return false;
- // If there is a label, there is a chain and that should only be possible with those two kinds of references
- if (label.has_value())
+ const bool chainActuallyHasOptionals = m_optionalChainsStates.top().actuallyHasOptionals;
+ if (chainActuallyHasOptionals)
Q_ASSERT(expr.type == Reference::Member || expr.type == Reference::Subscript);
switch (expr.type) {
@@ -2317,10 +2307,11 @@ bool Codegen::visit(DeleteExpression *ast)
//### maybe add a variant where the base can be in the accumulator?
expr = expr.asLValue();
- if (!expr.optionalChainJumpLabel.isNull() && expr.optionalChainJumpLabel->isValid()) {
+ if (chainActuallyHasOptionals) {
expr.loadInAccumulator();
bytecodeGenerator->addInstruction(Instruction::CmpEqNull());
- bytecodeGenerator->jumpTrue().link(*expr.optionalChainJumpLabel.get());
+ auto jumpToUndefined = bytecodeGenerator->jumpTrue();
+ m_optionalChainsStates.top().jumpsToPatch.emplace_back(std::move(jumpToUndefined));
}
Instruction::LoadRuntimeString instr;
@@ -2332,42 +2323,29 @@ bool Codegen::visit(DeleteExpression *ast)
del.base = expr.propertyBase.stackSlot();
del.index = index.stackSlot();
bytecodeGenerator->addInstruction(del);
- setExprResult(Reference::fromAccumulator(this));
-
- if (label.has_value()) {
- auto jump = bytecodeGenerator->jump();
- label->link();
- Instruction::LoadTrue loadTrue;
- bytecodeGenerator->addInstruction(loadTrue);
- jump.link();
- }
+ auto ref = Reference::fromAccumulator(this);
+ optionalChainFinalizer(ref, isTailOfChain, true);
return false;
}
case Reference::Subscript: {
//### maybe add a variant where the index can be in the accumulator?
expr = expr.asLValue();
- if (!expr.optionalChainJumpLabel.isNull() && expr.optionalChainJumpLabel->isValid()) {
+ if (chainActuallyHasOptionals) {
expr.loadInAccumulator();
bytecodeGenerator->addInstruction(Instruction::CmpEqNull());
- bytecodeGenerator->jumpTrue().link(*expr.optionalChainJumpLabel.get());
+ auto jumpToUndefined = bytecodeGenerator->jumpTrue();
+ m_optionalChainsStates.top().jumpsToPatch.emplace_back(std::move(jumpToUndefined));
}
Instruction::DeleteProperty del;
del.base = expr.elementBase;
del.index = expr.elementSubscript.stackSlot();
bytecodeGenerator->addInstruction(del);
- setExprResult(Reference::fromAccumulator(this));
-
- if (label.has_value()) {
- auto jump = bytecodeGenerator->jump();
- label->link();
- Instruction::LoadTrue loadTrue;
- bytecodeGenerator->addInstruction(loadTrue);
- jump.link();
- }
+ auto ref = Reference::fromAccumulator(this);
+ optionalChainFinalizer(ref, isTailOfChain, true);
return false;
}
default:
@@ -2400,72 +2378,104 @@ bool Codegen::visit(SuperLiteral *)
return false;
}
-std::optional<Moth::BytecodeGenerator::Label> Codegen::traverseOptionalChain(Node *node) {
+bool Codegen::traverseOptionalChain(Node *node)
+{
if (m_seenOptionalChainNodes.contains(node))
- return {};
-
- auto label = bytecodeGenerator->newLabel();
+ return false;
- auto isOptionalChainNode = [](const Node *node) {
+ const auto isOptionalChainableNode = [](const Node *node) {
return node->kind == Node::Kind_FieldMemberExpression ||
node->kind == Node::Kind_CallExpression ||
node->kind == Node::Kind_ArrayMemberExpression ||
node->kind == Node::Kind_DeleteExpression;
};
-
- bool labelUsed = false;
-
- while (isOptionalChainNode(node)) {
+ m_optionalChainsStates.emplace();
+ while (isOptionalChainableNode(node)) {
m_seenOptionalChainNodes.insert(node);
switch (node->kind) {
case Node::Kind_FieldMemberExpression: {
- auto *fme = AST::cast<FieldMemberExpression*>(node);
-
- if (fme->isOptional) {
- m_optionalChainLabels.insert(fme, label);
- labelUsed = true;
- }
-
+ auto *fme = AST::cast<FieldMemberExpression *>(node);
+ m_optionalChainsStates.top().actuallyHasOptionals |= fme->isOptional;
node = fme->base;
break;
}
case Node::Kind_CallExpression: {
- auto *ce = AST::cast<CallExpression*>(node);
-
- if (ce->isOptional) {
- m_optionalChainLabels.insert(ce, label);
- labelUsed = true;
- }
-
+ auto *ce = AST::cast<CallExpression *>(node);
+ m_optionalChainsStates.top().actuallyHasOptionals |= ce->isOptional;
node = ce->base;
break;
}
case Node::Kind_ArrayMemberExpression: {
- auto *ame = AST::cast<ArrayMemberExpression*>(node);
-
- if (ame->isOptional) {
- m_optionalChainLabels.insert(ame, label);
- labelUsed = true;
- }
-
+ auto *ame = AST::cast<ArrayMemberExpression *>(node);
+ m_optionalChainsStates.top().actuallyHasOptionals |= ame->isOptional;
node = ame->base;
break;
}
- case Node::Kind_DeleteExpression: {
- auto *de = AST::cast<DeleteExpression*>(node);
- node = de->expression;
+ case Node::Kind_DeleteExpression:
+ node = AST::cast<DeleteExpression *>(node)->expression;
break;
- }
+ default:
+ Q_UNREACHABLE();
}
}
- if (!labelUsed) {
- label.link(); // If we don't link the unused label here, we would hit an assert later.
- return {};
+ return true;
+}
+
+void Codegen::optionalChainFinalizer(Reference expressionResult, bool tailOfChain,
+ bool isDeleteExpression)
+{
+ auto &chainState = m_optionalChainsStates.top();
+ if (!tailOfChain) {
+ setExprResult(expressionResult);
+ return;
+ } else if (!chainState.actuallyHasOptionals) {
+ setExprResult(expressionResult);
+ m_optionalChainsStates.pop();
+ return;
}
- return label;
+ auto savedBaseSlot = -1;
+ if (expressionResult.type == Reference::Member)
+ savedBaseSlot = expressionResult.propertyBase.storeOnStack().stackSlot();
+ expressionResult.loadInAccumulator();
+
+ std::optional<Moth::BytecodeGenerator::Jump> jumpToDone;
+ if (!isDeleteExpression) // Delete expressions always return true, avoid the extra jump
+ jumpToDone.emplace(bytecodeGenerator->jump());
+
+ for (auto &jump : chainState.jumpsToPatch)
+ jump.link();
+
+ if (isDeleteExpression)
+ bytecodeGenerator->addInstruction(Instruction::LoadTrue());
+ else
+ bytecodeGenerator->addInstruction(Instruction::LoadUndefined());
+
+ if (jumpToDone.has_value())
+ jumpToDone.value().link();
+
+ auto ref = Reference::fromAccumulator(this);
+ if (expressionResult.type == Reference::Member) {
+ /* Because the whole optional chain is handled at once with a chain finalizer instead of
+ * instruction by instruction, the result of the chain (either undefined or the result of
+ * the optional operation) is stored in the accumulator. This works fine except for one
+ * edge case where the `this` context is required in a call
+ * (see tst_ecmascripttests: language/expressions/optional-chaining/optional-call-preserves-this.js).
+ *
+ * In order to preserve the `this` context in the call, the call base and the property name
+ * index need to be available as with a Member reference. However, since the result must be
+ * in the accumulator the resulting reference is of type Accumulator. Therefore, the call
+ * base and the property name index are `glued` to an accumulator reference to make it work
+ * when deciding which call instruction to use later on.
+ */
+ ref.hasSavedCallBaseSlot = true;
+ ref.savedCallBaseSlot = savedBaseSlot;
+ ref.savedCallPropertyNameIndex = expressionResult.propertyNameIndex;
+ }
+ setExprResult(ref);
+ m_optionalChainsStates.pop();
}
bool Codegen::visit(FieldMemberExpression *ast)
@@ -2473,9 +2483,10 @@ bool Codegen::visit(FieldMemberExpression *ast)
if (hasError())
return false;
- auto label = traverseOptionalChain(ast);
+ const bool isTailOfChain = traverseOptionalChain(ast);
TailCallBlocker blockTailCalls(this);
+
if (AST::IdentifierExpression *id = AST::cast<AST::IdentifierExpression *>(ast->base)) {
if (id->name == QLatin1String("new")) {
// new.target
@@ -2486,27 +2497,17 @@ bool Codegen::visit(FieldMemberExpression *ast)
r.isReadonly = true;
setExprResult(r);
- if (label.has_value())
- label->link();
-
return false;
}
- Reference r = Reference::fromStackSlot(this, CallData::NewTarget);
- setExprResult(r);
-
- if (label.has_value())
- label->link();
-
+ auto ref = Reference::fromStackSlot(this, CallData::NewTarget);
+ optionalChainFinalizer(ref, isTailOfChain);
return false;
}
}
Reference base = expression(ast->base);
- if (ast->isOptional)
- Q_ASSERT(m_optionalChainLabels.contains(ast));
-
if (hasError())
return false;
if (base.isSuper()) {
@@ -2514,19 +2515,15 @@ bool Codegen::visit(FieldMemberExpression *ast)
load.stringId = registerString(ast->name.toString());
bytecodeGenerator->addInstruction(load);
Reference property = Reference::fromAccumulator(this).storeOnStack();
- setExprResult(Reference::fromSuperProperty(property));
-
- if (label.has_value())
- label->link();
+ optionalChainFinalizer(Reference::fromSuperProperty(property), isTailOfChain);
return false;
}
- setExprResult(Reference::fromMember(
- base, ast->name.toString(), ast->lastSourceLocation(),
- ast->isOptional ? m_optionalChainLabels.take(ast) : Moth::BytecodeGenerator::Label(),
- label.has_value() ? label.value() : Moth::BytecodeGenerator::Label()));
+ auto ref = Reference::fromMember(base, ast->name.toString(), ast->lastSourceLocation(),
+ ast->isOptional, &m_optionalChainsStates.top().jumpsToPatch);
+ optionalChainFinalizer(ref, isTailOfChain);
return false;
}
@@ -2580,6 +2577,7 @@ bool Codegen::handleTaggedTemplate(Reference base, TaggedTemplate *ast)
--calldata.argv;
handleCall(base, calldata, functionObject, thisObject);
+ setExprResult(Reference::fromAccumulator(this));
return false;
}
@@ -4735,12 +4733,11 @@ QT_WARNING_POP
codegen->bytecodeGenerator->setLocation(sourceLocation);
if (codegen->useFastLookups) {
- if (optionalChainJumpLabel->isValid()) {
- // If we got a valid jump label, this means it's an optional lookup
+ if (optionalChainJumpsToPatch && isOptional) {
auto jump = codegen->bytecodeGenerator->jumpOptionalLookup(
codegen->registerGetterLookup(
propertyNameIndex, JSUnitGenerator::LookupForStorage));
- jump.link(*optionalChainJumpLabel.get());
+ optionalChainJumpsToPatch->emplace_back(std::move(jump));
} else {
Instruction::GetLookup load;
load.index = codegen->registerGetterLookup(
@@ -4748,18 +4745,15 @@ QT_WARNING_POP
codegen->bytecodeGenerator->addInstruction(load);
}
} else {
- if (optionalChainJumpLabel->isValid()) {
+ if (optionalChainJumpsToPatch && isOptional) {
auto jump = codegen->bytecodeGenerator->jumpOptionalProperty(propertyNameIndex);
- jump.link(*optionalChainJumpLabel.get());
+ optionalChainJumpsToPatch->emplace_back(std::move(jump));
} else {
Instruction::LoadProperty load;
load.name = propertyNameIndex;
codegen->bytecodeGenerator->addInstruction(load);
}
}
- if (optionalChainTargetLabel->isValid()) {
- optionalChainTargetLabel->link();
- }
return;
case Import: {
Instruction::LoadImport load;
@@ -4774,10 +4768,6 @@ QT_WARNING_POP
Instruction::LoadElement load;
load.base = elementBase;
codegen->bytecodeGenerator->addInstruction(load);
-
- if (optionalChainTargetLabel->isValid()) {
- optionalChainTargetLabel->link();
- }
} return;
case Invalid:
break;