diff options
Diffstat (limited to 'src/qml/compiler/qv4codegen.cpp')
-rw-r--r-- | src/qml/compiler/qv4codegen.cpp | 486 |
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; |