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.cpp486
1 files changed, 256 insertions, 230 deletions
diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp
index 157f831bb8..25831eab73 100644
--- a/src/qml/compiler/qv4codegen.cpp
+++ b/src/qml/compiler/qv4codegen.cpp
@@ -21,9 +21,6 @@
#include <private/qqmljsdiagnosticmessage_p.h>
#include <cmath>
-#include <iostream>
-
-static const bool disable_lookups = false;
#ifdef CONST
#undef CONST
@@ -33,7 +30,8 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-Q_LOGGING_CATEGORY(lcQmlCompiler, "qt.qml.compiler");
+Q_LOGGING_CATEGORY(lcQmlUsedBeforeDeclared, "qt.qml.usedbeforedeclared");
+Q_LOGGING_CATEGORY(lcQmlInjectedParameter, "qt.qml.injectedparameter");
using namespace QV4;
using namespace QV4::Compiler;
@@ -44,7 +42,7 @@ void CodegenWarningInterface::reportVarUsedBeforeDeclaration(
const QString &name, const QString &fileName, QQmlJS::SourceLocation declarationLocation,
QQmlJS::SourceLocation accessLocation)
{
- qCWarning(lcQmlCompiler).nospace().noquote()
+ qCWarning(lcQmlUsedBeforeDeclared).nospace().noquote()
<< fileName << ":" << accessLocation.startLine << ":" << accessLocation.startColumn
<< " Variable \"" << name << "\" is used before its declaration at "
<< declarationLocation.startLine << ":" << declarationLocation.startColumn << ".";
@@ -92,7 +90,7 @@ void Codegen::generateThrowException(const QString &type, const QString &text)
}
Codegen::Codegen(QV4::Compiler::JSUnitGenerator *jsUnitGenerator, bool strict,
- CodegenWarningInterface *interface, bool storeSourceLocations)
+ CodegenWarningInterface *iface, bool storeSourceLocations)
: _module(nullptr),
_returnAddress(-1),
_context(nullptr),
@@ -101,7 +99,7 @@ Codegen::Codegen(QV4::Compiler::JSUnitGenerator *jsUnitGenerator, bool strict,
_strictMode(strict),
storeSourceLocations(storeSourceLocations),
_fileNameIsUrl(false),
- _interface(interface)
+ _interface(iface)
{
jsUnitGenerator->codeGeneratorName = QStringLiteral("moth");
pushExpr();
@@ -714,9 +712,8 @@ void Codegen::destructureElementList(const Codegen::Reference &array, PatternEle
RegisterScope scope(this);
Reference iterator = Reference::fromStackSlot(this);
- Reference iteratorValue = Reference::fromStackSlot(this);
- Reference iteratorDone = Reference::fromStackSlot(this);
- Reference::storeConstOnStack(this, Encode(false), iteratorDone.stackSlot());
+ QVarLengthArray<Reference> iteratorValues;
+ Reference ignored;
array.loadInAccumulator();
Instruction::GetIterator iteratorObjInstr;
@@ -724,45 +721,76 @@ void Codegen::destructureElementList(const Codegen::Reference &array, PatternEle
bytecodeGenerator->addInstruction(iteratorObjInstr);
iterator.storeConsumeAccumulator();
+ BytecodeGenerator::Label done = bytecodeGenerator->newLabel();
+ Reference needsClose = Reference::storeConstOnStack(this, Encode(false));
+
+ for (PatternElementList *p = bindingList; p; p = p->next) {
+ PatternElement *e = p->element;
+ for (Elision *elision = p->elision; elision; elision = elision->next) {
+ iterator.loadInAccumulator();
+ Instruction::IteratorNext next;
+ if (!ignored.isValid())
+ ignored = Reference::fromStackSlot(this);
+ next.value = ignored.stackSlot();
+ bytecodeGenerator->addJumpInstruction(next).link(done);
+ }
+
+ if (!e)
+ continue;
+
+ if (e->type != PatternElement::RestElement) {
+ iterator.loadInAccumulator();
+ Instruction::IteratorNext next;
+ iteratorValues.push_back(Reference::fromStackSlot(this));
+ next.value = iteratorValues.back().stackSlot();
+ bytecodeGenerator->addJumpInstruction(next).link(done);
+ }
+ }
+
+ // If we've iterated through all the patterns without exhausing the iterator, it needs
+ // to be closed. But we don't close it here because:
+ // a, closing might throw an exception and we want to assign the values before we handle that
+ // b, there might be a rest element that could still continue iterating
+ Reference::fromConst(this, Encode(true)).storeOnStack(needsClose.stackSlot());
+
+ done.link();
+ bytecodeGenerator->checkException();
+
{
- auto cleanup = [this, iterator, iteratorDone]() {
+ ControlFlowUnwindCleanup flow(this, [&]() {
+ BytecodeGenerator::Label skipClose = bytecodeGenerator->newLabel();
+ needsClose.loadInAccumulator();
+ bytecodeGenerator->jumpFalse().link(skipClose);
iterator.loadInAccumulator();
Instruction::IteratorClose close;
- close.done = iteratorDone.stackSlot();
bytecodeGenerator->addInstruction(close);
- };
-
- ControlFlowUnwindCleanup flow(this, cleanup);
+ skipClose.link();
+ });
+ auto it = iteratorValues.constBegin();
for (PatternElementList *p = bindingList; p; p = p->next) {
PatternElement *e = p->element;
- for (Elision *elision = p->elision; elision; elision = elision->next) {
- iterator.loadInAccumulator();
- Instruction::IteratorNext next;
- next.value = iteratorValue.stackSlot();
- next.done = iteratorDone.stackSlot();
- bytecodeGenerator->addInstruction(next);
- }
if (!e)
continue;
- RegisterScope scope(this);
- iterator.loadInAccumulator();
-
if (e->type == PatternElement::RestElement) {
- Reference::fromConst(this, Encode(true)).storeOnStack(iteratorDone.stackSlot());
+ Q_ASSERT(it == iteratorValues.constEnd());
+
+ // The rest element is guaranteed to exhaust the iterator
+ Reference::fromConst(this, Encode(false)).storeOnStack(needsClose.stackSlot());
+
+ iterator.loadInAccumulator();
bytecodeGenerator->addInstruction(Instruction::DestructureRestElement());
- initializeAndDestructureBindingElement(e, Reference::fromAccumulator(this), isDefinition);
+ initializeAndDestructureBindingElement(
+ e, Reference::fromAccumulator(this), isDefinition);
} else {
- Instruction::IteratorNext next;
- next.value = iteratorValue.stackSlot();
- next.done = iteratorDone.stackSlot();
- bytecodeGenerator->addInstruction(next);
- initializeAndDestructureBindingElement(e, iteratorValue, isDefinition);
- if (hasError())
- return;
+ Q_ASSERT(it != iteratorValues.constEnd());
+ initializeAndDestructureBindingElement(e, *it++, isDefinition);
}
+
+ if (hasError())
+ return;
}
}
}
@@ -905,6 +933,11 @@ bool Codegen::visit(UiHeaderItemList *)
Q_UNREACHABLE_RETURN(false);
}
+bool Codegen::visit(UiPragmaValueList *)
+{
+ Q_UNREACHABLE_RETURN(false);
+}
+
bool Codegen::visit(UiPragma *)
{
Q_UNREACHABLE_RETURN(false);
@@ -1163,7 +1196,6 @@ bool Codegen::visit(ArrayPattern *ast)
RegisterScope scope(this);
Reference iterator = Reference::fromStackSlot(this);
- Reference iteratorDone = Reference::fromConst(this, Encode(false)).storeOnStack();
Reference lhsValue = Reference::fromStackSlot(this);
// There should be a temporal block, so that variables declared in lhs shadow outside vars.
@@ -1183,24 +1215,23 @@ bool Codegen::visit(ArrayPattern *ast)
BytecodeGenerator::Label in = bytecodeGenerator->newLabel();
BytecodeGenerator::Label end = bytecodeGenerator->newLabel();
+ BytecodeGenerator::Label done = bytecodeGenerator->newLabel();
{
- auto cleanup = [this, iterator, iteratorDone]() {
+ auto cleanup = [this, iterator, done]() {
iterator.loadInAccumulator();
Instruction::IteratorClose close;
- close.done = iteratorDone.stackSlot();
bytecodeGenerator->addInstruction(close);
+ done.link();
};
- ControlFlowLoop flow(this, &end, &in, cleanup);
+ ControlFlowLoop flow(this, &end, &in, std::move(cleanup));
in.link();
bytecodeGenerator->addLoopStart(in);
iterator.loadInAccumulator();
Instruction::IteratorNext next;
next.value = lhsValue.stackSlot();
- next.done = iteratorDone.stackSlot();
- bytecodeGenerator->addInstruction(next);
- bytecodeGenerator->addJumpInstruction(Instruction::JumpTrue()).link(end);
+ bytecodeGenerator->addJumpInstruction(next).link(done);
lhsValue.loadInAccumulator();
pushAccumulator();
@@ -1230,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();
@@ -1268,11 +1290,11 @@ 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);
+ optionalChainFinalizer(ref, isTailOfChain);
return false;
}
@@ -1280,7 +1302,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;
}
@@ -1293,8 +1315,7 @@ bool Codegen::visit(ArrayMemberExpression *ast)
if (hasError())
return false;
- setExprResult(Reference::fromSubscript(base, index, targetLabel));
-
+ optionalChainFinalizer(Reference::fromSubscript(base, index), isTailOfChain);
return false;
}
@@ -1394,26 +1415,20 @@ bool Codegen::visit(BinaryExpression *ast)
return false;
BytecodeGenerator::Label iftrue = bytecodeGenerator->newLabel();
- BytecodeGenerator::Label iffalse = bytecodeGenerator->newLabel();
- Instruction::CmpNeNull cmp;
+ Instruction::CmpEqNull cmp;
left = left.storeOnStack();
left.loadInAccumulator();
bytecodeGenerator->addInstruction(cmp);
bytecodeGenerator->jumpTrue().link(iftrue);
- bytecodeGenerator->jumpFalse().link(iffalse);
blockTailCalls.unblock();
- iftrue.link();
-
left.loadInAccumulator();
-
BytecodeGenerator::Jump jump_endif = bytecodeGenerator->jump();
-
- iffalse.link();
+ iftrue.link();
Reference right = expression(ast->right);
right.loadInAccumulator();
@@ -1496,7 +1511,7 @@ bool Codegen::visit(BinaryExpression *ast)
if (hasError())
return false;
- binopHelper(baseOp(ast->op), tempLeft, right).loadInAccumulator();
+ binopHelper(ast, baseOp(ast->op), tempLeft, right).loadInAccumulator();
setExprResult(left.storeRetainAccumulator());
break;
@@ -1509,7 +1524,7 @@ bool Codegen::visit(BinaryExpression *ast)
Reference right = expression(ast->right);
if (hasError())
return false;
- setExprResult(binopHelper(static_cast<QSOperator::Op>(ast->op), right, left));
+ setExprResult(binopHelper(ast, static_cast<QSOperator::Op>(ast->op), right, left));
break;
}
Q_FALLTHROUGH();
@@ -1544,7 +1559,7 @@ bool Codegen::visit(BinaryExpression *ast)
if (hasError())
return false;
- setExprResult(binopHelper(static_cast<QSOperator::Op>(ast->op), left, right));
+ setExprResult(binopHelper(ast, static_cast<QSOperator::Op>(ast->op), left, right));
break;
}
@@ -1553,8 +1568,11 @@ bool Codegen::visit(BinaryExpression *ast)
return false;
}
-Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Reference &right)
+Codegen::Reference Codegen::binopHelper(BinaryExpression *ast, QSOperator::Op oper, Reference &left,
+ Reference &right)
{
+ auto loc = combine(ast->left->firstSourceLocation(), ast->right->lastSourceLocation());
+ bytecodeGenerator->setLocation(loc);
switch (oper) {
case QSOperator::Add: {
left = left.storeOnStack();
@@ -1946,12 +1964,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;
@@ -1975,21 +1994,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 || m_optionalChainsStates.top().actuallyHasOptionals) {
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);
@@ -2032,20 +2051,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;
}
@@ -2060,19 +2071,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 (!disable_lookups && useFastLookups) {
+ 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);
@@ -2090,7 +2112,7 @@ void Codegen::handleCall(Reference &base, Arguments calldata, int slotForFunctio
call.argc = calldata.argc;
call.argv = calldata.argv;
bytecodeGenerator->addInstruction(call);
- } else if (!disable_lookups && useFastLookups && base.global) {
+ } else if (useFastLookups && base.global) {
if (base.qmlGlobal) {
Instruction::CallQmlContextPropertyLookup call;
call.index = registerQmlContextPropertyGetterLookup(
@@ -2137,8 +2159,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)
@@ -2248,7 +2268,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);
@@ -2256,8 +2276,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) {
@@ -2291,10 +2311,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;
@@ -2306,42 +2327,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:
@@ -2374,72 +2382,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(const 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)
@@ -2447,9 +2487,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
@@ -2460,27 +2501,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()) {
@@ -2488,19 +2519,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;
}
@@ -2554,6 +2581,7 @@ bool Codegen::handleTaggedTemplate(Reference base, TaggedTemplate *ast)
--calldata.argv;
handleCall(base, calldata, functionObject, thisObject);
+ setExprResult(Reference::fromAccumulator(this));
return false;
}
@@ -2612,7 +2640,7 @@ Codegen::Reference Codegen::referenceForName(const QString &name, bool isLhs, co
}
if (resolved.isInjected && accessLocation.isValid()) {
- qCWarning(lcQmlCompiler).nospace().noquote()
+ qCWarning(lcQmlInjectedParameter).nospace().noquote()
<< url().toString() << ":" << accessLocation.startLine
<< ":" << accessLocation.startColumn << " Parameter \"" << name
<< "\" is not declared."
@@ -3222,15 +3250,15 @@ bool Codegen::visit(YieldExpression *ast)
Instruction::IteratorNextForYieldStar next;
next.object = lhsValue.stackSlot();
next.iterator = iterator.stackSlot();
- bytecodeGenerator->addInstruction(next);
-
- BytecodeGenerator::Jump done = bytecodeGenerator->jumpTrue();
+ BytecodeGenerator::Jump done = bytecodeGenerator->addJumpInstruction(next);
bytecodeGenerator->jumpNotUndefined().link(loop);
+
lhsValue.loadInAccumulator();
emitReturn(acc);
done.link();
+ bytecodeGenerator->checkException();
lhsValue.loadInAccumulator();
setExprResult(acc);
@@ -3411,8 +3439,9 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, AST::FormalPara
if (showCode) {
qDebug() << "=== Bytecode for" << _context->name << "strict mode" << _context->isStrict
<< "register count" << _context->registerCountInFunction << "implicit return" << requiresReturnValue;
- QV4::Moth::dumpBytecode(_context->code, _context->locals.size(), _context->arguments.size(),
- _context->line, _context->lineAndStatementNumberMapping);
+ qDebug().noquote() << QV4::Moth::dumpBytecode(
+ _context->code, _context->locals.size(), _context->arguments.size(),
+ _context->line, _context->lineAndStatementNumberMapping);
qDebug();
}
}
@@ -3573,7 +3602,6 @@ bool Codegen::visit(ForEachStatement *ast)
TailCallBlocker blockTailCalls(this);
Reference iterator = Reference::fromStackSlot(this);
- Reference iteratorDone = Reference::fromConst(this, Encode(false)).storeOnStack();
Reference lhsValue = Reference::fromStackSlot(this);
// There should be a temporal block, so that variables declared in lhs shadow outside vars.
@@ -3594,25 +3622,28 @@ bool Codegen::visit(ForEachStatement *ast)
BytecodeGenerator::Label in = bytecodeGenerator->newLabel();
BytecodeGenerator::Label end = bytecodeGenerator->newLabel();
+ BytecodeGenerator::Label done;
{
- auto cleanup = [ast, iterator, iteratorDone, this]() {
- if (ast->type == ForEachType::Of) {
+ std::function<void()> cleanup;
+ if (ast->type == ForEachType::Of) {
+ done = bytecodeGenerator->newLabel();
+ cleanup = [iterator, this, done]() {
iterator.loadInAccumulator();
Instruction::IteratorClose close;
- close.done = iteratorDone.stackSlot();
bytecodeGenerator->addInstruction(close);
- }
- };
- ControlFlowLoop flow(this, &end, &in, cleanup);
+ done.link();
+ };
+ } else {
+ done = end;
+ }
+ ControlFlowLoop flow(this, &end, &in, std::move(cleanup));
bytecodeGenerator->addLoopStart(in);
in.link();
iterator.loadInAccumulator();
Instruction::IteratorNext next;
next.value = lhsValue.stackSlot();
- next.done = iteratorDone.stackSlot();
- bytecodeGenerator->addInstruction(next);
- bytecodeGenerator->addJumpInstruction(Instruction::JumpTrue()).link(end);
+ bytecodeGenerator->addJumpInstruction(next).link(done);
// each iteration gets it's own context, as per spec
{
@@ -3928,7 +3959,8 @@ void Codegen::handleTryCatch(TryStatement *ast)
void Codegen::handleTryFinally(TryStatement *ast)
{
RegisterScope scope(this);
- ControlFlowFinally finally(this, ast->finallyExpression);
+ const bool hasCatchBlock = ast->catchExpression;
+ ControlFlowFinally finally(this, ast->finallyExpression, hasCatchBlock);
TailCallBlocker blockTailCalls(this); // IMPORTANT: destruction will unblock tail calls before finally is generated
if (ast->catchExpression) {
@@ -4100,14 +4132,16 @@ QQmlJS::DiagnosticMessage Codegen::error() const
return _error;
}
-QV4::CompiledData::CompilationUnit Codegen::generateCompilationUnit(
+QQmlRefPointer<QV4::CompiledData::CompilationUnit> Codegen::generateCompilationUnit(
bool generateUnitData)
{
- return QV4::CompiledData::CompilationUnit(
- generateUnitData ? jsUnitGenerator->generateUnit() : nullptr);
+ return QQmlRefPointer<QV4::CompiledData::CompilationUnit>(
+ new QV4::CompiledData::CompilationUnit(
+ generateUnitData ? jsUnitGenerator->generateUnit() : nullptr),
+ QQmlRefPointer<QV4::CompiledData::CompilationUnit>::Adopt);
}
-CompiledData::CompilationUnit Codegen::compileModule(
+QQmlRefPointer<QV4::CompiledData::CompilationUnit> Codegen::compileModule(
bool debugMode, const QString &url, const QString &sourceCode,
const QDateTime &sourceTimeStamp, QList<QQmlJS::DiagnosticMessage> *diagnostics)
{
@@ -4122,7 +4156,7 @@ CompiledData::CompilationUnit Codegen::compileModule(
*diagnostics = parser.diagnosticMessages();
if (!parsed)
- return CompiledData::CompilationUnit();
+ return QQmlRefPointer<CompiledData::CompilationUnit>();
QQmlJS::AST::ESModule *moduleNode = QQmlJS::AST::cast<QQmlJS::AST::ESModule*>(parser.rootNode());
if (!moduleNode) {
@@ -4143,7 +4177,7 @@ CompiledData::CompilationUnit Codegen::compileModule(
if (cg.hasError()) {
if (diagnostics)
*diagnostics << cg.error();
- return CompiledData::CompilationUnit();
+ return QQmlRefPointer<CompiledData::CompilationUnit>();
}
return cg.generateCompilationUnit();
@@ -4561,7 +4595,7 @@ void Codegen::Reference::storeAccumulator() const
}
} return;
case Member:
- if (!disable_lookups && codegen->useFastLookups) {
+ if (codegen->useFastLookups) {
Instruction::SetLookup store;
store.base = propertyBase.stackSlot();
store.index = codegen->registerSetterLookup(propertyNameIndex);
@@ -4621,7 +4655,7 @@ QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") // the loads below are empty str
StaticValue p = StaticValue::fromReturnedValue(constant);
if (p.isNumber()) {
double d = p.asDouble();
- int i = static_cast<int>(d);
+ int i = QJSNumberCoercion::toInteger(d);
if (d == i && (d != 0 || !std::signbit(d))) {
if (!i) {
Instruction::LoadZero load;
@@ -4679,7 +4713,7 @@ QT_WARNING_POP
if (sourceLocation.isValid())
codegen->bytecodeGenerator->setLocation(sourceLocation);
- if (!disable_lookups && global) {
+ if (global) {
if (qmlGlobal) {
Instruction::LoadQmlContextPropertyLookup load;
load.index = codegen->registerQmlContextPropertyGetterLookup(
@@ -4704,13 +4738,12 @@ QT_WARNING_POP
if (sourceLocation.isValid())
codegen->bytecodeGenerator->setLocation(sourceLocation);
- if (!disable_lookups && codegen->useFastLookups) {
- if (optionalChainJumpLabel->isValid()) {
- // If we got a valid jump label, this means it's an optional lookup
+ if (codegen->useFastLookups) {
+ 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(
@@ -4718,18 +4751,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;
@@ -4744,10 +4774,6 @@ QT_WARNING_POP
Instruction::LoadElement load;
load.base = elementBase;
codegen->bytecodeGenerator->addInstruction(load);
-
- if (optionalChainTargetLabel->isValid()) {
- optionalChainTargetLabel->link();
- }
} return;
case Invalid:
break;