diff options
author | Maximilian Goldstein <max.goldstein@qt.io> | 2021-04-09 17:42:06 +0200 |
---|---|---|
committer | Maximilian Goldstein <max.goldstein@qt.io> | 2021-04-13 19:19:56 +0200 |
commit | 5f7ecce23321f499b1b002c32a27c63815535baa (patch) | |
tree | 4e1e23e18cacd4e0d9883fc0f581a77cd3df56e6 /src/qml/compiler/qv4codegen.cpp | |
parent | 9b0069d94a5b725923e303ccdb3d7739088e06fc (diff) |
Implement optional chaining
This change implements optional chaining (https://github.com/tc39/proposal-optional-chaining) by adding a new type of optional lookup with an offset to the end of a chain.
If `undefined` or `null` is encountered during an access marked as optional, we jump to that end offset.
Features:
- Full support for all kinds of optional chain
- With some codegen overhead but zero overhead during normal non-optional FieldMemberExpression resolution
- Properly retains this contexts and does not need to resolve anything twice (this has been an issue previously)
- No extra AST structures, just flags for existing ones
[ChangeLog][QtQml] Added support for optional chaining (https://github.com/tc39/proposal-optional-chaining)
Fixes: QTBUG-77926
Change-Id: I9a41cdc4ca272066c79c72b9b22206498a546843
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'src/qml/compiler/qv4codegen.cpp')
-rw-r--r-- | src/qml/compiler/qv4codegen.cpp | 244 |
1 files changed, 231 insertions, 13 deletions
diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index ab40f0ceba..d13ef1bc86 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -1253,11 +1253,30 @@ 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)); + + 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(); + }; + if (hasError()) return false; if (base.isSuper()) { @@ -1272,17 +1291,31 @@ bool Codegen::visit(ArrayMemberExpression *ast) QString s = str->value.toString(); uint arrayIndex = stringToArrayIndex(s); if (arrayIndex == UINT_MAX) { - setExprResult(Reference::fromMember(base, str->value.toString())); + auto jumpLabel = ast->isOptional ? m_optionalChainLabels.take(ast) : Moth::BytecodeGenerator::Label(); + + setExprResult(Reference::fromMember(base, str->value.toString(), jumpLabel, targetLabel)); return false; } + + if (ast->isOptional) + writeSkip(); + Reference index = Reference::fromConst(this, QV4::Encode(arrayIndex)); - setExprResult(Reference::fromSubscript(base, index)); + setExprResult(Reference::fromSubscript(base, index, targetLabel)); return false; } + + + if (ast->isOptional) + writeSkip(); + Reference index = expression(ast->expression); + if (hasError()) return false; - setExprResult(Reference::fromSubscript(base, index)); + + setExprResult(Reference::fromSubscript(base, index, targetLabel)); + return false; } @@ -1917,6 +1950,8 @@ bool Codegen::visit(CallExpression *ast) if (hasError()) return false; + auto label = traverseOptionalChain(ast); + RegisterScope scope(this); TailCallBlocker blockTailCalls(this); @@ -1944,6 +1979,22 @@ bool Codegen::visit(CallExpression *ast) 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(); + + auto acc = Reference::fromAccumulator(this).storeOnStack(); + base.loadInAccumulator(); + bytecodeGenerator->addInstruction(Instruction::CmpEqNull()); + auto jumpFalse = bytecodeGenerator->jumpFalse(); + bytecodeGenerator->addInstruction(Instruction::LoadUndefined()); + bytecodeGenerator->jump().link(jumpLabel); + jumpFalse.link(); + acc.loadInAccumulator(); + } + auto calldata = pushArgs(ast->arguments); if (hasError()) return false; @@ -1977,15 +2028,28 @@ bool Codegen::visit(CallExpression *ast) } setExprResult(Reference::fromAccumulator(this)); + + if (label.has_value()) + label->link(); + return false; } - handleCall(base, calldata, functionObject, thisObject); + handleCall(base, calldata, functionObject, thisObject, ast->isOptional); + + if (label.has_value()) + label->link(); + return false; } -void Codegen::handleCall(Reference &base, Arguments calldata, int slotForFunction, int slotForThisObject) +void Codegen::endVisit(CallExpression *ast) +{ + m_seenOptionalChainNodes.remove(ast); +} + +void Codegen::handleCall(Reference &base, Arguments calldata, int slotForFunction, int slotForThisObject, bool optional) { //### Do we really need all these call instructions? can's we load the callee in a temp? if (base.type == Reference::Member) { @@ -2012,7 +2076,7 @@ void Codegen::handleCall(Reference &base, Arguments calldata, int slotForFunctio call.argv = calldata.argv; bytecodeGenerator->addInstruction(call); } else if (base.type == Reference::Name) { - if (base.name == QStringLiteral("eval")) { + if (base.name == QStringLiteral("eval") && !optional) { Instruction::CallPossiblyDirectEval call; call.argc = calldata.argc; call.argv = calldata.argv; @@ -2171,12 +2235,18 @@ bool Codegen::visit(DeleteExpression *ast) if (hasError()) return false; + auto label = traverseOptionalChain(ast); + RegisterScope scope(this); TailCallBlocker blockTailCalls(this); Reference expr = expression(ast->expression); 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()) + Q_ASSERT(expr.type == Reference::Member || expr.type == Reference::Subscript); + switch (expr.type) { case Reference::SuperProperty: // ### this should throw a reference error at runtime. @@ -2207,6 +2277,13 @@ bool Codegen::visit(DeleteExpression *ast) case Reference::Member: { //### maybe add a variant where the base can be in the accumulator? expr = expr.asLValue(); + + if (!expr.optionalChainJumpLabel.isNull() && expr.optionalChainJumpLabel->isValid()) { + expr.loadInAccumulator(); + bytecodeGenerator->addInstruction(Instruction::CmpEqNull()); + bytecodeGenerator->jumpTrue().link(*expr.optionalChainJumpLabel.get()); + } + Instruction::LoadRuntimeString instr; instr.stringId = expr.propertyNameIndex; bytecodeGenerator->addInstruction(instr); @@ -2217,16 +2294,41 @@ bool Codegen::visit(DeleteExpression *ast) 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(); + } + 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()) { + expr.loadInAccumulator(); + bytecodeGenerator->addInstruction(Instruction::CmpEqNull()); + bytecodeGenerator->jumpTrue().link(*expr.optionalChainJumpLabel.get()); + } + 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(); + } + return false; } default: @@ -2237,6 +2339,10 @@ bool Codegen::visit(DeleteExpression *ast) return false; } +void Codegen::endVisit(DeleteExpression *ast) { + m_seenOptionalChainNodes.remove(ast); +} + bool Codegen::visit(FalseLiteral *) { if (hasError()) @@ -2255,11 +2361,81 @@ bool Codegen::visit(SuperLiteral *) return false; } +std::optional<Moth::BytecodeGenerator::Label> Codegen::traverseOptionalChain(Node *node) { + if (m_seenOptionalChainNodes.contains(node)) + return {}; + + auto label = bytecodeGenerator->newLabel(); + + auto isOptionalChainNode = [](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_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; + } + + node = fme->base; + break; + } + case Node::Kind_CallExpression: { + auto *ce = AST::cast<CallExpression*>(node); + + if (ce->isOptional) { + m_optionalChainLabels.insert(ce, label); + labelUsed = true; + } + + node = ce->base; + break; + } + case Node::Kind_ArrayMemberExpression: { + auto *ame = AST::cast<ArrayMemberExpression*>(node); + + if (ame->isOptional) { + m_optionalChainLabels.insert(ame, label); + labelUsed = true; + } + + node = ame->base; + break; + } + case Node::Kind_DeleteExpression: { + auto *de = AST::cast<DeleteExpression*>(node); + node = de->expression; + break; + } + } + } + + if (!labelUsed) { + label.link(); // If we don't link the unused label here, we would hit an assert later. + return {}; + } + + return label; +} + bool Codegen::visit(FieldMemberExpression *ast) { if (hasError()) return false; + auto label = traverseOptionalChain(ast); + TailCallBlocker blockTailCalls(this); if (AST::IdentifierExpression *id = AST::cast<AST::IdentifierExpression *>(ast->base)) { if (id->name == QLatin1String("new")) { @@ -2270,16 +2446,28 @@ bool Codegen::visit(FieldMemberExpression *ast) Reference r = referenceForName(QStringLiteral("new.target"), false); 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(); + return false; } } Reference base = expression(ast->base); + + if (ast->isOptional) + Q_ASSERT(m_optionalChainLabels.contains(ast)); + if (hasError()) return false; if (base.isSuper()) { @@ -2288,12 +2476,25 @@ bool Codegen::visit(FieldMemberExpression *ast) bytecodeGenerator->addInstruction(load); Reference property = Reference::fromAccumulator(this).storeOnStack(); setExprResult(Reference::fromSuperProperty(property)); + + if (label.has_value()) + label->link(); + return false; } - setExprResult(Reference::fromMember(base, ast->name.toString())); + + setExprResult(Reference::fromMember(base, ast->name.toString(), + ast->isOptional ? m_optionalChainLabels.take(ast) : Moth::BytecodeGenerator::Label(), + label.has_value() ? label.value() : Moth::BytecodeGenerator::Label())); + return false; } +void Codegen::endVisit(FieldMemberExpression *ast) +{ + m_seenOptionalChainNodes.remove(ast); +} + bool Codegen::visit(TaggedTemplate *ast) { if (hasError()) @@ -4454,13 +4655,26 @@ QT_WARNING_POP propertyBase.loadInAccumulator(); tdzCheck(requiresTDZCheck); if (!disable_lookups && codegen->useFastLookups) { - Instruction::GetLookup load; - load.index = codegen->registerGetterLookup(propertyNameIndex); - codegen->bytecodeGenerator->addInstruction(load); + if (optionalChainJumpLabel->isValid()) { // If we got a valid jump label, this means it's an optional lookup + auto jump = codegen->bytecodeGenerator->jumpOptionalLookup(codegen->registerGetterLookup(propertyNameIndex)); + jump.link(*optionalChainJumpLabel.get()); + } else { + Instruction::GetLookup load; + load.index = codegen->registerGetterLookup(propertyNameIndex); + codegen->bytecodeGenerator->addInstruction(load); + } } else { - Instruction::LoadProperty load; - load.name = propertyNameIndex; - codegen->bytecodeGenerator->addInstruction(load); + if (optionalChainJumpLabel->isValid()) { + auto jump = codegen->bytecodeGenerator->jumpOptionalProperty(propertyNameIndex); + jump.link(*optionalChainJumpLabel.get()); + } else { + Instruction::LoadProperty load; + load.name = propertyNameIndex; + codegen->bytecodeGenerator->addInstruction(load); + } + } + if (optionalChainTargetLabel->isValid()) { + optionalChainTargetLabel->link(); } return; case Import: { @@ -4476,6 +4690,10 @@ QT_WARNING_POP Instruction::LoadElement load; load.base = elementBase; codegen->bytecodeGenerator->addInstruction(load); + + if (optionalChainTargetLabel->isValid()) { + optionalChainTargetLabel->link(); + } } return; case Invalid: break; |