From 744fe84d890479962ccb85d0ed6c4515a6ea11f4 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Thu, 28 Feb 2019 15:11:36 +0100 Subject: Save some stack space during code generation Result objects are rather large, 96 bytes here. In a recursive algorithm such as our parser, we should not keep too many of them on the stack. Also, the size of Reference can be reduced by employing a bit field rather than a number of booleans. Also, try to convince the compiler to inline the accept() functions. The extra stack frames those create are unnecessary. Task-number: QTBUG-74087 Change-Id: I5c064491172366bb0abef99ffe9314080401a7d1 Reviewed-by: Simon Hausmann --- src/qml/compiler/qqmlirbuilder.cpp | 2 +- src/qml/compiler/qv4codegen.cpp | 221 +++++++++++++++++-------------------- src/qml/compiler/qv4codegen_p.h | 88 ++++++++++++--- src/qml/parser/qqmljsast.cpp | 15 --- src/qml/parser/qqmljsast_p.h | 19 +++- 5 files changed, 192 insertions(+), 153 deletions(-) (limited to 'src/qml') diff --git a/src/qml/compiler/qqmlirbuilder.cpp b/src/qml/compiler/qqmlirbuilder.cpp index 2421e8c1ea..59b9968bf6 100644 --- a/src/qml/compiler/qqmlirbuilder.cpp +++ b/src/qml/compiler/qqmlirbuilder.cpp @@ -566,7 +566,7 @@ bool IRBuilder::visit(QQmlJS::AST::UiQualifiedId *id) void IRBuilder::accept(QQmlJS::AST::Node *node) { - QQmlJS::AST::Node::acceptChild(node, this); + QQmlJS::AST::Node::accept(node, this); } bool IRBuilder::defineQMLObject(int *objectIndex, QQmlJS::AST::UiQualifiedId *qualifiedTypeNameId, const QQmlJS::AST::SourceLocation &location, QQmlJS::AST::UiObjectInitializer *initializer, Object *declarationsOverride) diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index 256ab6e0be..b4da05eb9a 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -100,6 +100,7 @@ Codegen::Codegen(QV4::Compiler::JSUnitGenerator *jsUnitGenerator, bool strict) , hasError(false) { jsUnitGenerator->codeGeneratorName = QStringLiteral("moth"); + pushExpr(); } const char *globalNames[] = { @@ -264,7 +265,7 @@ Context *Codegen::enterBlock(Node *node) Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr) { if (hasError) - return _expr.result(); + return exprResult(); if (expr.isConstant()) { auto v = Value::fromReturnedValue(expr.constant); @@ -310,7 +311,7 @@ Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr) return Reference::fromAccumulator(this); } case PostIncrement: - if (!_expr.accept(nx) || requiresReturnValue) { + if (!exprAccept(nx) || requiresReturnValue) { Reference e = expr.asLValue(); e.loadInAccumulator(); Instruction::UPlus uplus; @@ -330,13 +331,13 @@ Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr) e.loadInAccumulator(); Instruction::Increment inc; bytecodeGenerator->addInstruction(inc); - if (_expr.accept(nx)) + if (exprAccept(nx)) return e.storeConsumeAccumulator(); else return e.storeRetainAccumulator(); } case PostDecrement: - if (!_expr.accept(nx) || requiresReturnValue) { + if (!exprAccept(nx) || requiresReturnValue) { Reference e = expr.asLValue(); e.loadInAccumulator(); Instruction::UPlus uplus; @@ -356,7 +357,7 @@ Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr) e.loadInAccumulator(); Instruction::Decrement dec; bytecodeGenerator->addInstruction(dec); - if (_expr.accept(nx)) + if (exprAccept(nx)) return e.storeConsumeAccumulator(); else return e.storeRetainAccumulator(); @@ -368,17 +369,9 @@ Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr) void Codegen::addCJump() { - bytecodeGenerator->addCJumpInstruction(_expr.trueBlockFollowsCondition(), - _expr.iftrue(), _expr.iffalse()); -} - -void Codegen::accept(Node *node) -{ - if (hasError) - return; - - if (node) - node->accept(this); + const Result &expression = currentExpr(); + bytecodeGenerator->addCJumpInstruction(expression.trueBlockFollowsCondition(), + expression.iftrue(), expression.iffalse()); } void Codegen::statement(Statement *ast) @@ -402,20 +395,19 @@ void Codegen::statement(ExpressionNode *ast) RecursionDepthCheck depthCheck(this, ast->lastSourceLocation()); RegisterScope scope(this); - Result r(nx); - qSwap(_expr, r); + pushExpr(Result(nx)); VolatileMemoryLocations vLocs = scanVolatileMemoryLocations(ast); qSwap(_volatileMemoryLocations, vLocs); accept(ast); qSwap(_volatileMemoryLocations, vLocs); - qSwap(_expr, r); + Reference result = popResult(); if (hasError) return; - if (r.result().loadTriggersSideEffect()) - r.result().loadInAccumulator(); // triggers side effects + if (result.loadTriggersSideEffect()) + result.loadInAccumulator(); // triggers side effects } } @@ -429,10 +421,9 @@ void Codegen::condition(ExpressionNode *ast, const BytecodeGenerator::Label *ift return; RecursionDepthCheck depthCheck(this, ast->lastSourceLocation()); - Result r(iftrue, iffalse, trueBlockFollowsCondition); - qSwap(_expr, r); + pushExpr(Result(iftrue, iffalse, trueBlockFollowsCondition)); accept(ast); - qSwap(_expr, r); + Result r = popExpr(); if (hasError) return; @@ -450,18 +441,6 @@ void Codegen::condition(ExpressionNode *ast, const BytecodeGenerator::Label *ift } } -Codegen::Reference Codegen::expression(ExpressionNode *ast) -{ - RecursionDepthCheck depthCheck(this, ast->lastSourceLocation()); - Result r; - if (ast) { - qSwap(_expr, r); - accept(ast); - qSwap(_expr, r); - } - return r.result(); -} - void Codegen::program(Program *ast) { if (ast) { @@ -875,17 +854,13 @@ bool Codegen::visit(ExportDeclaration *ast) Reference exportedValue; if (auto *fdecl = AST::cast(ast->variableStatementOrDeclaration)) { - Result r; - qSwap(_expr, r); + pushExpr(); visit(static_cast(fdecl)); - qSwap(_expr, r); - exportedValue = r.result(); + exportedValue = popResult(); } else if (auto *classDecl = AST::cast(ast->variableStatementOrDeclaration)) { - Result r; - qSwap(_expr, r); + pushExpr(); visit(static_cast(classDecl)); - qSwap(_expr, r); - exportedValue = r.result(); + exportedValue = popResult(); } else if (ExpressionNode *expr = ast->variableStatementOrDeclaration->expressionCast()) { exportedValue = expression(expr); } @@ -1068,7 +1043,7 @@ bool Codegen::visit(ClassExpression *ast) (void) ctor.storeRetainAccumulator(); } - _expr.setResult(Reference::fromAccumulator(this)); + setExprResult(Reference::fromAccumulator(this)); return false; } @@ -1151,7 +1126,7 @@ bool Codegen::visit(ArrayPattern *ast) } if (!it) { - _expr.setResult(Reference::fromAccumulator(this)); + setExprResult(Reference::fromAccumulator(this)); return false; } Q_ASSERT(it->element && it->element->type == PatternElement::SpreadElement); @@ -1245,7 +1220,7 @@ bool Codegen::visit(ArrayPattern *ast) } array.loadInAccumulator(); - _expr.setResult(Reference::fromAccumulator(this)); + setExprResult(Reference::fromAccumulator(this)); return false; } @@ -1261,7 +1236,7 @@ bool Codegen::visit(ArrayMemberExpression *ast) return false; if (base.isSuper()) { Reference index = expression(ast->expression).storeOnStack(); - _expr.setResult(Reference::fromSuperProperty(index)); + setExprResult(Reference::fromSuperProperty(index)); return false; } base = base.storeOnStack(); @@ -1271,17 +1246,17 @@ bool Codegen::visit(ArrayMemberExpression *ast) QString s = str->value.toString(); uint arrayIndex = QV4::String::toArrayIndex(s); if (arrayIndex == UINT_MAX) { - _expr.setResult(Reference::fromMember(base, str->value.toString())); + setExprResult(Reference::fromMember(base, str->value.toString())); return false; } Reference index = Reference::fromConst(this, QV4::Encode(arrayIndex)); - _expr.setResult(Reference::fromSubscript(base, index)); + setExprResult(Reference::fromSubscript(base, index)); return false; } Reference index = expression(ast->expression); if (hasError) return false; - _expr.setResult(Reference::fromSubscript(base, index)); + setExprResult(Reference::fromSubscript(base, index)); return false; } @@ -1312,12 +1287,13 @@ bool Codegen::visit(BinaryExpression *ast) TailCallBlocker blockTailCalls(this); if (ast->op == QSOperator::And) { - if (_expr.accept(cx)) { + if (exprAccept(cx)) { auto iftrue = bytecodeGenerator->newLabel(); - condition(ast->left, &iftrue, _expr.iffalse(), true); + condition(ast->left, &iftrue, currentExpr().iffalse(), true); iftrue.link(); blockTailCalls.unblock(); - condition(ast->right, _expr.iftrue(), _expr.iffalse(), _expr.trueBlockFollowsCondition()); + const Result &expr = currentExpr(); + condition(ast->right, expr.iftrue(), expr.iffalse(), expr.trueBlockFollowsCondition()); } else { auto iftrue = bytecodeGenerator->newLabel(); auto endif = bytecodeGenerator->newLabel(); @@ -1339,15 +1315,16 @@ bool Codegen::visit(BinaryExpression *ast) endif.link(); - _expr.setResult(Reference::fromAccumulator(this)); + setExprResult(Reference::fromAccumulator(this)); } return false; } else if (ast->op == QSOperator::Or) { - if (_expr.accept(cx)) { + if (exprAccept(cx)) { auto iffalse = bytecodeGenerator->newLabel(); - condition(ast->left, _expr.iftrue(), &iffalse, false); + condition(ast->left, currentExpr().iftrue(), &iffalse, false); iffalse.link(); - condition(ast->right, _expr.iftrue(), _expr.iffalse(), _expr.trueBlockFollowsCondition()); + const Result &expr = currentExpr(); + condition(ast->right, expr.iftrue(), expr.iffalse(), expr.trueBlockFollowsCondition()); } else { auto iffalse = bytecodeGenerator->newLabel(); auto endif = bytecodeGenerator->newLabel(); @@ -1369,7 +1346,7 @@ bool Codegen::visit(BinaryExpression *ast) endif.link(); - _expr.setResult(Reference::fromAccumulator(this)); + setExprResult(Reference::fromAccumulator(this)); } return false; } else if (ast->op == QSOperator::Assign) { @@ -1380,9 +1357,9 @@ bool Codegen::visit(BinaryExpression *ast) return false; right = right.storeOnStack(); destructurePattern(p, right); - if (!_expr.accept(nx)) { + if (!exprAccept(nx)) { right.loadInAccumulator(); - _expr.setResult(Reference::fromAccumulator(this)); + setExprResult(Reference::fromAccumulator(this)); } return false; } @@ -1402,10 +1379,10 @@ bool Codegen::visit(BinaryExpression *ast) if (hasError) return false; r.loadInAccumulator(); - if (_expr.accept(nx)) - _expr.setResult(left.storeConsumeAccumulator()); + if (exprAccept(nx)) + setExprResult(left.storeConsumeAccumulator()); else - _expr.setResult(left.storeRetainAccumulator()); + setExprResult(left.storeRetainAccumulator()); return false; } @@ -1448,7 +1425,7 @@ bool Codegen::visit(BinaryExpression *ast) return false; binopHelper(baseOp(ast->op), tempLeft, right).loadInAccumulator(); - _expr.setResult(left.storeRetainAccumulator()); + setExprResult(left.storeRetainAccumulator()); break; } @@ -1460,7 +1437,7 @@ bool Codegen::visit(BinaryExpression *ast) Reference right = expression(ast->right); if (hasError) return false; - _expr.setResult(binopHelper(static_cast(ast->op), right, left)); + setExprResult(binopHelper(static_cast(ast->op), right, left)); break; } // intentional fall-through! @@ -1486,7 +1463,7 @@ bool Codegen::visit(BinaryExpression *ast) Reference right; if (AST::NumericLiteral *rhs = AST::cast(ast->right)) { visit(rhs); - right = _expr.result(); + right = exprResult(); } else { left = left.storeOnStack(); // force any loads of the lhs, so the rhs won't clobber it right = expression(ast->right); @@ -1494,7 +1471,7 @@ bool Codegen::visit(BinaryExpression *ast) if (hasError) return false; - _expr.setResult(binopHelper(static_cast(ast->op), left, right)); + setExprResult(binopHelper(static_cast(ast->op), left, right)); break; } @@ -1671,7 +1648,7 @@ Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Re break; } case QSOperator::StrictEqual: { - if (_expr.accept(cx)) + if (exprAccept(cx)) return jumpBinop(oper, left, right); Instruction::CmpStrictEqual cmp; @@ -1682,7 +1659,7 @@ Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Re break; } case QSOperator::StrictNotEqual: { - if (_expr.accept(cx)) + if (exprAccept(cx)) return jumpBinop(oper, left, right); Instruction::CmpStrictNotEqual cmp; @@ -1693,7 +1670,7 @@ Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Re break; } case QSOperator::Equal: { - if (_expr.accept(cx)) + if (exprAccept(cx)) return jumpBinop(oper, left, right); Instruction::CmpEq cmp; @@ -1704,7 +1681,7 @@ Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Re break; } case QSOperator::NotEqual: { - if (_expr.accept(cx)) + if (exprAccept(cx)) return jumpBinop(oper, left, right); Instruction::CmpNe cmp; @@ -1715,7 +1692,7 @@ Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Re break; } case QSOperator::Gt: { - if (_expr.accept(cx)) + if (exprAccept(cx)) return jumpBinop(oper, left, right); Instruction::CmpGt cmp; @@ -1726,7 +1703,7 @@ Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Re break; } case QSOperator::Ge: { - if (_expr.accept(cx)) + if (exprAccept(cx)) return jumpBinop(oper, left, right); Instruction::CmpGe cmp; @@ -1737,7 +1714,7 @@ Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Re break; } case QSOperator::Lt: { - if (_expr.accept(cx)) + if (exprAccept(cx)) return jumpBinop(oper, left, right); Instruction::CmpLt cmp; @@ -1748,7 +1725,7 @@ Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Re break; } case QSOperator::Le: - if (_expr.accept(cx)) + if (exprAccept(cx)) return jumpBinop(oper, left, right); Instruction::CmpLe cmp; @@ -1952,7 +1929,7 @@ bool Codegen::visit(CallExpression *ast) bytecodeGenerator->addInstruction(call); } - _expr.setResult(Reference::fromAccumulator(this)); + setExprResult(Reference::fromAccumulator(this)); return false; } @@ -2045,7 +2022,7 @@ void Codegen::handleCall(Reference &base, Arguments calldata, int slotForFunctio bytecodeGenerator->addInstruction(call); } - _expr.setResult(Reference::fromAccumulator(this)); + setExprResult(Reference::fromAccumulator(this)); } Codegen::Arguments Codegen::pushArgs(ArgumentList *args) @@ -2141,7 +2118,7 @@ bool Codegen::visit(ConditionalExpression *ast) ko.loadInAccumulator(); jump_endif.link(); - _expr.setResult(Reference::fromAccumulator(this)); + setExprResult(Reference::fromAccumulator(this)); return false; } @@ -2171,7 +2148,7 @@ bool Codegen::visit(DeleteExpression *ast) throwSyntaxError(ast->deleteToken, QStringLiteral("Delete of an unqualified identifier in strict mode.")); return false; } - _expr.setResult(Reference::fromConst(this, QV4::Encode(false))); + setExprResult(Reference::fromConst(this, QV4::Encode(false))); return false; case Reference::Name: { if (_context->isStrict) { @@ -2181,7 +2158,7 @@ bool Codegen::visit(DeleteExpression *ast) Instruction::DeleteName del; del.name = expr.nameAsIndex(); bytecodeGenerator->addInstruction(del); - _expr.setResult(Reference::fromAccumulator(this)); + setExprResult(Reference::fromAccumulator(this)); return false; } case Reference::Member: { @@ -2196,7 +2173,7 @@ bool Codegen::visit(DeleteExpression *ast) del.base = expr.propertyBase.stackSlot(); del.index = index.stackSlot(); bytecodeGenerator->addInstruction(del); - _expr.setResult(Reference::fromAccumulator(this)); + setExprResult(Reference::fromAccumulator(this)); return false; } case Reference::Subscript: { @@ -2206,14 +2183,14 @@ bool Codegen::visit(DeleteExpression *ast) del.base = expr.elementBase; del.index = expr.elementSubscript.stackSlot(); bytecodeGenerator->addInstruction(del); - _expr.setResult(Reference::fromAccumulator(this)); + setExprResult(Reference::fromAccumulator(this)); return false; } default: break; } // [[11.4.1]] Return true if it's not a reference - _expr.setResult(Reference::fromConst(this, QV4::Encode(true))); + setExprResult(Reference::fromConst(this, QV4::Encode(true))); return false; } @@ -2222,7 +2199,7 @@ bool Codegen::visit(FalseLiteral *) if (hasError) return false; - _expr.setResult(Reference::fromConst(this, QV4::Encode(false))); + setExprResult(Reference::fromConst(this, QV4::Encode(false))); return false; } @@ -2231,7 +2208,7 @@ bool Codegen::visit(SuperLiteral *) if (hasError) return false; - _expr.setResult(Reference::fromSuper(this)); + setExprResult(Reference::fromSuper(this)); return false; } @@ -2249,12 +2226,12 @@ bool Codegen::visit(FieldMemberExpression *ast) if (_context->isArrowFunction || _context->contextType == ContextType::Eval) { Reference r = referenceForName(QStringLiteral("new.target"), false); r.isReadonly = true; - _expr.setResult(r); + setExprResult(r); return false; } Reference r = Reference::fromStackSlot(this, CallData::NewTarget); - _expr.setResult(r); + setExprResult(r); return false; } } @@ -2267,10 +2244,10 @@ bool Codegen::visit(FieldMemberExpression *ast) load.stringId = registerString(ast->name.toString()); bytecodeGenerator->addInstruction(load); Reference property = Reference::fromAccumulator(this).storeOnStack(); - _expr.setResult(Reference::fromSuperProperty(property)); + setExprResult(Reference::fromSuperProperty(property)); return false; } - _expr.setResult(Reference::fromMember(base, ast->name.toString())); + setExprResult(Reference::fromMember(base, ast->name.toString())); return false; } @@ -2280,12 +2257,15 @@ bool Codegen::visit(TaggedTemplate *ast) return false; RegisterScope scope(this); + return handleTaggedTemplate(expression(ast->base), ast); +} - int functionObject = -1, thisObject = -1; - - Reference base = expression(ast->base); +bool Codegen::handleTaggedTemplate(Reference base, TaggedTemplate *ast) +{ if (hasError) return false; + + int functionObject = -1, thisObject = -1; switch (base.type) { case Reference::Member: case Reference::Subscript: @@ -2346,7 +2326,7 @@ bool Codegen::visit(FunctionExpression *ast) if (hasError) return false; loadClosure(function); - _expr.setResult(Reference::fromAccumulator(this)); + setExprResult(Reference::fromAccumulator(this)); return false; } @@ -2412,7 +2392,7 @@ bool Codegen::visit(IdentifierExpression *ast) if (hasError) return false; - _expr.setResult(referenceForName(ast->name.toString(), false, ast->firstSourceLocation())); + setExprResult(referenceForName(ast->name.toString(), false, ast->firstSourceLocation())); return false; } @@ -2462,7 +2442,7 @@ void Codegen::handleConstruct(const Reference &base, ArgumentList *arguments) // set the result up as the thisObject Reference::fromAccumulator(this).storeOnStack(CallData::This); - _expr.setResult(Reference::fromAccumulator(this)); + setExprResult(Reference::fromAccumulator(this)); } bool Codegen::visit(NewExpression *ast) @@ -2511,7 +2491,7 @@ bool Codegen::visit(NotExpression *ast) return false; TailCallBlocker blockTailCalls(this); - _expr.setResult(unop(Not, expression(ast->expression))); + setExprResult(unop(Not, expression(ast->expression))); return false; } @@ -2520,10 +2500,10 @@ bool Codegen::visit(NullExpression *) if (hasError) return false; - if (_expr.accept(cx)) - bytecodeGenerator->jump().link(*_expr.iffalse()); + if (exprAccept(cx)) + bytecodeGenerator->jump().link(*currentExpr().iffalse()); else - _expr.setResult(Reference::fromConst(this, Encode::null())); + setExprResult(Reference::fromConst(this, Encode::null())); return false; } @@ -2533,7 +2513,7 @@ bool Codegen::visit(NumericLiteral *ast) if (hasError) return false; - _expr.setResult(Reference::fromConst(this, QV4::Encode::smallestNumber(ast->value))); + setExprResult(Reference::fromConst(this, QV4::Encode::smallestNumber(ast->value))); return false; } @@ -2648,8 +2628,7 @@ bool Codegen::visit(ObjectPattern *ast) call.argc = argc; call.args = Moth::StackSlot::createRegister(args); bytecodeGenerator->addInstruction(call); - Reference result = Reference::fromAccumulator(this); - _expr.setResult(result); + setExprResult(Reference::fromAccumulator(this)); return false; } @@ -2668,7 +2647,7 @@ bool Codegen::visit(PostDecrementExpression *ast) if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(expr, ast->decrementToken)) return false; - _expr.setResult(unop(PostDecrement, expr)); + setExprResult(unop(PostDecrement, expr)); return false; } @@ -2688,7 +2667,7 @@ bool Codegen::visit(PostIncrementExpression *ast) if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(expr, ast->incrementToken)) return false; - _expr.setResult(unop(PostIncrement, expr)); + setExprResult(unop(PostIncrement, expr)); return false; } @@ -2706,7 +2685,7 @@ bool Codegen::visit(PreDecrementExpression *ast) if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(expr, ast->decrementToken)) return false; - _expr.setResult(unop(PreDecrement, expr)); + setExprResult(unop(PreDecrement, expr)); return false; } @@ -2725,7 +2704,7 @@ bool Codegen::visit(PreIncrementExpression *ast) if (throwSyntaxErrorOnEvalOrArgumentsInStrictMode(expr, ast->incrementToken)) return false; - _expr.setResult(unop(PreIncrement, expr)); + setExprResult(unop(PreIncrement, expr)); return false; } @@ -2736,7 +2715,7 @@ bool Codegen::visit(RegExpLiteral *ast) auto r = Reference::fromStackSlot(this); r.isReadonly = true; - _expr.setResult(r); + setExprResult(r); Instruction::MoveRegExp instr; instr.regExpId = jsUnitGenerator->registerRegExp(ast); @@ -2752,7 +2731,7 @@ bool Codegen::visit(StringLiteral *ast) auto r = Reference::fromAccumulator(this); r.isReadonly = true; - _expr.setResult(r); + setExprResult(r); Instruction::LoadRuntimeString instr; instr.stringId = registerString(ast->value.toString()); @@ -2802,7 +2781,7 @@ bool Codegen::visit(TemplateLiteral *ast) auto r = Reference::fromAccumulator(this); r.isReadonly = true; - _expr.setResult(r); + setExprResult(r); return false; } @@ -2815,10 +2794,10 @@ bool Codegen::visit(ThisExpression *) if (_context->isArrowFunction) { Reference r = referenceForName(QStringLiteral("this"), false); r.isReadonly = true; - _expr.setResult(r); + setExprResult(r); return false; } - _expr.setResult(Reference::fromThis(this)); + setExprResult(Reference::fromThis(this)); return false; } @@ -2828,7 +2807,7 @@ bool Codegen::visit(TildeExpression *ast) return false; TailCallBlocker blockTailCalls(this); - _expr.setResult(unop(Compl, expression(ast->expression))); + setExprResult(unop(Compl, expression(ast->expression))); return false; } @@ -2837,7 +2816,7 @@ bool Codegen::visit(TrueLiteral *) if (hasError) return false; - _expr.setResult(Reference::fromConst(this, QV4::Encode(true))); + setExprResult(Reference::fromConst(this, QV4::Encode(true))); return false; } @@ -2863,7 +2842,7 @@ bool Codegen::visit(TypeOfExpression *ast) Instruction::TypeofValue instr; bytecodeGenerator->addInstruction(instr); } - _expr.setResult(Reference::fromAccumulator(this)); + setExprResult(Reference::fromAccumulator(this)); return false; } @@ -2874,7 +2853,7 @@ bool Codegen::visit(UnaryMinusExpression *ast) return false; TailCallBlocker blockTailCalls(this); - _expr.setResult(unop(UMinus, expression(ast->expression))); + setExprResult(unop(UMinus, expression(ast->expression))); return false; } @@ -2884,7 +2863,7 @@ bool Codegen::visit(UnaryPlusExpression *ast) return false; TailCallBlocker blockTailCalls(this); - _expr.setResult(unop(UPlus, expression(ast->expression))); + setExprResult(unop(UPlus, expression(ast->expression))); return false; } @@ -2897,7 +2876,7 @@ bool Codegen::visit(VoidExpression *ast) TailCallBlocker blockTailCalls(this); statement(ast->expression); - _expr.setResult(Reference::fromConst(this, Encode::undefined())); + setExprResult(Reference::fromConst(this, Encode::undefined())); return false; } @@ -2911,7 +2890,7 @@ bool Codegen::visit(FunctionDeclaration * ast) if (_functionContext->contextType == ContextType::Binding) referenceForName(ast->name.toString(), true).loadInAccumulator(); - _expr.accept(nx); + exprAccept(nx); return false; } @@ -2967,7 +2946,7 @@ bool Codegen::visit(YieldExpression *ast) done.link(); lhsValue.loadInAccumulator(); - _expr.setResult(acc); + setExprResult(acc); return false; } @@ -2978,7 +2957,7 @@ bool Codegen::visit(YieldExpression *ast) BytecodeGenerator::Jump jump = bytecodeGenerator->addJumpInstruction(resume); emitReturn(acc); jump.link(); - _expr.setResult(acc); + setExprResult(acc); return false; } diff --git a/src/qml/compiler/qv4codegen_p.h b/src/qml/compiler/qv4codegen_p.h index 4d7001fe64..c1063bc0d0 100644 --- a/src/qml/compiler/qv4codegen_p.h +++ b/src/qml/compiler/qv4codegen_p.h @@ -192,8 +192,24 @@ public: bool isLValue() const { return !isReadonly && type > Accumulator; } - Reference(Codegen *cg, Type type = Invalid) : type(type), constant(0), codegen(cg) {} - Reference(): constant(0) {} + Reference(Codegen *cg, Type t = Invalid) : Reference() + { + type = t; + codegen = cg; + } + + Reference() : + constant(0), + isArgOrEval(false), + isReadonly(false), + isReferenceToConst(false), + requiresTDZCheck(false), + subscriptRequiresTDZCheck(false), + stackSlotIsLocalOrArgument(false), + isVolatile(false), + global(false) + {} + Reference(const Reference &) = default; Reference(Reference &&) = default; Reference &operator =(const Reference &) = default; @@ -395,16 +411,17 @@ public: Moth::StackSlot property; // super property }; QString name; - mutable bool isArgOrEval = false; - bool isReadonly = false; - bool isReferenceToConst = false; - bool requiresTDZCheck = false; - bool subscriptRequiresTDZCheck = false; - bool stackSlotIsLocalOrArgument = false; - bool isVolatile = false; - bool global = false; Codegen *codegen = nullptr; + quint32 isArgOrEval:1; + quint32 isReadonly:1; + quint32 isReferenceToConst:1; + quint32 requiresTDZCheck:1; + quint32 subscriptRequiresTDZCheck:1; + quint32 stackSlotIsLocalOrArgument:1; + quint32 isVolatile:1; + quint32 global:1; + private: void storeAccumulator() const; Reference doStoreOnStack(int tempIndex) const; @@ -499,6 +516,10 @@ protected: void setResult(const Reference &result) { _result = result; } + + void setResult(Reference &&result) { + _result = std::move(result); + } }; void enterContext(AST::Node *node); @@ -544,9 +565,23 @@ protected: void condition(AST::ExpressionNode *ast, const BytecodeGenerator::Label *iftrue, const BytecodeGenerator::Label *iffalse, bool trueBlockFollowsCondition); - Reference expression(AST::ExpressionNode *ast); - void accept(AST::Node *node); + inline Reference expression(AST::ExpressionNode *ast) + { + if (!ast || hasError) + return Reference(); + + RecursionDepthCheck depthCheck(this, ast->lastSourceLocation()); + pushExpr(); + ast->accept(this); + return popResult(); + } + + inline void accept(AST::Node *node) + { + if (!hasError && node) + node->accept(this); + } void program(AST::Program *ast); void statementList(AST::StatementList *ast); @@ -684,6 +719,7 @@ public: void handleCall(Reference &base, Arguments calldata, int slotForFunction, int slotForThisObject); Arguments pushTemplateArgs(AST::TemplateLiteral *args); + bool handleTaggedTemplate(Reference base, AST::TaggedTemplate *ast); void createTemplateObject(AST::TemplateLiteral *t); void setUseFastLookups(bool b) { useFastLookups = b; } @@ -720,7 +756,33 @@ protected: friend struct ControlFlow; friend struct ControlFlowCatch; friend struct ControlFlowFinally; - Result _expr; + + inline void setExprResult(const Reference &result) { m_expressions.back().setResult(result); } + inline void setExprResult(Reference &&result) { m_expressions.back().setResult(std::move(result)); } + inline Reference exprResult() const { return m_expressions.back().result(); } + + inline bool exprAccept(Format f) { return m_expressions.back().accept(f); } + + inline const Result ¤tExpr() const { return m_expressions.back(); } + + inline void pushExpr(Result &&expr) { m_expressions.push_back(std::move(expr)); } + inline void pushExpr(const Result &expr) { m_expressions.push_back(expr); } + inline void pushExpr() { m_expressions.emplace_back(); } + + inline Result popExpr() + { + const Result result = m_expressions.back(); + m_expressions.pop_back(); + return result; + } + + inline Reference popResult() { + const Reference result = m_expressions.back().result(); + m_expressions.pop_back(); + return result; + } + + std::vector m_expressions; VolatileMemoryLocations _volatileMemoryLocations; Module *_module; int _returnAddress; diff --git a/src/qml/parser/qqmljsast.cpp b/src/qml/parser/qqmljsast.cpp index 4ebb2d3b5c..54a1200493 100644 --- a/src/qml/parser/qqmljsast.cpp +++ b/src/qml/parser/qqmljsast.cpp @@ -65,21 +65,6 @@ ClassExpression *asAnonymousClassDefinition(Node *n) return c; } - -void Node::accept(Visitor *visitor) -{ - if (visitor->preVisit(this)) { - accept0(visitor); - } - visitor->postVisit(this); -} - -void Node::accept(Node *node, Visitor *visitor) -{ - if (node) - node->accept(visitor); -} - ExpressionNode *Node::expressionCast() { return nullptr; diff --git a/src/qml/parser/qqmljsast_p.h b/src/qml/parser/qqmljsast_p.h index 43aeec6525..0978ab523a 100644 --- a/src/qml/parser/qqmljsast_p.h +++ b/src/qml/parser/qqmljsast_p.h @@ -271,11 +271,24 @@ public: virtual FunctionExpression *asFunctionDefinition(); virtual ClassExpression *asClassDefinition(); - void accept(Visitor *visitor); - static void accept(Node *node, Visitor *visitor); + inline void accept(Visitor *visitor) + { + if (visitor->preVisit(this)) + accept0(visitor); + visitor->postVisit(this); + } + inline static void accept(Node *node, Visitor *visitor) + { + if (node) + node->accept(visitor); + } + + // ### Remove when we can. This is part of the qmldevtools library, though. inline static void acceptChild(Node *node, Visitor *visitor) - { return accept(node, visitor); } // ### remove + { + return accept(node, visitor); + } virtual void accept0(Visitor *visitor) = 0; virtual SourceLocation firstSourceLocation() const = 0; -- cgit v1.2.3 From 7e32be87c0f53ce2e5d3c1e351a559d6c600f1bf Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Mon, 4 Mar 2019 15:36:57 +0100 Subject: Don't keep raw pointers to SparseArrayNode The nodes are owned by the SparseArrayData and will be freed whenever an item is deleted from the array. Therefore, we have to look up the node for each iteration. This is slightly slower, but at least it doesn't crash. Fixes: QTBUG-74188 Change-Id: Id24324a8c83b00b3ad1212cdaabccabd6c8a999f Reviewed-by: Simon Hausmann Reviewed-by: Lars Knoll --- src/qml/jsruntime/qv4object.cpp | 8 +++++--- src/qml/jsruntime/qv4object_p.h | 1 - src/qml/jsruntime/qv4stringobject.cpp | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4object.cpp b/src/qml/jsruntime/qv4object.cpp index 3d2d54f651..9d6cb730a9 100644 --- a/src/qml/jsruntime/qv4object.cpp +++ b/src/qml/jsruntime/qv4object.cpp @@ -320,8 +320,11 @@ bool Object::virtualDeleteProperty(Managed *m, PropertyKey id) PropertyKey ObjectOwnPropertyKeyIterator::next(const Object *o, Property *pd, PropertyAttributes *attrs) { if (arrayIndex != UINT_MAX && o->arrayData()) { - if (!arrayIndex) - arrayNode = o->sparseBegin(); + SparseArrayNode *arrayNode = nullptr; + if (o->arrayType() == Heap::ArrayData::Sparse) { + SparseArray *sparse = o->arrayData()->sparse; + arrayNode = arrayIndex ? sparse->lowerBound(arrayIndex) : sparse->begin(); + } // sparse arrays if (arrayNode) { @@ -339,7 +342,6 @@ PropertyKey ObjectOwnPropertyKeyIterator::next(const Object *o, Property *pd, Pr *attrs = a; return PropertyKey::fromArrayIndex(k); } - arrayNode = nullptr; arrayIndex = UINT_MAX; } // dense arrays diff --git a/src/qml/jsruntime/qv4object_p.h b/src/qml/jsruntime/qv4object_p.h index ff47810994..9f24449c91 100644 --- a/src/qml/jsruntime/qv4object_p.h +++ b/src/qml/jsruntime/qv4object_p.h @@ -408,7 +408,6 @@ struct ObjectOwnPropertyKeyIterator : OwnPropertyKeyIterator uint arrayIndex = 0; uint memberIndex = 0; bool iterateOverSymbols = false; - SparseArrayNode *arrayNode = nullptr; ~ObjectOwnPropertyKeyIterator() override = default; PropertyKey next(const Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override; diff --git a/src/qml/jsruntime/qv4stringobject.cpp b/src/qml/jsruntime/qv4stringobject.cpp index 8186153ba4..dee6a67792 100644 --- a/src/qml/jsruntime/qv4stringobject.cpp +++ b/src/qml/jsruntime/qv4stringobject.cpp @@ -130,7 +130,7 @@ PropertyKey StringObjectOwnPropertyKeyIterator::next(const QV4::Object *o, Prope return PropertyKey::fromArrayIndex(index); } else if (arrayIndex == slen) { if (s->arrayData()) { - arrayNode = s->sparseBegin(); + SparseArrayNode *arrayNode = s->sparseBegin(); // iterate until we're past the end of the string while (arrayNode && arrayNode->key() < slen) arrayNode = arrayNode->nextNode(); -- cgit v1.2.3 From 73231fe953145ac0df4e62f173e1a90076466012 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 27 Feb 2019 16:01:54 +0100 Subject: Unify the JavaScript parsing recursion checks We only need to check in one central location and we can allow for more recursion. 4k recursions seem tolerable. A common default for stack sizes is 8MB. Each recursion step takes up to 1k stack space in debug mode. So, exhausting this would burn about half of the available stack size. We don't report the exact source location in this case as finding the source location may itself trigger a deep recursion. Fixes: QTBUG-74087 Change-Id: I43e6e20b322f6035c7136a6f381230ec285c30ae Reviewed-by: Simon Hausmann --- src/qml/compiler/qqmlirbuilder_p.h | 6 +++++ src/qml/compiler/qv4codegen.cpp | 37 ++++++++++++++++++++------- src/qml/compiler/qv4codegen_p.h | 33 +++++------------------- src/qml/compiler/qv4compilerscanfunctions.cpp | 27 +++++-------------- src/qml/compiler/qv4compilerscanfunctions_p.h | 7 ++--- src/qml/jsruntime/qv4runtimecodegen_p.h | 1 + src/qml/parser/qqmljs.g | 10 +------- src/qml/parser/qqmljsast_p.h | 11 +++++--- src/qml/parser/qqmljsastvisitor.cpp | 2 +- src/qml/parser/qqmljsastvisitor_p.h | 36 +++++++++++++++++++++++++- 10 files changed, 95 insertions(+), 75 deletions(-) (limited to 'src/qml') diff --git a/src/qml/compiler/qqmlirbuilder_p.h b/src/qml/compiler/qqmlirbuilder_p.h index 1a3ca4163e..6affca3ca6 100644 --- a/src/qml/compiler/qqmlirbuilder_p.h +++ b/src/qml/compiler/qqmlirbuilder_p.h @@ -498,6 +498,12 @@ public: bool visit(QQmlJS::AST::UiScriptBinding *ast) override; bool visit(QQmlJS::AST::UiSourceElement *ast) override; + void throwRecursionDepthError() override + { + recordError(AST::SourceLocation(), + QStringLiteral("Maximum statement or expression depth exceeded")); + } + void accept(QQmlJS::AST::Node *node); // returns index in _objects diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index b4da05eb9a..0ca452b93e 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -376,7 +376,6 @@ void Codegen::addCJump() void Codegen::statement(Statement *ast) { - RecursionDepthCheck depthCheck(this, ast->lastSourceLocation()); RegisterScope scope(this); bytecodeGenerator->setLocation(ast->firstSourceLocation()); @@ -392,7 +391,6 @@ void Codegen::statement(ExpressionNode *ast) if (! ast) { return; } else { - RecursionDepthCheck depthCheck(this, ast->lastSourceLocation()); RegisterScope scope(this); pushExpr(Result(nx)); @@ -420,7 +418,6 @@ void Codegen::condition(ExpressionNode *ast, const BytecodeGenerator::Label *ift if (!ast) return; - RecursionDepthCheck depthCheck(this, ast->lastSourceLocation()); pushExpr(Result(iftrue, iffalse, trueBlockFollowsCondition)); accept(ast); Result r = popExpr(); @@ -3825,8 +3822,14 @@ QQmlRefPointer Codegen::createUnitForLoading() class Codegen::VolatileMemoryLocationScanner: protected QQmlJS::AST::Visitor { VolatileMemoryLocations locs; + Codegen *parent; public: + VolatileMemoryLocationScanner(Codegen *parent) : + QQmlJS::AST::Visitor(parent->recursionDepth()), + parent(parent) + {} + Codegen::VolatileMemoryLocations scan(AST::Node *s) { s->accept(this); @@ -3891,25 +3894,41 @@ public: } } + void throwRecursionDepthError() override + { + parent->throwRecursionDepthError(); + } + private: - void collectIdentifiers(QVector &ids, AST::Node *node) const { + void collectIdentifiers(QVector &ids, AST::Node *node) { class Collector: public QQmlJS::AST::Visitor { + private: QVector &ids; + VolatileMemoryLocationScanner *parent; + public: - Collector(QVector &ids): ids(ids) {} - virtual bool visit(IdentifierExpression *ie) { + Collector(QVector &ids, VolatileMemoryLocationScanner *parent) : + QQmlJS::AST::Visitor(parent->recursionDepth()), ids(ids), parent(parent) + {} + + bool visit(IdentifierExpression *ie) final { ids.append(ie->name); return false; } + + void throwRecursionDepthError() final + { + parent->throwRecursionDepthError(); + } }; - Collector collector(ids); + Collector collector(ids, this); node->accept(&collector); } }; -Codegen::VolatileMemoryLocations Codegen::scanVolatileMemoryLocations(AST::Node *ast) const +Codegen::VolatileMemoryLocations Codegen::scanVolatileMemoryLocations(AST::Node *ast) { - VolatileMemoryLocationScanner scanner; + VolatileMemoryLocationScanner scanner(this); return scanner.scan(ast); } diff --git a/src/qml/compiler/qv4codegen_p.h b/src/qml/compiler/qv4codegen_p.h index c1063bc0d0..a6355bf93a 100644 --- a/src/qml/compiler/qv4codegen_p.h +++ b/src/qml/compiler/qv4codegen_p.h @@ -571,7 +571,6 @@ protected: if (!ast || hasError) return Reference(); - RecursionDepthCheck depthCheck(this, ast->lastSourceLocation()); pushExpr(); ast->accept(this); return popResult(); @@ -705,6 +704,11 @@ protected: bool throwSyntaxErrorOnEvalOrArgumentsInStrictMode(const Reference &r, const AST::SourceLocation &loc); virtual void throwSyntaxError(const AST::SourceLocation &loc, const QString &detail); virtual void throwReferenceError(const AST::SourceLocation &loc, const QString &detail); + void throwRecursionDepthError() override + { + throwSyntaxError(AST::SourceLocation(), + QStringLiteral("Maximum statement or expression depth exceeded")); + } public: QList errors() const; @@ -831,33 +835,8 @@ protected: bool _onoff; }; - class RecursionDepthCheck { - public: - RecursionDepthCheck(Codegen *cg, const AST::SourceLocation &loc) - : _cg(cg) - { -#ifdef QT_NO_DEBUG - const int depthLimit = 4000; // limit to ~1000 deep -#else - const int depthLimit = 1000; // limit to ~250 deep -#endif // QT_NO_DEBUG - - ++_cg->_recursionDepth; - if (_cg->_recursionDepth > depthLimit) - _cg->throwSyntaxError(loc, QStringLiteral("Maximum statement or expression depth exceeded")); - } - - ~RecursionDepthCheck() - { --_cg->_recursionDepth; } - - private: - Codegen *_cg; - }; - int _recursionDepth = 0; - friend class RecursionDepthCheck; - private: - VolatileMemoryLocations scanVolatileMemoryLocations(AST::Node *ast) const; + VolatileMemoryLocations scanVolatileMemoryLocations(AST::Node *ast); void handleConstruct(const Reference &base, AST::ArgumentList *args); }; diff --git a/src/qml/compiler/qv4compilerscanfunctions.cpp b/src/qml/compiler/qv4compilerscanfunctions.cpp index e0eaa8867b..04593f202a 100644 --- a/src/qml/compiler/qv4compilerscanfunctions.cpp +++ b/src/qml/compiler/qv4compilerscanfunctions.cpp @@ -57,7 +57,8 @@ using namespace QV4::Compiler; using namespace QQmlJS::AST; ScanFunctions::ScanFunctions(Codegen *cg, const QString &sourceCode, ContextType defaultProgramType) - : _cg(cg) + : QQmlJS::AST::Visitor(cg->recursionDepth()) + , _cg(cg) , _sourceCode(sourceCode) , _context(nullptr) , _allowFuncDecls(true) @@ -96,25 +97,6 @@ void ScanFunctions::leaveEnvironment() _context = _contextStack.isEmpty() ? nullptr : _contextStack.top(); } -bool ScanFunctions::preVisit(Node *ast) -{ - if (_cg->hasError) - return false; - ++_recursionDepth; - - if (_recursionDepth > 1000) { - _cg->throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Maximum statement or expression depth exceeded")); - return false; - } - - return true; -} - -void ScanFunctions::postVisit(Node *) -{ - --_recursionDepth; -} - void ScanFunctions::checkDirectivePrologue(StatementList *ast) { for (StatementList *it = ast; it; it = it->next) { @@ -893,3 +875,8 @@ void ScanFunctions::calcEscapingVariables() } } } + +void ScanFunctions::throwRecursionDepthError() +{ + _cg->throwRecursionDepthError(); +} diff --git a/src/qml/compiler/qv4compilerscanfunctions_p.h b/src/qml/compiler/qv4compilerscanfunctions_p.h index 28ad846bcd..0f7bf1818a 100644 --- a/src/qml/compiler/qv4compilerscanfunctions_p.h +++ b/src/qml/compiler/qv4compilerscanfunctions_p.h @@ -96,9 +96,6 @@ protected: using Visitor::visit; using Visitor::endVisit; - bool preVisit(AST::Node *ast) override; - void postVisit(AST::Node *) override; - void checkDirectivePrologue(AST::StatementList *ast); void checkName(const QStringRef &name, const AST::SourceLocation &loc); @@ -160,6 +157,8 @@ protected: bool visit(AST::WithStatement *ast) override; void endVisit(AST::WithStatement *ast) override; + void throwRecursionDepthError() override; + protected: bool enterFunction(AST::Node *ast, const QString &name, AST::FormalParameterList *formals, AST::StatementList *body, bool enterName); @@ -173,8 +172,6 @@ protected: bool _allowFuncDecls; ContextType defaultProgramType; - unsigned _recursionDepth = 0; - private: static constexpr AST::Node *astNodeForGlobalEnvironment = nullptr; }; diff --git a/src/qml/jsruntime/qv4runtimecodegen_p.h b/src/qml/jsruntime/qv4runtimecodegen_p.h index be66dc57ca..006a6a3cde 100644 --- a/src/qml/jsruntime/qv4runtimecodegen_p.h +++ b/src/qml/jsruntime/qv4runtimecodegen_p.h @@ -71,6 +71,7 @@ public: void throwSyntaxError(const AST::SourceLocation &loc, const QString &detail) override; void throwReferenceError(const AST::SourceLocation &loc, const QString &detail) override; + private: ExecutionEngine *engine; }; diff --git a/src/qml/parser/qqmljs.g b/src/qml/parser/qqmljs.g index b86dba6daa..8ae51a795f 100644 --- a/src/qml/parser/qqmljs.g +++ b/src/qml/parser/qqmljs.g @@ -614,16 +614,8 @@ bool Parser::parse(int startToken) program = 0; do { - if (++tos == stack_size) { + if (++tos == stack_size) reallocateStack(); - if (stack_size > 10000) { - // We're now in some serious right-recursive stuff, which will probably result in - // an AST that's so deep that recursively visiting it will run out of stack space. - const QString msg = QCoreApplication::translate("QQmlParser", "Maximum statement or expression depth exceeded"); - diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, token_buffer[0].loc, msg)); - return false; - } - } state_stack[tos] = action; diff --git a/src/qml/parser/qqmljsast_p.h b/src/qml/parser/qqmljsast_p.h index 0978ab523a..e84c62af2f 100644 --- a/src/qml/parser/qqmljsast_p.h +++ b/src/qml/parser/qqmljsast_p.h @@ -273,9 +273,14 @@ public: inline void accept(Visitor *visitor) { - if (visitor->preVisit(this)) - accept0(visitor); - visitor->postVisit(this); + Visitor::RecursionDepthCheck recursionCheck(visitor); + if (recursionCheck()) { + if (visitor->preVisit(this)) + accept0(visitor); + visitor->postVisit(this); + } else { + visitor->throwRecursionDepthError(); + } } inline static void accept(Node *node, Visitor *visitor) diff --git a/src/qml/parser/qqmljsastvisitor.cpp b/src/qml/parser/qqmljsastvisitor.cpp index eec151298e..666623eecc 100644 --- a/src/qml/parser/qqmljsastvisitor.cpp +++ b/src/qml/parser/qqmljsastvisitor.cpp @@ -43,7 +43,7 @@ QT_QML_BEGIN_NAMESPACE namespace QQmlJS { namespace AST { -Visitor::Visitor() +Visitor::Visitor(quint16 parentRecursionDepth) : m_recursionDepth(parentRecursionDepth) { } diff --git a/src/qml/parser/qqmljsastvisitor_p.h b/src/qml/parser/qqmljsastvisitor_p.h index c925096de6..9c69f88e0c 100644 --- a/src/qml/parser/qqmljsastvisitor_p.h +++ b/src/qml/parser/qqmljsastvisitor_p.h @@ -61,7 +61,33 @@ namespace QQmlJS { namespace AST { class QML_PARSER_EXPORT Visitor { public: - Visitor(); + class RecursionDepthCheck + { + Q_DISABLE_COPY(RecursionDepthCheck) + public: + RecursionDepthCheck(RecursionDepthCheck &&) = delete; + RecursionDepthCheck &operator=(RecursionDepthCheck &&) = delete; + + RecursionDepthCheck(Visitor *visitor) : m_visitor(visitor) + { + ++(m_visitor->m_recursionDepth); + } + + ~RecursionDepthCheck() + { + --(m_visitor->m_recursionDepth); + } + + bool operator()() const { + return m_visitor->m_recursionDepth < s_recursionLimit; + } + + private: + static const quint16 s_recursionLimit = 4096; + Visitor *m_visitor; + }; + + Visitor(quint16 parentRecursionDepth = 0); virtual ~Visitor(); virtual bool preVisit(Node *) { return true; } @@ -374,6 +400,14 @@ public: virtual bool visit(DebuggerStatement *) { return true; } virtual void endVisit(DebuggerStatement *) {} + + virtual void throwRecursionDepthError() = 0; + + quint16 recursionDepth() const { return m_recursionDepth; } + +protected: + quint16 m_recursionDepth = 0; + friend class RecursionDepthCheck; }; } } // namespace AST -- cgit v1.2.3 From a79d1038a14db01037d80b9701a530c3654c7adc Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Fri, 21 Dec 2018 11:26:45 +0100 Subject: Make object property lookup resolution virtual This allows sub-classes of Object to have their own lookup resolution and verification logic, instead of squeezing it all into qv4lookup.cpp. The typical fallbacks are still in qv4lookup.cpp though. Task-number: QTBUG-69898 Change-Id: I32a8884982b37e2065090666a7bf05b198f5b7fd Reviewed-by: Ulf Hermann --- src/qml/jsruntime/qv4lookup.cpp | 76 +------------------------------------- src/qml/jsruntime/qv4object.cpp | 82 +++++++++++++++++++++++++++++++++++++++++ src/qml/jsruntime/qv4object_p.h | 7 ++++ src/qml/jsruntime/qv4vtable_p.h | 14 +++++++ 4 files changed, 105 insertions(+), 74 deletions(-) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4lookup.cpp b/src/qml/jsruntime/qv4lookup.cpp index 994daa864b..790a5843c2 100644 --- a/src/qml/jsruntime/qv4lookup.cpp +++ b/src/qml/jsruntime/qv4lookup.cpp @@ -69,37 +69,7 @@ void Lookup::resolveProtoGetter(PropertyKey name, const Heap::Object *proto) ReturnedValue Lookup::resolveGetter(ExecutionEngine *engine, const Object *object) { - Heap::Object *obj = object->d(); - PropertyKey name = engine->identifierTable->asPropertyKey(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); - if (name.isArrayIndex()) { - indexedLookup.index = name.asArrayIndex(); - getter = getterIndexed; - return getter(this, engine, *object); - } - - auto index = obj->internalClass->findValueOrGetter(name); - if (index.isValid()) { - PropertyAttributes attrs = index.attrs; - uint nInline = obj->vtable()->nInlineProperties; - if (attrs.isData()) { - if (index.index < obj->vtable()->nInlineProperties) { - index.index += obj->vtable()->inlinePropertyOffset; - getter = getter0Inline; - } else { - index.index -= nInline; - getter = getter0MemberData; - } - } else { - getter = getterAccessor; - } - objectLookup.ic = obj->internalClass; - objectLookup.offset = index.index; - return getter(this, engine, *object); - } - - protoLookup.protoId = obj->internalClass->protoId; - resolveProtoGetter(name, obj->prototype()); - return getter(this, engine, *object); + return object->resolveLookupGetter(engine, this); } ReturnedValue Lookup::resolvePrimitiveGetter(ExecutionEngine *engine, const Value &object) @@ -473,49 +443,7 @@ ReturnedValue Lookup::globalGetterProtoAccessor(Lookup *l, ExecutionEngine *engi bool Lookup::resolveSetter(ExecutionEngine *engine, Object *object, const Value &value) { - Scope scope(engine); - ScopedString name(scope, scope.engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); - - Heap::InternalClass *c = object->internalClass(); - PropertyKey key = name->toPropertyKey(); - auto idx = c->findValueOrSetter(key); - if (idx.isValid()) { - if (object->isArrayObject() && idx.index == Heap::ArrayObject::LengthPropertyIndex) { - Q_ASSERT(!idx.attrs.isAccessor()); - setter = arrayLengthSetter; - return setter(this, engine, *object, value); - } else if (idx.attrs.isData() && idx.attrs.isWritable()) { - objectLookup.ic = object->internalClass(); - objectLookup.offset = idx.index; - setter = idx.index < object->d()->vtable()->nInlineProperties ? Lookup::setter0Inline : Lookup::setter0; - return setter(this, engine, *object, value); - } else { - // ### handle setter - setter = setterFallback; - } - return setter(this, engine, *object, value); - } - - insertionLookup.protoId = c->protoId; - if (!object->put(key, value)) { - setter = Lookup::setterFallback; - return false; - } - - if (object->internalClass() == c) { - // ### setter in the prototype, should handle this - setter = setterFallback; - return true; - } - idx = object->internalClass()->findValueOrSetter(key); - if (!idx.isValid() || idx.attrs.isAccessor()) { // ### can this even happen? - setter = setterFallback; - return false; - } - insertionLookup.newClass = object->internalClass(); - insertionLookup.offset = idx.index; - setter = setterInsert; - return true; + return object->resolveLookupSetter(engine, this, value); } bool Lookup::setterGeneric(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value) diff --git a/src/qml/jsruntime/qv4object.cpp b/src/qml/jsruntime/qv4object.cpp index 9d6cb730a9..7dd0a247d6 100644 --- a/src/qml/jsruntime/qv4object.cpp +++ b/src/qml/jsruntime/qv4object.cpp @@ -736,6 +736,88 @@ ReturnedValue Object::virtualInstanceOf(const Object *typeObject, const Value &v return checkedInstanceOf(engine, function, var); } +ReturnedValue Object::virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup) +{ + Heap::Object *obj = object->d(); + PropertyKey name = engine->identifierTable->asPropertyKey(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[lookup->nameIndex]); + if (name.isArrayIndex()) { + lookup->indexedLookup.index = name.asArrayIndex(); + lookup->getter = Lookup::getterIndexed; + return lookup->getter(lookup, engine, *object); + } + + auto index = obj->internalClass->findValueOrGetter(name); + if (index.isValid()) { + PropertyAttributes attrs = index.attrs; + uint nInline = obj->vtable()->nInlineProperties; + if (attrs.isData()) { + if (index.index < obj->vtable()->nInlineProperties) { + index.index += obj->vtable()->inlinePropertyOffset; + lookup->getter = Lookup::getter0Inline; + } else { + index.index -= nInline; + lookup->getter = Lookup::getter0MemberData; + } + } else { + lookup->getter = Lookup::getterAccessor; + } + lookup->objectLookup.ic = obj->internalClass; + lookup->objectLookup.offset = index.index; + return lookup->getter(lookup, engine, *object); + } + + lookup->protoLookup.protoId = obj->internalClass->protoId; + lookup->resolveProtoGetter(name, obj->prototype()); + return lookup->getter(lookup, engine, *object); +} + +bool Object::virtualResolveLookupSetter(Object *object, ExecutionEngine *engine, Lookup *lookup, const Value &value) +{ + Scope scope(engine); + ScopedString name(scope, scope.engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[lookup->nameIndex]); + + Heap::InternalClass *c = object->internalClass(); + PropertyKey key = name->toPropertyKey(); + auto idx = c->findValueOrSetter(key); + if (idx.isValid()) { + if (object->isArrayObject() && idx.index == Heap::ArrayObject::LengthPropertyIndex) { + Q_ASSERT(!idx.attrs.isAccessor()); + lookup->setter = Lookup::arrayLengthSetter; + return lookup->setter(lookup, engine, *object, value); + } else if (idx.attrs.isData() && idx.attrs.isWritable()) { + lookup->objectLookup.ic = object->internalClass(); + lookup->objectLookup.offset = idx.index; + lookup->setter = idx.index < object->d()->vtable()->nInlineProperties ? Lookup::setter0Inline : Lookup::setter0; + return lookup->setter(lookup, engine, *object, value); + } else { + // ### handle setter + lookup->setter = Lookup::setterFallback; + } + return lookup->setter(lookup, engine, *object, value); + } + + lookup->insertionLookup.protoId = c->protoId; + if (!object->put(key, value)) { + lookup->setter = Lookup::setterFallback; + return false; + } + + if (object->internalClass() == c) { + // ### setter in the prototype, should handle this + lookup->setter = Lookup::setterFallback; + return true; + } + idx = object->internalClass()->findValueOrSetter(key); + if (!idx.isValid() || idx.attrs.isAccessor()) { // ### can this even happen? + lookup->setter = Lookup::setterFallback; + return false; + } + lookup->insertionLookup.newClass = object->internalClass(); + lookup->insertionLookup.offset = idx.index; + lookup->setter = Lookup::setterInsert; + return true; +} + ReturnedValue Object::checkedInstanceOf(ExecutionEngine *engine, const FunctionObject *f, const Value &var) { Scope scope(engine); diff --git a/src/qml/jsruntime/qv4object_p.h b/src/qml/jsruntime/qv4object_p.h index 9f24449c91..c3f1cb2c35 100644 --- a/src/qml/jsruntime/qv4object_p.h +++ b/src/qml/jsruntime/qv4object_p.h @@ -375,6 +375,11 @@ public: bool setProtoFromNewTarget(const Value *newTarget); + ReturnedValue resolveLookupGetter(ExecutionEngine *engine, Lookup *lookup) const + { return vtable()->resolveLookupGetter(this, engine, lookup); } + ReturnedValue resolveLookupSetter(ExecutionEngine *engine, Lookup *lookup, const Value &value) + { return vtable()->resolveLookupSetter(this, engine, lookup, value); } + protected: static ReturnedValue virtualGet(const Managed *m, PropertyKey id, const Value *receiver,bool *hasProperty); static bool virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver); @@ -389,6 +394,8 @@ protected: static OwnPropertyKeyIterator *virtualOwnPropertyKeys(const Object *m, Value *target); static qint64 virtualGetLength(const Managed *m); static ReturnedValue virtualInstanceOf(const Object *typeObject, const Value &var); + static ReturnedValue virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup); + static bool virtualResolveLookupSetter(Object *object, ExecutionEngine *engine, Lookup *lookup, const Value &value); public: // qv4runtime uses this directly static ReturnedValue checkedInstanceOf(ExecutionEngine *engine, const FunctionObject *typeObject, const Value &var); diff --git a/src/qml/jsruntime/qv4vtable_p.h b/src/qml/jsruntime/qv4vtable_p.h index 4acefecdf5..aff1ae82d7 100644 --- a/src/qml/jsruntime/qv4vtable_p.h +++ b/src/qml/jsruntime/qv4vtable_p.h @@ -56,6 +56,8 @@ QT_BEGIN_NAMESPACE namespace QV4 { +struct Lookup; + struct OwnPropertyKeyIterator { virtual ~OwnPropertyKeyIterator() = 0; virtual PropertyKey next(const Object *o, Property *p = nullptr, PropertyAttributes *attrs = nullptr) = 0; @@ -84,6 +86,9 @@ struct VTable typedef ReturnedValue (*Call)(const FunctionObject *, const Value *thisObject, const Value *argv, int argc); typedef ReturnedValue (*CallAsConstructor)(const FunctionObject *, const Value *argv, int argc, const Value *newTarget); + typedef ReturnedValue (*ResolveLookupGetter)(const Object *, ExecutionEngine *, Lookup *); + typedef bool (*ResolveLookupSetter)(Object *, ExecutionEngine *, Lookup *, const Value &); + const VTable * const parent; quint32 inlinePropertyOffset : 16; quint32 nInlineProperties : 16; @@ -118,6 +123,9 @@ struct VTable Call call; CallAsConstructor callAsConstructor; + + ResolveLookupGetter resolveLookupGetter; + ResolveLookupSetter resolveLookupSetter; }; @@ -142,6 +150,9 @@ protected: static constexpr VTable::Call virtualCall = nullptr; static constexpr VTable::CallAsConstructor virtualCallAsConstructor = nullptr; + + static constexpr VTable::ResolveLookupGetter virtualResolveLookupGetter = nullptr; + static constexpr VTable::ResolveLookupSetter virtualResolveLookupSetter = nullptr; }; #define DEFINE_MANAGED_VTABLE_INT(classname, parentVTable) \ @@ -181,6 +192,9 @@ protected: \ classname::virtualCall, \ classname::virtualCallAsConstructor, \ + \ + classname::virtualResolveLookupGetter, \ + classname::virtualResolveLookupSetter \ } #define DEFINE_MANAGED_VTABLE(classname) \ -- cgit v1.2.3 From 8a69735c4914d02be9448b6ae98e4c550e1f187a Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Sat, 22 Dec 2018 09:09:57 +0100 Subject: Fix type error exception messages when using member lookups Our tests expect those exceptions to have the same message as when not using lookups. Task-number: QTBUG-69898 Change-Id: Iab36519844a3a49ef9e34346e9afeb2eee9f9ced Reviewed-by: Ulf Hermann --- src/qml/jsruntime/qv4vme_moth.cpp | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4vme_moth.cpp b/src/qml/jsruntime/qv4vme_moth.cpp index 937a535b83..000e1ab9d5 100644 --- a/src/qml/jsruntime/qv4vme_moth.cpp +++ b/src/qml/jsruntime/qv4vme_moth.cpp @@ -594,7 +594,17 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_BEGIN_INSTR(GetLookup) STORE_IP(); STORE_ACC(); + QV4::Lookup *l = function->compilationUnit->runtimeLookups + index; + + if (accumulator.isNullOrUndefined()) { + QString message = QStringLiteral("Cannot read property '%1' of %2") + .arg(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[l->nameIndex]->toQString()) + .arg(accumulator.toQStringNoThrow()); + acc = engine->throwTypeError(message); + goto handleUnwind; + } + acc = l->getter(l, engine, accumulator); CHECK_EXCEPTION; MOTH_END_INSTR(GetLookup) @@ -723,11 +733,23 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_BEGIN_INSTR(CallPropertyLookup) STORE_IP(); Lookup *l = function->compilationUnit->runtimeLookups + lookupIndex; + + if (stack[base].isNullOrUndefined()) { + QString message = QStringLiteral("Cannot call method '%1' of %2") + .arg(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[l->nameIndex]->toQString()) + .arg(stack[base].toQStringNoThrow()); + acc = engine->throwTypeError(message); + goto handleUnwind; + } + // ok to have the value on the stack here Value f = Value::fromReturnedValue(l->getter(l, engine, stack[base])); if (Q_UNLIKELY(!f.isFunctionObject())) { - acc = engine->throwTypeError(); + QString message = QStringLiteral("Property '%1' of object %2 is not a function") + .arg(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[l->nameIndex]->toQString()) + .arg(stack[base].toQStringNoThrow()); + acc = engine->throwTypeError(message); goto handleUnwind; } -- cgit v1.2.3 From 24ec7b6c8352600230c40b61be4bfce07076c9a5 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Sat, 22 Dec 2018 09:41:10 +0100 Subject: Fix up global name determination when compiling ahead of time The list of names is still suboptimal, but at least it's shared now. Task-number: QTBUG-69898 Change-Id: I16c9839c4a1f097053b28caea894b67757972826 Reviewed-by: Ulf Hermann --- src/qml/compiler/qv4codegen.cpp | 4 ++-- src/qml/compiler/qv4codegen_p.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'src/qml') diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index 0ca452b93e..66c334d197 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -103,7 +103,7 @@ Codegen::Codegen(QV4::Compiler::JSUnitGenerator *jsUnitGenerator, bool strict) pushExpr(); } -const char *globalNames[] = { +const char *Codegen::s_globalNames[] = { "isNaN", "parseFloat", "String", @@ -183,7 +183,7 @@ void Codegen::generateFromProgram(const QString &fileName, // // Since this can be called from the loader thread we can't get the list // directly from the engine, so let's hardcode the most important ones here - for (const char **g = globalNames; *g != nullptr; ++g) + for (const char **g = s_globalNames; *g != nullptr; ++g) m_globalNames << QString::fromLatin1(*g); } diff --git a/src/qml/compiler/qv4codegen_p.h b/src/qml/compiler/qv4codegen_p.h index a6355bf93a..0b25d9c53d 100644 --- a/src/qml/compiler/qv4codegen_p.h +++ b/src/qml/compiler/qv4codegen_p.h @@ -754,6 +754,7 @@ public: m_globalNames = globalNames; } + static const char *s_globalNames[]; protected: friend class ScanFunctions; -- cgit v1.2.3 From 5f24122c780b462d7091abc12c9861d1e9713dca Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Sat, 22 Dec 2018 10:56:52 +0100 Subject: Fix lookups the transition between primitive strings and string objects Suppose we have function foo(x) { return x.constructor; } and we call it first with foo("hello") then the lookup will be initialized with a primitive getter from the prototype. When we subsequently call foo(new String("world")) then the primitiveGetterProto() will check that the provided object is of the same type as last time, which erroneously succeeds. Indeed, both are of Managed type. However now we're passing a full-fledged object, which is not a primitive anymore - hence the additional check to ensure that we fall back to the generic getter. Task-number: QTBUG-69898 Change-Id: I3c7a8384bfdf0e31b7c6247cce80fe7448b627b3 Reviewed-by: Ulf Hermann --- src/qml/jsruntime/qv4lookup.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4lookup.cpp b/src/qml/jsruntime/qv4lookup.cpp index 790a5843c2..54bce7d7b3 100644 --- a/src/qml/jsruntime/qv4lookup.cpp +++ b/src/qml/jsruntime/qv4lookup.cpp @@ -379,7 +379,7 @@ ReturnedValue Lookup::getterIndexed(Lookup *l, ExecutionEngine *engine, const Va ReturnedValue Lookup::primitiveGetterProto(Lookup *l, ExecutionEngine *engine, const Value &object) { - if (object.type() == l->primitiveLookup.type) { + if (object.type() == l->primitiveLookup.type && !object.isObject()) { Heap::Object *o = l->primitiveLookup.proto; if (l->primitiveLookup.protoId == o->internalClass->protoId) return l->primitiveLookup.data->asReturnedValue(); @@ -390,7 +390,7 @@ ReturnedValue Lookup::primitiveGetterProto(Lookup *l, ExecutionEngine *engine, c ReturnedValue Lookup::primitiveGetterAccessor(Lookup *l, ExecutionEngine *engine, const Value &object) { - if (object.type() == l->primitiveLookup.type) { + if (object.type() == l->primitiveLookup.type && !object.isObject()) { Heap::Object *o = l->primitiveLookup.proto; if (l->primitiveLookup.protoId == o->internalClass->protoId) { const Value *getter = l->primitiveLookup.data; -- cgit v1.2.3 From dc3e3090d21339d78abc706369117b3396c843af Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Fri, 15 Mar 2019 09:39:58 +0100 Subject: Fix qdoc errors Add ',' to enumerator and rearrange Q_QDOC #ifdefs, fixing: LOG Build & visit PCH for QtQuickDoc src/quick/scenegraph/coreapi/qsgnode.h:97:49: error: missing ',' between enumerators src/qml/qml/qqml.h:593:17: error: expected namespace name Change-Id: I4491c5885c5cdb8a156d9a7abcca5db4d68d1c2e Reviewed-by: Ulf Hermann Reviewed-by: Paul Wicking --- src/qml/qml/qqml.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/qml') diff --git a/src/qml/qml/qqml.h b/src/qml/qml/qqml.h index 05a9f70247..bf9330856d 100644 --- a/src/qml/qml/qqml.h +++ b/src/qml/qml/qqml.h @@ -584,7 +584,6 @@ namespace QtQml { const QMetaObject *, bool create); #ifndef Q_QDOC } -#endif QT_WARNING_PUSH QT_WARNING_DISABLE_CLANG("-Wheader-hygiene") @@ -594,6 +593,8 @@ using namespace QtQml; QT_WARNING_POP +#endif // Q_QDOC + //The C++ version of protected namespaces in qmldir Q_QML_EXPORT bool qmlProtectModule(const char* uri, int majVersion); Q_QML_EXPORT void qmlRegisterModule(const char *uri, int versionMajor, int versionMinor); -- cgit v1.2.3 From f396cc753da75c68c6a501379a18df3099697f42 Mon Sep 17 00:00:00 2001 From: Kirill Burtsev Date: Thu, 14 Mar 2019 18:41:03 +0100 Subject: Fix leaking propertyCache inside QQmlAdaptorModel::Accessors QQmlRefPointer works in AddRef mode for assignment of a raw pointer. This creates additional reference to original object and it will never be garbage collected in this case. Amends change 99e2356a73. Task-number: QTBUG-74148 Change-Id: Ic0f03e842965c5c82b357d10ab8b0d6a62411fc8 Reviewed-by: Simon Hausmann --- src/qml/util/qqmladaptormodel.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/qml') diff --git a/src/qml/util/qqmladaptormodel.cpp b/src/qml/util/qqmladaptormodel.cpp index d9cb6506b8..a9a38c5381 100644 --- a/src/qml/util/qqmladaptormodel.cpp +++ b/src/qml/util/qqmladaptormodel.cpp @@ -525,7 +525,7 @@ public: metaObject.reset(builder.toMetaObject()); *static_cast(this) = *metaObject; - propertyCache = new QQmlPropertyCache(metaObject.data(), model.modelItemRevision); + propertyCache.adopt(new QQmlPropertyCache(metaObject.data(), model.modelItemRevision)); } }; @@ -659,8 +659,8 @@ public: { VDMListDelegateDataType *dataType = const_cast(this); if (!propertyCache) { - dataType->propertyCache = new QQmlPropertyCache( - &QQmlDMListAccessorData::staticMetaObject, model.modelItemRevision); + dataType->propertyCache.adopt(new QQmlPropertyCache( + &QQmlDMListAccessorData::staticMetaObject, model.modelItemRevision)); } return new QQmlDMListAccessorData( -- cgit v1.2.3 From f4649ebfe5be81c24a384c0806fd015e756a4dca Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Mon, 25 Feb 2019 16:01:03 +0100 Subject: Update JavaScript documentation Update function list and refer to the 7th edition of the ECMAScript standard. Fixes: QTBUG-73837 Change-Id: I4332de3ded0373393107b6d5bab95363a0b9ebb9 Reviewed-by: Simon Hausmann --- src/qml/doc/src/javascript/functionlist.qdoc | 61 +++++++++++++++++++++++++ src/qml/doc/src/javascript/hostenvironment.qdoc | 5 +- 2 files changed, 63 insertions(+), 3 deletions(-) (limited to 'src/qml') diff --git a/src/qml/doc/src/javascript/functionlist.qdoc b/src/qml/doc/src/javascript/functionlist.qdoc index 24ff640284..7a6a922480 100644 --- a/src/qml/doc/src/javascript/functionlist.qdoc +++ b/src/qml/doc/src/javascript/functionlist.qdoc @@ -55,6 +55,8 @@ \li decodeURIComponent(encodedURIComponent) \li encodeURI(uri) \li encodeURIComponent(uriComponent) + \li escape(string) + \li unescape(string) \endlist \section2 Constructor Properties @@ -63,11 +65,20 @@ \li Object \li Function \li Array + \li ArrayBuffer \li String \li Boolean \li Number + \li DataView \li Date + \li Promise \li RegExp + \li Map + \li WeakMap + \li Set + \li WeakSet + \li SharedArrayBuffer + \li Symbol \li Error \li EvalError \li RangeError @@ -80,8 +91,11 @@ \section2 Other Properties \list + \li Atomics \li Math \li JSON + \li Reflect + \li Proxy \endlist \section1 The Object Object @@ -92,12 +106,19 @@ \list \li getPrototypeOf(O) + \li setPrototypeOf(O, P) \li getOwnPropertyDescriptor(O, P) + \li getOwnPropertyDescriptors(O) \li getOwnPropertyNames(O) + \li getOwnPropertySymbols(O) + \li assign(O [, Properties]) \li create(O [, Properties]) \li defineProperty(O, P, Attributes) \li defineProperties(O, Properties) + \li entries(O) + \li is(V1, V2) \li keys(O) + \li values(O) \li seal(O) \li isSealed(O) \li freeze(O) @@ -117,6 +138,8 @@ \li hasOwnProperty(V) \li isPrototypeOf(V) \li propertyIsEnumerable(V) + \li __defineGetter__(P, F) + \li __defineSetter__(P, F) \endlist \section1 Function Objects @@ -130,6 +153,7 @@ \li apply(thisArg, argArray) \li call(thisArg [, arg1 [, arg2, ...]]) \li bind((thisArg [, arg1 [, arg2, …]]) + \li [Symbol.hasInstance](O) \endlist \section1 Array Objects @@ -142,9 +166,14 @@ \li toString() \li toLocaleString() \li concat([item1 [, item2 [, ...]]]) + \li copyWithin([item1 [, item2 [, ...]]]) + \li entries() + \li fill(item [, index1 [, index2]]) \li join(separator) \li find(callbackfn [, thisArg]) // ECMAScript 6: Added in Qt 5.9 \li findIndex(callbackfn [, thisArg]) // ECMAScript 6: Added in Qt 5.9 + \li includes(item) + \li keys() \li pop() \li push([item1 [, item2 [, ...]]]) \li reverse() @@ -162,6 +191,8 @@ \li filter(callbackfn [, thisArg]) \li reduce(callbackfn [, initialValue]) \li reduceRight(callbackfn [, initialValue]) + \li values() + \li [Symbol.iterator]() \endlist \section1 String Objects @@ -175,6 +206,7 @@ \li valueOf() \li charAt(pos) \li charCodeAt(pos) + \li codePointAt(pos) \li concat([string1 [, string2 [, ...]]]) \li endsWith(searchString [, endPosition ]) // ECMAScript 6: Added in Qt 5.8 \li includes(searchString [, position ]) // ECMAScript 6: Added in 5.8 @@ -182,18 +214,23 @@ \li lastIndexOf(searchString, position) \li localeCompare(that) \li match(regexp) + \li normalize() + \li padEnd(length [, string]) + \li padStart(length [, string]) \li repeat(count) // ECMAScript 6: Added in Qt 5.9 \li replace(searchValue, replaceValue) \li search(regexp) \li slice(start, end) \li split(separator, limit) \li startsWith(searchString [, position ]) // ECMAScript 6: Added in Qt 5.8 + \li substr(start, length) \li substring(start, end) \li toLowerCase() \li toLocaleLowerCase() \li toUpperCase() \li toLocaleUpperCase() \li trim() + \li [Symbol.iterator]() \endlist Additionally, the QML engine adds the following functions to the \l String prototype: @@ -222,6 +259,7 @@ \list \li toString(radix) \li toLocaleString() + \li valueOf() \li toFixed(fractionDigits) \li toExponential(fractionDigits) \li toPrecision(precision) @@ -245,12 +283,16 @@ \li MAX_VALUE \li MIN_VALUE \li EPSILON // ECMAScript 6: Added in Qt 5.8 + \li MAX_SAFE_INTEGER + \li MIN_SAFE_INTEGER \endlist \section3 Function Properties \list \li isFinite(x) // ECMAScript 6: Added in Qt 5.8 + \li isInteger(x) + \li isSafeInteger(x) \li isNaN(x) // ECMAScript 6: Added in Qt 5.8 \endlist @@ -274,14 +316,27 @@ \list \li abs(x) \li acos(x) + \li acosh(x) \li asin(x) + \li asinh(x) \li atan(x) + \li atanh(x) \li atan2(y, x) + \li cbrt(x) \li ceil(x) + \li clz32(x) \li cos(x) + \li cosh(x) \li exp(x) + \li expm1(x) \li floor(x) + \li fround(x) + \li hypot(x, y) + \li imul(x, y) \li log(x) + \li log10(x) + \li log1p(x) + \li log2(x) \li max([value1 [, value2 [, ...]]]) \li min([value1 [, value2 [, ...]]]) \li pow(x, y) @@ -289,8 +344,11 @@ \li round(x) \li sign(x) // ECMAScript 6: Added in Qt 5.8 \li sin(x) + \li sinh(x) \li sqrt(x) \li tan(x) + \li tanh(x) + \li trunc(x) \endlist \section1 Date Objects @@ -338,11 +396,14 @@ \li setUTCDate(date) \li setMonth(month [, date]) \li setUTCMonth(month [, date]) + \li setYear(year) \li setFullYear(year [, month [, date]]) \li setUTCFullYear(year [, month [, date]]) \li toUTCString() + \li toGMTString() \li toISOString() \li toJSON() + \li [Symbol.toPrimitive](hint) \endlist Additionally, the QML engine adds the following functions to the \l Date prototype: diff --git a/src/qml/doc/src/javascript/hostenvironment.qdoc b/src/qml/doc/src/javascript/hostenvironment.qdoc index eb40f10065..c22c392b80 100644 --- a/src/qml/doc/src/javascript/hostenvironment.qdoc +++ b/src/qml/doc/src/javascript/hostenvironment.qdoc @@ -40,11 +40,10 @@ not provide a \c window object or \c{DOM API} as commonly found in a browser env Like a browser or server-side JavaScript environment, the QML runtime implements the \l{ECMA-262}{ECMAScript Language Specification} standard. This provides access to all of the built-in types and functions defined by the standard, such as Object, Array, and Math. -The QML runtime implements the 5th edition of the standard, which is the same edition commonly -implemented by browsers. +The QML runtime implements the 7th edition of the standard. The standard ECMAScript built-ins are not explicitly documented in the QML documentation. For more -information on their use, please refer to the ECMA-262 5th edition standard or one of the many online +information on their use, please refer to the ECMA-262 7th edition standard or one of the many online JavaScript reference and tutorial sites, such as the \l{W3Schools JavaScript Reference} (JavaScript Objects Reference section). Many sites focus on JavaScript in the browser, so in some cases you may need to double check the specification to determine whether a given function or object is part of standard ECMAScript or -- cgit v1.2.3 From 0dd884aca1fffcd94fbe55006c94363415aa0965 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Thu, 14 Mar 2019 10:42:19 +0100 Subject: Baseline JIT: Save accumulator in toInt32LhsAcc() toInt32LhsAcc convertes both the lhs and the accumulator to int32. If the accumulator is not saved, a GC run during the conversion of the lhs might trash its value. Fixes: QTBUG-74058 Change-Id: Ic42693061c7d483bb430d77bcc095de6ff9a6843 Reviewed-by: Simon Hausmann --- src/qml/jit/qv4baselineassembler.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) (limited to 'src/qml') diff --git a/src/qml/jit/qv4baselineassembler.cpp b/src/qml/jit/qv4baselineassembler.cpp index 5c08c42977..b13f646360 100644 --- a/src/qml/jit/qv4baselineassembler.cpp +++ b/src/qml/jit/qv4baselineassembler.cpp @@ -208,17 +208,20 @@ public: isNumber.link(this); } + // this converts both the lhs and the accumulator to int32 void toInt32LhsAcc(Address lhs, RegisterID lhsTarget) { load64(lhs, lhsTarget); urshift64(lhsTarget, TrustedImm32(Value::QuickType_Shift), ScratchRegister2); auto lhsIsInt = branch32(Equal, TrustedImm32(Value::QT_Int), ScratchRegister2); - pushAligned(AccumulatorRegister); + const Address accumulatorStackAddress(JSStackFrameRegister, + offsetof(CallData, accumulator)); + storeAccumulator(accumulatorStackAddress); move(lhsTarget, registerForArg(0)); callHelper(toInt32Helper); move(ReturnValueRegister, lhsTarget); - popAligned(AccumulatorRegister); + loadAccumulator(accumulatorStackAddress); lhsIsInt.link(this); urshift64(AccumulatorRegister, TrustedImm32(Value::QuickType_Shift), ScratchRegister2); @@ -498,6 +501,7 @@ public: isNumber.link(this); } + // this converts both the lhs and the accumulator to int32 void toInt32LhsAcc(Address lhs, RegisterID lhsTarget) { bool accumulatorNeedsSaving = AccumulatorRegisterValue == ReturnValueRegisterValue @@ -510,32 +514,28 @@ public: auto lhsIsInt = jump(); lhsIsNotInt.link(this); - if (accumulatorNeedsSaving) { - push(AccumulatorRegisterTag); - push(AccumulatorRegisterValue); - } + + // Save accumulator from being garbage collected, no matter if we will reuse the register. + const Address accumulatorStackAddress(JSStackFrameRegister, + offsetof(CallData, accumulator)); + storeAccumulator(accumulatorStackAddress); if (ArgInRegCount < 2) { - if (!accumulatorNeedsSaving) - subPtr(TrustedImm32(2 * PointerSize), StackPointerRegister); + subPtr(TrustedImm32(2 * PointerSize), StackPointerRegister); push(lhsTarget); load32(lhs, lhsTarget); push(lhsTarget); } else { - if (accumulatorNeedsSaving) - subPtr(TrustedImm32(2 * PointerSize), StackPointerRegister); move(lhsTarget, registerForArg(1)); load32(lhs, registerForArg(0)); } callHelper(toInt32Helper); move(ReturnValueRegisterValue, lhsTarget); - if (accumulatorNeedsSaving) { - addPtr(TrustedImm32(2 * PointerSize), StackPointerRegister); - pop(AccumulatorRegisterValue); - pop(AccumulatorRegisterTag); - } else if (ArgInRegCount < 2) { + if (ArgInRegCount < 2) addPtr(TrustedImm32(4 * PointerSize), StackPointerRegister); - } + + if (accumulatorNeedsSaving) // otherwise it's still the same + loadAccumulator(accumulatorStackAddress); lhsIsInt.link(this); -- cgit v1.2.3 From db292d1fe70a0cfaf315a72d099441cf3969e284 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Fri, 21 Dec 2018 16:00:48 +0100 Subject: Enable lookups in QML The main feature that needs to be implemented in order to enable lookups in QML files is to respect that the QObject wrapper has its own storage layer (meta-object properties). Lookups need to be able to index those when the base is a QObject. This is done by caching the property data and guarding the validity by comparing property cache pointers. The same lookup logic is also implemented for value type wrappers. OVerall there's more that can be done with lookups in meta-objects, for constant properties for example. For "global" lookups we have a safeguard in place that generates a LoadName instruction for property access that should end up in the qml context wrapper. So no changes are needed here at first, but the lookup in the QML context can be optimized in the future. The way of storing the property cache in the lookup itself trades ugliness on destruction against the creation of less internal classes. Another option would be to store the property cache in the internal class and let QObjectWrapper always transition via the property cache. Task-number: QTBUG-69898 Change-Id: I9c378c071acc6d7d4a34a2a76616f9594119d515 Reviewed-by: Ulf Hermann --- src/qml/compiler/qqmltypecompiler.cpp | 1 - src/qml/compiler/qv4compileddata.cpp | 15 +++ src/qml/jsruntime/qv4lookup_p.h | 12 +++ src/qml/jsruntime/qv4qobjectwrapper.cpp | 173 +++++++++++++++++++++++++------- src/qml/jsruntime/qv4qobjectwrapper_p.h | 7 +- src/qml/jsruntime/qv4value_p.h | 16 +++ src/qml/qml/qqmlvaluetypewrapper.cpp | 151 +++++++++++++++++++++------- src/qml/qml/qqmlvaluetypewrapper_p.h | 3 + src/qml/types/qqmllistmodel.cpp | 7 ++ src/qml/types/qqmllistmodel_p_p.h | 2 + 10 files changed, 311 insertions(+), 76 deletions(-) (limited to 'src/qml') diff --git a/src/qml/compiler/qqmltypecompiler.cpp b/src/qml/compiler/qqmltypecompiler.cpp index 75ed8dd10f..f753c78b1a 100644 --- a/src/qml/compiler/qqmltypecompiler.cpp +++ b/src/qml/compiler/qqmltypecompiler.cpp @@ -147,7 +147,6 @@ QQmlRefPointer QQmlTypeCompiler::compile() document->jsModule.finalUrl = typeData->finalUrlString(); QmlIR::JSCodeGen v4CodeGenerator(document->code, &document->jsGenerator, &document->jsModule, &document->jsParserEngine, document->program, typeNameCache.data(), &document->jsGenerator.stringTable, engine->v8engine()->illegalNames()); - v4CodeGenerator.setUseFastLookups(false); QQmlJSCodeGenerator jsCodeGen(this, &v4CodeGenerator); if (!jsCodeGen.generateCodeForComponents()) return nullptr; diff --git a/src/qml/compiler/qv4compileddata.cpp b/src/qml/compiler/qv4compileddata.cpp index 65690ec1f6..e428340f62 100644 --- a/src/qml/compiler/qv4compileddata.cpp +++ b/src/qml/compiler/qv4compileddata.cpp @@ -51,6 +51,8 @@ #include #include #include +#include +#include #include "qv4compilationunitmapper_p.h" #include #include @@ -270,6 +272,19 @@ void CompilationUnit::unlink() propertyCaches.clear(); + if (runtimeLookups) { + for (uint i = 0; i < data->lookupTableSize; ++i) { + QV4::Lookup &l = runtimeLookups[i]; + if (l.getter == QV4::QObjectWrapper::lookupGetter) { + if (QQmlPropertyCache *pc = l.qobjectLookup.propertyCache) + pc->release(); + } else if (l.getter == QQmlValueTypeWrapper::lookupGetter) { + if (QQmlPropertyCache *pc = l.qgadgetLookup.propertyCache) + pc->release(); + } + } + } + dependentScripts.clear(); typeNameCache = nullptr; diff --git a/src/qml/jsruntime/qv4lookup_p.h b/src/qml/jsruntime/qv4lookup_p.h index 83e561863b..b0d5706dea 100644 --- a/src/qml/jsruntime/qv4lookup_p.h +++ b/src/qml/jsruntime/qv4lookup_p.h @@ -116,6 +116,18 @@ struct Lookup { quintptr _unused2; uint index; } indexedLookup; + struct { + Heap::InternalClass *ic; + quintptr unused; + QQmlPropertyCache *propertyCache; + QQmlPropertyData *propertyData; + } qobjectLookup; + struct { + Heap::InternalClass *ic; + quintptr unused; + QQmlPropertyCache *propertyCache; + QQmlPropertyData *propertyData; + } qgadgetLookup; }; uint nameIndex; diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index 40be6f41c8..711c910906 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -56,6 +56,8 @@ #include #include #include +#include +#include #if QT_CONFIG(qml_sequence_object) #include @@ -269,9 +271,53 @@ ReturnedValue QObjectWrapper::getProperty(ExecutionEngine *engine, QObject *obje } } +static OptionalReturnedValue getDestroyOrToStringMethod(ExecutionEngine *v4, String *name, QObject *qobj, bool *hasProperty = nullptr) +{ + int index = 0; + if (name->equals(v4->id_destroy())) + index = QV4::QObjectMethod::DestroyMethod; + else if (name->equals(v4->id_toString())) + index = QV4::QObjectMethod::ToStringMethod; + else + return OptionalReturnedValue(); + + if (hasProperty) + *hasProperty = true; + ExecutionContext *global = v4->rootContext(); + return OptionalReturnedValue(QV4::QObjectMethod::create(global, qobj, index)); +} + +static OptionalReturnedValue getPropertyFromImports(ExecutionEngine *v4, String *name, QQmlContextData *qmlContext, QObject *qobj, + bool *hasProperty = nullptr) +{ + if (!qmlContext || !qmlContext->imports) + return OptionalReturnedValue(); + + QQmlTypeNameCache::Result r = qmlContext->imports->query(name); + + if (hasProperty) + *hasProperty = true; + + if (!r.isValid()) + return OptionalReturnedValue(); + + if (r.scriptIndex != -1) { + return OptionalReturnedValue(QV4::Encode::undefined()); + } else if (r.type.isValid()) { + return OptionalReturnedValue(QQmlTypeWrapper::create(v4, qobj,r.type, Heap::QQmlTypeWrapper::ExcludeEnums)); + } else if (r.importNamespace) { + return OptionalReturnedValue(QQmlTypeWrapper::create(v4, qobj, qmlContext->imports, r.importNamespace, + Heap::QQmlTypeWrapper::ExcludeEnums)); + } + Q_UNREACHABLE(); + return OptionalReturnedValue(); +} + ReturnedValue QObjectWrapper::getQmlProperty(QQmlContextData *qmlContext, String *name, QObjectWrapper::RevisionMode revisionMode, bool *hasProperty, bool includeImports) const { + // Keep this code in sync with ::virtualResolveLookupGetter + if (QQmlData::wasDeleted(d()->object())) { if (hasProperty) *hasProperty = false; @@ -280,39 +326,17 @@ ReturnedValue QObjectWrapper::getQmlProperty(QQmlContextData *qmlContext, String ExecutionEngine *v4 = engine(); - if (name->equals(v4->id_destroy()) || name->equals(v4->id_toString())) { - int index = name->equals(v4->id_destroy()) ? QV4::QObjectMethod::DestroyMethod : QV4::QObjectMethod::ToStringMethod; - if (hasProperty) - *hasProperty = true; - ExecutionContext *global = v4->rootContext(); - return QV4::QObjectMethod::create(global, d()->object(), index); - } + if (auto methodValue = getDestroyOrToStringMethod(v4, name, d()->object(), hasProperty)) + return *methodValue; QQmlPropertyData local; QQmlPropertyData *result = findProperty(v4, qmlContext, name, revisionMode, &local); if (!result) { + // Check for attached properties if (includeImports && name->startsWithUpper()) { - // Check for attached properties - if (qmlContext && qmlContext->imports) { - QQmlTypeNameCache::Result r = qmlContext->imports->query(name); - - if (hasProperty) - *hasProperty = true; - - if (r.isValid()) { - if (r.scriptIndex != -1) { - return QV4::Encode::undefined(); - } else if (r.type.isValid()) { - return QQmlTypeWrapper::create(v4, d()->object(), - r.type, Heap::QQmlTypeWrapper::ExcludeEnums); - } else if (r.importNamespace) { - return QQmlTypeWrapper::create(v4, d()->object(), - qmlContext->imports, r.importNamespace, Heap::QQmlTypeWrapper::ExcludeEnums); - } - Q_ASSERT(!"Unreachable"); - } - } + if (auto importProperty = getPropertyFromImports(v4, name, qmlContext, d()->object(), hasProperty)) + return *importProperty; } return QV4::Object::virtualGet(this, name->propertyKey(), this, hasProperty); } @@ -361,13 +385,8 @@ ReturnedValue QObjectWrapper::getQmlProperty(QV4::ExecutionEngine *engine, QQmlC return QV4::Encode::null(); } - if (name->equals(engine->id_destroy()) || name->equals(engine->id_toString())) { - int index = name->equals(engine->id_destroy()) ? QV4::QObjectMethod::DestroyMethod : QV4::QObjectMethod::ToStringMethod; - if (hasProperty) - *hasProperty = true; - ExecutionContext *global = engine->rootContext(); - return QV4::QObjectMethod::create(global, object, index); - } + if (auto methodValue = getDestroyOrToStringMethod(engine, name, object, hasProperty)) + return *methodValue; QQmlData *ddata = QQmlData::get(object, false); QQmlPropertyData local; @@ -829,6 +848,86 @@ OwnPropertyKeyIterator *QObjectWrapper::virtualOwnPropertyKeys(const Object *m, return new QObjectWrapperOwnPropertyKeyIterator; } +ReturnedValue QObjectWrapper::virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup) +{ + // Keep this code in sync with ::getQmlProperty + PropertyKey id = engine->identifierTable->asPropertyKey(engine->currentStackFrame->v4Function->compilationUnit-> + runtimeStrings[lookup->nameIndex]); + if (!id.isString()) + return Object::virtualResolveLookupGetter(object, engine, lookup); + Scope scope(engine); + + const QObjectWrapper *This = static_cast(object); + ScopedString name(scope, id.asStringOrSymbol()); + QQmlContextData *qmlContext = engine->callingQmlContext(); + + QObject * const qobj = This->d()->object(); + + if (QQmlData::wasDeleted(qobj)) + return QV4::Encode::undefined(); + + if (auto methodValue = getDestroyOrToStringMethod(engine, name, qobj)) + return *methodValue; + + QQmlData *ddata = QQmlData::get(qobj, false); + if (!ddata || !ddata->propertyCache) { + QQmlPropertyData local; + QQmlPropertyData *property = QQmlPropertyCache::property(engine->jsEngine(), qobj, name, qmlContext, local); + return getProperty(engine, qobj, property, /*captureRequired*/true); + } + QQmlPropertyData *property = ddata->propertyCache->property(name.getPointer(), qobj, qmlContext); + + if (!property) { + // Check for attached properties + if (name->startsWithUpper()) { + if (auto importProperty = getPropertyFromImports(engine, name, qmlContext, qobj)) + return *importProperty; + } + return QV4::Object::virtualResolveLookupGetter(object, engine, lookup); + } + + lookup->qobjectLookup.ic = This->internalClass(); + lookup->qobjectLookup.propertyCache = ddata->propertyCache; + lookup->qobjectLookup.propertyCache->addref(); + lookup->qobjectLookup.propertyData = property; + lookup->getter = QV4::QObjectWrapper::lookupGetter; + return lookup->getter(lookup, engine, *object); +} + +ReturnedValue QObjectWrapper::lookupGetter(Lookup *lookup, ExecutionEngine *engine, const Value &object) +{ + const auto revertLookup = [lookup, engine, &object]() { + lookup->qobjectLookup.propertyCache->release(); + lookup->qobjectLookup.propertyCache = nullptr; + lookup->getter = Lookup::getterGeneric; + return Lookup::getterGeneric(lookup, engine, object); + }; + + // we can safely cast to a QV4::Object here. If object is something else, + // the internal class won't match + Heap::Object *o = static_cast(object.heapObject()); + if (!o || o->internalClass != lookup->qobjectLookup.ic) + return revertLookup(); + + const Heap::QObjectWrapper *This = static_cast(o); + QObject *qobj = This->object(); + if (QQmlData::wasDeleted(qobj)) + return QV4::Encode::undefined(); + + QQmlData *ddata = QQmlData::get(qobj, /*create*/false); + if (!ddata || ddata->propertyCache != lookup->qobjectLookup.propertyCache) + return revertLookup(); + + QQmlPropertyData *property = lookup->qobjectLookup.propertyData; + return getProperty(engine, qobj, property, /*captureRequired = */true); +} + +bool QObjectWrapper::virtualResolveLookupSetter(Object *object, ExecutionEngine *engine, Lookup *lookup, + const Value &value) +{ + return Object::virtualResolveLookupSetter(object, engine, lookup, value); +} + namespace QV4 { struct QObjectSlotDispatcher : public QtPrivate::QSlotObjectBase @@ -1920,13 +2019,13 @@ ReturnedValue QObjectMethod::create(ExecutionContext *scope, QObject *object, in return method.asReturnedValue(); } -ReturnedValue QObjectMethod::create(ExecutionContext *scope, const QQmlValueTypeWrapper *valueType, int index) +ReturnedValue QObjectMethod::create(ExecutionContext *scope, Heap::QQmlValueTypeWrapper *valueType, int index) { Scope valueScope(scope); Scoped method(valueScope, valueScope.engine->memoryManager->allocate(scope)); - method->d()->setPropertyCache(valueType->d()->propertyCache()); + method->d()->setPropertyCache(valueType->propertyCache()); method->d()->index = index; - method->d()->valueTypeWrapper.set(valueScope.engine, valueType->d()); + method->d()->valueTypeWrapper.set(valueScope.engine, valueType); return method.asReturnedValue(); } diff --git a/src/qml/jsruntime/qv4qobjectwrapper_p.h b/src/qml/jsruntime/qv4qobjectwrapper_p.h index 6465ee0fa6..a09e7b6e95 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper_p.h +++ b/src/qml/jsruntime/qv4qobjectwrapper_p.h @@ -183,6 +183,11 @@ struct Q_QML_EXPORT QObjectWrapper : public Object void destroyObject(bool lastCall); static ReturnedValue getProperty(ExecutionEngine *engine, QObject *object, QQmlPropertyData *property, bool captureRequired = true); + + static ReturnedValue virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup); + static ReturnedValue lookupGetter(Lookup *l, ExecutionEngine *engine, const Value &object); + static bool virtualResolveLookupSetter(Object *object, ExecutionEngine *engine, Lookup *lookup, const Value &value); + protected: static void setProperty(ExecutionEngine *engine, QObject *object, QQmlPropertyData *property, const Value &value); @@ -228,7 +233,7 @@ struct Q_QML_EXPORT QObjectMethod : public QV4::FunctionObject enum { DestroyMethod = -1, ToStringMethod = -2 }; static ReturnedValue create(QV4::ExecutionContext *scope, QObject *object, int index); - static ReturnedValue create(QV4::ExecutionContext *scope, const QQmlValueTypeWrapper *valueType, int index); + static ReturnedValue create(QV4::ExecutionContext *scope, Heap::QQmlValueTypeWrapper *valueType, int index); int methodIndex() const { return d()->index; } QObject *object() const { return d()->object(); } diff --git a/src/qml/jsruntime/qv4value_p.h b/src/qml/jsruntime/qv4value_p.h index 20a84beccd..b4a045edfb 100644 --- a/src/qml/jsruntime/qv4value_p.h +++ b/src/qml/jsruntime/qv4value_p.h @@ -863,6 +863,22 @@ struct ValueArray { // have wrong offsets between host and target. Q_STATIC_ASSERT(offsetof(ValueArray<0>, values) == 8); +class OptionalReturnedValue { + ReturnedValue value; +public: + + OptionalReturnedValue() : value(Value::emptyValue().asReturnedValue()) {} + explicit OptionalReturnedValue(ReturnedValue v) + : value(v) + { + Q_ASSERT(!Value::fromReturnedValue(v).isEmpty()); + } + + ReturnedValue operator->() const { return value; } + ReturnedValue operator*() const { return value; } + explicit operator bool() const { return !Value::fromReturnedValue(value).isEmpty(); } +}; + } QT_END_NAMESPACE diff --git a/src/qml/qml/qqmlvaluetypewrapper.cpp b/src/qml/qml/qqmlvaluetypewrapper.cpp index b503d75a47..b22d1530e2 100644 --- a/src/qml/qml/qqmlvaluetypewrapper.cpp +++ b/src/qml/qml/qqmlvaluetypewrapper.cpp @@ -51,6 +51,8 @@ #include #include #include +#include +#include #include QT_BEGIN_NAMESPACE @@ -372,6 +374,117 @@ ReturnedValue QQmlValueTypeWrapper::method_toString(const FunctionObject *b, con return Encode(b->engine()->newString(result)); } +Q_ALWAYS_INLINE static ReturnedValue getGadgetProperty(ExecutionEngine *engine, + Heap::QQmlValueTypeWrapper *valueTypeWrapper, + QQmlPropertyData *property) +{ + if (property->isFunction()) { + // calling a Q_INVOKABLE function of a value type + return QV4::QObjectMethod::create(engine->rootContext(), valueTypeWrapper, property->coreIndex()); + } + +#define VALUE_TYPE_LOAD(metatype, cpptype, constructor) \ + if (property->propType() == metatype) { \ + cpptype v; \ + void *args[] = { &v, nullptr }; \ + metaObject->d.static_metacall(reinterpret_cast(valueTypeWrapper->gadgetPtr), \ + QMetaObject::ReadProperty, index, args); \ + return QV4::Encode(constructor(v)); \ + } + + const QMetaObject *metaObject = valueTypeWrapper->propertyCache()->metaObject(); + + int index = property->coreIndex(); + QQmlMetaObject::resolveGadgetMethodOrPropertyIndex(QMetaObject::ReadProperty, &metaObject, &index); + + // These four types are the most common used by the value type wrappers + VALUE_TYPE_LOAD(QMetaType::QReal, qreal, qreal); + VALUE_TYPE_LOAD(QMetaType::Int || property->isEnum(), int, int); + VALUE_TYPE_LOAD(QMetaType::Int, int, int); + VALUE_TYPE_LOAD(QMetaType::QString, QString, engine->newString); + VALUE_TYPE_LOAD(QMetaType::Bool, bool, bool); + + QVariant v; + void *args[] = { nullptr, nullptr }; + if (property->propType() == QMetaType::QVariant) { + args[0] = &v; + } else { + v = QVariant(property->propType(), static_cast(nullptr)); + args[0] = v.data(); + } + metaObject->d.static_metacall(reinterpret_cast(valueTypeWrapper->gadgetPtr), QMetaObject::ReadProperty, + index, args); + return engine->fromVariant(v); +#undef VALUE_TYPE_LOAD +} + +ReturnedValue QQmlValueTypeWrapper::virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, + Lookup *lookup) +{ + PropertyKey id = engine->identifierTable->asPropertyKey(engine->currentStackFrame->v4Function->compilationUnit-> + runtimeStrings[lookup->nameIndex]); + if (!id.isString()) + return Object::virtualResolveLookupGetter(object, engine, lookup); + + const QQmlValueTypeWrapper *r = static_cast(object); + QV4::ExecutionEngine *v4 = r->engine(); + Scope scope(v4); + ScopedString name(scope, id.asStringOrSymbol()); + + // Note: readReferenceValue() can change the reference->type. + if (const QQmlValueTypeReference *reference = r->as()) { + if (!reference->readReferenceValue()) + return Value::undefinedValue().asReturnedValue(); + } + + QQmlPropertyData *result = r->d()->propertyCache()->property(name.getPointer(), nullptr, nullptr); + if (!result) + return QV4::Object::virtualResolveLookupGetter(object, engine, lookup); + + lookup->qgadgetLookup.ic = r->internalClass(); + lookup->qgadgetLookup.propertyCache = r->d()->propertyCache(); + lookup->qgadgetLookup.propertyCache->addref(); + lookup->qgadgetLookup.propertyData = result; + lookup->getter = QQmlValueTypeWrapper::lookupGetter; + return lookup->getter(lookup, engine, *object); +} + +ReturnedValue QQmlValueTypeWrapper::lookupGetter(Lookup *lookup, ExecutionEngine *engine, const Value &object) +{ + const auto revertLookup = [lookup, engine, &object]() { + lookup->qgadgetLookup.propertyCache->release(); + lookup->qgadgetLookup.propertyCache = nullptr; + lookup->getter = Lookup::getterGeneric; + return Lookup::getterGeneric(lookup, engine, object); + }; + + // we can safely cast to a QV4::Object here. If object is something else, + // the internal class won't match + Heap::Object *o = static_cast(object.heapObject()); + if (!o || o->internalClass != lookup->qgadgetLookup.ic) + return revertLookup(); + + Heap::QQmlValueTypeWrapper *valueTypeWrapper = + const_cast(static_cast(o)); + if (valueTypeWrapper->propertyCache() != lookup->qgadgetLookup.propertyCache) + return revertLookup(); + + if (lookup->qgadgetLookup.ic->vtable == QQmlValueTypeReference::staticVTable()) { + Scope scope(engine); + Scoped referenceWrapper(scope, valueTypeWrapper); + referenceWrapper->readReferenceValue(); + } + + QQmlPropertyData *property = lookup->qgadgetLookup.propertyData; + return getGadgetProperty(engine, valueTypeWrapper, property); +} + +bool QQmlValueTypeWrapper::virtualResolveLookupSetter(Object *object, ExecutionEngine *engine, Lookup *lookup, + const Value &value) +{ + return Object::virtualResolveLookupSetter(object, engine, lookup, value); +} + ReturnedValue QQmlValueTypeWrapper::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) { Q_ASSERT(m->as()); @@ -397,43 +510,7 @@ ReturnedValue QQmlValueTypeWrapper::virtualGet(const Managed *m, PropertyKey id, if (hasProperty) *hasProperty = true; - if (result->isFunction()) - // calling a Q_INVOKABLE function of a value type - return QV4::QObjectMethod::create(v4->rootContext(), r, result->coreIndex()); - -#define VALUE_TYPE_LOAD(metatype, cpptype, constructor) \ - if (result->propType() == metatype) { \ - cpptype v; \ - void *args[] = { &v, 0 }; \ - metaObject->d.static_metacall(reinterpret_cast(gadget), QMetaObject::ReadProperty, index, args); \ - return QV4::Encode(constructor(v)); \ - } - - const QMetaObject *metaObject = r->d()->propertyCache()->metaObject(); - - int index = result->coreIndex(); - QQmlMetaObject::resolveGadgetMethodOrPropertyIndex(QMetaObject::ReadProperty, &metaObject, &index); - - void *gadget = r->d()->gadgetPtr; - - // These four types are the most common used by the value type wrappers - VALUE_TYPE_LOAD(QMetaType::QReal, qreal, qreal); - VALUE_TYPE_LOAD(QMetaType::Int || result->isEnum(), int, int); - VALUE_TYPE_LOAD(QMetaType::Int, int, int); - VALUE_TYPE_LOAD(QMetaType::QString, QString, v4->newString); - VALUE_TYPE_LOAD(QMetaType::Bool, bool, bool); - - QVariant v; - void *args[] = { nullptr, nullptr }; - if (result->propType() == QMetaType::QVariant) { - args[0] = &v; - } else { - v = QVariant(result->propType(), static_cast(nullptr)); - args[0] = v.data(); - } - metaObject->d.static_metacall(reinterpret_cast(gadget), QMetaObject::ReadProperty, index, args); - return v4->fromVariant(v); -#undef VALUE_TYPE_ACCESSOR + return getGadgetProperty(v4, r->d(), result); } bool QQmlValueTypeWrapper::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver) diff --git a/src/qml/qml/qqmlvaluetypewrapper_p.h b/src/qml/qml/qqmlvaluetypewrapper_p.h index 8db9474132..baac129afa 100644 --- a/src/qml/qml/qqmlvaluetypewrapper_p.h +++ b/src/qml/qml/qqmlvaluetypewrapper_p.h @@ -112,6 +112,9 @@ public: static PropertyAttributes virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p); static OwnPropertyKeyIterator *virtualOwnPropertyKeys(const Object *m, Value *target); static ReturnedValue method_toString(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc); + static ReturnedValue virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup); + static bool virtualResolveLookupSetter(Object *object, ExecutionEngine *engine, Lookup *lookup, const Value &value); + static ReturnedValue lookupGetter(Lookup *lookup, ExecutionEngine *engine, const Value &object); static void initProto(ExecutionEngine *v4); }; diff --git a/src/qml/types/qqmllistmodel.cpp b/src/qml/types/qqmllistmodel.cpp index debf14df97..006825cc93 100644 --- a/src/qml/types/qqmllistmodel.cpp +++ b/src/qml/types/qqmllistmodel.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include #include @@ -1613,6 +1614,12 @@ ReturnedValue ModelObject::virtualGet(const Managed *m, PropertyKey id, const Va return that->engine()->fromVariant(value); } +ReturnedValue ModelObject::virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup) +{ + lookup->getter = Lookup::getterFallback; + return lookup->getter(lookup, engine, *object); +} + struct ModelObjectOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator { int roleNameIndex = 0; diff --git a/src/qml/types/qqmllistmodel_p_p.h b/src/qml/types/qqmllistmodel_p_p.h index ff52ee049f..2876c71de6 100644 --- a/src/qml/types/qqmllistmodel_p_p.h +++ b/src/qml/types/qqmllistmodel_p_p.h @@ -181,6 +181,8 @@ struct ModelObject : public QObjectWrapper protected: static bool virtualPut(Managed *m, PropertyKey id, const Value& value, Value *receiver); static ReturnedValue virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty); + static ReturnedValue virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup); + static ReturnedValue lookupGetter(Lookup *l, ExecutionEngine *engine, const Value &object); static OwnPropertyKeyIterator *virtualOwnPropertyKeys(const Object *m, Value *target); }; -- cgit v1.2.3 From 0b7e479235aec74f051af4d5ef95e74753b59c6d Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Fri, 22 Feb 2019 18:13:34 +0100 Subject: Create import directory from intercepted URL, not orignial one If the qmldir URL got intercepted, we should use the intercepted URL to get to the contents to be loaded. Fixes: QTBUG-73843 Change-Id: I51715575e767ed429a8237517f47196677409fe0 Reviewed-by: Simon Hausmann --- src/qml/qml/qqmlimport.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/qml') diff --git a/src/qml/qml/qqmlimport.cpp b/src/qml/qml/qqmlimport.cpp index e379d416fd..a31dbd1e08 100644 --- a/src/qml/qml/qqmlimport.cpp +++ b/src/qml/qml/qqmlimport.cpp @@ -1546,7 +1546,7 @@ bool QQmlImportsPrivate::addFileImport(const QString& uri, const QString &prefix QString localFileOrQrc = QQmlFile::urlToLocalFileOrQrc(qmldirUrl); Q_ASSERT(!localFileOrQrc.isEmpty()); - QString dir = QQmlFile::urlToLocalFileOrQrc(resolveLocalUrl(base, importUri)); + const QString dir = localFileOrQrc.left(localFileOrQrc.lastIndexOf(Slash) + 1); if (!typeLoader->directoryExists(dir)) { if (!isImplicitImport) { QQmlError error; -- cgit v1.2.3 From fdd988494aedba9f58b54e958fab91f566186ed1 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Thu, 14 Mar 2019 13:44:47 +0100 Subject: Doc: Clarify section about qmlRegisterInterface() There is very little you can do with things registered as interfaces. Change-Id: I5e4dcf8529c2d7c8012db3fa1dcfc23563cc2cba Fixes: QTBUG-74318 Reviewed-by: Simon Hausmann --- src/qml/doc/src/cppintegration/definetypes.qdoc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src/qml') diff --git a/src/qml/doc/src/cppintegration/definetypes.qdoc b/src/qml/doc/src/cppintegration/definetypes.qdoc index f6f630c749..41bc9fd140 100644 --- a/src/qml/doc/src/cppintegration/definetypes.qdoc +++ b/src/qml/doc/src/cppintegration/definetypes.qdoc @@ -142,9 +142,10 @@ types: \li qmlRegisterType() (with no parameters) registers a C++ type that is not instantiable and cannot be referred to from QML. This enables the engine to coerce any inherited types that are instantiable from QML. -\li qmlRegisterInterface() registers a Qt interface type with a specific QML -type name. The type is not instantiable from QML but can be referred to by its -type name. +\li qmlRegisterInterface() registers an existing Qt interface type. The type is +not instantiable from QML, and you cannot declare QML properties with it. Using +C++ properties of this type from QML will do the expected interface casts, +though. \li qmlRegisterUncreatableType() registers a named C++ type that is not instantiable but should be identifiable as a type to the QML type system. This is useful if a type's enums or attached properties should be accessible from QML -- cgit v1.2.3 From c9e6251cc8dfcf002f64b07e48dd68b7edd3f630 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Wed, 2 Jan 2019 16:09:56 +0100 Subject: Implement dummy QML lookups for "global" variables When resolving names in the context of QML bindings, we now direct runtime access to QQmlContextWrapper::resolveQmlPropertyLookupGetter. At the moment this does basically the same as Runtime::method_loadName, which we called earlier. However this now provides the opportunity to optimize lookups in the QML context in a central place. When performing a call on a scope or context object property, we also did not use a CallName() instruction - which would have gotten the thisObject wrong - but instead we use a dedicated CallScopeObjectProperty and CallContextObjectProperty instruction. These rely on identifying these properties at compile time, which goes away with lookups (and also doesn't work when using ahead-of-time compilation). Therefore the qml context property lookup is using a getPropertyAndBase style signature and Runtime::method_callQmlContextPropertyLookup uses that. For the tests to pass, some error expectations need adjusting. In particular the compile-time detection of write attempts to id objects is now delayed to the run-time. The old code path is still there and will be removed separately in the next commit (as it is massive). Task-number: QTBUG-69898 Change-Id: Iad1ff93d3758c4db984a7c2d003beee21ed2275c Reviewed-by: Ulf Hermann --- src/qml/compiler/qqmlirbuilder.cpp | 3 +- src/qml/compiler/qv4bytecodehandler.cpp | 6 ++++ src/qml/compiler/qv4codegen.cpp | 35 +++++++++++++------ src/qml/compiler/qv4codegen_p.h | 5 ++- src/qml/compiler/qv4compileddata.cpp | 2 ++ src/qml/compiler/qv4compileddata_p.h | 7 ++-- src/qml/compiler/qv4compiler.cpp | 12 ++++--- src/qml/compiler/qv4compiler_p.h | 2 +- src/qml/compiler/qv4compilercontext.cpp | 7 ++-- src/qml/compiler/qv4compilercontext_p.h | 1 + src/qml/compiler/qv4instr_moth.cpp | 8 +++++ src/qml/compiler/qv4instr_moth_p.h | 4 +++ src/qml/jit/qv4baselinejit.cpp | 22 ++++++++++++ src/qml/jit/qv4baselinejit_p.h | 2 ++ src/qml/jit/qv4jithelpers.cpp | 6 ++++ src/qml/jit/qv4jithelpers_p.h | 1 + src/qml/jsruntime/qv4engine.cpp | 8 +++++ src/qml/jsruntime/qv4engine_p.h | 1 + src/qml/jsruntime/qv4lookup_p.h | 1 + src/qml/jsruntime/qv4qmlcontext.cpp | 62 ++++++++++++++++++++++++++++----- src/qml/jsruntime/qv4qmlcontext_p.h | 4 +++ src/qml/jsruntime/qv4runtime.cpp | 49 ++++++++++++++++---------- src/qml/jsruntime/qv4runtimeapi_p.h | 1 + src/qml/jsruntime/qv4vme_moth.cpp | 13 +++++++ 24 files changed, 213 insertions(+), 49 deletions(-) (limited to 'src/qml') diff --git a/src/qml/compiler/qqmlirbuilder.cpp b/src/qml/compiler/qqmlirbuilder.cpp index 59b9968bf6..4b4d3c27e8 100644 --- a/src/qml/compiler/qqmlirbuilder.cpp +++ b/src/qml/compiler/qqmlirbuilder.cpp @@ -2228,7 +2228,8 @@ void JSCodeGen::beginFunctionBodyHook() QV4::Compiler::Codegen::Reference JSCodeGen::fallbackNameLookup(const QString &name) { #ifndef V4_BOOTSTRAP - if (_disableAcceleratedLookups) + // FIXME: Remove this function. + if (_disableAcceleratedLookups || true) return Reference(); // Implement QML lookup semantics in the current file context. diff --git a/src/qml/compiler/qv4bytecodehandler.cpp b/src/qml/compiler/qv4bytecodehandler.cpp index af86b70014..a34472010b 100644 --- a/src/qml/compiler/qv4bytecodehandler.cpp +++ b/src/qml/compiler/qv4bytecodehandler.cpp @@ -192,6 +192,9 @@ std::vector ByteCodeHandler::collectLabelsInBytecode(const char *code, uint COLLECTOR_BEGIN_INSTR(LoadGlobalLookup) COLLECTOR_END_INSTR(LoadGlobalLookup) + COLLECTOR_BEGIN_INSTR(LoadQmlContextPropertyLookup) + COLLECTOR_END_INSTR(LoadQmlContextPropertyLookup) + COLLECTOR_BEGIN_INSTR(StoreNameSloppy) COLLECTOR_END_INSTR(StoreNameSloppy) @@ -270,6 +273,9 @@ std::vector ByteCodeHandler::collectLabelsInBytecode(const char *code, uint COLLECTOR_BEGIN_INSTR(CallGlobalLookup) COLLECTOR_END_INSTR(CallGlobalLookup) + COLLECTOR_BEGIN_INSTR(CallQmlContextPropertyLookup) + COLLECTOR_END_INSTR(CallQmlContextPropertyLookup) + COLLECTOR_BEGIN_INSTR(CallScopeObjectProperty) COLLECTOR_END_INSTR(CallScopeObjectProperty) diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index 66c334d197..de87d6d48c 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -1982,11 +1982,19 @@ void Codegen::handleCall(Reference &base, Arguments calldata, int slotForFunctio call.argv = calldata.argv; bytecodeGenerator->addInstruction(call); } else if (!disable_lookups && useFastLookups && base.global) { - Instruction::CallGlobalLookup call; - call.index = registerGlobalGetterLookup(base.nameAsIndex()); - call.argc = calldata.argc; - call.argv = calldata.argv; - bytecodeGenerator->addInstruction(call); + if (base.qmlGlobal) { + Instruction::CallQmlContextPropertyLookup call; + call.index = registerQmlContextPropertyGetterLookup(base.nameAsIndex()); + call.argc = calldata.argc; + call.argv = calldata.argv; + bytecodeGenerator->addInstruction(call); + } else { + Instruction::CallGlobalLookup call; + call.index = registerGlobalGetterLookup(base.nameAsIndex()); + call.argc = calldata.argc; + call.argv = calldata.argv; + bytecodeGenerator->addInstruction(call); + } } else { Instruction::CallName call; call.name = base.nameAsIndex(); @@ -2361,8 +2369,9 @@ Codegen::Reference Codegen::referenceForName(const QString &name, bool isLhs, co return fallback; Reference r = Reference::fromName(this, name); - r.global = useFastLookups && (resolved.type == Context::ResolvedName::Global); - if (!r.global && canAccelerateGlobalLookups() && m_globalNames.contains(name)) + r.global = useFastLookups && (resolved.type == Context::ResolvedName::Global || resolved.type == Context::ResolvedName::QmlGlobal); + r.qmlGlobal = resolved.type == Context::ResolvedName::QmlGlobal; + if (!r.global && !r.qmlGlobal && m_globalNames.contains(name)) r.global = true; return r; } @@ -4385,9 +4394,15 @@ QT_WARNING_POP } } if (!disable_lookups && global) { - Instruction::LoadGlobalLookup load; - load.index = codegen->registerGlobalGetterLookup(nameAsIndex()); - codegen->bytecodeGenerator->addInstruction(load); + if (qmlGlobal) { + Instruction::LoadQmlContextPropertyLookup load; + load.index = codegen->registerQmlContextPropertyGetterLookup(nameAsIndex()); + codegen->bytecodeGenerator->addInstruction(load); + } else { + Instruction::LoadGlobalLookup load; + load.index = codegen->registerGlobalGetterLookup(nameAsIndex()); + codegen->bytecodeGenerator->addInstruction(load); + } } else { Instruction::LoadName load; load.name = nameAsIndex(); diff --git a/src/qml/compiler/qv4codegen_p.h b/src/qml/compiler/qv4codegen_p.h index 0b25d9c53d..b1cc4c090a 100644 --- a/src/qml/compiler/qv4codegen_p.h +++ b/src/qml/compiler/qv4codegen_p.h @@ -207,7 +207,8 @@ public: subscriptRequiresTDZCheck(false), stackSlotIsLocalOrArgument(false), isVolatile(false), - global(false) + global(false), + qmlGlobal(false) {} Reference(const Reference &) = default; @@ -421,6 +422,7 @@ public: quint32 stackSlotIsLocalOrArgument:1; quint32 isVolatile:1; quint32 global:1; + quint32 qmlGlobal:1; private: void storeAccumulator() const; @@ -553,6 +555,7 @@ public: int registerGetterLookup(int nameIndex) { return jsUnitGenerator->registerGetterLookup(nameIndex); } int registerSetterLookup(int nameIndex) { return jsUnitGenerator->registerSetterLookup(nameIndex); } int registerGlobalGetterLookup(int nameIndex) { return jsUnitGenerator->registerGlobalGetterLookup(nameIndex); } + int registerQmlContextPropertyGetterLookup(int nameIndex) { return jsUnitGenerator->registerQmlContextPropertyGetterLookup(nameIndex); } // Returns index in _module->functions virtual int defineFunction(const QString &name, AST::Node *ast, diff --git a/src/qml/compiler/qv4compileddata.cpp b/src/qml/compiler/qv4compileddata.cpp index e428340f62..6701443971 100644 --- a/src/qml/compiler/qv4compileddata.cpp +++ b/src/qml/compiler/qv4compileddata.cpp @@ -170,6 +170,8 @@ QV4::Function *CompilationUnit::linkToEngine(ExecutionEngine *engine) l->setter = QV4::Lookup::setterGeneric; else if (type == CompiledData::Lookup::Type_GlobalGetter) l->globalGetter = QV4::Lookup::globalGetterGeneric; + else if (type == CompiledData::Lookup::Type_QmlContextPropertyGetter) + l->qmlContextPropertyGetter = QQmlContextWrapper::resolveQmlContextPropertyLookupGetter; l->nameIndex = compiledLookups[i].nameIndex; } } diff --git a/src/qml/compiler/qv4compileddata_p.h b/src/qml/compiler/qv4compileddata_p.h index c4821daa86..d63b1fd2b9 100644 --- a/src/qml/compiler/qv4compileddata_p.h +++ b/src/qml/compiler/qv4compileddata_p.h @@ -160,9 +160,10 @@ static_assert(sizeof(RegExp) == 4, "RegExp structure needs to have the expected struct Lookup { enum Type : unsigned int { - Type_Getter = 0x0, - Type_Setter = 0x1, - Type_GlobalGetter = 2 + Type_Getter = 0, + Type_Setter = 1, + Type_GlobalGetter = 2, + Type_QmlContextPropertyGetter = 3 }; union { diff --git a/src/qml/compiler/qv4compiler.cpp b/src/qml/compiler/qv4compiler.cpp index 3076c6b526..a3c9347c67 100644 --- a/src/qml/compiler/qv4compiler.cpp +++ b/src/qml/compiler/qv4compiler.cpp @@ -154,15 +154,19 @@ int QV4::Compiler::JSUnitGenerator::registerSetterLookup(int nameIndex) return lookups.size() - 1; } -int QV4::Compiler::JSUnitGenerator::registerGlobalGetterLookup(const QString &name) +int QV4::Compiler::JSUnitGenerator::registerGlobalGetterLookup(int nameIndex) { - return registerGlobalGetterLookup(registerString(name)); + CompiledData::Lookup l; + l.type_and_flags = CompiledData::Lookup::Type_GlobalGetter; + l.nameIndex = nameIndex; + lookups << l; + return lookups.size() - 1; } -int QV4::Compiler::JSUnitGenerator::registerGlobalGetterLookup(int nameIndex) +int QV4::Compiler::JSUnitGenerator::registerQmlContextPropertyGetterLookup(int nameIndex) { CompiledData::Lookup l; - l.type_and_flags = CompiledData::Lookup::Type_GlobalGetter; + l.type_and_flags = CompiledData::Lookup::Type_QmlContextPropertyGetter; l.nameIndex = nameIndex; lookups << l; return lookups.size() - 1; diff --git a/src/qml/compiler/qv4compiler_p.h b/src/qml/compiler/qv4compiler_p.h index 2f5889ab53..49e334bb81 100644 --- a/src/qml/compiler/qv4compiler_p.h +++ b/src/qml/compiler/qv4compiler_p.h @@ -118,8 +118,8 @@ struct Q_QML_PRIVATE_EXPORT JSUnitGenerator { int registerGetterLookup(int nameIndex); int registerSetterLookup(const QString &name); int registerSetterLookup(int nameIndex); - int registerGlobalGetterLookup(const QString &name); int registerGlobalGetterLookup(int nameIndex); + int registerQmlContextPropertyGetterLookup(int nameIndex); int registerRegExp(QQmlJS::AST::RegExpLiteral *regexp); diff --git a/src/qml/compiler/qv4compilercontext.cpp b/src/qml/compiler/qv4compilercontext.cpp index 5772bff7bf..84c5d67e8d 100644 --- a/src/qml/compiler/qv4compilercontext.cpp +++ b/src/qml/compiler/qv4compilercontext.cpp @@ -187,10 +187,13 @@ Context::ResolvedName Context::resolveName(const QString &name, const QQmlJS::AS } // ### can we relax the restrictions here? - if (c->contextType == ContextType::Eval || c->contextType == ContextType::Binding) + if (c->contextType == ContextType::Eval) return result; - result.type = ResolvedName::Global; + if (c->contextType == ContextType::Binding) + result.type = ResolvedName::QmlGlobal; + else + result.type = ResolvedName::Global; return result; } diff --git a/src/qml/compiler/qv4compilercontext_p.h b/src/qml/compiler/qv4compilercontext_p.h index 328715da07..c9e54c0d1b 100644 --- a/src/qml/compiler/qv4compilercontext_p.h +++ b/src/qml/compiler/qv4compilercontext_p.h @@ -336,6 +336,7 @@ struct Context { struct ResolvedName { enum Type { Unresolved, + QmlGlobal, Global, Local, Stack, diff --git a/src/qml/compiler/qv4instr_moth.cpp b/src/qml/compiler/qv4instr_moth.cpp index 83aca3a02c..d2e95b83c4 100644 --- a/src/qml/compiler/qv4instr_moth.cpp +++ b/src/qml/compiler/qv4instr_moth.cpp @@ -286,6 +286,10 @@ void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*st d << index; MOTH_END_INSTR(LoadGlobalLookup) + MOTH_BEGIN_INSTR(LoadQmlContextPropertyLookup) + d << index; + MOTH_END_INSTR(LoadQmlContextPropertyLookup) + MOTH_BEGIN_INSTR(StoreNameSloppy) d << name; MOTH_END_INSTR(StoreNameSloppy) @@ -388,6 +392,10 @@ void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*st d << index << dumpArguments(argc, argv, nFormals); MOTH_END_INSTR(CallGlobalLookup) + MOTH_BEGIN_INSTR(CallQmlContextPropertyLookup) + d << index << dumpArguments(argc, argv, nFormals); + MOTH_END_INSTR(CallQmlContextPropertyLookup) + MOTH_BEGIN_INSTR(CallScopeObjectProperty) d << dumpRegister(base, nFormals) << "." << name << dumpArguments(argc, argv, nFormals); MOTH_END_INSTR(CallScopeObjectProperty) diff --git a/src/qml/compiler/qv4instr_moth_p.h b/src/qml/compiler/qv4instr_moth_p.h index 2ca8f692b8..1c77e6050c 100644 --- a/src/qml/compiler/qv4instr_moth_p.h +++ b/src/qml/compiler/qv4instr_moth_p.h @@ -86,6 +86,7 @@ QT_BEGIN_NAMESPACE #define INSTR_LoadClosure(op) INSTRUCTION(op, LoadClosure, 1, value) #define INSTR_LoadName(op) INSTRUCTION(op, LoadName, 1, name) #define INSTR_LoadGlobalLookup(op) INSTRUCTION(op, LoadGlobalLookup, 1, index) +#define INSTR_LoadQmlContextPropertyLookup(op) INSTRUCTION(op, LoadQmlContextPropertyLookup, 1, index) #define INSTR_StoreNameSloppy(op) INSTRUCTION(op, StoreNameSloppy, 1, name) #define INSTR_StoreNameStrict(op) INSTRUCTION(op, StoreNameStrict, 1, name) #define INSTR_LoadProperty(op) INSTRUCTION(op, LoadProperty, 1, name) @@ -113,6 +114,7 @@ QT_BEGIN_NAMESPACE #define INSTR_CallName(op) INSTRUCTION(op, CallName, 3, name, argc, argv) #define INSTR_CallPossiblyDirectEval(op) INSTRUCTION(op, CallPossiblyDirectEval, 2, argc, argv) #define INSTR_CallGlobalLookup(op) INSTRUCTION(op, CallGlobalLookup, 3, index, argc, argv) +#define INSTR_CallQmlContextPropertyLookup(op) INSTRUCTION(op, CallQmlContextPropertyLookup, 3, index, argc, argv) #define INSTR_CallScopeObjectProperty(op) INSTRUCTION(op, CallScopeObjectProperty, 4, name, base, argc, argv) #define INSTR_CallContextObjectProperty(op) INSTRUCTION(op, CallContextObjectProperty, 4, name, base, argc, argv) #define INSTR_CallWithSpread(op) INSTRUCTION(op, CallWithSpread, 4, func, thisObject, argc, argv) @@ -228,6 +230,7 @@ QT_BEGIN_NAMESPACE F(LoadClosure) \ F(LoadName) \ F(LoadGlobalLookup) \ + F(LoadQmlContextPropertyLookup) \ F(StoreNameSloppy) \ F(StoreNameStrict) \ F(LoadElement) \ @@ -296,6 +299,7 @@ QT_BEGIN_NAMESPACE F(CallName) \ F(CallPossiblyDirectEval) \ F(CallGlobalLookup) \ + F(CallQmlContextPropertyLookup) \ F(CallScopeObjectProperty) \ F(CallContextObjectProperty) \ F(CallWithSpread) \ diff --git a/src/qml/jit/qv4baselinejit.cpp b/src/qml/jit/qv4baselinejit.cpp index 098bbfc6c6..47cef3b3bd 100644 --- a/src/qml/jit/qv4baselinejit.cpp +++ b/src/qml/jit/qv4baselinejit.cpp @@ -213,6 +213,16 @@ void BaselineJIT::generate_LoadGlobalLookup(int index) as->checkException(); } +void BaselineJIT::generate_LoadQmlContextPropertyLookup(int index) +{ + as->prepareCallWithArgCount(3); + as->passInt32AsArg(index, 2); + as->passEngineAsArg(1); + as->passFunctionAsArg(0); + BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::loadQmlContextPropertyLookup, CallResultDestination::InAccumulator); + as->checkException(); +} + void BaselineJIT::generate_StoreNameSloppy(int name) { STORE_IP(); @@ -514,6 +524,18 @@ void BaselineJIT::generate_CallGlobalLookup(int index, int argc, int argv) as->checkException(); } +void BaselineJIT::generate_CallQmlContextPropertyLookup(int index, int argc, int argv) +{ + STORE_IP(); + as->prepareCallWithArgCount(4); + as->passInt32AsArg(argc, 3); + as->passJSSlotAsArg(argv, 2); + as->passInt32AsArg(index, 1); + as->passEngineAsArg(0); + BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_callQmlContextPropertyLookup, CallResultDestination::InAccumulator); + as->checkException(); +} + void BaselineJIT::generate_CallScopeObjectProperty(int propIdx, int base, int argc, int argv) { STORE_IP(); diff --git a/src/qml/jit/qv4baselinejit_p.h b/src/qml/jit/qv4baselinejit_p.h index 98d23f4517..4db1eb1806 100644 --- a/src/qml/jit/qv4baselinejit_p.h +++ b/src/qml/jit/qv4baselinejit_p.h @@ -97,6 +97,7 @@ public: void generate_LoadClosure(int value) override; void generate_LoadName(int name) override; void generate_LoadGlobalLookup(int index) override; + void generate_LoadQmlContextPropertyLookup(int index) override; void generate_StoreNameSloppy(int name) override; void generate_StoreNameStrict(int name) override; void generate_LoadElement(int base) override; @@ -128,6 +129,7 @@ public: void generate_CallName(int name, int argc, int argv) override; void generate_CallPossiblyDirectEval(int argc, int argv) override; void generate_CallGlobalLookup(int index, int argc, int argv) override; + void generate_CallQmlContextPropertyLookup(int index, int argc, int argv) override; void generate_CallScopeObjectProperty(int propIdx, int base, int argc, int argv) override; void generate_CallContextObjectProperty(int propIdx, int base, int argc, int argv) override; void generate_CallWithSpread(int func, int thisObject, int argc, int argv) override; diff --git a/src/qml/jit/qv4jithelpers.cpp b/src/qml/jit/qv4jithelpers.cpp index f43f37ad70..674fd8c8c8 100644 --- a/src/qml/jit/qv4jithelpers.cpp +++ b/src/qml/jit/qv4jithelpers.cpp @@ -70,6 +70,12 @@ ReturnedValue loadGlobalLookup(Function *f, ExecutionEngine *engine, int index) return l->globalGetter(l, engine); } +ReturnedValue loadQmlContextPropertyLookup(Function *f, ExecutionEngine *engine, int index) +{ + Lookup *l = f->compilationUnit->runtimeLookups + index; + return l->qmlContextPropertyGetter(l, engine, nullptr); +} + ReturnedValue toObject(ExecutionEngine *engine, const Value &obj) { if (obj.isObject()) diff --git a/src/qml/jit/qv4jithelpers_p.h b/src/qml/jit/qv4jithelpers_p.h index bd5f65034d..d9abfc071e 100644 --- a/src/qml/jit/qv4jithelpers_p.h +++ b/src/qml/jit/qv4jithelpers_p.h @@ -66,6 +66,7 @@ namespace Helpers { void convertThisToObject(ExecutionEngine *engine, Value *t); ReturnedValue loadGlobalLookup(Function *f, ExecutionEngine *engine, int index); +ReturnedValue loadQmlContextPropertyLookup(Function *f, ExecutionEngine *engine, int index); ReturnedValue toObject(ExecutionEngine *engine, const Value &obj); ReturnedValue exp(const Value &base, const Value &exp); ReturnedValue getLookup(Function *f, ExecutionEngine *engine, const Value &base, int index); diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index f00578aa70..1d0c7c13fa 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -1169,6 +1169,14 @@ ReturnedValue ExecutionEngine::throwTypeError(const QString &message) return throwError(error); } +ReturnedValue ExecutionEngine::throwReferenceError(const QString &name) +{ + Scope scope(this); + QString msg = name + QLatin1String(" is not defined"); + ScopedObject error(scope, newReferenceErrorObject(msg)); + return throwError(error); +} + ReturnedValue ExecutionEngine::throwReferenceError(const Value &value) { Scope scope(this); diff --git a/src/qml/jsruntime/qv4engine_p.h b/src/qml/jsruntime/qv4engine_p.h index 64c7e2f32b..86367c0ece 100644 --- a/src/qml/jsruntime/qv4engine_p.h +++ b/src/qml/jsruntime/qv4engine_p.h @@ -569,6 +569,7 @@ public: ReturnedValue throwTypeError(); ReturnedValue throwTypeError(const QString &message); ReturnedValue throwReferenceError(const Value &value); + ReturnedValue throwReferenceError(const QString &name); ReturnedValue throwReferenceError(const QString &value, const QString &fileName, int lineNumber, int column); ReturnedValue throwRangeError(const Value &value); ReturnedValue throwRangeError(const QString &message); diff --git a/src/qml/jsruntime/qv4lookup_p.h b/src/qml/jsruntime/qv4lookup_p.h index b0d5706dea..e4d9ad8328 100644 --- a/src/qml/jsruntime/qv4lookup_p.h +++ b/src/qml/jsruntime/qv4lookup_p.h @@ -68,6 +68,7 @@ struct Lookup { union { ReturnedValue (*getter)(Lookup *l, ExecutionEngine *engine, const Value &object); ReturnedValue (*globalGetter)(Lookup *l, ExecutionEngine *engine); + ReturnedValue (*qmlContextPropertyGetter)(Lookup *l, ExecutionEngine *engine, Value *thisObject); bool (*setter)(Lookup *l, ExecutionEngine *engine, Value &object, const Value &v); }; union { diff --git a/src/qml/jsruntime/qv4qmlcontext.cpp b/src/qml/jsruntime/qv4qmlcontext.cpp index 88b0822f42..ae458c604a 100644 --- a/src/qml/jsruntime/qv4qmlcontext.cpp +++ b/src/qml/jsruntime/qv4qmlcontext.cpp @@ -55,6 +55,8 @@ #include #include #include +#include +#include QT_BEGIN_NAMESPACE @@ -77,14 +79,11 @@ void Heap::QQmlContextWrapper::destroy() Object::destroy(); } -ReturnedValue QQmlContextWrapper::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) +ReturnedValue QQmlContextWrapper::getPropertyAndBase(const QQmlContextWrapper *resource, PropertyKey id, const Value *receiver, bool *hasProperty, Value *base) { - Q_ASSERT(m->as()); - if (!id.isString()) - return Object::virtualGet(m, id, receiver, hasProperty); + return Object::virtualGet(resource, id, receiver, hasProperty); - const QQmlContextWrapper *resource = static_cast(m); QV4::ExecutionEngine *v4 = resource->engine(); QV4::Scope scope(v4); @@ -100,11 +99,11 @@ ReturnedValue QQmlContextWrapper::virtualGet(const Managed *m, PropertyKey id, c } } - return Object::virtualGet(m, id, receiver, hasProperty); + return Object::virtualGet(resource, id, receiver, hasProperty); } bool hasProp = false; - ScopedValue result(scope, Object::virtualGet(m, id, receiver, &hasProp)); + ScopedValue result(scope, Object::virtualGet(resource, id, receiver, &hasProp)); if (hasProp) { if (hasProperty) *hasProperty = hasProp; @@ -234,6 +233,8 @@ ReturnedValue QQmlContextWrapper::virtualGet(const Managed *m, PropertyKey id, c if (hasProp) { if (hasProperty) *hasProperty = true; + if (base) + *base = QV4::QObjectWrapper::wrap(v4, scopeObject); return result->asReturnedValue(); } } @@ -247,6 +248,8 @@ ReturnedValue QQmlContextWrapper::virtualGet(const Managed *m, PropertyKey id, c if (hasProp) { if (hasProperty) *hasProperty = true; + if (base) + *base = QV4::QObjectWrapper::wrap(v4, context->contextObject); return result->asReturnedValue(); } } @@ -264,6 +267,13 @@ ReturnedValue QQmlContextWrapper::virtualGet(const Managed *m, PropertyKey id, c return Encode::undefined(); } +ReturnedValue QQmlContextWrapper::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) +{ + Q_ASSERT(m->as()); + const QQmlContextWrapper *This = static_cast(m); + return getPropertyAndBase(This, id, receiver, hasProperty, /*base*/nullptr); +} + bool QQmlContextWrapper::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver) { Q_ASSERT(m->as()); @@ -298,8 +308,16 @@ bool QQmlContextWrapper::virtualPut(Managed *m, PropertyKey id, const Value &val while (context) { const QV4::IdentifierHash &properties = context->propertyNames(); // Search context properties - if (properties.count() && properties.value(name) != -1) - return false; + if (properties.count()) { + const int propertyIndex = properties.value(name); + if (propertyIndex != -1) { + if (propertyIndex < context->idValueCount) { + v4->throwError(QLatin1String("left-hand side of assignment operator is not an lvalue")); + return false; + } + return false; + } + } // Search scope object if (scopeObject && @@ -323,6 +341,32 @@ bool QQmlContextWrapper::virtualPut(Managed *m, PropertyKey id, const Value &val return false; } +ReturnedValue QQmlContextWrapper::resolveQmlContextPropertyLookupGetter(Lookup *l, ExecutionEngine *engine, Value *base) +{ + Scope scope(engine); + PropertyKey name =engine->identifierTable->asPropertyKey(engine->currentStackFrame->v4Function->compilationUnit-> + runtimeStrings[l->nameIndex]); + + // Special hack for bounded signal expressions, where the parameters of signals are injected + // into the handler expression through the locals of the call context. So for onClicked: { ... } + // the parameters of the clicked signal are injected and we must allow for them to be found here + // before any other property from the QML context. + ExecutionContext &ctx = static_cast(engine->currentStackFrame->jsFrame->context); + if (ctx.d()->type == Heap::ExecutionContext::Type_CallContext) { + uint index = ctx.d()->internalClass->indexOfValueOrGetter(name); + if (index < UINT_MAX) + return static_cast(ctx.d())->locals[index].asReturnedValue(); + } + + Scoped qmlContext(scope, engine->qmlContext()->qml()); + bool hasProperty = false; + ScopedValue result(scope, QQmlContextWrapper::getPropertyAndBase(qmlContext, name, /*receiver*/nullptr, + &hasProperty, base)); + if (!hasProperty) + return engine->throwReferenceError(name.toQString()); + return result->asReturnedValue(); +} + void Heap::QmlContext::init(QV4::ExecutionContext *outerContext, QV4::QQmlContextWrapper *qml) { Heap::ExecutionContext::init(Heap::ExecutionContext::Type_QmlContext); diff --git a/src/qml/jsruntime/qv4qmlcontext_p.h b/src/qml/jsruntime/qv4qmlcontext_p.h index dd6de3323d..f704280bf4 100644 --- a/src/qml/jsruntime/qv4qmlcontext_p.h +++ b/src/qml/jsruntime/qv4qmlcontext_p.h @@ -99,8 +99,12 @@ struct Q_QML_EXPORT QQmlContextWrapper : Object inline QObject *getScopeObject() const { return d()->scopeObject; } inline QQmlContextData *getContext() const { return *d()->context; } + static ReturnedValue getPropertyAndBase(const QQmlContextWrapper *resource, PropertyKey id, const Value *receiver, + bool *hasProperty, Value *base); static ReturnedValue virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty); static bool virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver); + + static ReturnedValue resolveQmlContextPropertyLookupGetter(Lookup *l, ExecutionEngine *engine, Value *base); }; struct Q_QML_EXPORT QmlContext : public ExecutionContext diff --git a/src/qml/jsruntime/qv4runtime.cpp b/src/qml/jsruntime/qv4runtime.cpp index 53dd3a66dd..7163a51af1 100644 --- a/src/qml/jsruntime/qv4runtime.cpp +++ b/src/qml/jsruntime/qv4runtime.cpp @@ -1301,18 +1301,42 @@ uint Runtime::method_compareIn(ExecutionEngine *engine, const Value &left, const return v->booleanValue(); } +static ReturnedValue throwPropertyIsNotAFunctionTypeError(ExecutionEngine *engine, Value *thisObject, const QString &propertyName) +{ + QString objectAsString = QStringLiteral("[null]"); + if (!thisObject->isUndefined()) + objectAsString = thisObject->toQStringNoThrow(); + QString msg = QStringLiteral("Property '%1' of object %2 is not a function") + .arg(propertyName, objectAsString); + return engine->throwTypeError(msg); +} ReturnedValue Runtime::method_callGlobalLookup(ExecutionEngine *engine, uint index, Value *argv, int argc) { + Scope scope(engine); Lookup *l = engine->currentStackFrame->v4Function->compilationUnit->runtimeLookups + index; Value function = Value::fromReturnedValue(l->globalGetter(l, engine)); + Value thisObject = Value::undefinedValue(); if (!function.isFunctionObject()) - return engine->throwTypeError(); + return throwPropertyIsNotAFunctionTypeError(engine, &thisObject, + engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[l->nameIndex]->toQString()); - Value thisObject = Value::undefinedValue(); return static_cast(function).call(&thisObject, argv, argc); } +ReturnedValue Runtime::method_callQmlContextPropertyLookup(ExecutionEngine *engine, uint index, Value *argv, int argc) +{ + Scope scope(engine); + ScopedValue thisObject(scope); + Lookup *l = engine->currentStackFrame->v4Function->compilationUnit->runtimeLookups + index; + Value function = Value::fromReturnedValue(l->qmlContextPropertyGetter(l, engine, thisObject)); + if (!function.isFunctionObject()) + return throwPropertyIsNotAFunctionTypeError(engine, thisObject, + engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[l->nameIndex]->toQString()); + + return static_cast(function).call(thisObject, argv, argc); +} + ReturnedValue Runtime::method_callPossiblyDirectEval(ExecutionEngine *engine, Value *argv, int argc) { Scope scope(engine); @@ -1323,13 +1347,8 @@ ReturnedValue Runtime::method_callPossiblyDirectEval(ExecutionEngine *engine, Va if (engine->hasException) return Encode::undefined(); - if (!function) { - QString objectAsString = QStringLiteral("[null]"); - if (!thisObject->isUndefined()) - objectAsString = thisObject->toQStringNoThrow(); - QString msg = QStringLiteral("Property 'eval' of object %2 is not a function").arg(objectAsString); - return engine->throwTypeError(msg); - } + if (!function) + return throwPropertyIsNotAFunctionTypeError(engine, thisObject, QLatin1String("eval")); if (function->d() == engine->evalFunction()->d()) return static_cast(function.getPointer())->evalCall(thisObject, argv, argc, true); @@ -1348,15 +1367,9 @@ ReturnedValue Runtime::method_callName(ExecutionEngine *engine, int nameIndex, V if (engine->hasException) return Encode::undefined(); - if (!f) { - QString objectAsString = QStringLiteral("[null]"); - if (!thisObject->isUndefined()) - objectAsString = thisObject->toQStringNoThrow(); - QString msg = QStringLiteral("Property '%1' of object %2 is not a function") - .arg(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]->toQString(), - objectAsString); - return engine->throwTypeError(msg); - } + if (!f) + return throwPropertyIsNotAFunctionTypeError(engine, thisObject, + engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]->toQString()); return f->call(thisObject, argv, argc); } diff --git a/src/qml/jsruntime/qv4runtimeapi_p.h b/src/qml/jsruntime/qv4runtimeapi_p.h index 06e8a8a960..aa4ac27621 100644 --- a/src/qml/jsruntime/qv4runtimeapi_p.h +++ b/src/qml/jsruntime/qv4runtimeapi_p.h @@ -93,6 +93,7 @@ struct ExceptionCheck { #define FOR_EACH_RUNTIME_METHOD(F) \ /* call */ \ F(ReturnedValue, callGlobalLookup, (ExecutionEngine *engine, uint index, Value *argv, int argc)) \ + F(ReturnedValue, callQmlContextPropertyLookup, (ExecutionEngine *engine, uint index, Value *argv, int argc)) \ F(ReturnedValue, callName, (ExecutionEngine *engine, int nameIndex, Value *argv, int argc)) \ F(ReturnedValue, callProperty, (ExecutionEngine *engine, Value *base, int nameIndex, Value *argv, int argc)) \ F(ReturnedValue, callPropertyLookup, (ExecutionEngine *engine, Value *base, uint index, Value *argv, int argc)) \ diff --git a/src/qml/jsruntime/qv4vme_moth.cpp b/src/qml/jsruntime/qv4vme_moth.cpp index 000e1ab9d5..2d9337b03e 100644 --- a/src/qml/jsruntime/qv4vme_moth.cpp +++ b/src/qml/jsruntime/qv4vme_moth.cpp @@ -556,6 +556,13 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, CHECK_EXCEPTION; MOTH_END_INSTR(LoadGlobalLookup) + MOTH_BEGIN_INSTR(LoadQmlContextPropertyLookup) + STORE_IP(); + QV4::Lookup *l = function->compilationUnit->runtimeLookups + index; + acc = l->qmlContextPropertyGetter(l, engine, nullptr); + CHECK_EXCEPTION; + MOTH_END_INSTR(LoadQmlContextPropertyLookup) + MOTH_BEGIN_INSTR(StoreNameStrict) STORE_IP(); STORE_ACC(); @@ -781,6 +788,12 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, CHECK_EXCEPTION; MOTH_END_INSTR(CallGlobalLookup) + MOTH_BEGIN_INSTR(CallQmlContextPropertyLookup) + STORE_IP(); + acc = Runtime::method_callQmlContextPropertyLookup(engine, index, stack + argv, argc); + CHECK_EXCEPTION; + MOTH_END_INSTR(CallQmlContextPropertyLookup) + MOTH_BEGIN_INSTR(CallScopeObjectProperty) STORE_IP(); acc = Runtime::method_callQmlScopeObjectProperty(engine, stack + base, name, stack + argv, argc); -- cgit v1.2.3 From 5cfccf30898aed5ca96c0f8779b0f8a1117118b7 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Thu, 3 Jan 2019 15:44:34 +0100 Subject: Remove dead compile time QML context/scope property and id object code After enabling lookups in QML files, we can remove all the code that tries to deal with (type) compile time detection of access to id objects and properties of the scope/context object. This also allows removing quite a bit of run-time code paths and even byte code instructions. Task-number: QTBUG-69898 Change-Id: I7b26d7983393594a3ef56466d3e633f1822b76f4 Reviewed-by: Ulf Hermann --- src/qml/compiler/qqmlirbuilder.cpp | 410 +------------------------------ src/qml/compiler/qqmlirbuilder_p.h | 34 +-- src/qml/compiler/qqmltypecompiler.cpp | 33 +-- src/qml/compiler/qv4bytecodehandler.cpp | 27 -- src/qml/compiler/qv4codegen.cpp | 71 +----- src/qml/compiler/qv4codegen_p.h | 58 +---- src/qml/compiler/qv4compileddata_p.h | 27 +- src/qml/compiler/qv4compiler.cpp | 45 +--- src/qml/compiler/qv4compilercontext_p.h | 5 - src/qml/compiler/qv4instr_moth.cpp | 36 --- src/qml/compiler/qv4instr_moth_p.h | 16 -- src/qml/jit/qv4baselinejit.cpp | 103 -------- src/qml/jit/qv4baselinejit_p.h | 13 - src/qml/jsruntime/qv4function.cpp | 1 - src/qml/jsruntime/qv4function_p.h | 1 - src/qml/jsruntime/qv4qobjectwrapper.cpp | 28 +-- src/qml/jsruntime/qv4qobjectwrapper_p.h | 3 +- src/qml/jsruntime/qv4runtime.cpp | 86 ------- src/qml/jsruntime/qv4runtimeapi_p.h | 14 +- src/qml/jsruntime/qv4vme_moth.cpp | 51 ---- src/qml/qml/qqmlbinding.cpp | 32 ++- src/qml/qml/qqmljavascriptexpression.cpp | 82 +------ src/qml/qml/qqmljavascriptexpression_p.h | 21 +- src/qml/types/qqmllistmodel.cpp | 3 +- 24 files changed, 41 insertions(+), 1159 deletions(-) (limited to 'src/qml') diff --git a/src/qml/compiler/qqmlirbuilder.cpp b/src/qml/compiler/qqmlirbuilder.cpp index 4b4d3c27e8..ab43ea350b 100644 --- a/src/qml/compiler/qqmlirbuilder.cpp +++ b/src/qml/compiler/qqmlirbuilder.cpp @@ -974,7 +974,6 @@ bool IRBuilder::visit(QQmlJS::AST::UiSourceElement *node) foe->node = funDecl; foe->parentNode = funDecl; foe->nameIndex = registerString(funDecl->name.toString()); - foe->disableAcceleratedLookups = false; const int index = _object->functionsAndExpressions->append(foe); Function *f = New(); @@ -1095,7 +1094,6 @@ void IRBuilder::setBindingValue(QV4::CompiledData::Binding *binding, QQmlJS::AST expr->parentNode = parentNode; expr->nameIndex = registerString(QLatin1String("expression for ") + stringAt(binding->propertyNameIndex)); - expr->disableAcceleratedLookups = false; const int index = bindingsTarget()->functionsAndExpressions->append(expr); binding->value.compiledScriptIndex = index; // We don't need to store the binding script as string, except for script strings @@ -1822,19 +1820,13 @@ char *QmlUnitGenerator::writeBindings(char *bindingPtr, const Object *o, Binding JSCodeGen::JSCodeGen(const QString &sourceCode, QV4::Compiler::JSUnitGenerator *jsUnitGenerator, QV4::Compiler::Module *jsModule, QQmlJS::Engine *jsEngine, - QQmlJS::AST::UiProgram *qmlRoot, QQmlTypeNameCache *imports, + QQmlJS::AST::UiProgram *qmlRoot, const QV4::Compiler::StringTableGenerator *stringPool, const QSet &globalNames) : QV4::Compiler::Codegen(jsUnitGenerator, /*strict mode*/false) , sourceCode(sourceCode) , jsEngine(jsEngine) , qmlRoot(qmlRoot) - , imports(imports) , stringPool(stringPool) - , _disableAcceleratedLookups(false) - , _contextObject(nullptr) - , _scopeObject(nullptr) - , _qmlContextSlot(-1) - , _importedScriptsSlot(-1) { m_globalNames = globalNames; @@ -1842,18 +1834,6 @@ JSCodeGen::JSCodeGen(const QString &sourceCode, QV4::Compiler::JSUnitGenerator * _fileNameIsUrl = true; } -void JSCodeGen::beginContextScope(const JSCodeGen::ObjectIdMapping &objectIds, QQmlPropertyCache *contextObject) -{ - _idObjects = objectIds; - _contextObject = contextObject; - _scopeObject = nullptr; -} - -void JSCodeGen::beginObjectScope(QQmlPropertyCache *scopeObject) -{ - _scopeObject = scopeObject; -} - QVector JSCodeGen::generateJSCodeForFunctionsAndBindings(const QList &functions) { auto qmlName = [&](const CompiledFunctionOrExpression &c) { @@ -1918,7 +1898,6 @@ QVector JSCodeGen::generateJSCodeForFunctionsAndBindings(const QListfinish(); } - _disableAcceleratedLookups = qmlFunction.disableAcceleratedLookups; int idx = defineFunction(name, function ? function : qmlFunction.parentNode, function ? function->formals : nullptr, body); @@ -1928,392 +1907,6 @@ QVector JSCodeGen::generateJSCodeForFunctionsAndBindings(const QListproperty(name, /*object*/nullptr, /*context*/nullptr); - - if (pd && !cache->isAllowedInRevision(pd)) - return nullptr; - - return pd; -} - -enum MetaObjectResolverFlags { - AllPropertiesAreFinal = 0x1, - LookupsIncludeEnums = 0x2, - LookupsExcludeProperties = 0x4, - ResolveTypeInformationOnly = 0x8 -}; - -#if 0 -static void initMetaObjectResolver(QV4::IR::MemberExpressionResolver *resolver, QQmlPropertyCache *metaObject); - -static void initScopedEnumResolver(QV4::IR::MemberExpressionResolver *resolver, const QQmlType &qmlType, int index); - -static QV4::IR::DiscoveredType resolveQmlType(QQmlEnginePrivate *qmlEngine, - const QV4::IR::MemberExpressionResolver *resolver, - QV4::IR::Member *member) -{ - QV4::IR::Type result = QV4::IR::VarType; - - QQmlType type = resolver->qmlType; - - if (member->name->constData()->isUpper()) { - bool ok = false; - int value = type.enumValue(qmlEngine, *member->name, &ok); - if (ok) { - member->setEnumValue(value); - return QV4::IR::SInt32Type; - } else { - int index = type.scopedEnumIndex(qmlEngine, *member->name, &ok); - if (ok) { - auto newResolver = resolver->owner->New(); - newResolver->owner = resolver->owner; - initScopedEnumResolver(newResolver, type, index); - return QV4::IR::DiscoveredType(newResolver); - } - } - } - - if (type.isCompositeSingleton()) { - QQmlRefPointer tdata = qmlEngine->typeLoader.getType(type.singletonInstanceInfo()->url); - Q_ASSERT(tdata); - tdata->release(); // Decrease the reference count added from QQmlTypeLoader::getType() - // When a singleton tries to reference itself, it may not be complete yet. - if (tdata->isComplete()) { - auto newResolver = resolver->owner->New(); - newResolver->owner = resolver->owner; - initMetaObjectResolver(newResolver, qmlEngine->propertyCacheForType(tdata->compilationUnit()->metaTypeId)); - newResolver->flags |= AllPropertiesAreFinal; - return newResolver->resolveMember(qmlEngine, newResolver, member); - } - } else if (type.isSingleton()) { - const QMetaObject *singletonMeta = type.singletonInstanceInfo()->instanceMetaObject; - if (singletonMeta) { // QJSValue-based singletons cannot be accelerated - auto newResolver = resolver->owner->New(); - newResolver->owner = resolver->owner; - initMetaObjectResolver(newResolver, qmlEngine->cache(singletonMeta)); - member->kind = QV4::IR::Member::MemberOfSingletonObject; - return newResolver->resolveMember(qmlEngine, newResolver, member); - } - } -#if 0 - else if (const QMetaObject *attachedMeta = type->attachedPropertiesType(qmlEngine)) { - // Right now the attached property IDs are not stable and cannot be embedded in the - // code that is cached on disk. - QQmlPropertyCache *cache = qmlEngine->cache(attachedMeta); - auto newResolver = resolver->owner->New(); - newResolver->owner = resolver->owner; - initMetaObjectResolver(newResolver, cache); - member->setAttachedPropertiesId(type->attachedPropertiesId(qmlEngine)); - return newResolver->resolveMember(qmlEngine, newResolver, member); - } -#endif - - return result; -} - -static void initQmlTypeResolver(QV4::IR::MemberExpressionResolver *resolver, const QQmlType &qmlType) -{ - Q_ASSERT(resolver); - - resolver->resolveMember = &resolveQmlType; - resolver->qmlType = qmlType; - resolver->typenameCache = 0; - resolver->flags = 0; -} - -static QV4::IR::DiscoveredType resolveImportNamespace( - QQmlEnginePrivate *, const QV4::IR::MemberExpressionResolver *resolver, - QV4::IR::Member *member) -{ - QV4::IR::Type result = QV4::IR::VarType; - QQmlTypeNameCache *typeNamespace = resolver->typenameCache; - const QQmlImportRef *importNamespace = resolver->import; - - QQmlTypeNameCache::Result r = typeNamespace->query(*member->name, importNamespace); - if (r.isValid()) { - member->freeOfSideEffects = true; - if (r.scriptIndex != -1) { - // TODO: remember the index and replace with subscript later. - result = QV4::IR::VarType; - } else if (r.type.isValid()) { - // TODO: Propagate singleton information, so that it is loaded - // through the singleton getter in the run-time. Until then we - // can't accelerate access :( - if (!r.type.isSingleton()) { - auto newResolver = resolver->owner->New(); - newResolver->owner = resolver->owner; - initQmlTypeResolver(newResolver, r.type); - return QV4::IR::DiscoveredType(newResolver); - } - } else { - Q_ASSERT(false); // How can this happen? - } - } - - return result; -} - -static void initImportNamespaceResolver(QV4::IR::MemberExpressionResolver *resolver, - QQmlTypeNameCache *imports, const QQmlImportRef *importNamespace) -{ - resolver->resolveMember = &resolveImportNamespace; - resolver->import = importNamespace; - resolver->typenameCache = imports; - resolver->flags = 0; -} - -static QV4::IR::DiscoveredType resolveMetaObjectProperty( - QQmlEnginePrivate *qmlEngine, const QV4::IR::MemberExpressionResolver *resolver, - QV4::IR::Member *member) -{ - QV4::IR::Type result = QV4::IR::VarType; - QQmlPropertyCache *metaObject = resolver->propertyCache; - - if (member->name->constData()->isUpper() && (resolver->flags & LookupsIncludeEnums)) { - const QMetaObject *mo = metaObject->createMetaObject(); - QByteArray enumName = member->name->toUtf8(); - for (int ii = mo->enumeratorCount() - 1; ii >= 0; --ii) { - QMetaEnum metaEnum = mo->enumerator(ii); - bool ok; - int value = metaEnum.keyToValue(enumName.constData(), &ok); - if (ok) { - member->setEnumValue(value); - return QV4::IR::SInt32Type; - } - } - } - - if (member->kind != QV4::IR::Member::MemberOfIdObjectsArray && member->kind != QV4::IR::Member::MemberOfSingletonObject && - qmlEngine && !(resolver->flags & LookupsExcludeProperties)) { - QQmlPropertyData *property = member->property; - if (!property && metaObject) { - if (QQmlPropertyData *candidate = metaObject->property(*member->name, /*object*/0, /*context*/0)) { - const bool isFinalProperty = (candidate->isFinal() || (resolver->flags & AllPropertiesAreFinal)) - && !candidate->isFunction(); - - if (lookupHints() - && !(resolver->flags & AllPropertiesAreFinal) - && !candidate->isFinal() - && !candidate->isFunction() - && candidate->isDirect()) { - qWarning() << "Hint: Access to property" << *member->name << "of" << metaObject->className() << "could be accelerated if it was marked as FINAL"; - } - - if (isFinalProperty && metaObject->isAllowedInRevision(candidate)) { - property = candidate; - member->inhibitTypeConversionOnWrite = true; - if (!(resolver->flags & ResolveTypeInformationOnly)) - member->property = candidate; // Cache for next iteration and isel needs it. - } - } - } - - if (property) { - // Enums cannot be mapped to IR types, they need to go through the run-time handling - // of accepting strings that will then be converted to the right values. - if (property->isEnum()) - return QV4::IR::VarType; - - switch (property->propType()) { - case QMetaType::Bool: result = QV4::IR::BoolType; break; - case QMetaType::Int: result = QV4::IR::SInt32Type; break; - case QMetaType::Double: result = QV4::IR::DoubleType; break; - case QMetaType::QString: result = QV4::IR::StringType; break; - default: - if (property->isQObject()) { - if (QQmlPropertyCache *cache = qmlEngine->propertyCacheForType(property->propType())) { - auto newResolver = resolver->owner->New(); - newResolver->owner = resolver->owner; - initMetaObjectResolver(newResolver, cache); - return QV4::IR::DiscoveredType(newResolver); - } - } else if (const QMetaObject *valueTypeMetaObject = QQmlValueTypeFactory::metaObjectForMetaType(property->propType())) { - if (QQmlPropertyCache *cache = qmlEngine->cache(valueTypeMetaObject)) { - auto newResolver = resolver->owner->New(); - newResolver->owner = resolver->owner; - initMetaObjectResolver(newResolver, cache); - newResolver->flags |= ResolveTypeInformationOnly; - return QV4::IR::DiscoveredType(newResolver); - } - } - break; - } - } - } - - return result; -} - -static void initMetaObjectResolver(QV4::IR::MemberExpressionResolver *resolver, QQmlPropertyCache *metaObject) -{ - Q_ASSERT(resolver); - - resolver->resolveMember = &resolveMetaObjectProperty; - resolver->propertyCache = metaObject; - resolver->flags = 0; -} - -static QV4::IR::DiscoveredType resolveScopedEnum(QQmlEnginePrivate *qmlEngine, - const QV4::IR::MemberExpressionResolver *resolver, - QV4::IR::Member *member) -{ - if (!member->name->constData()->isUpper()) - return QV4::IR::VarType; - - QQmlType type = resolver->qmlType; - int index = resolver->flags; - - bool ok = false; - int value = type.scopedEnumValue(qmlEngine, index, *member->name, &ok); - if (!ok) - return QV4::IR::VarType; - member->setEnumValue(value); - return QV4::IR::SInt32Type; -} - -static void initScopedEnumResolver(QV4::IR::MemberExpressionResolver *resolver, const QQmlType &qmlType, int index) -{ - Q_ASSERT(resolver); - - resolver->resolveMember = &resolveScopedEnum; - resolver->qmlType = qmlType; - resolver->flags = index; -} -#endif - -#endif // V4_BOOTSTRAP - -void JSCodeGen::beginFunctionBodyHook() -{ - _qmlContextSlot = bytecodeGenerator->newRegister(); - _importedScriptsSlot = bytecodeGenerator->newRegister(); - -#ifndef V4_BOOTSTRAP - Instruction::LoadQmlContext load; - load.result = Reference::fromStackSlot(this, _qmlContextSlot).stackSlot(); - bytecodeGenerator->addInstruction(load); - -#if 0 - temp->type = QV4::IR::QObjectType; - temp->memberResolver = _function->New(); - initMetaObjectResolver(temp->memberResolver, _scopeObject); - auto name = _block->NAME(QV4::IR::Name::builtin_qml_context, 0, 0); - name->type = temp->type; -#endif - - Instruction::LoadQmlImportedScripts loadScripts; - loadScripts.result = Reference::fromStackSlot(this, _importedScriptsSlot).stackSlot(); - bytecodeGenerator->addInstruction(loadScripts); -#endif -} - -QV4::Compiler::Codegen::Reference JSCodeGen::fallbackNameLookup(const QString &name) -{ -#ifndef V4_BOOTSTRAP - // FIXME: Remove this function. - if (_disableAcceleratedLookups || true) - return Reference(); - - // Implement QML lookup semantics in the current file context. - // - // Note: We do not check if properties of the qml scope object or context object - // are final. That's because QML tries to get as close as possible to lexical scoping, - // which means in terms of properties that only those visible at compile time are chosen. - // I.e. access to a "foo" property declared within the same QML component as "property int foo" - // will always access that instance and as integer. If a sub-type implements its own property string foo, - // then that one is not chosen for accesses from within this file, because it wasn't visible at compile - // time. This corresponds to the logic in QQmlPropertyCache::findProperty to find the property associated - // with the correct QML context. - - // Look for IDs first. - for (const IdMapping &mapping : qAsConst(_idObjects)) { - if (name == mapping.name) { - if (_context->contextType == QV4::Compiler::ContextType::Binding) - _context->idObjectDependencies.insert(mapping.idIndex); - - Instruction::LoadIdObject load; - load.base = Reference::fromStackSlot(this, _qmlContextSlot).stackSlot(); - load.index = mapping.idIndex; - - Reference result = Reference::fromAccumulator(this); - bytecodeGenerator->addInstruction(load); - result.isReadonly = true; - return result; - } - } - - if (name.at(0).isUpper()) { - QQmlTypeNameCache::Result r = imports->query(name); - if (r.isValid()) { - if (r.scriptIndex != -1) { - Reference imports = Reference::fromStackSlot(this, _importedScriptsSlot); - return Reference::fromSubscript(imports, Reference::fromConst(this, QV4::Encode(r.scriptIndex))); - } else if (r.type.isValid()) { - return Reference::fromName(this, name); - } else { - Q_ASSERT(r.importNamespace); - return Reference::fromName(this, name); - } - } - } - - if (_scopeObject) { - QQmlPropertyData *data = lookupQmlCompliantProperty(_scopeObject, name); - if (data) { - // Q_INVOKABLEs can't be FINAL, so we have to look them up at run-time - if (data->isFunction()) - return Reference::fromName(this, name); - - Reference base = Reference::fromStackSlot(this, _qmlContextSlot); - Reference::PropertyCapturePolicy capturePolicy; - if (!data->isConstant() && !data->isQmlBinding()) - capturePolicy = Reference::CaptureAtRuntime; - else - capturePolicy = data->isConstant() ? Reference::DontCapture : Reference::CaptureAheadOfTime; - return Reference::fromQmlScopeObject(base, data->coreIndex(), data->notifyIndex(), capturePolicy); - } - } - - if (_contextObject) { - QQmlPropertyData *data = lookupQmlCompliantProperty(_contextObject, name); - if (data) { - // Q_INVOKABLEs can't be FINAL, so we have to look them up at run-time - if (data->isFunction()) - return Reference::fromName(this, name); - - Reference base = Reference::fromStackSlot(this, _qmlContextSlot); - Reference::PropertyCapturePolicy capturePolicy; - if (!data->isConstant() && !data->isQmlBinding()) - capturePolicy = Reference::CaptureAtRuntime; - else - capturePolicy = data->isConstant() ? Reference::DontCapture : Reference::CaptureAheadOfTime; - return Reference::fromQmlContextObject(base, data->coreIndex(), data->notifyIndex(), capturePolicy); - } - } -#else - Q_UNUSED(name) -#endif // V4_BOOTSTRAP - return Reference(); -} - #ifndef V4_BOOTSTRAP QQmlPropertyData *PropertyResolver::property(const QString &name, bool *notInRevision, RevisionCheck check) const @@ -2433,7 +2026,6 @@ QmlIR::Object *IRLoader::loadObject(const QV4::CompiledData::Object *serializedO b->value.compiledScriptIndex = functionIndices.count() - 1; QmlIR::CompiledFunctionOrExpression *foe = pool->New(); - foe->disableAcceleratedLookups = true; foe->nameIndex = 0; QQmlJS::AST::ExpressionNode *expr; diff --git a/src/qml/compiler/qqmlirbuilder_p.h b/src/qml/compiler/qqmlirbuilder_p.h index 6affca3ca6..56724bcda5 100644 --- a/src/qml/compiler/qqmlirbuilder_p.h +++ b/src/qml/compiler/qqmlirbuilder_p.h @@ -345,7 +345,6 @@ struct Q_QML_PRIVATE_EXPORT CompiledFunctionOrExpression QQmlJS::AST::Node *parentNode = nullptr; // FunctionDeclaration, Statement or Expression QQmlJS::AST::Node *node = nullptr; // FunctionDeclaration, Statement or Expression quint32 nameIndex = 0; - bool disableAcceleratedLookups = false; CompiledFunctionOrExpression *next = nullptr; }; @@ -606,47 +605,16 @@ struct Q_QML_PRIVATE_EXPORT JSCodeGen : public QV4::Compiler::Codegen { JSCodeGen(const QString &sourceCode, QV4::Compiler::JSUnitGenerator *jsUnitGenerator, QV4::Compiler::Module *jsModule, QQmlJS::Engine *jsEngine, QQmlJS::AST::UiProgram *qmlRoot, - QQmlTypeNameCache *imports, const QV4::Compiler::StringTableGenerator *stringPool, const QSet &globalNames); - - struct IdMapping - { - QString name; - int idIndex; - QQmlPropertyCache *type; - }; - typedef QVector ObjectIdMapping; - - void beginContextScope(const ObjectIdMapping &objectIds, QQmlPropertyCache *contextObject); - void beginObjectScope(QQmlPropertyCache *scopeObject); + const QV4::Compiler::StringTableGenerator *stringPool, const QSet &globalNames); // Returns mapping from input functions to index in IR::Module::functions / compiledData->runtimeFunctions QVector generateJSCodeForFunctionsAndBindings(const QList &functions); - int defineFunction(const QString &name, AST::Node *ast, - AST::FormalParameterList *formals, - AST::StatementList *body) override; - -protected: - void beginFunctionBodyHook() override; - bool canAccelerateGlobalLookups() const override { return !_disableAcceleratedLookups; } - Reference fallbackNameLookup(const QString &name) override; - private: - // returns nullptr if lookup needs to happen by name - QQmlPropertyData *lookupQmlCompliantProperty(QQmlPropertyCache *cache, const QString &name); - QString sourceCode; QQmlJS::Engine *jsEngine; // needed for memory pool QQmlJS::AST::UiProgram *qmlRoot; - QQmlTypeNameCache *imports; const QV4::Compiler::StringTableGenerator *stringPool; - - bool _disableAcceleratedLookups; - ObjectIdMapping _idObjects; - QQmlPropertyCache *_contextObject; - QQmlPropertyCache *_scopeObject; - int _qmlContextSlot; - int _importedScriptsSlot; }; struct Q_QML_PRIVATE_EXPORT IRLoader { diff --git a/src/qml/compiler/qqmltypecompiler.cpp b/src/qml/compiler/qqmltypecompiler.cpp index f753c78b1a..66d3afc7a0 100644 --- a/src/qml/compiler/qqmltypecompiler.cpp +++ b/src/qml/compiler/qqmltypecompiler.cpp @@ -146,7 +146,7 @@ QQmlRefPointer QQmlTypeCompiler::compile() document->jsModule.fileName = typeData->urlString(); document->jsModule.finalUrl = typeData->finalUrlString(); QmlIR::JSCodeGen v4CodeGenerator(document->code, &document->jsGenerator, &document->jsModule, &document->jsParserEngine, - document->program, typeNameCache.data(), &document->jsGenerator.stringTable, engine->v8engine()->illegalNames()); + document->program, &document->jsGenerator.stringTable, engine->v8engine()->illegalNames()); QQmlJSCodeGenerator jsCodeGen(this, &v4CodeGenerator); if (!jsCodeGen.generateCodeForComponents()) return nullptr; @@ -766,10 +766,6 @@ void QQmlScriptStringScanner::scan() if (!pd || pd->propType() != scriptStringMetaType) continue; - QmlIR::CompiledFunctionOrExpression *foe = obj->functionsAndExpressions->slowAt(binding->value.compiledScriptIndex); - if (foe) - foe->disableAcceleratedLookups = true; - QString script = compiler->bindingAsString(obj, binding->value.compiledScriptIndex); binding->stringIndex = compiler->registerString(script); } @@ -1323,24 +1319,6 @@ bool QQmlJSCodeGenerator::compileComponent(int contextObject) contextObject = componentBinding->value.objectIndex; } - QmlIR::JSCodeGen::ObjectIdMapping idMapping; - idMapping.reserve(obj->namedObjectsInComponent.count); - for (int i = 0; i < obj->namedObjectsInComponent.count; ++i) { - const int objectIndex = obj->namedObjectsInComponent.at(i); - QmlIR::JSCodeGen::IdMapping m; - const QmlIR::Object *obj = qmlObjects.at(objectIndex); - m.name = stringAt(obj->idNameIndex); - m.idIndex = obj->id; - m.type = propertyCaches->at(objectIndex); - - auto *tref = resolvedType(obj->inheritedTypeNameIndex); - if (tref && tref->isFullyDynamicType) - m.type = nullptr; - - idMapping << m; - } - v4CodeGen->beginContextScope(idMapping, propertyCaches->at(contextObject)); - if (!compileJavaScriptCodeInObjectsRecursively(contextObject, contextObject)) return false; @@ -1354,16 +1332,9 @@ bool QQmlJSCodeGenerator::compileJavaScriptCodeInObjectsRecursively(int objectIn return true; if (object->functionsAndExpressions->count > 0) { - QQmlPropertyCache *scopeObject = propertyCaches->at(scopeObjectIndex); - v4CodeGen->beginObjectScope(scopeObject); - QList functionsToCompile; - for (QmlIR::CompiledFunctionOrExpression *foe = object->functionsAndExpressions->first; foe; foe = foe->next) { - const bool haveCustomParser = customParsers.contains(object->inheritedTypeNameIndex); - if (haveCustomParser) - foe->disableAcceleratedLookups = true; + for (QmlIR::CompiledFunctionOrExpression *foe = object->functionsAndExpressions->first; foe; foe = foe->next) functionsToCompile << *foe; - } const QVector runtimeFunctionIndices = v4CodeGen->generateJSCodeForFunctionsAndBindings(functionsToCompile); const QList jsErrors = v4CodeGen->qmlErrors(); if (!jsErrors.isEmpty()) { diff --git a/src/qml/compiler/qv4bytecodehandler.cpp b/src/qml/compiler/qv4bytecodehandler.cpp index a34472010b..e1fc0c6ee3 100644 --- a/src/qml/compiler/qv4bytecodehandler.cpp +++ b/src/qml/compiler/qv4bytecodehandler.cpp @@ -225,21 +225,6 @@ std::vector ByteCodeHandler::collectLabelsInBytecode(const char *code, uint COLLECTOR_BEGIN_INSTR(StoreSuperProperty) COLLECTOR_END_INSTR(StoreSuperProperty) - COLLECTOR_BEGIN_INSTR(StoreScopeObjectProperty) - COLLECTOR_END_INSTR(StoreScopeObjectProperty) - - COLLECTOR_BEGIN_INSTR(LoadScopeObjectProperty) - COLLECTOR_END_INSTR(LoadScopeObjectProperty) - - COLLECTOR_BEGIN_INSTR(StoreContextObjectProperty) - COLLECTOR_END_INSTR(StoreContextObjectProperty) - - COLLECTOR_BEGIN_INSTR(LoadContextObjectProperty) - COLLECTOR_END_INSTR(LoadContextObjectProperty) - - COLLECTOR_BEGIN_INSTR(LoadIdObject) - COLLECTOR_END_INSTR(LoadIdObject) - COLLECTOR_BEGIN_INSTR(Yield) COLLECTOR_END_INSTR(Yield) @@ -276,12 +261,6 @@ std::vector ByteCodeHandler::collectLabelsInBytecode(const char *code, uint COLLECTOR_BEGIN_INSTR(CallQmlContextPropertyLookup) COLLECTOR_END_INSTR(CallQmlContextPropertyLookup) - COLLECTOR_BEGIN_INSTR(CallScopeObjectProperty) - COLLECTOR_END_INSTR(CallScopeObjectProperty) - - COLLECTOR_BEGIN_INSTR(CallContextObjectProperty) - COLLECTOR_END_INSTR(CallContextObjectProperty) - COLLECTOR_BEGIN_INSTR(CallWithSpread) COLLECTOR_END_INSTR(CallWithSpread) @@ -546,12 +525,6 @@ std::vector ByteCodeHandler::collectLabelsInBytecode(const char *code, uint COLLECTOR_BEGIN_INSTR(GetTemplateObject) COLLECTOR_END_INSTR(GetTemplateObject) - COLLECTOR_BEGIN_INSTR(LoadQmlContext) - COLLECTOR_END_INSTR(LoadQmlContext) - - COLLECTOR_BEGIN_INSTR(LoadQmlImportedScripts) - COLLECTOR_END_INSTR(LoadQmlImportedScripts) - COLLECTOR_BEGIN_INSTR(TailCall) COLLECTOR_END_INSTR(TailCall) } diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index de87d6d48c..b0bec5b6f2 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -1875,8 +1875,6 @@ bool Codegen::visit(CallExpression *ast) switch (base.type) { case Reference::Member: case Reference::Subscript: - case Reference::QmlScopeObject: - case Reference::QmlContextObject: base = base.asLValue(); break; case Reference::Name: @@ -1938,21 +1936,7 @@ bool Codegen::visit(CallExpression *ast) void Codegen::handleCall(Reference &base, Arguments calldata, int slotForFunction, int slotForThisObject) { //### Do we really need all these call instructions? can's we load the callee in a temp? - if (base.type == Reference::QmlScopeObject) { - Instruction::CallScopeObjectProperty call; - call.base = base.qmlBase.stackSlot(); - call.name = base.qmlCoreIndex; - call.argc = calldata.argc; - call.argv = calldata.argv; - bytecodeGenerator->addInstruction(call); - } else if (base.type == Reference::QmlContextObject) { - Instruction::CallContextObjectProperty call; - call.base = base.qmlBase.stackSlot(); - call.name = base.qmlCoreIndex; - call.argc = calldata.argc; - call.argv = calldata.argv; - bytecodeGenerator->addInstruction(call); - } else if (base.type == Reference::Member) { + if (base.type == Reference::Member) { if (!disable_lookups && useFastLookups) { Instruction::CallPropertyLookup call; call.base = base.propertyBase.stackSlot(); @@ -2363,11 +2347,6 @@ Codegen::Reference Codegen::referenceForName(const QString &name, bool isLhs, co return r; } - // This hook allows implementing QML lookup semantics - Reference fallback = fallbackNameLookup(name); - if (fallback.type != Reference::Invalid) - return fallback; - Reference r = Reference::fromName(this, name); r.global = useFastLookups && (resolved.type == Context::ResolvedName::Global || resolved.type == Context::ResolvedName::QmlGlobal); r.qmlGlobal = resolved.type == Context::ResolvedName::QmlGlobal; @@ -2387,12 +2366,6 @@ void Codegen::loadClosure(int closureId) } } -Codegen::Reference Codegen::fallbackNameLookup(const QString &name) -{ - Q_UNUSED(name) - return Reference(); -} - bool Codegen::visit(IdentifierExpression *ast) { if (hasError) @@ -3101,8 +3074,6 @@ int Codegen::defineFunction(const QString &name, AST::Node *ast, bytecodeGenerator->addInstruction(yield); } - beginFunctionBodyHook(); - statementList(body); if (!hasError) { @@ -4039,10 +4010,6 @@ bool Codegen::Reference::operator==(const Codegen::Reference &other) const return index == other.index; case Const: return constant == other.constant; - case QmlScopeObject: - case QmlContextObject: - return qmlCoreIndex == other.qmlCoreIndex && qmlNotifyIndex == other.qmlNotifyIndex - && capturePolicy == other.capturePolicy; } return true; } @@ -4100,9 +4067,7 @@ Codegen::Reference Codegen::Reference::storeConsumeAccumulator() const Codegen::Reference Codegen::Reference::baseObject() const { - if (type == Reference::QmlScopeObject || type == Reference::QmlContextObject) { - return Reference::fromStackSlot(codegen, qmlBase.stackSlot()); - } else if (type == Reference::Member) { + if (type == Reference::Member) { RValue rval = propertyBase; if (!rval.isValid()) return Reference::fromConst(codegen, Encode::undefined()); @@ -4187,8 +4152,6 @@ bool Codegen::Reference::storeWipesAccumulator() const case Name: case Member: case Subscript: - case QmlScopeObject: - case QmlContextObject: return true; } } @@ -4268,18 +4231,6 @@ void Codegen::Reference::storeAccumulator() const store.index = elementSubscript.stackSlot(); codegen->bytecodeGenerator->addInstruction(store); } return; - case QmlScopeObject: { - Instruction::StoreScopeObjectProperty store; - store.base = qmlBase; - store.propertyIndex = qmlCoreIndex; - codegen->bytecodeGenerator->addInstruction(store); - } return; - case QmlContextObject: { - Instruction::StoreContextObjectProperty store; - store.base = qmlBase; - store.propertyIndex = qmlCoreIndex; - codegen->bytecodeGenerator->addInstruction(store); - } return; case Invalid: case Accumulator: case Const: @@ -4436,24 +4387,6 @@ QT_WARNING_POP load.base = elementBase; codegen->bytecodeGenerator->addInstruction(load); } return; - case QmlScopeObject: { - Instruction::LoadScopeObjectProperty load; - load.base = qmlBase; - load.propertyIndex = qmlCoreIndex; - load.captureRequired = capturePolicy == CaptureAtRuntime; - codegen->bytecodeGenerator->addInstruction(load); - if (capturePolicy == CaptureAheadOfTime) - codegen->_context->scopeObjectPropertyDependencies.insert(qmlCoreIndex, qmlNotifyIndex); - } return; - case QmlContextObject: { - Instruction::LoadContextObjectProperty load; - load.base = qmlBase; - load.propertyIndex = qmlCoreIndex; - load.captureRequired = capturePolicy == CaptureAtRuntime; - codegen->bytecodeGenerator->addInstruction(load); - if (capturePolicy == CaptureAheadOfTime) - codegen->_context->contextObjectPropertyDependencies.insert(qmlCoreIndex, qmlNotifyIndex); - } return; case Invalid: break; } diff --git a/src/qml/compiler/qv4codegen_p.h b/src/qml/compiler/qv4codegen_p.h index b1cc4c090a..ad86483132 100644 --- a/src/qml/compiler/qv4codegen_p.h +++ b/src/qml/compiler/qv4codegen_p.h @@ -184,9 +184,7 @@ public: Member, Subscript, Import, - QmlScopeObject, - QmlContextObject, - LastLValue = QmlContextObject, + LastLValue = Import, Const } type = Invalid; @@ -223,10 +221,6 @@ public: bool isValid() const { return type != Invalid; } bool loadTriggersSideEffect() const { switch (type) { - case QmlScopeObject: - return capturePolicy != DontCapture; - case QmlContextObject: - return capturePolicy != DontCapture; case Name: case Member: case Subscript: @@ -245,28 +239,6 @@ public: return isStackSlot(); } - enum PropertyCapturePolicy { - /* - We're reading a property from the scope or context object, but it's a CONSTANT property, - so we don't need to register a dependency at all. - */ - DontCapture, - /* - We're reading the property of a QObject, and we know that it's the - scope object or context object, which we know very well. Instead of registering a - property capture every time, we can do that ahead of time and then register all those - captures in one shot in registerQmlDependencies(). - */ - CaptureAheadOfTime, - /* - We're reading the property of a QObject, and we're not quite sure where - the QObject comes from or what it is. So, when reading that property at run-time, - make sure that we capture where we read that property so that if it changes we can - re-evaluate the entire expression. - */ - CaptureAtRuntime - }; - static Reference fromAccumulator(Codegen *cg) { return Reference(cg, Accumulator); } @@ -333,22 +305,6 @@ public: r.isReadonly = true; return r; } - static Reference fromQmlScopeObject(const Reference &base, qint16 coreIndex, qint16 notifyIndex, PropertyCapturePolicy capturePolicy) { - Reference r(base.codegen, QmlScopeObject); - r.qmlBase = base.storeOnStack().stackSlot(); - r.qmlCoreIndex = coreIndex; - r.qmlNotifyIndex = notifyIndex; - r.capturePolicy = capturePolicy; - return r; - } - static Reference fromQmlContextObject(const Reference &base, qint16 coreIndex, qint16 notifyIndex, PropertyCapturePolicy capturePolicy) { - Reference r(base.codegen, QmlContextObject); - r.qmlBase = base.storeOnStack().stackSlot(); - r.qmlCoreIndex = coreIndex; - r.qmlNotifyIndex = notifyIndex; - r.capturePolicy = capturePolicy; - return r; - } static Reference fromThis(Codegen *cg) { Reference r = fromStackSlot(cg, CallData::This); r.isReadonly = true; @@ -403,12 +359,6 @@ public: Moth::StackSlot elementBase; RValue elementSubscript; }; - struct { // QML scope/context object case - Moth::StackSlot qmlBase; - qint16 qmlCoreIndex; - qint16 qmlNotifyIndex; - PropertyCapturePolicy capturePolicy; - }; Moth::StackSlot property; // super property }; QString name; @@ -598,12 +548,6 @@ protected: Reference referenceForPropertyName(const Codegen::Reference &object, AST::PropertyName *name); - // Hooks provided to implement QML lookup semantics - virtual bool canAccelerateGlobalLookups() const { return true; } - virtual Reference fallbackNameLookup(const QString &name); - - virtual void beginFunctionBodyHook() {} - void emitReturn(const Reference &expr); // nodes diff --git a/src/qml/compiler/qv4compileddata_p.h b/src/qml/compiler/qv4compileddata_p.h index d63b1fd2b9..1341c91e97 100644 --- a/src/qml/compiler/qv4compileddata_p.h +++ b/src/qml/compiler/qv4compileddata_p.h @@ -73,7 +73,7 @@ QT_BEGIN_NAMESPACE // Bump this whenever the compiler data structures change in an incompatible way. -#define QV4_DATA_STRUCTURE_VERSION 0x19 +#define QV4_DATA_STRUCTURE_VERSION 0x20 class QIODevice; class QQmlPropertyCache; @@ -288,21 +288,10 @@ struct Function quint16_le nRegisters; Location location; - // Qml Extensions Begin - // Array of resolved ID objects - size_t dependingIdObjectsOffset() const { return lineNumberOffset() + nLineNumbers * sizeof(CodeOffsetToLine); } - quint16_le nDependingIdObjects; - quint16_le nDependingContextProperties; - // Array of int pairs (property index and notify index) - size_t dependingContextPropertiesOffset() const { return dependingIdObjectsOffset() + nDependingIdObjects * sizeof(quint32); } - quint16_le nDependingScopeProperties; - // Array of int pairs (property index and notify index) - size_t dependingScopePropertiesOffset() const { return dependingContextPropertiesOffset() + nDependingContextProperties * sizeof(quint32); } - // Qml Extensions End - // Keep all unaligned data at the end quint8 flags; quint8 padding1; + quint16 padding2; // quint32 formalsIndex[nFormals] // quint32 localsIndex[nLocals] @@ -310,9 +299,6 @@ struct Function const quint32_le *formalsTable() const { return reinterpret_cast(reinterpret_cast(this) + formalsOffset); } const quint32_le *localsTable() const { return reinterpret_cast(reinterpret_cast(this) + localsOffset); } const CodeOffsetToLine *lineNumberTable() const { return reinterpret_cast(reinterpret_cast(this) + lineNumberOffset()); } - const quint32_le *qmlIdObjectDependencyTable() const { return reinterpret_cast(reinterpret_cast(this) + dependingIdObjectsOffset()); } - const quint32_le *qmlContextPropertiesDependencyTable() const { return reinterpret_cast(reinterpret_cast(this) + dependingContextPropertiesOffset()); } - const quint32_le *qmlScopePropertiesDependencyTable() const { return reinterpret_cast(reinterpret_cast(this) + dependingScopePropertiesOffset()); } // --- QQmlPropertyCacheCreator interface const quint32_le *formalsBegin() const { return formalsTable(); } @@ -321,11 +307,8 @@ struct Function const char *code() const { return reinterpret_cast(this) + codeOffset; } - inline bool hasQmlDependencies() const { return nDependingIdObjects > 0 || nDependingContextProperties > 0 || nDependingScopeProperties > 0; } - - static int calculateSize(int nFormals, int nLocals, int nLines, int nInnerfunctions, int nIdObjectDependencies, int nPropertyDependencies, int codeSize) { - int trailingData = (nFormals + nLocals + nInnerfunctions + nIdObjectDependencies + - 2 * nPropertyDependencies)*sizeof (quint32) + nLines*sizeof(CodeOffsetToLine); + static int calculateSize(int nFormals, int nLocals, int nLines, int nInnerfunctions, int codeSize) { + int trailingData = (nFormals + nLocals + nInnerfunctions)*sizeof (quint32) + nLines*sizeof(CodeOffsetToLine); size_t size = align(align(sizeof(Function)) + size_t(trailingData)) + align(codeSize); Q_ASSERT(size < INT_MAX); return int(size); @@ -335,7 +318,7 @@ struct Function return (a + 7) & ~size_t(7); } }; -static_assert(sizeof(Function) == 52, "Function structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); +static_assert(sizeof(Function) == 48, "Function structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); struct Method { enum Type { diff --git a/src/qml/compiler/qv4compiler.cpp b/src/qml/compiler/qv4compiler.cpp index a3c9347c67..4d85f25d4b 100644 --- a/src/qml/compiler/qv4compiler.cpp +++ b/src/qml/compiler/qv4compiler.cpp @@ -427,28 +427,6 @@ void QV4::Compiler::JSUnitGenerator::writeFunction(char *f, QV4::Compiler::Conte function->nRegisters = irFunction->registerCountInFunction; - function->nDependingIdObjects = 0; - function->nDependingContextProperties = 0; - function->nDependingScopeProperties = 0; - - if (!irFunction->idObjectDependencies.isEmpty()) { - function->nDependingIdObjects = irFunction->idObjectDependencies.count(); - Q_ASSERT(function->dependingIdObjectsOffset() == currentOffset); - currentOffset += function->nDependingIdObjects * sizeof(quint32); - } - - if (!irFunction->contextObjectPropertyDependencies.isEmpty()) { - function->nDependingContextProperties = irFunction->contextObjectPropertyDependencies.count(); - Q_ASSERT(function->dependingContextPropertiesOffset() == currentOffset); - currentOffset += function->nDependingContextProperties * sizeof(quint32) * 2; - } - - if (!irFunction->scopeObjectPropertyDependencies.isEmpty()) { - function->nDependingScopeProperties = irFunction->scopeObjectPropertyDependencies.count(); - Q_ASSERT(function->dependingScopePropertiesOffset() == currentOffset); - currentOffset += function->nDependingScopeProperties * sizeof(quint32) * 2; - } - function->location.line = irFunction->line; function->location.column = irFunction->column; @@ -468,25 +446,6 @@ void QV4::Compiler::JSUnitGenerator::writeFunction(char *f, QV4::Compiler::Conte // write line numbers memcpy(f + function->lineNumberOffset(), irFunction->lineNumberMapping.constData(), irFunction->lineNumberMapping.size()*sizeof(CompiledData::CodeOffsetToLine)); - // write QML dependencies - quint32_le *writtenDeps = (quint32_le *)(f + function->dependingIdObjectsOffset()); - for (int id : irFunction->idObjectDependencies) { - Q_ASSERT(id >= 0); - *writtenDeps++ = static_cast(id); - } - - writtenDeps = (quint32_le *)(f + function->dependingContextPropertiesOffset()); - for (auto property : irFunction->contextObjectPropertyDependencies) { - *writtenDeps++ = property.key(); // property index - *writtenDeps++ = property.value(); // notify index - } - - writtenDeps = (quint32_le *)(f + function->dependingScopePropertiesOffset()); - for (auto property : irFunction->scopeObjectPropertyDependencies) { - *writtenDeps++ = property.key(); // property index - *writtenDeps++ = property.value(); // notify index - } - // write byte code memcpy(f + function->codeOffset, irFunction->code.constData(), irFunction->code.size()); } @@ -683,10 +642,8 @@ QV4::CompiledData::Unit QV4::Compiler::JSUnitGenerator::generateHeader(QV4::Comp Context *f = module->functions.at(i); blockAndFunctionOffsets[i] = nextOffset; - const int qmlIdDepsCount = f->idObjectDependencies.count(); - const int qmlPropertyDepsCount = f->scopeObjectPropertyDependencies.count() + f->contextObjectPropertyDependencies.count(); quint32 size = QV4::CompiledData::Function::calculateSize(f->arguments.size(), f->locals.size(), f->lineNumberMapping.size(), f->nestedContexts.size(), - qmlIdDepsCount, qmlPropertyDepsCount, f->code.size()); + f->code.size()); functionSize += size - f->code.size(); nextOffset += size; } diff --git a/src/qml/compiler/qv4compilercontext_p.h b/src/qml/compiler/qv4compilercontext_p.h index c9e54c0d1b..8f3b60e395 100644 --- a/src/qml/compiler/qv4compilercontext_p.h +++ b/src/qml/compiler/qv4compilercontext_p.h @@ -276,11 +276,6 @@ struct Context { } }; - // Qml extension: - SmallSet idObjectDependencies; - PropertyDependencyMap contextObjectPropertyDependencies; - PropertyDependencyMap scopeObjectPropertyDependencies; - Context(Context *parent, ContextType type) : parent(parent) , contextType(type) diff --git a/src/qml/compiler/qv4instr_moth.cpp b/src/qml/compiler/qv4instr_moth.cpp index d2e95b83c4..c6c8caffba 100644 --- a/src/qml/compiler/qv4instr_moth.cpp +++ b/src/qml/compiler/qv4instr_moth.cpp @@ -330,26 +330,6 @@ void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*st d << dumpRegister(property, nFormals); MOTH_END_INSTR(StoreSuperProperty) - MOTH_BEGIN_INSTR(StoreScopeObjectProperty) - d << dumpRegister(base, nFormals) << "[" << propertyIndex << "]"; - MOTH_END_INSTR(StoreScopeObjectProperty) - - MOTH_BEGIN_INSTR(LoadScopeObjectProperty) - d << dumpRegister(base, nFormals) << "[" << propertyIndex << "]" << (captureRequired ? " (capture)" : " (no capture)"); - MOTH_END_INSTR(LoadScopeObjectProperty) - - MOTH_BEGIN_INSTR(StoreContextObjectProperty) - d << dumpRegister(base, nFormals) << "[" << propertyIndex << "]"; - MOTH_END_INSTR(StoreContextObjectProperty) - - MOTH_BEGIN_INSTR(LoadContextObjectProperty) - d << dumpRegister(base, nFormals) << "[" << propertyIndex << "]" << (captureRequired ? " (capture)" : " (no capture)"); - MOTH_END_INSTR(LoadContextObjectProperty) - - MOTH_BEGIN_INSTR(LoadIdObject) - d << dumpRegister(base, nFormals) << "[" << index << "]"; - MOTH_END_INSTR(LoadIdObject) - MOTH_BEGIN_INSTR(Yield) MOTH_END_INSTR(Yield) @@ -396,14 +376,6 @@ void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*st d << index << dumpArguments(argc, argv, nFormals); MOTH_END_INSTR(CallQmlContextPropertyLookup) - MOTH_BEGIN_INSTR(CallScopeObjectProperty) - d << dumpRegister(base, nFormals) << "." << name << dumpArguments(argc, argv, nFormals); - MOTH_END_INSTR(CallScopeObjectProperty) - - MOTH_BEGIN_INSTR(CallContextObjectProperty) - d << dumpRegister(base, nFormals) << "." << name << dumpArguments(argc, argv, nFormals); - MOTH_END_INSTR(CallContextObjectProperty) - MOTH_BEGIN_INSTR(CallWithSpread) d << "new" << dumpRegister(func, nFormals) << dumpRegister(thisObject, nFormals) << dumpArguments(argc, argv, nFormals); MOTH_END_INSTR(CallWithSpread) @@ -724,14 +696,6 @@ void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*st d << index; MOTH_END_INSTR(GetTemplateObject) - MOTH_BEGIN_INSTR(LoadQmlContext) - d << dumpRegister(result, nFormals); - MOTH_END_INSTR(LoadQmlContext) - - MOTH_BEGIN_INSTR(LoadQmlImportedScripts) - d << dumpRegister(result, nFormals); - MOTH_END_INSTR(LoadQmlImportedScripts) - MOTH_BEGIN_INSTR(TailCall) d << dumpRegister(func, nFormals) << dumpRegister(thisObject, nFormals) << dumpArguments(argc, argv, nFormals); MOTH_END_INSTR(TailCall) diff --git a/src/qml/compiler/qv4instr_moth_p.h b/src/qml/compiler/qv4instr_moth_p.h index 1c77e6050c..25fca3c0b0 100644 --- a/src/qml/compiler/qv4instr_moth_p.h +++ b/src/qml/compiler/qv4instr_moth_p.h @@ -91,8 +91,6 @@ QT_BEGIN_NAMESPACE #define INSTR_StoreNameStrict(op) INSTRUCTION(op, StoreNameStrict, 1, name) #define INSTR_LoadProperty(op) INSTRUCTION(op, LoadProperty, 1, name) #define INSTR_GetLookup(op) INSTRUCTION(op, GetLookup, 1, index) -#define INSTR_LoadScopeObjectProperty(op) INSTRUCTION(op, LoadScopeObjectProperty, 3, propertyIndex, base, captureRequired) -#define INSTR_LoadContextObjectProperty(op) INSTRUCTION(op, LoadContextObjectProperty, 3, propertyIndex, base, captureRequired) #define INSTR_LoadIdObject(op) INSTRUCTION(op, LoadIdObject, 2, index, base) #define INSTR_Yield(op) INSTRUCTION(op, Yield, 0) #define INSTR_YieldStar(op) INSTRUCTION(op, YieldStar, 0) @@ -102,8 +100,6 @@ QT_BEGIN_NAMESPACE #define INSTR_SetLookup(op) INSTRUCTION(op, SetLookup, 2, index, base) #define INSTR_LoadSuperProperty(op) INSTRUCTION(op, LoadSuperProperty, 1, property) #define INSTR_StoreSuperProperty(op) INSTRUCTION(op, StoreSuperProperty, 1, property) -#define INSTR_StoreScopeObjectProperty(op) INSTRUCTION(op, StoreScopeObjectProperty, 2, base, propertyIndex) -#define INSTR_StoreContextObjectProperty(op) INSTRUCTION(op, StoreContextObjectProperty, 2, base, propertyIndex) #define INSTR_LoadElement(op) INSTRUCTION(op, LoadElement, 1, base) #define INSTR_StoreElement(op) INSTRUCTION(op, StoreElement, 2, base, index) #define INSTR_CallValue(op) INSTRUCTION(op, CallValue, 3, name, argc, argv) @@ -115,8 +111,6 @@ QT_BEGIN_NAMESPACE #define INSTR_CallPossiblyDirectEval(op) INSTRUCTION(op, CallPossiblyDirectEval, 2, argc, argv) #define INSTR_CallGlobalLookup(op) INSTRUCTION(op, CallGlobalLookup, 3, index, argc, argv) #define INSTR_CallQmlContextPropertyLookup(op) INSTRUCTION(op, CallQmlContextPropertyLookup, 3, index, argc, argv) -#define INSTR_CallScopeObjectProperty(op) INSTRUCTION(op, CallScopeObjectProperty, 4, name, base, argc, argv) -#define INSTR_CallContextObjectProperty(op) INSTRUCTION(op, CallContextObjectProperty, 4, name, base, argc, argv) #define INSTR_CallWithSpread(op) INSTRUCTION(op, CallWithSpread, 4, func, thisObject, argc, argv) #define INSTR_Construct(op) INSTRUCTION(op, Construct, 3, func, argc, argv) #define INSTR_ConstructWithSpread(op) INSTRUCTION(op, ConstructWithSpread, 3, func, argc, argv) @@ -196,7 +190,6 @@ QT_BEGIN_NAMESPACE #define INSTR_Div(op) INSTRUCTION(op, Div, 1, lhs) #define INSTR_Mod(op) INSTRUCTION(op, Mod, 1, lhs) #define INSTR_Sub(op) INSTRUCTION(op, Sub, 1, lhs) -#define INSTR_LoadQmlContext(op) INSTRUCTION(op, LoadQmlContext, 1, result) #define INSTR_LoadQmlImportedScripts(op) INSTRUCTION(op, LoadQmlImportedScripts, 1, result) #define INSTR_InitializeBlockDeadTemporalZone(op) INSTRUCTION(op, InitializeBlockDeadTemporalZone, 2, firstReg, count) #define INSTR_ThrowOnNullOrUndefined(op) INSTRUCTION(op, ThrowOnNullOrUndefined, 0) @@ -241,11 +234,6 @@ QT_BEGIN_NAMESPACE F(SetLookup) \ F(LoadSuperProperty) \ F(StoreSuperProperty) \ - F(StoreScopeObjectProperty) \ - F(StoreContextObjectProperty) \ - F(LoadScopeObjectProperty) \ - F(LoadContextObjectProperty) \ - F(LoadIdObject) \ F(ConvertThisToObject) \ F(ToObject) \ F(Jump) \ @@ -300,8 +288,6 @@ QT_BEGIN_NAMESPACE F(CallPossiblyDirectEval) \ F(CallGlobalLookup) \ F(CallQmlContextPropertyLookup) \ - F(CallScopeObjectProperty) \ - F(CallContextObjectProperty) \ F(CallWithSpread) \ F(Construct) \ F(ConstructWithSpread) \ @@ -332,8 +318,6 @@ QT_BEGIN_NAMESPACE F(CreateMappedArgumentsObject) \ F(CreateUnmappedArgumentsObject) \ F(CreateRestParameter) \ - F(LoadQmlContext) \ - F(LoadQmlImportedScripts) \ F(Yield) \ F(YieldStar) \ F(Resume) \ diff --git a/src/qml/jit/qv4baselinejit.cpp b/src/qml/jit/qv4baselinejit.cpp index 47cef3b3bd..1e4288e3c9 100644 --- a/src/qml/jit/qv4baselinejit.cpp +++ b/src/qml/jit/qv4baselinejit.cpp @@ -347,66 +347,6 @@ void BaselineJIT::generate_StoreSuperProperty(int property) as->checkException(); } - -void BaselineJIT::generate_StoreScopeObjectProperty(int base, int propertyIndex) -{ - STORE_ACC(); - as->prepareCallWithArgCount(4); - as->passAccumulatorAsArg(3); - as->passInt32AsArg(propertyIndex, 2); - as->passJSSlotAsArg(base, 1); - as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_storeQmlScopeObjectProperty, CallResultDestination::Ignore); - as->checkException(); -} - -void BaselineJIT::generate_StoreContextObjectProperty(int base, int propertyIndex) -{ - STORE_ACC(); - as->prepareCallWithArgCount(4); - as->passAccumulatorAsArg(3); - as->passInt32AsArg(propertyIndex, 2); - as->passJSSlotAsArg(base, 1); - as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_storeQmlContextObjectProperty, CallResultDestination::Ignore); - as->checkException(); -} - -void BaselineJIT::generate_LoadScopeObjectProperty(int propertyIndex, int base, int captureRequired) -{ - STORE_IP(); - as->prepareCallWithArgCount(4); - as->passInt32AsArg(captureRequired, 3); - as->passInt32AsArg(propertyIndex, 2); - as->passJSSlotAsArg(base, 1); - as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_loadQmlScopeObjectProperty, CallResultDestination::InAccumulator); - as->checkException(); -} - -void BaselineJIT::generate_LoadContextObjectProperty(int propertyIndex, int base, int captureRequired) -{ - STORE_IP(); - as->prepareCallWithArgCount(4); - as->passInt32AsArg(captureRequired, 3); - as->passInt32AsArg(propertyIndex, 2); - as->passJSSlotAsArg(base, 1); - as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_loadQmlContextObjectProperty, CallResultDestination::InAccumulator); - as->checkException(); -} - -void BaselineJIT::generate_LoadIdObject(int index, int base) -{ - STORE_IP(); - as->prepareCallWithArgCount(3); - as->passInt32AsArg(index, 2); - as->passJSSlotAsArg(base, 1); - as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_loadQmlIdObject, CallResultDestination::InAccumulator); - as->checkException(); -} - void BaselineJIT::generate_Yield() { // ##### @@ -536,33 +476,6 @@ void BaselineJIT::generate_CallQmlContextPropertyLookup(int index, int argc, int as->checkException(); } -void BaselineJIT::generate_CallScopeObjectProperty(int propIdx, int base, int argc, int argv) -{ - STORE_IP(); - as->prepareCallWithArgCount(5); - as->passInt32AsArg(argc, 4); - as->passJSSlotAsArg(argv, 3); - as->passInt32AsArg(propIdx, 2); - as->passJSSlotAsArg(base, 1); - as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_callQmlScopeObjectProperty, CallResultDestination::InAccumulator); - as->checkException(); -} - -void BaselineJIT::generate_CallContextObjectProperty(int propIdx, int base, int argc, int argv) -{ - STORE_IP(); - as->prepareCallWithArgCount(5); - as->passInt32AsArg(argc, 4); - as->passJSSlotAsArg(argv, 3); - as->passInt32AsArg(propIdx, 2); - as->passJSSlotAsArg(base, 1); - as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_callQmlContextObjectProperty, CallResultDestination::InAccumulator); - as->checkException(); -} - - void BaselineJIT::generate_CallWithSpread(int func, int thisObject, int argc, int argv) { STORE_IP(); @@ -981,22 +894,6 @@ void BaselineJIT::generate_Sub(int lhs) { as->sub(lhs); } // as->checkException(); //} -void BaselineJIT::generate_LoadQmlContext(int result) -{ - as->prepareCallWithArgCount(1); - as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_loadQmlContext, CallResultDestination::InAccumulator); - as->storeReg(result); -} - -void BaselineJIT::generate_LoadQmlImportedScripts(int result) -{ - as->prepareCallWithArgCount(1); - as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_loadQmlImportedScripts, CallResultDestination::InAccumulator); - as->storeReg(result); -} - void BaselineJIT::generate_InitializeBlockDeadTemporalZone(int firstReg, int count) { as->loadValue(Value::emptyValue().rawValue()); diff --git a/src/qml/jit/qv4baselinejit_p.h b/src/qml/jit/qv4baselinejit_p.h index 4db1eb1806..6646eb713e 100644 --- a/src/qml/jit/qv4baselinejit_p.h +++ b/src/qml/jit/qv4baselinejit_p.h @@ -108,15 +108,6 @@ public: void generate_SetLookup(int index, int base) override; void generate_LoadSuperProperty(int property) override; void generate_StoreSuperProperty(int property) override; - void generate_StoreScopeObjectProperty(int base, - int propertyIndex) override; - void generate_StoreContextObjectProperty(int base, - int propertyIndex) override; - void generate_LoadScopeObjectProperty(int propertyIndex, int base, - int captureRequired) override; - void generate_LoadContextObjectProperty(int propertyIndex, int base, - int captureRequired) override; - void generate_LoadIdObject(int index, int base) override; void generate_Yield() override; void generate_YieldStar() override; void generate_Resume(int) override; @@ -130,8 +121,6 @@ public: void generate_CallPossiblyDirectEval(int argc, int argv) override; void generate_CallGlobalLookup(int index, int argc, int argv) override; void generate_CallQmlContextPropertyLookup(int index, int argc, int argv) override; - void generate_CallScopeObjectProperty(int propIdx, int base, int argc, int argv) override; - void generate_CallContextObjectProperty(int propIdx, int base, int argc, int argv) override; void generate_CallWithSpread(int func, int thisObject, int argc, int argv) override; void generate_TailCall(int func, int thisObject, int argc, int argv) override; void generate_Construct(int func, int argc, int argv) override; @@ -213,8 +202,6 @@ public: void generate_Div(int lhs) override; void generate_Mod(int lhs) override; void generate_Sub(int lhs) override; - void generate_LoadQmlContext(int result) override; - void generate_LoadQmlImportedScripts(int result) override; void generate_InitializeBlockDeadTemporalZone(int firstReg, int count) override; void generate_ThrowOnNullOrUndefined() override; void generate_GetTemplateObject(int index) override; diff --git a/src/qml/jsruntime/qv4function.cpp b/src/qml/jsruntime/qv4function.cpp index 941c37de5b..51a9b92967 100644 --- a/src/qml/jsruntime/qv4function.cpp +++ b/src/qml/jsruntime/qv4function.cpp @@ -78,7 +78,6 @@ Function::Function(ExecutionEngine *engine, CompiledData::CompilationUnit *unit, , codeData(function->code()) , jittedCode(nullptr) , codeRef(nullptr) - , hasQmlDependencies(function->hasQmlDependencies()) { Scope scope(engine); Scoped ic(scope, engine->internalClasses(EngineBase::Class_CallContext)); diff --git a/src/qml/jsruntime/qv4function_p.h b/src/qml/jsruntime/qv4function_p.h index 029dd7786b..62c5d24fc4 100644 --- a/src/qml/jsruntime/qv4function_p.h +++ b/src/qml/jsruntime/qv4function_p.h @@ -80,7 +80,6 @@ struct Q_QML_EXPORT Function { Heap::InternalClass *internalClass; uint nFormals; int interpreterCallCount = 0; - bool hasQmlDependencies; bool isEval = false; Function(ExecutionEngine *engine, CompiledData::CompilationUnit *unit, const CompiledData::Function *function); diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index 711c910906..053581226f 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -233,7 +233,7 @@ QQmlPropertyData *QObjectWrapper::findProperty(ExecutionEngine *engine, QObject return result; } -ReturnedValue QObjectWrapper::getProperty(ExecutionEngine *engine, QObject *object, QQmlPropertyData *property, bool captureRequired) +ReturnedValue QObjectWrapper::getProperty(ExecutionEngine *engine, QObject *object, QQmlPropertyData *property) { QQmlData::flushPendingBinding(object, QQmlPropertyIndex(property->coreIndex())); @@ -259,7 +259,7 @@ ReturnedValue QObjectWrapper::getProperty(ExecutionEngine *engine, QObject *obje QQmlEnginePrivate *ep = engine->qmlEngine() ? QQmlEnginePrivate::get(engine->qmlEngine()) : nullptr; - if (captureRequired && ep && ep->propertyCapture && !property->isConstant()) + if (ep && ep->propertyCapture && !property->isConstant()) ep->propertyCapture->captureProperty(object, property->coreIndex(), property->notifyIndex()); if (property->isVarProperty()) { @@ -357,26 +357,6 @@ ReturnedValue QObjectWrapper::getQmlProperty(QQmlContextData *qmlContext, String return getProperty(v4, d()->object(), result); } -ReturnedValue QObjectWrapper::getProperty(ExecutionEngine *engine, QObject *object, int propertyIndex, bool captureRequired) -{ - if (QQmlData::wasDeleted(object)) - return QV4::Encode::null(); - QQmlData *ddata = QQmlData::get(object, /*create*/false); - if (!ddata) - return QV4::Encode::undefined(); - - if (Q_UNLIKELY(!ddata->propertyCache)) { - ddata->propertyCache = QQmlEnginePrivate::get(engine)->cache(object->metaObject()); - ddata->propertyCache->addref(); - } - - QQmlPropertyCache *cache = ddata->propertyCache; - Q_ASSERT(cache); - QQmlPropertyData *property = cache->property(propertyIndex); - Q_ASSERT(property); // We resolved this property earlier, so it better exist! - return getProperty(engine, object, property, captureRequired); -} - ReturnedValue QObjectWrapper::getQmlProperty(QV4::ExecutionEngine *engine, QQmlContextData *qmlContext, QObject *object, String *name, QObjectWrapper::RevisionMode revisionMode, bool *hasProperty) { if (QQmlData::wasDeleted(object)) { @@ -873,7 +853,7 @@ ReturnedValue QObjectWrapper::virtualResolveLookupGetter(const Object *object, E if (!ddata || !ddata->propertyCache) { QQmlPropertyData local; QQmlPropertyData *property = QQmlPropertyCache::property(engine->jsEngine(), qobj, name, qmlContext, local); - return getProperty(engine, qobj, property, /*captureRequired*/true); + return getProperty(engine, qobj, property); } QQmlPropertyData *property = ddata->propertyCache->property(name.getPointer(), qobj, qmlContext); @@ -919,7 +899,7 @@ ReturnedValue QObjectWrapper::lookupGetter(Lookup *lookup, ExecutionEngine *engi return revertLookup(); QQmlPropertyData *property = lookup->qobjectLookup.propertyData; - return getProperty(engine, qobj, property, /*captureRequired = */true); + return getProperty(engine, qobj, property); } bool QObjectWrapper::virtualResolveLookupSetter(Object *object, ExecutionEngine *engine, Lookup *lookup, diff --git a/src/qml/jsruntime/qv4qobjectwrapper_p.h b/src/qml/jsruntime/qv4qobjectwrapper_p.h index a09e7b6e95..471f352c2a 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper_p.h +++ b/src/qml/jsruntime/qv4qobjectwrapper_p.h @@ -176,13 +176,12 @@ struct Q_QML_EXPORT QObjectWrapper : public Object using Object::get; - static ReturnedValue getProperty(ExecutionEngine *engine, QObject *object, int propertyIndex, bool captureRequired); static void setProperty(ExecutionEngine *engine, QObject *object, int propertyIndex, const Value &value); void setProperty(ExecutionEngine *engine, int propertyIndex, const Value &value); void destroyObject(bool lastCall); - static ReturnedValue getProperty(ExecutionEngine *engine, QObject *object, QQmlPropertyData *property, bool captureRequired = true); + static ReturnedValue getProperty(ExecutionEngine *engine, QObject *object, QQmlPropertyData *property); static ReturnedValue virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup); static ReturnedValue lookupGetter(Lookup *l, ExecutionEngine *engine, const Value &object); diff --git a/src/qml/jsruntime/qv4runtime.cpp b/src/qml/jsruntime/qv4runtime.cpp index 7163a51af1..85d06bcabe 100644 --- a/src/qml/jsruntime/qv4runtime.cpp +++ b/src/qml/jsruntime/qv4runtime.cpp @@ -1457,38 +1457,6 @@ ReturnedValue Runtime::method_callWithReceiver(ExecutionEngine *engine, const Va return static_cast(func).call(thisObject, argv, argc); } -ReturnedValue Runtime::method_callQmlScopeObjectProperty(ExecutionEngine *engine, Value *base, - int propertyIndex, Value *argv, int argc) -{ - Scope scope(engine); - ScopedFunctionObject fo(scope, method_loadQmlScopeObjectProperty(engine, *base, propertyIndex, - /*captureRequired*/true)); - if (!fo) { - QString error = QStringLiteral("Property '%1' of scope object is not a function").arg(propertyIndex); - return engine->throwTypeError(error); - } - - QObject *qmlScopeObj = static_cast(base)->d()->qml()->scopeObject; - ScopedValue qmlScopeValue(scope, QObjectWrapper::wrap(engine, qmlScopeObj)); - return fo->call(qmlScopeValue, argv, argc); -} - -ReturnedValue Runtime::method_callQmlContextObjectProperty(ExecutionEngine *engine, Value *base, - int propertyIndex, Value *argv, int argc) -{ - Scope scope(engine); - ScopedFunctionObject fo(scope, method_loadQmlContextObjectProperty(engine, *base, propertyIndex, - /*captureRequired*/true)); - if (!fo) { - QString error = QStringLiteral("Property '%1' of context object is not a function").arg(propertyIndex); - return engine->throwTypeError(error); - } - - QObject *qmlContextObj = static_cast(base)->d()->qml()->context->contextData()->contextObject; - ScopedValue qmlContextValue(scope, QObjectWrapper::wrap(engine, qmlContextObj)); - return fo->call(qmlContextValue, argv, argc); -} - struct CallArgs { Value *argv; int argc; @@ -1904,65 +1872,11 @@ QV4::ReturnedValue Runtime::method_createRestParameter(ExecutionEngine *engine, return engine->newArrayObject(values, nValues)->asReturnedValue(); } - -ReturnedValue Runtime::method_loadQmlContext(NoThrowEngine *engine) -{ - Heap::QmlContext *ctx = engine->qmlContext(); - Q_ASSERT(ctx); - return ctx->asReturnedValue(); -} - ReturnedValue Runtime::method_regexpLiteral(ExecutionEngine *engine, int id) { Heap::RegExpObject *ro = engine->newRegExpObject(engine->currentStackFrame->v4Function->compilationUnit->runtimeRegularExpressions[id].as()); return ro->asReturnedValue(); } - -ReturnedValue Runtime::method_loadQmlScopeObjectProperty(ExecutionEngine *engine, const Value &context, int propertyIndex, bool captureRequired) -{ - const QmlContext &c = static_cast(context); - return QV4::QObjectWrapper::getProperty(engine, c.d()->qml()->scopeObject, propertyIndex, captureRequired); -} - -ReturnedValue Runtime::method_loadQmlContextObjectProperty(ExecutionEngine *engine, const Value &context, int propertyIndex, bool captureRequired) -{ - const QmlContext &c = static_cast(context); - return QV4::QObjectWrapper::getProperty(engine, (*c.d()->qml()->context)->contextObject, propertyIndex, captureRequired); -} - -ReturnedValue Runtime::method_loadQmlIdObject(ExecutionEngine *engine, const Value &c, uint index) -{ - const QmlContext &qmlContext = static_cast(c); - QQmlContextData *context = *qmlContext.d()->qml()->context; - if (!context || index >= (uint)context->idValueCount) - return Encode::undefined(); - - QQmlEnginePrivate *ep = engine->qmlEngine() ? QQmlEnginePrivate::get(engine->qmlEngine()) : nullptr; - if (ep && ep->propertyCapture) - ep->propertyCapture->captureProperty(&context->idValues[index].bindings); - - return QObjectWrapper::wrap(engine, context->idValues[index].data()); -} - -void Runtime::method_storeQmlScopeObjectProperty(ExecutionEngine *engine, const Value &context, int propertyIndex, const Value &value) -{ - const QmlContext &c = static_cast(context); - return QV4::QObjectWrapper::setProperty(engine, c.d()->qml()->scopeObject, propertyIndex, value); -} - -void Runtime::method_storeQmlContextObjectProperty(ExecutionEngine *engine, const Value &context, int propertyIndex, const Value &value) -{ - const QmlContext &c = static_cast(context); - return QV4::QObjectWrapper::setProperty(engine, (*c.d()->qml()->context)->contextObject, propertyIndex, value); -} - -ReturnedValue Runtime::method_loadQmlImportedScripts(NoThrowEngine *engine) -{ - QQmlContextData *context = engine->callingQmlContext(); - if (!context) - return Encode::undefined(); - return context->importedScripts.value(); -} #endif // V4_BOOTSTRAP ReturnedValue Runtime::method_uMinus(const Value &value) diff --git a/src/qml/jsruntime/qv4runtimeapi_p.h b/src/qml/jsruntime/qv4runtimeapi_p.h index aa4ac27621..4b3905c56f 100644 --- a/src/qml/jsruntime/qv4runtimeapi_p.h +++ b/src/qml/jsruntime/qv4runtimeapi_p.h @@ -195,19 +195,7 @@ struct ExceptionCheck { F(Bool, compareInstanceof, (ExecutionEngine *engine, const Value &left, const Value &right)) \ F(Bool, compareIn, (ExecutionEngine *engine, const Value &left, const Value &right)) \ \ - F(ReturnedValue, regexpLiteral, (ExecutionEngine *engine, int id)) \ - \ - /* qml */ \ - F(ReturnedValue, loadQmlContext, (NoThrowEngine *engine)) \ - F(ReturnedValue, loadQmlImportedScripts, (NoThrowEngine *engine)) \ - F(ReturnedValue, loadQmlScopeObjectProperty, (ExecutionEngine *engine, const Value &context, int propertyIndex, bool captureRequired)) \ - F(ReturnedValue, loadQmlContextObjectProperty, (ExecutionEngine *engine, const Value &context, int propertyIndex, bool captureRequired)) \ - F(ReturnedValue, loadQmlIdObject, (ExecutionEngine *engine, const Value &context, uint index)) \ - F(ReturnedValue, callQmlScopeObjectProperty, (ExecutionEngine *engine, Value *base, int propertyIndex, Value *argv, int argc)) \ - F(ReturnedValue, callQmlContextObjectProperty, (ExecutionEngine *engine, Value *base, int propertyIndex, Value *argv, int argc)) \ - \ - F(void, storeQmlScopeObjectProperty, (ExecutionEngine *engine, const Value &context, int propertyIndex, const Value &value)) \ - F(void, storeQmlContextObjectProperty, (ExecutionEngine *engine, const Value &context, int propertyIndex, const Value &value)) \ + F(ReturnedValue, regexpLiteral, (ExecutionEngine *engine, int id)) struct Q_QML_PRIVATE_EXPORT Runtime { Runtime(); diff --git a/src/qml/jsruntime/qv4vme_moth.cpp b/src/qml/jsruntime/qv4vme_moth.cpp index 2d9337b03e..ea2217499f 100644 --- a/src/qml/jsruntime/qv4vme_moth.cpp +++ b/src/qml/jsruntime/qv4vme_moth.cpp @@ -646,37 +646,6 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, CHECK_EXCEPTION; MOTH_END_INSTR(StoreSuperProperty) - MOTH_BEGIN_INSTR(StoreScopeObjectProperty) - STORE_ACC(); - Runtime::method_storeQmlScopeObjectProperty(engine, STACK_VALUE(base), propertyIndex, accumulator); - CHECK_EXCEPTION; - MOTH_END_INSTR(StoreScopeObjectProperty) - - MOTH_BEGIN_INSTR(LoadScopeObjectProperty) - STORE_IP(); - acc = Runtime::method_loadQmlScopeObjectProperty(engine, STACK_VALUE(base), propertyIndex, captureRequired); - CHECK_EXCEPTION; - MOTH_END_INSTR(LoadScopeObjectProperty) - - MOTH_BEGIN_INSTR(StoreContextObjectProperty) - STORE_IP(); - STORE_ACC(); - Runtime::method_storeQmlContextObjectProperty(engine, STACK_VALUE(base), propertyIndex, accumulator); - CHECK_EXCEPTION; - MOTH_END_INSTR(StoreContextObjectProperty) - - MOTH_BEGIN_INSTR(LoadContextObjectProperty) - STORE_IP(); - acc = Runtime::method_loadQmlContextObjectProperty(engine, STACK_VALUE(base), propertyIndex, captureRequired); - CHECK_EXCEPTION; - MOTH_END_INSTR(LoadContextObjectProperty) - - MOTH_BEGIN_INSTR(LoadIdObject) - STORE_IP(); - acc = Runtime::method_loadQmlIdObject(engine, STACK_VALUE(base), index); - CHECK_EXCEPTION; - MOTH_END_INSTR(LoadIdObject) - MOTH_BEGIN_INSTR(Yield) frame->yield = code; frame->yieldIsIterator = false; @@ -794,18 +763,6 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, CHECK_EXCEPTION; MOTH_END_INSTR(CallQmlContextPropertyLookup) - MOTH_BEGIN_INSTR(CallScopeObjectProperty) - STORE_IP(); - acc = Runtime::method_callQmlScopeObjectProperty(engine, stack + base, name, stack + argv, argc); - CHECK_EXCEPTION; - MOTH_END_INSTR(CallScopeObjectProperty) - - MOTH_BEGIN_INSTR(CallContextObjectProperty) - STORE_IP(); - acc = Runtime::method_callQmlContextObjectProperty(engine, stack + base, name, stack + argv, argc); - CHECK_EXCEPTION; - MOTH_END_INSTR(CallContextObjectProperty) - MOTH_BEGIN_INSTR(CallWithSpread) STORE_IP(); acc = Runtime::method_callWithSpread(engine, STACK_VALUE(func), STACK_VALUE(thisObject), stack + argv, argc); @@ -1425,14 +1382,6 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, #endif // QT_CONFIG(qml_debug) MOTH_END_INSTR(Debug) - MOTH_BEGIN_INSTR(LoadQmlContext) - STACK_VALUE(result) = Runtime::method_loadQmlContext(static_cast(engine)); - MOTH_END_INSTR(LoadQmlContext) - - MOTH_BEGIN_INSTR(LoadQmlImportedScripts) - STACK_VALUE(result) = Runtime::method_loadQmlImportedScripts(static_cast(engine)); - MOTH_END_INSTR(LoadQmlImportedScripts) - handleUnwind: Q_ASSERT(engine->hasException || frame->unwindLevel); if (!frame->unwindHandler) { diff --git a/src/qml/qml/qqmlbinding.cpp b/src/qml/qml/qqmlbinding.cpp index eea3670191..7f0442d034 100644 --- a/src/qml/qml/qqmlbinding.cpp +++ b/src/qml/qml/qqmlbinding.cpp @@ -260,8 +260,6 @@ protected: } else { clearError(); } - - cancelPermanentGuards(); } ep->dereferenceScarceResources(); @@ -643,24 +641,22 @@ QVector QQmlBinding::dependencies() const if (!m_target.data()) return dependencies; - for (const auto &guardList : { permanentGuards, activeGuards }) { - for (QQmlJavaScriptExpressionGuard *guard = guardList.first(); guard; guard = guardList.next(guard)) { - if (guard->signalIndex() == -1) // guard's sender is a QQmlNotifier, not a QObject*. - continue; + for (QQmlJavaScriptExpressionGuard *guard = activeGuards.first(); guard; guard = activeGuards.next(guard)) { + if (guard->signalIndex() == -1) // guard's sender is a QQmlNotifier, not a QObject*. + continue; - QObject *senderObject = guard->senderAsObject(); - if (!senderObject) - continue; + QObject *senderObject = guard->senderAsObject(); + if (!senderObject) + continue; - const QMetaObject *senderMeta = senderObject->metaObject(); - if (!senderMeta) - continue; + const QMetaObject *senderMeta = senderObject->metaObject(); + if (!senderMeta) + continue; - for (int i = 0; i < senderMeta->propertyCount(); i++) { - QMetaProperty property = senderMeta->property(i); - if (property.notifySignalIndex() == QMetaObjectPrivate::signal(senderMeta, guard->signalIndex()).methodIndex()) { - dependencies.push_back(QQmlProperty(senderObject, QString::fromUtf8(senderObject->metaObject()->property(i).name()))); - } + for (int i = 0; i < senderMeta->propertyCount(); i++) { + QMetaProperty property = senderMeta->property(i); + if (property.notifySignalIndex() == QMetaObjectPrivate::signal(senderMeta, guard->signalIndex()).methodIndex()) { + dependencies.push_back(QQmlProperty(senderObject, QString::fromUtf8(senderObject->metaObject()->property(i).name()))); } } } @@ -670,7 +666,7 @@ QVector QQmlBinding::dependencies() const bool QQmlBinding::hasDependencies() const { - return !permanentGuards.isEmpty() || !activeGuards.isEmpty() || translationsCaptured(); + return !activeGuards.isEmpty() || translationsCaptured(); } class QObjectPointerBinding: public QQmlNonbindingBinding diff --git a/src/qml/qml/qqmljavascriptexpression.cpp b/src/qml/qml/qqmljavascriptexpression.cpp index 380163202a..9a3a5218e0 100644 --- a/src/qml/qml/qqmljavascriptexpression.cpp +++ b/src/qml/qml/qqmljavascriptexpression.cpp @@ -109,7 +109,6 @@ QQmlJavaScriptExpression::~QQmlJavaScriptExpression() } clearActiveGuards(); - clearPermanentGuards(); clearError(); if (m_scopeObject.isT2()) // notify DeleteWatcher of our deletion. m_scopeObject.asT2()->_s = nullptr; @@ -118,12 +117,8 @@ QQmlJavaScriptExpression::~QQmlJavaScriptExpression() void QQmlJavaScriptExpression::setNotifyOnValueChanged(bool v) { activeGuards.setFlagValue(v); - permanentGuards.setFlagValue(v); - if (!v) { + if (!v) clearActiveGuards(); - clearPermanentGuards(); - m_permanentDependenciesRegistered = false; - } } void QQmlJavaScriptExpression::resetNotifyOnValueChanged() @@ -216,10 +211,6 @@ QV4::ReturnedValue QQmlJavaScriptExpression::evaluate(QV4::CallData *callData, b QV4::ReturnedValue res = v4Function->call(&callData->thisObject, callData->args, callData->argc(), static_cast(m_qmlScope.valueRef())); QV4::Scope scope(v4); QV4::ScopedValue result(scope, res); - if (v4Function->hasQmlDependencies) { - QV4::Heap::QmlContext *qc = m_qmlScope.as()->d(); - QQmlPropertyCapture::registerQmlDependencies(qc, v4, v4Function->compiledFunction); - } if (scope.hasException()) { if (watcher.wasDeleted()) @@ -254,7 +245,7 @@ QV4::ReturnedValue QQmlJavaScriptExpression::evaluate(QV4::CallData *callData, b return result->asReturnedValue(); } -void QQmlPropertyCapture::captureProperty(QQmlNotifier *n, Duration duration) +void QQmlPropertyCapture::captureProperty(QQmlNotifier *n) { if (watcher->wasDeleted()) return; @@ -274,17 +265,14 @@ void QQmlPropertyCapture::captureProperty(QQmlNotifier *n, Duration duration) g->connect(n); } - if (duration == Permanently) - expression->permanentGuards.prepend(g); - else - expression->activeGuards.prepend(g); + expression->activeGuards.prepend(g); } /*! \internal \a n is in the signal index range (see QObjectPrivate::signalIndex()). */ -void QQmlPropertyCapture::captureProperty(QObject *o, int c, int n, Duration duration, bool doNotify) +void QQmlPropertyCapture::captureProperty(QObject *o, int c, int n, bool doNotify) { if (watcher->wasDeleted()) return; @@ -323,61 +311,8 @@ void QQmlPropertyCapture::captureProperty(QObject *o, int c, int n, Duration dur g->connect(o, n, engine, doNotify); } - if (duration == Permanently) - expression->permanentGuards.prepend(g); - else - expression->activeGuards.prepend(g); - } -} - -void QQmlPropertyCapture::registerQmlDependencies(QV4::Heap::QmlContext *context, const QV4::ExecutionEngine *engine, const QV4::CompiledData::Function *compiledFunction) -{ - // Let the caller check and avoid the function call :) - Q_ASSERT(compiledFunction->hasQmlDependencies()); - - QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine->qmlEngine()); - if (!ep) - return; - QQmlPropertyCapture *capture = ep->propertyCapture; - if (!capture || capture->watcher->wasDeleted()) - return; - - if (capture->expression->m_permanentDependenciesRegistered) - return; - - capture->expression->m_permanentDependenciesRegistered = true; - - QV4::Heap::QQmlContextWrapper *wrapper = context->qml(); - QQmlContextData *qmlContext = wrapper->context->contextData(); - - const quint32_le *idObjectDependency = compiledFunction->qmlIdObjectDependencyTable(); - const int idObjectDependencyCount = compiledFunction->nDependingIdObjects; - for (int i = 0; i < idObjectDependencyCount; ++i, ++idObjectDependency) { - Q_ASSERT(int(*idObjectDependency) < qmlContext->idValueCount); - capture->captureProperty(&qmlContext->idValues[*idObjectDependency].bindings, - QQmlPropertyCapture::Permanently); - } - - Q_ASSERT(qmlContext->contextObject); - const quint32_le *contextPropertyDependency = compiledFunction->qmlContextPropertiesDependencyTable(); - const int contextPropertyDependencyCount = compiledFunction->nDependingContextProperties; - for (int i = 0; i < contextPropertyDependencyCount; ++i) { - const int propertyIndex = *contextPropertyDependency++; - const int notifyIndex = *contextPropertyDependency++; - capture->captureProperty(qmlContext->contextObject, propertyIndex, notifyIndex, - QQmlPropertyCapture::Permanently); - } - - QObject *scopeObject = wrapper->scopeObject; - const quint32_le *scopePropertyDependency = compiledFunction->qmlScopePropertiesDependencyTable(); - const int scopePropertyDependencyCount = compiledFunction->nDependingScopeProperties; - for (int i = 0; i < scopePropertyDependencyCount; ++i) { - const int propertyIndex = *scopePropertyDependency++; - const int notifyIndex = *scopePropertyDependency++; - capture->captureProperty(scopeObject, propertyIndex, notifyIndex, - QQmlPropertyCapture::Permanently); + expression->activeGuards.prepend(g); } - } QQmlError QQmlJavaScriptExpression::error(QQmlEngine *engine) const @@ -471,13 +406,6 @@ void QQmlJavaScriptExpression::clearActiveGuards() g->Delete(); } -void QQmlJavaScriptExpression::clearPermanentGuards() -{ - m_permanentDependenciesRegistered = false; - while (QQmlJavaScriptExpressionGuard *g = permanentGuards.takeFirst()) - g->Delete(); -} - void QQmlJavaScriptExpressionGuard_callback(QQmlNotifierEndpoint *e, void **) { QQmlJavaScriptExpression *expression = diff --git a/src/qml/qml/qqmljavascriptexpression_p.h b/src/qml/qml/qqmljavascriptexpression_p.h index de3fba0774..453c8ab8a8 100644 --- a/src/qml/qml/qqmljavascriptexpression_p.h +++ b/src/qml/qml/qqmljavascriptexpression_p.h @@ -144,7 +144,6 @@ public: QQmlError error(QQmlEngine *) const; void clearError(); void clearActiveGuards(); - void clearPermanentGuards(); QQmlDelayedError *delayedError(); static QV4::ReturnedValue evalFunction(QQmlContextData *ctxt, QObject *scope, @@ -153,14 +152,6 @@ public: protected: void createQmlBinding(QQmlContextData *ctxt, QObject *scope, const QString &code, const QString &filename, quint16 line); - void cancelPermanentGuards() const - { - if (m_permanentDependenciesRegistered) { - for (QQmlJavaScriptExpressionGuard *it = permanentGuards.first(); it; it = permanentGuards.next(it)) - it->cancelNotify(); - } - } - void setupFunction(QV4::ExecutionContext *qmlContext, QV4::Function *f); void setCompilationUnit(const QQmlRefPointer &compilationUnit); @@ -169,7 +160,6 @@ protected: // activeGuards:flag2 - useSharedContext QBiPointer m_scopeObject; QForwardFieldList activeGuards; - QForwardFieldList permanentGuards; void setTranslationsCaptured(bool captured) { m_error.setFlagValue(captured); } bool translationsCaptured() const { return m_error.flag(); } @@ -186,7 +176,6 @@ private: QQmlContextData *m_context; QQmlJavaScriptExpression **m_prevExpression; QQmlJavaScriptExpression *m_nextExpression; - bool m_permanentDependenciesRegistered = false; QV4::PersistentValue m_qmlScope; QQmlRefPointer m_compilationUnit; @@ -204,14 +193,8 @@ public: Q_ASSERT(errorString == nullptr); } - enum Duration { - OnlyOnce, - Permanently - }; - - static void registerQmlDependencies(QV4::Heap::QmlContext *context, const QV4::ExecutionEngine *engine, const QV4::CompiledData::Function *compiledFunction); - void captureProperty(QQmlNotifier *, Duration duration = OnlyOnce); - void captureProperty(QObject *, int, int, Duration duration = OnlyOnce, bool doNotify = true); + void captureProperty(QQmlNotifier *); + void captureProperty(QObject *, int, int, bool doNotify = true); void captureTranslation() { translationCaptured = true; } QQmlEngine *engine; diff --git a/src/qml/types/qqmllistmodel.cpp b/src/qml/types/qqmllistmodel.cpp index 006825cc93..69b7876cf6 100644 --- a/src/qml/types/qqmllistmodel.cpp +++ b/src/qml/types/qqmllistmodel.cpp @@ -1605,8 +1605,7 @@ ReturnedValue ModelObject::virtualGet(const Managed *m, PropertyKey id, const Va if (QQmlEngine *qmlEngine = that->engine()->qmlEngine()) { QQmlEnginePrivate *ep = QQmlEnginePrivate::get(qmlEngine); if (ep && ep->propertyCapture) - ep->propertyCapture->captureProperty(that->object(), -1, role->index, - QQmlPropertyCapture::OnlyOnce, false); + ep->propertyCapture->captureProperty(that->object(), -1, role->index, /*doNotify=*/ false); } const int elementIndex = that->d()->elementIndex(); -- cgit v1.2.3 From 69d76d59cec0dcff4c52eef24e779fbef14beeca Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Tue, 19 Mar 2019 13:39:10 +0100 Subject: V4: Don't mark InternalClass::parent when garbage collecting The parent pointer is only kept so that we can update the parent's transitions when removing a child. There is no need to keep the parents alive for the children. Fixes: QTBUG-58559 Change-Id: Ia28183966bde6d478ca030fe11195489925dfc13 Reviewed-by: Simon Hausmann --- src/qml/jsruntime/qv4internalclass.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4internalclass.cpp b/src/qml/jsruntime/qv4internalclass.cpp index ddb8542e07..9906e2b1a0 100644 --- a/src/qml/jsruntime/qv4internalclass.cpp +++ b/src/qml/jsruntime/qv4internalclass.cpp @@ -257,11 +257,15 @@ void InternalClass::init(Heap::InternalClass *other) void InternalClass::destroy() { -#ifndef QT_NO_DEBUG for (const auto &t : transitions) { - Q_ASSERT(!t.lookup || !t.lookup->isMarked()); - } + if (t.lookup) { +#ifndef QT_NO_DEBUG + Q_ASSERT(t.lookup->parent == this); #endif + t.lookup->parent = nullptr; + } + } + if (parent && parent->engine && parent->isMarked()) parent->removeChildEntry(this); @@ -659,8 +663,6 @@ void InternalClass::markObjects(Heap::Base *b, MarkStack *stack) Heap::InternalClass *ic = static_cast(b); if (ic->prototype) ic->prototype->mark(stack); - if (ic->parent) - ic->parent->mark(stack); ic->nameMap.mark(stack); } -- cgit v1.2.3 From 710580aa84c5c3273e883d9e650f3159ad8250f3 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Thu, 3 Jan 2019 17:00:36 +0100 Subject: Accelerate access to singletons and imported scripts Use a dedicated lookup type to provide super fast access to engine wide singleton objects as well as scripts Task-number: QTBUG-69898 Change-Id: Ie430f48f6576a9171018ef18742dcf6b2adb4310 Reviewed-by: Ulf Hermann --- src/qml/jsruntime/qv4lookup_p.h | 9 ++++++ src/qml/jsruntime/qv4qmlcontext.cpp | 55 +++++++++++++++++++++++++++++++++++-- src/qml/jsruntime/qv4qmlcontext_p.h | 4 ++- 3 files changed, 65 insertions(+), 3 deletions(-) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4lookup_p.h b/src/qml/jsruntime/qv4lookup_p.h index e4d9ad8328..2384a1194e 100644 --- a/src/qml/jsruntime/qv4lookup_p.h +++ b/src/qml/jsruntime/qv4lookup_p.h @@ -129,6 +129,15 @@ struct Lookup { QQmlPropertyCache *propertyCache; QQmlPropertyData *propertyData; } qgadgetLookup; + struct { + quintptr unused1; + quintptr unused2; + int scriptIndex; + } qmlContextScriptLookup; + struct { + Heap::Object *singleton; + quintptr unused; + } qmlContextSingletonLookup; }; uint nameIndex; diff --git a/src/qml/jsruntime/qv4qmlcontext.cpp b/src/qml/jsruntime/qv4qmlcontext.cpp index ae458c604a..ab80e6bc45 100644 --- a/src/qml/jsruntime/qv4qmlcontext.cpp +++ b/src/qml/jsruntime/qv4qmlcontext.cpp @@ -79,7 +79,7 @@ void Heap::QQmlContextWrapper::destroy() Object::destroy(); } -ReturnedValue QQmlContextWrapper::getPropertyAndBase(const QQmlContextWrapper *resource, PropertyKey id, const Value *receiver, bool *hasProperty, Value *base) +ReturnedValue QQmlContextWrapper::getPropertyAndBase(const QQmlContextWrapper *resource, PropertyKey id, const Value *receiver, bool *hasProperty, Value *base, Lookup *lookup) { if (!id.isString()) return Object::virtualGet(resource, id, receiver, hasProperty); @@ -159,6 +159,8 @@ ReturnedValue QQmlContextWrapper::getPropertyAndBase(const QQmlContextWrapper *r // Note: The scope object is only a QADMO for example when somebody registers a QQmlPropertyMap // sub-class as QML type and then instantiates it in .qml. if (scopeObject && QQmlPropertyCache::isDynamicMetaObject(scopeObject->metaObject())) { + // all bets are off, so don't try to optimize any lookups + lookup = nullptr; if (performGobalLookUp()) return result->asReturnedValue(); } @@ -171,11 +173,35 @@ ReturnedValue QQmlContextWrapper::getPropertyAndBase(const QQmlContextWrapper *r if (hasProperty) *hasProperty = true; if (r.scriptIndex != -1) { + if (lookup) { + lookup->qmlContextScriptLookup.scriptIndex = r.scriptIndex; + lookup->qmlContextPropertyGetter = QQmlContextWrapper::lookupScript; + return lookup->qmlContextPropertyGetter(lookup, v4, base); + } QV4::ScopedObject scripts(scope, context->importedScripts.valueRef()); if (scripts) return scripts->get(r.scriptIndex); return QV4::Encode::null(); } else if (r.type.isValid()) { + if (lookup) { + if (r.type.isSingleton()) { + QQmlEngine *e = v4->qmlEngine(); + QQmlType::SingletonInstanceInfo *siinfo = r.type.singletonInstanceInfo(); + siinfo->init(e); + if (siinfo->qobjectApi(e)) { + lookup->qmlContextSingletonLookup.singleton = + static_cast( + Value::fromReturnedValue( + QQmlTypeWrapper::create(v4, nullptr, r.type) + ).heapObject()); + } else { + QV4::ScopedObject o(scope, QJSValuePrivate::convertedToValue(v4, siinfo->scriptApi(e))); + lookup->qmlContextSingletonLookup.singleton = o->d(); + } + lookup->qmlContextPropertyGetter = QQmlContextWrapper::lookupSingleton; + return lookup->qmlContextPropertyGetter(lookup, v4, base); + } + } return QQmlTypeWrapper::create(v4, scopeObject, r.type); } else if (r.importNamespace) { return QQmlTypeWrapper::create(v4, scopeObject, context->imports, r.importNamespace); @@ -361,12 +387,37 @@ ReturnedValue QQmlContextWrapper::resolveQmlContextPropertyLookupGetter(Lookup * Scoped qmlContext(scope, engine->qmlContext()->qml()); bool hasProperty = false; ScopedValue result(scope, QQmlContextWrapper::getPropertyAndBase(qmlContext, name, /*receiver*/nullptr, - &hasProperty, base)); + &hasProperty, base, l)); if (!hasProperty) return engine->throwReferenceError(name.toQString()); return result->asReturnedValue(); } +ReturnedValue QQmlContextWrapper::lookupScript(Lookup *l, ExecutionEngine *engine, Value *base) +{ + Q_UNUSED(base) + Scope scope(engine); + Scoped qmlContext(scope, engine->qmlContext()); + if (!qmlContext) + return QV4::Encode::null(); + + QQmlContextData *context = qmlContext->qmlContext(); + if (!context) + return QV4::Encode::null(); + + QV4::ScopedObject scripts(scope, context->importedScripts.valueRef()); + if (!scripts) + return QV4::Encode::null(); + return scripts->get(l->qmlContextScriptLookup.scriptIndex); +} + +ReturnedValue QQmlContextWrapper::lookupSingleton(Lookup *l, ExecutionEngine *engine, Value *base) +{ + Q_UNUSED(engine) + Q_UNUSED(base) + return Value::fromHeapObject(l->qmlContextSingletonLookup.singleton).asReturnedValue(); +} + void Heap::QmlContext::init(QV4::ExecutionContext *outerContext, QV4::QQmlContextWrapper *qml) { Heap::ExecutionContext::init(Heap::ExecutionContext::Type_QmlContext); diff --git a/src/qml/jsruntime/qv4qmlcontext_p.h b/src/qml/jsruntime/qv4qmlcontext_p.h index f704280bf4..5eacea5830 100644 --- a/src/qml/jsruntime/qv4qmlcontext_p.h +++ b/src/qml/jsruntime/qv4qmlcontext_p.h @@ -100,11 +100,13 @@ struct Q_QML_EXPORT QQmlContextWrapper : Object inline QQmlContextData *getContext() const { return *d()->context; } static ReturnedValue getPropertyAndBase(const QQmlContextWrapper *resource, PropertyKey id, const Value *receiver, - bool *hasProperty, Value *base); + bool *hasProperty, Value *base, Lookup *lookup = nullptr); static ReturnedValue virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty); static bool virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver); static ReturnedValue resolveQmlContextPropertyLookupGetter(Lookup *l, ExecutionEngine *engine, Value *base); + static ReturnedValue lookupScript(Lookup *l, ExecutionEngine *engine, Value *base); + static ReturnedValue lookupSingleton(Lookup *l, ExecutionEngine *engine, Value *base); }; struct Q_QML_EXPORT QmlContext : public ExecutionContext -- cgit v1.2.3 From 78a2887d8c5d866fc430f197abea6bb31293c4b1 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Fri, 4 Jan 2019 10:41:02 +0100 Subject: Accelerate access to id objects in lookups Task-number: QTBUG-69898 Change-Id: Ifbd9b3bf8d4b0c82b4c3933912e61eea8e0bb987 Reviewed-by: Ulf Hermann --- src/qml/jsruntime/qv4lookup_p.h | 5 +++++ src/qml/jsruntime/qv4qmlcontext.cpp | 35 +++++++++++++++++++++++++++++++++-- src/qml/jsruntime/qv4qmlcontext_p.h | 1 + 3 files changed, 39 insertions(+), 2 deletions(-) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4lookup_p.h b/src/qml/jsruntime/qv4lookup_p.h index 2384a1194e..68da224273 100644 --- a/src/qml/jsruntime/qv4lookup_p.h +++ b/src/qml/jsruntime/qv4lookup_p.h @@ -138,6 +138,11 @@ struct Lookup { Heap::Object *singleton; quintptr unused; } qmlContextSingletonLookup; + struct { + quintptr unused1; + quintptr unused2; + int objectId; + } qmlContextIdObjectLookup; }; uint nameIndex; diff --git a/src/qml/jsruntime/qv4qmlcontext.cpp b/src/qml/jsruntime/qv4qmlcontext.cpp index ab80e6bc45..3d9e1cc0f8 100644 --- a/src/qml/jsruntime/qv4qmlcontext.cpp +++ b/src/qml/jsruntime/qv4qmlcontext.cpp @@ -223,11 +223,17 @@ ReturnedValue QQmlContextWrapper::getPropertyAndBase(const QQmlContextWrapper *r if (propertyIdx != -1) { if (propertyIdx < context->idValueCount) { + if (hasProperty) + *hasProperty = true; + + if (lookup) { + lookup->qmlContextIdObjectLookup.objectId = propertyIdx; + lookup->qmlContextPropertyGetter = QQmlContextWrapper::lookupIdObject; + return lookup->qmlContextPropertyGetter(lookup, v4, base); + } if (ep->propertyCapture) ep->propertyCapture->captureProperty(&context->idValues[propertyIdx].bindings); - if (hasProperty) - *hasProperty = true; return QV4::QObjectWrapper::wrap(v4, context->idValues[propertyIdx]); } else { @@ -281,6 +287,10 @@ ReturnedValue QQmlContextWrapper::getPropertyAndBase(const QQmlContextWrapper *r } context = context->parent; + + // As the hierarchy of contexts is not stable, we can't do accelerated lookups beyond + // the immediate QML context (of the .qml file). + lookup = nullptr; } // Do a lookup in the global object here to avoid expressionContext->unresolvedNames becoming @@ -418,6 +428,27 @@ ReturnedValue QQmlContextWrapper::lookupSingleton(Lookup *l, ExecutionEngine *en return Value::fromHeapObject(l->qmlContextSingletonLookup.singleton).asReturnedValue(); } +ReturnedValue QQmlContextWrapper::lookupIdObject(Lookup *l, ExecutionEngine *engine, Value *base) +{ + Q_UNUSED(base) + Scope scope(engine); + Scoped qmlContext(scope, engine->qmlContext()); + if (!qmlContext) + return QV4::Encode::null(); + + QQmlContextData *context = qmlContext->qmlContext(); + if (!context) + return QV4::Encode::null(); + + QQmlEnginePrivate *qmlEngine = QQmlEnginePrivate::get(engine->qmlEngine()); + const int objectId = l->qmlContextIdObjectLookup.objectId; + + if (qmlEngine->propertyCapture) + qmlEngine->propertyCapture->captureProperty(&context->idValues[objectId].bindings); + + return QV4::QObjectWrapper::wrap(engine, context->idValues[objectId]); +} + void Heap::QmlContext::init(QV4::ExecutionContext *outerContext, QV4::QQmlContextWrapper *qml) { Heap::ExecutionContext::init(Heap::ExecutionContext::Type_QmlContext); diff --git a/src/qml/jsruntime/qv4qmlcontext_p.h b/src/qml/jsruntime/qv4qmlcontext_p.h index 5eacea5830..caf9281540 100644 --- a/src/qml/jsruntime/qv4qmlcontext_p.h +++ b/src/qml/jsruntime/qv4qmlcontext_p.h @@ -107,6 +107,7 @@ struct Q_QML_EXPORT QQmlContextWrapper : Object static ReturnedValue resolveQmlContextPropertyLookupGetter(Lookup *l, ExecutionEngine *engine, Value *base); static ReturnedValue lookupScript(Lookup *l, ExecutionEngine *engine, Value *base); static ReturnedValue lookupSingleton(Lookup *l, ExecutionEngine *engine, Value *base); + static ReturnedValue lookupIdObject(Lookup *l, ExecutionEngine *engine, Value *base); }; struct Q_QML_EXPORT QmlContext : public ExecutionContext -- cgit v1.2.3 From bb34abcc05d11f23c329f4c52aab638d991c1b98 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Fri, 4 Jan 2019 15:32:33 +0100 Subject: Fix lookup fallback into the global object in QML bindings This change addresses in particular qmlbench's fib10.qml, where usage of properties of the global object needs to go be accelerated in order to avoid regressing in performance. Task-number: QTBUG-69898 Change-Id: Ic43c64f4dd5459c4e92f87f03235ea836f971515 Reviewed-by: Ulf Hermann --- src/qml/jsruntime/qv4lookup_p.h | 7 +++++++ src/qml/jsruntime/qv4qmlcontext.cpp | 33 +++++++++++++++++++++++++++++++-- src/qml/jsruntime/qv4qmlcontext_p.h | 1 + 3 files changed, 39 insertions(+), 2 deletions(-) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4lookup_p.h b/src/qml/jsruntime/qv4lookup_p.h index 68da224273..ee6da21121 100644 --- a/src/qml/jsruntime/qv4lookup_p.h +++ b/src/qml/jsruntime/qv4lookup_p.h @@ -143,6 +143,13 @@ struct Lookup { quintptr unused2; int objectId; } qmlContextIdObjectLookup; + struct { + // Same as protoLookup, as used for global lookups + quintptr reserved1; + quintptr reserved2; + quintptr reserved3; + ReturnedValue (*getterTrampoline)(Lookup *l, ExecutionEngine *engine); + } qmlContextGlobalLookup; }; uint nameIndex; diff --git a/src/qml/jsruntime/qv4qmlcontext.cpp b/src/qml/jsruntime/qv4qmlcontext.cpp index 3d9e1cc0f8..e728cfb457 100644 --- a/src/qml/jsruntime/qv4qmlcontext.cpp +++ b/src/qml/jsruntime/qv4qmlcontext.cpp @@ -213,6 +213,7 @@ ReturnedValue QQmlContextWrapper::getPropertyAndBase(const QQmlContextWrapper *r } QQmlEnginePrivate *ep = QQmlEnginePrivate::get(v4->qmlEngine()); + Lookup * const originalLookup = lookup; while (context) { // Search context properties @@ -295,8 +296,24 @@ ReturnedValue QQmlContextWrapper::getPropertyAndBase(const QQmlContextWrapper *r // Do a lookup in the global object here to avoid expressionContext->unresolvedNames becoming // true if we access properties of the global object. - if (performGobalLookUp()) - return result->asReturnedValue(); + if (originalLookup) { + // Try a lookup in the global object. It's theoretically possible to first find a property + // in the global object and then later a context property with the same name is added, but that + // never really worked as we used to detect access to global properties at type compile time anyway. + lookup = originalLookup; + result = lookup->resolveGlobalGetter(v4); + if (lookup->globalGetter != Lookup::globalGetterGeneric) { + if (hasProperty) + *hasProperty = true; + lookup->qmlContextGlobalLookup.getterTrampoline = lookup->globalGetter; + lookup->qmlContextPropertyGetter = QQmlContextWrapper::lookupInGlobalObject; + return result->asReturnedValue(); + } + lookup->qmlContextPropertyGetter = QQmlContextWrapper::resolveQmlContextPropertyLookupGetter; + } else { + if (performGobalLookUp()) + return result->asReturnedValue(); + } expressionContext->unresolvedNames = true; @@ -449,6 +466,18 @@ ReturnedValue QQmlContextWrapper::lookupIdObject(Lookup *l, ExecutionEngine *eng return QV4::QObjectWrapper::wrap(engine, context->idValues[objectId]); } +ReturnedValue QQmlContextWrapper::lookupInGlobalObject(Lookup *l, ExecutionEngine *engine, Value *base) +{ + Q_UNUSED(base); + ReturnedValue result = l->qmlContextGlobalLookup.getterTrampoline(l, engine); + // In the unlikely event of mutation of the global object, update the trampoline. + if (l->qmlContextPropertyGetter != lookupInGlobalObject) { + l->qmlContextGlobalLookup.getterTrampoline = l->globalGetter; + l->qmlContextPropertyGetter = QQmlContextWrapper::lookupInGlobalObject; + } + return result; +} + void Heap::QmlContext::init(QV4::ExecutionContext *outerContext, QV4::QQmlContextWrapper *qml) { Heap::ExecutionContext::init(Heap::ExecutionContext::Type_QmlContext); diff --git a/src/qml/jsruntime/qv4qmlcontext_p.h b/src/qml/jsruntime/qv4qmlcontext_p.h index caf9281540..cfcc53c7c9 100644 --- a/src/qml/jsruntime/qv4qmlcontext_p.h +++ b/src/qml/jsruntime/qv4qmlcontext_p.h @@ -108,6 +108,7 @@ struct Q_QML_EXPORT QQmlContextWrapper : Object static ReturnedValue lookupScript(Lookup *l, ExecutionEngine *engine, Value *base); static ReturnedValue lookupSingleton(Lookup *l, ExecutionEngine *engine, Value *base); static ReturnedValue lookupIdObject(Lookup *l, ExecutionEngine *engine, Value *base); + static ReturnedValue lookupInGlobalObject(Lookup *l, ExecutionEngine *engine, Value *base); }; struct Q_QML_EXPORT QmlContext : public ExecutionContext -- cgit v1.2.3 From f9dac6f900fde93014305854b1bf3a81d9e58b92 Mon Sep 17 00:00:00 2001 From: Michael Brasser Date: Thu, 3 Jan 2019 16:28:16 -0600 Subject: Accelerate lookup of singleton properties Task-number: QTBUG-69898 Change-Id: Id03ba543fa293da2690099c3e6f94b2725de562f Reviewed-by: Ulf Hermann --- src/qml/jsruntime/qv4lookup_p.h | 3 +- src/qml/jsruntime/qv4qobjectwrapper.cpp | 4 ++- src/qml/qml/qqmltypewrapper.cpp | 61 +++++++++++++++++++++++++++++++++ src/qml/qml/qqmltypewrapper_p.h | 3 ++ 4 files changed, 69 insertions(+), 2 deletions(-) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4lookup_p.h b/src/qml/jsruntime/qv4lookup_p.h index ee6da21121..03dc5f6d3c 100644 --- a/src/qml/jsruntime/qv4lookup_p.h +++ b/src/qml/jsruntime/qv4lookup_p.h @@ -71,6 +71,7 @@ struct Lookup { ReturnedValue (*qmlContextPropertyGetter)(Lookup *l, ExecutionEngine *engine, Value *thisObject); bool (*setter)(Lookup *l, ExecutionEngine *engine, Value &object, const Value &v); }; + // NOTE: gc assumes the first two entries in the struct are pointers to heap objects or null union { struct { Heap::Base *h1; @@ -119,7 +120,7 @@ struct Lookup { } indexedLookup; struct { Heap::InternalClass *ic; - quintptr unused; + Heap::QObjectWrapper *staticQObject; QQmlPropertyCache *propertyCache; QQmlPropertyData *propertyData; } qobjectLookup; diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index 053581226f..351076ac28 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -867,6 +867,7 @@ ReturnedValue QObjectWrapper::virtualResolveLookupGetter(const Object *object, E } lookup->qobjectLookup.ic = This->internalClass(); + lookup->qobjectLookup.staticQObject = nullptr; lookup->qobjectLookup.propertyCache = ddata->propertyCache; lookup->qobjectLookup.propertyCache->addref(); lookup->qobjectLookup.propertyData = property; @@ -889,7 +890,8 @@ ReturnedValue QObjectWrapper::lookupGetter(Lookup *lookup, ExecutionEngine *engi if (!o || o->internalClass != lookup->qobjectLookup.ic) return revertLookup(); - const Heap::QObjectWrapper *This = static_cast(o); + const Heap::QObjectWrapper *This = lookup->qobjectLookup.staticQObject ? lookup->qobjectLookup.staticQObject : + static_cast(o); QObject *qobj = This->object(); if (QQmlData::wasDeleted(qobj)) return QV4::Encode::undefined(); diff --git a/src/qml/qml/qqmltypewrapper.cpp b/src/qml/qml/qqmltypewrapper.cpp index 4089a7f030..d30c225741 100644 --- a/src/qml/qml/qqmltypewrapper.cpp +++ b/src/qml/qml/qqmltypewrapper.cpp @@ -47,6 +47,8 @@ #include #include #include +#include +#include QT_BEGIN_NAMESPACE @@ -169,6 +171,7 @@ static ReturnedValue throwLowercaseEnumError(QV4::ExecutionEngine *v4, String *n ReturnedValue QQmlTypeWrapper::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) { + // Keep this code in sync with ::virtualResolveLookupGetter Q_ASSERT(m->as()); if (!id.isString()) @@ -425,6 +428,64 @@ ReturnedValue QQmlTypeWrapper::virtualInstanceOf(const Object *typeObject, const return QV4::Encode(QQmlMetaObject::canConvert(theirType, myQmlType)); } +ReturnedValue QQmlTypeWrapper::virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup) +{ + // Keep this code in sync with ::virtualGet + PropertyKey id = engine->identifierTable->asPropertyKey(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[lookup->nameIndex]); + if (!id.isString()) + return Object::virtualResolveLookupGetter(object, engine, lookup); + Scope scope(engine); + + const QQmlTypeWrapper *This = static_cast(object); + ScopedString name(scope, id.asStringOrSymbol()); + QQmlContextData *qmlContext = engine->callingQmlContext(); + + Scoped w(scope, static_cast(This)); + QQmlType type = w->d()->type(); + + if (type.isValid()) { + + if (type.isSingleton()) { + QQmlEngine *e = engine->qmlEngine(); + QQmlType::SingletonInstanceInfo *siinfo = type.singletonInstanceInfo(); + siinfo->init(e); + + QObject *qobjectSingleton = siinfo->qobjectApi(e); + if (qobjectSingleton) { + + const bool includeEnums = w->d()->mode == Heap::QQmlTypeWrapper::IncludeEnums; + if (!includeEnums || !name->startsWithUpper()) { + QQmlData *ddata = QQmlData::get(qobjectSingleton, false); + if (ddata && ddata->propertyCache) { + ScopedValue val(scope, Value::fromReturnedValue(QV4::QObjectWrapper::wrap(engine, qobjectSingleton))); + QQmlPropertyData *property = ddata->propertyCache->property(name.getPointer(), qobjectSingleton, qmlContext); + if (property) { + lookup->qobjectLookup.ic = This->internalClass(); + lookup->qobjectLookup.staticQObject = static_cast(val->heapObject()); + lookup->qobjectLookup.propertyCache = ddata->propertyCache; + lookup->qobjectLookup.propertyCache->addref(); + lookup->qobjectLookup.propertyData = property; + lookup->getter = QV4::QObjectWrapper::lookupGetter; + return lookup->getter(lookup, engine, *This); + } + // Fall through to base implementation + } + // Fall through to base implementation + } + // Fall through to base implementation + } + // Fall through to base implementation + } + // Fall through to base implementation + } + return QV4::Object::virtualResolveLookupGetter(object, engine, lookup); +} + +bool QQmlTypeWrapper::virtualResolveLookupSetter(Object *object, ExecutionEngine *engine, Lookup *lookup, const Value &value) +{ + return Object::virtualResolveLookupSetter(object, engine, lookup, value); +} + void Heap::QQmlScopedEnumWrapper::destroy() { QQmlType::derefHandle(typePrivate); diff --git a/src/qml/qml/qqmltypewrapper_p.h b/src/qml/qml/qqmltypewrapper_p.h index fa1ad56902..44e82dec2b 100644 --- a/src/qml/qml/qqmltypewrapper_p.h +++ b/src/qml/qml/qqmltypewrapper_p.h @@ -111,6 +111,9 @@ struct Q_QML_EXPORT QQmlTypeWrapper : Object static ReturnedValue create(ExecutionEngine *, QObject *, const QQmlRefPointer &, const QQmlImportRef *, Heap::QQmlTypeWrapper::TypeNameMode = Heap::QQmlTypeWrapper::IncludeEnums); + static ReturnedValue virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup); + static bool virtualResolveLookupSetter(Object *object, ExecutionEngine *engine, Lookup *lookup, const Value &value); + protected: static ReturnedValue virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty); static bool virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver); -- cgit v1.2.3 From bcbce96fffd25a4f2810f03cec060ab13e34ac9e Mon Sep 17 00:00:00 2001 From: Michael Brasser Date: Fri, 4 Jan 2019 13:09:21 -0600 Subject: Accelerate access to scope object properties in lookups Task-number: QTBUG-69898 Change-Id: I94bf1aa85c9b2302894f3224e41de81a456211f9 Reviewed-by: Ulf Hermann --- src/qml/compiler/qv4compileddata.cpp | 5 ++++ src/qml/jsruntime/qv4qmlcontext.cpp | 45 ++++++++++++++++++++++++++++++++- src/qml/jsruntime/qv4qmlcontext_p.h | 1 + src/qml/jsruntime/qv4qobjectwrapper.cpp | 24 ++++-------------- src/qml/jsruntime/qv4qobjectwrapper_p.h | 45 ++++++++++++++++++++++++++++++++- 5 files changed, 99 insertions(+), 21 deletions(-) (limited to 'src/qml') diff --git a/src/qml/compiler/qv4compileddata.cpp b/src/qml/compiler/qv4compileddata.cpp index 6701443971..7906b3572c 100644 --- a/src/qml/compiler/qv4compileddata.cpp +++ b/src/qml/compiler/qv4compileddata.cpp @@ -284,6 +284,11 @@ void CompilationUnit::unlink() if (QQmlPropertyCache *pc = l.qgadgetLookup.propertyCache) pc->release(); } + + if (l.qmlContextPropertyGetter == QQmlContextWrapper::lookupScopeObjectProperty) { + if (QQmlPropertyCache *pc = l.qobjectLookup.propertyCache) + pc->release(); + } } } diff --git a/src/qml/jsruntime/qv4qmlcontext.cpp b/src/qml/jsruntime/qv4qmlcontext.cpp index e728cfb457..9bdfa10030 100644 --- a/src/qml/jsruntime/qv4qmlcontext.cpp +++ b/src/qml/jsruntime/qv4qmlcontext.cpp @@ -261,13 +261,30 @@ ReturnedValue QQmlContextWrapper::getPropertyAndBase(const QQmlContextWrapper *r // Search scope object if (scopeObject) { bool hasProp = false; + + QQmlPropertyData *propertyData = nullptr; QV4::ScopedValue result(scope, QV4::QObjectWrapper::getQmlProperty(v4, context, scopeObject, - name, QV4::QObjectWrapper::CheckRevision, &hasProp)); + name, QV4::QObjectWrapper::CheckRevision, &hasProp, &propertyData)); if (hasProp) { if (hasProperty) *hasProperty = true; if (base) *base = QV4::QObjectWrapper::wrap(v4, scopeObject); + + if (lookup && propertyData) { + QQmlData *ddata = QQmlData::get(scopeObject, false); + if (ddata && ddata->propertyCache) { + ScopedValue val(scope, base ? *base : Value::fromReturnedValue(QV4::QObjectWrapper::wrap(v4, scopeObject))); + const QObjectWrapper *That = static_cast(val->objectValue()); + lookup->qobjectLookup.ic = That->internalClass(); + lookup->qobjectLookup.staticQObject = nullptr; + lookup->qobjectLookup.propertyCache = ddata->propertyCache; + lookup->qobjectLookup.propertyCache->addref(); + lookup->qobjectLookup.propertyData = propertyData; + lookup->qmlContextPropertyGetter = QQmlContextWrapper::lookupScopeObjectProperty; + } + } + return result->asReturnedValue(); } } @@ -466,6 +483,32 @@ ReturnedValue QQmlContextWrapper::lookupIdObject(Lookup *l, ExecutionEngine *eng return QV4::QObjectWrapper::wrap(engine, context->idValues[objectId]); } +ReturnedValue QQmlContextWrapper::lookupScopeObjectProperty(Lookup *l, ExecutionEngine *engine, Value *base) +{ + Q_UNUSED(base) + Scope scope(engine); + Scoped qmlContext(scope, engine->qmlContext()); + if (!qmlContext) + return QV4::Encode::undefined(); + + QObject *scopeObject = qmlContext->qmlScope(); + if (!scopeObject) + return QV4::Encode::undefined(); + + if (QQmlData::wasDeleted(scopeObject)) + return QV4::Encode::undefined(); + + const auto revertLookup = [l, engine, base]() { + l->qobjectLookup.propertyCache->release(); + l->qobjectLookup.propertyCache = nullptr; + l->qmlContextPropertyGetter = QQmlContextWrapper::resolveQmlContextPropertyLookupGetter; + return QQmlContextWrapper::resolveQmlContextPropertyLookupGetter(l, engine, base); + }; + + ScopedValue obj(scope, QV4::QObjectWrapper::wrap(engine, scopeObject)); + return QObjectWrapper::lookupGetterImpl(l, engine, obj, /*useOriginalProperty*/ true, revertLookup); +} + ReturnedValue QQmlContextWrapper::lookupInGlobalObject(Lookup *l, ExecutionEngine *engine, Value *base) { Q_UNUSED(base); diff --git a/src/qml/jsruntime/qv4qmlcontext_p.h b/src/qml/jsruntime/qv4qmlcontext_p.h index cfcc53c7c9..83bf2a1d3e 100644 --- a/src/qml/jsruntime/qv4qmlcontext_p.h +++ b/src/qml/jsruntime/qv4qmlcontext_p.h @@ -108,6 +108,7 @@ struct Q_QML_EXPORT QQmlContextWrapper : Object static ReturnedValue lookupScript(Lookup *l, ExecutionEngine *engine, Value *base); static ReturnedValue lookupSingleton(Lookup *l, ExecutionEngine *engine, Value *base); static ReturnedValue lookupIdObject(Lookup *l, ExecutionEngine *engine, Value *base); + static ReturnedValue lookupScopeObjectProperty(Lookup *l, ExecutionEngine *engine, Value *base); static ReturnedValue lookupInGlobalObject(Lookup *l, ExecutionEngine *engine, Value *base); }; diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index 351076ac28..377c30617a 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -357,7 +357,7 @@ ReturnedValue QObjectWrapper::getQmlProperty(QQmlContextData *qmlContext, String return getProperty(v4, d()->object(), result); } -ReturnedValue QObjectWrapper::getQmlProperty(QV4::ExecutionEngine *engine, QQmlContextData *qmlContext, QObject *object, String *name, QObjectWrapper::RevisionMode revisionMode, bool *hasProperty) +ReturnedValue QObjectWrapper::getQmlProperty(QV4::ExecutionEngine *engine, QQmlContextData *qmlContext, QObject *object, String *name, QObjectWrapper::RevisionMode revisionMode, bool *hasProperty, QQmlPropertyData **property) { if (QQmlData::wasDeleted(object)) { if (hasProperty) @@ -384,6 +384,9 @@ ReturnedValue QObjectWrapper::getQmlProperty(QV4::ExecutionEngine *engine, QQmlC if (hasProperty) *hasProperty = true; + if (property) + *property = result; + return getProperty(engine, object, result); } else { // Check if this object is already wrapped. @@ -884,24 +887,7 @@ ReturnedValue QObjectWrapper::lookupGetter(Lookup *lookup, ExecutionEngine *engi return Lookup::getterGeneric(lookup, engine, object); }; - // we can safely cast to a QV4::Object here. If object is something else, - // the internal class won't match - Heap::Object *o = static_cast(object.heapObject()); - if (!o || o->internalClass != lookup->qobjectLookup.ic) - return revertLookup(); - - const Heap::QObjectWrapper *This = lookup->qobjectLookup.staticQObject ? lookup->qobjectLookup.staticQObject : - static_cast(o); - QObject *qobj = This->object(); - if (QQmlData::wasDeleted(qobj)) - return QV4::Encode::undefined(); - - QQmlData *ddata = QQmlData::get(qobj, /*create*/false); - if (!ddata || ddata->propertyCache != lookup->qobjectLookup.propertyCache) - return revertLookup(); - - QQmlPropertyData *property = lookup->qobjectLookup.propertyData; - return getProperty(engine, qobj, property); + return lookupGetterImpl(lookup, engine, object, /*useOriginalProperty*/ false, revertLookup); } bool QObjectWrapper::virtualResolveLookupSetter(Object *object, ExecutionEngine *engine, Lookup *lookup, diff --git a/src/qml/jsruntime/qv4qobjectwrapper_p.h b/src/qml/jsruntime/qv4qobjectwrapper_p.h index 471f352c2a..2558ede401 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper_p.h +++ b/src/qml/jsruntime/qv4qobjectwrapper_p.h @@ -62,6 +62,7 @@ #include #include +#include QT_BEGIN_NAMESPACE @@ -167,7 +168,7 @@ struct Q_QML_EXPORT QObjectWrapper : public Object QObject *object() const { return d()->object(); } ReturnedValue getQmlProperty(QQmlContextData *qmlContext, String *name, RevisionMode revisionMode, bool *hasProperty = nullptr, bool includeImports = false) const; - static ReturnedValue getQmlProperty(ExecutionEngine *engine, QQmlContextData *qmlContext, QObject *object, String *name, RevisionMode revisionMode, bool *hasProperty = nullptr); + static ReturnedValue getQmlProperty(ExecutionEngine *engine, QQmlContextData *qmlContext, QObject *object, String *name, RevisionMode revisionMode, bool *hasProperty = nullptr, QQmlPropertyData **property = nullptr); static bool setQmlProperty(ExecutionEngine *engine, QQmlContextData *qmlContext, QObject *object, String *name, RevisionMode revisionMode, const Value &value); @@ -185,6 +186,7 @@ struct Q_QML_EXPORT QObjectWrapper : public Object static ReturnedValue virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup); static ReturnedValue lookupGetter(Lookup *l, ExecutionEngine *engine, const Value &object); + template static ReturnedValue lookupGetterImpl(Lookup *l, ExecutionEngine *engine, const Value &object, bool useOriginalProperty, ReversalFunctor revert); static bool virtualResolveLookupSetter(Object *object, ExecutionEngine *engine, Lookup *lookup, const Value &value); protected: @@ -222,6 +224,47 @@ inline ReturnedValue QObjectWrapper::wrap(ExecutionEngine *engine, QObject *obje return wrap_slowPath(engine, object); } +template +inline ReturnedValue QObjectWrapper::lookupGetterImpl(Lookup *lookup, ExecutionEngine *engine, const Value &object, bool useOriginalProperty, ReversalFunctor revertLookup) +{ + // we can safely cast to a QV4::Object here. If object is something else, + // the internal class won't match + Heap::Object *o = static_cast(object.heapObject()); + if (!o || o->internalClass != lookup->qobjectLookup.ic) + return revertLookup(); + + const Heap::QObjectWrapper *This = lookup->qobjectLookup.staticQObject ? lookup->qobjectLookup.staticQObject : + static_cast(o); + QObject *qobj = This->object(); + if (QQmlData::wasDeleted(qobj)) + return QV4::Encode::undefined(); + + QQmlData *ddata = QQmlData::get(qobj, /*create*/false); + if (!ddata) + return revertLookup(); + + QQmlPropertyData *property = lookup->qobjectLookup.propertyData; + if (ddata->propertyCache != lookup->qobjectLookup.propertyCache) { + if (property->isOverridden() && (!useOriginalProperty || property->isFunction() || property->isSignalHandler())) + return revertLookup(); + + QQmlPropertyCache *fromMo = ddata->propertyCache; + QQmlPropertyCache *toMo = lookup->qobjectLookup.propertyCache; + bool canConvert = false; + while (fromMo) { + if (fromMo == toMo) { + canConvert = true; + break; + } + fromMo = fromMo->parent(); + } + if (!canConvert) + return revertLookup(); + } + + return getProperty(engine, qobj, property); +} + struct QQmlValueTypeWrapper; struct Q_QML_EXPORT QObjectMethod : public QV4::FunctionObject -- cgit v1.2.3 From 937d8114e9ccf607462ab72a4b6e801756698473 Mon Sep 17 00:00:00 2001 From: Michael Brasser Date: Fri, 22 Feb 2019 16:12:50 -0600 Subject: Accelerate access to initial context object properties in lookups Task-number: QTBUG-69898 Change-Id: If92a0931bd4d64f6c176e93effb04df85ce27284 Reviewed-by: Ulf Hermann --- src/qml/jsruntime/qv4qmlcontext.cpp | 57 ++++++++++++++++++++++++++++++++++++- src/qml/jsruntime/qv4qmlcontext_p.h | 1 + 2 files changed, 57 insertions(+), 1 deletion(-) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4qmlcontext.cpp b/src/qml/jsruntime/qv4qmlcontext.cpp index 9bdfa10030..97b955632d 100644 --- a/src/qml/jsruntime/qv4qmlcontext.cpp +++ b/src/qml/jsruntime/qv4qmlcontext.cpp @@ -215,6 +215,14 @@ ReturnedValue QQmlContextWrapper::getPropertyAndBase(const QQmlContextWrapper *r QQmlEnginePrivate *ep = QQmlEnginePrivate::get(v4->qmlEngine()); Lookup * const originalLookup = lookup; + decltype(lookup->qmlContextPropertyGetter) contextGetterFunction = QQmlContextWrapper::lookupContextObjectProperty; + + // minor optimization so we don't potentially try two property lookups on the same object + if (scopeObject == context->contextObject) { + scopeObject = nullptr; + contextGetterFunction = QQmlContextWrapper::lookupScopeObjectProperty; + } + while (context) { // Search context properties const QV4::IdentifierHash &properties = context->propertyNames(); @@ -294,12 +302,29 @@ ReturnedValue QQmlContextWrapper::getPropertyAndBase(const QQmlContextWrapper *r // Search context object if (context->contextObject) { bool hasProp = false; - result = QV4::QObjectWrapper::getQmlProperty(v4, context, context->contextObject, name, QV4::QObjectWrapper::CheckRevision, &hasProp); + QQmlPropertyData *propertyData = nullptr; + result = QV4::QObjectWrapper::getQmlProperty(v4, context, context->contextObject, + name, QV4::QObjectWrapper::CheckRevision, &hasProp, &propertyData); if (hasProp) { if (hasProperty) *hasProperty = true; if (base) *base = QV4::QObjectWrapper::wrap(v4, context->contextObject); + + if (lookup && propertyData) { + QQmlData *ddata = QQmlData::get(context->contextObject, false); + if (ddata && ddata->propertyCache) { + ScopedValue val(scope, base ? *base : Value::fromReturnedValue(QV4::QObjectWrapper::wrap(v4, context->contextObject))); + const QObjectWrapper *That = static_cast(val->objectValue()); + lookup->qobjectLookup.ic = That->internalClass(); + lookup->qobjectLookup.staticQObject = nullptr; + lookup->qobjectLookup.propertyCache = ddata->propertyCache; + lookup->qobjectLookup.propertyCache->addref(); + lookup->qobjectLookup.propertyData = propertyData; + lookup->qmlContextPropertyGetter = contextGetterFunction; + } + } + return result->asReturnedValue(); } } @@ -509,6 +534,36 @@ ReturnedValue QQmlContextWrapper::lookupScopeObjectProperty(Lookup *l, Execution return QObjectWrapper::lookupGetterImpl(l, engine, obj, /*useOriginalProperty*/ true, revertLookup); } +ReturnedValue QQmlContextWrapper::lookupContextObjectProperty(Lookup *l, ExecutionEngine *engine, Value *base) +{ + Q_UNUSED(base) + Scope scope(engine); + Scoped qmlContext(scope, engine->qmlContext()); + if (!qmlContext) + return QV4::Encode::undefined(); + + QQmlContextData *context = qmlContext->qmlContext(); + if (!context) + return QV4::Encode::undefined(); + + QObject *contextObject = context->contextObject; + if (!contextObject) + return QV4::Encode::undefined(); + + if (QQmlData::wasDeleted(contextObject)) + return QV4::Encode::undefined(); + + const auto revertLookup = [l, engine, base]() { + l->qobjectLookup.propertyCache->release(); + l->qobjectLookup.propertyCache = nullptr; + l->qmlContextPropertyGetter = QQmlContextWrapper::resolveQmlContextPropertyLookupGetter; + return QQmlContextWrapper::resolveQmlContextPropertyLookupGetter(l, engine, base); + }; + + ScopedValue obj(scope, QV4::QObjectWrapper::wrap(engine, contextObject)); + return QObjectWrapper::lookupGetterImpl(l, engine, obj, /*useOriginalProperty*/ true, revertLookup); +} + ReturnedValue QQmlContextWrapper::lookupInGlobalObject(Lookup *l, ExecutionEngine *engine, Value *base) { Q_UNUSED(base); diff --git a/src/qml/jsruntime/qv4qmlcontext_p.h b/src/qml/jsruntime/qv4qmlcontext_p.h index 83bf2a1d3e..6375294375 100644 --- a/src/qml/jsruntime/qv4qmlcontext_p.h +++ b/src/qml/jsruntime/qv4qmlcontext_p.h @@ -109,6 +109,7 @@ struct Q_QML_EXPORT QQmlContextWrapper : Object static ReturnedValue lookupSingleton(Lookup *l, ExecutionEngine *engine, Value *base); static ReturnedValue lookupIdObject(Lookup *l, ExecutionEngine *engine, Value *base); static ReturnedValue lookupScopeObjectProperty(Lookup *l, ExecutionEngine *engine, Value *base); + static ReturnedValue lookupContextObjectProperty(Lookup *l, ExecutionEngine *engine, Value *base); static ReturnedValue lookupInGlobalObject(Lookup *l, ExecutionEngine *engine, Value *base); }; -- cgit v1.2.3 From c091f3f4b4889ac6be26e018c5e8b673adee7c47 Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Mon, 18 Mar 2019 11:49:05 +0100 Subject: V4: Do not invert non-reflexive comparison binops This is only useful for the few (4) comparisons where we have specialized instructions, and it's very error-prone. Change-Id: I37efe94f54ba0adf393d9236df2d13aa6685eb46 Fixes: QTBUG-74476 Reviewed-by: Simon Hausmann --- src/qml/compiler/qv4codegen.cpp | 85 +++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 49 deletions(-) (limited to 'src/qml') diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index b0bec5b6f2..3fdba08f20 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -1738,59 +1738,46 @@ Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Re return Reference::fromAccumulator(this); } -static QSOperator::Op operatorForSwappedOperands(QSOperator::Op oper) -{ - switch (oper) { - case QSOperator::StrictEqual: return QSOperator::StrictEqual; - case QSOperator::StrictNotEqual: return QSOperator::StrictNotEqual; - case QSOperator::Equal: return QSOperator::Equal; - case QSOperator::NotEqual: return QSOperator::NotEqual; - case QSOperator::Gt: return QSOperator::Le; - case QSOperator::Ge: return QSOperator::Lt; - case QSOperator::Lt: return QSOperator::Ge; - case QSOperator::Le: return QSOperator::Gt; - default: Q_UNIMPLEMENTED(); return QSOperator::Invalid; - } -} - Codegen::Reference Codegen::jumpBinop(QSOperator::Op oper, Reference &left, Reference &right) { - if (left.isConstant()) { - oper = operatorForSwappedOperands(oper); - qSwap(left, right); - } + // See if we can generate specialized comparison instructions: + if (oper == QSOperator::Equal || oper == QSOperator::NotEqual) { + // Because == and != are reflexive, we can do the following: + if (left.isConstant() && !right.isConstant()) + qSwap(left, right); // null==a -> a==null - if (right.isConstant() && (oper == QSOperator::Equal || oper == QSOperator::NotEqual)) { - Value c = Value::fromReturnedValue(right.constant); - if (c.isNull() || c.isUndefined()) { - left.loadInAccumulator(); - if (oper == QSOperator::Equal) { - Instruction::CmpEqNull cmp; - bytecodeGenerator->addInstruction(cmp); - addCJump(); - return Reference(); - } else if (oper == QSOperator::NotEqual) { - Instruction::CmpNeNull cmp; - bytecodeGenerator->addInstruction(cmp); - addCJump(); - return Reference(); - } - } else if (c.isInt32()) { - left.loadInAccumulator(); - if (oper == QSOperator::Equal) { - Instruction::CmpEqInt cmp; - cmp.lhs = c.int_32(); - bytecodeGenerator->addInstruction(cmp); - addCJump(); - return Reference(); - } else if (oper == QSOperator::NotEqual) { - Instruction::CmpNeInt cmp; - cmp.lhs = c.int_32(); - bytecodeGenerator->addInstruction(cmp); - addCJump(); - return Reference(); - } + if (right.isConstant()) { + Value c = Value::fromReturnedValue(right.constant); + if (c.isNull() || c.isUndefined()) { + left.loadInAccumulator(); + if (oper == QSOperator::Equal) { + Instruction::CmpEqNull cmp; + bytecodeGenerator->addInstruction(cmp); + addCJump(); + return Reference(); + } else if (oper == QSOperator::NotEqual) { + Instruction::CmpNeNull cmp; + bytecodeGenerator->addInstruction(cmp); + addCJump(); + return Reference(); + } + } else if (c.isInt32()) { + left.loadInAccumulator(); + if (oper == QSOperator::Equal) { + Instruction::CmpEqInt cmp; + cmp.lhs = c.int_32(); + bytecodeGenerator->addInstruction(cmp); + addCJump(); + return Reference(); + } else if (oper == QSOperator::NotEqual) { + Instruction::CmpNeInt cmp; + cmp.lhs = c.int_32(); + bytecodeGenerator->addInstruction(cmp); + addCJump(); + return Reference(); + } + } } } -- cgit v1.2.3 From 77847ca2c3bad62bab2219d37e1d3d51ce83ade2 Mon Sep 17 00:00:00 2001 From: Kai Koehne Date: Mon, 18 Mar 2019 16:55:49 +0100 Subject: Doc: Rename State Machine QML Types page Make name more in line with the import and with the other overview pages. This became apparent because the All QML Modules overview page now shows the name of the page, instead of the import: https://doc-snapshots.qt.io/qt5-5.13/modules-qml.html# Change-Id: Iee1362247ea6837dd9404885890b2adb7af2c544 Reviewed-by: Ulf Hermann Reviewed-by: Venugopal Shivashankar --- src/qml/doc/src/statemachine.qdoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/qml') diff --git a/src/qml/doc/src/statemachine.qdoc b/src/qml/doc/src/statemachine.qdoc index 6986f1baa0..231b85af76 100644 --- a/src/qml/doc/src/statemachine.qdoc +++ b/src/qml/doc/src/statemachine.qdoc @@ -27,7 +27,7 @@ /*! \qmlmodule QtQml.StateMachine 1.\QtMinorVersion - \title Declarative State Machine QML Types + \title Qt QML State Machine QML Types \brief Provides QML types to create and execute state graphs. The following is a list of QML types provided by the module: @@ -322,7 +322,7 @@ \section1 Related Information \list - \li \l{Declarative State Machine QML Types} + \li \l{Qt QML State Machine QML Types} \li \l{The State Machine Framework} \endlist */ -- cgit v1.2.3 From 4d80e1c6a9d04d87b3ddcb3e1724281525a27442 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Fri, 1 Mar 2019 09:58:04 +0100 Subject: Accelerate lookups of properties in outer QML contexts If we can determine with certainty that a property cannot be found in the innermost QML context, then we can avoid one entire iteration for all future lookups. Task-number: QTBUG-69898 Change-Id: I2a579aa9f60811a818e45235a60a93fc2ede3206 Reviewed-by: Ulf Hermann --- src/qml/jsruntime/qv4qmlcontext.cpp | 165 ++++++++++++++++++++++++------------ src/qml/jsruntime/qv4qmlcontext_p.h | 1 + 2 files changed, 114 insertions(+), 52 deletions(-) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4qmlcontext.cpp b/src/qml/jsruntime/qv4qmlcontext.cpp index 97b955632d..a2c8e3916f 100644 --- a/src/qml/jsruntime/qv4qmlcontext.cpp +++ b/src/qml/jsruntime/qv4qmlcontext.cpp @@ -79,6 +79,53 @@ void Heap::QQmlContextWrapper::destroy() Object::destroy(); } +static OptionalReturnedValue searchContextProperties(QV4::ExecutionEngine *v4, QQmlContextData *context, String *name, + bool *hasProperty, Value *base, QV4::Lookup *lookup, + QV4::Lookup *originalLookup, QQmlEnginePrivate *ep) +{ + const QV4::IdentifierHash &properties = context->propertyNames(); + if (properties.count() == 0) + return OptionalReturnedValue(); + + const int propertyIdx = properties.value(name); + + if (propertyIdx == -1) + return OptionalReturnedValue(); + + if (propertyIdx < context->idValueCount) { + if (hasProperty) + *hasProperty = true; + + if (lookup) { + lookup->qmlContextIdObjectLookup.objectId = propertyIdx; + lookup->qmlContextPropertyGetter = QQmlContextWrapper::lookupIdObject; + return OptionalReturnedValue(lookup->qmlContextPropertyGetter(lookup, v4, base)); + } else if (originalLookup) { + originalLookup->qmlContextPropertyGetter = QQmlContextWrapper::lookupInParentContextHierarchy; + } + + if (ep->propertyCapture) + ep->propertyCapture->captureProperty(&context->idValues[propertyIdx].bindings); + return OptionalReturnedValue(QV4::QObjectWrapper::wrap(v4, context->idValues[propertyIdx])); + } + + QQmlContextPrivate *cp = context->asQQmlContextPrivate(); + + if (ep->propertyCapture) + ep->propertyCapture->captureProperty(context->asQQmlContext(), -1, propertyIdx + cp->notifyIndex); + + const QVariant &value = cp->propertyValues.at(propertyIdx); + if (hasProperty) + *hasProperty = true; + if (value.userType() == qMetaTypeId >()) { + QQmlListProperty prop(context->asQQmlContext(), (void*) qintptr(propertyIdx), + QQmlContextPrivate::context_count, + QQmlContextPrivate::context_at); + return OptionalReturnedValue(QmlListWrapper::create(v4, prop, qMetaTypeId >())); + } + return OptionalReturnedValue(v4->fromVariant(cp->propertyValues.at(propertyIdx))); +} + ReturnedValue QQmlContextWrapper::getPropertyAndBase(const QQmlContextWrapper *resource, PropertyKey id, const Value *receiver, bool *hasProperty, Value *base, Lookup *lookup) { if (!id.isString()) @@ -224,47 +271,8 @@ ReturnedValue QQmlContextWrapper::getPropertyAndBase(const QQmlContextWrapper *r } while (context) { - // Search context properties - const QV4::IdentifierHash &properties = context->propertyNames(); - if (properties.count()) { - int propertyIdx = properties.value(name); - - if (propertyIdx != -1) { - - if (propertyIdx < context->idValueCount) { - if (hasProperty) - *hasProperty = true; - - if (lookup) { - lookup->qmlContextIdObjectLookup.objectId = propertyIdx; - lookup->qmlContextPropertyGetter = QQmlContextWrapper::lookupIdObject; - return lookup->qmlContextPropertyGetter(lookup, v4, base); - } - - if (ep->propertyCapture) - ep->propertyCapture->captureProperty(&context->idValues[propertyIdx].bindings); - return QV4::QObjectWrapper::wrap(v4, context->idValues[propertyIdx]); - } else { - - QQmlContextPrivate *cp = context->asQQmlContextPrivate(); - - if (ep->propertyCapture) - ep->propertyCapture->captureProperty(context->asQQmlContext(), -1, propertyIdx + cp->notifyIndex); - - const QVariant &value = cp->propertyValues.at(propertyIdx); - if (hasProperty) - *hasProperty = true; - if (value.userType() == qMetaTypeId >()) { - QQmlListProperty prop(context->asQQmlContext(), (void*) qintptr(propertyIdx), - QQmlContextPrivate::context_count, - QQmlContextPrivate::context_at); - return QmlListWrapper::create(v4, prop, qMetaTypeId >()); - } else { - return scope.engine->fromVariant(cp->propertyValues.at(propertyIdx)); - } - } - } - } + if (auto property = searchContextProperties(v4, context, name, hasProperty, base, lookup, originalLookup, ep)) + return *property; // Search scope object if (scopeObject) { @@ -311,17 +319,21 @@ ReturnedValue QQmlContextWrapper::getPropertyAndBase(const QQmlContextWrapper *r if (base) *base = QV4::QObjectWrapper::wrap(v4, context->contextObject); - if (lookup && propertyData) { - QQmlData *ddata = QQmlData::get(context->contextObject, false); - if (ddata && ddata->propertyCache) { - ScopedValue val(scope, base ? *base : Value::fromReturnedValue(QV4::QObjectWrapper::wrap(v4, context->contextObject))); - const QObjectWrapper *That = static_cast(val->objectValue()); - lookup->qobjectLookup.ic = That->internalClass(); - lookup->qobjectLookup.staticQObject = nullptr; - lookup->qobjectLookup.propertyCache = ddata->propertyCache; - lookup->qobjectLookup.propertyCache->addref(); - lookup->qobjectLookup.propertyData = propertyData; - lookup->qmlContextPropertyGetter = contextGetterFunction; + if (propertyData) { + if (lookup) { + QQmlData *ddata = QQmlData::get(context->contextObject, false); + if (ddata && ddata->propertyCache) { + ScopedValue val(scope, base ? *base : Value::fromReturnedValue(QV4::QObjectWrapper::wrap(v4, context->contextObject))); + const QObjectWrapper *That = static_cast(val->objectValue()); + lookup->qobjectLookup.ic = That->internalClass(); + lookup->qobjectLookup.staticQObject = nullptr; + lookup->qobjectLookup.propertyCache = ddata->propertyCache; + lookup->qobjectLookup.propertyCache->addref(); + lookup->qobjectLookup.propertyData = propertyData; + lookup->qmlContextPropertyGetter = contextGetterFunction; + } + } else if (originalLookup) { + originalLookup->qmlContextPropertyGetter = lookupInParentContextHierarchy; } } @@ -576,6 +588,55 @@ ReturnedValue QQmlContextWrapper::lookupInGlobalObject(Lookup *l, ExecutionEngin return result; } +ReturnedValue QQmlContextWrapper::lookupInParentContextHierarchy(Lookup *l, ExecutionEngine *engine, Value *base) +{ + Scope scope(engine); + Scoped qmlContext(scope, engine->qmlContext()); + if (!qmlContext) + return QV4::Encode::undefined(); + + QQmlContextData *context = qmlContext->qmlContext(); + if (!context) + return QV4::Encode::undefined(); + + QQmlContextData *expressionContext = context; + + QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine->qmlEngine()); + + PropertyKey id =engine->identifierTable->asPropertyKey(engine->currentStackFrame->v4Function->compilationUnit-> + runtimeStrings[l->nameIndex]); + ScopedString name(scope, id.asStringOrSymbol()); + + ScopedValue result(scope); + + for (context = context->parent; context; context = context->parent) { + if (auto property = searchContextProperties(engine, context, name, nullptr, base, nullptr, nullptr, ep)) + return *property; + + // Search context object + if (context->contextObject) { + bool hasProp = false; + result = QV4::QObjectWrapper::getQmlProperty(engine, context, context->contextObject, + name, QV4::QObjectWrapper::CheckRevision, &hasProp); + if (hasProp) { + if (base) + *base = QV4::QObjectWrapper::wrap(engine, context->contextObject); + + return result->asReturnedValue(); + } + } + } + + bool hasProp = false; + result = engine->globalObject->get(name, &hasProp); + if (hasProp) + return result->asReturnedValue(); + + expressionContext->unresolvedNames = true; + + return Encode::undefined(); +} + void Heap::QmlContext::init(QV4::ExecutionContext *outerContext, QV4::QQmlContextWrapper *qml) { Heap::ExecutionContext::init(Heap::ExecutionContext::Type_QmlContext); diff --git a/src/qml/jsruntime/qv4qmlcontext_p.h b/src/qml/jsruntime/qv4qmlcontext_p.h index 6375294375..4c8287ef2f 100644 --- a/src/qml/jsruntime/qv4qmlcontext_p.h +++ b/src/qml/jsruntime/qv4qmlcontext_p.h @@ -111,6 +111,7 @@ struct Q_QML_EXPORT QQmlContextWrapper : Object static ReturnedValue lookupScopeObjectProperty(Lookup *l, ExecutionEngine *engine, Value *base); static ReturnedValue lookupContextObjectProperty(Lookup *l, ExecutionEngine *engine, Value *base); static ReturnedValue lookupInGlobalObject(Lookup *l, ExecutionEngine *engine, Value *base); + static ReturnedValue lookupInParentContextHierarchy(Lookup *l, ExecutionEngine *engine, Value *base); }; struct Q_QML_EXPORT QmlContext : public ExecutionContext -- cgit v1.2.3 From 497a795081b95d487f0ec33746cc2a12ffeae5a0 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Thu, 21 Mar 2019 16:25:55 +0100 Subject: Initialize TranslationData padding before writing cache files Otherwise the resulting files differ subtly. Fixes: QTBUG-74532 Change-Id: I12b4f1ba6dda781d63ad50cce87861ba24582bf7 Reviewed-by: Simon Hausmann --- src/qml/compiler/qqmlirbuilder.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/qml') diff --git a/src/qml/compiler/qqmlirbuilder.cpp b/src/qml/compiler/qqmlirbuilder.cpp index ab43ea350b..868f600a10 100644 --- a/src/qml/compiler/qqmlirbuilder.cpp +++ b/src/qml/compiler/qqmlirbuilder.cpp @@ -1108,6 +1108,7 @@ void IRBuilder::tryGeneratingTranslationBinding(const QStringRef &base, AST::Arg QV4::CompiledData::TranslationData translationData; translationData.number = -1; translationData.commentIndex = 0; // empty string + translationData.padding = 0; if (!args || !args->expression) return; // no arguments, stop @@ -1148,6 +1149,7 @@ void IRBuilder::tryGeneratingTranslationBinding(const QStringRef &base, AST::Arg QV4::CompiledData::TranslationData translationData; translationData.number = -1; translationData.commentIndex = 0; // empty string, but unused + translationData.padding = 0; if (!args || !args->expression) return; // no arguments, stop -- cgit v1.2.3 From b7eebec9597b38fed52710bc1dcc166d456a415d Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Thu, 7 Mar 2019 11:53:56 +0100 Subject: V4: Add trace slot for UPlus Change-Id: I0bb5055024e30c32b82e1555c820ea5ced8923f5 Reviewed-by: Simon Hausmann --- src/qml/compiler/qv4codegen.cpp | 12 ++++++------ src/qml/compiler/qv4compileddata_p.h | 8 +++++++- src/qml/compiler/qv4instr_moth.cpp | 1 + src/qml/compiler/qv4instr_moth_p.h | 2 +- src/qml/jit/qv4baselinejit.cpp | 2 +- src/qml/jit/qv4baselinejit_p.h | 2 +- src/qml/jit/qv4graphbuilder.cpp | 2 +- src/qml/jit/qv4graphbuilder_p.h | 2 +- src/qml/jsruntime/qv4vme_moth.cpp | 19 ++++++++++++++++++- 9 files changed, 37 insertions(+), 13 deletions(-) (limited to 'src/qml') diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index 448fbff27b..1149c9ff1e 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -293,8 +293,8 @@ Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr) } case UPlus: { expr.loadInAccumulator(); - Instruction::UPlus uplus; - bytecodeGenerator->addInstruction(uplus); + Instruction::UPlus uplus = {}; + bytecodeGenerator->addTracingInstruction(uplus); return Reference::fromAccumulator(this); } case Not: { @@ -313,8 +313,8 @@ Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr) if (!_expr.accept(nx) || requiresReturnValue) { Reference e = expr.asLValue(); e.loadInAccumulator(); - Instruction::UPlus uplus; - bytecodeGenerator->addInstruction(uplus); + Instruction::UPlus uplus = {}; + bytecodeGenerator->addTracingInstruction(uplus); Reference originalValue = Reference::fromStackSlot(this).storeRetainAccumulator(); Instruction::Increment inc = {}; bytecodeGenerator->addTracingInstruction(inc); @@ -339,8 +339,8 @@ Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr) if (!_expr.accept(nx) || requiresReturnValue) { Reference e = expr.asLValue(); e.loadInAccumulator(); - Instruction::UPlus uplus; - bytecodeGenerator->addInstruction(uplus); + Instruction::UPlus uplus = {}; + bytecodeGenerator->addTracingInstruction(uplus); Reference originalValue = Reference::fromStackSlot(this).storeRetainAccumulator(); Instruction::Decrement dec = {}; bytecodeGenerator->addTracingInstruction(dec); diff --git a/src/qml/compiler/qv4compileddata_p.h b/src/qml/compiler/qv4compileddata_p.h index 1aaba13241..726b82fea9 100644 --- a/src/qml/compiler/qv4compileddata_p.h +++ b/src/qml/compiler/qv4compileddata_p.h @@ -73,7 +73,13 @@ QT_BEGIN_NAMESPACE // Bump this whenever the compiler data structures change in an incompatible way. -#define QV4_DATA_STRUCTURE_VERSION 0x1b +// +// IMPORTANT: +// +// Also change the comment behind the number to describe the latest change. This has the added +// benefit that if another patch changes the version too, it will result in a merge conflict, and +// not get removed silently. +#define QV4_DATA_STRUCTURE_VERSION 0x1c // Add trace slot to UPlus class QIODevice; class QQmlPropertyData; diff --git a/src/qml/compiler/qv4instr_moth.cpp b/src/qml/compiler/qv4instr_moth.cpp index c0b1be1492..f528e1f9d3 100644 --- a/src/qml/compiler/qv4instr_moth.cpp +++ b/src/qml/compiler/qv4instr_moth.cpp @@ -615,6 +615,7 @@ void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*st MOTH_END_INSTR(UNot) MOTH_BEGIN_INSTR(UPlus) + d << TRACE_SLOT; MOTH_END_INSTR(UPlus) MOTH_BEGIN_INSTR(UMinus) diff --git a/src/qml/compiler/qv4instr_moth_p.h b/src/qml/compiler/qv4instr_moth_p.h index 3996143843..f853c1f1ab 100644 --- a/src/qml/compiler/qv4instr_moth_p.h +++ b/src/qml/compiler/qv4instr_moth_p.h @@ -171,7 +171,7 @@ QT_BEGIN_NAMESPACE #define INSTR_CmpIn(op) INSTRUCTION(op, CmpIn, 1, lhs) #define INSTR_CmpInstanceOf(op) INSTRUCTION(op, CmpInstanceOf, 1, lhs) #define INSTR_UNot(op) INSTRUCTION(op, UNot, 0) -#define INSTR_UPlus(op) INSTRUCTION(op, UPlus, 0) +#define INSTR_UPlus(op) INSTRUCTION(op, UPlus, 1, traceSlot) #define INSTR_UMinus(op) INSTRUCTION(op, UMinus, 1, traceSlot) #define INSTR_UCompl(op) INSTRUCTION(op, UCompl, 0) #define INSTR_Increment(op) INSTRUCTION(op, Increment, 1, traceSlot) diff --git a/src/qml/jit/qv4baselinejit.cpp b/src/qml/jit/qv4baselinejit.cpp index f2f7a12598..6635ee7530 100644 --- a/src/qml/jit/qv4baselinejit.cpp +++ b/src/qml/jit/qv4baselinejit.cpp @@ -891,7 +891,7 @@ void BaselineJIT::generate_CmpInstanceOf(int lhs) } void BaselineJIT::generate_UNot() { as->unot(); } -void BaselineJIT::generate_UPlus() { as->toNumber(); } +void BaselineJIT::generate_UPlus(int /*traceSlot*/) { as->toNumber(); } void BaselineJIT::generate_UMinus(int /*traceSlot*/) { as->uminus(); } void BaselineJIT::generate_UCompl() { as->ucompl(); } void BaselineJIT::generate_Increment(int /*traceSlot*/) { as->inc(); } diff --git a/src/qml/jit/qv4baselinejit_p.h b/src/qml/jit/qv4baselinejit_p.h index 5f3cb7eb2e..df263b066b 100644 --- a/src/qml/jit/qv4baselinejit_p.h +++ b/src/qml/jit/qv4baselinejit_p.h @@ -188,7 +188,7 @@ public: void generate_CmpIn(int lhs) override; void generate_CmpInstanceOf(int lhs) override; void generate_UNot() override; - void generate_UPlus() override; + void generate_UPlus(int) override; void generate_UMinus(int traceSlot) override; void generate_UCompl() override; void generate_Increment(int traceSlot) override; diff --git a/src/qml/jit/qv4graphbuilder.cpp b/src/qml/jit/qv4graphbuilder.cpp index 94b8e86e08..19f83ad0ec 100644 --- a/src/qml/jit/qv4graphbuilder.cpp +++ b/src/qml/jit/qv4graphbuilder.cpp @@ -1472,7 +1472,7 @@ void GraphBuilder::generate_UNot() createToBoolean(env()->accumulator()))); } -void GraphBuilder::generate_UPlus() +void GraphBuilder::generate_UPlus(int /*traceSlot*/) { Node* control = createNode(opBuilder()->get(), env()->accumulator()); diff --git a/src/qml/jit/qv4graphbuilder_p.h b/src/qml/jit/qv4graphbuilder_p.h index 6393cab9ef..b6b2931ff0 100644 --- a/src/qml/jit/qv4graphbuilder_p.h +++ b/src/qml/jit/qv4graphbuilder_p.h @@ -261,7 +261,7 @@ protected: // ByteCodeHandler void generate_CmpIn(int lhs) override; void generate_CmpInstanceOf(int lhs) override; void generate_UNot() override; - void generate_UPlus() override; + void generate_UPlus(int traceSlot) override; void generate_UMinus(int traceSlot) override; void generate_UCompl() override; void generate_Increment(int traceSlot) override; diff --git a/src/qml/jsruntime/qv4vme_moth.cpp b/src/qml/jsruntime/qv4vme_moth.cpp index 3098837e1c..e3cdc4552a 100644 --- a/src/qml/jsruntime/qv4vme_moth.cpp +++ b/src/qml/jsruntime/qv4vme_moth.cpp @@ -388,6 +388,18 @@ static inline void traceValue(ReturnedValue acc, Function *f, int slot) #endif } +static inline void traceIntValue(Function *f, int slot) +{ +#if QT_CONFIG(qml_tracing) + quint8 *traceInfo = f->traceInfo(slot); + Q_ASSERT(traceInfo); + *traceInfo |= quint8(ObservedTraceValues::Integer); +#else + Q_UNUSED(f); + Q_UNUSED(slot); +#endif +} + static inline void traceDoubleValue(Function *f, int slot) { #if QT_CONFIG(qml_tracing) @@ -1285,7 +1297,12 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_END_INSTR(UNot) MOTH_BEGIN_INSTR(UPlus) - if (Q_UNLIKELY(!ACC.isNumber())) { + if (Q_LIKELY(ACC.isNumber())) { + if (ACC.isDouble()) + traceDoubleValue(function, traceSlot); + else + traceIntValue(function, traceSlot); + } else { acc = Encode(ACC.toNumberImpl()); CHECK_EXCEPTION; } -- cgit v1.2.3 From 90a08b0c52fbe20ea3324f7315c04c48c95a8e61 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Fri, 22 Mar 2019 09:43:16 +0100 Subject: Fix memory "leaks" in qmlcachegen The CompilationUnit class owns the unit data. An exception was made in bootstrap builds, where it was up to the caller to free the memory. This lead to cases in qmlcachegen where we didn't free the memory. This is best fixed by unifying the behavior. This fixes the build when using an ASAN enabled build, as the runtime aborts after calling qmlcachegen due to "leaks". Change-Id: I8b55b4e302a9569a1d4e09eeb488c479368b50f0 Reviewed-by: Lars Knoll --- src/qml/compiler/qv4compileddata.cpp | 10 +++++++++- src/qml/compiler/qv4compileddata_p.h | 4 ---- 2 files changed, 9 insertions(+), 5 deletions(-) (limited to 'src/qml') diff --git a/src/qml/compiler/qv4compileddata.cpp b/src/qml/compiler/qv4compileddata.cpp index 7906b3572c..9ffcb81fa2 100644 --- a/src/qml/compiler/qv4compileddata.cpp +++ b/src/qml/compiler/qv4compileddata.cpp @@ -98,18 +98,25 @@ CompilationUnit::CompilationUnit(const Unit *unitData, const QString &fileName, setUnitData(unitData, nullptr, fileName, finalUrlString); } -#ifndef V4_BOOTSTRAP CompilationUnit::~CompilationUnit() { +#ifndef V4_BOOTSTRAP unlink(); +#endif if (data) { if (data->qmlUnit() != qmlData) free(const_cast(qmlData)); qmlData = nullptr; +#ifndef V4_BOOTSTRAP if (!(data->flags & QV4::CompiledData::Unit::StaticData)) free(const_cast(data)); +#else + // Unconditionally free the memory. In the dev tools we create units that have + // the flag set and will be saved to disk, so intended to persist later. + free(const_cast(data)); +#endif } data = nullptr; #if Q_BYTE_ORDER == Q_BIG_ENDIAN @@ -120,6 +127,7 @@ CompilationUnit::~CompilationUnit() delete [] imports; imports = nullptr; } +#ifndef V4_BOOTSTRAP QString CompilationUnit::localCacheFilePath(const QUrl &url) { diff --git a/src/qml/compiler/qv4compileddata_p.h b/src/qml/compiler/qv4compileddata_p.h index 1341c91e97..3fd9fdf74b 100644 --- a/src/qml/compiler/qv4compileddata_p.h +++ b/src/qml/compiler/qv4compileddata_p.h @@ -1079,11 +1079,7 @@ struct Q_QML_PRIVATE_EXPORT CompilationUnit final : public CompilationUnitBase const QmlUnit *qmlData = nullptr; public: CompilationUnit(const Unit *unitData = nullptr, const QString &fileName = QString(), const QString &finalUrlString = QString()); -#ifdef V4_BOOTSTRAP - ~CompilationUnit() {} -#else ~CompilationUnit(); -#endif void addref() { -- cgit v1.2.3 From 3980ccf785462dc7b2cb53f72790638eb9e7b8c0 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Thu, 21 Mar 2019 16:15:03 +0100 Subject: Eliminate QT_QML_BEGIN_NAMESPACE and QT_QML_END_NAMESPACE These are equivalent to the usual namespace macros. Change-Id: I3891397511e29694fe508414e77eb8483b877e03 Reviewed-by: Ulf Hermann --- src/qml/parser/qqmljs.g | 8 ++++---- src/qml/parser/qqmljsast.cpp | 4 ++-- src/qml/parser/qqmljsast_p.h | 4 ++-- src/qml/parser/qqmljsastfwd_p.h | 4 ++-- src/qml/parser/qqmljsastvisitor.cpp | 4 ++-- src/qml/parser/qqmljsastvisitor_p.h | 4 ++-- src/qml/parser/qqmljsengine_p.cpp | 4 ++-- src/qml/parser/qqmljsengine_p.h | 4 ++-- src/qml/parser/qqmljsglobal_p.h | 4 ---- src/qml/parser/qqmljskeywords_p.h | 4 ++-- src/qml/parser/qqmljslexer_p.h | 4 ++-- src/qml/parser/qqmljsmemorypool_p.h | 4 ++-- src/qml/parser/qqmljssourcelocation_p.h | 4 ++-- 13 files changed, 26 insertions(+), 30 deletions(-) (limited to 'src/qml') diff --git a/src/qml/parser/qqmljs.g b/src/qml/parser/qqmljs.g index 8ae51a795f..0c947b541b 100644 --- a/src/qml/parser/qqmljs.g +++ b/src/qml/parser/qqmljs.g @@ -249,7 +249,7 @@ #include #include -QT_QML_BEGIN_NAMESPACE +QT_BEGIN_NAMESPACE namespace QQmlJS { @@ -486,7 +486,7 @@ protected: using namespace QQmlJS; -QT_QML_BEGIN_NAMESPACE +QT_BEGIN_NAMESPACE void Parser::reallocateStack() { @@ -4476,12 +4476,12 @@ ExportSpecifier: IdentifierName T_AS IdentifierName; return false; } -QT_QML_END_NAMESPACE +QT_END_NAMESPACE ./ /: -QT_QML_END_NAMESPACE +QT_END_NAMESPACE diff --git a/src/qml/parser/qqmljsast.cpp b/src/qml/parser/qqmljsast.cpp index 54a1200493..e5817ab763 100644 --- a/src/qml/parser/qqmljsast.cpp +++ b/src/qml/parser/qqmljsast.cpp @@ -41,7 +41,7 @@ #include "qqmljsastvisitor_p.h" -QT_QML_BEGIN_NAMESPACE +QT_BEGIN_NAMESPACE namespace QQmlJS { namespace AST { @@ -1474,6 +1474,6 @@ LeftHandSideExpression *LeftHandSideExpression::leftHandSideExpressionCast() } } // namespace QQmlJS::AST -QT_QML_END_NAMESPACE +QT_END_NAMESPACE diff --git a/src/qml/parser/qqmljsast_p.h b/src/qml/parser/qqmljsast_p.h index e84c62af2f..b81553776d 100644 --- a/src/qml/parser/qqmljsast_p.h +++ b/src/qml/parser/qqmljsast_p.h @@ -57,7 +57,7 @@ #include -QT_QML_BEGIN_NAMESPACE +QT_BEGIN_NAMESPACE #define QQMLJS_DECLARE_AST_NODE(name) \ enum { K = Kind_##name }; @@ -3395,6 +3395,6 @@ public: -QT_QML_END_NAMESPACE +QT_END_NAMESPACE #endif diff --git a/src/qml/parser/qqmljsastfwd_p.h b/src/qml/parser/qqmljsastfwd_p.h index 7795e0ce71..e9caa918d5 100644 --- a/src/qml/parser/qqmljsastfwd_p.h +++ b/src/qml/parser/qqmljsastfwd_p.h @@ -56,7 +56,7 @@ // We mean it. // -QT_QML_BEGIN_NAMESPACE +QT_BEGIN_NAMESPACE namespace QQmlJS { namespace AST { @@ -181,6 +181,6 @@ class UiEnumMemberList; } } // namespace AST -QT_QML_END_NAMESPACE +QT_END_NAMESPACE #endif diff --git a/src/qml/parser/qqmljsastvisitor.cpp b/src/qml/parser/qqmljsastvisitor.cpp index 666623eecc..5ecac36423 100644 --- a/src/qml/parser/qqmljsastvisitor.cpp +++ b/src/qml/parser/qqmljsastvisitor.cpp @@ -39,7 +39,7 @@ #include "qqmljsastvisitor_p.h" -QT_QML_BEGIN_NAMESPACE +QT_BEGIN_NAMESPACE namespace QQmlJS { namespace AST { @@ -53,4 +53,4 @@ Visitor::~Visitor() } } // namespace QQmlJS::AST -QT_QML_END_NAMESPACE +QT_END_NAMESPACE diff --git a/src/qml/parser/qqmljsastvisitor_p.h b/src/qml/parser/qqmljsastvisitor_p.h index 9c69f88e0c..9115449a46 100644 --- a/src/qml/parser/qqmljsastvisitor_p.h +++ b/src/qml/parser/qqmljsastvisitor_p.h @@ -54,7 +54,7 @@ #include "qqmljsastfwd_p.h" #include "qqmljsglobal_p.h" -QT_QML_BEGIN_NAMESPACE +QT_BEGIN_NAMESPACE namespace QQmlJS { namespace AST { @@ -412,6 +412,6 @@ protected: } } // namespace AST -QT_QML_END_NAMESPACE +QT_END_NAMESPACE #endif // QQMLJSASTVISITOR_P_H diff --git a/src/qml/parser/qqmljsengine_p.cpp b/src/qml/parser/qqmljsengine_p.cpp index 97ce6ebea3..bb27f3992e 100644 --- a/src/qml/parser/qqmljsengine_p.cpp +++ b/src/qml/parser/qqmljsengine_p.cpp @@ -44,7 +44,7 @@ #include #include -QT_QML_BEGIN_NAMESPACE +QT_BEGIN_NAMESPACE namespace QQmlJS { @@ -155,4 +155,4 @@ QStringRef Engine::newStringRef(const QChar *chars, int size) } // end of namespace QQmlJS -QT_QML_END_NAMESPACE +QT_END_NAMESPACE diff --git a/src/qml/parser/qqmljsengine_p.h b/src/qml/parser/qqmljsengine_p.h index 07b5026eb9..b7f7da9478 100644 --- a/src/qml/parser/qqmljsengine_p.h +++ b/src/qml/parser/qqmljsengine_p.h @@ -58,7 +58,7 @@ #include #include -QT_QML_BEGIN_NAMESPACE +QT_BEGIN_NAMESPACE namespace QQmlJS { @@ -150,6 +150,6 @@ double integerFromString(const char *buf, int size, int radix); } // end of namespace QQmlJS -QT_QML_END_NAMESPACE +QT_END_NAMESPACE #endif // QQMLJSENGINE_P_H diff --git a/src/qml/parser/qqmljsglobal_p.h b/src/qml/parser/qqmljsglobal_p.h index 0e195994b4..bf8155c6ec 100644 --- a/src/qml/parser/qqmljsglobal_p.h +++ b/src/qml/parser/qqmljsglobal_p.h @@ -53,8 +53,6 @@ #include #ifdef QT_CREATOR -# define QT_QML_BEGIN_NAMESPACE -# define QT_QML_END_NAMESPACE # ifdef QDECLARATIVEJS_BUILD_DIR # define QML_PARSER_EXPORT Q_DECL_EXPORT @@ -65,8 +63,6 @@ # endif // QQMLJS_BUILD_DIR #else // !QT_CREATOR -# define QT_QML_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE -# define QT_QML_END_NAMESPACE QT_END_NAMESPACE # ifndef QT_STATIC # if defined(QT_BUILD_QMLDEVTOOLS_LIB) || defined(QT_QMLDEVTOOLS_LIB) // QmlDevTools is a static library diff --git a/src/qml/parser/qqmljskeywords_p.h b/src/qml/parser/qqmljskeywords_p.h index b0a4951ece..96b3709162 100644 --- a/src/qml/parser/qqmljskeywords_p.h +++ b/src/qml/parser/qqmljskeywords_p.h @@ -53,7 +53,7 @@ #include "qqmljslexer_p.h" -QT_QML_BEGIN_NAMESPACE +QT_BEGIN_NAMESPACE namespace QQmlJS { @@ -918,6 +918,6 @@ int Lexer::classify(const QChar *s, int n, int parseModeFlags) { } // namespace QQmlJS -QT_QML_END_NAMESPACE +QT_END_NAMESPACE #endif // QQMLJSKEYWORDS_P_H diff --git a/src/qml/parser/qqmljslexer_p.h b/src/qml/parser/qqmljslexer_p.h index 03f33f6e06..51152bfd6e 100644 --- a/src/qml/parser/qqmljslexer_p.h +++ b/src/qml/parser/qqmljslexer_p.h @@ -57,7 +57,7 @@ #include #include -QT_QML_BEGIN_NAMESPACE +QT_BEGIN_NAMESPACE namespace QQmlJS { @@ -257,6 +257,6 @@ private: } // end of namespace QQmlJS -QT_QML_END_NAMESPACE +QT_END_NAMESPACE #endif // LEXER_H diff --git a/src/qml/parser/qqmljsmemorypool_p.h b/src/qml/parser/qqmljsmemorypool_p.h index bcd6d8672b..e7b1f46414 100644 --- a/src/qml/parser/qqmljsmemorypool_p.h +++ b/src/qml/parser/qqmljsmemorypool_p.h @@ -59,7 +59,7 @@ #include -QT_QML_BEGIN_NAMESPACE +QT_BEGIN_NAMESPACE namespace QQmlJS { @@ -251,6 +251,6 @@ public: } // namespace QQmlJS -QT_QML_END_NAMESPACE +QT_END_NAMESPACE #endif diff --git a/src/qml/parser/qqmljssourcelocation_p.h b/src/qml/parser/qqmljssourcelocation_p.h index dc307ba168..d76e701d49 100644 --- a/src/qml/parser/qqmljssourcelocation_p.h +++ b/src/qml/parser/qqmljssourcelocation_p.h @@ -55,7 +55,7 @@ // We mean it. // -QT_QML_BEGIN_NAMESPACE +QT_BEGIN_NAMESPACE namespace QQmlJS { namespace AST { @@ -82,6 +82,6 @@ public: } } // namespace AST -QT_QML_END_NAMESPACE +QT_END_NAMESPACE #endif -- cgit v1.2.3 From b92b125455cc01dd48f496d85bdb7a1c96a258fd Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Fri, 23 Nov 2018 12:53:18 +0100 Subject: V4: Add a lowering pass to the traced JIT This pass converts high-level operations like e.g. a JSAdd to lower level operations, like a runtime call. This pass will be extended to take trace information, which can indicate that it can be lowered to e.g. an AddInt32. Change-Id: Ieae8df235217189c90048515e199f7e7c7f220b3 Reviewed-by: Ulf Hermann --- src/qml/jit/jit.pri | 4 +- src/qml/jit/qv4lowering.cpp | 200 ++++++++++++++++++++++++++++++++++++++++++ src/qml/jit/qv4lowering_p.h | 107 ++++++++++++++++++++++ src/qml/jit/qv4tracingjit.cpp | 5 ++ 4 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 src/qml/jit/qv4lowering.cpp create mode 100644 src/qml/jit/qv4lowering_p.h (limited to 'src/qml') diff --git a/src/qml/jit/jit.pri b/src/qml/jit/jit.pri index 49eb2e8a37..e8d5860498 100644 --- a/src/qml/jit/jit.pri +++ b/src/qml/jit/jit.pri @@ -18,7 +18,8 @@ SOURCES += \ $$PWD/qv4node.cpp \ $$PWD/qv4graph.cpp \ $$PWD/qv4graphbuilder.cpp \ - $$PWD/qv4tracingjit.cpp \ + $$PWD/qv4lowering.cpp \ + $$PWD/qv4tracingjit.cpp HEADERS += \ $$PWD/qv4ir_p.h \ @@ -27,4 +28,5 @@ HEADERS += \ $$PWD/qv4node_p.h \ $$PWD/qv4graph_p.h \ $$PWD/qv4graphbuilder_p.h \ + $$PWD/qv4lowering_p.h } diff --git a/src/qml/jit/qv4lowering.cpp b/src/qml/jit/qv4lowering.cpp new file mode 100644 index 0000000000..3b3711e7fa --- /dev/null +++ b/src/qml/jit/qv4lowering.cpp @@ -0,0 +1,200 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#include "qv4lowering_p.h" +#include "qv4graph_p.h" + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +Q_LOGGING_CATEGORY(lcLower, "qt.v4.ir.lowering") + +GenericLowering::GenericLowering(Function &f) + : m_function(f) +{} + +void GenericLowering::lower() +{ + NodeWorkList worklist(graph()); + // The order doesn't really matter for generic lowering, as long as it's done in 1 pass, and + // have any clean-up done afterwards. + worklist.enqueueAllInputs(graph()->endNode()); + + while (Node *n = worklist.dequeueNextNodeForVisiting()) { + worklist.enqueueAllInputs(n); + + if (!CallPayload::isRuntimeCall(n->opcode())) + continue; + + if (CallPayload::isVarArgsCall(n->opcode())) + replaceWithVarArgsCall(n); + else + replaceWithCall(n); + } +} + +void GenericLowering::replaceWithCall(Node *n) +{ + auto newOp = opBuilder()->getCall(n->opcode()); + + QVarLengthArray args; + if (CallPayload::takesEngineAsArg(n->opcode(), 0)) + args.append(graph()->engineNode()); + if (CallPayload::takesFunctionAsArg(n->opcode(), args.size())) + args.append(graph()->functionNode()); + if (CallPayload::takesFrameAsArg(n->opcode(), args.size())) + args.append(graph()->cppFrameNode()); + const int extraLeadingArguments = args.size(); + + for (unsigned arg = 0, earg = n->inputCount(); arg != earg; ++arg) { + Node *input = n->input(arg); + if (input->opcode() == Meta::FrameState) + continue; + + if (arg >= n->operation()->valueInputCount()) { + // effect or control input + args.append(input); + continue; + } + + if (CallPayload::needsStorageOnJSStack(n->opcode(), args.size(), input->operation(), + function().nodeInfo(input)->type())) + input = graph()->createNode(opBuilder()->get(), input); + + args.append(input); + } + + Node *newCall = graph()->createNode(newOp, args.data(), args.size()); + + qCDebug(lcLower) << "replacing node" << n->id() << n->operation()->debugString() + << "with node" << newCall->id() << newOp->debugString(); + qCDebug(lcLower) << "... old node #inputs:" << n->inputCount(); + qCDebug(lcLower) << "... old node #uses:" << n->useCount(); + + function().nodeInfo(newCall)->setType(CallPayload::returnType(n->opcode())); + n->replaceAllUsesWith(newCall); + n->kill(); + + qCDebug(lcLower) << "... new node #inputs:" << newCall->inputCount(); + qCDebug(lcLower) << "... new node #uses:" << newCall->useCount(); + + for (Node *use : newCall->uses()) { + // fix-up indices for SelectOutput: + if (use->opcode() == Meta::SelectOutput) { + const int oldIndex = ConstantPayload::get(*use->input(1)->operation())->value().int_32(); + const int newIndex = oldIndex + extraLeadingArguments; + use->replaceInput(1, graph()->createConstantIntNode(newIndex)); + use->replaceInput(2, newCall->input(newIndex)); + break; + } + } +} + +void GenericLowering::replaceWithVarArgsCall(Node *n) +{ + const bool isTailCall = n->opcode() == Meta::JSTailCall; + Operation *newOp = isTailCall ? opBuilder()->getTailCall() + : opBuilder()->getCall(n->opcode()); + + //### optimize this for 0 and 1 argument: we don't need to create a VarArgs array for these cases + + const unsigned varArgsStart = CallPayload::varArgsStart(n->opcode()) - 1; // subtract 1 because the runtime calls all take the engine argument as arg0, which isn't in the graph before lowering. + Node *vaAlloc = graph()->createNode( + opBuilder()->get(), + graph()->createConstantIntNode(n->operation()->valueInputCount() - varArgsStart), + n->effectInput()); + QVarLengthArray vaSealIn; + vaSealIn.append(vaAlloc); + for (unsigned i = varArgsStart, ei = n->operation()->valueInputCount(); i != ei; ++i) { + vaSealIn.append(graph()->createNode(opBuilder()->get(), vaAlloc, + graph()->createConstantIntNode(vaSealIn.size() - 1), + n->input(i))); + } + vaSealIn.append(vaAlloc); + Node *vaSeal = graph()->createNode(opBuilder()->getVASeal(vaSealIn.size() - 2), + vaSealIn.data(), + vaSealIn.size()); + QVarLengthArray callArgs; + if (isTailCall) + callArgs.append(graph()->cppFrameNode()); + callArgs.append(graph()->engineNode()); + for (unsigned i = 0; i != varArgsStart; ++i) { + Node *input = n->input(i); + if (CallPayload::needsStorageOnJSStack(n->opcode(), callArgs.size(), input->operation(), + function().nodeInfo(input)->type())) + input = graph()->createNode(opBuilder()->get(), input); + callArgs.append(input); + } + callArgs.append(vaSeal); // args + if (n->opcode() != Meta::JSCreateClass) // JSCreateClass is the odd duck + callArgs.append(graph()->createConstantIntNode(vaSealIn.size() - 2)); // argc + callArgs.append(vaSeal); // effect + callArgs.append(n->controlInput(0)); // control flow + Node *newCall = graph()->createNode(newOp, callArgs.data(), unsigned(callArgs.size())); + + qCDebug(lcLower) << "replacing node" << n->id() << n->operation()->debugString() + << "with node" << newCall->id() << newOp->debugString(); + qCDebug(lcLower) << "... old node #inputs:" << n->inputCount(); + qCDebug(lcLower) << "... old node #uses:" << n->useCount(); + + n->replaceAllUsesWith(newCall); + n->kill(); + + qCDebug(lcLower) << "... new node #inputs:" << newCall->inputCount(); + qCDebug(lcLower) << "... new node #uses:" << newCall->useCount(); +} + +bool GenericLowering::allUsesAsUnboxedBool(Node *n) +{ + for (Node *use : n->uses()) { + if (use->operation()->kind() != Meta::Branch) + return false; + } + + return true; +} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4lowering_p.h b/src/qml/jit/qv4lowering_p.h new file mode 100644 index 0000000000..0b482bc9f0 --- /dev/null +++ b/src/qml/jit/qv4lowering_p.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4LOWERING_P_H +#define QV4LOWERING_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include +#include + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +// Lowering replaces JS level operations with lower level ones. E.g. a JSAdd is lowered to an AddI32 +// if both inputs and the output are 32bit integers, or to a runtime call in all other cases. This +// transforms the graph into something that is closer to actual executable code. + + +// Last lowering phase: replace all JSOperations that are left with runtime calls. There is nothing +// smart here, all that should have been done before this phase. +class GenericLowering final +{ + Q_DISABLE_COPY(GenericLowering) + +public: + GenericLowering(Function &f); + + void lower(); + +private: + void replaceWithCall(Node *n); + void replaceWithVarArgsCall(Node *n); + static bool allUsesAsUnboxedBool(Node *n); + + Function &function() + { return m_function; } + + Graph *graph() + { return function().graph(); } + + OperationBuilder *opBuilder() + { return graph()->opBuilder(); } + +private: + Function &m_function; +}; + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4LOWERING_P_H diff --git a/src/qml/jit/qv4tracingjit.cpp b/src/qml/jit/qv4tracingjit.cpp index ded2488905..889c0516d3 100644 --- a/src/qml/jit/qv4tracingjit.cpp +++ b/src/qml/jit/qv4tracingjit.cpp @@ -41,6 +41,7 @@ #include "qv4vme_moth_p.h" #include "qv4graphbuilder_p.h" +#include "qv4lowering_p.h" QT_BEGIN_NAMESPACE @@ -73,6 +74,10 @@ void Moth::runTracingJit(QV4::Function *function) IR::GraphBuilder::buildGraph(&irFunction); irFunction.dump(QStringLiteral("initial IR")); irFunction.verify(); + + IR::GenericLowering(irFunction).lower(); + irFunction.dump(QStringLiteral("after generic lowering")); + irFunction.verify(); } } // QV4 namespace -- cgit v1.2.3 From c740143ad6938bf1c81a40de73649e8664f6037f Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Tue, 9 Oct 2018 16:15:10 +0200 Subject: V4: Add headers from LLVM that define an intrusive list template These intrusive list is used in the follow-up patch, where it is used to hold a list of basic blocks in a function, and a list of instructions in the basic block. The advantage of this implementation is that it there is an easy and direct access to iterators when you have an element, it defines STL-like iterators, is double-linked, and you can easily insert before and after elements. We don't have something like this in Qt, and it's better to take an existing and well-tested implementation that making a home-grown one. Change-Id: Ie8c726eafe6d7558ae6a3550e8ecc2f15f362b17 Reviewed-by: Ulf Hermann --- src/qml/qml.pro | 1 + 1 file changed, 1 insertion(+) (limited to 'src/qml') diff --git a/src/qml/qml.pro b/src/qml/qml.pro index 94717a8f43..d96a1c285a 100644 --- a/src/qml/qml.pro +++ b/src/qml/qml.pro @@ -80,6 +80,7 @@ qtConfig(qml-animation) { include(types/types.pri) include(../3rdparty/masm/masm-defs.pri) include(../3rdparty/masm/masm.pri) +include(../3rdparty/llvm/llvm.pri) MODULE_PLUGIN_TYPES = \ qmltooling -- cgit v1.2.3 From 516523750dbae4fdabed5b09281fdb2da843941e Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Fri, 22 Mar 2019 14:03:26 +0100 Subject: Enable lookups for imported scripts and worker scripts This is straight-forward to enable, with the minor adjustment that we need to handle the case where a global lookup is done without a calling qml context (worker script). We don't know at compile time whether a script will be imported directly or used as a worker script, so we have to generate the global qml lookup instruction regardless and handle it at run-time. Change-Id: Ia033afa214d919d906c676498dd3eceb1c5639d8 Reviewed-by: Michael Brasser Reviewed-by: Ulf Hermann --- src/qml/compiler/qv4compilercontext.cpp | 2 +- src/qml/jsruntime/qv4qmlcontext.cpp | 19 ++++++++++++++++--- src/qml/jsruntime/qv4script.cpp | 1 - 3 files changed, 17 insertions(+), 5 deletions(-) (limited to 'src/qml') diff --git a/src/qml/compiler/qv4compilercontext.cpp b/src/qml/compiler/qv4compilercontext.cpp index 84c5d67e8d..d1a5fee92b 100644 --- a/src/qml/compiler/qv4compilercontext.cpp +++ b/src/qml/compiler/qv4compilercontext.cpp @@ -190,7 +190,7 @@ Context::ResolvedName Context::resolveName(const QString &name, const QQmlJS::AS if (c->contextType == ContextType::Eval) return result; - if (c->contextType == ContextType::Binding) + if (c->contextType == ContextType::Binding || c->contextType == ContextType::ScriptImportedByQML) result.type = ResolvedName::QmlGlobal; else result.type = ResolvedName::Global; diff --git a/src/qml/jsruntime/qv4qmlcontext.cpp b/src/qml/jsruntime/qv4qmlcontext.cpp index a2c8e3916f..12ada7ee70 100644 --- a/src/qml/jsruntime/qv4qmlcontext.cpp +++ b/src/qml/jsruntime/qv4qmlcontext.cpp @@ -465,10 +465,23 @@ ReturnedValue QQmlContextWrapper::resolveQmlContextPropertyLookupGetter(Lookup * return static_cast(ctx.d())->locals[index].asReturnedValue(); } - Scoped qmlContext(scope, engine->qmlContext()->qml()); bool hasProperty = false; - ScopedValue result(scope, QQmlContextWrapper::getPropertyAndBase(qmlContext, name, /*receiver*/nullptr, - &hasProperty, base, l)); + ScopedValue result(scope); + + Scoped callingQmlContext(scope, engine->qmlContext()); + if (callingQmlContext) { + Scoped qmlContextWrapper(scope, callingQmlContext->d()->qml()); + result = QQmlContextWrapper::getPropertyAndBase(qmlContextWrapper, name, /*receiver*/nullptr, &hasProperty, + base, l); + } else { + // Code path typical to worker scripts, compiled with lookups but no qml context. + result = l->resolveGlobalGetter(engine); + if (l->globalGetter != Lookup::globalGetterGeneric) { + hasProperty = true; + l->qmlContextGlobalLookup.getterTrampoline = l->globalGetter; + l->qmlContextPropertyGetter = QQmlContextWrapper::lookupInGlobalObject; + } + } if (!hasProperty) return engine->throwReferenceError(name.toQString()); return result->asReturnedValue(); diff --git a/src/qml/jsruntime/qv4script.cpp b/src/qml/jsruntime/qv4script.cpp index 7bbef3335e..6cb2e95cdc 100644 --- a/src/qml/jsruntime/qv4script.cpp +++ b/src/qml/jsruntime/qv4script.cpp @@ -201,7 +201,6 @@ QQmlRefPointer Script::precompile(QV4::Compi } Codegen cg(unitGenerator, /*strict mode*/false); - cg.setUseFastLookups(false); cg.generateFromProgram(fileName, finalUrl, source, program, module, contextType); errors = cg.qmlErrors(); if (!errors.isEmpty()) { -- cgit v1.2.3 From d8110b53ed9ee4d69b92e602e812c6311c1b863b Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 20 Mar 2019 17:15:29 +0100 Subject: Trigger the garbage collector when allocating InternalClass objects As we check the icAllocator's slots on shouldRunGC() we should also check shouldRunGC() when adding slots. Otherwise we might never run the GC when only allocating InternalClasses. In addition, account for the "unmanaged" size of the PropertyAttributes that are part of the InternalClass objects. Those can be large. In cases where an excessive number of large InternalClass objects is created the garbage collector is now invoked frequently, which costs a significant number of CPU cycles, but prevents the memory usage from growing indefinitely. Task-number: QTBUG-58559 Change-Id: Icf102cb6100f6dba212b8bffe1c178897880eda0 Reviewed-by: Lars Knoll --- src/qml/jsruntime/qv4internalclass.cpp | 56 +++++++++++++++++++++++++++++++++ src/qml/jsruntime/qv4internalclass_p.h | 57 ++++++++++------------------------ src/qml/memory/qv4mm.cpp | 55 ++------------------------------ src/qml/memory/qv4mm_p.h | 44 ++++++++++++++++++++++++-- 4 files changed, 118 insertions(+), 94 deletions(-) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4internalclass.cpp b/src/qml/jsruntime/qv4internalclass.cpp index 9906e2b1a0..8e2657f4a5 100644 --- a/src/qml/jsruntime/qv4internalclass.cpp +++ b/src/qml/jsruntime/qv4internalclass.cpp @@ -44,6 +44,7 @@ #include "qv4object_p.h" #include "qv4identifiertable_p.h" #include "qv4value_p.h" +#include "qv4mm_p.h" QT_BEGIN_NAMESPACE @@ -204,7 +205,62 @@ void SharedInternalClassDataPrivate::mark(MarkStack *s) data->mark(s); } +SharedInternalClassDataPrivate::SharedInternalClassDataPrivate( + const SharedInternalClassDataPrivate &other, uint pos, + PropertyAttributes value) + : refcount(1), + m_alloc(pos + 8), + m_size(pos + 1), + m_engine(other.m_engine) +{ + m_engine->memoryManager->changeUnmanagedHeapSizeUsage(m_alloc * sizeof(PropertyAttributes)); + data = new PropertyAttributes[m_alloc]; + if (other.data) + memcpy(data, other.data, (m_size - 1) * sizeof(PropertyAttributes)); + data[pos] = value; +} + +SharedInternalClassDataPrivate::SharedInternalClassDataPrivate( + const SharedInternalClassDataPrivate &other) + : refcount(1), + m_alloc(other.m_alloc), + m_size(other.m_size), + m_engine(other.m_engine) +{ + if (m_alloc) { + m_engine->memoryManager->changeUnmanagedHeapSizeUsage(m_alloc * sizeof(PropertyAttributes)); + data = new PropertyAttributes[m_alloc]; + memcpy(data, other.data, m_size*sizeof(PropertyAttributes)); + } else { + data = nullptr; + } +} +SharedInternalClassDataPrivate::~SharedInternalClassDataPrivate() +{ + m_engine->memoryManager->changeUnmanagedHeapSizeUsage( + -qptrdiff(m_alloc * sizeof(PropertyAttributes))); + delete [] data; +} + +void SharedInternalClassDataPrivate::grow() { + if (!m_alloc) { + m_alloc = 4; + m_engine->memoryManager->changeUnmanagedHeapSizeUsage( + 2 * m_alloc * sizeof(PropertyAttributes)); + } else { + m_engine->memoryManager->changeUnmanagedHeapSizeUsage( + m_alloc * sizeof(PropertyAttributes)); + } + + auto *n = new PropertyAttributes[m_alloc * 2]; + if (data) { + memcpy(n, data, m_alloc*sizeof(PropertyAttributes)); + delete [] data; + } + data = n; + m_alloc *= 2; +} namespace Heap { diff --git a/src/qml/jsruntime/qv4internalclass_p.h b/src/qml/jsruntime/qv4internalclass_p.h index 681cbda5f9..c689637721 100644 --- a/src/qml/jsruntime/qv4internalclass_p.h +++ b/src/qml/jsruntime/qv4internalclass_p.h @@ -149,55 +149,31 @@ inline PropertyHash::Entry *PropertyHash::lookup(PropertyKey identifier) const } } -template -struct SharedInternalClassDataPrivate { - SharedInternalClassDataPrivate(ExecutionEngine *) +template +struct SharedInternalClassDataPrivate {}; + +template<> +struct SharedInternalClassDataPrivate { + SharedInternalClassDataPrivate(ExecutionEngine *engine) : refcount(1), m_alloc(0), m_size(0), - data(nullptr) + data(nullptr), + m_engine(engine) { } - SharedInternalClassDataPrivate(const SharedInternalClassDataPrivate &other) - : refcount(1), - m_alloc(other.m_alloc), - m_size(other.m_size) - { - if (m_alloc) { - data = new T[m_alloc]; - memcpy(data, other.data, m_size*sizeof(T)); - } - } - SharedInternalClassDataPrivate(const SharedInternalClassDataPrivate &other, uint pos, T value) - : refcount(1), - m_alloc(pos + 8), - m_size(pos + 1) - { - data = new T[m_alloc]; - if (other.data) - memcpy(data, other.data, (m_size - 1)*sizeof(T)); - data[pos] = value; - } - ~SharedInternalClassDataPrivate() { delete [] data; } - + SharedInternalClassDataPrivate(const SharedInternalClassDataPrivate &other); + SharedInternalClassDataPrivate(const SharedInternalClassDataPrivate &other, + uint pos, PropertyAttributes value); + ~SharedInternalClassDataPrivate(); - void grow() { - if (!m_alloc) - m_alloc = 4; - T *n = new T[m_alloc * 2]; - if (data) { - memcpy(n, data, m_alloc*sizeof(T)); - delete [] data; - } - data = n; - m_alloc *= 2; - } + void grow(); uint alloc() const { return m_alloc; } uint size() const { return m_size; } void setSize(uint s) { m_size = s; } - T at(uint i) { Q_ASSERT(data && i < m_alloc); return data[i]; } - void set(uint i, T t) { Q_ASSERT(data && i < m_alloc); data[i] = t; } + PropertyAttributes at(uint i) { Q_ASSERT(data && i < m_alloc); return data[i]; } + void set(uint i, PropertyAttributes t) { Q_ASSERT(data && i < m_alloc); data[i] = t; } void mark(MarkStack *) {} @@ -205,7 +181,8 @@ struct SharedInternalClassDataPrivate { private: uint m_alloc; uint m_size; - T *data; + PropertyAttributes *data; + ExecutionEngine *m_engine; }; template<> diff --git a/src/qml/memory/qv4mm.cpp b/src/qml/memory/qv4mm.cpp index 203f1f424f..cac68bdcaf 100644 --- a/src/qml/memory/qv4mm.cpp +++ b/src/qml/memory/qv4mm.cpp @@ -93,8 +93,6 @@ #include #endif -#define MIN_UNMANAGED_HEAPSIZE_GC_LIMIT std::size_t(128 * 1024) - Q_LOGGING_CATEGORY(lcGcStats, "qt.qml.gc.statistics") Q_DECLARE_LOGGING_CATEGORY(lcGcStats) Q_LOGGING_CATEGORY(lcGcAllocatorStats, "qt.qml.gc.allocatorStats") @@ -759,7 +757,7 @@ MemoryManager::MemoryManager(ExecutionEngine *engine) , hugeItemAllocator(chunkAllocator, engine) , m_persistentValues(new PersistentValueStorage(engine)) , m_weakValues(new PersistentValueStorage(engine)) - , unmanagedHeapSizeGCLimit(MIN_UNMANAGED_HEAPSIZE_GC_LIMIT) + , unmanagedHeapSizeGCLimit(MinUnmanagedHeapSizeGCLimit) , aggressiveGC(!qEnvironmentVariableIsEmpty("QV4_MM_AGGRESSIVE_GC")) , gcStats(lcGcStats().isDebugEnabled()) , gcCollectorStats(lcGcAllocatorStats().isDebugEnabled()) @@ -779,35 +777,9 @@ Heap::Base *MemoryManager::allocString(std::size_t unmanagedSize) lastAllocRequestedSlots = stringSize >> Chunk::SlotSizeShift; ++allocationCount; #endif - - bool didGCRun = false; - if (aggressiveGC) { - runGC(); - didGCRun = true; - } - unmanagedHeapSize += unmanagedSize; - if (unmanagedHeapSize > unmanagedHeapSizeGCLimit) { - if (!didGCRun) - runGC(); - - if (3*unmanagedHeapSizeGCLimit <= 4*unmanagedHeapSize) - // more than 75% full, raise limit - unmanagedHeapSizeGCLimit = std::max(unmanagedHeapSizeGCLimit, unmanagedHeapSize) * 2; - else if (unmanagedHeapSize * 4 <= unmanagedHeapSizeGCLimit) - // less than 25% full, lower limit - unmanagedHeapSizeGCLimit = qMax(MIN_UNMANAGED_HEAPSIZE_GC_LIMIT, unmanagedHeapSizeGCLimit/2); - didGCRun = true; - } - - HeapItem *m = blockAllocator.allocate(stringSize); - if (!m) { - if (!didGCRun && shouldRunGC()) - runGC(); - m = blockAllocator.allocate(stringSize, true); - } -// qDebug() << "allocated string" << m; + HeapItem *m = allocate(&blockAllocator, stringSize); memset(m, 0, stringSize); return *m; } @@ -819,32 +791,11 @@ Heap::Base *MemoryManager::allocData(std::size_t size) ++allocationCount; #endif - bool didRunGC = false; - if (aggressiveGC) { - runGC(); - didRunGC = true; - } - Q_ASSERT(size >= Chunk::SlotSize); Q_ASSERT(size % Chunk::SlotSize == 0); -// qDebug() << "unmanagedHeapSize:" << unmanagedHeapSize << "limit:" << unmanagedHeapSizeGCLimit << "unmanagedSize:" << unmanagedSize; - - if (size > Chunk::DataSize) { - HeapItem *h = hugeItemAllocator.allocate(size); -// qDebug() << "allocating huge item" << h; - return *h; - } - - HeapItem *m = blockAllocator.allocate(size); - if (!m) { - if (!didRunGC && shouldRunGC()) - runGC(); - m = blockAllocator.allocate(size, true); - } - + HeapItem *m = allocate(&blockAllocator, size); memset(m, 0, size); -// qDebug() << "allocating data" << m; return *m; } diff --git a/src/qml/memory/qv4mm_p.h b/src/qml/memory/qv4mm_p.h index bbbbb1aef6..6dfdd81cb2 100644 --- a/src/qml/memory/qv4mm_p.h +++ b/src/qml/memory/qv4mm_p.h @@ -264,13 +264,13 @@ public: size_t getLargeItemsMem() const; // called when a JS object grows itself. Specifically: Heap::String::append + // and InternalClassDataPrivate. void changeUnmanagedHeapSizeUsage(qptrdiff delta) { unmanagedHeapSize += delta; } template typename ManagedType::Data *allocIC() { - size_t size = align(sizeof(typename ManagedType::Data)); - Heap::Base *b = *icAllocator.allocate(size, true); + Heap::Base *b = *allocate(&icAllocator, align(sizeof(typename ManagedType::Data))); return static_cast(b); } @@ -284,12 +284,52 @@ protected: Heap::Object *allocObjectWithMemberData(const QV4::VTable *vtable, uint nMembers); private: + enum { + MinUnmanagedHeapSizeGCLimit = 128 * 1024 + }; + void collectFromJSStack(MarkStack *markStack) const; void mark(); void sweep(bool lastSweep = false, ClassDestroyStatsCallback classCountPtr = nullptr); bool shouldRunGC() const; void collectRoots(MarkStack *markStack); + HeapItem *allocate(BlockAllocator *allocator, std::size_t size) + { + bool didGCRun = false; + if (aggressiveGC) { + runGC(); + didGCRun = true; + } + + if (unmanagedHeapSize > unmanagedHeapSizeGCLimit) { + if (!didGCRun) + runGC(); + + if (3*unmanagedHeapSizeGCLimit <= 4 * unmanagedHeapSize) { + // more than 75% full, raise limit + unmanagedHeapSizeGCLimit = std::max(unmanagedHeapSizeGCLimit, + unmanagedHeapSize) * 2; + } else if (unmanagedHeapSize * 4 <= unmanagedHeapSizeGCLimit) { + // less than 25% full, lower limit + unmanagedHeapSizeGCLimit = qMax(std::size_t(MinUnmanagedHeapSizeGCLimit), + unmanagedHeapSizeGCLimit/2); + } + didGCRun = true; + } + + if (size > Chunk::DataSize) + return hugeItemAllocator.allocate(size); + + if (HeapItem *m = allocator->allocate(size)) + return m; + + if (!didGCRun && shouldRunGC()) + runGC(); + + return allocator->allocate(size, true); + } + public: QV4::ExecutionEngine *engine; ChunkAllocator *chunkAllocator; -- cgit v1.2.3 From 0db3493107df8cf8a8f79dfee7a1c1f25b03c655 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Tue, 26 Mar 2019 14:34:14 +0100 Subject: Fix logging output for GC It failed to consider the IC allocator when calculating various sizes. Task-number: QTBUG-74190 Change-Id: Id34c31d639896eed8422790b4adc56c989402807 Reviewed-by: Lars Knoll --- src/qml/memory/qv4mm.cpp | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) (limited to 'src/qml') diff --git a/src/qml/memory/qv4mm.cpp b/src/qml/memory/qv4mm.cpp index 203f1f424f..b3d9155ebc 100644 --- a/src/qml/memory/qv4mm.cpp +++ b/src/qml/memory/qv4mm.cpp @@ -1048,12 +1048,12 @@ bool MemoryManager::shouldRunGC() const return false; } -size_t dumpBins(BlockAllocator *b, bool printOutput = true) +static size_t dumpBins(BlockAllocator *b, const char *title) { const QLoggingCategory &stats = lcGcAllocatorStats(); size_t totalSlotMem = 0; - if (printOutput) - qDebug(stats) << "Slot map:"; + if (title) + qDebug(stats) << "Slot map for" << title << "allocator:"; for (uint i = 0; i < BlockAllocator::NumBins; ++i) { uint nEntries = 0; HeapItem *h = b->freeBins[i]; @@ -1062,7 +1062,7 @@ size_t dumpBins(BlockAllocator *b, bool printOutput = true) totalSlotMem += h->freeData.availableSlots; h = h->freeData.next; } - if (printOutput) + if (title) qDebug(stats) << " number of entries in slot" << i << ":" << nEntries; } SDUMP() << " large slot map"; @@ -1072,7 +1072,7 @@ size_t dumpBins(BlockAllocator *b, bool printOutput = true) h = h->freeData.next; } - if (printOutput) + if (title) qDebug(stats) << " total mem in bins" << totalSlotMem*Chunk::SlotSize; return totalSlotMem*Chunk::SlotSize; } @@ -1113,7 +1113,8 @@ void MemoryManager::runGC() size_t oldChunks = blockAllocator.chunks.size(); qDebug(stats) << "Allocated" << totalMem << "bytes in" << oldChunks << "chunks"; qDebug(stats) << "Fragmented memory before GC" << (totalMem - usedBefore); - dumpBins(&blockAllocator); + dumpBins(&blockAllocator, "Block"); + dumpBins(&icAllocator, "InternalClass"); QElapsedTimer t; t.start(); @@ -1131,7 +1132,8 @@ void MemoryManager::runGC() qDebug(stats) << " new unmanaged heap:" << unmanagedHeapSize; qDebug(stats) << " unmanaged heap limit:" << unmanagedHeapSizeGCLimit; } - size_t memInBins = dumpBins(&blockAllocator); + size_t memInBins = dumpBins(&blockAllocator, "Block") + + dumpBins(&icAllocator, "InternalClasss"); qDebug(stats) << "Marked object in" << markTime << "us."; qDebug(stats) << " " << markStackSize << "objects marked"; qDebug(stats) << "Sweeped object in" << sweepTime << "us."; @@ -1153,7 +1155,8 @@ void MemoryManager::runGC() qDebug(stats) << "Used memory after GC:" << usedAfter; qDebug(stats) << "Freed up bytes :" << (usedBefore - usedAfter); qDebug(stats) << "Freed up chunks :" << (oldChunks - blockAllocator.chunks.size()); - size_t lost = blockAllocator.allocatedMem() - memInBins - usedAfter; + size_t lost = blockAllocator.allocatedMem() + icAllocator.allocatedMem() + - memInBins - usedAfter; if (lost) qDebug(stats) << "!!!!!!!!!!!!!!!!!!!!! LOST MEM:" << lost << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"; if (largeItemsBefore || largeItemsAfter) { @@ -1175,7 +1178,9 @@ void MemoryManager::runGC() if (aggressiveGC) { // ensure we don't 'loose' any memory Q_ASSERT(blockAllocator.allocatedMem() - == blockAllocator.usedMem() + dumpBins(&blockAllocator, false)); + == blockAllocator.usedMem() + dumpBins(&blockAllocator, nullptr)); + Q_ASSERT(icAllocator.allocatedMem() + == icAllocator.usedMem() + dumpBins(&icAllocator, nullptr)); } usedSlotsAfterLastFullSweep = blockAllocator.usedSlotsAfterLastSweep + icAllocator.usedSlotsAfterLastSweep; -- cgit v1.2.3 From b6d0ed435d2d57359b6eafb2728c024eab0b1ece Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Mon, 25 Mar 2019 15:43:13 +0100 Subject: Scope intermediate values when allocating objects Otherwise the garbage collector might interfere and we might end up with dangling pointers in random places. Task-number: QTBUG-74190 Change-Id: I253c526ef930b8f0be14e96a42af8b66fda3b22d Reviewed-by: Lars Knoll --- src/qml/jsruntime/qv4arrayobject.cpp | 4 +++- src/qml/jsruntime/qv4engine.cpp | 16 +++++++++++----- src/qml/jsruntime/qv4generatorobject.cpp | 4 +++- 3 files changed, 17 insertions(+), 7 deletions(-) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4arrayobject.cpp b/src/qml/jsruntime/qv4arrayobject.cpp index 8637db3dfd..b5b421fa39 100644 --- a/src/qml/jsruntime/qv4arrayobject.cpp +++ b/src/qml/jsruntime/qv4arrayobject.cpp @@ -103,7 +103,9 @@ void ArrayPrototype::init(ExecutionEngine *engine, Object *ctor) ctor->defineDefaultProperty(QStringLiteral("from"), method_from, 1); ctor->addSymbolSpecies(); - ScopedObject unscopables(scope, engine->newObject(engine->classes[EngineBase::Class_Empty]->changeVTable(QV4::Object::staticVTable()))); + Scoped ic(scope, engine->classes[EngineBase::Class_Empty] + ->changeVTable(QV4::Object::staticVTable())); + ScopedObject unscopables(scope, engine->newObject(ic->d())); ScopedString name(scope); defineDefaultProperty(QStringLiteral("constructor"), (o = ctor)); defineDefaultProperty(engine->id_toString(), method_toString, 0); diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index 1d0c7c13fa..bcd577c24d 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -464,11 +464,17 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) jsObjects[TypeError_Ctor] = memoryManager->allocate(global); jsObjects[URIError_Ctor] = memoryManager->allocate(global); jsObjects[IteratorProto] = memoryManager->allocate(); - jsObjects[ForInIteratorProto] = memoryManager->allocObject(newInternalClass(ForInIteratorPrototype::staticVTable(), iteratorPrototype())); - jsObjects[MapIteratorProto] = memoryManager->allocObject(newInternalClass(SetIteratorPrototype::staticVTable(), iteratorPrototype())); - jsObjects[SetIteratorProto] = memoryManager->allocObject(newInternalClass(SetIteratorPrototype::staticVTable(), iteratorPrototype())); - jsObjects[ArrayIteratorProto] = memoryManager->allocObject(newInternalClass(ArrayIteratorPrototype::staticVTable(), iteratorPrototype())); - jsObjects[StringIteratorProto] = memoryManager->allocObject(newInternalClass(StringIteratorPrototype::staticVTable(), iteratorPrototype())); + + ic = newInternalClass(ForInIteratorPrototype::staticVTable(), iteratorPrototype()); + jsObjects[ForInIteratorProto] = memoryManager->allocObject(ic); + ic = newInternalClass(SetIteratorPrototype::staticVTable(), iteratorPrototype()); + jsObjects[MapIteratorProto] = memoryManager->allocObject(ic); + ic = newInternalClass(SetIteratorPrototype::staticVTable(), iteratorPrototype()); + jsObjects[SetIteratorProto] = memoryManager->allocObject(ic); + ic = newInternalClass(ArrayIteratorPrototype::staticVTable(), iteratorPrototype()); + jsObjects[ArrayIteratorProto] = memoryManager->allocObject(ic); + ic = newInternalClass(StringIteratorPrototype::staticVTable(), iteratorPrototype()); + jsObjects[StringIteratorProto] = memoryManager->allocObject(ic); str = newString(QStringLiteral("get [Symbol.species]")); jsObjects[GetSymbolSpecies] = FunctionObject::createBuiltinFunction(this, str, ArrayPrototype::method_get_species, 0); diff --git a/src/qml/jsruntime/qv4generatorobject.cpp b/src/qml/jsruntime/qv4generatorobject.cpp index 566db6fd4e..14caa6953f 100644 --- a/src/qml/jsruntime/qv4generatorobject.cpp +++ b/src/qml/jsruntime/qv4generatorobject.cpp @@ -139,7 +139,9 @@ void GeneratorPrototype::init(ExecutionEngine *engine, Object *ctor) Scope scope(engine); ScopedValue v(scope); - ScopedObject ctorProto(scope, engine->newObject(engine->newInternalClass(Object::staticVTable(), engine->functionPrototype()))); + Scoped ic(scope, engine->newInternalClass( + Object::staticVTable(), engine->functionPrototype())); + ScopedObject ctorProto(scope, engine->newObject(ic->d())); ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(1)); ctor->defineReadonlyProperty(engine->id_prototype(), ctorProto); -- cgit v1.2.3 From 60f766f5c68fc33322c6d095d81b1856828b2b0b Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Tue, 26 Mar 2019 13:57:33 +0100 Subject: Re-use existing IC when freezing properties There is no reason to start from the empty class in that case. Furthermore, if the properties are already frozen, starting from the empty class will walk the IC hierarchy to the current IC. However, if the garbage collector has removed the intermediate classes in the mean time, we end up at a new IC which is equivalent but not the same. Therefore, the freezing never terminates. Task-number: QTBUG-74190 Change-Id: Id544bd00d3b4b563fb06dfce0edd0385e1d32a6c Reviewed-by: Lars Knoll --- src/qml/jsruntime/qv4internalclass.cpp | 7 +++---- src/qml/jsruntime/qv4internalclass_p.h | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4internalclass.cpp b/src/qml/jsruntime/qv4internalclass.cpp index 8e2657f4a5..3fb2465a45 100644 --- a/src/qml/jsruntime/qv4internalclass.cpp +++ b/src/qml/jsruntime/qv4internalclass.cpp @@ -658,11 +658,10 @@ Heap::InternalClass *InternalClass::frozen() return f; } -Heap::InternalClass *InternalClass::propertiesFrozen() const +Heap::InternalClass *InternalClass::propertiesFrozen() { Scope scope(engine); - Scoped frozen(scope, engine->internalClasses(EngineBase::Class_Empty)->changeVTable(vtable)); - frozen = frozen->changePrototype(prototype); + Scoped frozen(scope, this); for (uint i = 0; i < size; ++i) { PropertyAttributes attrs = propertyData.at(i); if (!nameMap.at(i).isValid()) @@ -671,7 +670,7 @@ Heap::InternalClass *InternalClass::propertiesFrozen() const attrs.setWritable(false); attrs.setConfigurable(false); } - frozen = frozen->addMember(nameMap.at(i), attrs); + frozen = frozen->changeMember(nameMap.at(i), attrs); } return frozen->d(); } diff --git a/src/qml/jsruntime/qv4internalclass_p.h b/src/qml/jsruntime/qv4internalclass_p.h index c689637721..121238c555 100644 --- a/src/qml/jsruntime/qv4internalclass_p.h +++ b/src/qml/jsruntime/qv4internalclass_p.h @@ -428,7 +428,7 @@ struct InternalClass : Base { Q_REQUIRED_RESULT InternalClass *sealed(); Q_REQUIRED_RESULT InternalClass *frozen(); - Q_REQUIRED_RESULT InternalClass *propertiesFrozen() const; + Q_REQUIRED_RESULT InternalClass *propertiesFrozen(); Q_REQUIRED_RESULT InternalClass *asProtoClass(); -- cgit v1.2.3 From 1e18f2c4a647923fc66a3e3204fcccd88a2960a6 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Tue, 26 Mar 2019 09:40:03 +0100 Subject: Check for numeric limits when growing SharedInternalClassDataPrivate We can effectively only deal with values of < 2GB for m_alloc * sizeof(Data). This is not much more than the values seen in the wild. Change-Id: Ia6972df33d34a320b5b087d38db81aae24ce5bbe Reviewed-by: Lars Knoll --- src/qml/jsruntime/qv4internalclass.cpp | 28 ++++++++++++++++++---------- src/qml/jsruntime/qv4internalclass_p.h | 8 ++++++-- src/qml/jsruntime/qv4memberdata.cpp | 21 +++++++++++++++++---- 3 files changed, 41 insertions(+), 16 deletions(-) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4internalclass.cpp b/src/qml/jsruntime/qv4internalclass.cpp index 3fb2465a45..a10fda79f2 100644 --- a/src/qml/jsruntime/qv4internalclass.cpp +++ b/src/qml/jsruntime/qv4internalclass.cpp @@ -145,7 +145,7 @@ SharedInternalClassDataPrivate::SharedInternalClassDataPrivate(cons data(nullptr) { if (other.alloc()) { - int s = other.size(); + const uint s = other.size(); data = MemberData::allocate(engine, other.alloc(), other.data); setSize(s); } @@ -164,8 +164,8 @@ SharedInternalClassDataPrivate::SharedInternalClassDataPrivate(cons void SharedInternalClassDataPrivate::grow() { - uint a = alloc() * 2; - int s = size(); + const uint a = alloc() * 2; + const uint s = size(); data = MemberData::allocate(engine, a, data); setSize(s); Q_ASSERT(alloc() >= a); @@ -209,10 +209,11 @@ SharedInternalClassDataPrivate::SharedInternalClassDataPriva const SharedInternalClassDataPrivate &other, uint pos, PropertyAttributes value) : refcount(1), - m_alloc(pos + 8), + m_alloc(qMin(other.m_alloc, pos + 8)), m_size(pos + 1), m_engine(other.m_engine) { + Q_ASSERT(m_size <= m_alloc); m_engine->memoryManager->changeUnmanagedHeapSizeUsage(m_alloc * sizeof(PropertyAttributes)); data = new PropertyAttributes[m_alloc]; if (other.data) @@ -244,22 +245,29 @@ SharedInternalClassDataPrivate::~SharedInternalClassDataPriv } void SharedInternalClassDataPrivate::grow() { + uint alloc; if (!m_alloc) { - m_alloc = 4; - m_engine->memoryManager->changeUnmanagedHeapSizeUsage( - 2 * m_alloc * sizeof(PropertyAttributes)); + alloc = 8; + m_engine->memoryManager->changeUnmanagedHeapSizeUsage(alloc * sizeof(PropertyAttributes)); } else { + // yes, signed. We don't want to deal with stuff > 2G + const uint currentSize = m_alloc * sizeof(PropertyAttributes); + if (currentSize < uint(std::numeric_limits::max() / 2)) + alloc = m_alloc * 2; + else + alloc = std::numeric_limits::max() / sizeof(PropertyAttributes); + m_engine->memoryManager->changeUnmanagedHeapSizeUsage( - m_alloc * sizeof(PropertyAttributes)); + (alloc - m_alloc) * sizeof(PropertyAttributes)); } - auto *n = new PropertyAttributes[m_alloc * 2]; + auto *n = new PropertyAttributes[alloc]; if (data) { memcpy(n, data, m_alloc*sizeof(PropertyAttributes)); delete [] data; } data = n; - m_alloc *= 2; + m_alloc = alloc; } namespace Heap { diff --git a/src/qml/jsruntime/qv4internalclass_p.h b/src/qml/jsruntime/qv4internalclass_p.h index 121238c555..42b61218a5 100644 --- a/src/qml/jsruntime/qv4internalclass_p.h +++ b/src/qml/jsruntime/qv4internalclass_p.h @@ -247,8 +247,12 @@ struct SharedInternalClassData { Q_ASSERT(pos == d->size()); if (pos == d->alloc()) d->grow(); - d->setSize(d->size() + 1); - d->set(pos, value); + if (pos >= d->alloc()) { + qBadAlloc(); + } else { + d->setSize(d->size() + 1); + d->set(pos, value); + } } void set(uint pos, T value) { diff --git a/src/qml/jsruntime/qv4memberdata.cpp b/src/qml/jsruntime/qv4memberdata.cpp index 246f857643..f327c85001 100644 --- a/src/qml/jsruntime/qv4memberdata.cpp +++ b/src/qml/jsruntime/qv4memberdata.cpp @@ -69,12 +69,25 @@ Heap::MemberData *MemberData::allocate(ExecutionEngine *e, uint n, Heap::MemberD size_t alloc = MemoryManager::align(sizeof(Heap::MemberData) + (n - 1)*sizeof(Value)); // round up to next power of two to avoid quadratic behaviour for very large objects alloc = nextPowerOfTwo(alloc); - Heap::MemberData *m = e->memoryManager->allocManaged(alloc); - if (old) + + // The above code can overflow in a number of interesting ways. All of those are unsigned, + // and therefore defined behavior. Still, apply some sane bounds. + if (alloc > std::numeric_limits::max()) + alloc = std::numeric_limits::max(); + + Heap::MemberData *m; + if (old) { + const size_t oldSize = sizeof(Heap::MemberData) + (old->values.size - 1) * sizeof(Value); + if (oldSize > alloc) + alloc = oldSize; + m = e->memoryManager->allocManaged(alloc); // no write barrier required here - memcpy(m, old, sizeof(Heap::MemberData) + (old->values.size - 1) * sizeof(Value)); - else + memcpy(m, old, oldSize); + } else { + m = e->memoryManager->allocManaged(alloc); m->init(); + } + m->values.alloc = static_cast((alloc - sizeof(Heap::MemberData) + sizeof(Value))/sizeof(Value)); m->values.size = m->values.alloc; return m; -- cgit v1.2.3 From 28a114cbf7a3c2c18948e8bd582e5b5463443cab Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 27 Mar 2019 13:23:18 +0100 Subject: Allow enums in QQmlType to be set up in two separate passes The baseMetaObject and the property cache can become available at different points in time. If we have initialized the enums before either of them is available we want to add the additional enums when the other one appears. Fixes: QTBUG-74677 Change-Id: I57276681a50b6c04181c6a29e736f2dc20632a0c Reviewed-by: Lars Knoll --- src/qml/qml/qqmlmetatype.cpp | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) (limited to 'src/qml') diff --git a/src/qml/qml/qqmlmetatype.cpp b/src/qml/qml/qqmlmetatype.cpp index 8db0bc92b8..f801e9aeba 100644 --- a/src/qml/qml/qqmlmetatype.cpp +++ b/src/qml/qml/qqmlmetatype.cpp @@ -261,7 +261,8 @@ public: int index; mutable volatile bool isSetup:1; - mutable volatile bool isEnumSetup:1; + mutable volatile bool isEnumFromCacheSetup:1; + mutable volatile bool isEnumFromBaseSetup:1; mutable bool haveSuperType:1; mutable QList metaObjects; mutable QStringHash enums; @@ -367,7 +368,8 @@ QHash QQmlTypePrivate::attachedPropertyIds; QQmlTypePrivate::QQmlTypePrivate(QQmlType::RegistrationType type) : refCount(1), regType(type), iid(nullptr), typeId(0), listId(0), revision(0), containsRevisionedAttributes(false), baseMetaObject(nullptr), - index(-1), isSetup(false), isEnumSetup(false), haveSuperType(false) + index(-1), isSetup(false), isEnumFromCacheSetup(false), isEnumFromBaseSetup(false), + haveSuperType(false) { switch (type) { case QQmlType::CppType: @@ -829,19 +831,24 @@ void QQmlTypePrivate::init() const void QQmlTypePrivate::initEnums(const QQmlPropertyCache *cache) const { - if (isEnumSetup) return; + if ((isEnumFromBaseSetup || !baseMetaObject) + && (isEnumFromCacheSetup || !cache)) { + return; + } init(); QMutexLocker lock(metaTypeDataLock()); - if (isEnumSetup) return; - if (cache) + if (!isEnumFromCacheSetup && cache) { insertEnumsFromPropertyCache(cache); - if (baseMetaObject) // could be singleton type without metaobject - insertEnums(baseMetaObject); + isEnumFromCacheSetup = true; + } - isEnumSetup = true; + if (!isEnumFromBaseSetup && baseMetaObject) { // could be singleton type without metaobject + insertEnums(baseMetaObject); + isEnumFromBaseSetup = true; + } } void QQmlTypePrivate::insertEnums(const QMetaObject *metaObject) const -- cgit v1.2.3 From aa51b3bf4ea854ffe7b39403289689daa428b221 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 27 Mar 2019 14:56:37 +0100 Subject: Avoid redundant slashes when creating qmldir URLs If the path starts with ":" and we assume it's an absolute qrc path, the only thing we need to do to make it a URL is prepend "qrc". Fixes: QTBUG-74677 Change-Id: Iee0239296b6f48ce909db02ae16e24dfc745b637 Reviewed-by: Lars Knoll --- src/qml/qml/qqmlimport.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/qml') diff --git a/src/qml/qml/qqmlimport.cpp b/src/qml/qml/qqmlimport.cpp index a31dbd1e08..a79d4d074c 100644 --- a/src/qml/qml/qqmlimport.cpp +++ b/src/qml/qml/qqmlimport.cpp @@ -1331,7 +1331,7 @@ bool QQmlImportsPrivate::locateQmldir(const QString &uri, int vmaj, int vmin, QQ QString url; const QStringRef absolutePath = absoluteFilePath.leftRef(absoluteFilePath.lastIndexOf(Slash) + 1); if (absolutePath.at(0) == Colon) - url = QLatin1String("qrc://") + absolutePath.mid(1); + url = QLatin1String("qrc") + absolutePath; else url = QUrl::fromLocalFile(absolutePath.toString()).toString(); -- cgit v1.2.3 From d10ddb040a0be1e179c60d0e364c7ab28b059e15 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 27 Mar 2019 16:03:57 +0100 Subject: Make sure we can delete value type providers in any order Previously the null provider had to be deleted last as it would leave a dangling pointer in the global static. This meant that we could not delete any other value providers on shutdown as the deletion order between different Q_GLOBAL_STATICs is undefined. Task-number: QTBUG-74348 Change-Id: I12aab6ab9ec4c71b90af69c5f824e0c6686433f5 Reviewed-by: Simon Hausmann --- src/qml/qml/qqmlglobal.cpp | 62 +++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 36 deletions(-) (limited to 'src/qml') diff --git a/src/qml/qml/qqmlglobal.cpp b/src/qml/qml/qqmlglobal.cpp index 1d60c518c4..a10ded6487 100644 --- a/src/qml/qml/qqmlglobal.cpp +++ b/src/qml/qml/qqmlglobal.cpp @@ -231,57 +231,47 @@ bool QQmlValueTypeProvider::store(int, const void *, void *, size_t) { return fa bool QQmlValueTypeProvider::read(const QVariant&, void *, int) { return false; } bool QQmlValueTypeProvider::write(int, const void *, QVariant&) { return false; } -Q_GLOBAL_STATIC(QQmlValueTypeProvider, nullValueTypeProvider) -static QQmlValueTypeProvider *valueTypeProvider = nullptr; +struct ValueTypeProviderList { + QQmlValueTypeProvider nullProvider; + QQmlValueTypeProvider *head = &nullProvider; +}; -static QQmlValueTypeProvider **getValueTypeProvider(void) -{ - if (valueTypeProvider == nullptr) { - valueTypeProvider = nullValueTypeProvider; - } - - return &valueTypeProvider; -} +Q_GLOBAL_STATIC(ValueTypeProviderList, valueTypeProviders) Q_QML_PRIVATE_EXPORT void QQml_addValueTypeProvider(QQmlValueTypeProvider *newProvider) { - static QQmlValueTypeProvider **providerPtr = getValueTypeProvider(); - newProvider->next = *providerPtr; - *providerPtr = newProvider; + if (ValueTypeProviderList *providers = valueTypeProviders()) { + newProvider->next = providers->head; + providers->head = newProvider; + } } Q_QML_PRIVATE_EXPORT void QQml_removeValueTypeProvider(QQmlValueTypeProvider *oldProvider) { - if (oldProvider == nullValueTypeProvider) { - // don't remove the null provider - // we get here when the QtQml library is being unloaded - return; - } - - // the only entry with next = 0 is the null provider - Q_ASSERT(oldProvider->next); - - QQmlValueTypeProvider *prev = valueTypeProvider; - if (prev == oldProvider) { - valueTypeProvider = oldProvider->next; - return; - } + if (ValueTypeProviderList *providers = valueTypeProviders()) { + QQmlValueTypeProvider *prev = providers->head; + if (prev == oldProvider) { + providers->head = oldProvider->next; + return; + } - // singly-linked list removal - for ( ; prev; prev = prev->next) { - if (prev->next != oldProvider) - continue; // this is not the provider you're looking for - prev->next = oldProvider->next; - return; + // singly-linked list removal + for (; prev; prev = prev->next) { + if (prev->next != oldProvider) + continue; // this is not the provider you're looking for + prev->next = oldProvider->next; + return; + } } qWarning("QQml_removeValueTypeProvider: was asked to remove provider %p but it was not found", oldProvider); } -Q_AUTOTEST_EXPORT QQmlValueTypeProvider *QQml_valueTypeProvider(void) +Q_AUTOTEST_EXPORT QQmlValueTypeProvider *QQml_valueTypeProvider() { - static QQmlValueTypeProvider **providerPtr = getValueTypeProvider(); - return *providerPtr; + if (ValueTypeProviderList *providers = valueTypeProviders()) + return providers->head; + return nullptr; } QQmlColorProvider::~QQmlColorProvider() {} -- cgit v1.2.3 From 9de640b8198c517411971a881c6b77a05a668123 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 12 Dec 2018 15:16:27 +0100 Subject: QML: Delete plugin loaders on shutdown The StringRegisteredPluginMap owns its QPluginLoaders. Therefore, it should delete them in the end. ASAN complains if not. Change-Id: Iecd425e340d13db5cecd44bb123818e2c2a3b6a5 Reviewed-by: Simon Hausmann Reviewed-by: BogDan Vatra --- src/qml/qml/qqmlimport.cpp | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/qml') diff --git a/src/qml/qml/qqmlimport.cpp b/src/qml/qml/qqmlimport.cpp index 454bd3abfb..09d430b5c0 100644 --- a/src/qml/qml/qqmlimport.cpp +++ b/src/qml/qml/qqmlimport.cpp @@ -141,6 +141,13 @@ struct RegisteredPlugin { struct StringRegisteredPluginMap : public QMap { QMutex mutex; + + ~StringRegisteredPluginMap() + { + QMutexLocker lock(&mutex); + for (const RegisteredPlugin &plugin : qAsConst(*this)) + delete plugin.loader; + } }; Q_GLOBAL_STATIC(StringRegisteredPluginMap, qmlEnginePluginsWithRegisteredTypes); // stores the uri and the PluginLoaders -- cgit v1.2.3 From d9f849b33591458fbb66724e4e6d59a1b87678f2 Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Fri, 23 Nov 2018 12:58:25 +0100 Subject: V4 tracing: Add a scheduler for the tracing JIT IR This is a scheduler for the graph nodes, which first reconstructs the control-flow graph, and then places all remaining nodes inside the basic blocks. The output of this pass is an MIFunction (MI = Machine Interface), which uses a representation suitable for feeding to an assembler. Note however that it still uses "virtual registers" at this point, so the next pass will have to place those virtual registers in physical registers or on a stack. The code for the dominator tree calculation, block scheduling, loop info and the blockset were lifted from the 5.10 JIT. Change-Id: I11c4cc3f64fedba6dd4275b35bbea85d30d76f7d Reviewed-by: Ulf Hermann --- src/qml/jit/jit.pri | 15 +- src/qml/jit/qv4blockscheduler.cpp | 208 +++++++++ src/qml/jit/qv4blockscheduler_p.h | 155 +++++++ src/qml/jit/qv4domtree.cpp | 436 ++++++++++++++++++ src/qml/jit/qv4domtree_p.h | 136 ++++++ src/qml/jit/qv4loopinfo.cpp | 199 +++++++++ src/qml/jit/qv4loopinfo_p.h | 159 +++++++ src/qml/jit/qv4mi.cpp | 251 +++++++++++ src/qml/jit/qv4mi_p.h | 627 ++++++++++++++++++++++++++ src/qml/jit/qv4miblockset_p.h | 291 ++++++++++++ src/qml/jit/qv4node_p.h | 22 +- src/qml/jit/qv4schedulers.cpp | 912 ++++++++++++++++++++++++++++++++++++++ src/qml/jit/qv4schedulers_p.h | 162 +++++++ src/qml/jit/qv4tracingjit.cpp | 7 + 14 files changed, 3559 insertions(+), 21 deletions(-) create mode 100644 src/qml/jit/qv4blockscheduler.cpp create mode 100644 src/qml/jit/qv4blockscheduler_p.h create mode 100644 src/qml/jit/qv4domtree.cpp create mode 100644 src/qml/jit/qv4domtree_p.h create mode 100644 src/qml/jit/qv4loopinfo.cpp create mode 100644 src/qml/jit/qv4loopinfo_p.h create mode 100644 src/qml/jit/qv4mi.cpp create mode 100644 src/qml/jit/qv4mi_p.h create mode 100644 src/qml/jit/qv4miblockset_p.h create mode 100644 src/qml/jit/qv4schedulers.cpp create mode 100644 src/qml/jit/qv4schedulers_p.h (limited to 'src/qml') diff --git a/src/qml/jit/jit.pri b/src/qml/jit/jit.pri index e8d5860498..bc0d20dba7 100644 --- a/src/qml/jit/jit.pri +++ b/src/qml/jit/jit.pri @@ -19,7 +19,12 @@ SOURCES += \ $$PWD/qv4graph.cpp \ $$PWD/qv4graphbuilder.cpp \ $$PWD/qv4lowering.cpp \ - $$PWD/qv4tracingjit.cpp + $$PWD/qv4tracingjit.cpp \ + $$PWD/qv4mi.cpp \ + $$PWD/qv4domtree.cpp \ + $$PWD/qv4schedulers.cpp \ + $$PWD/qv4blockscheduler.cpp \ + $$PWD/qv4loopinfo.cpp HEADERS += \ $$PWD/qv4ir_p.h \ @@ -28,5 +33,11 @@ HEADERS += \ $$PWD/qv4node_p.h \ $$PWD/qv4graph_p.h \ $$PWD/qv4graphbuilder_p.h \ - $$PWD/qv4lowering_p.h + $$PWD/qv4lowering_p.h \ + $$PWD/qv4mi_p.h \ + $$PWD/qv4miblockset_p.h \ + $$PWD/qv4domtree_p.h \ + $$PWD/qv4schedulers_p.h \ + $$PWD/qv4blockscheduler_p.h \ + $$PWD/qv4loopinfo_p.h } diff --git a/src/qml/jit/qv4blockscheduler.cpp b/src/qml/jit/qv4blockscheduler.cpp new file mode 100644 index 0000000000..3e2bfe15c5 --- /dev/null +++ b/src/qml/jit/qv4blockscheduler.cpp @@ -0,0 +1,208 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#include "qv4blockscheduler_p.h" +#include "qv4domtree_p.h" +#include "qv4loopinfo_p.h" + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +Q_LOGGING_CATEGORY(lcBlockScheduler, "qt.v4.ir.blockscheduler") + +bool BlockScheduler::checkCandidate(MIBlock *candidate) +{ + Q_ASSERT(loopInfo.loopHeaderFor(candidate) == currentGroup.group); + + for (MIBlock *pred : candidate->inEdges()) { + if (pred->isDeoptBlock()) + continue; + + if (emitted.alreadyProcessed(pred)) + continue; + + if (dominatorTree.dominates(candidate->index(), pred->index())) { + // this is a loop, where there in + // -> candidate edge is the jump back to the top of the loop. + continue; + } + + if (pred == candidate) + // this is a very tight loop, e.g.: + // L1: ... + // goto L1 + // This can happen when, for example, the basic-block merging gets rid of the empty + // body block. In this case, we can safely schedule this block (if all other + // incoming edges are either loop-back edges, or have been scheduled already). + continue; + + return false; // an incoming edge that is not yet emitted, and is not a back-edge + } + + if (loopInfo.isLoopHeader(candidate)) { + // postpone everything, and schedule the loop first. + postponedGroups.push(currentGroup); + currentGroup = WorkForGroup(candidate); + } + + return true; +} + +MIBlock *BlockScheduler::pickNext() +{ + while (true) { + while (currentGroup.postponed.isEmpty()) { + if (postponedGroups.isEmpty()) + return nullptr; + if (currentGroup.group) // record the first and the last node of a group + loopsStartEnd[currentGroup.group] = sequence.back(); + currentGroup = postponedGroups.pop(); + } + + MIBlock *next = currentGroup.postponed.pop(); + if (checkCandidate(next)) + return next; + } + + Q_UNREACHABLE(); + return nullptr; +} + +void BlockScheduler::emitBlock(MIBlock *bb) +{ + if (emitted.alreadyProcessed(bb)) + return; + + sequence.push_back(bb); + emitted.markAsProcessed(bb); +} + +void BlockScheduler::schedule(MIBlock *functionEntryPoint) +{ + MIBlock *next = functionEntryPoint; + + while (next) { + emitBlock(next); + // postpone all outgoing edges, if they were not already processed + QVarLengthArray nonExceptionEdges; + // first postpone all exception edges, so they will be processed last + for (int i = next->outEdges().size(); i != 0; ) { + --i; + MIBlock *out = next->outEdges().at(i); + if (emitted.alreadyProcessed(out)) + continue; + if (out == nullptr) + continue; + if (out->instructions().front().opcode() == Meta::OnException) + postpone(out); + else + nonExceptionEdges.append(out); + } + for (MIBlock *edge : nonExceptionEdges) + postpone(edge); + next = pickNext(); + } + + // finally schedule all de-optimization blocks at the end + for (auto bb : dominatorTree.function()->blocks()) { + if (bb->isDeoptBlock()) + emitBlock(bb); + } +} + +void BlockScheduler::postpone(MIBlock *bb) +{ + if (currentGroup.group == loopInfo.loopHeaderFor(bb)) { + currentGroup.postponed.append(bb); + return; + } + + for (int i = postponedGroups.size(); i != 0; ) { + --i; + WorkForGroup &g = postponedGroups[i]; + if (g.group == loopInfo.loopHeaderFor(bb)) { + g.postponed.append(bb); + return; + } + } + + Q_UNREACHABLE(); +} + +void BlockScheduler::dump() const +{ + if (!lcBlockScheduler().isDebugEnabled()) + return; + + QString s = QStringLiteral("Scheduled blocks:\n"); + for (auto *bb : sequence) { + s += QLatin1String(" L") + QString::number(bb->index()); + MIBlock *loopEnd = loopsStartEnd[bb]; + if (loopEnd) + s += QLatin1String(", loop start, ends at L") + QString::number(loopEnd->index()); + s += QLatin1Char('\n'); + } + qCDebug(lcBlockScheduler).noquote().nospace() << s; +} + +BlockScheduler::BlockScheduler(const DominatorTree &dominatorTree, const LoopInfo &loopInfo) + : dominatorTree(dominatorTree) + , loopInfo(loopInfo) + , sequence(0) + , emitted(dominatorTree.function()) +{ + schedule(dominatorTree.function()->blocks().front()); + + dump(); + + if (dominatorTree.function()->blockCount() != sequence.size()) { + qFatal("The block scheduler did not schedule all blocks. This is most likely due to" + "a non-natural loop."); + // Usually caused by having an execution path that manages to skip over unwind handler + // reset: any exception happening after will jump back to the unwind handler, and thereby + // creating a loop that can be entered in 2 different ways. + } +} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4blockscheduler_p.h b/src/qml/jit/qv4blockscheduler_p.h new file mode 100644 index 0000000000..1289fda9f0 --- /dev/null +++ b/src/qml/jit/qv4blockscheduler_p.h @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4BLOCKSCHEDULER_P_H +#define QV4BLOCKSCHEDULER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include "qv4mi_p.h" +#include "qv4util_p.h" + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +class DominatorTree; +class LoopInfo; + +// High-level algorithm: +// 0. start with the first node (the start node) of a function +// 1. emit the node +// 2. add all outgoing edges that are not yet emitted to the postponed stack +// 3. When the postponed stack is empty, pop a stack from the loop stack. If that is empty too, +// we're done. +// 4. pop a node from the postponed stack, and check if it can be scheduled: +// a. if all incoming edges are scheduled, go to 4. +// b. if an incoming edge is unscheduled, but it's a back-edge (an edge in a loop that jumps +// back to the start of the loop), ignore it +// c. if there is any unscheduled edge that is not a back-edge, ignore this node, and go to 4. +// 5. if this node is the start of a loop, push the postponed stack on the loop stack. +// 6. go back to 1. +// +// The postponing action in step 2 will put the node into its containing group. The case where this +// is important is when a (labeled) continue or a (labeled) break statement occur in a loop: the +// outgoing edge points to a node that is not part of the current loop (and possibly not of the +// parent loop). +// +// Linear scan register allocation benefits greatly from short life-time intervals with few holes +// (see for example section 4 (Lifetime Analysis) of [Wimmer1]). This algorithm makes sure that the +// blocks of a group are scheduled together, with no non-loop blocks in between. This applies +// recursively for nested loops. It also schedules groups of if-then-else-endif blocks together for +// the same reason. +class BlockScheduler +{ + const DominatorTree &dominatorTree; + const LoopInfo &loopInfo; + + struct WorkForGroup + { + MIBlock *group; + QStack postponed; + + WorkForGroup(MIBlock *group = nullptr) : group(group) {} + }; + WorkForGroup currentGroup; + QStack postponedGroups; + std::vector sequence; + + class ProcessedBlocks + { + BitVector processed; + + public: + ProcessedBlocks(MIFunction *function) + : processed(int(function->blockCount()), false) + {} + + bool alreadyProcessed(MIBlock *bb) const + { + Q_ASSERT(bb); + + return processed.at(bb->index()); + } + + void markAsProcessed(MIBlock *bb) + { + processed.setBit(bb->index()); + } + } emitted; + QHash loopsStartEnd; + + bool checkCandidate(MIBlock *candidate); + MIBlock *pickNext(); + void emitBlock(MIBlock *bb); + void schedule(MIBlock *functionEntryPoint); + void postpone(MIBlock *bb); + void dump() const; + +public: + BlockScheduler(const DominatorTree &dominatorTree, const LoopInfo &loopInfo); + + const std::vector &scheduledBlockSequence() const + { return sequence; } + + QHash loopEndsByStartBlock() const + { return loopsStartEnd; } +}; + + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4BLOCKSCHEDULER_P_H diff --git a/src/qml/jit/qv4domtree.cpp b/src/qml/jit/qv4domtree.cpp new file mode 100644 index 0000000000..9484f4e2dc --- /dev/null +++ b/src/qml/jit/qv4domtree.cpp @@ -0,0 +1,436 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include "qv4domtree_p.h" + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +Q_LOGGING_CATEGORY(lcDomTree, "qt.v4.ir.domTree") +Q_LOGGING_CATEGORY(lcDomFrontier, "qt.v4.ir.domFrontier") + +DominatorTree::DominatorTree(MIFunction *f) + : m_function(f) + , m_data(new Data) +{ + calculateIDoms(); + m_data.reset(); +} + +void DominatorTree::dumpImmediateDominators() const +{ + QBuffer buf; + buf.open(QIODevice::WriteOnly); + QTextStream qout(&buf); + qout << "Immediate dominators for " << m_function->irFunction()->name() << ":" << endl; + for (MIBlock *to : m_function->blocks()) { + MIBlock::Index from = m_idom.at(to->index()); + if (from != MIBlock::InvalidIndex) + qout << " " << from; + else + qout << " (none)"; + qout << " dominates " << to->index() << endl; + } + qCDebug(lcDomTree, "%s", buf.data().constData()); +} + +void DominatorTree::setImmediateDominator(MIBlock::Index dominated, MIBlock::Index dominator) +{ + if (m_idom.size() <= size_t(dominated)) + m_idom.resize(dominated + 1); + m_idom[dominated] = dominator; +} + +bool DominatorTree::dominates(MIBlock::Index dominator, MIBlock::Index dominated) const +{ + // dominator can be Invalid when the dominated block has no dominator (i.e. the start node) + Q_ASSERT(dominated != MIBlock::InvalidIndex); + + if (dominator == dominated) + return false; + + for (MIBlock::Index it = m_idom[dominated]; it != MIBlock::InvalidIndex; it = m_idom[it]) { + if (it == dominator) + return true; + } + + return false; +} + +// Calculate a depth-first iteration order on the nodes of the dominator tree. +// +// The order of the nodes in the vector is not the same as one where a recursive depth-first +// iteration is done on a tree. Rather, the nodes are (reverse) sorted on tree depth. +// So for the: +// 1 dominates 2 +// 2 dominates 3 +// 3 dominates 4 +// 2 dominates 5 +// the order will be: +// 4, 3, 5, 2, 1 +// or: +// 4, 5, 3, 2, 1 +// So the order of nodes on the same depth is undefined, but it will be after the nodes +// they dominate, and before the nodes that dominate them. +// +// The reason for this order is that a proper DFS pre-/post-order would require inverting +// the idom vector by either building a real tree datastructure or by searching the idoms +// for siblings and children. Both have a higher time complexity than sorting by depth. +std::vector DominatorTree::calculateDFNodeIterOrder() const +{ + std::vector depths = calculateNodeDepths(); + std::vector order = m_function->blocks(); + std::sort(order.begin(), order.end(), [&depths](MIBlock *one, MIBlock *two) -> bool { + return depths.at(one->index()) > depths.at(two->index()); + }); + return order; +} + +// Algorithm: +// - for each node: +// - get the depth of a node. If it's unknown (-1): +// - get the depth of the immediate dominator. +// - if that's unknown too, calculate it by calling calculateNodeDepth +// - set the current node's depth to that of immediate dominator + 1 +std::vector DominatorTree::calculateNodeDepths() const +{ + std::vector nodeDepths(size_t(m_function->blockCount()), -1); + for (MIBlock *bb : m_function->blocks()) { + int &bbDepth = nodeDepths[bb->index()]; + if (bbDepth == -1) { + const int immDom = m_idom[bb->index()]; + if (immDom == -1) { + // no immediate dominator, so it's either the start block, or an unreachable block + bbDepth = 0; + } else { + int immDomDepth = nodeDepths[immDom]; + if (immDomDepth == -1) + immDomDepth = calculateNodeDepth(immDom, nodeDepths); + bbDepth = immDomDepth + 1; + } + } + } + return nodeDepths; +} + +// Algorithm: +// - search for the first dominator of a node that has a known depth. As all nodes are +// reachable from the start node, and that node's depth is 0, this is finite. +// - while doing that search, put all unknown nodes in the worklist +// - pop all nodes from the worklist, and set their depth to the previous' (== dominating) +// node's depth + 1 +// This way every node's depth is calculated once, and the complexity is O(n). +int DominatorTree::calculateNodeDepth(MIBlock::Index nodeIdx, std::vector &nodeDepths) const +{ + std::vector worklist; + worklist.reserve(8); + int depth = -1; + + do { + worklist.push_back(nodeIdx); + nodeIdx = m_idom[nodeIdx]; + depth = nodeDepths[nodeIdx]; + } while (depth == -1); + + for (auto it = worklist.rbegin(), eit = worklist.rend(); it != eit; ++it) + nodeDepths[*it] = ++depth; + + return depth; +} + +namespace { +struct DFSTodo { + MIBlock::Index node = MIBlock::InvalidIndex; + MIBlock::Index parent = MIBlock::InvalidIndex; + + DFSTodo() = default; + DFSTodo(MIBlock::Index node, MIBlock::Index parent) + : node(node) + , parent(parent) + {} +}; +} // anonymous namespace + +void DominatorTree::dfs(MIBlock::Index node) +{ + std::vector worklist; + worklist.reserve(m_data->vertex.capacity() / 2); + DFSTodo todo(node, MIBlock::InvalidIndex); + + while (true) { + MIBlock::Index n = todo.node; + + if (m_data->dfnum[n] == 0) { + m_data->dfnum[n] = m_data->size; + m_data->vertex[m_data->size] = n; + m_data->parent[n] = todo.parent; + ++m_data->size; + + MIBlock::OutEdges out = m_function->block(n)->outEdges(); + for (int i = out.size() - 1; i > 0; --i) + worklist.emplace_back(out[i]->index(), n); + + if (!out.isEmpty()) { + todo.node = out.first()->index(); + todo.parent = n; + continue; + } + } + + if (worklist.empty()) + break; + + todo = worklist.back(); + worklist.pop_back(); + } +} + +void DominatorTree::link(MIBlock::Index p, MIBlock::Index n) +{ + m_data->ancestor[n] = p; + m_data->best[n] = n; +} + +void DominatorTree::calculateIDoms() +{ + Q_ASSERT(m_function->block(0)->inEdges().count() == 0); + + const size_t bbCount = m_function->blockCount(); + m_data->vertex = std::vector(bbCount, MIBlock::InvalidIndex); + m_data->parent = std::vector(bbCount, MIBlock::InvalidIndex); + m_data->dfnum = std::vector(bbCount, 0); + m_data->semi = std::vector(bbCount, MIBlock::InvalidIndex); + m_data->ancestor = std::vector(bbCount, MIBlock::InvalidIndex); + m_idom = std::vector(bbCount, MIBlock::InvalidIndex); + m_data->samedom = std::vector(bbCount, MIBlock::InvalidIndex); + m_data->best = std::vector(bbCount, MIBlock::InvalidIndex); + + QHash> bucket; + bucket.reserve(int(bbCount)); + + dfs(m_function->block(0)->index()); + + std::vector worklist; + worklist.reserve(m_data->vertex.capacity() / 2); + + for (int i = m_data->size - 1; i > 0; --i) { + MIBlock::Index n = m_data->vertex[i]; + MIBlock::Index p = m_data->parent[n]; + MIBlock::Index s = p; + + for (auto inEdge : m_function->block(n)->inEdges()) { + if (inEdge->isDeoptBlock()) + continue; + MIBlock::Index v = inEdge->index(); + MIBlock::Index ss = MIBlock::InvalidIndex; + if (m_data->dfnum[v] <= m_data->dfnum[n]) + ss = v; + else + ss = m_data->semi[ancestorWithLowestSemi(v, worklist)]; + if (m_data->dfnum[ss] < m_data->dfnum[s]) + s = ss; + } + m_data->semi[n] = s; + bucket[s].push_back(n); + link(p, n); + if (bucket.contains(p)) { + for (MIBlock::Index v : bucket[p]) { + MIBlock::Index y = ancestorWithLowestSemi(v, worklist); + MIBlock::Index semi_v = m_data->semi[v]; + if (m_data->semi[y] == semi_v) + m_idom[v] = semi_v; + else + m_data->samedom[v] = y; + } + bucket.remove(p); + } + } + + for (unsigned i = 1; i < m_data->size; ++i) { + MIBlock::Index n = m_data->vertex[i]; + Q_ASSERT(n != MIBlock::InvalidIndex); + Q_ASSERT(!bucket.contains(n)); + Q_ASSERT(m_data->ancestor[n] != MIBlock::InvalidIndex); + Q_ASSERT((m_data->semi[n] != MIBlock::InvalidIndex + && m_data->dfnum[m_data->ancestor[n]] <= m_data->dfnum[m_data->semi[n]]) + || m_data->semi[n] == n); + MIBlock::Index sdn = m_data->samedom[n]; + if (sdn != MIBlock::InvalidIndex) + m_idom[n] = m_idom[sdn]; + } + + if (lcDomTree().isDebugEnabled()) + dumpImmediateDominators(); + + m_data.reset(nullptr); +} + +MIBlock::Index DominatorTree::ancestorWithLowestSemi(MIBlock::Index v, + std::vector &worklist) +{ + worklist.clear(); + for (MIBlock::Index it = v; it != MIBlock::InvalidIndex; it = m_data->ancestor[it]) + worklist.push_back(it); + + if (worklist.size() < 2) + return m_data->best[v]; + + MIBlock::Index b = MIBlock::InvalidIndex; + MIBlock::Index last = worklist.back(); + Q_ASSERT(worklist.size() <= INT_MAX); + for (int it = static_cast(worklist.size()) - 2; it >= 0; --it) { + MIBlock::Index bbIt = worklist[it]; + m_data->ancestor[bbIt] = last; + MIBlock::Index &best_it = m_data->best[bbIt]; + if (b != MIBlock::InvalidIndex + && m_data->dfnum[m_data->semi[b]] < m_data->dfnum[m_data->semi[best_it]]) { + best_it = b; + } else { + b = best_it; + } + } + return b; +} + +void DominatorFrontier::compute(const DominatorTree &domTree) +{ + struct NodeProgress { + std::vector children; + std::vector todo; + }; + + MIFunction *function = domTree.function(); + m_df.resize(function->blockCount()); + + // compute children of each node in the dominator tree + std::vector > children; // BasicBlock index -> children + children.resize(function->blockCount()); + for (MIBlock *n : function->blocks()) { + const MIBlock::Index nodeIndex = n->index(); + Q_ASSERT(function->block(nodeIndex) == n); + const MIBlock::Index nodeDominator = domTree.immediateDominator(nodeIndex); + if (nodeDominator == MIBlock::InvalidIndex) + continue; // there is no dominator to add this node to as a child (e.g. the start node) + children[nodeDominator].push_back(nodeIndex); + } + + // Fill the worklist and initialize the node status for each basic-block + std::vector nodeStatus; + nodeStatus.resize(function->blockCount()); + std::vector worklist; + worklist.reserve(function->blockCount()); + for (MIBlock *bb : function->blocks()) { + MIBlock::Index nodeIndex = bb->index(); + worklist.push_back(nodeIndex); + NodeProgress &np = nodeStatus[nodeIndex]; + np.children = children[nodeIndex]; + np.todo = children[nodeIndex]; + } + + BitVector DF_done(int(function->blockCount()), false); + + while (!worklist.empty()) { + MIBlock::Index node = worklist.back(); + + if (DF_done.at(node)) { + worklist.pop_back(); + continue; + } + + NodeProgress &np = nodeStatus[node]; + auto it = np.todo.begin(); + while (it != np.todo.end()) { + if (DF_done.at(*it)) { + it = np.todo.erase(it); + } else { + worklist.push_back(*it); + break; + } + } + + if (np.todo.empty()) { + MIBlockSet &miBlockSet = m_df[node]; + miBlockSet.init(function); + for (MIBlock *y : function->block(node)->outEdges()) { + if (domTree.immediateDominator(y->index()) != node) + miBlockSet.insert(y); + } + for (MIBlock::Index child : np.children) { + const MIBlockSet &ws = m_df[child]; + for (auto w : ws) { + const MIBlock::Index wIndex = w->index(); + if (node == wIndex || !domTree.dominates(node, w->index())) + miBlockSet.insert(w); + } + } + DF_done.setBit(node); + worklist.pop_back(); + } + } + + if (lcDomFrontier().isDebugEnabled()) + dump(domTree.function()); +} + +void DominatorFrontier::dump(MIFunction *function) +{ + QBuffer buf; + buf.open(QIODevice::WriteOnly); + QTextStream qout(&buf); + qout << "Dominator Frontiers:" << endl; + for (MIBlock *n : function->blocks()) { + qout << "\tDF[" << n->index() << "]: {"; + const MIBlockSet &SList = m_df[n->index()]; + for (MIBlockSet::const_iterator i = SList.begin(), ei = SList.end(); i != ei; ++i) { + if (i != SList.begin()) + qout << ", "; + qout << (*i)->index(); + } + qout << "}" << endl; + } + qCDebug(lcDomFrontier, "%s", buf.data().constData()); +} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4domtree_p.h b/src/qml/jit/qv4domtree_p.h new file mode 100644 index 0000000000..703e17ab61 --- /dev/null +++ b/src/qml/jit/qv4domtree_p.h @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4DOMTREE_P_H +#define QV4DOMTREE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4mi_p.h" +#include "qv4miblockset_p.h" + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +class DominatorTree +{ + Q_DISABLE_COPY_MOVE(DominatorTree) + +public: + DominatorTree(MIFunction *f); + ~DominatorTree() = default; + + void dumpImmediateDominators() const; + MIFunction *function() const + { return m_function; } + + void setImmediateDominator(MIBlock::Index dominated, MIBlock::Index dominator); + + MIBlock::Index immediateDominator(MIBlock::Index blockIndex) const + { return m_idom[blockIndex]; } + + bool dominates(MIBlock::Index dominator, MIBlock::Index dominated) const; + + bool insideSameDominatorChain(MIBlock::Index one, MIBlock::Index other) const + { return one == other || dominates(one, other) || dominates(other, one); } + + std::vector calculateDFNodeIterOrder() const; + + std::vector calculateNodeDepths() const; + +private: // functions + int calculateNodeDepth(MIBlock::Index nodeIdx, std::vector &nodeDepths) const; + void link(MIBlock::Index p, MIBlock::Index n); + void calculateIDoms(); + void dfs(MIBlock::Index node); + MIBlock::Index ancestorWithLowestSemi(MIBlock::Index v, std::vector &worklist); + +private: // data + struct Data { + std::vector dfnum; // MIBlock index -> dfnum + std::vector vertex; + std::vector parent; // MIBlock index -> parent MIBlock index + std::vector ancestor; // MIBlock index -> ancestor MIBlock index + std::vector best; // MIBlock index -> best MIBlock index + std::vector semi; // MIBlock index -> semi dominator MIBlock index + std::vector samedom; // MIBlock index -> same dominator MIBlock index + unsigned size = 0; + }; + + MIFunction *m_function; + QScopedPointer m_data; + std::vector m_idom; // MIBlock index -> immediate dominator MIBlock index +}; + +class DominatorFrontier +{ +public: + DominatorFrontier(const DominatorTree &domTree) + { compute(domTree); } + + const MIBlockSet &operator[](MIBlock *n) const + { return m_df[n->index()]; } + +private: // functions + void compute(const DominatorTree &domTree); + void dump(MIFunction *function); + +private: // data + std::vector m_df; // MIBlock index -> dominator frontier +}; + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4DOMTREE_P_H diff --git a/src/qml/jit/qv4loopinfo.cpp b/src/qml/jit/qv4loopinfo.cpp new file mode 100644 index 0000000000..0366c49e30 --- /dev/null +++ b/src/qml/jit/qv4loopinfo.cpp @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#include "qv4loopinfo_p.h" +#include "qv4domtree_p.h" + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +Q_LOGGING_CATEGORY(lcLoopinfo, "qt.v4.ir.loopinfo") + +void LoopInfo::detectLoops() +{ + blockInfos.resize(dt.function()->blockCount()); + + std::vector backedges; + backedges.reserve(4); + + const auto order = dt.calculateDFNodeIterOrder(); + for (MIBlock *bb : order) { + if (bb->isDeoptBlock()) + continue; + + backedges.clear(); + + for (MIBlock *pred : bb->inEdges()) { + if (bb == pred || dt.dominates(bb->index(), pred->index())) + backedges.push_back(pred); + } + + if (!backedges.empty()) + subLoop(bb, backedges); + } + + collectLoopExits(); + + dump(); +} + +void LoopInfo::collectLoopExits() +{ + for (MIBlock::Index i = 0, ei = MIBlock::Index(blockInfos.size()); i != ei; ++i) { + BlockInfo &bi = blockInfos[i]; + MIBlock *currentBlock = dt.function()->block(i); + if (bi.isLoopHeader) { + for (MIBlock *outEdge : currentBlock->outEdges()) { + if (outEdge != currentBlock && !inLoopOrSubLoop(outEdge, currentBlock)) + bi.loopExits.push_back(outEdge); + } + } + if (MIBlock *containingLoop = bi.loopHeader) { + BlockInfo &loopInfo = blockInfos[containingLoop->index()]; + for (MIBlock *outEdge : currentBlock->outEdges()) { + if (outEdge != containingLoop && !inLoopOrSubLoop(outEdge, containingLoop)) + loopInfo.loopExits.push_back(outEdge); + } + } + } +} + +bool LoopInfo::inLoopOrSubLoop(MIBlock *block, MIBlock *loopHeader) const +{ + const BlockInfo &bi = blockInfos[block->index()]; + MIBlock *loopHeaderForBlock = bi.loopHeader; + if (loopHeaderForBlock == nullptr) + return false; // block is not in any loop + + while (loopHeader) { + if (loopHeader == loopHeaderForBlock) + return true; + // look into the parent loop of loopHeader to see if block is contained there + loopHeader = blockInfos[loopHeader->index()].loopHeader; + } + + return false; +} + +void LoopInfo::subLoop(MIBlock *loopHead, const std::vector &backedges) +{ + blockInfos[loopHead->index()].isLoopHeader = true; + + std::vector worklist; + worklist.reserve(backedges.size() + 8); + worklist.insert(worklist.end(), backedges.begin(), backedges.end()); + while (!worklist.empty()) { + MIBlock *predIt = worklist.back(); + worklist.pop_back(); + + MIBlock *subloop = blockInfos[predIt->index()].loopHeader; + if (subloop) { + // This is a discovered block. Find its outermost discovered loop. + while (MIBlock *parentLoop = blockInfos[subloop->index()].loopHeader) + subloop = parentLoop; + + // If it is already discovered to be a subloop of this loop, continue. + if (subloop == loopHead) + continue; + + // Yay, it's a subloop of this loop. + blockInfos[subloop->index()].loopHeader = loopHead; + predIt = subloop; + + // Add all predecessors of the subloop header to the worklist, as long as + // those predecessors are not in the current subloop. It might be the case + // that they are in other loops, which we will then add as a subloop to the + // current loop. + for (MIBlock *predIn : predIt->inEdges()) + if (blockInfos[predIn->index()].loopHeader != subloop) + worklist.push_back(predIn); + } else { + if (predIt == loopHead) + continue; + + // This is an undiscovered block. Map it to the current loop. + blockInfos[predIt->index()].loopHeader = loopHead; + + // Add all incoming edges to the worklist. + for (MIBlock *bb : predIt->inEdges()) + worklist.push_back(bb); + } + } +} + +void LoopInfo::dump() const +{ + if (!lcLoopinfo().isDebugEnabled()) + return; + + QString s = QStringLiteral("Loop information:\n"); + for (size_t i = 0, ei = blockInfos.size(); i != ei; ++i) { + const BlockInfo &bi = blockInfos[i]; + s += QStringLiteral(" %1 : is loop header: %2, contained in loop header's loop: ") + .arg(i).arg(bi.isLoopHeader ? QLatin1String("yes") : QLatin1String("no")); + if (bi.loopHeader) + s += QString::number(bi.loopHeader->index()); + else + s += QLatin1String(""); + if (bi.isLoopHeader) { + s += QStringLiteral(", loop exits: "); + if (bi.loopExits.empty()) { + s += QLatin1String(""); + } else { + bool first = true; + for (MIBlock *exit : bi.loopExits) { + if (first) + first = false; + else + s += QStringLiteral(", "); + s += QString::number(exit->index()); + } + } + } + s += QLatin1Char('\n'); + } + qCDebug(lcLoopinfo).noquote().nospace() << s; +} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4loopinfo_p.h b/src/qml/jit/qv4loopinfo_p.h new file mode 100644 index 0000000000..6a865e6dc6 --- /dev/null +++ b/src/qml/jit/qv4loopinfo_p.h @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4LOOPINFO_P_H +#define QV4LOOPINFO_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4mi_p.h" + +#include + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +class DominatorTree; + +// Detect all (sub-)loops in a function. +// +// Doing loop detection on the CFG is better than relying on the statement information in +// order to mark loops. Although JavaScript only has natural loops, it can still be the case +// that something is not a loop even though a loop-like-statement is in the source. For +// example: +// while (true) { +// if (i > 0) +// break; +// else +// break; +// } +// +// Algorithm: +// - do a DFS on the dominator tree, where for each node: +// - collect all back-edges +// - if there are back-edges, the node is a loop-header for a new loop, so: +// - walk the CFG is reverse-direction, and for every node: +// - if the node already belongs to a loop, we've found a nested loop: +// - get the loop-header for the (outermost) nested loop +// - add that loop-header to the current loop +// - continue by walking all incoming edges that do not yet belong to the current loop +// - if the node does not belong to a loop yet, add it to the current loop, and +// go on with all incoming edges +// +// Loop-header detection by checking for back-edges is very straight forward: a back-edge is +// an incoming edge where the other node is dominated by the current node. Meaning: all +// execution paths that reach that other node have to go through the current node, that other +// node ends with a (conditional) jump back to the loop header. +// +// The exact order of the DFS on the dominator tree is not important. The only property has to +// be that a node is only visited when all the nodes it dominates have been visited before. +// The reason for the DFS is that for nested loops, the inner loop's loop-header is dominated +// by the outer loop's header. So, by visiting depth-first, sub-loops are identified before +// their containing loops, which makes nested-loop identification free. An added benefit is +// that the nodes for those sub-loops are only processed once. +// +// Note: independent loops that share the same header are merged together. For example, in +// the code snippet below, there are 2 back-edges into the loop-header, but only one single +// loop will be detected. +// while (a) { +// if (b) +// continue; +// else +// continue; +// } +class LoopInfo +{ + Q_DISABLE_COPY_MOVE(LoopInfo) + + struct BlockInfo + { + MIBlock *loopHeader = nullptr; + bool isLoopHeader = false; + std::vector loopExits; + }; + +public: + LoopInfo(const DominatorTree &dt) + : dt(dt) + {} + + ~LoopInfo() = default; + + void detectLoops(); + + MIBlock *loopHeaderFor(MIBlock *bodyBlock) const + { return blockInfos[bodyBlock->index()].loopHeader; } + + bool isLoopHeader(MIBlock *block) const + { return blockInfos[block->index()].isLoopHeader; } + + const std::vector loopExitsForLoop(MIBlock *loopHeader) const + { return blockInfos[loopHeader->index()].loopExits; } + +private: + void subLoop(MIBlock *loopHead, const std::vector &backedges); + void collectLoopExits(); + bool inLoopOrSubLoop(MIBlock *block, MIBlock *loopHeader) const; + + void dump() const; + +private: + const DominatorTree &dt; + std::vector blockInfos; +}; + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4LOOPINFO_P_H diff --git a/src/qml/jit/qv4mi.cpp b/src/qml/jit/qv4mi.cpp new file mode 100644 index 0000000000..f0b172243d --- /dev/null +++ b/src/qml/jit/qv4mi.cpp @@ -0,0 +1,251 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include "qv4mi_p.h" +#include "qv4node_p.h" + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +Q_LOGGING_CATEGORY(lcMI, "qt.v4.ir.mi") + +QString MIOperand::debugString() const +{ + switch (kind()) { + case Invalid: return QStringLiteral("<>"); + case Constant: return ConstantPayload::debugString(constantValue()); + case VirtualRegister: return QStringLiteral("vreg%1").arg(virtualRegister()); + case EngineRegister: return QStringLiteral("engine"); + case CppFrameRegister: return QStringLiteral("cppFrame"); + case Function: return QStringLiteral("function"); + case JSStackSlot: return QStringLiteral("jsstack[%1]").arg(stackSlot()); + case BoolStackSlot: return QStringLiteral("bstack[%1]").arg(stackSlot()); + case JumpTarget: return targetBlock() ? QStringLiteral("L%1").arg(targetBlock()->index()) + : QStringLiteral("<>"); + default: Q_UNREACHABLE(); + } +} + +MIInstr *MIInstr::create(QQmlJS::MemoryPool *pool, Node *irNode, unsigned nOperands) +{ + return pool->New(irNode, pool, nOperands); +} + +static QString commentIndent(const QString &line) +{ + int spacing = std::max(70 - line.length(), 1); + return line + QString(spacing, QLatin1Char(' ')); +} + +static QString indent(int nr) +{ + QString s = nr == -1 ? QString() : QString::number(nr); + int padding = 6 - s.size(); + if (padding > 0) + s = QString(padding, QLatin1Char(' ')) + s; + return s; +} + +MIFunction::MIFunction(Function *irFunction) + : m_irFunction(irFunction) +{} + +void MIFunction::renumberBlocks() +{ + for (size_t i = 0, ei = m_blocks.size(); i != ei; ++i) { + MIBlock *b = m_blocks[i]; + b->setIndex(unsigned(i)); + } +} + +void MIFunction::renumberInstructions() +{ + int pos = 0; + for (MIBlock *b : m_blocks) { + for (MIInstr &instr : b->instructions()) { + pos += 2; + instr.setPosition(pos); + } + } +} + +void MIFunction::dump(const QString &description) const +{ + if (!lcMI().isDebugEnabled()) + return; + + QString s = description + QLatin1String(":\n"); + QString name; + if (auto n = irFunction()->v4Function()->name()) + name = n->toQString(); + if (name.isEmpty()) + QString::asprintf("%p", static_cast(irFunction()->v4Function())); + QString line = QStringLiteral("function %1 {").arg(name); + auto loc = irFunction()->v4Function()->sourceLocation(); + s += commentIndent(line) + QStringLiteral("; %1:%2:%3\n").arg(loc.sourceFile, + QString::number(loc.line), + QString::number(loc.column)); + for (const MIBlock *b : blocks()) { + line = QStringLiteral("L%1").arg(b->index()); + bool first = true; + if (!b->arguments().empty()) { + line += QLatin1Char('('); + for (const MIOperand &arg : b->arguments()) { + if (first) + first = false; + else + line += QStringLiteral(", "); + line += arg.debugString(); + } + line += QLatin1Char(')'); + } + line += QLatin1Char(':'); + line = commentIndent(line) + QStringLiteral("; preds: "); + if (b->inEdges().isEmpty()) { + line += QStringLiteral(""); + } else { + bool first = true; + for (MIBlock *in : b->inEdges()) { + if (first) + first = false; + else + line += QStringLiteral(", "); + line += QStringLiteral("L%1").arg(in->index()); + } + } + s += line + QLatin1Char('\n'); + for (const MIInstr &i : b->instructions()) { + line = indent(i.position()) + QLatin1String(": "); + if (i.hasDestination()) + line += i.destination().debugString() + QStringLiteral(" = "); + line += i.irNode()->operation()->debugString(); + bool first = true; + for (const MIOperand &op : i.operands()) { + if (first) + first = false; + else + line += QLatin1Char(','); + line += QLatin1Char(' ') + op.debugString(); + } + line = commentIndent(line) + QStringLiteral("; node-id: %1").arg(i.irNode()->id()); + if (i.irNode()->operation()->needsBytecodeOffsets()) + line += QStringLiteral(", bytecode-offset: %1").arg(irFunction()->nodeInfo(i.irNode())->currentInstructionOffset()); + s += line + QLatin1Char('\n'); + } + s += commentIndent(QString()) + QStringLiteral("; succs: "); + if (b->outEdges().isEmpty()) { + s += QStringLiteral(""); + } else { + bool first = true; + for (MIBlock *succ : b->outEdges()) { + if (first) + first = false; + else + s += QStringLiteral(", "); + s += QStringLiteral("L%1").arg(succ->index()); + } + } + s += QLatin1Char('\n'); + } + s += QLatin1Char('}'); + + for (const QStringRef &line : s.splitRef('\n')) + qCDebug(lcMI).noquote().nospace() << line; +} + +unsigned MIFunction::extraJSSlots() const +{ + uint interpreterFrameSize = CppStackFrame::requiredJSStackFrameSize(irFunction()->v4Function()); + if (m_jsSlotCount <= interpreterFrameSize) + return 0; + return m_jsSlotCount - interpreterFrameSize; +} + +void MIFunction::setStartBlock(MIBlock *newStartBlock) +{ + auto it = std::find(m_blocks.begin(), m_blocks.end(), newStartBlock); + Q_ASSERT(it != m_blocks.end()); + std::swap(*m_blocks.begin(), *it); +} + +void MIFunction::setStackSlotCounts(unsigned dword, unsigned qword, unsigned js) +{ + m_vregCount = 0; + m_dwordSlotCount = dword; + m_qwordSlotCount = qword; + m_jsSlotCount = js; +} + +void MIFunction::verifyCFG() const +{ + if (block(MIFunction::StartBlockIndex)->instructions().front().opcode() != Meta::Start) + qFatal("MIFunction block 0 is not the start block"); + + for (MIBlock *b : m_blocks) { + for (MIBlock *in : b->inEdges()) { + if (!in->outEdges().contains(b)) + qFatal("block %u has incoming edge from block %u, " + "but does not appear in that block's outgoing edges", + b->index(), in->index()); + } + for (MIBlock *out : b->outEdges()) { + if (!out->inEdges().contains(b)) + qFatal("block %u has outgoing edge from block %u, " + "but does not appear in that block's incoming edges", + b->index(), out->index()); + } + } +} + +MIBlock *MIBlock::findEdgeTo(Operation::Kind target) const +{ + for (MIBlock *outEdge : outEdges()) { + if (outEdge->instructions().front().opcode() == target) + return outEdge; + } + return nullptr; +} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4mi_p.h b/src/qml/jit/qv4mi_p.h new file mode 100644 index 0000000000..f976d1dc94 --- /dev/null +++ b/src/qml/jit/qv4mi_p.h @@ -0,0 +1,627 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4MI_P_H +#define QV4MI_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include + +#include +#include +#include +#include + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +// This file contains the Machine Interface (MI) data structures, on which ultimately the assembler +// will operate: + +class MIFunction; // containing all basic blocks, and a reference to the IR function + +class MIBlock; // containing an ordered sequence of instructions + +class MIInstr; // containing operands, and a reference to the IR node, that indicates which + // operation is represented by an instruction + +class MIOperand; // contains a description of where to get/put the input/result of an operation + +// A detail about the stack slots: there two stacks, the JS stack and the native stack. A frame on +// the native stack is divided in two parts: the quad-word part and the double-word part. The +// qword part holds 64bit values, like doubles, and pointers on 64bit architectures. The dword part +// holds 32bit values, like int32s, booleans, and pointers on 32bit architectures. We need to know +// the type of value a slot holds, because if we have to move it to the JS stack, we have to box it +// correctly. +class MIOperand final +{ +public: + enum Kind { + Invalid = 0, + Constant, + VirtualRegister, + + EngineRegister, + CppFrameRegister, + Function, + + JSStackSlot, + BoolStackSlot, + + JumpTarget, + }; + + using List = QQmlJS::FixedPoolArray; + +public: + MIOperand() = default; + + static MIOperand createConstant(Node *irNode) + { + MIOperand op; + op.m_kind = Constant; + op.m_irNode = irNode; + return op; + } + + static MIOperand createVirtualRegister(Node *irNode, unsigned vreg) + { + MIOperand op; + op.m_kind = VirtualRegister; + op.m_irNode = irNode; + op.m_vreg = vreg; + return op; + } + + static MIOperand createEngineRegister(Node *irNode) + { + MIOperand op; + op.m_kind = EngineRegister; + op.m_irNode = irNode; + return op; + } + + static MIOperand createCppFrameRegister(Node *irNode) + { + MIOperand op; + op.m_kind = CppFrameRegister; + op.m_irNode = irNode; + return op; + } + + static MIOperand createFunction(Node *irNode) + { + MIOperand op; + op.m_kind = Function; + op.m_irNode = irNode; + return op; + } + + static MIOperand createJSStackSlot(Node *irNode, unsigned slot) + { + MIOperand op; + op.m_kind = JSStackSlot; + op.m_irNode = irNode; + op.m_slot = slot; + return op; + } + + static MIOperand createBoolStackSlot(Node *irNode, unsigned slot) + { + MIOperand op; + op.m_kind = BoolStackSlot; + op.m_irNode = irNode; + op.m_slot = slot; + return op; + } + + //### or name this createDeoptBlock? + static MIOperand createJumpTarget(Node *irNode, MIBlock *targetBlock) + { + MIOperand op; + op.m_kind = JumpTarget; + op.m_irNode = irNode; + op.m_targetBlock = targetBlock; + return op; + } + + Kind kind() const + { return m_kind; } + + bool isValid() const + { return m_kind != Invalid; } + + bool isConstant() const + { return m_kind == Constant; } + + bool isVirtualRegister() const + { return kind() == VirtualRegister; } + + bool isEngineRegister() const + { return kind() == EngineRegister; } + + bool isCppFrameRegister() const + { return kind() == CppFrameRegister; } + + bool isFunction() const + { return kind() == Function; } + + bool isJSStackSlot() const + { return kind() == JSStackSlot; } + + bool isBoolStackSlot() const + { return kind() == BoolStackSlot; } + + bool isStackSlot() const + { return isJSStackSlot() || isDWordSlot() || isQWordSlot(); } + + bool isJumpTarget() const + { return kind() == JumpTarget; } + + Node *irNode() const + { return m_irNode; } + + inline Type nodeType(MIFunction *f) const; + + QString debugString() const; + + QV4::Value constantValue() const + { + Q_ASSERT(isConstant()); + if (irNode()->opcode() == Meta::Undefined) + return QV4::Value::undefinedValue(); + if (irNode()->opcode() == Meta::Empty) + return QV4::Value::emptyValue(); + return ConstantPayload::get(*irNode()->operation())->value(); + } + + unsigned virtualRegister() const + { Q_ASSERT(isVirtualRegister()); return m_vreg; } + + unsigned stackSlot() const + { Q_ASSERT(isStackSlot()); return m_slot; } + + MIBlock *targetBlock() const + { Q_ASSERT(isJumpTarget()); return m_targetBlock; } + + inline bool operator==(const MIOperand &other) const + { + if (kind() != other.kind()) + return false; + + if (isStackSlot()) + return stackSlot() == other.stackSlot(); + + switch (kind()) { + case MIOperand::Invalid: + return !other.isValid(); + case MIOperand::Constant: + return constantValue().asReturnedValue() == other.constantValue().asReturnedValue(); + case MIOperand::VirtualRegister: + return virtualRegister() == other.virtualRegister(); + case MIOperand::JumpTarget: + return targetBlock() == other.targetBlock(); + default: + Q_UNREACHABLE(); + return false; + } + } + + bool isDWordSlot() const + { + switch (kind()) { + case BoolStackSlot: + return true; + default: + return false; + } + } + + bool isQWordSlot() const + { + switch (kind()) { + //### TODO: double slots + default: + return false; + } + } + + bool overlaps(const MIOperand &other) const + { + if ((isDWordSlot() && other.isDWordSlot()) || (isQWordSlot() && other.isQWordSlot())) + ; // fine, these are the same + else if (kind() != other.kind()) + return false; + + if (isStackSlot()) + return stackSlot() == other.stackSlot(); + + return false; + } + +private: + Node *m_irNode = nullptr; + union { + unsigned m_vreg; + unsigned m_slot; + MIBlock *m_targetBlock = nullptr; + }; + Kind m_kind = Invalid; +}; + +template struct MIInstrListParentType {}; +template <> struct MIInstrListParentType { using type = MIBlock; }; + +template class MIInstrList; + +template +class MIInstrListTraits : public llvm::ilist_noalloc_traits +{ +protected: + using ListTy = MIInstrList; + using iterator = typename llvm::simple_ilist::iterator; + using ItemParentClass = typename MIInstrListParentType::type; + +public: + MIInstrListTraits() = default; + +protected: + void setListOwner(ItemParentClass *listOwner) + { m_owner = listOwner; } + +private: + ItemParentClass *m_owner = nullptr; + + /// getListOwner - Return the object that owns this list. If this is a list + /// of instructions, it returns the BasicBlock that owns them. + ItemParentClass *getListOwner() const { + return m_owner; + } + + static ListTy &getList(ItemParentClass *Par) { + return Par->*(Par->getSublistAccess()); + } + + static MIInstrListTraits *getSymTab(ItemParentClass *Par) { + return Par ? toPtr(Par->getValueSymbolTable()) : nullptr; + } + +public: + void addNodeToList(MISubClass *V) { V->setParent(getListOwner()); } + void removeNodeFromList(MISubClass *V) { V->setParent(nullptr); } + void transferNodesFromList(MIInstrListTraits &L2, iterator first, + iterator last); +}; + +template +class MIInstrList: public llvm::iplist_impl, MIInstrListTraits> +{ +public: + MIInstrList(typename MIInstrListTraits::ItemParentClass *owner) + { this->setListOwner(owner); } +}; + +class MIInstr final : public llvm::ilist_node_with_parent +{ + Q_DISABLE_COPY_MOVE(MIInstr) // heap use only! + +protected: + friend class QQmlJS::MemoryPool; + MIInstr() : m_operands(nullptr, 0) {} + + explicit MIInstr(Node *irNode, QQmlJS::MemoryPool *pool, unsigned nOperands) + : m_irNode(irNode) + , m_operands(pool, nOperands) + {} + + ~MIInstr() = default; + +public: + static MIInstr *create(QQmlJS::MemoryPool *pool, Node *irNode, unsigned nOperands); + + MIBlock *parent() const + { return m_parent; } + + MIBlock *getParent() const // for ilist_node_with_parent + { return parent(); } + + void setParent(MIBlock *parent) + { m_parent = parent; } + + Node *irNode() const + { return m_irNode; } + + Operation::Kind opcode() const + { return m_irNode->opcode(); } + + int position() const + { return m_position; } + + inline void insertBefore(MIInstr *insertPos); + inline void insertAfter(MIInstr *insertPos); + inline MIInstrList::iterator eraseFromParent(); + + bool hasDestination() const + { return m_destination.isValid(); } + + MIOperand destination() const + { return m_destination; } + + void setDestination(const MIOperand &dest) + { m_destination = dest; } + + const MIOperand &operand(unsigned index) const + { return m_operands.at(index); } + + void setOperand(unsigned index, const MIOperand &op) + { m_operands.at(index) = op; } + + MIOperand &operand(unsigned index) + { return m_operands.at(index); } + + const MIOperand::List &operands() const + { return m_operands; } + + MIOperand::List &operands() + { return m_operands; } + +private: + friend MIFunction; + void setPosition(int newPosition) + { m_position = newPosition; } + +private: + MIBlock *m_parent = nullptr; + Node *m_irNode = nullptr; + int m_position = -1; + MIOperand m_destination; + MIOperand::List m_operands; +}; + +class MIBlock final +{ + Q_DISABLE_COPY_MOVE(MIBlock) + +public: + using Index = unsigned; + enum : Index { InvalidIndex = std::numeric_limits::max() }; + + using MIInstructionList = MIInstrList; + + using InEdges = QVarLengthArray; + using OutEdges = QVarLengthArray; + +protected: + friend MIFunction; + explicit MIBlock(Index index) + : m_instructions(this), + m_index(index) + {} + + void setIndex(Index newIndex) + { m_index = newIndex; } + +public: + ~MIBlock() = default; + + const MIInstructionList &instructions() const + { return m_instructions; } + + MIInstructionList &instructions() + { return m_instructions; } + + static MIInstructionList MIBlock::*getSublistAccess(MIInstr * = nullptr) + { return &MIBlock::m_instructions; } + + void addArgument(MIOperand &&arg) + { m_arguments.push_back(arg); } + + const std::vector &arguments() const + { return m_arguments; } + + std::vector &arguments() + { return m_arguments; } + + void clearArguments() + { m_arguments.resize(0); } + + const InEdges &inEdges() const + { return m_inEdges; } + + void addInEdge(MIBlock *edge) + { m_inEdges.append(edge); } + + const OutEdges &outEdges() const + { return m_outEdges; } + + void addOutEdge(MIBlock *edge) + { m_outEdges.append(edge); } + + Index index() const + { return m_index; } + + MIBlock *findEdgeTo(Operation::Kind target) const; + + bool isDeoptBlock() const + { return m_isDeoptBlock; } + + void markAsDeoptBlock() + { m_isDeoptBlock = true; } + +private: + std::vector m_arguments; + MIInstructionList m_instructions; + InEdges m_inEdges; + OutEdges m_outEdges; + Index m_index; + bool m_isDeoptBlock = false; +}; + +class MIFunction final +{ + Q_DISABLE_COPY_MOVE(MIFunction) + +public: + static constexpr MIBlock::Index StartBlockIndex = 0; + +public: + MIFunction(Function *irFunction); + ~MIFunction() + { qDeleteAll(m_blocks); } + + Function *irFunction() const + { return m_irFunction; } + + void setStartBlock(MIBlock *newStartBlock); + void renumberBlocks(); + void renumberInstructions(); + + void dump(const QString &description) const; + + size_t blockCount() const + { return blocks().size(); } + + MIBlock *block(MIBlock::Index index) const + { return m_blocks[index]; } + + const std::vector &blocks() const + { return m_blocks; } + + MIBlock *addBlock() + { + auto *b = new MIBlock(unsigned(m_blocks.size())); + m_blocks.push_back(b); + return b; + } + + void setBlockOrder(const std::vector &newSequence) + { m_blocks = newSequence; } + + unsigned vregCount() const + { return m_vregCount; } + + void setVregCount(unsigned vregCount) + { m_vregCount = vregCount; } + + unsigned dwordSlotCount() const + { return m_dwordSlotCount; } + + unsigned qwordSlotCount() const + { return m_qwordSlotCount; } + + unsigned jsSlotCount() const + { return m_jsSlotCount; } + + unsigned extraJSSlots() const; + + void setStackSlotCounts(unsigned dword, unsigned qword, unsigned js); + + void verifyCFG() const; + +private: + Function *m_irFunction = nullptr; + std::vector m_blocks; + unsigned m_vregCount = 0; + unsigned m_dwordSlotCount = 0; + unsigned m_qwordSlotCount = 0; + unsigned m_jsSlotCount = 0; +}; + +Type MIOperand::nodeType(MIFunction *f) const +{ + return f->irFunction()->nodeInfo(irNode())->type(); +} + +inline uint qHash(const MIOperand &key, uint seed) +{ + uint h = ::qHash(key.kind(), seed); + switch (key.kind()) { + case MIOperand::VirtualRegister: + h ^= key.virtualRegister(); + break; + case MIOperand::BoolStackSlot: Q_FALLTHROUGH(); + case MIOperand::JSStackSlot: + h ^= key.stackSlot(); + break; + default: + qFatal("%s: cannot hash %s", Q_FUNC_INFO, key.debugString().toUtf8().constData()); + } + return h; +} + +void MIInstr::insertBefore(MIInstr *insertPos) +{ + insertPos->parent()->instructions().insert(insertPos->getIterator(), this); +} + +void MIInstr::insertAfter(MIInstr *insertPos) +{ + insertPos->parent()->instructions().insert(++insertPos->getIterator(), this); +} + +MIInstrList::iterator MIInstr::eraseFromParent() +{ + auto p = parent(); + setParent(nullptr); + return p->instructions().erase(getIterator()); +} + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4MI_P_H diff --git a/src/qml/jit/qv4miblockset_p.h b/src/qml/jit/qv4miblockset_p.h new file mode 100644 index 0000000000..5f814b99e0 --- /dev/null +++ b/src/qml/jit/qv4miblockset_p.h @@ -0,0 +1,291 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4MIBLOCKSET_P_H +#define QV4MIBLOCKSET_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4mi_p.h" +#include "qv4util_p.h" + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +class MIBlockSet +{ + using Flags = BitVector; + + QVarLengthArray blockNumbers; + Flags *blockFlags = nullptr; + MIFunction *function = nullptr; + enum { MaxVectorCapacity = 8 }; + +public: + class const_iterator; + friend class const_iterator; + +public: + MIBlockSet(MIFunction *f = nullptr) + { + if (f) + init(f); + } + + MIBlockSet(MIBlockSet &&other) noexcept + { + std::swap(blockNumbers, other.blockNumbers); + std::swap(blockFlags, other.blockFlags); + std::swap(function, other.function); + } + + MIBlockSet(const MIBlockSet &other) + : function(other.function) + { + if (other.blockFlags) + blockFlags = new Flags(*other.blockFlags); + blockNumbers = other.blockNumbers; + } + + MIBlockSet &operator=(const MIBlockSet &other) + { + if (blockFlags) { + delete blockFlags; + blockFlags = nullptr; + } + function = other.function; + if (other.blockFlags) + blockFlags = new Flags(*other.blockFlags); + blockNumbers = other.blockNumbers; + return *this; + } + + MIBlockSet &operator=(MIBlockSet &&other) noexcept + { + if (&other != this) { + std::swap(blockNumbers, other.blockNumbers); + + delete blockFlags; + blockFlags = other.blockFlags; + other.blockFlags = nullptr; + + function = other.function; + } + return *this; + } + + ~MIBlockSet() + { + delete blockFlags; + } + + void init(MIFunction *f) + { + Q_ASSERT(!function); + Q_ASSERT(f); + function = f; + } + + bool empty() const; + + void insert(MIBlock *bb) + { + Q_ASSERT(function); + + if (blockFlags) { + blockFlags->setBit(bb->index()); + return; + } + + for (unsigned int blockNumber : qAsConst(blockNumbers)) { + if (blockNumber == bb->index()) + return; + } + + if (blockNumbers.size() == MaxVectorCapacity) { + blockFlags = new Flags(int(function->blockCount()), false); + for (unsigned int blockNumber : qAsConst(blockNumbers)) { + blockFlags->setBit(int(blockNumber)); + } + blockNumbers.clear(); + blockFlags->setBit(int(bb->index())); + } else { + blockNumbers.append(bb->index()); + } + } + + void remove(MIBlock *bb) + { + Q_ASSERT(function); + + if (blockFlags) { + blockFlags->clearBit(bb->index()); + return; + } + + for (int i = 0; i < blockNumbers.size(); ++i) { + if (blockNumbers[i] == bb->index()) { + blockNumbers.remove(i); + return; + } + } + } + + const_iterator begin() const; + const_iterator end() const; + + void collectValues(std::vector &bbs) const; + + bool contains(MIBlock *bb) const + { + Q_ASSERT(function); + + if (blockFlags) + return blockFlags->at(bb->index()); + + for (unsigned int blockNumber : blockNumbers) { + if (blockNumber == bb->index()) + return true; + } + + return false; + } +}; + +class MIBlockSet::const_iterator +{ + const MIBlockSet &set; + // ### These two members could go into a union, but clang won't compile + // (https://codereview.qt-project.org/#change,74259) + QVarLengthArray::const_iterator numberIt; + MIBlock::Index flagIt; + + friend class MIBlockSet; + const_iterator(const MIBlockSet &set, bool end) + : set(set) + { + if (end || !set.function) { + if (!set.blockFlags) + numberIt = set.blockNumbers.end(); + else + flagIt = set.blockFlags->size(); + } else { + if (!set.blockFlags) + numberIt = set.blockNumbers.begin(); + else + findNextWithFlags(0); + } + } + + void findNextWithFlags(int start) + { + flagIt = MIBlock::Index(set.blockFlags->findNext(start, true, /*wrapAround = */false)); + Q_ASSERT(flagIt <= MIBlock::Index(set.blockFlags->size())); + } + +public: + MIBlock *operator*() const + { + if (!set.blockFlags) + return set.function->block(*numberIt); + + Q_ASSERT(flagIt <= set.function->blockCount()); + return set.function->block(flagIt); + + } + + bool operator==(const const_iterator &other) const + { + if (&set != &other.set) + return false; + if (!set.blockFlags) + return numberIt == other.numberIt; + return flagIt == other.flagIt; + } + + bool operator!=(const const_iterator &other) const + { return !(*this == other); } + + const_iterator &operator++() + { + if (!set.blockFlags) + ++numberIt; + else + findNextWithFlags(flagIt + 1); + + return *this; + } +}; + +inline bool MIBlockSet::empty() const +{ return begin() == end(); } + +inline MIBlockSet::const_iterator MIBlockSet::begin() const +{ return const_iterator(*this, false); } + +inline MIBlockSet::const_iterator MIBlockSet::end() const +{ return const_iterator(*this, true); } + +inline void MIBlockSet::collectValues(std::vector &bbs) const +{ + Q_ASSERT(function); + + for (auto it : *this) + bbs.push_back(it); +} + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4MIBLOCKSET_P_H diff --git a/src/qml/jit/qv4node_p.h b/src/qml/jit/qv4node_p.h index 679a29764a..76065fb1bc 100644 --- a/src/qml/jit/qv4node_p.h +++ b/src/qml/jit/qv4node_p.h @@ -472,17 +472,6 @@ public: m_worklist.push_back(n); } - void reEnqueueLate(Node *n) - { - if (!n) - return; - State &s = nodeState(n); - if (s == Queued) - return; - s = Queued; - m_worklist.insert(m_worklist.begin(), n); - } - void enqueueAllInputs(Node *n) { for (Node *input : n->inputs()) @@ -519,19 +508,14 @@ public: Node *n = m_worklist.back(); m_worklist.pop_back(); State &s = nodeState(n); - if (s == Queued) { - s = Visited; - return n; - } - Q_UNREACHABLE(); + Q_ASSERT(s == Queued); + s = Visited; + return n; } return nullptr; } - void markAsVisited(Node *n) - { nodeState(n) = Visited; } - bool isVisited(Node *n) const { return nodeState(n) == Visited; } diff --git a/src/qml/jit/qv4schedulers.cpp b/src/qml/jit/qv4schedulers.cpp new file mode 100644 index 0000000000..0dffefa951 --- /dev/null +++ b/src/qml/jit/qv4schedulers.cpp @@ -0,0 +1,912 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#include "qv4schedulers_p.h" +#include "qv4util_p.h" +#include "qv4graph_p.h" +#include "qv4blockscheduler_p.h" +#include "qv4stackframe_p.h" + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +Q_LOGGING_CATEGORY(lcSched, "qt.v4.ir.scheduling") +Q_LOGGING_CATEGORY(lcDotCFG, "qt.v4.ir.scheduling.cfg") + +static bool needsScheduling(Node *n) +{ + if (n->operation()->isConstant()) + return false; + switch (n->opcode()) { + case Meta::Function: Q_FALLTHROUGH(); + case Meta::CppFrame: + case Meta::Phi: + case Meta::EffectPhi: + return false; + default: + return true; + } +} + +bool NodeScheduler::canStartBlock(Node *node) const +{ + switch (node->operation()->kind()) { + case Meta::Start: Q_FALLTHROUGH(); + case Meta::IfTrue: + case Meta::IfFalse: + case Meta::Region: + case Meta::HandleUnwind: + case Meta::OnException: + return true; + + default: + return false; + } +} + +bool NodeScheduler::isControlFlowSplit(Node *node) const +{ + int nOutputs = node->operation()->controlOutputCount(); + if (nOutputs == 2) { + // if there is a "missing" control output, it's for exception flow without unwinder + int controlUses = 0; + auto uses = node->uses(); + for (auto it = uses.begin(), eit = uses.end(); it != eit; ++it) { + if (isLive(*it) && it.isUsedAsControl()) + ++controlUses; + } + return controlUses == 2; + } + return nOutputs > 2; +} + +bool NodeScheduler::isBlockTerminator(Node *node) const +{ + switch (node->operation()->kind()) { + case Meta::Branch: Q_FALLTHROUGH(); + case Meta::Jump: + case Meta::Return: + case Meta::TailCall: + case Meta::UnwindDispatch: + case Meta::End: + return true; + case Meta::Call: + return isControlFlowSplit(node); + default: + return false; + } +} + +MIBlock *NodeScheduler::getCommonDominator(MIBlock *one, MIBlock *other) const +{ + MIBlock::Index a = one->index(); + MIBlock::Index b = other->index(); + + while (a != b) { + if (m_dominatorDepthForBlock[a] < m_dominatorDepthForBlock[b]) + b = m_domTree->immediateDominator(b); + else + a = m_domTree->immediateDominator(a); + } + + return m_miFunction->block(a); +} + +// For Nodes that end up inside loops, it'd be great if we can move (hoist) them out of the loop. +// To do that, we need a block that preceeds the loop. (So the block before the loop header.) +// This function calculates that hoist block if the original block is in a loop. +MIBlock *NodeScheduler::getHoistBlock(MIBlock *block) const +{ + if (m_loopInfo->isLoopHeader(block)) + return m_miFunction->block(m_domTree->immediateDominator(block->index())); + + // make the loop header a candidate: + MIBlock *loopHeader = m_loopInfo->loopHeaderFor(block); + if (loopHeader == nullptr) + return nullptr; // block is not in a loop + + // And now the tricky part: block has to dominate all exits from the loop. If it does not do + // that, it meanse that there is an exit from the loop that can be reached before block. In + // that case, hoisting from "block" to "loopHeader" would mean there now is an extra calculation + // that is not needed for a certain loop exit. + for (MIBlock *outEdge : m_loopInfo->loopExitsForLoop(loopHeader)) { + if (getCommonDominator(block, outEdge) != block) + return nullptr; + } + + return m_miFunction->block(m_domTree->immediateDominator(loopHeader->index())); +} + +NodeScheduler::NodeScheduler(Function *irFunction) + : m_irFunction(irFunction) + , m_vregs(irFunction->graph()->nodeCount(), std::numeric_limits::max()) + , m_live(irFunction->graph(), /*collectUses =*/ false /* do explicitly NOT collect uses! */) +{ +} + +MIFunction *NodeScheduler::buildMIFunction() +{ + m_miFunction = new MIFunction(m_irFunction); + + // step 1: build the CFG + auto roots = buildCFG(); + m_miFunction->renumberBlocks(); + m_miFunction->dump(QStringLiteral("CFG after renumbering")); + + Q_ASSERT(m_miFunction->block(MIFunction::StartBlockIndex)->index() + == MIFunction::StartBlockIndex); + Q_ASSERT(m_miFunction->block(MIFunction::StartBlockIndex)->instructions().front().opcode() + == Meta::Start); + + // step 2: build the dominator tree + if (lcDotCFG().isDebugEnabled()) + dumpDotCFG(); + m_domTree.reset(new DominatorTree(m_miFunction)); + m_dominatorDepthForBlock = m_domTree->calculateNodeDepths(); + + // step 3: find loops + m_loopInfo.reset(new LoopInfo(*m_domTree.data())); + m_loopInfo->detectLoops(); + + // step 4: schedule early + scheduleEarly(roots); + showNodesByBlock(QStringLiteral("nodes per block after early scheduling")); + + // step 5: schedule late + scheduleLate(roots); + showNodesByBlock(QStringLiteral("nodes per block after late scheduling")); + + // step 6: schedule instructions in each block + scheduleNodesInBlock(); + + m_miFunction->dump(QStringLiteral("MI before block scheduling")); + + // step 7: order the basic blocks in the CFG + BlockScheduler blockScheduler(*m_domTree.data(), *m_loopInfo.data()); + m_miFunction->setBlockOrder(blockScheduler.scheduledBlockSequence()); + + // we're done + m_miFunction->renumberInstructions(); + m_miFunction->setVregCount(m_nextVReg); + m_miFunction->dump(QStringLiteral("MI after scheduling")); + return m_miFunction; +} + +static Node *splitEdge(Function *irFunction, Node *node, unsigned inputIndex) +{ + Graph *g = irFunction->graph(); + Node *in = node->input(inputIndex); + Node *region = g->createNode(g->opBuilder()->getRegion(1), &in, 1); + Node *jump = g->createNode(g->opBuilder()->get(), ®ion, 1); + + qCDebug(lcSched) << "splitting critical edge from node" << node->id() + << "to node" << node->input(inputIndex)->id() + << "by inserting jump node" << jump->id() + << "and region node" << region->id(); + + node->replaceInput(inputIndex, jump); + return jump; +} + +// See Chapter 6.3.1 of https://scholarship.rice.edu/bitstream/handle/1911/96451/TR95-252.pdf for +// a description of the algorithm. +std::vector NodeScheduler::buildCFG() +{ + std::vector roots; + roots.reserve(32); + NodeWorkList todo(m_irFunction->graph()); + + auto enqueueControlInputs = [this, &todo](Node *node) { + for (unsigned i = 0, ei = node->operation()->controlInputCount(); i != ei; ++i) { + const auto inputIndex = node->operation()->indexOfFirstControl() + i; + Node *input = node->input(inputIndex); + Q_ASSERT(input); + if (node->operation()->kind() == Meta::Region + && node->operation()->controlInputCount() > 1 + && isControlFlowSplit(input)) { + // critical edge! + input = splitEdge(m_irFunction, node, inputIndex); + m_live.markReachable(input); + m_live.markReachable(input->controlInput(0)); + } + if (!isBlockTerminator(input)) { + auto g = m_irFunction->graph(); + Node *jump = g->createNode(g->opBuilder()->get(), &input, 1); + node->replaceInput(inputIndex, jump); + m_live.markReachable(jump); + qCDebug(lcSched) << "inserting jump node" << jump->id() + << "between node" << node->id() + << "and node" << input->id(); + input = jump; + } + todo.enqueue(input); + } + }; + + // create the CFG by scheduling control dependencies that start/end blocks: + todo.enqueue(m_irFunction->graph()->endNode()); + while (Node *node = todo.dequeueNextNodeForVisiting()) { + Q_ASSERT(isBlockTerminator(node)); + + if (schedulerData(node)->minimumBlock) + continue; + + MIBlock *b = m_miFunction->addBlock(); + + qCDebug(lcSched) << "scheduling node" << node->id() << "as terminator for new block" + << b->index(); + b->instructions().push_front(createMIInstruction(node)); + placeFixed(node, b, Schedule); + roots.push_back(node); + + if (Node *framestate = node->frameStateInput()) { + placeFixed(framestate, b, DontSchedule); + qCDebug(lcSched) << ".. also scheduling framestate dependency node" << node->id() + << "in block" << b->index(); + } + + if (node->opcode() == Meta::End) { + enqueueControlInputs(node); + continue; + } + + while (true) { + Node *controlDependency = node->controlInput(0); + if (!controlDependency) + break; + if (todo.isVisited(controlDependency)) + break; + if (schedulerData(controlDependency)->isFixed) + break; + + if (controlDependency->opcode() == Meta::Start) { + qCDebug(lcSched) << "placing start node" << controlDependency->id() + << "in block" << b->index(); + handleStartNode(controlDependency, b); + placeFixed(controlDependency, b, Schedule); + roots.push_back(controlDependency); + break; // we're done with this block + } + if (isBlockTerminator(controlDependency)) { + qCDebug(lcSched) << "found terminator node" << controlDependency->id() + << "for another block, so finish block" << b->index(); + Node *merge = m_irFunction->graph()->createNode( + m_irFunction->graph()->opBuilder()->getRegion(1), &controlDependency, 1); + node->replaceInput(node->operation()->indexOfFirstControl(), merge); + addBlockStart(roots, merge, b); + placeFixed(merge, b, Schedule); + m_live.markReachable(merge); + todo.enqueue(controlDependency); + break; // we're done with this block + } + if (canStartBlock(controlDependency) + || schedulerData(controlDependency->controlInput())->isFixed) { + qCDebug(lcSched) << "found block start node" << controlDependency->id() + << "for this block, so finish block" << b->index(); + addBlockStart(roots, controlDependency, b); + placeFixed(controlDependency, b, Schedule); + roots.push_back(controlDependency); + enqueueControlInputs(controlDependency); + break; // we're done with this block + } + qCDebug(lcSched) << "skipping node" << controlDependency->id(); + node = controlDependency; + } + } + + // link the edges of the MIBlocks, and add basic-block arguments: + for (MIBlock *toBlock : m_miFunction->blocks()) { + Q_ASSERT(!toBlock->instructions().empty()); + MIInstr &instr = toBlock->instructions().front(); + Node *toNode = instr.irNode(); + const auto opcode = toNode->operation()->kind(); + if (opcode == Meta::Region) { + unsigned inputNr = 0; + for (Node *input : toNode->inputs()) { + MIBlock *fromBlock = schedulerData(input)->minimumBlock; + fromBlock->addOutEdge(toBlock); + toBlock->addInEdge(fromBlock); + MIInstr &fromTerminator = fromBlock->instructions().back(); + if (fromTerminator.irNode()->opcode() == Meta::Jump || + fromTerminator.irNode()->opcode() == Meta::UnwindDispatch) { + unsigned arg = 0; + for (const MIOperand &bbArg : toBlock->arguments()) { + fromTerminator.setOperand(arg++, + createMIOperand(bbArg.irNode()->input(inputNr))); + } + } + ++inputNr; + } + } else if (opcode == Meta::End) { + for (Node *input : toNode->inputs()) { + MIBlock *fromBlock = schedulerData(input)->minimumBlock; + fromBlock->addOutEdge(toBlock); + toBlock->addInEdge(fromBlock); + } + } else if (Node *fromNode = toNode->controlInput()) { + MIBlock *fromBlock = schedulerData(fromNode)->minimumBlock; + fromBlock->addOutEdge(toBlock); + toBlock->addInEdge(fromBlock); + } + } + + m_irFunction->dump(QStringLiteral("graph after building CFG")); + + auto startBlock = schedulerData(m_irFunction->graph()->startNode())->minimumBlock; + m_miFunction->setStartBlock(startBlock); + + if (lcSched().isDebugEnabled()) + m_miFunction->dump(QStringLiteral("control flow graph before renumbering")); + m_miFunction->verifyCFG(); + + return roots; +} + +// See Chapter 6.3.3 of https://scholarship.rice.edu/bitstream/handle/1911/96451/TR95-252.pdf for +// a description of the algorithm. +void NodeScheduler::scheduleEarly(const std::vector &roots) +{ + // scheduling one node might have the effect of queueing its dependencies + NodeWorkList todo(m_irFunction->graph()); + for (Node *root : roots) { + todo.enqueue(root); + while (Node *node = todo.dequeueNextNodeForVisiting()) + scheduleEarly(node, todo); + } +} + +void NodeScheduler::scheduleEarly(Node *node, NodeWorkList &todo) +{ + qCDebug(lcSched) << "Scheduling node" << node->id() << "early..."; + + SchedulerData *sd = schedulerData(node); + + if (sd->isFixed) { + // Fixed nodes already know their schedule early position. + qCDebug(lcSched) << ".. Fixed node" << node->id() << "is on minimum block" + << sd->minimumBlock->index() + << "which has dominator depth" + << m_dominatorDepthForBlock[sd->minimumBlock->index()]; + } + + for (Node *use : node->uses()) { + if (isLive(use)) + propagateMinimumPosition(sd->minimumBlock, use, todo); + else + qCDebug(lcSched) << ".. Skipping node" << use->id() << "as it's not live"; + } +} + +void NodeScheduler::propagateMinimumPosition(MIBlock *newMinimumPosition, Node *toNode, + NodeWorkList &todo) +{ + Q_ASSERT(newMinimumPosition); + + SchedulerData *sd = schedulerData(toNode); + if (sd->isFixed) // nothing to do + return; + + MIBlock::Index minimumBlockIndex = sd->minimumBlock + ? sd->minimumBlock->index() + : MIFunction::StartBlockIndex; + Q_ASSERT(m_domTree->insideSameDominatorChain(newMinimumPosition->index(), minimumBlockIndex)); + if (sd->minimumBlock == nullptr + || m_dominatorDepthForBlock[newMinimumPosition->index()] + > m_dominatorDepthForBlock[minimumBlockIndex]) { + // ok, some input for toNode is scheduled *after* our current minimum depth, so we need + // to adjust out minimal position. (This might involve rescheduling toNode's uses.) + place(toNode, newMinimumPosition); + todo.reEnqueue(toNode); + qCDebug(lcSched) << ".. Propagating minimum block" << sd->minimumBlock->index() + << "which has dominator depth" + << m_dominatorDepthForBlock[newMinimumPosition->index()] + << "to use node" << toNode->id(); + } else { + qCDebug(lcSched) << ".. Minimum position" << newMinimumPosition->index() + << "is not better than" << minimumBlockIndex + << "for node" << toNode->id(); + } +} + +// See Chapter 6.3.4 of https://scholarship.rice.edu/bitstream/handle/1911/96451/TR95-252.pdf for +// a description of the algorithm. +// +// There is one extra detail not described in the thesis mentioned above: loop hoisting. Before we +// place a node in the latest block that dominates all uses, we check if we accidentally sink it +// *into* a loop (meaning the latest block is inside a loop, where it is not if the earliest +// possible block would be chosen). If we detect that a nodes is going to sink into a loop, we walk +// the dominator path from the latest block up to the earliest block, and pick the first block that +// is in the same loop (if any) as the earlieast block. +// +// As noted in the thesis, this strategy might enlongen life times, which could be harmful for +// values that are simple to re-materialized or re-calculate. +void NodeScheduler::scheduleLate(const std::vector &roots) +{ + NodeWorkList todo(m_irFunction->graph()); + for (Node *root : roots) + todo.enqueue(root); + + while (Node *node = todo.dequeueNextNodeForVisiting()) + scheduleNodeLate(node, todo); +} + +void NodeScheduler::scheduleNodeLate(Node *node, NodeWorkList &todo) +{ + if (!needsScheduling(node)) + return; + qCDebug(lcSched) << "Scheduling node" << node->id() << "late..."; + + auto sd = schedulerData(node); + if (sd->unscheduledUses == SchedulerData::NotYetCalculated) { + sd->unscheduledUses = 0; + for (Node *use : node->uses()) { + if (!isLive(use)) + continue; + if (!needsScheduling(use)) + continue; + if (schedulerData(use)->isFixed) + continue; + todo.enqueue(use); + ++sd->unscheduledUses; + } + } + + if (sd->isFixed) { + qCDebug(lcSched) << ".. it's fixed"; + enqueueInputs(node, todo); + return; + } + + if (sd->unscheduledUses) { + qCDebug(lcSched).noquote() << ".. not all uses are fixed, postpone it."<< todo.status(node); + return; + } + + MIBlock *&minBlock = sd->minimumBlock; + if (minBlock == nullptr) + minBlock = m_miFunction->block(MIFunction::StartBlockIndex); + MIBlock *commonUseDominator = commonDominatorOfUses(node); + qCDebug(lcSched) << ".. common use dominator: block" << commonUseDominator->index(); + + // the minBlock has to dominate the uses, *and* the common dominator of the uses. + Q_ASSERT(minBlock->index() == commonUseDominator->index() || + m_domTree->dominates(minBlock->index(), commonUseDominator->index())); + + // we now found the deepest block, so use it as the target block: + MIBlock *targetBlock = commonUseDominator; + + if (node->opcode() == Meta::FrameState) { + // never hoist framestates: they're used (among other things) to keep their inputs alive, so + // hoisting them out would end the life-time of those inputs prematurely + } else { + // but we want to prevent blocks sinking into loops unnecessary + MIBlock *hoistBlock = getHoistBlock(targetBlock); + while (hoistBlock + && m_dominatorDepthForBlock[hoistBlock->index()] + >= m_dominatorDepthForBlock[minBlock->index()]) { + qCDebug(lcSched) << ".. hoisting node" << node->id() << "from block" + << targetBlock->index() << "to block" << hoistBlock->index(); + // ok, so there a) is a hoist block and b) it's deeper than the minimum block, + // so lift it up one level ... + targetBlock = hoistBlock; + // ... and see if we can lift it one more level + hoistBlock = getHoistBlock(targetBlock); + } + } + + qCDebug(lcSched) << ".. fixating it in block" << targetBlock->index() + << "where the minimum block was" << minBlock->index(); + + placeFixed(node, targetBlock, DontSchedule); + enqueueInputs(node, todo); +} + +void NodeScheduler::enqueueInputs(Node *node, NodeWorkList &todo) +{ + for (Node *input : node->inputs()) { + if (!input) + continue; + if (!needsScheduling(input)) + continue; + if (!isLive(input)) + continue; + auto sd = schedulerData(input); + if (sd->isFixed) + continue; + qCDebug(lcSched).noquote() << "... enqueueing input node" << input->id() + << todo.status(input); + if (sd->unscheduledUses != SchedulerData::NotYetCalculated) { + if (sd->unscheduledUses > 0) + --sd->unscheduledUses; + if (sd->unscheduledUses == 0) + todo.reEnqueue(input); + } else { + todo.reEnqueue(input); + } + } +} + +Node *NodeScheduler::firstNotFixedUse(Node *node) +{ + for (Node *use : node->uses()) { + if (!isLive(use)) + continue; + if (!schedulerData(use)->isFixed) + return use; + } + return nullptr; +} + +MIBlock *NodeScheduler::commonDominatorOfUses(Node *node) +{ + MIBlock *commonDominator = nullptr; + for (auto useIt = node->uses().begin(), useEIt = node->uses().end(); useIt != useEIt; ++useIt) { + Node *use = *useIt; + if (!isLive(use)) + continue; + // region nodes use other nodes through their control dependency. But those nodes should + // already have been placed as block terminators before. + Q_ASSERT(use->opcode() != Meta::Region); + if (use->opcode() == Meta::Phi || use->opcode() == Meta::EffectPhi) { + // find the predecessor block defining this input + Node *region = use->controlInput(0); + Node *input = region->controlInput(useIt.inputIndex()); + use = input; + } + auto minBlock = schedulerData(use)->minimumBlock; + if (commonDominator == nullptr) + commonDominator = minBlock; + else + commonDominator = getCommonDominator(commonDominator, minBlock); + } + return commonDominator; +} + +void NodeScheduler::scheduleNodesInBlock() +{ + auto startBlock = m_miFunction->block(MIFunction::StartBlockIndex); + for (Node *n : m_live.reachable()) { + auto sd = schedulerData(n); + if (!sd->minimumBlock) + sd->minimumBlock = startBlock; + } + + std::vector> nodesForBlock; + nodesForBlock.resize(m_miFunction->blockCount()); + + for (auto sd : m_schedulerData) { + if (sd == nullptr) + continue; + if (!isLive(sd->node)) + continue; + sd->unscheduledUses = 0; + for (Node *use : sd->node->uses()) { + if (!needsScheduling(use)) + continue; + if (schedulerData(use)->isScheduledInBlock) + continue; + if (schedulerData(use)->minimumBlock == sd->minimumBlock) + ++sd->unscheduledUses; + } + if (sd->unscheduledUses == 0) + nodesForBlock[sd->minimumBlock->index()].push_back(sd); + } + + NodeWorkList todo(m_irFunction->graph()); + for (MIBlock *b : m_miFunction->blocks()) { + qCDebug(lcSched) << "Scheduling inside block" << b->index(); + MIInstr *insertionPoint = &b->instructions().back(); + todo.enqueue(insertionPoint->irNode()); + scheduleNodesInBlock(insertionPoint, b, todo); + Q_ASSERT(todo.isEmpty()); + for (auto sd : nodesForBlock[b->index()]) { + if (!sd->isScheduledInBlock) + todo.enqueue(sd->node); + } + scheduleNodesInBlock(insertionPoint, b, todo); + Q_ASSERT(todo.isEmpty()); + todo.reset(); + } +} + +void NodeScheduler::scheduleNodesInBlock(MIInstr *&insertionPoint, MIBlock *b, NodeWorkList &todo) +{ + while (Node *n = todo.dequeueNextNodeForVisiting()) + scheduleNodeInBlock(n, insertionPoint, b, todo); +} + +void NodeScheduler::scheduleNodeInBlock(Node *node, MIInstr *&insertionPoint, MIBlock *b, + NodeWorkList &todo) +{ + Q_ASSERT(!node->isDead()); + + if (!isLive(node)) + return; + + if (!needsScheduling(node)) + return; + + auto nodeData = schedulerData(node); + if (nodeData->minimumBlock != b) + return; + + const bool wasAlreadyScheduled = nodeData->isScheduledInBlock; + if (!wasAlreadyScheduled) { + if (nodeData->unscheduledUses) + return; + + scheduleNodeNow(node, insertionPoint); + } + + if (Node *framestate = node->frameStateInput()) + scheduleNodeInBlock(framestate, insertionPoint, b, todo); + + for (Node *input : node->inputs()) { + if (!input) + continue; + if (!needsScheduling(input)) + continue; + if (!isLive(input)) + continue; + auto inputInfo = schedulerData(input); + if (inputInfo->isScheduledInBlock) + continue; + Q_ASSERT(inputInfo->minimumBlock != nullptr); + if (inputInfo->minimumBlock != b) + continue; + Q_ASSERT(!input->isDead()); + Q_ASSERT(inputInfo->unscheduledUses != SchedulerData::NotYetCalculated); + if (!wasAlreadyScheduled && inputInfo->unscheduledUses > 0) + --inputInfo->unscheduledUses; + if (inputInfo->unscheduledUses == 0) + todo.enqueue(input); + } +} + +void NodeScheduler::scheduleNodeNow(Node *node, MIInstr *&insertionPoint) +{ + qCDebug(lcSched) << ".. scheduling node" << node->id() + << "in block" << insertionPoint->parent()->index() + << "before node" << insertionPoint->irNode()->id(); + + MIInstr *newInstr = createMIInstruction(node); + newInstr->insertBefore(insertionPoint); + insertionPoint = newInstr; +} + +void NodeScheduler::place(Node *node, MIBlock *b) +{ + Q_ASSERT(!node->isDead()); + + if (b == nullptr) + return; + + schedulerData(node)->minimumBlock = b; +} + +void NodeScheduler::placeFixed(Node *node, MIBlock *b, ScheduleOrNot markScheduled) +{ + place(node, b); + auto sd = schedulerData(node); + Q_ASSERT(!sd->isFixed); + sd->isFixed = true; + sd->isScheduledInBlock = markScheduled == Schedule; +} + +unsigned NodeScheduler::vregForNode(Node *node) +{ + unsigned &vreg = m_vregs[unsigned(node->id())]; + if (vreg == std::numeric_limits::max()) + vreg = m_nextVReg++; + return vreg; +} + +void NodeScheduler::addBlockStart(std::vector &roots, Node *startNode, MIBlock *block) +{ + block->instructions().insert(block->instructions().begin(), createMIInstruction(startNode)); + if (startNode->opcode() == Meta::Region) { + for (Node *use : startNode->uses()) { + if (use->opcode() == Meta::Phi && isLive(use)) { + block->addArgument(MIOperand::createVirtualRegister(use, vregForNode(use))); + placeFixed(use, block, Schedule); + roots.push_back(use); + } else if (use->opcode() == Meta::EffectPhi && isLive(use)) { + placeFixed(use, block, Schedule); + roots.push_back(use); + } + } + } +} + +void NodeScheduler::handleStartNode(Node *startNode, MIBlock *startBlock) +{ + startBlock->instructions().push_front(createMIInstruction(startNode)); + + QVarLengthArray args; + for (Node *use : startNode->uses()) { + switch (use->opcode()) { + case Meta::Engine: Q_FALLTHROUGH(); + case Meta::CppFrame: + case Meta::Function: + placeFixed(use, startBlock, Schedule); + break; + case Meta::Parameter: { + auto param = ParameterPayload::get(*use->operation()); + int idx = int(param->parameterIndex()); + if (args.size() <= idx) + args.resize(idx + 1); + args[int(idx)] = use; + placeFixed(use, startBlock, Schedule); + } + break; + default: + break; + } + } + + for (unsigned i = 0, ei = unsigned(args.size()); i != ei; ++i) { + if (Node *arg = args.at(int(i))) + startBlock->addArgument(MIOperand::createJSStackSlot(arg, i)); + } +} + +static Node *firstControlOutput(Node *n) +{ + for (auto it = n->uses().begin(), eit = n->uses().end(); it != eit; ++it) { + if (it.isUsedAsControl()) + return *it; + } + return nullptr; +} + +MIInstr *NodeScheduler::createMIInstruction(Node *node) +{ + const auto opcode = node->operation()->kind(); + + unsigned nArgs = 0; + switch (opcode) { + case Meta::UnwindDispatch: Q_FALLTHROUGH(); + case Meta::Jump: { + Node *target = firstControlOutput(node); + if (target->opcode() == Meta::Region) { + for (Node *n : target->uses()) { + if (n->opcode() == Meta::Phi && isLive(n)) + ++nArgs; + } + } + } + break; + case Meta::Branch: + nArgs = 1; + break; + case Meta::Return: + nArgs = 1; + break; + default: + nArgs = node->operation()->valueInputCount(); + break; + } + + MIInstr *instr = MIInstr::create(m_irFunction->pool(), node, nArgs); + for (unsigned i = 0, ei = node->operation()->valueInputCount(); i != ei; ++i) + instr->setOperand(i, createMIOperand(node->input(i))); + if (node->opcode() != Meta::Start && node->operation()->valueOutputCount() > 0) + instr->setDestination(createMIOperand(node)); + + schedulerData(node)->isScheduledInBlock = true; + return instr; +} + +MIOperand NodeScheduler::createMIOperand(Node *node) +{ + if (node->operation()->isConstant()) + return MIOperand::createConstant(node); + + auto opcode = node->operation()->kind(); + switch (opcode) { + case Meta::Parameter: + return MIOperand::createJSStackSlot( + node, unsigned(ParameterPayload::get(*node->operation())->parameterIndex())); + case Meta::Engine: + return MIOperand::createEngineRegister(node); + case Meta::CppFrame: + return MIOperand::createCppFrameRegister(node); + case Meta::Function: + return MIOperand::createFunction(node); + default: + if ((node->opcode() == Meta::Call + && CallPayload::get(*node->operation())->callee() == Meta::JSThisToObject) + || node->opcode() == Meta::StoreThis) { + return MIOperand::createJSStackSlot(node, CallData::This); + } else { + return MIOperand::createVirtualRegister(node, vregForNode(node)); + } + } +} + +void NodeScheduler::showNodesByBlock(const QString &description) const +{ + if (!lcSched().isDebugEnabled()) + return; + + qCDebug(lcSched) << description; + + for (MIBlock *b : m_miFunction->blocks()) { + QString s; + for (const SchedulerData *sd : m_schedulerData) { + if (!sd) + continue; + if (!isLive(sd->node)) + continue; + if (sd->minimumBlock == b) { + if (!s.isEmpty()) + s += QStringLiteral(", "); + s += QStringLiteral("%1 (%2)").arg(QString::number(sd->node->id()), + sd->node->operation()->debugString()); + } + } + if (s.isEmpty()) + s = QStringLiteral("<>"); + qCDebug(lcSched, "Nodes in block %u: %s", b->index(), s.toUtf8().constData()); + } +} + +void NodeScheduler::dumpDotCFG() const +{ + QString out; + out += QLatin1Char('\n'); + out += QStringLiteral("digraph{root=\"L%1\" label=\"Control Flow Graph\";" + "node[shape=circle];edge[dir=forward fontsize=10]\n") + .arg(MIFunction::StartBlockIndex); + for (MIBlock *src : m_miFunction->blocks()) { + for (MIBlock *dst : src->outEdges()) { + out += QStringLiteral("L%1->L%2\n").arg(QString::number(src->index()), + QString::number(dst->index())); + } + } + out += QStringLiteral("}\n"); + qCDebug(lcDotCFG).nospace().noquote() << out; +} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4schedulers_p.h b/src/qml/jit/qv4schedulers_p.h new file mode 100644 index 0000000000..f9179816df --- /dev/null +++ b/src/qml/jit/qv4schedulers_p.h @@ -0,0 +1,162 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4SCHEDULER_P_H +#define QV4SCHEDULER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qv4global_p.h" +#include "qv4mi_p.h" +#include "qv4node_p.h" +#include "qv4domtree_p.h" +#include "qv4loopinfo_p.h" + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +// Node scheduling "flattens" the graph into basic blocks with an ordered list of instructions. +// +// The various steps are mentioned in buildMIFunction, but the general idea is described in +// https://scholarship.rice.edu/bitstream/handle/1911/96451/TR95-252.pdf in chapter 6. +class NodeScheduler final +{ + Q_DISABLE_COPY_MOVE(NodeScheduler) + + class SchedulerData final { + Q_DISABLE_COPY_MOVE(SchedulerData) + public: + static SchedulerData *create(QQmlJS::MemoryPool *pool) + { return pool->New(); } + + SchedulerData() = default; + ~SchedulerData() = default; + + Node *node = nullptr; + MIBlock *minimumBlock = nullptr; + bool isFixed = false; + bool isScheduledInBlock = false; + static constexpr unsigned NotYetCalculated = std::numeric_limits::max(); + unsigned unscheduledUses = NotYetCalculated; + }; + +public: + NodeScheduler(Function *irFunction); + ~NodeScheduler() = default; + + MIFunction *buildMIFunction(); + +private: + std::vector buildCFG(); + void scheduleEarly(const std::vector &roots); + void scheduleEarly(Node *node, NodeWorkList &todo); + void propagateMinimumPosition(MIBlock *newMinimumPosition, Node *toNode, NodeWorkList &todo); + void scheduleLate(const std::vector &roots); + void scheduleNodeLate(Node *node, NodeWorkList &todo); + void enqueueInputs(Node *node, NodeWorkList &todo); + Node *firstNotFixedUse(Node *node); + MIBlock *commonDominatorOfUses(Node *node); + void scheduleNodesInBlock(); + void scheduleNodesInBlock(MIInstr *&insertionPoint, MIBlock *b, NodeWorkList &todo); + void scheduleNodeInBlock(Node *node, MIInstr *&insertionPoint, MIBlock *b, NodeWorkList &todo); + void scheduleNodeNow(Node *node, MIInstr *&insertionPoint); + + void place(Node *node, MIBlock *b); + enum ScheduleOrNot { DontSchedule, Schedule }; + void placeFixed(Node *node, MIBlock *b, ScheduleOrNot markScheduled); + unsigned vregForNode(Node *node); + void addBlockStart(std::vector &roots, Node *startNode, MIBlock *block); + void enqueueControlInputs(Node *node); + void handleStartNode(Node *startNode, MIBlock *startBlock); + MIInstr *createMIInstruction(Node *node); + MIOperand createMIOperand(Node *node); + SchedulerData *schedulerData(Node *n) + { + if (Q_UNLIKELY(m_schedulerData.size() <= n->id())) + m_schedulerData.resize(n->id() + 8); + SchedulerData *&sd = m_schedulerData[n->id()]; + if (Q_UNLIKELY(sd == nullptr)) { + sd = SchedulerData::create(m_irFunction->pool()); + sd->node = n; + } + return sd; + } + bool isLive(Node *n) const + { return m_live.isReachable(n->id()); } + bool canStartBlock(Node *node) const; + bool isControlFlowSplit(Node *node) const; + bool isBlockTerminator(Node *node) const; + MIBlock *getCommonDominator(MIBlock *one, MIBlock *other) const; + MIBlock *getHoistBlock(MIBlock *block) const; + + void showNodesByBlock(const QString &description) const; + + void dumpDotCFG() const; + +private: + Function *m_irFunction = nullptr; + MIFunction *m_miFunction = nullptr; + QScopedPointer m_loopInfo; + QScopedPointer m_domTree; + std::vector m_dominatorDepthForBlock; + std::vector m_vregs; + std::vector m_schedulerData; + NodeCollector m_live; + unsigned m_nextVReg = 0; +}; + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4SCHEDULER_P_H diff --git a/src/qml/jit/qv4tracingjit.cpp b/src/qml/jit/qv4tracingjit.cpp index 889c0516d3..c8974b3a1b 100644 --- a/src/qml/jit/qv4tracingjit.cpp +++ b/src/qml/jit/qv4tracingjit.cpp @@ -42,6 +42,8 @@ #include "qv4vme_moth_p.h" #include "qv4graphbuilder_p.h" #include "qv4lowering_p.h" +#include "qv4mi_p.h" +#include "qv4schedulers_p.h" QT_BEGIN_NAMESPACE @@ -78,6 +80,11 @@ void Moth::runTracingJit(QV4::Function *function) IR::GenericLowering(irFunction).lower(); irFunction.dump(QStringLiteral("after generic lowering")); irFunction.verify(); + + IR::NodeScheduler scheduler(&irFunction); + QScopedPointer miFunction(scheduler.buildMIFunction()); + miFunction->dump(QStringLiteral("initial MI")); + irFunction.verify(); } } // QV4 namespace -- cgit v1.2.3 From 42f0b0b245d29118e91fffaddeafcbdb8b48cc63 Mon Sep 17 00:00:00 2001 From: Venugopal Shivashankar Date: Thu, 28 Mar 2019 14:41:16 +0100 Subject: Doc: Use the \nullptr macro instead of 0 This enables overriding the macro so that it translates to 'None' in the Qt for Python context. Change-Id: Ied1ddcb0f9e6e2299d9042e4cc4c2bb6d11a491b Reviewed-by: Shawn Rutledge --- src/qml/jsapi/qjsvalue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/qml') diff --git a/src/qml/jsapi/qjsvalue.cpp b/src/qml/jsapi/qjsvalue.cpp index f6fb93eab3..1ab6e8c767 100644 --- a/src/qml/jsapi/qjsvalue.cpp +++ b/src/qml/jsapi/qjsvalue.cpp @@ -1368,7 +1368,7 @@ QObject *QJSValue::toQObject() const \since 5.8 * If this QJSValue is a QMetaObject, returns the QMetaObject pointer - * that the QJSValue represents; otherwise, returns 0. + * that the QJSValue represents; otherwise, returns \nullptr. * * \sa isQMetaObject() */ -- cgit v1.2.3 From 2486c31e8823dc8c37f67716c422f38d58ec1ec3 Mon Sep 17 00:00:00 2001 From: Paul Wicking Date: Mon, 1 Apr 2019 08:51:31 +0200 Subject: Doc: Add missing quote in example code Fixes: QTBUG-74851 Change-Id: Ib4c4b7fd3a01a360ae923f718e7ce74b3ae7f3eb Reviewed-by: Mitch Curtis --- src/qml/doc/src/javascript/number.qdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/qml') diff --git a/src/qml/doc/src/javascript/number.qdoc b/src/qml/doc/src/javascript/number.qdoc index b6f80f474a..288232255c 100644 --- a/src/qml/doc/src/javascript/number.qdoc +++ b/src/qml/doc/src/javascript/number.qdoc @@ -96,7 +96,7 @@ \code var german = Qt.locale("de_DE"); var d; - d = Number.fromLocaleString(german, "1234,56) // d == 1234.56 + d = Number.fromLocaleString(german, "1234,56") // d == 1234.56 d = Number.fromLocaleString(german, "1.234,56") // d == 1234.56 d = Number.fromLocaleString(german, "1234.56") // throws exception d = Number.fromLocaleString(german, "1.234") // d == 1234.0 -- cgit v1.2.3 From 4e5ff56a18dc7bb13680a8fbe30ba5aae2dfa206 Mon Sep 17 00:00:00 2001 From: Mitch Curtis Date: Mon, 4 Mar 2019 12:18:52 +0100 Subject: QQmlInstanceModel: add variantValue() This is necessary in order to add valueRole API to Qt Quick Controls 2's ComboBox. Make stringValue() non-virtual and have it just call variantValue().toString(). Task-number: QTBUG-73491 Change-Id: Ic4ecf8370eb0bb8045a967bd589e54dfc1ac5263 Reviewed-by: Richard Moe Gustavsen --- src/qml/types/qqmldelegatemodel.cpp | 18 +++++++++--------- src/qml/types/qqmldelegatemodel_p.h | 2 +- src/qml/types/qqmldelegatemodel_p_p.h | 4 ++-- src/qml/types/qqmlobjectmodel.cpp | 4 ++-- src/qml/types/qqmlobjectmodel_p.h | 5 +++-- src/qml/types/qqmltableinstancemodel_p.h | 2 +- 6 files changed, 18 insertions(+), 17 deletions(-) (limited to 'src/qml') diff --git a/src/qml/types/qqmldelegatemodel.cpp b/src/qml/types/qqmldelegatemodel.cpp index 572f58339f..0b95c7c3e7 100644 --- a/src/qml/types/qqmldelegatemodel.cpp +++ b/src/qml/types/qqmldelegatemodel.cpp @@ -1127,7 +1127,7 @@ QQmlIncubator::Status QQmlDelegateModel::incubationStatus(int index) return QQmlIncubator::Ready; } -QString QQmlDelegateModelPrivate::stringValue(Compositor::Group group, int index, const QString &name) +QVariant QQmlDelegateModelPrivate::variantValue(QQmlListCompositor::Group group, int index, const QString &name) { Compositor::iterator it = m_compositor.find(group, index); if (QQmlAdaptorModel *model = it.list()) { @@ -1139,20 +1139,20 @@ QString QQmlDelegateModelPrivate::stringValue(Compositor::Group group, int index while (dot > 0) { QObject *obj = qvariant_cast(value); if (!obj) - return QString(); - int from = dot+1; + return QVariant(); + const int from = dot + 1; dot = name.indexOf(QLatin1Char('.'), from); value = obj->property(name.midRef(from, dot - from).toUtf8()); } - return value.toString(); + return value; } - return QString(); + return QVariant(); } -QString QQmlDelegateModel::stringValue(int index, const QString &name) +QVariant QQmlDelegateModel::variantValue(int index, const QString &role) { Q_D(QQmlDelegateModel); - return d->stringValue(d->m_compositorGroup, index, name); + return d->variantValue(d->m_compositorGroup, index, role); } int QQmlDelegateModel::indexOf(QObject *item, QObject *) const @@ -3338,9 +3338,9 @@ QQmlInstanceModel::ReleaseFlags QQmlPartsModel::release(QObject *item) return flags; } -QString QQmlPartsModel::stringValue(int index, const QString &role) +QVariant QQmlPartsModel::variantValue(int index, const QString &role) { - return QQmlDelegateModelPrivate::get(m_model)->stringValue(m_compositorGroup, index, role); + return QQmlDelegateModelPrivate::get(m_model)->variantValue(m_compositorGroup, index, role); } void QQmlPartsModel::setWatchedRoles(const QList &roles) diff --git a/src/qml/types/qqmldelegatemodel_p.h b/src/qml/types/qqmldelegatemodel_p.h index 0ad8939732..fbf3614a7c 100644 --- a/src/qml/types/qqmldelegatemodel_p.h +++ b/src/qml/types/qqmldelegatemodel_p.h @@ -114,7 +114,7 @@ public: QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override; ReleaseFlags release(QObject *object) override; void cancel(int index) override; - QString stringValue(int index, const QString &role) override; + QVariant variantValue(int index, const QString &role) override; void setWatchedRoles(const QList &roles) override; QQmlIncubator::Status incubationStatus(int index) override; diff --git a/src/qml/types/qqmldelegatemodel_p_p.h b/src/qml/types/qqmldelegatemodel_p_p.h index 0028849828..7f10bbf370 100644 --- a/src/qml/types/qqmldelegatemodel_p_p.h +++ b/src/qml/types/qqmldelegatemodel_p_p.h @@ -276,7 +276,7 @@ public: void requestMoreIfNecessary(); QObject *object(Compositor::Group group, int index, QQmlIncubator::IncubationMode incubationMode); QQmlDelegateModel::ReleaseFlags release(QObject *object); - QString stringValue(Compositor::Group group, int index, const QString &name); + QVariant variantValue(Compositor::Group group, int index, const QString &name); void emitCreatedPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package); void emitInitPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package); void emitCreatedItem(QQDMIncubationTask *incubationTask, QObject *item) { @@ -378,7 +378,7 @@ public: bool isValid() const override; QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override; ReleaseFlags release(QObject *item) override; - QString stringValue(int index, const QString &role) override; + QVariant variantValue(int index, const QString &role) override; QList watchedRoles() const { return m_watchedRoles; } void setWatchedRoles(const QList &roles) override; QQmlIncubator::Status incubationStatus(int index) override; diff --git a/src/qml/types/qqmlobjectmodel.cpp b/src/qml/types/qqmlobjectmodel.cpp index 2f4d427430..b6330b4295 100644 --- a/src/qml/types/qqmlobjectmodel.cpp +++ b/src/qml/types/qqmlobjectmodel.cpp @@ -273,12 +273,12 @@ QQmlInstanceModel::ReleaseFlags QQmlObjectModel::release(QObject *item) return nullptr; } -QString QQmlObjectModel::stringValue(int index, const QString &name) +QVariant QQmlObjectModel::variantValue(int index, const QString &role) { Q_D(QQmlObjectModel); if (index < 0 || index >= d->children.count()) return QString(); - return QQmlEngine::contextForObject(d->children.at(index).item)->contextProperty(name).toString(); + return QQmlEngine::contextForObject(d->children.at(index).item)->contextProperty(role); } QQmlIncubator::Status QQmlObjectModel::incubationStatus(int) diff --git a/src/qml/types/qqmlobjectmodel_p.h b/src/qml/types/qqmlobjectmodel_p.h index 4ac4f1c65b..1284ba1780 100644 --- a/src/qml/types/qqmlobjectmodel_p.h +++ b/src/qml/types/qqmlobjectmodel_p.h @@ -79,7 +79,8 @@ public: virtual QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) = 0; virtual ReleaseFlags release(QObject *object) = 0; virtual void cancel(int) {} - virtual QString stringValue(int, const QString &) = 0; + QString stringValue(int index, const QString &role) { return variantValue(index, role).toString(); } + virtual QVariant variantValue(int, const QString &) = 0; virtual void setWatchedRoles(const QList &roles) = 0; virtual QQmlIncubator::Status incubationStatus(int index) = 0; @@ -119,7 +120,7 @@ public: bool isValid() const override; QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override; ReleaseFlags release(QObject *object) override; - QString stringValue(int index, const QString &role) override; + QVariant variantValue(int index, const QString &role) override; void setWatchedRoles(const QList &) override {} QQmlIncubator::Status incubationStatus(int index) override; diff --git a/src/qml/types/qqmltableinstancemodel_p.h b/src/qml/types/qqmltableinstancemodel_p.h index 3dd5c4e4ce..39ec66d136 100644 --- a/src/qml/types/qqmltableinstancemodel_p.h +++ b/src/qml/types/qqmltableinstancemodel_p.h @@ -122,7 +122,7 @@ public: QQmlIncubator::Status incubationStatus(int index) override; - QString stringValue(int, const QString &) override { Q_UNREACHABLE(); return QString(); } + QVariant variantValue(int, const QString &) override { Q_UNREACHABLE(); return QVariant(); } void setWatchedRoles(const QList &) override { Q_UNREACHABLE(); } int indexOf(QObject *, QObject *) const override { Q_UNREACHABLE(); return 0; } -- cgit v1.2.3 From 03314e4c8337895f89f456273526d62cf317c3c5 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 3 Apr 2019 15:10:32 +0200 Subject: QQuickWorkerScript: remove unused includes Change-Id: Ib231bb59347bc47edd2e339da4c197daf9bc24e3 Reviewed-by: Simon Hausmann --- src/qml/types/qquickworkerscript.cpp | 4 ---- 1 file changed, 4 deletions(-) (limited to 'src/qml') diff --git a/src/qml/types/qquickworkerscript.cpp b/src/qml/types/qquickworkerscript.cpp index edb112276c..aaa89846aa 100644 --- a/src/qml/types/qquickworkerscript.cpp +++ b/src/qml/types/qquickworkerscript.cpp @@ -39,10 +39,6 @@ #include "qtqmlglobal_p.h" #include "qquickworkerscript_p.h" -#if QT_CONFIG(qml_list_model) -#include "qqmllistmodel_p.h" -#include "qqmllistmodelworkeragent_p.h" -#endif #include #include -- cgit v1.2.3 From 74313fd30a79e6f26734127157870c4491331501 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 3 Apr 2019 14:53:43 +0200 Subject: QQmlTypeCompiler: Avoid including qqmldelegatecomponent_p.h QQmlAbstractDelegateComponent conceptually belongs to the models library. Change-Id: I03ad008645742c212dad6191cb1cdd16f17a0e14 Reviewed-by: Simon Hausmann --- src/qml/compiler/qqmltypecompiler.cpp | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) (limited to 'src/qml') diff --git a/src/qml/compiler/qqmltypecompiler.cpp b/src/qml/compiler/qqmltypecompiler.cpp index 66d3afc7a0..239f04a58f 100644 --- a/src/qml/compiler/qqmltypecompiler.cpp +++ b/src/qml/compiler/qqmltypecompiler.cpp @@ -44,7 +44,6 @@ #include #include #include -#include #define COMPILE_EXCEPTION(token, desc) \ { \ @@ -781,6 +780,23 @@ QQmlComponentAndAliasResolver::QQmlComponentAndAliasResolver(QQmlTypeCompiler *t { } +static bool isUsableComponent(const QMetaObject *metaObject) +{ + // The metaObject is a component we're interested in if it either is a QQmlComponent itself + // or if any of its parents is a QQmlAbstractDelegateComponent. We don't want to include + // qqmldelegatecomponent_p.h because it belongs to QtQmlModels. + + if (metaObject == &QQmlComponent::staticMetaObject) + return true; + + for (; metaObject; metaObject = metaObject->superClass()) { + if (qstrcmp(metaObject->className(), "QQmlAbstractDelegateComponent") == 0) + return true; + } + + return false; +} + void QQmlComponentAndAliasResolver::findAndRegisterImplicitComponents(const QmlIR::Object *obj, QQmlPropertyCache *propertyCache) { QmlIR::PropertyResolver propertyResolver(propertyCache); @@ -802,15 +818,9 @@ void QQmlComponentAndAliasResolver::findAndRegisterImplicitComponents(const QmlI firstMetaObject = tr->type.metaObject(); else if (tr->compilationUnit) firstMetaObject = tr->compilationUnit->rootPropertyCache()->firstCppMetaObject(); - // 1: test for QQmlComponent - if (firstMetaObject && firstMetaObject == &QQmlComponent::staticMetaObject) - continue; - // 2: test for QQmlAbstractDelegateComponent - while (firstMetaObject && firstMetaObject != &QQmlAbstractDelegateComponent::staticMetaObject) - firstMetaObject = firstMetaObject->superClass(); - if (firstMetaObject) + if (isUsableComponent(firstMetaObject)) continue; - // if here, not a QQmlComponent or a QQmlAbstractDelegateComponent, so needs wrapping + // if here, not a QQmlComponent, so needs wrapping QQmlPropertyData *pd = nullptr; if (binding->propertyNameIndex != quint32(0)) { -- cgit v1.2.3 From 7a303424f2095c53889f8102f115ec38013ef8d9 Mon Sep 17 00:00:00 2001 From: Mitch Curtis Date: Thu, 7 Mar 2019 10:21:53 +0100 Subject: Add TableModelColumn This allows us to support simple object rows by default, which we expect to be the most common use case for TableModel. Complex rows are supported, but with a limited subset of functionality. Things that could be improved: - Would be nice if we could get arbitrary/dynamic properties like ListModel has, without the complex code that comes with it. That way we could get rid of all of the role properties and users could have their own custom roles. The limitation of only having built-in roles becomes too restrictive very quickly. Change-Id: Icbdb6b39665851c55c69c0b79e0aa523c5d46dfe Reviewed-by: Venugopal Shivashankar Reviewed-by: Richard Moe Gustavsen --- .../qml/tablemodel/fruit-example-complex.qml | 134 ++++ .../tablemodel/fruit-example-delegatechooser.qml | 61 +- .../tablemodel/fruit-example-simpledelegate.qml | 54 +- .../snippets/qml/tablemodel/roleDataProvider.qml | 83 --- src/qml/types/qqmlmodelsmodule.cpp | 2 + src/qml/types/qqmltablemodel.cpp | 731 ++++++++++++--------- src/qml/types/qqmltablemodel_p.h | 56 +- src/qml/types/qqmltablemodelcolumn.cpp | 200 ++++++ src/qml/types/qqmltablemodelcolumn_p.h | 224 +++++++ src/qml/types/types.pri | 6 +- 10 files changed, 1080 insertions(+), 471 deletions(-) create mode 100644 src/qml/doc/snippets/qml/tablemodel/fruit-example-complex.qml delete mode 100644 src/qml/doc/snippets/qml/tablemodel/roleDataProvider.qml create mode 100644 src/qml/types/qqmltablemodelcolumn.cpp create mode 100644 src/qml/types/qqmltablemodelcolumn_p.h (limited to 'src/qml') diff --git a/src/qml/doc/snippets/qml/tablemodel/fruit-example-complex.qml b/src/qml/doc/snippets/qml/tablemodel/fruit-example-complex.qml new file mode 100644 index 0000000000..104a2209d7 --- /dev/null +++ b/src/qml/doc/snippets/qml/tablemodel/fruit-example-complex.qml @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//![file] +import QtQuick 2.12 +import QtQuick.Window 2.12 +import Qt.labs.qmlmodels 1.0 + +Window { + width: 400 + height: 400 + visible: true + + TableView { + anchors.fill: parent + columnSpacing: 1 + rowSpacing: 1 + boundsBehavior: Flickable.StopAtBounds + + model: TableModel { + TableModelColumn { + display: function(modelIndex) { return rows[modelIndex.row][0].checked } + setDisplay: function(modelIndex, cellData) { rows[modelIndex.row][0].checked = cellData } + } + TableModelColumn { + display: function(modelIndex) { return rows[modelIndex.row][1].amount } + setDisplay: function(modelIndex, cellData) { rows[modelIndex.row][1].amount = cellData } + } + TableModelColumn { + display: function(modelIndex) { return rows[modelIndex.row][2].fruitType } + setDisplay: function(modelIndex, cellData) { rows[modelIndex.row][2].fruitType = cellData } + } + TableModelColumn { + display: function(modelIndex) { return rows[modelIndex.row][3].fruitName } + setDisplay: function(modelIndex, cellData) { rows[modelIndex.row][3].fruitName = cellData } + } + TableModelColumn { + display: function(modelIndex) { return rows[modelIndex.row][4].fruitPrice } + setDisplay: function(modelIndex, cellData) { rows[modelIndex.row][4].fruitPrice = cellData } + } + + // Each row is one type of fruit that can be ordered +//![rows] + rows: [ + [ + // Each object (line) is one cell/column. + { checked: false, checkable: true }, + { amount: 1 }, + { fruitType: "Apple" }, + { fruitName: "Granny Smith" }, + { fruitPrice: 1.50 } + ], + [ + { checked: true, checkable: true }, + { amount: 4 }, + { fruitType: "Orange" }, + { fruitName: "Navel" }, + { fruitPrice: 2.50 } + ], + [ + { checked: false, checkable: false }, + { amount: 1 }, + { fruitType: "Banana" }, + { fruitName: "Cavendish" }, + { fruitPrice: 3.50 } + ] + ] +//![rows] + } +//![delegate] + delegate: TextInput { + text: model.display + padding: 12 + selectByMouse: true + + onAccepted: model.display = text + + Rectangle { + anchors.fill: parent + color: "#efefef" + z: -1 + } + } +//![delegate] + } +} +//![file] diff --git a/src/qml/doc/snippets/qml/tablemodel/fruit-example-delegatechooser.qml b/src/qml/doc/snippets/qml/tablemodel/fruit-example-delegatechooser.qml index 3d44f61668..d3f6176c70 100644 --- a/src/qml/doc/snippets/qml/tablemodel/fruit-example-delegatechooser.qml +++ b/src/qml/doc/snippets/qml/tablemodel/fruit-example-delegatechooser.qml @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2018 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the documentation of the Qt Toolkit. @@ -65,32 +65,37 @@ ApplicationWindow { boundsBehavior: Flickable.StopAtBounds model: TableModel { + TableModelColumn { display: "checked" } + TableModelColumn { display: "amount" } + TableModelColumn { display: "fruitType" } + TableModelColumn { display: "fruitName" } + TableModelColumn { display: "fruitPrice" } + // Each row is one type of fruit that can be ordered //![rows] rows: [ - [ - // Each object (line) is one cell/column, - // and each property in that object is a role. - { checked: false, checkable: true }, - { amount: 1 }, - { fruitType: "Apple" }, - { fruitName: "Granny Smith" }, - { fruitPrice: 1.50 } - ], - [ - { checked: true, checkable: true }, - { amount: 4 }, - { fruitType: "Orange" }, - { fruitName: "Navel" }, - { fruitPrice: 2.50 } - ], - [ - { checked: false, checkable: true }, - { amount: 1 }, - { fruitType: "Banana" }, - { fruitName: "Cavendish" }, - { fruitPrice: 3.50 } - ] + { + // Each property is one cell/column. + checked: false, + amount: 1, + fruitType: "Apple", + fruitName: "Granny Smith", + fruitPrice: 1.50 + }, + { + checked: true, + amount: 4, + fruitType: "Orange", + fruitName: "Navel", + fruitPrice: 2.50 + }, + { + checked: false, + amount: 1, + fruitType: "Banana", + fruitName: "Cavendish", + fruitPrice: 3.50 + } ] //![rows] } @@ -99,15 +104,15 @@ ApplicationWindow { DelegateChoice { column: 0 delegate: CheckBox { - checked: model.checked - onToggled: model.checked = checked + checked: model.display + onToggled: model.display = checked } } DelegateChoice { column: 1 delegate: SpinBox { - value: model.amount - onValueModified: model.amount = value + value: model.display + onValueModified: model.display = value } } DelegateChoice { diff --git a/src/qml/doc/snippets/qml/tablemodel/fruit-example-simpledelegate.qml b/src/qml/doc/snippets/qml/tablemodel/fruit-example-simpledelegate.qml index 5f00eb484b..f51c1818c3 100644 --- a/src/qml/doc/snippets/qml/tablemodel/fruit-example-simpledelegate.qml +++ b/src/qml/doc/snippets/qml/tablemodel/fruit-example-simpledelegate.qml @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2018 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the documentation of the Qt Toolkit. @@ -65,32 +65,37 @@ Window { boundsBehavior: Flickable.StopAtBounds model: TableModel { + TableModelColumn { display: "checked" } + TableModelColumn { display: "amount" } + TableModelColumn { display: "fruitType" } + TableModelColumn { display: "fruitName" } + TableModelColumn { display: "fruitPrice" } + // Each row is one type of fruit that can be ordered //![rows] rows: [ - [ - // Each object (line) is one cell/column, - // and each property in that object is a role. - { checked: false, checkable: true }, - { amount: 1 }, - { fruitType: "Apple" }, - { fruitName: "Granny Smith" }, - { fruitPrice: 1.50 } - ], - [ - { checked: true, checkable: true }, - { amount: 4 }, - { fruitType: "Orange" }, - { fruitName: "Navel" }, - { fruitPrice: 2.50 } - ], - [ - { checked: false, checkable: true }, - { amount: 1 }, - { fruitType: "Banana" }, - { fruitName: "Cavendish" }, - { fruitPrice: 3.50 } - ] + { + // Each property is one cell/column. + checked: false, + amount: 1, + fruitType: "Apple", + fruitName: "Granny Smith", + fruitPrice: 1.50 + }, + { + checked: true, + amount: 4, + fruitType: "Orange", + fruitName: "Navel", + fruitPrice: 2.50 + }, + { + checked: false, + amount: 1, + fruitType: "Banana", + fruitName: "Cavendish", + fruitPrice: 3.50 + } ] //![rows] } @@ -100,7 +105,6 @@ Window { padding: 12 selectByMouse: true - // TODO: the property used here is undefined onAccepted: model.display = text Rectangle { diff --git a/src/qml/doc/snippets/qml/tablemodel/roleDataProvider.qml b/src/qml/doc/snippets/qml/tablemodel/roleDataProvider.qml deleted file mode 100644 index 63978a370d..0000000000 --- a/src/qml/doc/snippets/qml/tablemodel/roleDataProvider.qml +++ /dev/null @@ -1,83 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the documentation of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, you may use this file under the terms of the BSD license -** as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ -//![0] -import QtQuick 2.12 -import Qt.labs.qmlmodels 1.0 - -TableView { - columnSpacing: 1; rowSpacing: 1 - model: TableModel { - property real taxPercent: 10 - rows: [ - [{ fruitType: "Apple" }, { fruitPrice: 1.50 }], - [{ fruitType: "Orange" }, { fruitPrice: 2.50 }] - ] - roleDataProvider: function(index, role, cellData) { - if (role === "display") { - if (cellData.hasOwnProperty("fruitPrice")) { - console.log("row", index.row, "taxing your fruit", JSON.stringify(cellData)) - return (cellData.fruitPrice * (1 + taxPercent / 100)).toFixed(2); - } - else if (cellData.hasOwnProperty("fruitType")) - return cellData.fruitType; - } - return cellData; - } - } - delegate: Rectangle { - implicitWidth: 150; implicitHeight: 30 - color: "lightsteelblue" - Text { - x: 6; anchors.verticalCenter: parent.verticalCenter - text: display - } - } -} -//![0] diff --git a/src/qml/types/qqmlmodelsmodule.cpp b/src/qml/types/qqmlmodelsmodule.cpp index b7b9c9ee1c..119ede256f 100644 --- a/src/qml/types/qqmlmodelsmodule.cpp +++ b/src/qml/types/qqmlmodelsmodule.cpp @@ -48,6 +48,7 @@ #endif #include #include +#include QT_BEGIN_NAMESPACE @@ -77,6 +78,7 @@ void QQmlModelsModule::defineLabsModule() qmlRegisterType(uri, 1, 0, "DelegateChooser"); qmlRegisterType(uri, 1, 0, "DelegateChoice"); qmlRegisterType(uri, 1, 0, "TableModel"); + qmlRegisterType(uri, 1, 0, "TableModelColumn"); } QT_END_NAMESPACE diff --git a/src/qml/types/qqmltablemodel.cpp b/src/qml/types/qqmltablemodel.cpp index 6068155f5a..4a96e7a46b 100644 --- a/src/qml/types/qqmltablemodel.cpp +++ b/src/qml/types/qqmltablemodel.cpp @@ -47,27 +47,26 @@ QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcTableModel, "qt.qml.tablemodel") -static const QString lengthPropertyName = QStringLiteral("length"); -static const QString displayRoleName = QStringLiteral("display"); - /*! \qmltype TableModel \instantiates QQmlTableModel \inqmlmodule Qt.labs.qmlmodels \brief Encapsulates a simple table model. - \since 5.12 - - The TableModel type stores JavaScript objects as data for a table model - that can be used with \l TableView. + \since 5.14 - The following snippet shows the simplest use case for TableModel: + The TableModel type stores JavaScript/JSON objects as data for a table + model that can be used with \l TableView. It is intended to support + very simple models without requiring the creation of a custom + QAbstractTableModel subclass in C++. \snippet qml/tablemodel/fruit-example-simpledelegate.qml file - The model's initial data is set with either the \l rows property or by - calling \l appendRow(). Once the first row has been added to the table, the - columns and roles are established and will be fixed for the lifetime of the - model. + The model's initial row data is set with either the \l rows property or by + calling \l appendRow(). Each column in the model is specified by declaring + a \l TableModelColumn instance, where the order of each instance determines + its column index. Once the model's \l Component.completed() signal has been + emitted, the columns and roles will have been established and are then + fixed for the lifetime of the model. To access a specific row, the \l getRow() function can be used. It's also possible to access the model's JavaScript data @@ -87,14 +86,65 @@ static const QString displayRoleName = QStringLiteral("display"); data that is set, it will be automatically converted via \l {QVariant::canConvert()}{QVariant}. - For convenience, TableModel provides the \c display role if it is not - explicitly specified in any column. When a column only has one role - declared, that role will be used used as the display role. However, when - there is more than one role in a column, which role will be used is - undefined. This is because JavaScript does not guarantee that properties - within an object can be accessed according to the order in which they were - declared. This is why \c checkable may be used as the display role for the - first column even though \c checked is declared before it, for example. + \section1 Supported Row Data Structures + + TableModel is designed to work with JavaScript/JSON data, where each row + is a simple key-pair object: + + \code + { + // Each property is one cell/column. + checked: false, + amount: 1, + fruitType: "Apple", + fruitName: "Granny Smith", + fruitPrice: 1.50 + }, + // ... + \endcode + + As model manipulation in Qt is done via row and column indices, + and because object keys are unordered, each column must be specified via + TableModelColumn. This allows mapping Qt's built-in roles to any property + in each row object. + + Complex row structures are supported, but with limited functionality. + As TableModel has no way of knowing how each row is structured, + it cannot manipulate it. As a consequence of this, the copy of the + model data that TableModel has stored in \l rows is not kept in sync + with the source data that was set in QML. For these reasons, TableModel + relies on the user to handle simple data manipulation. + + For example, suppose you wanted to have several roles per column. One way + of doing this is to use a data source where each row is an array and each + cell is an object. To use this data source with TableModel, define a + getter and setter: + + \code + TableModel { + TableModelColumn { + display: function(modelIndex) { return rows[modelIndex.row][0].checked } + setDisplay: function(modelIndex, cellData) { rows[modelIndex.row][0].checked = cellData } + } + // ... + + rows: [ + [ + { checked: false, checkable: true }, + { amount: 1 }, + { fruitType: "Apple" }, + { fruitName: "Granny Smith" }, + { fruitPrice: 1.50 } + ] + // ... + ] + } + \endcode + + The row above is one example of a complex row. + + \note Row manipulation functions such as \l appendRow(), \l removeRow(), + etc. are not supported when using complex rows. \section1 Using DelegateChooser with TableModel @@ -112,13 +162,12 @@ static const QString displayRoleName = QStringLiteral("display"); \l [QtQuickControls2]{TextField}, and so that delegate is declared last as a fallback. - \sa QAbstractTableModel, TableView + \sa TableModelColumn, TableView, QAbstractTableModel */ QQmlTableModel::QQmlTableModel(QObject *parent) : QAbstractTableModel(parent) { - mRoleNames = QAbstractTableModel::roleNames(); } QQmlTableModel::~QQmlTableModel() @@ -153,31 +202,34 @@ void QQmlTableModel::setRows(const QVariant &rows) return; } - QVariant firstRowAsVariant; - QVariantList firstRow; - if (!rowsAsVariantList.isEmpty()) { - // There are rows to validate. If they're not valid, - // we'll return early without changing anything. - firstRowAsVariant = rowsAsVariantList.first(); - firstRow = firstRowAsVariant.toList(); + if (!componentCompleted) { + // Store the rows until we can call doSetRows() after component completion. + mRows = rowsAsVariantList; + return; + } - if (firstRowAsVariant.type() != QVariant::List) { - qmlWarning(this) << "setRows(): each row in \"rows\" must be an array of objects"; - return; - } + doSetRows(rowsAsVariantList); +} - if (mColumnCount > 0) { - qCDebug(lcTableModel) << "validating" << rowsAsVariantList.size() - << "rows against existing metadata"; - - // This is not the first time the rows have been set; validate the new columns. - for (int i = 0; i < rowsAsVariantList.size(); ++i) { - // validateNewRow() expects a QVariant wrapping a QJSValue, so to - // simplify the code, just create one here. - const QVariant row = QVariant::fromValue(rowsAsJSValue.property(i)); - if (!validateNewRow("setRows()", row, i)) - return; - } +void QQmlTableModel::doSetRows(const QVariantList &rowsAsVariantList) +{ + Q_ASSERT(componentCompleted); + + // By now, all TableModelColumns should have been set. + if (mColumns.isEmpty()) { + qmlWarning(this) << "No TableModelColumns were set; model will be empty"; + return; + } + + const bool firstTimeValidRowsHaveBeenSet = mColumnMetadata.isEmpty(); + if (!firstTimeValidRowsHaveBeenSet) { + // This is not the first time rows have been set; validate each one. + for (int rowIndex = 0; rowIndex < rowsAsVariantList.size(); ++rowIndex) { + // validateNewRow() expects a QVariant wrapping a QJSValue, so to + // simplify the code, just create one here. + const QVariant row = QVariant::fromValue(rowsAsVariantList.at(rowIndex)); + if (!validateNewRow("setRows()", row, rowIndex, SetRowsOperation)) + return; } } @@ -191,59 +243,9 @@ void QQmlTableModel::setRows(const QVariant &rows) mRows = rowsAsVariantList; mRowCount = mRows.size(); - const bool isFirstTimeSet = mColumnCount == 0; - if (isFirstTimeSet && mRowCount > 0) { - // This is the first time the rows have been set, so establish - // the column count and gather column metadata. - mColumnCount = firstRow.size(); - qCDebug(lcTableModel) << "gathering metadata for" << mColumnCount << "columns from first row:"; - - // Go through each property of each cell in the first row - // and make a role name from it. - int userRoleKey = Qt::UserRole; - for (int columnIndex = 0; columnIndex < mColumnCount; ++columnIndex) { - // We need it as a QVariantMap because we need to get - // the name of the property, which we can't do with QJSValue's API. - const QVariantMap column = firstRow.at(columnIndex).toMap(); - const QStringList columnPropertyNames = column.keys(); - ColumnProperties properties; - int propertyInfoIndex = 0; - - qCDebug(lcTableModel).nospace() << "- column " << columnIndex << ":"; - - for (const QString &roleName : columnPropertyNames) { - // QML/JS supports utf8. - const QByteArray roleNameUtf8 = roleName.toUtf8(); - if (!mRoleNames.values().contains(roleNameUtf8)) { - // We don't already have this role name, so it's a user role. - mRoleNames[userRoleKey] = roleName.toUtf8().constData(); - qCDebug(lcTableModel) << " - added new user role" << roleName << "with key" << userRoleKey; - ++userRoleKey; - } else { - qCDebug(lcTableModel) << " - found existing role" << roleName; - } - - if (properties.explicitDisplayRoleIndex == -1 && roleName == displayRoleName) { - // The user explicitly declared a "display" role, - // so now we don't need to make it the first role in the column for them. - properties.explicitDisplayRoleIndex = propertyInfoIndex; - } - - // Keep track of the type of property so we can use it to validate new rows later on. - const QVariant roleValue = column.value(roleName); - const auto propertyInfo = ColumnPropertyInfo(roleName, roleValue.type(), - QString::fromLatin1(roleValue.typeName())); - properties.infoForProperties.append(propertyInfo); - - qCDebug(lcTableModel) << " - column property" << propertyInfo.name - << "has type" << propertyInfo.typeName; - - ++propertyInfoIndex; - } - - mColumnProperties.append(properties); - } - } + // Gather metadata the first time rows is set. + if (firstTimeValidRowsHaveBeenSet && !mRows.isEmpty()) + fetchColumnMetadata(); endResetModel(); @@ -255,6 +257,94 @@ void QQmlTableModel::setRows(const QVariant &rows) emit columnCountChanged(); } +QQmlTableModel::ColumnRoleMetadata QQmlTableModel::fetchColumnRoleData(const QString &roleNameKey, + QQmlTableModelColumn *tableModelColumn, int columnIndex) const +{ + const QVariant firstRow = mRows.first(); + ColumnRoleMetadata roleData; + + QJSValue columnRoleGetter = tableModelColumn->getterAtRole(roleNameKey); + if (columnRoleGetter.isUndefined()) { + // This role is not defined, which is fine; just skip it. + return roleData; + } + + if (columnRoleGetter.isString()) { + // The role is set as a string, so we assume the row is a simple object. + if (firstRow.type() != QVariant::Map) { + qmlWarning(this).quote() << "expected row for role " + << roleNameKey << " of TableModelColumn at index " + << columnIndex << " to be a simple object, but it's " + << firstRow.typeName() << " instead: " << firstRow; + return roleData; + } + const QVariantMap firstRowAsMap = firstRow.toMap(); + const QString rolePropertyName = columnRoleGetter.toString(); + const QVariant roleProperty = firstRowAsMap.value(rolePropertyName); + + roleData.isStringRole = true; + roleData.name = rolePropertyName; + roleData.type = roleProperty.type(); + roleData.typeName = QString::fromLatin1(roleProperty.typeName()); + } else if (columnRoleGetter.isCallable()) { + // The role is provided via a function, which means the row is complex and + // the user needs to provide the data for it. + const auto modelIndex = index(0, columnIndex); + const auto args = QJSValueList() << qmlEngine(this)->toScriptValue(modelIndex); + const QVariant cellData = columnRoleGetter.call(args).toVariant(); + + // We don't know the property name since it's provided through the function. + // roleData.name = ??? + roleData.isStringRole = false; + roleData.type = cellData.type(); + roleData.typeName = QString::fromLatin1(cellData.typeName()); + } else { + // Invalid role. + qmlWarning(this) << "TableModelColumn role for column at index " + << columnIndex << " must be either a string or a function; actual type is: " + << columnRoleGetter.toString(); + } + + return roleData; +} + +void QQmlTableModel::fetchColumnMetadata() +{ + qCDebug(lcTableModel) << "gathering metadata for" << mColumnCount << "columns from first row:"; + + static const auto supportedRoleNames = QQmlTableModelColumn::supportedRoleNames(); + + // Since we support different data structures at the row level, we require that there + // is a TableModelColumn for each column. + // Collect and cache metadata for each column. This makes data lookup faster. + for (int columnIndex = 0; columnIndex < mColumns.size(); ++columnIndex) { + QQmlTableModelColumn *column = mColumns.at(columnIndex); + qCDebug(lcTableModel).nospace() << "- column " << columnIndex << ":"; + + ColumnMetadata metaData; + const auto builtInRoleKeys = supportedRoleNames.keys(); + for (const int builtInRoleKey : builtInRoleKeys) { + const QString builtInRoleName = supportedRoleNames.value(builtInRoleKey); + ColumnRoleMetadata roleData = fetchColumnRoleData(builtInRoleName, column, columnIndex); + if (roleData.type == QVariant::Invalid) { + // This built-in role was not specified in this column. + continue; + } + + qCDebug(lcTableModel).nospace() << " - added metadata for built-in role " + << builtInRoleName << " at column index " << columnIndex + << ": name=" << roleData.name << " typeName=" << roleData.typeName + << " type=" << roleData.type; + + // This column now supports this specific built-in role. + metaData.roles.insert(builtInRoleName, roleData); + // Add it if it doesn't already exist. + mRoleNames[builtInRoleKey] = builtInRoleName.toLatin1(); + } + mColumnMetadata.insert(columnIndex, metaData); + } +} + /*! \qmlmethod TableModel::appendRow(object row) @@ -262,13 +352,13 @@ void QQmlTableModel::setRows(const QVariant &rows) values (cells) in \a row. \code - model.appendRow([ - { checkable: true, checked: false }, - { amount: 1 }, - { fruitType: "Pear" }, - { fruitName: "Williams" }, - { fruitPrice: 1.50 }, - ]) + model.appendRow({ + checkable: true, + amount: 1, + fruitType: "Pear", + fruitName: "Williams", + fruitPrice: 1.50, + }) \endcode \sa insertRow(), setRow(), removeRow() @@ -306,7 +396,7 @@ void QQmlTableModel::clear() \code Component.onCompleted: { // These two lines are equivalent. - console.log(model.getRow(0).fruitName); + console.log(model.getRow(0).display); console.log(model.rows[0].fruitName); } \endcode @@ -331,13 +421,13 @@ QVariant QQmlTableModel::getRow(int rowIndex) values (cells) in \a row. \code - model.insertRow(2, [ - { checkable: true, checked: false }, - { amount: 1 }, - { fruitType: "Pear" }, - { fruitName: "Williams" }, - { fruitPrice: 1.50 }, - ]) + model.insertRow(2, { + checkable: true, checked: false, + amount: 1, + fruitType: "Pear", + fruitName: "Williams", + fruitPrice: 1.50, + }) \endcode The \a rowIndex must be to an existing item in the list, or one past @@ -363,13 +453,32 @@ void QQmlTableModel::doInsert(int rowIndex, const QVariant &row) mRows.insert(rowIndex, rowAsVariant); ++mRowCount; - qCDebug(lcTableModel).nospace() << "inserted the following row to the model at index" - << rowIndex << ":\n" << rowAsVariant.toList(); + qCDebug(lcTableModel).nospace() << "inserted the following row to the model at index " + << rowIndex << ":\n" << rowAsVariant.toMap(); + + // Gather metadata the first time a row is added. + if (mColumnMetadata.isEmpty()) + fetchColumnMetadata(); endInsertRows(); emit rowCountChanged(); } +void QQmlTableModel::classBegin() +{ +} + +void QQmlTableModel::componentComplete() +{ + componentCompleted = true; + + mColumnCount = mColumns.size(); + if (mColumnCount > 0) + emit columnCountChanged(); + + doSetRows(mRows); +} + /*! \qmlmethod TableModel::moveRow(int fromRowIndex, int toRowIndex, int rows) @@ -496,13 +605,13 @@ void QQmlTableModel::removeRow(int rowIndex, int rows) All columns/cells must be present in \c row, and in the correct order. \code - model.setRow(0, [ - { checkable: true, checked: false }, - { amount: 1 }, - { fruitType: "Pear" }, - { fruitName: "Williams" }, - { fruitPrice: 1.50 }, - ]) + model.setRow(0, { + checkable: true, + amount: 1, + fruitType: "Pear", + fruitName: "Williams", + fruitPrice: 1.50, + }) \endcode If \a rowIndex is equal to \c rowCount(), then a new row is appended to the @@ -529,36 +638,40 @@ void QQmlTableModel::setRow(int rowIndex, const QVariant &row) } } -/*! - \qmlproperty var TableModel::roleDataProvider - - This property can hold a function that will map roles to values. - - When assigned, it will be called each time data() is called, to enable - extracting arbitrary values, converting the data in arbitrary ways, or even - doing calculations. It takes 3 arguments: \c index (\l QModelIndex), - \c role (string), and \c cellData (object), which is the complete data that - is stored in the given cell. (If the cell contains a JS object with - multiple named values, the entire object will be given in \c cellData.) - The function that you define must return the value to be used; for example - a typical delegate will display the value returned for the \c display role, - so you can check whether that is the role and return data in a form that is - suitable for the delegate to show: - - \snippet qml/tablemodel/roleDataProvider.qml 0 -*/ -QJSValue QQmlTableModel::roleDataProvider() const +QQmlListProperty QQmlTableModel::columns() { - return mRoleDataProvider; + return QQmlListProperty(this, nullptr, + &QQmlTableModel::columns_append, + &QQmlTableModel::columns_count, + &QQmlTableModel::columns_at, + &QQmlTableModel::columns_clear); } -void QQmlTableModel::setRoleDataProvider(QJSValue roleDataProvider) +void QQmlTableModel::columns_append(QQmlListProperty *property, + QQmlTableModelColumn *value) { - if (roleDataProvider.strictlyEquals(mRoleDataProvider)) - return; + QQmlTableModel *model = static_cast(property->object); + QQmlTableModelColumn *column = qobject_cast(value); + if (column) + model->mColumns.append(column); +} - mRoleDataProvider = roleDataProvider; - emit roleDataProviderChanged(); +int QQmlTableModel::columns_count(QQmlListProperty *property) +{ + const QQmlTableModel *model = static_cast(property->object); + return model->mColumns.count(); +} + +QQmlTableModelColumn *QQmlTableModel::columns_at(QQmlListProperty *property, int index) +{ + const QQmlTableModel *model = static_cast(property->object); + return model->mColumns.at(index); +} + +void QQmlTableModel::columns_clear(QQmlListProperty *property) +{ + QQmlTableModel *model = static_cast(property->object); + return model->mColumns.clear(); } /*! @@ -574,14 +687,19 @@ void QQmlTableModel::setRoleDataProvider(QJSValue roleDataProvider) TableModel { id: model + + TableModelColumn { display: "fruitType" } + TableModelColumn { display: "fruitPrice" } + rows: [ - [{ fruitType: "Apple" }, { fruitPrice: 1.50 }], - [{ fruitType: "Orange" }, { fruitPrice: 2.50 }] + { fruitType: "Apple", fruitPrice: 1.50 }, + { fruitType: "Orange", fruitPrice: 2.50 } ] + Component.onCompleted: { for (var r = 0; r < model.rowCount; ++r) { - console.log("An " + model.data(model.index(r, 0)).fruitType + - " costs " + model.data(model.index(r, 1)).fruitPrice.toFixed(2)) + console.log("An " + model.data(model.index(r, 0)).display + + " costs " + model.data(model.index(r, 1)).display.toFixed(2)) } } } @@ -658,27 +776,31 @@ QVariant QQmlTableModel::data(const QModelIndex &index, int role) const if (column < 0 || column >= columnCount()) return QVariant(); - if (!mRoleNames.contains(role)) + const ColumnMetadata columnMetadata = mColumnMetadata.at(index.column()); + const QString roleName = QString::fromUtf8(mRoleNames.value(role)); + if (!columnMetadata.roles.contains(roleName)) { + qmlWarning(this) << "setData(): no role named " << roleName + << " at column index " << column << ". The available roles for that column are: " + << columnMetadata.roles.keys(); return QVariant(); + } - const QVariantList rowData = mRows.at(row).toList(); - - if (mRoleDataProvider.isCallable()) { - auto engine = qmlEngine(this); - const auto args = QJSValueList() << - engine->toScriptValue(index) << - QString::fromUtf8(mRoleNames.value(role)) << - engine->toScriptValue(rowData.at(column)); - return const_cast(this)->mRoleDataProvider.call(args).toVariant(); + const ColumnRoleMetadata roleData = columnMetadata.roles.value(roleName); + if (roleData.isStringRole) { + // We know the data structure, so we can get the data for the user. + const QVariantMap rowData = mRows.at(row).toMap(); + const QString propertyName = columnMetadata.roles.value(roleName).name; + const QVariant value = rowData.value(propertyName); + return value; } - // TODO: should we also allow this code to be executed if roleDataProvider doesn't - // handle the role/column, so that it only has to handle the case where there is - // more than one role in a column? - const QVariantMap columnData = rowData.at(column).toMap(); - const QString propertyName = columnPropertyNameFromRole(column, role); - const QVariant value = columnData.value(propertyName); - return value; + // We don't know the data structure, so the user has to modify their data themselves. + // First, find the getter for this column and role. + QJSValue getter = mColumns.at(column)->getterAtRole(roleName); + + // Then, call it and return what it returned. + const auto args = QJSValueList() << qmlEngine(this)->toScriptValue(index); + return getter.call(args).toVariant(); } /*! @@ -691,9 +813,9 @@ QVariant QQmlTableModel::data(const QModelIndex &index, int role) const */ bool QQmlTableModel::setData(const QModelIndex &index, const QString &role, const QVariant &value) { - const int iRole = mRoleNames.key(role.toUtf8(), -1); - if (iRole >= 0) - return setData(index, value, iRole); + const int intRole = mRoleNames.key(role.toUtf8(), -1); + if (intRole >= 0) + return setData(index, value, intRole); return false; } @@ -707,56 +829,92 @@ bool QQmlTableModel::setData(const QModelIndex &index, const QVariant &value, in if (column < 0 || column >= columnCount()) return false; - if (!mRoleNames.contains(role)) - return false; - - const QVariantList rowData = mRows.at(row).toList(); - const QString propertyName = columnPropertyNameFromRole(column, role); + const QString roleName = QString::fromUtf8(mRoleNames.value(role)); qCDebug(lcTableModel).nospace() << "setData() called with index " - << index << ", value " << value << " and role " << propertyName; + << index << ", value " << value << " and role " << roleName; // Verify that the role exists for this column. - const ColumnPropertyInfo propertyInfo = findColumnPropertyInfo(column, propertyName); - if (!propertyInfo.isValid()) { - QString message; - QDebug stream(&message); - stream.nospace() << "setData(): no role named " << propertyName - << " at column index " << column << ". The available roles for that column are:\n"; - - const QVector availableProperties = mColumnProperties.at(column).infoForProperties; - for (auto propertyInfo : availableProperties) - stream << " - " << propertyInfo.name << " (" << qPrintable(propertyInfo.typeName) << ")"; - - qmlWarning(this) << message; + const ColumnMetadata columnMetadata = mColumnMetadata.at(index.column()); + if (!columnMetadata.roles.contains(roleName)) { + qmlWarning(this) << "setData(): no role named \"" << roleName + << "\" at column index " << column << ". The available roles for that column are: " + << columnMetadata.roles.keys(); return false; } // Verify that the type of the value is what we expect. // If the value set is not of the expected type, we can try to convert it automatically. + const ColumnRoleMetadata roleData = columnMetadata.roles.value(roleName); QVariant effectiveValue = value; - if (value.type() != propertyInfo.type) { - if (!value.canConvert(int(propertyInfo.type))) { + if (value.type() != roleData.type) { + if (!value.canConvert(int(roleData.type))) { qmlWarning(this).nospace() << "setData(): the value " << value - << " set at row " << row << " column " << column << " with role " << propertyName - << " cannot be converted to " << propertyInfo.typeName; + << " set at row " << row << " column " << column << " with role " << roleName + << " cannot be converted to " << roleData.typeName; return false; } - if (!effectiveValue.convert(int(propertyInfo.type))) { + if (!effectiveValue.convert(int(roleData.type))) { qmlWarning(this).nospace() << "setData(): failed converting value " << value - << " set at row " << row << " column " << column << " with role " << propertyName - << " to " << propertyInfo.typeName; + << " set at row " << row << " column " << column << " with role " << roleName + << " to " << roleData.typeName; return false; } } - QVariantMap modifiedColumn = rowData.at(column).toMap(); - modifiedColumn[propertyName] = value; + if (roleData.isStringRole) { + // We know the data structure, so we can set it for the user. + QVariantMap modifiedRow = mRows.at(row).toMap(); + modifiedRow[roleData.name] = value; + + mRows[row] = modifiedRow; + } else { + // We don't know the data structure, so the user has to modify their data themselves. + auto engine = qmlEngine(this); + auto args = QJSValueList() + // arg 0: modelIndex. + << engine->toScriptValue(index) + // arg 1: cellData. + << engine->toScriptValue(value); + // Do the actual setting. + QJSValue setter = mColumns.at(column)->setterAtRole(roleName); + setter.call(args); + + /* + The chain of events so far: - QVariantList modifiedRow = rowData; - modifiedRow[column] = modifiedColumn; - mRows[row] = modifiedRow; + - User did e.g.: model.edit = textInput.text + - setData() is called + - setData() calls the setter + (remember that we need to emit the dataChanged() signal, + which is why the user can't just set the data directly in the delegate) + + Now the user's setter function has modified *their* copy of the + data, but *our* copy of the data is old. Imagine the getters and setters looked like this: + + display: function(modelIndex) { return rows[modelIndex.row][1].amount } + setDisplay: function(modelIndex, cellData) { rows[modelIndex.row][1].amount = cellData } + + We don't know the structure of the user's data, so we can't just do + what we do above for the isStringRole case: + + modifiedRow[column][roleName] = value + + This means that, besides getting the implicit row count when rows is initially set, + our copy of the data is unused when it comes to complex columns. + + Another point to note is that we can't pass rowData in to the getter as a convenience, + because we would be passing in *our* copy of the row, which is not up-to-date. + Since the user already has access to the data, it's not a big deal for them to do: + + display: function(modelIndex) { return rows[modelIndex.row][1].amount } + + instead of: + + display: function(modelIndex, rowData) { return rowData[1].amount } + */ + } QVector rolesChanged; rolesChanged.append(role); @@ -770,35 +928,36 @@ QHash QQmlTableModel::roleNames() const return mRoleNames; } -QQmlTableModel::ColumnPropertyInfo::ColumnPropertyInfo() +QQmlTableModel::ColumnRoleMetadata::ColumnRoleMetadata() { } -QQmlTableModel::ColumnPropertyInfo::ColumnPropertyInfo( - const QString &name, QVariant::Type type, const QString &typeName) : +QQmlTableModel::ColumnRoleMetadata::ColumnRoleMetadata( + bool isStringRole, const QString &name, QVariant::Type type, const QString &typeName) : + isStringRole(isStringRole), name(name), type(type), typeName(typeName) { } -bool QQmlTableModel::ColumnPropertyInfo::isValid() const +bool QQmlTableModel::ColumnRoleMetadata::isValid() const { return !name.isEmpty(); } bool QQmlTableModel::validateRowType(const char *functionName, const QVariant &row) const { - if (row.userType() != qMetaTypeId()) { - qmlWarning(this) << functionName << ": expected \"row\" argument to be an array," - << " but got " << row.typeName() << " instead"; + if (!row.canConvert()) { + qmlWarning(this) << functionName << ": expected \"row\" argument to be a QJSValue," + << " but got " << row.typeName() << " instead:\n" << row; return false; } - const QVariant rowAsVariant = row.value().toVariant(); - if (rowAsVariant.type() != QVariant::List) { - qmlWarning(this) << functionName << ": expected \"row\" argument to be an array," - << " but got " << row.typeName() << " instead"; + const QJSValue rowAsJSValue = row.value(); + if (!rowAsJSValue.isObject() && !rowAsJSValue.isArray()) { + qmlWarning(this) << functionName << ": expected \"row\" argument " + << "to be an object or array, but got:\n" << rowAsJSValue.toString(); return false; } @@ -806,12 +965,21 @@ bool QQmlTableModel::validateRowType(const char *functionName, const QVariant &r } bool QQmlTableModel::validateNewRow(const char *functionName, const QVariant &row, - int rowIndex, NewRowOperationFlag appendFlag) const + int rowIndex, NewRowOperationFlag operation) const { - if (!validateRowType(functionName, row)) + if (mColumnMetadata.isEmpty()) { + // There is no column metadata, so we have nothing to validate the row against. + // Rows have to be added before we can gather metadata from them, so just this + // once we'll return true to allow the rows to be added. + return true; + } + + // Don't require each row to be a QJSValue when setting all rows, + // as they won't be; they'll be QVariantMap. + if (operation != SetRowsOperation && !validateRowType(functionName, row)) return false; - if (appendFlag == OtherOperation) { + if (operation == OtherOperation) { // Inserting/setting. if (rowIndex < 0) { qmlWarning(this) << functionName << ": \"rowIndex\" cannot be negative"; @@ -825,29 +993,48 @@ bool QQmlTableModel::validateNewRow(const char *functionName, const QVariant &ro } } - const QVariant rowAsVariant = row.value().toVariant(); - const QVariantList rowAsList = rowAsVariant.toList(); + const QVariant rowAsVariant = operation == SetRowsOperation + ? row : row.value().toVariant(); + if (rowAsVariant.type() != QVariant::Map) { + qmlWarning(this) << functionName << ": row manipulation functions " + << "do not support complex rows (row index: " << rowIndex << ")"; + return false; + } - const int columnCount = rowAsList.size(); - if (columnCount != mColumnCount) { + const QVariantMap rowAsMap = rowAsVariant.toMap(); + const int columnCount = rowAsMap.size(); + if (columnCount < mColumnCount) { qmlWarning(this) << functionName << ": expected " << mColumnCount - << " columns, but got " << columnCount; + << " columns, but only got " << columnCount; return false; } - // Verify that the row's columns and their roles match the name and type of existing data. - // This iterates across the columns in the row. For example: - // [ - // { checkable: true, checked: false }, // columnIndex == 0 - // { amount: 1 }, // columnIndex == 1 - // { fruitType: "Orange" }, // etc. - // { fruitName: "Navel" }, - // { fruitPrice: 2.50 } - // ], - for (int columnIndex = 0; columnIndex < mColumnCount; ++columnIndex) { - const QVariantMap column = rowAsList.at(columnIndex).toMap(); - if (!validateColumnPropertyTypes(functionName, column, columnIndex)) - return false; + // We can't validate complex structures, but we can make sure that + // each simple string-based role in each column is correct. + for (int columnIndex = 0; columnIndex < mColumns.size(); ++columnIndex) { + QQmlTableModelColumn *column = mColumns.at(columnIndex); + const QHash getters = column->getters(); + const auto roleNames = getters.keys(); + const ColumnMetadata columnMetadata = mColumnMetadata.at(columnIndex); + for (const QString &roleName : roleNames) { + const ColumnRoleMetadata roleData = columnMetadata.roles.value(roleName); + if (!roleData.isStringRole) + continue; + + if (!rowAsMap.contains(roleData.name)) { + qmlWarning(this).quote() << functionName << ": expected a property named " + << roleData.name << " in row at index " << rowIndex << ", but couldn't find one"; + return false; + } + + const QVariant rolePropertyValue = rowAsMap.value(roleData.name); + if (rolePropertyValue.type() != roleData.type) { + qmlWarning(this).quote() << functionName << ": expected the property named " + << roleData.name << " to be of type " << roleData.typeName + << ", but got " << QString::fromLatin1(rolePropertyValue.typeName()) << " instead"; + return false; + } + } } return true; @@ -869,82 +1056,4 @@ bool QQmlTableModel::validateRowIndex(const char *functionName, const char *argu return true; } -bool QQmlTableModel::validateColumnPropertyTypes(const char *functionName, - const QVariantMap &column, int columnIndex) const -{ - // Actual - const QVariantList columnProperties = column.values(); - const QStringList propertyNames = column.keys(); - // Expected - const QVector properties = mColumnProperties.at(columnIndex).infoForProperties; - - // This iterates across the properties in the column. For example: - // 0 1 2 - // { foo: "A", bar: 1, baz: true }, - for (int propertyIndex = 0; propertyIndex < properties.size(); ++propertyIndex) { - const QString propertyName = propertyNames.at(propertyIndex); - const QVariant propertyValue = columnProperties.at(propertyIndex); - const ColumnPropertyInfo expectedPropertyFormat = properties.at(propertyIndex); - - if (!validateColumnPropertyType(functionName, propertyName, - propertyValue, expectedPropertyFormat, columnIndex)) { - return false; - } - } - - return true; -} - -bool QQmlTableModel::validateColumnPropertyType(const char *functionName, const QString &propertyName, - const QVariant &propertyValue, const ColumnPropertyInfo &expectedPropertyFormat, int columnIndex) const -{ - if (propertyName != expectedPropertyFormat.name) { - qmlWarning(this) << functionName - << ": expected property named " << expectedPropertyFormat.name - << " at column index " << columnIndex - << ", but got " << propertyName << " instead"; - return false; - } - - if (propertyValue.type() != expectedPropertyFormat.type) { - qmlWarning(this) << functionName - << ": expected property with type " << expectedPropertyFormat.typeName - << " at column index " << columnIndex - << ", but got " << propertyValue.typeName() << " instead"; - return false; - } - - return true; -} - -QQmlTableModel::ColumnPropertyInfo QQmlTableModel::findColumnPropertyInfo( - int columnIndex, const QString &columnPropertyName) const -{ - // TODO: check if a hash with its string-based lookup is faster, - // keeping in mind that we may be doing index-based lookups too. - const QVector properties = mColumnProperties.at(columnIndex).infoForProperties; - for (int i = 0; i < properties.size(); ++i) { - const ColumnPropertyInfo &info = properties.at(i); - if (info.name == columnPropertyName) - return info; - } - - return ColumnPropertyInfo(); -} - -QString QQmlTableModel::columnPropertyNameFromRole(int columnIndex, int role) const -{ - QString propertyName; - if (role == Qt::DisplayRole && mColumnProperties.at(columnIndex).explicitDisplayRoleIndex == -1) { - // The user is getting or setting data for the display role, - // but didn't specify any role with the name "display" in this column. - // So, we give them the implicit display role, aka the first property we find. - propertyName = mColumnProperties.at(columnIndex).infoForProperties.first().name; - } else { - // QML/JS supports utf8. - propertyName = QString::fromUtf8(mRoleNames.value(role)); - } - return propertyName; -} - QT_END_NAMESPACE diff --git a/src/qml/types/qqmltablemodel_p.h b/src/qml/types/qqmltablemodel_p.h index 33b2189fcd..a1bb97e7d4 100644 --- a/src/qml/types/qqmltablemodel_p.h +++ b/src/qml/types/qqmltablemodel_p.h @@ -55,17 +55,21 @@ #include #include #include +#include #include +#include QT_BEGIN_NAMESPACE -class Q_QML_PRIVATE_EXPORT QQmlTableModel : public QAbstractTableModel +class Q_QML_PRIVATE_EXPORT QQmlTableModel : public QAbstractTableModel, public QQmlParserStatus { Q_OBJECT Q_PROPERTY(int columnCount READ columnCount NOTIFY columnCountChanged FINAL) Q_PROPERTY(int rowCount READ rowCount NOTIFY rowCountChanged FINAL) Q_PROPERTY(QVariant rows READ rows WRITE setRows NOTIFY rowsChanged FINAL) - Q_PROPERTY(QJSValue roleDataProvider READ roleDataProvider WRITE setRoleDataProvider NOTIFY roleDataProviderChanged) + Q_PROPERTY(QQmlListProperty columns READ columns CONSTANT FINAL) + Q_INTERFACES(QQmlParserStatus) + Q_CLASSINFO("DefaultProperty", "columns") public: QQmlTableModel(QObject *parent = nullptr); @@ -82,8 +86,12 @@ public: Q_INVOKABLE void removeRow(int rowIndex, int rows = 1); Q_INVOKABLE void setRow(int rowIndex, const QVariant &row); - QJSValue roleDataProvider() const; - void setRoleDataProvider(QJSValue roleDataProvider); + QQmlListProperty columns(); + + static void columns_append(QQmlListProperty *property, QQmlTableModelColumn *value); + static int columns_count(QQmlListProperty *property); + static QQmlTableModelColumn *columns_at(QQmlListProperty *property, int index); + static void columns_clear(QQmlListProperty *property); QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; @@ -98,57 +106,61 @@ Q_SIGNALS: void columnCountChanged(); void rowCountChanged(); void rowsChanged(); - void roleDataProviderChanged(); private: - class ColumnPropertyInfo + class ColumnRoleMetadata { public: - ColumnPropertyInfo(); - ColumnPropertyInfo(const QString &name, QVariant::Type type, const QString &typeName); + ColumnRoleMetadata(); + ColumnRoleMetadata(bool isStringRole, const QString &name, QVariant::Type type, const QString &typeName); bool isValid() const; + // If this is false, it's a function role. + bool isStringRole = false; QString name; QVariant::Type type = QVariant::Invalid; QString typeName; }; - struct ColumnProperties + struct ColumnMetadata { - QVector infoForProperties; - // If there was a display role found in this column, it'll be stored here. - // The index is into infoForProperties. - int explicitDisplayRoleIndex = -1; + // Key = role name that will be made visible to the delegate + // Value = metadata about that role, including actual name in the model data, type, etc. + QHash roles; }; enum NewRowOperationFlag { OtherOperation, // insert(), set(), etc. + SetRowsOperation, AppendOperation }; + void doSetRows(const QVariantList &rowsAsVariantList); + ColumnRoleMetadata fetchColumnRoleData(const QString &roleNameKey, + QQmlTableModelColumn *tableModelColumn, int columnIndex) const; + void fetchColumnMetadata(); + bool validateRowType(const char *functionName, const QVariant &row) const; bool validateNewRow(const char *functionName, const QVariant &row, - int rowIndex, NewRowOperationFlag appendFlag = OtherOperation) const; + int rowIndex, NewRowOperationFlag operation = OtherOperation) const; bool validateRowIndex(const char *functionName, const char *argumentName, int rowIndex) const; - bool validateColumnPropertyTypes(const char *functionName, const QVariantMap &column, int columnIndex) const; - bool validateColumnPropertyType(const char *functionName, const QString &propertyName, - const QVariant &propertyValue, const ColumnPropertyInfo &expectedPropertyFormat, int columnIndex) const; - - ColumnPropertyInfo findColumnPropertyInfo(int columnIndex, const QString &columnPropertyNameFromRole) const; - QString columnPropertyNameFromRole(int columnIndex, int role) const; void doInsert(int rowIndex, const QVariant &row); + void classBegin() override; + void componentComplete() override; + + bool componentCompleted = false; QVariantList mRows; + QList mColumns; int mRowCount = 0; int mColumnCount = 0; // Each entry contains information about the properties of the column at that index. - QVector mColumnProperties; + QVector mColumnMetadata; // key = property index (0 to number of properties across all columns) // value = role name QHash mRoleNames; - QJSValue mRoleDataProvider; }; QT_END_NAMESPACE diff --git a/src/qml/types/qqmltablemodelcolumn.cpp b/src/qml/types/qqmltablemodelcolumn.cpp new file mode 100644 index 0000000000..93da0642de --- /dev/null +++ b/src/qml/types/qqmltablemodelcolumn.cpp @@ -0,0 +1,200 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmltablemodelcolumn_p.h" + +#include + +QT_BEGIN_NAMESPACE + +/*! + \qmltype TableModelColumn + \instantiates QQmlTableModelColumn + \inqmlmodule Qt.labs.qmlmodels + \brief Represents a column in a model. + \since 5.14 + + \section1 Supported Roles + + TableModelColumn supports all of \l {Qt::ItemDataRole}{Qt's roles}, + with the exception of \c Qt::InitialSortOrderRole. + + \sa TableModel, TableView +*/ + +static const QString displayRoleName = QStringLiteral("display"); +static const QString decorationRoleName = QStringLiteral("decoration"); +static const QString editRoleName = QStringLiteral("edit"); +static const QString toolTipRoleName = QStringLiteral("toolTip"); +static const QString statusTipRoleName = QStringLiteral("statusTip"); +static const QString whatsThisRoleName = QStringLiteral("whatsThis"); + +static const QString fontRoleName = QStringLiteral("font"); +static const QString textAlignmentRoleName = QStringLiteral("textAlignment"); +static const QString backgroundRoleName = QStringLiteral("background"); +static const QString foregroundRoleName = QStringLiteral("foreground"); +static const QString checkStateRoleName = QStringLiteral("checkState"); + +static const QString accessibleTextRoleName = QStringLiteral("accessibleText"); +static const QString accessibleDescriptionRoleName = QStringLiteral("accessibleDescription"); + +static const QString sizeHintRoleName = QStringLiteral("sizeHint"); + + +QQmlTableModelColumn::QQmlTableModelColumn(QObject *parent) + : QObject(parent) +{ +} + +QQmlTableModelColumn::~QQmlTableModelColumn() +{ +} + +#define DEFINE_ROLE_PROPERTIES(getterGetterName, getterSetterName, getterSignal, setterGetterName, setterSetterName, setterSignal, roleName) \ +QJSValue QQmlTableModelColumn::getterGetterName() const \ +{ \ + return mGetters.value(roleName); \ +} \ +\ +void QQmlTableModelColumn::getterSetterName(const QJSValue &stringOrFunction) \ +{ \ + if (!stringOrFunction.isString() && !stringOrFunction.isCallable()) { \ + qmlWarning(this).quote() << "getter for " << roleName << " must be a function"; \ + return; \ + } \ + if (stringOrFunction.strictlyEquals(decoration())) \ + return; \ +\ + mGetters[roleName] = stringOrFunction; \ + emit decorationChanged(); \ +} \ +\ +QJSValue QQmlTableModelColumn::setterGetterName() const \ +{ \ + return mSetters.value(roleName); \ +} \ +\ +void QQmlTableModelColumn::setterSetterName(const QJSValue &function) \ +{ \ + if (!function.isCallable()) { \ + qmlWarning(this).quote() << "setter for " << roleName << " must be a function"; \ + return; \ + } \ +\ + if (function.strictlyEquals(getSetDisplay())) \ + return; \ +\ + mSetters[roleName] = function; \ + emit setDisplayChanged(); \ +} + +DEFINE_ROLE_PROPERTIES(display, setDisplay, displayChanged, + getSetDisplay, setSetDisplay, setDisplayChanged, displayRoleName) +DEFINE_ROLE_PROPERTIES(decoration, setDecoration, decorationChanged, + getSetDecoration, setSetDecoration, setDecorationChanged, decorationRoleName) +DEFINE_ROLE_PROPERTIES(edit, setEdit, editChanged, + getSetEdit, setSetEdit, setEditChanged, editRoleName) +DEFINE_ROLE_PROPERTIES(toolTip, setToolTip, toolTipChanged, + getSetToolTip, setSetToolTip, setToolTipChanged, toolTipRoleName) +DEFINE_ROLE_PROPERTIES(statusTip, setStatusTip, statusTipChanged, + getSetStatusTip, setSetStatusTip, setStatusTipChanged, statusTipRoleName) +DEFINE_ROLE_PROPERTIES(whatsThis, setWhatsThis, whatsThisChanged, + getSetWhatsThis, setSetWhatsThis, setWhatsThisChanged, whatsThisRoleName) + +DEFINE_ROLE_PROPERTIES(font, setFont, fontChanged, + getSetFont, setSetFont, setFontChanged, fontRoleName) +DEFINE_ROLE_PROPERTIES(textAlignment, setTextAlignment, textAlignmentChanged, + getSetTextAlignment, setSetTextAlignment, setTextAlignmentChanged, textAlignmentRoleName) +DEFINE_ROLE_PROPERTIES(background, setBackground, backgroundChanged, + getSetBackground, setSetBackground, setBackgroundChanged, backgroundRoleName) +DEFINE_ROLE_PROPERTIES(foreground, setForeground, foregroundChanged, + getSetForeground, setSetForeground, setForegroundChanged, foregroundRoleName) +DEFINE_ROLE_PROPERTIES(checkState, setCheckState, checkStateChanged, + getSetCheckState, setSetCheckState, setCheckStateChanged, checkStateRoleName) + +DEFINE_ROLE_PROPERTIES(accessibleText, setAccessibleText, accessibleTextChanged, + getSetAccessibleText, setSetAccessibleText, setAccessibleTextChanged, accessibleTextRoleName) +DEFINE_ROLE_PROPERTIES(accessibleDescription, setAccessibleDescription, accessibleDescriptionChanged, + getSetAccessibleDescription, setSetAccessibleDescription, setAccessibleDescriptionChanged, accessibleDescriptionRoleName) + +DEFINE_ROLE_PROPERTIES(sizeHint, setSizeHint, sizeHintChanged, + getSetSizeHint, setSetSizeHint, setSizeHintChanged, sizeHintRoleName) + +QJSValue QQmlTableModelColumn::getterAtRole(const QString &roleName) +{ + auto it = mGetters.find(roleName); + if (it == mGetters.end()) + return QJSValue(); + return *it; +} + +QJSValue QQmlTableModelColumn::setterAtRole(const QString &roleName) +{ + auto it = mSetters.find(roleName); + if (it == mSetters.end()) + return QJSValue(); + return *it; +} + +const QHash QQmlTableModelColumn::getters() const +{ + return mGetters; +} + +const QHash QQmlTableModelColumn::supportedRoleNames() +{ + QHash names; + names[Qt::DisplayRole] = QLatin1String("display"); + names[Qt::DecorationRole] = QLatin1String("decoration"); + names[Qt::EditRole] = QLatin1String("edit"); + names[Qt::ToolTipRole] = QLatin1String("toolTip"); + names[Qt::StatusTipRole] = QLatin1String("statusTip"); + names[Qt::WhatsThisRole] = QLatin1String("whatsThis"); + names[Qt::FontRole] = QLatin1String("font"); + names[Qt::TextAlignmentRole] = QLatin1String("textAlignment"); + names[Qt::BackgroundRole] = QLatin1String("background"); + names[Qt::ForegroundRole] = QLatin1String("foreground"); + names[Qt::CheckStateRole] = QLatin1String("checkState"); + names[Qt::AccessibleTextRole] = QLatin1String("accessibleText"); + names[Qt::AccessibleDescriptionRole] = QLatin1String("accessibleDescription"); + names[Qt::SizeHintRole] = QLatin1String("sizeHint"); + return names; +} + +QT_END_NAMESPACE diff --git a/src/qml/types/qqmltablemodelcolumn_p.h b/src/qml/types/qqmltablemodelcolumn_p.h new file mode 100644 index 0000000000..41c02482c0 --- /dev/null +++ b/src/qml/types/qqmltablemodelcolumn_p.h @@ -0,0 +1,224 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLTABLEMODELCOLUMN_P_H +#define QQMLTABLEMODELCOLUMN_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class Q_QML_AUTOTEST_EXPORT QQmlTableModelColumn : public QObject +{ + Q_OBJECT + Q_PROPERTY(QJSValue display READ display WRITE setDisplay NOTIFY displayChanged FINAL) + Q_PROPERTY(QJSValue setDisplay READ getSetDisplay WRITE setSetDisplay NOTIFY setDisplayChanged) + Q_PROPERTY(QJSValue decoration READ decoration WRITE setDecoration NOTIFY decorationChanged FINAL) + Q_PROPERTY(QJSValue setDecoration READ getSetDecoration WRITE setSetDecoration NOTIFY setDecorationChanged FINAL) + Q_PROPERTY(QJSValue edit READ edit WRITE setEdit NOTIFY editChanged FINAL) + Q_PROPERTY(QJSValue setEdit READ getSetEdit WRITE setSetEdit NOTIFY setEditChanged FINAL) + Q_PROPERTY(QJSValue toolTip READ toolTip WRITE setToolTip NOTIFY toolTipChanged FINAL) + Q_PROPERTY(QJSValue setToolTip READ getSetToolTip WRITE setSetToolTip NOTIFY setToolTipChanged FINAL) + Q_PROPERTY(QJSValue statusTip READ statusTip WRITE setStatusTip NOTIFY statusTipChanged FINAL) + Q_PROPERTY(QJSValue setStatusTip READ getSetStatusTip WRITE setSetStatusTip NOTIFY setStatusTipChanged FINAL) + Q_PROPERTY(QJSValue whatsThis READ whatsThis WRITE setWhatsThis NOTIFY whatsThisChanged FINAL) + Q_PROPERTY(QJSValue setWhatsThis READ getSetWhatsThis WRITE setSetWhatsThis NOTIFY setWhatsThisChanged FINAL) + + Q_PROPERTY(QJSValue font READ font WRITE setFont NOTIFY fontChanged FINAL) + Q_PROPERTY(QJSValue setFont READ getSetFont WRITE setSetFont NOTIFY setFontChanged FINAL) + Q_PROPERTY(QJSValue textAlignment READ textAlignment WRITE setTextAlignment NOTIFY textAlignmentChanged FINAL) + Q_PROPERTY(QJSValue setTextAlignment READ getSetTextAlignment WRITE setSetTextAlignment NOTIFY setTextAlignmentChanged FINAL) + Q_PROPERTY(QJSValue background READ background WRITE setBackground NOTIFY backgroundChanged FINAL) + Q_PROPERTY(QJSValue setBackground READ getSetBackground WRITE setSetBackground NOTIFY setBackgroundChanged FINAL) + Q_PROPERTY(QJSValue foreground READ foreground WRITE setForeground NOTIFY foregroundChanged FINAL) + Q_PROPERTY(QJSValue setForeground READ getSetForeground WRITE setSetForeground NOTIFY setForegroundChanged FINAL) + Q_PROPERTY(QJSValue checkState READ checkState WRITE setCheckState NOTIFY checkStateChanged FINAL) + Q_PROPERTY(QJSValue setCheckState READ getSetCheckState WRITE setSetCheckState NOTIFY setCheckStateChanged FINAL) + + Q_PROPERTY(QJSValue accessibleText READ accessibleText WRITE setAccessibleText NOTIFY accessibleTextChanged FINAL) + Q_PROPERTY(QJSValue setAccessibleText READ getSetAccessibleText WRITE setSetAccessibleText NOTIFY setAccessibleTextChanged FINAL) + Q_PROPERTY(QJSValue accessibleDescription READ accessibleDescription + WRITE setAccessibleDescription NOTIFY accessibleDescriptionChanged FINAL) + Q_PROPERTY(QJSValue setAccessibleDescription READ getSetAccessibleDescription + WRITE setSetAccessibleDescription NOTIFY setAccessibleDescriptionChanged FINAL) + + Q_PROPERTY(QJSValue sizeHint READ sizeHint WRITE setSizeHint NOTIFY sizeHintChanged FINAL) + Q_PROPERTY(QJSValue setSizeHint READ getSetSizeHint WRITE setSetSizeHint NOTIFY setSizeHintChanged FINAL) + +public: + QQmlTableModelColumn(QObject *parent = nullptr); + ~QQmlTableModelColumn() override; + + QJSValue display() const; + void setDisplay(const QJSValue &stringOrFunction); + QJSValue getSetDisplay() const; + void setSetDisplay(const QJSValue &function); + + QJSValue decoration() const; + void setDecoration(const QJSValue &stringOrFunction); + QJSValue getSetDecoration() const; + void setSetDecoration(const QJSValue &function); + + QJSValue edit() const; + void setEdit(const QJSValue &stringOrFunction); + QJSValue getSetEdit() const; + void setSetEdit(const QJSValue &function); + + QJSValue toolTip() const; + void setToolTip(const QJSValue &stringOrFunction); + QJSValue getSetToolTip() const; + void setSetToolTip(const QJSValue &function); + + QJSValue statusTip() const; + void setStatusTip(const QJSValue &stringOrFunction); + QJSValue getSetStatusTip() const; + void setSetStatusTip(const QJSValue &function); + + QJSValue whatsThis() const; + void setWhatsThis(const QJSValue &stringOrFunction); + QJSValue getSetWhatsThis() const; + void setSetWhatsThis(const QJSValue &function); + + QJSValue font() const; + void setFont(const QJSValue &stringOrFunction); + QJSValue getSetFont() const; + void setSetFont(const QJSValue &function); + + QJSValue textAlignment() const; + void setTextAlignment(const QJSValue &stringOrFunction); + QJSValue getSetTextAlignment() const; + void setSetTextAlignment(const QJSValue &function); + + QJSValue background() const; + void setBackground(const QJSValue &stringOrFunction); + QJSValue getSetBackground() const; + void setSetBackground(const QJSValue &function); + + QJSValue foreground() const; + void setForeground(const QJSValue &stringOrFunction); + QJSValue getSetForeground() const; + void setSetForeground(const QJSValue &function); + + QJSValue checkState() const; + void setCheckState(const QJSValue &stringOrFunction); + QJSValue getSetCheckState() const; + void setSetCheckState(const QJSValue &function); + + QJSValue accessibleText() const; + void setAccessibleText(const QJSValue &stringOrFunction); + QJSValue getSetAccessibleText() const; + void setSetAccessibleText(const QJSValue &function); + + QJSValue accessibleDescription() const; + void setAccessibleDescription(const QJSValue &stringOrFunction); + QJSValue getSetAccessibleDescription() const; + void setSetAccessibleDescription(const QJSValue &function); + + QJSValue sizeHint() const; + void setSizeHint(const QJSValue &stringOrFunction); + QJSValue getSetSizeHint() const; + void setSetSizeHint(const QJSValue &function); + + QJSValue getterAtRole(const QString &roleName); + QJSValue setterAtRole(const QString &roleName); + + const QHash getters() const; + + static const QHash supportedRoleNames(); + +Q_SIGNALS: + void indexChanged(); + void displayChanged(); + void setDisplayChanged(); + void decorationChanged(); + void setDecorationChanged(); + void editChanged(); + void setEditChanged(); + void toolTipChanged(); + void setToolTipChanged(); + void statusTipChanged(); + void setStatusTipChanged(); + void whatsThisChanged(); + void setWhatsThisChanged(); + + void fontChanged(); + void setFontChanged(); + void textAlignmentChanged(); + void setTextAlignmentChanged(); + void backgroundChanged(); + void setBackgroundChanged(); + void foregroundChanged(); + void setForegroundChanged(); + void checkStateChanged(); + void setCheckStateChanged(); + + void accessibleTextChanged(); + void setAccessibleTextChanged(); + void accessibleDescriptionChanged(); + void setAccessibleDescriptionChanged(); + void sizeHintChanged(); + void setSizeHintChanged(); + +private: + int mIndex = -1; + + // We store these in hashes because QQuickTableModel needs string-based lookup in certain situations. + QHash mGetters; + QHash mSetters; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQmlTableModelColumn) + +#endif // QQMLTABLEMODELCOLUMN_P_H diff --git a/src/qml/types/types.pri b/src/qml/types/types.pri index 1765beb09e..5a56208dc4 100644 --- a/src/qml/types/types.pri +++ b/src/qml/types/types.pri @@ -7,7 +7,8 @@ SOURCES += \ $$PWD/qquickpackage.cpp \ $$PWD/qqmlinstantiator.cpp \ $$PWD/qqmltableinstancemodel.cpp \ - $$PWD/qqmltablemodel.cpp + $$PWD/qqmltablemodel.cpp \ + $$PWD/qqmltablemodelcolumn.cpp HEADERS += \ $$PWD/qqmlbind_p.h \ @@ -19,7 +20,8 @@ HEADERS += \ $$PWD/qqmlinstantiator_p.h \ $$PWD/qqmlinstantiator_p_p.h \ $$PWD/qqmltableinstancemodel_p.h \ - $$PWD/qqmltablemodel_p.h + $$PWD/qqmltablemodel_p.h \ + $$PWD/qqmltablemodelcolumn_p.h qtConfig(qml-worker-script) { SOURCES += \ -- cgit v1.2.3 From 2895402cf60f0ac9f3dee87f9823120a4b8463ea Mon Sep 17 00:00:00 2001 From: hjk Date: Fri, 29 Mar 2019 16:02:09 +0100 Subject: Add another helper function for stack extraction This makes the use of the feature easier and lowers the dependency on setup of internal structures. Also, evaluation of expressions is notoriously expensive and unreliable on the gdb side, so moving the complexity to the compiled side is an advantage. Task-number: QTCREATORBUG-22209 Change-Id: Id43d5c2bf4d852d496ceb59189209d167213afcb Reviewed-by: Friedemann Kleint Reviewed-by: Ulf Hermann --- src/qml/jsruntime/qv4engine.cpp | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index fdc2118840..ee617c2d58 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -1082,6 +1082,12 @@ extern "C" Q_QML_EXPORT char *qt_v4StackTrace(void *executionContext) return v4StackTrace(reinterpret_cast(executionContext)); } +extern "C" Q_QML_EXPORT char *qt_v4StackTraceForEngine(void *executionEngine) +{ + auto engine = (reinterpret_cast(executionEngine)); + return v4StackTrace(engine->currentContext()); +} + QUrl ExecutionEngine::resolvedUrl(const QString &file) { QUrl src(file); -- cgit v1.2.3 From 34c98070d158386673d607eb860f682635849d4a Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Fri, 5 Apr 2019 09:42:17 +0200 Subject: Fix deprecation warnings about QVariant API Fix warnings like: sruntime/qv4serialize.cpp:378:45: warning: 'QVariant qVariantFromValue(const T&) [with T = QQmlListModelWorkerAgent::VariantRef]' is deprecated: Use QVariant::fromValue() instead. [-Wdeprecated-declarations] qml/qqmlvmemetaobject.cpp:597:61: warning: 'QVariant qVariantFromValue(const T&) [with T = QList]' is deprecated: Use QVariant::fromValue() instead. [-Wdeprecated-declarations] jsruntime/qv4engine.cpp:1319:66: warning: 'QVariant qVariantFromValue(const T&) [with T = QObject*]' is deprecated: Use QVariant::fromValue() instead. [-Wdeprecated-declarations] jsruntime/qv4engine.cpp:1350:60: warning: 'QVariant qVariantFromValue(const T&) [with T = QList]' is deprecated: Use QVariant::fromValue() instead. [-Wdeprecated-declarations] items/qquickitem.cpp:8396:78: warning: 'QVariant qVariantFromValue(const T&) [with T = QObject*]' is deprecated: Use QVariant::fromValue() instead. [-Wdeprecated-declarations] items/qquickitem.cpp:8693:80: warning: 'QVariant qVariantFromValue(const T&) [with T = QObject*]' is deprecated: Use QVariant::fromValue() instead. [-Wdeprecated-declarations] items/qquickgenericshadereffect.cpp:126:69: warning: 'QVariant qVariantFromValue(const T&) [with T = QObject*]' is deprecated: Use QVariant::fromValue() instead. [-Wdeprecated-declarations] items/qquickgenericshadereffect.cpp:127:55: warning: 'QVariant qVariantFromValue(const T&) [with T = QSize]' is deprecated: Use QVariant::fromValue() instead. [-Wdeprecated-declarations] items/qquickopenglshadereffect.cpp:713:69: warning: 'QVariant qVariantFromValue(const T&) [with T = QObject*]' is deprecated: Use QVariant::fromValue() instead. [-Wdeprecated-declarations] items/qquickopenglshadereffect.cpp:714:55: warning: 'QVariant qVariantFromValue(const T&) [with T = QSize]' is deprecated: Use QVariant::fromValue() instead. [-Wdeprecated-declarations] qquickcustomparticle.cpp:416:89: warning: 'QVariant qVariantFromValue(const T&) [with T = double]' is deprecated: Use QVariant::fromValue() instead. [-Wdeprecated-declarations] qqmlenginedebugclient.cpp:403:47: warning: 'QVariant qVariantFromValue(const T&) [with T = QQmlEngineDebugObjectReference]' is deprecated: Use QVariant::fromValue() instead. [-Wdeprecated-declarations] Task-number: QTBUG-74043 Change-Id: I14cb7d7c1fb8dc6321e32208a7de15f6bdb19065 Reviewed-by: Ulf Hermann --- src/qml/jsruntime/qv4engine.cpp | 4 ++-- src/qml/jsruntime/qv4serialize.cpp | 2 +- src/qml/qml/qqmlvmemetaobject.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index ee617c2d58..ab980e99df 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -1322,7 +1322,7 @@ static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, int && !value.as() && !value.as()) { return QVariant::fromValue(QV4::JsonObject::toJsonObject(object)); } else if (QV4::QObjectWrapper *wrapper = object->as()) { - return qVariantFromValue(wrapper->object()); + return QVariant::fromValue(wrapper->object()); } else if (object->as()) { return QVariant(); } else if (QV4::QQmlTypeWrapper *w = object->as()) { @@ -1353,7 +1353,7 @@ static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, int } } - return qVariantFromValue >(list); + return QVariant::fromValue >(list); } else if (typeHint == QMetaType::QJsonArray) { return QVariant::fromValue(QV4::JsonObject::toJsonArray(a)); } diff --git a/src/qml/jsruntime/qv4serialize.cpp b/src/qml/jsruntime/qv4serialize.cpp index 50871a4d87..df54a18378 100644 --- a/src/qml/jsruntime/qv4serialize.cpp +++ b/src/qml/jsruntime/qv4serialize.cpp @@ -375,7 +375,7 @@ ReturnedValue Serialize::deserialize(const char *&data, ExecutionEngine *engine) QV4::ScopedValue rv(scope, QV4::QObjectWrapper::wrap(engine, agent)); // ### Find a better solution then the ugly property QQmlListModelWorkerAgent::VariantRef ref(agent); - QVariant var = qVariantFromValue(ref); + QVariant var = QVariant::fromValue(ref); QV4::ScopedValue v(scope, scope.engine->fromVariant(var)); QV4::ScopedString s(scope, engine->newString(QStringLiteral("__qml:hidden:ref"))); rv->as()->defineReadonlyProperty(s, v); diff --git a/src/qml/qml/qqmlvmemetaobject.cpp b/src/qml/qml/qqmlvmemetaobject.cpp index 6bc469c836..5d13415513 100644 --- a/src/qml/qml/qqmlvmemetaobject.cpp +++ b/src/qml/qml/qqmlvmemetaobject.cpp @@ -594,7 +594,7 @@ QList *QQmlVMEMetaObject::readPropertyAsList(int id) const QV4::Scope scope(engine); QV4::Scoped v(scope, *(md->data() + id)); if (!v || (int)v->d()->data().userType() != qMetaTypeId >()) { - QVariant variant(qVariantFromValue(QList())); + QVariant variant(QVariant::fromValue(QList())); v = engine->newVariantObject(variant); md->set(engine, id, v); } -- cgit v1.2.3 From b150debb687fda0387716b8b4eca08033da6b654 Mon Sep 17 00:00:00 2001 From: Kai Koehne Date: Fri, 5 Apr 2019 12:20:55 +0200 Subject: Fix clang-cl compiler warnings clang-cl.exe 8.0.0 warns: qv4internalclass_p.h(460,19): warning: unqualified friend declaration referring to type outside of the nearest enclosing namespace is a Microsoft extension; add a nested name specifier [-Wmicrosoft-unqualified-friend] The warning is most likely bogus (otherwise the code wouldn't compile on other platforms. But it's arguably not a bad idea to just qualify the friend declaration. Change-Id: Ia34119661c29cd8619adec70e9999cab2605ff32 Reviewed-by: Ulf Hermann --- src/qml/jsruntime/qv4internalclass_p.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4internalclass_p.h b/src/qml/jsruntime/qv4internalclass_p.h index 42b61218a5..7bb10f47a3 100644 --- a/src/qml/jsruntime/qv4internalclass_p.h +++ b/src/qml/jsruntime/qv4internalclass_p.h @@ -457,7 +457,7 @@ private: InternalClass *addMemberImpl(PropertyKey identifier, PropertyAttributes data, InternalClassEntry *entry); void removeChildEntry(InternalClass *child); - friend struct ExecutionEngine; + friend struct ::QV4::ExecutionEngine; }; inline -- cgit v1.2.3 From 0a9491d6ca95e2fdeb6c1eaef35beaa095eace5a Mon Sep 17 00:00:00 2001 From: Rainer Keller Date: Thu, 4 Apr 2019 11:28:13 +0200 Subject: Remove second evaluation of property name The name was evaluated already at the beginning of the function. Change-Id: I0bf6149c9bce83a543b1946330ec12f318620a71 Reviewed-by: Ulf Hermann --- src/qml/jsruntime/qv4runtime.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4runtime.cpp b/src/qml/jsruntime/qv4runtime.cpp index bde7cc6798..3e9e972d77 100644 --- a/src/qml/jsruntime/qv4runtime.cpp +++ b/src/qml/jsruntime/qv4runtime.cpp @@ -1453,7 +1453,7 @@ ReturnedValue Runtime::method_callProperty(ExecutionEngine *engine, Value *base, if (!f) { QString error = QStringLiteral("Property '%1' of object %2 is not a function") - .arg(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]->toQString(), + .arg(name->toQString(), base->toQStringNoThrow()); return engine->throwTypeError(error); } -- cgit v1.2.3 From 78787011651692b64bef19f6bec03d0dbf390e18 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Mon, 8 Apr 2019 14:45:23 +0200 Subject: Avoid INT_MIN % -1 and INT_MIN / -1 Those throw arithmetic exceptions as the result doesn't fit into an integer. Fixes: QTBUG-75030 Change-Id: Ibd978848f42cf1c9da1e4af2dc9d7da123ef8f5a Reviewed-by: Simon Hausmann --- src/qml/jsruntime/qv4runtime.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4runtime.cpp b/src/qml/jsruntime/qv4runtime.cpp index 85d06bcabe..6a242ba5f6 100644 --- a/src/qml/jsruntime/qv4runtime.cpp +++ b/src/qml/jsruntime/qv4runtime.cpp @@ -1942,6 +1942,7 @@ ReturnedValue Runtime::method_div(const Value &left, const Value &right) int lval = left.integerValue(); int rval = right.integerValue(); if (rval != 0 // division by zero should result in a NaN + && !(lval == std::numeric_limits::min() && rval == -1) // doesn't fit in int && (lval % rval == 0) // fractions can't be stored in an int && !(lval == 0 && rval < 0)) // 0 / -something results in -0.0 return Encode(int(lval / rval)); -- cgit v1.2.3 From 4b6832828e04a9806aaf345da5f456817dd3970b Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Mon, 8 Apr 2019 15:39:45 +0200 Subject: Don't use delegate model types on -no-feature-delegate-model Fixes: QTBUG-74884 Change-Id: I7a675f6ef41937cef0f8e67960486c5b022d735c Reviewed-by: Simon Hausmann --- src/qml/types/qqmlmodelsmodule.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/qml') diff --git a/src/qml/types/qqmlmodelsmodule.cpp b/src/qml/types/qqmlmodelsmodule.cpp index 30915d96fd..bb72771cd9 100644 --- a/src/qml/types/qqmlmodelsmodule.cpp +++ b/src/qml/types/qqmlmodelsmodule.cpp @@ -72,9 +72,11 @@ void QQmlModelsModule::defineLabsModule() { const char uri[] = "Qt.labs.qmlmodels"; +#if QT_CONFIG(qml_delegate_model) qmlRegisterUncreatableType(uri, 1, 0, "AbstractDelegateComponent", QQmlAbstractDelegateComponent::tr("Cannot create instance of abstract class AbstractDelegateComponent.")); qmlRegisterType(uri, 1, 0, "DelegateChooser"); qmlRegisterType(uri, 1, 0, "DelegateChoice"); +#endif } QT_END_NAMESPACE -- cgit v1.2.3 From bc00353cffbfe0f74b602a16452f2e7bcd588152 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Mon, 8 Apr 2019 16:57:30 +0200 Subject: Detect and reject cyclic aliases Previously those would result in infinite recursion. Fixes: QTBUG-74867 Change-Id: I6c0043b43e72fe7bc3a2a139ca600af2d5bca5ad Reviewed-by: Simon Hausmann --- src/qml/compiler/qqmlpropertycachecreator_p.h | 40 ++++++++++++++++++++------- 1 file changed, 30 insertions(+), 10 deletions(-) (limited to 'src/qml') diff --git a/src/qml/compiler/qqmlpropertycachecreator_p.h b/src/qml/compiler/qqmlpropertycachecreator_p.h index 6bee599c0a..074dc98648 100644 --- a/src/qml/compiler/qqmlpropertycachecreator_p.h +++ b/src/qml/compiler/qqmlpropertycachecreator_p.h @@ -692,11 +692,6 @@ inline QQmlCompileError QQmlPropertyCacheAliasCreator::property const CompiledObject &component, const QV4::CompiledData::Alias &alias, int *type, int *minorVersion, QQmlPropertyData::Flags *propertyFlags) { - const int targetObjectIndex = objectForId(component, alias.targetObjectId); - Q_ASSERT(targetObjectIndex >= 0); - - const CompiledObject &targetObject = *objectContainer->objectAt(targetObjectIndex); - *type = 0; bool writable = false; bool resettable = false; @@ -704,11 +699,36 @@ inline QQmlCompileError QQmlPropertyCacheAliasCreator::property propertyFlags->isAlias = true; if (alias.aliasToLocalAlias) { - auto targetAlias = targetObject.aliasesBegin(); - for (uint i = 0; i < alias.localAliasIndex; ++i) - ++targetAlias; - return propertyDataForAlias(component, *targetAlias, type, minorVersion, propertyFlags); - } else if (alias.encodedMetaPropertyIndex == -1) { + const QV4::CompiledData::Alias *lastAlias = &alias; + QVarLengthArray seenAliases({lastAlias}); + + do { + const CompiledObject *targetObject = objectContainer->objectAt( + objectForId(component, lastAlias->targetObjectId)); + Q_ASSERT(targetObject); + + auto nextAlias = targetObject->aliasesBegin(); + for (uint i = 0; i < lastAlias->localAliasIndex; ++i) + ++nextAlias; + + const QV4::CompiledData::Alias *targetAlias = &(*nextAlias); + if (seenAliases.contains(targetAlias)) { + return QQmlCompileError(targetAlias->location, + QQmlPropertyCacheCreatorBase::tr("Cyclic alias")); + } + + seenAliases.append(targetAlias); + lastAlias = targetAlias; + } while (lastAlias->aliasToLocalAlias); + + return propertyDataForAlias(component, *lastAlias, type, minorVersion, propertyFlags); + } + + const int targetObjectIndex = objectForId(component, alias.targetObjectId); + Q_ASSERT(targetObjectIndex >= 0); + const CompiledObject &targetObject = *objectContainer->objectAt(targetObjectIndex); + + if (alias.encodedMetaPropertyIndex == -1) { Q_ASSERT(alias.flags & QV4::CompiledData::Alias::AliasPointsToPointerObject); auto *typeRef = objectContainer->resolvedType(targetObject.inheritedTypeNameIndex); if (!typeRef) { -- cgit v1.2.3 From 96c4fffd8648e9c9fb95e8208a76933c8c2120bc Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Fri, 5 Apr 2019 09:58:31 +0200 Subject: Remove unused includes of qv8engine_p.h Change-Id: Ic135a863581d29a3afb9c6c7f070d2630b3913b4 Reviewed-by: Lars Knoll --- src/qml/jsapi/qjsvalue.cpp | 1 - src/qml/jsruntime/qv4qmlcontext.cpp | 1 - src/qml/jsruntime/qv4runtime.cpp | 1 - src/qml/jsruntime/qv4serialize.cpp | 1 - src/qml/jsruntime/qv4variantobject.cpp | 1 - src/qml/qml/qqmldelayedcallqueue.cpp | 1 - src/qml/qml/qqmlexpression.cpp | 1 - src/qml/qml/qqmllistwrapper.cpp | 1 - src/qml/qml/qqmlopenmetaobject.cpp | 1 - src/qml/qml/qqmltypewrapper.cpp | 1 - src/qml/qml/qqmlvaluetypewrapper.cpp | 2 +- src/qml/qml/qqmlvmemetaobject_p.h | 1 - 12 files changed, 1 insertion(+), 12 deletions(-) (limited to 'src/qml') diff --git a/src/qml/jsapi/qjsvalue.cpp b/src/qml/jsapi/qjsvalue.cpp index e20317cff1..1031d686e7 100644 --- a/src/qml/jsapi/qjsvalue.cpp +++ b/src/qml/jsapi/qjsvalue.cpp @@ -51,7 +51,6 @@ #include "qv4variantobject_p.h" #include "qv4regexpobject_p.h" #include "qv4errorobject_p.h" -#include "private/qv8engine_p.h" #include #include #include diff --git a/src/qml/jsruntime/qv4qmlcontext.cpp b/src/qml/jsruntime/qv4qmlcontext.cpp index 12ada7ee70..d2aa334805 100644 --- a/src/qml/jsruntime/qv4qmlcontext.cpp +++ b/src/qml/jsruntime/qv4qmlcontext.cpp @@ -38,7 +38,6 @@ ****************************************************************************/ #include "qv4qmlcontext_p.h" -#include #include #include diff --git a/src/qml/jsruntime/qv4runtime.cpp b/src/qml/jsruntime/qv4runtime.cpp index de46d046c9..359ea0fcfa 100644 --- a/src/qml/jsruntime/qv4runtime.cpp +++ b/src/qml/jsruntime/qv4runtime.cpp @@ -63,7 +63,6 @@ #include "qv4qobjectwrapper_p.h" #include "qv4symbol_p.h" #include "qv4generatorobject_p.h" -#include #endif #include diff --git a/src/qml/jsruntime/qv4serialize.cpp b/src/qml/jsruntime/qv4serialize.cpp index df54a18378..a84521e205 100644 --- a/src/qml/jsruntime/qv4serialize.cpp +++ b/src/qml/jsruntime/qv4serialize.cpp @@ -39,7 +39,6 @@ #include "qv4serialize_p.h" -#include #if QT_CONFIG(qml_list_model) #include #include diff --git a/src/qml/jsruntime/qv4variantobject.cpp b/src/qml/jsruntime/qv4variantobject.cpp index e4d8bcaafc..e117e509ab 100644 --- a/src/qml/jsruntime/qv4variantobject.cpp +++ b/src/qml/jsruntime/qv4variantobject.cpp @@ -41,7 +41,6 @@ #include "qv4functionobject_p.h" #include "qv4objectproto_p.h" #include -#include #include QT_BEGIN_NAMESPACE diff --git a/src/qml/qml/qqmldelayedcallqueue.cpp b/src/qml/qml/qqmldelayedcallqueue.cpp index 61cb0a9065..857b5be8b8 100644 --- a/src/qml/qml/qqmldelayedcallqueue.cpp +++ b/src/qml/qml/qqmldelayedcallqueue.cpp @@ -38,7 +38,6 @@ ****************************************************************************/ #include "qqmldelayedcallqueue_p.h" -#include #include #include #include diff --git a/src/qml/qml/qqmlexpression.cpp b/src/qml/qml/qqmlexpression.cpp index ac2629979f..6a11583f18 100644 --- a/src/qml/qml/qqmlexpression.cpp +++ b/src/qml/qml/qqmlexpression.cpp @@ -45,7 +45,6 @@ #include "qqmlcontext_p.h" #include "qqmlscriptstring_p.h" #include "qqmlbinding_p.h" -#include #include #include diff --git a/src/qml/qml/qqmllistwrapper.cpp b/src/qml/qml/qqmllistwrapper.cpp index 2f769c1aef..5349572921 100644 --- a/src/qml/qml/qqmllistwrapper.cpp +++ b/src/qml/qml/qqmllistwrapper.cpp @@ -38,7 +38,6 @@ ****************************************************************************/ #include "qqmllistwrapper_p.h" -#include #include #include #include diff --git a/src/qml/qml/qqmlopenmetaobject.cpp b/src/qml/qml/qqmlopenmetaobject.cpp index fc798a2c23..fe0946c6de 100644 --- a/src/qml/qml/qqmlopenmetaobject.cpp +++ b/src/qml/qml/qqmlopenmetaobject.cpp @@ -41,7 +41,6 @@ #include #include #include -#include #include #include diff --git a/src/qml/qml/qqmltypewrapper.cpp b/src/qml/qml/qqmltypewrapper.cpp index 6b977df453..86513b5ff8 100644 --- a/src/qml/qml/qqmltypewrapper.cpp +++ b/src/qml/qml/qqmltypewrapper.cpp @@ -38,7 +38,6 @@ ****************************************************************************/ #include "qqmltypewrapper_p.h" -#include #include #include diff --git a/src/qml/qml/qqmlvaluetypewrapper.cpp b/src/qml/qml/qqmlvaluetypewrapper.cpp index 7df5757b95..cf6553d129 100644 --- a/src/qml/qml/qqmlvaluetypewrapper.cpp +++ b/src/qml/qml/qqmlvaluetypewrapper.cpp @@ -38,7 +38,7 @@ ****************************************************************************/ #include "qqmlvaluetypewrapper_p.h" -#include + #include #include #include diff --git a/src/qml/qml/qqmlvmemetaobject_p.h b/src/qml/qml/qqmlvmemetaobject_p.h index 87dc9fb0b2..2371d70f10 100644 --- a/src/qml/qml/qqmlvmemetaobject_p.h +++ b/src/qml/qml/qqmlvmemetaobject_p.h @@ -66,7 +66,6 @@ #include "qqmlguard_p.h" #include "qqmlcontext_p.h" -#include #include #include -- cgit v1.2.3 From 28dfb8207f6a8a38fc587143f7f19325c3395059 Mon Sep 17 00:00:00 2001 From: Nodir Temirkhodjaev Date: Tue, 9 Apr 2019 21:26:53 +0500 Subject: Warn about non-existent providers only with the loaded QtQml library Task-number: QTBUG-74996 Change-Id: Ibc5de6e79889724850c9cdc16c8ba066a76298d1 Reviewed-by: Ulf Hermann --- src/qml/qml/qqmlglobal.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/qml') diff --git a/src/qml/qml/qqmlglobal.cpp b/src/qml/qml/qqmlglobal.cpp index a10ded6487..04e6b2aa75 100644 --- a/src/qml/qml/qqmlglobal.cpp +++ b/src/qml/qml/qqmlglobal.cpp @@ -262,9 +262,9 @@ Q_QML_PRIVATE_EXPORT void QQml_removeValueTypeProvider(QQmlValueTypeProvider *ol prev->next = oldProvider->next; return; } - } - qWarning("QQml_removeValueTypeProvider: was asked to remove provider %p but it was not found", oldProvider); + qWarning("QQml_removeValueTypeProvider: was asked to remove provider %p but it was not found", oldProvider); + } } Q_AUTOTEST_EXPORT QQmlValueTypeProvider *QQml_valueTypeProvider() -- cgit v1.2.3 From 2ccd4c3672e5db2c25ca0f8df439fe8f646aa942 Mon Sep 17 00:00:00 2001 From: Richard Weickelt Date: Wed, 10 Apr 2019 05:43:53 +0200 Subject: Fix false unused parameter warnings When building Qt on Ubuntu 16.04 with gcc 5.4, build fails due to a warning about unused parameters. Warnings are treated as errors by default. This is most likely a bug in g++ and has probably been fixed in later versions. For instance, no warning is emitted for the synthesized operator= few lines further down. Above setup will be officially supported by Qt 5.14 and therefore, I suggest to work around this issue by omitting the explicit parameter names. Change-Id: Ifb22e39b7f5fa5101b188eec715d8620d4589941 Reviewed-by: Ulf Hermann --- src/qml/qml/qqmltype.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/qml') diff --git a/src/qml/qml/qqmltype.cpp b/src/qml/qml/qqmltype.cpp index df8078fb58..5caeb8c1b0 100644 --- a/src/qml/qml/qqmltype.cpp +++ b/src/qml/qml/qqmltype.cpp @@ -177,8 +177,8 @@ QQmlTypePrivate::~QQmlTypePrivate() } QQmlType::QQmlType() = default; -QQmlType::QQmlType(const QQmlType &other) = default; -QQmlType::QQmlType(QQmlType &&other) = default; +QQmlType::QQmlType(const QQmlType &) = default; +QQmlType::QQmlType(QQmlType &&) = default; QQmlType &QQmlType::operator =(const QQmlType &other) = default; QQmlType &QQmlType::operator =(QQmlType &&other) = default; QQmlType::QQmlType(const QQmlTypePrivate *priv) : d(priv) {} -- cgit v1.2.3 From 65299ce292180a845eb2d76756faeeaa1456d8f0 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Thu, 4 Apr 2019 16:00:36 +0200 Subject: Remove QQmlV4Handle This is just an alias for QV4::ReturnedValue. We can as well use the latter. Change-Id: Ibd2c038a3ca726b39a8f0f05e02922adb9fccbdb Reviewed-by: Simon Hausmann --- src/qml/jsruntime/qv4qobjectwrapper.cpp | 36 +++++++++--------------------- src/qml/qml/qqmlboundsignal.cpp | 2 -- src/qml/qml/qqmlcomponent.cpp | 2 -- src/qml/qml/qqmlengine.cpp | 1 - src/qml/qml/qqmlglobal.cpp | 5 +++-- src/qml/qml/qqmlglobal_p.h | 4 ++-- src/qml/qml/qqmlpropertycache.cpp | 3 --- src/qml/qml/qqmlpropertyrawdata_p.h | 4 +--- src/qml/qml/qqmlxmlhttprequest.cpp | 2 -- src/qml/qml/v8/qqmlbuiltinfunctions.cpp | 4 ++-- src/qml/qml/v8/qv8engine_p.h | 15 ------------- src/qml/types/qqmldelegatemodel.cpp | 20 ++++++++--------- src/qml/types/qqmldelegatemodel_p.h | 5 ++--- src/qml/types/qqmllistmodel.cpp | 8 +++---- src/qml/types/qqmllistmodel_p.h | 4 ++-- src/qml/types/qqmllistmodelworkeragent.cpp | 4 ++-- src/qml/types/qqmllistmodelworkeragent_p.h | 4 ++-- src/qml/types/qquickworkerscript.cpp | 8 +++---- src/qml/types/qquickworkerscript_p.h | 3 +-- src/qml/util/qqmladaptormodel.cpp | 1 - 20 files changed, 43 insertions(+), 92 deletions(-) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index d85e8c94eb..ba9029bd4d 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -50,7 +50,6 @@ #include #include #include -#include #include #include @@ -165,10 +164,6 @@ static QV4::ReturnedValue loadProperty(QV4::ExecutionEngine *v4, QObject *object double v = 0; property.readProperty(object, &v); return QV4::Encode(v); - } else if (property.isV4Handle()) { - QQmlV4Handle handle; - property.readProperty(object, &handle); - return handle; } else if (property.propType() == qMetaTypeId()) { QJSValue v; property.readProperty(object, &v); @@ -1194,15 +1189,14 @@ DEFINE_OBJECT_VTABLE(QObjectWrapper); namespace { -template -class MaxSizeOf8 { +template +class MaxSizeOf7 { template struct SMax { char dummy[sizeof(Z) > sizeof(X) ? sizeof(Z) : sizeof(X)]; }; public: - static const size_t Size = sizeof(SMax > > > > > >); + static const size_t Size = sizeof(SMax > > > > >); }; struct CallArgument { @@ -1235,14 +1229,13 @@ private: std::vector *stdVectorQUrlPtr; std::vector *stdVectorQModelIndexPtr; - char allocData[MaxSizeOf8, - QJSValue, - QQmlV4Handle, - QJsonArray, - QJsonObject, - QJsonValue>::Size]; + char allocData[MaxSizeOf7, + QJSValue, + QJsonArray, + QJsonObject, + QJsonValue>::Size]; qint64 q_for_alignment; }; @@ -1253,7 +1246,6 @@ private: QVariant *qvariantPtr; QList *qlistPtr; QJSValue *qjsValuePtr; - QQmlV4Handle *handlePtr; QJsonArray *jsonArrayPtr; QJsonObject *jsonObjectPtr; QJsonValue *jsonValuePtr; @@ -1725,9 +1717,6 @@ void CallArgument::initAsType(int callType) } else if (callType == qMetaTypeId >()) { type = callType; qlistPtr = new (&allocData) QList(); - } else if (callType == qMetaTypeId()) { - type = callType; - handlePtr = new (&allocData) QQmlV4Handle; } else if (callType == QMetaType::QJsonArray) { type = callType; jsonArrayPtr = new (&allocData) QJsonArray(); @@ -1828,9 +1817,6 @@ bool CallArgument::fromValue(int callType, QV4::ExecutionEngine *engine, const Q return false; } } - } else if (callType == qMetaTypeId()) { - handlePtr = new (&allocData) QQmlV4Handle(value.asReturnedValue()); - type = callType; } else if (callType == QMetaType::QJsonArray) { QV4::ScopedArrayObject a(scope, value); jsonArrayPtr = new (&allocData) QJsonArray(QV4::JsonObject::toJsonArray(a)); @@ -1955,8 +1941,6 @@ QV4::ReturnedValue CallArgument::toValue(QV4::ExecutionEngine *engine) array->arrayPut(ii, (v = QV4::QObjectWrapper::wrap(scope.engine, list.at(ii)))); array->setArrayLengthUnchecked(list.count()); return array.asReturnedValue(); - } else if (type == qMetaTypeId()) { - return *handlePtr; } else if (type == QMetaType::QJsonArray) { return QV4::JsonObject::fromJsonArray(scope.engine, *jsonArrayPtr); } else if (type == QMetaType::QJsonObject) { diff --git a/src/qml/qml/qqmlboundsignal.cpp b/src/qml/qml/qqmlboundsignal.cpp index cf6f831818..dc973630a7 100644 --- a/src/qml/qml/qqmlboundsignal.cpp +++ b/src/qml/qml/qqmlboundsignal.cpp @@ -209,8 +209,6 @@ void QQmlBoundSignalExpression::evaluate(void **a) } else if (type == QMetaType::Int) { //### optimization. Can go away if we switch to metaTypeToJS, or be expanded otherwise jsCall->args[ii] = QV4::Value::fromInt32(*reinterpret_cast(a[ii + 1])); - } else if (type == qMetaTypeId()) { - jsCall->args[ii] = *reinterpret_cast(a[ii + 1]); } else if (ep->isQObject(type)) { if (!*reinterpret_cast(a[ii + 1])) jsCall->args[ii] = QV4::Value::nullValue(); diff --git a/src/qml/qml/qqmlcomponent.cpp b/src/qml/qml/qqmlcomponent.cpp index 57ea685a5d..64d2a064df 100644 --- a/src/qml/qml/qqmlcomponent.cpp +++ b/src/qml/qml/qqmlcomponent.cpp @@ -51,8 +51,6 @@ #include "qqmlincubator_p.h" #include -#include - #include #include #include diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp index 0a26ed89cc..20440d7f49 100644 --- a/src/qml/qml/qqmlengine.cpp +++ b/src/qml/qml/qqmlengine.cpp @@ -994,7 +994,6 @@ void QQmlEnginePrivate::init() qRegisterMetaType(); qRegisterMetaType >(); qRegisterMetaType >(); - qRegisterMetaType(); qRegisterMetaType(); v8engine()->setEngine(q); diff --git a/src/qml/qml/qqmlglobal.cpp b/src/qml/qml/qqmlglobal.cpp index 04e6b2aa75..acebb6bac3 100644 --- a/src/qml/qml/qqmlglobal.cpp +++ b/src/qml/qml/qqmlglobal.cpp @@ -149,7 +149,8 @@ QVariant QQmlValueTypeProvider::createVariantFromString(int type, const QString return QVariant(); } -QVariant QQmlValueTypeProvider::createVariantFromJsObject(int type, QQmlV4Handle obj, QV4::ExecutionEngine *e, bool *ok) +QVariant QQmlValueTypeProvider::createVariantFromJsObject(int type, const QV4::Value &obj, + QV4::ExecutionEngine *e, bool *ok) { QVariant v; @@ -225,7 +226,7 @@ bool QQmlValueTypeProvider::createFromString(int, const QString &, void *, size_ bool QQmlValueTypeProvider::createStringFrom(int, const void *, QString *) { return false; } bool QQmlValueTypeProvider::variantFromString(const QString &, QVariant *) { return false; } bool QQmlValueTypeProvider::variantFromString(int, const QString &, QVariant *) { return false; } -bool QQmlValueTypeProvider::variantFromJsObject(int, QQmlV4Handle, QV4::ExecutionEngine *, QVariant *) { return false; } +bool QQmlValueTypeProvider::variantFromJsObject(int, const QV4::Value &, QV4::ExecutionEngine *, QVariant *) { return false; } bool QQmlValueTypeProvider::equal(int, const void *, const QVariant&) { return false; } bool QQmlValueTypeProvider::store(int, const void *, void *, size_t) { return false; } bool QQmlValueTypeProvider::read(const QVariant&, void *, int) { return false; } diff --git a/src/qml/qml/qqmlglobal_p.h b/src/qml/qml/qqmlglobal_p.h index e2d53ab555..53caffe040 100644 --- a/src/qml/qml/qqmlglobal_p.h +++ b/src/qml/qml/qqmlglobal_p.h @@ -233,7 +233,7 @@ public: QVariant createVariantFromString(const QString &); QVariant createVariantFromString(int, const QString &, bool *); - QVariant createVariantFromJsObject(int, QQmlV4Handle, QV4::ExecutionEngine *, bool*); + QVariant createVariantFromJsObject(int, const QV4::Value &, QV4::ExecutionEngine *, bool *); bool equalValueType(int, const void *, const QVariant&); bool storeValueType(int, const void *, void *, size_t); @@ -250,7 +250,7 @@ private: virtual bool variantFromString(const QString &, QVariant *); virtual bool variantFromString(int, const QString &, QVariant *); - virtual bool variantFromJsObject(int, QQmlV4Handle, QV4::ExecutionEngine *, QVariant *); + virtual bool variantFromJsObject(int, const QV4::Value &, QV4::ExecutionEngine *, QVariant *); virtual bool equal(int, const void *, const QVariant&); virtual bool store(int, const void *, void *, size_t); diff --git a/src/qml/qml/qqmlpropertycache.cpp b/src/qml/qml/qqmlpropertycache.cpp index 46457a8d76..678b78ed17 100644 --- a/src/qml/qml/qqmlpropertycache.cpp +++ b/src/qml/qml/qqmlpropertycache.cpp @@ -42,7 +42,6 @@ #include #include #include -#include #include #include @@ -99,8 +98,6 @@ static void flagsForPropertyType(int propType, QQmlPropertyData::Flags &flags) flags.type = QQmlPropertyData::Flags::QmlBindingType; } else if (propType == qMetaTypeId()) { flags.type = QQmlPropertyData::Flags::QJSValueType; - } else if (propType == qMetaTypeId()) { - flags.type = QQmlPropertyData::Flags::V4HandleType; } else { QQmlMetaType::TypeCategory cat = QQmlMetaType::typeCategory(propType); diff --git a/src/qml/qml/qqmlpropertyrawdata_p.h b/src/qml/qml/qqmlpropertyrawdata_p.h index 833c0f6ad0..31d67b0953 100644 --- a/src/qml/qml/qqmlpropertyrawdata_p.h +++ b/src/qml/qml/qqmlpropertyrawdata_p.h @@ -74,7 +74,7 @@ public: QListType = 4, // Property type is a QML list QmlBindingType = 5, // Property type is a QQmlBinding* QJSValueType = 6, // Property type is a QScriptValue - V4HandleType = 7, // Property type is a QQmlV4Handle + // Gap, used to be V4HandleType VarPropertyType = 8, // Property type is a "var" property of VMEMO QVariantType = 9 // Property is a QVariant }; @@ -143,7 +143,6 @@ public: bool isQList() const { return _flags.type == Flags::QListType; } bool isQmlBinding() const { return _flags.type == Flags::QmlBindingType; } bool isQJSValue() const { return _flags.type == Flags::QJSValueType; } - bool isV4Handle() const { return _flags.type == Flags::V4HandleType; } bool isVarProperty() const { return _flags.type == Flags::VarPropertyType; } bool isQVariant() const { return _flags.type == Flags::QVariantType; } bool isVMEFunction() const { return _flags.isVMEFunction; } @@ -330,7 +329,6 @@ void QQmlPropertyRawData::Flags::copyPropertyTypeFlags(QQmlPropertyRawData::Flag case QListType: case QmlBindingType: case QJSValueType: - case V4HandleType: case QVariantType: type = from.type; } diff --git a/src/qml/qml/qqmlxmlhttprequest.cpp b/src/qml/qml/qqmlxmlhttprequest.cpp index 9f629f974d..1758158759 100644 --- a/src/qml/qml/qqmlxmlhttprequest.cpp +++ b/src/qml/qml/qqmlxmlhttprequest.cpp @@ -39,8 +39,6 @@ #include "qqmlxmlhttprequest_p.h" -#include - #include "qqmlengine.h" #include "qqmlengine_p.h" #include diff --git a/src/qml/qml/v8/qqmlbuiltinfunctions.cpp b/src/qml/qml/v8/qqmlbuiltinfunctions.cpp index 64dc581a56..3bc588b50e 100644 --- a/src/qml/qml/v8/qqmlbuiltinfunctions.cpp +++ b/src/qml/qml/v8/qqmlbuiltinfunctions.cpp @@ -436,7 +436,7 @@ ReturnedValue QtObject::method_font(const FunctionObject *b, const Value *, cons QV4::ExecutionEngine *v4 = scope.engine; bool ok = false; - QVariant v = QQml_valueTypeProvider()->createVariantFromJsObject(QMetaType::QFont, QQmlV4Handle(argv[0]), v4, &ok); + QVariant v = QQml_valueTypeProvider()->createVariantFromJsObject(QMetaType::QFont, argv[0], v4, &ok); if (!ok) THROW_GENERIC_ERROR("Qt.font(): Invalid argument: no valid font subproperties specified"); return scope.engine->fromVariant(v); @@ -540,7 +540,7 @@ ReturnedValue QtObject::method_matrix4x4(const FunctionObject *b, const Value *, if (argc == 1 && argv[0].isObject()) { bool ok = false; - QVariant v = QQml_valueTypeProvider()->createVariantFromJsObject(QMetaType::QMatrix4x4, QQmlV4Handle(argv[0]), scope.engine, &ok); + QVariant v = QQml_valueTypeProvider()->createVariantFromJsObject(QMetaType::QMatrix4x4, argv[0], scope.engine, &ok); if (!ok) THROW_GENERIC_ERROR("Qt.matrix4x4(): Invalid argument: not a valid matrix4x4 values array"); return scope.engine->fromVariant(v); diff --git a/src/qml/qml/v8/qv8engine_p.h b/src/qml/qml/v8/qv8engine_p.h index 23559618ef..f9b69cfacc 100644 --- a/src/qml/qml/v8/qv8engine_p.h +++ b/src/qml/qml/v8/qv8engine_p.h @@ -132,19 +132,6 @@ private: QV4::ExecutionEngine *e; }; -class Q_QML_PRIVATE_EXPORT QQmlV4Handle -{ -public: - QQmlV4Handle() : d(QV4::Encode::undefined()) {} - explicit QQmlV4Handle(const QV4::Value &v) : d(v.asReturnedValue()) {} - explicit QQmlV4Handle(QV4::ReturnedValue v) : d(v) {} - - operator QV4::ReturnedValue() const { return d; } - -private: - quint64 d; -}; - class QObject; class QQmlEngine; class QNetworkAccessManager; @@ -238,6 +225,4 @@ inline QV8Engine::Deletable *QV8Engine::extensionData(int index) const QT_END_NAMESPACE -Q_DECLARE_METATYPE(QQmlV4Handle) - #endif // QQMLV8ENGINE_P_H diff --git a/src/qml/types/qqmldelegatemodel.cpp b/src/qml/types/qqmldelegatemodel.cpp index 0b95c7c3e7..0e57119368 100644 --- a/src/qml/types/qqmldelegatemodel.cpp +++ b/src/qml/types/qqmldelegatemodel.cpp @@ -2419,17 +2419,15 @@ void QQmlDelegateModelGroupPrivate::setModel(QQmlDelegateModel *m, Compositor::G bool QQmlDelegateModelGroupPrivate::isChangedConnected() { Q_Q(QQmlDelegateModelGroup); - IS_SIGNAL_CONNECTED(q, QQmlDelegateModelGroup, changed, (const QQmlV4Handle &,const QQmlV4Handle &)); + IS_SIGNAL_CONNECTED(q, QQmlDelegateModelGroup, changed, (const QJSValue &,const QJSValue &)); } void QQmlDelegateModelGroupPrivate::emitChanges(QV4::ExecutionEngine *v4) { Q_Q(QQmlDelegateModelGroup); if (isChangedConnected() && !changeSet.isEmpty()) { - QV4::Scope scope(v4); - QV4::ScopedValue removed(scope, engineData(scope.engine)->array(v4, changeSet.removes())); - QV4::ScopedValue inserted(scope, engineData(scope.engine)->array(v4, changeSet.inserts())); - emit q->changed(QQmlV4Handle(removed), QQmlV4Handle(inserted)); + emit q->changed(QJSValue(v4, engineData(v4)->array(v4, changeSet.removes())), + QJSValue(v4, engineData(v4)->array(v4, changeSet.inserts()))); } if (changeSet.difference() != 0) emit q->countChanged(); @@ -2607,18 +2605,18 @@ void QQmlDelegateModelGroup::setDefaultInclude(bool include) \endlist */ -QQmlV4Handle QQmlDelegateModelGroup::get(int index) +QJSValue QQmlDelegateModelGroup::get(int index) { Q_D(QQmlDelegateModelGroup); if (!d->model) - return QQmlV4Handle(QV4::Encode::undefined()); + return QJSValue(); QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); if (!model->m_context || !model->m_context->isValid()) { - return QQmlV4Handle(QV4::Encode::undefined()); + return QJSValue(); } else if (index < 0 || index >= model->m_compositor.count(d->group)) { qmlWarning(this) << tr("get: index out of range"); - return QQmlV4Handle(QV4::Encode::undefined()); + return QJSValue(); } Compositor::iterator it = model->m_compositor.find(d->group, index); @@ -2630,7 +2628,7 @@ QQmlV4Handle QQmlDelegateModelGroup::get(int index) cacheItem = model->m_adaptorModel.createItem( model->m_cacheMetaType, it.modelIndex()); if (!cacheItem) - return QQmlV4Handle(QV4::Encode::undefined()); + return QJSValue(); cacheItem->groups = it->flags; model->m_cache.insert(it.cacheIndex, cacheItem); @@ -2646,7 +2644,7 @@ QQmlV4Handle QQmlDelegateModelGroup::get(int index) o->setPrototypeOf(p); ++cacheItem->scriptRef; - return QQmlV4Handle(o); + return QJSValue(v4, o->asReturnedValue()); } bool QQmlDelegateModelGroupPrivate::parseIndex(const QV4::Value &value, int *index, Compositor::Group *group) const diff --git a/src/qml/types/qqmldelegatemodel_p.h b/src/qml/types/qqmldelegatemodel_p.h index fbf3614a7c..2684162514 100644 --- a/src/qml/types/qqmldelegatemodel_p.h +++ b/src/qml/types/qqmldelegatemodel_p.h @@ -59,7 +59,6 @@ #include #include -#include #include QT_REQUIRE_CONFIG(qml_delegate_model); @@ -179,7 +178,7 @@ public: bool defaultInclude() const; void setDefaultInclude(bool include); - Q_INVOKABLE QQmlV4Handle get(int index); + Q_INVOKABLE QJSValue get(int index); public Q_SLOTS: void insert(QQmlV4Function *); @@ -195,7 +194,7 @@ Q_SIGNALS: void countChanged(); void nameChanged(); void defaultIncludeChanged(); - void changed(const QQmlV4Handle &removed, const QQmlV4Handle &inserted); + void changed(const QJSValue &removed, const QJSValue &inserted); private: Q_DECLARE_PRIVATE(QQmlDelegateModelGroup) }; diff --git a/src/qml/types/qqmllistmodel.cpp b/src/qml/types/qqmllistmodel.cpp index 565e60b3c1..5b5bcd8464 100644 --- a/src/qml/types/qqmllistmodel.cpp +++ b/src/qml/types/qqmllistmodel.cpp @@ -2545,7 +2545,7 @@ void QQmlListModel::append(QQmlV4Function *args) \sa append() */ -QQmlV4Handle QQmlListModel::get(int index) const +QJSValue QQmlListModel::get(int index) const { QV4::Scope scope(engine()); QV4::ScopedValue result(scope, QV4::Value::undefinedValue()); @@ -2568,7 +2568,7 @@ QQmlV4Handle QQmlListModel::get(int index) const } } - return QQmlV4Handle(result); + return QJSValue(engine(), result->asReturnedValue()); } /*! @@ -2587,10 +2587,10 @@ QQmlV4Handle QQmlListModel::get(int index) const \sa append() */ -void QQmlListModel::set(int index, const QQmlV4Handle &handle) +void QQmlListModel::set(int index, const QJSValue &value) { QV4::Scope scope(engine()); - QV4::ScopedObject object(scope, handle); + QV4::ScopedObject object(scope, QJSValuePrivate::getValue(&value)); if (!object) { qmlWarning(this) << tr("set: value is not an object"); diff --git a/src/qml/types/qqmllistmodel_p.h b/src/qml/types/qqmllistmodel_p.h index 95b797c898..471e33aa5a 100644 --- a/src/qml/types/qqmllistmodel_p.h +++ b/src/qml/types/qqmllistmodel_p.h @@ -100,8 +100,8 @@ public: Q_INVOKABLE void remove(QQmlV4Function *args); Q_INVOKABLE void append(QQmlV4Function *args); Q_INVOKABLE void insert(QQmlV4Function *args); - Q_INVOKABLE QQmlV4Handle get(int index) const; - Q_INVOKABLE void set(int index, const QQmlV4Handle &); + Q_INVOKABLE QJSValue get(int index) const; + Q_INVOKABLE void set(int index, const QJSValue &value); Q_INVOKABLE void setProperty(int index, const QString& property, const QVariant& value); Q_INVOKABLE void move(int from, int to, int count); Q_INVOKABLE void sync(); diff --git a/src/qml/types/qqmllistmodelworkeragent.cpp b/src/qml/types/qqmllistmodelworkeragent.cpp index fe3eaa3198..f7cb08dcf4 100644 --- a/src/qml/types/qqmllistmodelworkeragent.cpp +++ b/src/qml/types/qqmllistmodelworkeragent.cpp @@ -114,12 +114,12 @@ void QQmlListModelWorkerAgent::insert(QQmlV4Function *args) m_copy->insert(args); } -QQmlV4Handle QQmlListModelWorkerAgent::get(int index) const +QJSValue QQmlListModelWorkerAgent::get(int index) const { return m_copy->get(index); } -void QQmlListModelWorkerAgent::set(int index, const QQmlV4Handle &value) +void QQmlListModelWorkerAgent::set(int index, const QJSValue &value) { m_copy->set(index, value); } diff --git a/src/qml/types/qqmllistmodelworkeragent_p.h b/src/qml/types/qqmllistmodelworkeragent_p.h index ae2d4b11e0..69d1785618 100644 --- a/src/qml/types/qqmllistmodelworkeragent_p.h +++ b/src/qml/types/qqmllistmodelworkeragent_p.h @@ -85,8 +85,8 @@ public: Q_INVOKABLE void remove(QQmlV4Function *args); Q_INVOKABLE void append(QQmlV4Function *args); Q_INVOKABLE void insert(QQmlV4Function *args); - Q_INVOKABLE QQmlV4Handle get(int index) const; - Q_INVOKABLE void set(int index, const QQmlV4Handle &); + Q_INVOKABLE QJSValue get(int index) const; + Q_INVOKABLE void set(int index, const QJSValue &value); Q_INVOKABLE void setProperty(int index, const QString& property, const QVariant& value); Q_INVOKABLE void move(int from, int to, int count); Q_INVOKABLE void sync(); diff --git a/src/qml/types/qquickworkerscript.cpp b/src/qml/types/qquickworkerscript.cpp index aaa89846aa..c081c9e7fc 100644 --- a/src/qml/types/qquickworkerscript.cpp +++ b/src/qml/types/qquickworkerscript.cpp @@ -647,12 +647,10 @@ void QQuickWorkerScript::componentComplete() bool QQuickWorkerScript::event(QEvent *event) { if (event->type() == (QEvent::Type)WorkerDataEvent::WorkerData) { - QQmlEngine *engine = qmlEngine(this); - if (engine) { + if (QQmlEngine *engine = qmlEngine(this)) { + QV4::ExecutionEngine *v4 = engine->handle(); WorkerDataEvent *workerEvent = static_cast(event); - QV4::Scope scope(engine->handle()); - QV4::ScopedValue value(scope, QV4::Serialize::deserialize(workerEvent->data(), scope.engine)); - emit message(QQmlV4Handle(value)); + emit message(QJSValue(v4, QV4::Serialize::deserialize(workerEvent->data(), v4))); } return true; } else if (event->type() == (QEvent::Type)WorkerErrorEvent::WorkerError) { diff --git a/src/qml/types/qquickworkerscript_p.h b/src/qml/types/qquickworkerscript_p.h index 1a8d2ab076..87cf2e9754 100644 --- a/src/qml/types/qquickworkerscript_p.h +++ b/src/qml/types/qquickworkerscript_p.h @@ -83,7 +83,6 @@ private: }; class QQmlV4Function; -class QQmlV4Handle; class Q_AUTOTEST_EXPORT QQuickWorkerScript : public QObject, public QQmlParserStatus { Q_OBJECT @@ -102,7 +101,7 @@ public Q_SLOTS: Q_SIGNALS: void sourceChanged(); - void message(const QQmlV4Handle &messageObject); + void message(const QJSValue &messageObject); protected: void classBegin() override; diff --git a/src/qml/util/qqmladaptormodel.cpp b/src/qml/util/qqmladaptormodel.cpp index a9a38c5381..f991ae0a69 100644 --- a/src/qml/util/qqmladaptormodel.cpp +++ b/src/qml/util/qqmladaptormodel.cpp @@ -42,7 +42,6 @@ #include #include #include -#include #include #include -- cgit v1.2.3 From 0af154c41dc0dfbf5994e7eb3056b2333b5645b7 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Fri, 12 Apr 2019 10:42:56 +0200 Subject: Don't create value types for QImage and QPixmap Those are "scarce" resources which need to be kept as QVariant. Fixes: QTBUG-74751 Change-Id: I28381e2a754ed4bbf4e409dc275f6288b64416cc Reviewed-by: Simon Hausmann --- src/qml/jsruntime/qv4engine.cpp | 4 ++++ src/qml/qml/qqmlvaluetype.cpp | 29 ++++++++++++++++++----------- 2 files changed, 22 insertions(+), 11 deletions(-) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index bcd577c24d..bd1124beb6 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -1537,6 +1537,10 @@ QV4::ReturnedValue QV4::ExecutionEngine::fromVariant(const QVariant &variant) case QMetaType::QLocale: return QQmlLocale::wrap(this, *reinterpret_cast(ptr)); #endif + case QMetaType::QPixmap: + case QMetaType::QImage: + // Scarce value types + return QV4::Encode(newVariantObject(variant)); default: break; } diff --git a/src/qml/qml/qqmlvaluetype.cpp b/src/qml/qml/qqmlvaluetype.cpp index 2b21591017..cb6a467c6c 100644 --- a/src/qml/qml/qqmlvaluetype.cpp +++ b/src/qml/qml/qqmlvaluetype.cpp @@ -81,19 +81,26 @@ QQmlValueTypeFactoryImpl::~QQmlValueTypeFactoryImpl() bool QQmlValueTypeFactoryImpl::isValueType(int idx) { - if (idx >= (int)QVariant::UserType) { - return (valueType(idx) != nullptr); - } else if (idx >= 0 - && idx != QVariant::StringList - && idx != QMetaType::QObjectStar - && idx != QMetaType::VoidStar - && idx != QMetaType::Nullptr - && idx != QMetaType::QVariant - && idx != QMetaType::QLocale) { + if (idx >= QMetaType::User) + return valueType(idx) != nullptr; + + if (idx < 0) + return false; + + // Qt internal types + switch (idx) { + case QMetaType::QStringList: + case QMetaType::QObjectStar: + case QMetaType::VoidStar: + case QMetaType::Nullptr: + case QMetaType::QVariant: + case QMetaType::QLocale: + case QMetaType::QImage: // scarce type, keep as QVariant + case QMetaType::QPixmap: // scarce type, keep as QVariant + return false; + default: return true; } - - return false; } const QMetaObject *QQmlValueTypeFactoryImpl::metaObjectForMetaType(int t) -- cgit v1.2.3 From 6dac2e7df7fbf0f29667bf92b4e72426db47dbe9 Mon Sep 17 00:00:00 2001 From: Andy Shaw Date: Sat, 13 Apr 2019 00:31:06 +0200 Subject: Only create the imports array if importCount is greater than 0 Since it is possible that CompilationUnit::instantiate() might be called more than once when the importCount is 0 then it should only create the imports array when it is greater than 0. This prevents a memory leak due to the recreation of this array each time it is called even though there is no imports to assign. Change-Id: I5d84b01de10bff2ca25248251e8337839e434bd5 Reviewed-by: Simon Hausmann --- src/qml/compiler/qv4compileddata.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src/qml') diff --git a/src/qml/compiler/qv4compileddata.cpp b/src/qml/compiler/qv4compileddata.cpp index 9ffcb81fa2..8f2f4a11c1 100644 --- a/src/qml/compiler/qv4compileddata.cpp +++ b/src/qml/compiler/qv4compileddata.cpp @@ -470,8 +470,10 @@ Heap::Module *CompilationUnit::instantiate(ExecutionEngine *engine) ScopedString importName(scope); const uint importCount = data->importEntryTableSize; - imports = new const Value *[importCount]; - memset(imports, 0, importCount * sizeof(Value *)); + if (importCount > 0) { + imports = new const Value *[importCount]; + memset(imports, 0, importCount * sizeof(Value *)); + } for (uint i = 0; i < importCount; ++i) { const CompiledData::ImportEntry &entry = data->importEntryTable()[i]; auto dependentModuleUnit = engine->loadModule(QUrl(stringAt(entry.moduleRequest)), this); -- cgit v1.2.3 From 71cacf22b83722c9a8668b1518028fa0835fb286 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Fri, 12 Apr 2019 15:37:15 +0200 Subject: Fix string replacement with invalid captures If we have a theoretically valid capture reference that just didn't capture anything in this match, we don't want to treat it as literal. Only capture references that clearly are outside the range of things we can possibly capture with this expression should be treated as literal strings. Change-Id: Iab0bf329d11a6b9e172aa662f11751d86cfc26a6 Fixes: QTBUG-75121 Reviewed-by: Lars Knoll --- src/qml/jsruntime/qv4stringobject.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4stringobject.cpp b/src/qml/jsruntime/qv4stringobject.cpp index dee6a67792..6afa6d36d6 100644 --- a/src/qml/jsruntime/qv4stringobject.cpp +++ b/src/qml/jsruntime/qv4stringobject.cpp @@ -765,9 +765,8 @@ static void appendReplacementString(QString *result, const QString &input, const i += skip; if (substStart != JSC::Yarr::offsetNoMatch && substEnd != JSC::Yarr::offsetNoMatch) *result += input.midRef(substStart, substEnd - substStart); - else { + else if (skip == 0) // invalid capture reference. Taken as literal value *result += replaceValue.at(i); - } } else { *result += replaceValue.at(i); } -- cgit v1.2.3 From 2251fb3274ae66631be42d50508098f179f3c3e1 Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Tue, 18 Sep 2018 10:19:18 +0200 Subject: Prevent CoW detaches from happening Change-Id: Ia42c0d732e0f6ccfa2c70b86edccd9eb471aac7c Reviewed-by: Simon Hausmann --- src/qml/compiler/qv4compilerscanfunctions.cpp | 27 +++++++++++++++------------ src/qml/jsruntime/qv4identifiertable.cpp | 2 +- 2 files changed, 16 insertions(+), 13 deletions(-) (limited to 'src/qml') diff --git a/src/qml/compiler/qv4compilerscanfunctions.cpp b/src/qml/compiler/qv4compilerscanfunctions.cpp index 04593f202a..8984b6931e 100644 --- a/src/qml/compiler/qv4compilerscanfunctions.cpp +++ b/src/qml/compiler/qv4compilerscanfunctions.cpp @@ -741,9 +741,9 @@ void ScanFunctions::calcEscapingVariables() if (c->contextType != ContextType::ESModule) continue; for (const auto &entry: c->exportEntries) { - auto m = c->members.find(entry.localName); - if (m != c->members.end()) - m->canEscape = true; + auto mIt = c->members.constFind(entry.localName); + if (mIt != c->members.constEnd()) + mIt->canEscape = true; } break; } @@ -759,8 +759,8 @@ void ScanFunctions::calcEscapingVariables() } Q_ASSERT(c != inner); while (c) { - Context::MemberMap::const_iterator it = c->members.find(var); - if (it != c->members.end()) { + Context::MemberMap::const_iterator it = c->members.constFind(var); + if (it != c->members.constEnd()) { if (c->parent || it->isLexicallyScoped()) { it->canEscape = true; c->requiresExecutionContext = true; @@ -816,15 +816,17 @@ void ScanFunctions::calcEscapingVariables() // add an escaping 'this' variable c->addLocalVar(QStringLiteral("this"), Context::VariableDefinition, VariableScope::Let); c->requiresExecutionContext = true; - auto m = c->members.find(QStringLiteral("this")); - m->canEscape = true; + auto mIt = c->members.constFind(QStringLiteral("this")); + Q_ASSERT(mIt != c->members.constEnd()); + mIt->canEscape = true; } if (c->innerFunctionAccessesNewTarget) { // add an escaping 'new.target' variable c->addLocalVar(QStringLiteral("new.target"), Context::VariableDefinition, VariableScope::Let); c->requiresExecutionContext = true; - auto m = c->members.find(QStringLiteral("new.target")); - m->canEscape = true; + auto mIt = c->members.constFind(QStringLiteral("new.target")); + Q_ASSERT(mIt != c->members.constEnd()); + mIt->canEscape = true; } if (c->allVarsEscape && c->contextType == ContextType::Block && c->members.isEmpty()) c->allVarsEscape = false; @@ -845,8 +847,9 @@ void ScanFunctions::calcEscapingVariables() } if (c->contextType == ContextType::Block && c->isCatchBlock) { c->requiresExecutionContext = true; - auto m = c->members.find(c->caughtVariable); - m->canEscape = true; + auto mIt = c->members.constFind(c->caughtVariable); + Q_ASSERT(mIt != c->members.constEnd()); + mIt->canEscape = true; } const QLatin1String exprForOn("expression for on"); if (c->contextType == ContextType::Binding && c->name.length() > exprForOn.size() && @@ -855,7 +858,7 @@ void ScanFunctions::calcEscapingVariables() // we don't know if the code is a signal handler or not. c->requiresExecutionContext = true; if (c->allVarsEscape) { - for (auto &m : c->members) + for (const auto &m : qAsConst(c->members)) m.canEscape = true; } } diff --git a/src/qml/jsruntime/qv4identifiertable.cpp b/src/qml/jsruntime/qv4identifiertable.cpp index e476baa886..4305bc4647 100644 --- a/src/qml/jsruntime/qv4identifiertable.cpp +++ b/src/qml/jsruntime/qv4identifiertable.cpp @@ -70,7 +70,7 @@ IdentifierTable::~IdentifierTable() { free(entriesByHash); free(entriesById); - for (auto &h : idHashes) + for (const auto &h : qAsConst(idHashes)) h->identifierTable = nullptr; } -- cgit v1.2.3 From c8d00be64979c7d2b607eb88de9f3d4212f1c50d Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Fri, 23 Nov 2018 13:41:04 +0100 Subject: QMLJS: Remove unused boolean field hasTry from compiler context All uses have been refactored out in previous commits, so now also remove the field. Change-Id: Ic6fb92f70a0451db04ddf40344239a69312faf8f Reviewed-by: Simon Hausmann --- src/qml/compiler/qv4compilercontext_p.h | 1 - 1 file changed, 1 deletion(-) (limited to 'src/qml') diff --git a/src/qml/compiler/qv4compilercontext_p.h b/src/qml/compiler/qv4compilercontext_p.h index 8f3b60e395..020b403bc4 100644 --- a/src/qml/compiler/qv4compilercontext_p.h +++ b/src/qml/compiler/qv4compilercontext_p.h @@ -215,7 +215,6 @@ struct Context { bool usesThis = false; bool innerFunctionAccessesThis = false; bool innerFunctionAccessesNewTarget = false; - bool hasTry = false; bool returnsClosure = false; mutable bool argumentsCanEscape = false; bool requiresExecutionContext = false; -- cgit v1.2.3 From 5fcc9906d7b6d19ea3d79ccf0d6446d01b583973 Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Fri, 12 Oct 2018 10:47:51 +0200 Subject: QML: Check for (valid) compilation units while hashing When an error occurred while creating a compilation unit, the pointer might be set to null. Subsequent use in hashing should check for this, and not use a nullptr. Change-Id: I62650917a740c9c1be29608285670153bed8703c Reviewed-by: Ulf Hermann --- src/qml/compiler/qv4compileddata.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/qml') diff --git a/src/qml/compiler/qv4compileddata.cpp b/src/qml/compiler/qv4compileddata.cpp index 8f2f4a11c1..9fb91e9140 100644 --- a/src/qml/compiler/qv4compileddata.cpp +++ b/src/qml/compiler/qv4compileddata.cpp @@ -897,6 +897,8 @@ bool ResolvedTypeReference::addToHash(QCryptographicHash *hash, QQmlEngine *engi hash->addData(createPropertyCache(engine)->checksum(&ok)); return ok; } + if (!compilationUnit) + return false; hash->addData(compilationUnit->unitData()->md5Checksum, sizeof(compilationUnit->unitData()->md5Checksum)); return true; } -- cgit v1.2.3 From 68b7a66a6e4d673d11aab44cb87b3f005cdff8ea Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Fri, 29 Mar 2019 09:24:27 +0100 Subject: Don't use UINT_MAX as invalid array index in PropertyKey Technically UINT_MAX is actually a valid array index, although that is an academic problem right now. However, we do have a method isArrayIndex() and should just use that to determine if a PropertyKey is an array index. Fixes: QTBUG-73893 Change-Id: I302e7894331ed2ab4717f7d8d6cc7d8974dabb4e Reviewed-by: Erik Verbruggen --- src/qml/jsruntime/qv4argumentsobject.cpp | 58 +++++++++++++++++++------------- src/qml/jsruntime/qv4engine.cpp | 5 ++- src/qml/jsruntime/qv4identifiertable.cpp | 5 ++- src/qml/jsruntime/qv4object.cpp | 18 ++++------ src/qml/jsruntime/qv4propertykey_p.h | 4 +-- src/qml/jsruntime/qv4stringobject.cpp | 15 +++++---- src/qml/jsruntime/qv4typedarray.cpp | 40 +++++++++++----------- 7 files changed, 75 insertions(+), 70 deletions(-) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4argumentsobject.cpp b/src/qml/jsruntime/qv4argumentsobject.cpp index 4a21f62cf2..98e0ef9e70 100644 --- a/src/qml/jsruntime/qv4argumentsobject.cpp +++ b/src/qml/jsruntime/qv4argumentsobject.cpp @@ -116,6 +116,9 @@ bool ArgumentsObject::virtualDefineOwnProperty(Managed *m, PropertyKey id, const { ArgumentsObject *args = static_cast(m); args->fullyCreate(); + if (!id.isArrayIndex()) + return Object::virtualDefineOwnProperty(m, id, desc, attrs); + uint index = id.asArrayIndex(); if (!args->isMapped(index)) @@ -148,36 +151,42 @@ bool ArgumentsObject::virtualDefineOwnProperty(Managed *m, PropertyKey id, const ReturnedValue ArgumentsObject::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) { - const ArgumentsObject *args = static_cast(m); - uint index = id.asArrayIndex(); - if (index < args->d()->argCount && !args->d()->fullyCreated) { - if (hasProperty) - *hasProperty = true; - return args->context()->args()[index].asReturnedValue(); + if (id.isArrayIndex()) { + const ArgumentsObject *args = static_cast(m); + uint index = id.asArrayIndex(); + if (index < args->d()->argCount && !args->d()->fullyCreated) { + if (hasProperty) + *hasProperty = true; + return args->context()->args()[index].asReturnedValue(); + } + + if (args->isMapped(index)) { + Q_ASSERT(index < static_cast(args->context()->function->formalParameterCount())); + if (hasProperty) + *hasProperty = true; + return args->context()->args()[index].asReturnedValue(); + } } - if (!args->isMapped(index)) - return Object::virtualGet(m, id, receiver, hasProperty); - Q_ASSERT(index < static_cast(args->context()->function->formalParameterCount())); - if (hasProperty) - *hasProperty = true; - return args->context()->args()[index].asReturnedValue(); + return Object::virtualGet(m, id, receiver, hasProperty); } bool ArgumentsObject::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver) { - ArgumentsObject *args = static_cast(m); - uint index = id.asArrayIndex(); - - if (args == receiver && index < args->d()->argCount && !args->d()->fullyCreated) { - args->context()->setArg(index, value); - return true; + if (id.isArrayIndex()) { + ArgumentsObject *args = static_cast(m); + uint index = id.asArrayIndex(); + + if (args == receiver && index < args->d()->argCount && !args->d()->fullyCreated) { + args->context()->setArg(index, value); + return true; + } + + bool isMapped = (args == receiver && args->isMapped(index)); + if (isMapped) + args->context()->setArg(index, value); } - bool isMapped = (args == receiver && args->isMapped(index)); - if (isMapped) - args->context()->setArg(index, value); - return Object::virtualPut(m, id, value, receiver); } @@ -186,13 +195,16 @@ bool ArgumentsObject::virtualDeleteProperty(Managed *m, PropertyKey id) ArgumentsObject *args = static_cast(m); args->fullyCreate(); bool result = Object::virtualDeleteProperty(m, id); - if (result) + if (result && id.isArrayIndex()) args->removeMapping(id.asArrayIndex()); return result; } PropertyAttributes ArgumentsObject::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p) { + if (!id.isArrayIndex()) + return Object::virtualGetOwnProperty(m, id, p); + const ArgumentsObject *args = static_cast(m); uint index = id.asArrayIndex(); if (index < args->d()->argCount && !args->d()->fullyCreated) { diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index ab980e99df..5bc81de472 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -1659,9 +1659,8 @@ static QV4::ReturnedValue variantMapToJS(QV4::ExecutionEngine *v4, const QVarian s = v4->newIdentifier(it.key()); key = s->propertyKey(); v = variantToJS(v4, it.value()); - uint idx = key->asArrayIndex(); - if (idx < UINT_MAX) - o->arraySet(idx, v); + if (key->isArrayIndex()) + o->arraySet(key->asArrayIndex(), v); else o->insertMember(s, v); } diff --git a/src/qml/jsruntime/qv4identifiertable.cpp b/src/qml/jsruntime/qv4identifiertable.cpp index e476baa886..102c06d9b0 100644 --- a/src/qml/jsruntime/qv4identifiertable.cpp +++ b/src/qml/jsruntime/qv4identifiertable.cpp @@ -216,9 +216,8 @@ PropertyKey IdentifierTable::asPropertyKeyImpl(const Heap::String *str) Heap::StringOrSymbol *IdentifierTable::resolveId(PropertyKey i) const { - uint arrayIdx = i.asArrayIndex(); - if (arrayIdx < UINT_MAX) - return engine->newString(QString::number(arrayIdx)); + if (i.isArrayIndex()) + return engine->newString(QString::number(i.asArrayIndex())); if (!i.isValid()) return nullptr; diff --git a/src/qml/jsruntime/qv4object.cpp b/src/qml/jsruntime/qv4object.cpp index efab9a6454..206b410cf4 100644 --- a/src/qml/jsruntime/qv4object.cpp +++ b/src/qml/jsruntime/qv4object.cpp @@ -406,8 +406,8 @@ ReturnedValue Object::internalGet(PropertyKey id, const Value *receiver, bool *h { Heap::Object *o = d(); - uint index = id.asArrayIndex(); - if (index != UINT_MAX) { + if (id.isArrayIndex()) { + const uint index = id.asArrayIndex(); Scope scope(this); PropertyAttributes attrs; ScopedProperty pd(scope); @@ -431,8 +431,6 @@ ReturnedValue Object::internalGet(PropertyKey id, const Value *receiver, bool *h break; } } else { - Q_ASSERT(!id.isArrayIndex()); - while (1) { auto idx = o->internalClass->findValueOrGetter(id); if (idx.isValid()) { @@ -470,14 +468,13 @@ bool Object::internalPut(PropertyKey id, const Value &value, Value *receiver) if (d()->internalClass->vtable->getOwnProperty == Object::virtualGetOwnProperty) { // This object standard methods in the vtable, so we can take a shortcut // and avoid the calls to getOwnProperty and defineOwnProperty - uint index = id.asArrayIndex(); PropertyAttributes attrs; PropertyIndex propertyIndex{nullptr, nullptr}; - if (index != UINT_MAX) { + if (id.isArrayIndex()) { if (arrayData()) - propertyIndex = arrayData()->getValueOrSetter(index, &attrs); + propertyIndex = arrayData()->getValueOrSetter(id.asArrayIndex(), &attrs); } else { auto member = internalClass()->findValueOrSetter(id); if (member.isValid()) { @@ -546,12 +543,11 @@ bool Object::internalPut(PropertyKey id, const Value &value, Value *receiver) if (r->internalClass()->vtable->defineOwnProperty == virtualDefineOwnProperty) { // standard object, we can avoid some more checks - uint index = id.asArrayIndex(); - if (index == UINT_MAX) { + if (id.isArrayIndex()) { + r->arraySet(id.asArrayIndex(), value); + } else { ScopedStringOrSymbol s(scope, id.asStringOrSymbol()); r->insertMember(s, value); - } else { - r->arraySet(index, value); } return true; } diff --git a/src/qml/jsruntime/qv4propertykey_p.h b/src/qml/jsruntime/qv4propertykey_p.h index 47867765db..523afd4ccf 100644 --- a/src/qml/jsruntime/qv4propertykey_p.h +++ b/src/qml/jsruntime/qv4propertykey_p.h @@ -113,8 +113,8 @@ public: static PropertyKey invalid() { PropertyKey key; key.val = 0; return key; } static PropertyKey fromArrayIndex(uint idx) { PropertyKey key; key.val = ArrayIndexMask | static_cast(idx); return key; } bool isStringOrSymbol() const { return isManaged() && val != 0; } - uint asArrayIndex() const { return (isManaged() || val == 0) ? std::numeric_limits::max() : static_cast(val & 0xffffffff); } - uint isArrayIndex() const { return !isManaged() && val != 0 && static_cast(val & 0xffffffff) != std::numeric_limits::max(); } + uint asArrayIndex() const { Q_ASSERT(isArrayIndex()); return static_cast(val & 0xffffffff); } + uint isArrayIndex() const { return !isManaged() && val != 0; } bool isValid() const { return val != 0; } static PropertyKey fromStringOrSymbol(Heap::StringOrSymbol *b) { PropertyKey key; key.setM(b); return key; } diff --git a/src/qml/jsruntime/qv4stringobject.cpp b/src/qml/jsruntime/qv4stringobject.cpp index dee6a67792..1c6dfe0fdb 100644 --- a/src/qml/jsruntime/qv4stringobject.cpp +++ b/src/qml/jsruntime/qv4stringobject.cpp @@ -152,13 +152,14 @@ PropertyAttributes StringObject::virtualGetOwnProperty(const Managed *m, Propert if (attributes != Attr_Invalid) return attributes; - const StringObject *s = static_cast(m); - uint slen = s->d()->string->toQString().length(); - uint index = id.asArrayIndex(); - if (index < slen) { - if (p) - p->value = s->getIndex(index); - return Attr_NotConfigurable|Attr_NotWritable; + if (id.isArrayIndex()) { + const uint index = id.asArrayIndex(); + const auto s = static_cast(m); + if (index < uint(s->d()->string->toQString().length())) { + if (p) + p->value = s->getIndex(index); + return Attr_NotConfigurable|Attr_NotWritable; + } } return Object::virtualGetOwnProperty(m, id, p); } diff --git a/src/qml/jsruntime/qv4typedarray.cpp b/src/qml/jsruntime/qv4typedarray.cpp index d83f021450..43e1dabb6d 100644 --- a/src/qml/jsruntime/qv4typedarray.cpp +++ b/src/qml/jsruntime/qv4typedarray.cpp @@ -459,24 +459,23 @@ Heap::TypedArray *TypedArray::create(ExecutionEngine *e, Heap::TypedArray::Type ReturnedValue TypedArray::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) { - uint index = id.asArrayIndex(); - if (index == UINT_MAX && !id.isCanonicalNumericIndexString()) + const bool isArrayIndex = id.isArrayIndex(); + if (!isArrayIndex && !id.isCanonicalNumericIndexString()) return Object::virtualGet(m, id, receiver, hasProperty); - // fall through, with index == UINT_MAX it'll do the right thing. Scope scope(static_cast(m)->engine()); Scoped a(scope, static_cast(m)); if (a->d()->buffer->isDetachedBuffer()) return scope.engine->throwTypeError(); - if (index >= a->length()) { + if (!isArrayIndex || id.asArrayIndex() >= a->length()) { if (hasProperty) *hasProperty = false; return Encode::undefined(); } uint bytesPerElement = a->d()->type->bytesPerElement; - uint byteOffset = a->d()->byteOffset + index * bytesPerElement; + uint byteOffset = a->d()->byteOffset + id.asArrayIndex() * bytesPerElement; Q_ASSERT(byteOffset + bytesPerElement <= (uint)a->d()->buffer->byteLength()); if (hasProperty) @@ -486,27 +485,22 @@ ReturnedValue TypedArray::virtualGet(const Managed *m, PropertyKey id, const Val bool TypedArray::virtualHasProperty(const Managed *m, PropertyKey id) { - uint index = id.asArrayIndex(); - if (index == UINT_MAX && !id.isCanonicalNumericIndexString()) + const bool isArrayIndex = id.isArrayIndex(); + if (!isArrayIndex && !id.isCanonicalNumericIndexString()) return Object::virtualHasProperty(m, id); - // fall through, with index == UINT_MAX it'll do the right thing. const TypedArray *a = static_cast(m); if (a->d()->buffer->isDetachedBuffer()) { a->engine()->throwTypeError(); return false; } - if (index >= a->length()) - return false; - return true; + return isArrayIndex && id.asArrayIndex() < a->length(); } PropertyAttributes TypedArray::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p) { - uint index = id.asArrayIndex(); - if (index == UINT_MAX && !id.isCanonicalNumericIndexString()) + if (!id.isArrayIndex() && !id.isCanonicalNumericIndexString()) return Object::virtualGetOwnProperty(m, id, p); - // fall through, with index == UINT_MAX it'll do the right thing. bool hasProperty = false; ReturnedValue v = virtualGet(m, id, m, &hasProperty); @@ -517,10 +511,9 @@ PropertyAttributes TypedArray::virtualGetOwnProperty(const Managed *m, PropertyK bool TypedArray::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver) { - uint index = id.asArrayIndex(); - if (index == UINT_MAX && !id.isCanonicalNumericIndexString()) + const bool isArrayIndex = id.isArrayIndex(); + if (!isArrayIndex && !id.isCanonicalNumericIndexString()) return Object::virtualPut(m, id, value, receiver); - // fall through, with index == UINT_MAX it'll do the right thing. ExecutionEngine *v4 = static_cast(m)->engine(); if (v4->hasException) @@ -531,6 +524,10 @@ bool TypedArray::virtualPut(Managed *m, PropertyKey id, const Value &value, Valu if (a->d()->buffer->isDetachedBuffer()) return scope.engine->throwTypeError(); + if (!isArrayIndex) + return false; + + const uint index = id.asArrayIndex(); if (index >= a->length()) return false; @@ -547,11 +544,12 @@ bool TypedArray::virtualPut(Managed *m, PropertyKey id, const Value &value, Valu bool TypedArray::virtualDefineOwnProperty(Managed *m, PropertyKey id, const Property *p, PropertyAttributes attrs) { - uint index = id.asArrayIndex(); - if (index == UINT_MAX && !id.isCanonicalNumericIndexString()) - return Object::virtualDefineOwnProperty(m, id, p, attrs); - // fall through, with index == UINT_MAX it'll do the right thing. + if (!id.isArrayIndex()) { + return !id.isCanonicalNumericIndexString() + && Object::virtualDefineOwnProperty(m, id, p, attrs); + } + const uint index = id.asArrayIndex(); TypedArray *a = static_cast(m); if (index >= a->length() || attrs.isAccessor()) return false; -- cgit v1.2.3 From 398d586a59e768f6e442fdc912d9180df71ff374 Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Fri, 12 Oct 2018 12:13:45 +0200 Subject: QML: Allow fetchOrCreateTypeForUrl to report errors without qFatal Because qFatal will abort() the program. Task-number: QTBUG-71116 Change-Id: Ifd6be996cfbd6fff8e75ad2b26682c34f837ac88 Reviewed-by: Ulf Hermann --- src/qml/qml/qqmlimport.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/qml') diff --git a/src/qml/qml/qqmlimport.cpp b/src/qml/qml/qqmlimport.cpp index a79d4d074c..5a1364473e 100644 --- a/src/qml/qml/qqmlimport.cpp +++ b/src/qml/qml/qqmlimport.cpp @@ -867,7 +867,7 @@ bool QQmlImportInstance::resolveType(QQmlTypeLoader *typeLoader, const QHashedSt *typeRecursionDetected = true; } else { QQmlType returnType = fetchOrCreateTypeForUrl( - qmlUrl, type, registrationType == QQmlType::CompositeSingletonType, nullptr); + qmlUrl, type, registrationType == QQmlType::CompositeSingletonType, errors); if (type_return) *type_return = returnType; return returnType.isValid(); -- cgit v1.2.3 From 259c958e21c63f227a1bb678867210e0f6af0991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Storsj=C3=B6?= Date: Mon, 15 Apr 2019 13:36:46 +0300 Subject: V4: Only enable the JIT on ARM on specific known OSes On all other architectures, the JIT is only enabled for explicitly mentioned OSes. This fixes build errors for Windows on 32 bit ARM, about the cacheFlush function being unimplemented for that target. This keeps all other OSes enabled that are mentioned in conditionals for other architectures, except for windows. Change-Id: I8c29a9399a05a57d23b4fee506c3d04859a08a76 Reviewed-by: Ulf Hermann --- src/qml/jsruntime/qv4global_p.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4global_p.h b/src/qml/jsruntime/qv4global_p.h index 162fb66cba..58bffdf2d1 100644 --- a/src/qml/jsruntime/qv4global_p.h +++ b/src/qml/jsruntime/qv4global_p.h @@ -94,7 +94,8 @@ inline double trunc(double d) { return d > 0 ? floor(d) : ceil(d); } #elif defined(Q_PROCESSOR_X86_64) && (QT_POINTER_SIZE == 8) \ && (defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_QNX) || defined(Q_OS_MAC) || defined(Q_OS_FREEBSD)) # define V4_ENABLE_JIT -#elif defined(Q_PROCESSOR_ARM_32) && (QT_POINTER_SIZE == 4) +#elif defined(Q_PROCESSOR_ARM_32) && (QT_POINTER_SIZE == 4) \ + && (defined(Q_OS_LINUX) || defined(Q_OS_QNX) || defined(Q_OS_FREEBSD) || defined(Q_OS_INTEGRITY)) # if defined(thumb2) || defined(__thumb2__) || ((defined(__thumb) || defined(__thumb__)) && __TARGET_ARCH_THUMB-0 == 4) # define V4_ENABLE_JIT # elif defined(__ARM_ARCH_ISA_THUMB) && __ARM_ARCH_ISA_THUMB == 2 // clang 3.5 and later will set this if the core supports the Thumb-2 ISA. -- cgit v1.2.3 From 2bdbf216fe7b6cf9d404b80ac405bd3969f5c07f Mon Sep 17 00:00:00 2001 From: Rainer Keller Date: Wed, 17 Apr 2019 12:41:10 +0200 Subject: Improve error message Change-Id: I1f7f0781521757f5412b50680203698e33bd9d23 Reviewed-by: Ulf Hermann --- src/qml/compiler/qqmlpropertyvalidator.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/qml') diff --git a/src/qml/compiler/qqmlpropertyvalidator.cpp b/src/qml/compiler/qqmlpropertyvalidator.cpp index 1beaac8095..d20efe616b 100644 --- a/src/qml/compiler/qqmlpropertyvalidator.cpp +++ b/src/qml/compiler/qqmlpropertyvalidator.cpp @@ -692,7 +692,10 @@ QQmlCompileError QQmlPropertyValidator::validateObjectBinding(QQmlPropertyData * } else if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject && property->isFunction()) { return noError; } else if (QQmlValueTypeFactory::isValueType(property->propType())) { - return QQmlCompileError(binding->location, tr("Unexpected object assignment for property \"%1\"").arg(propertyName)); + auto typeName = QMetaType::typeName(property->propType()); + return QQmlCompileError(binding->location, tr("Can not assign value of type \"%1\" to property \"%2\", expecting an object") + .arg(typeName ? QString::fromLatin1(typeName) : QString::fromLatin1("")) + .arg(propertyName)); } else if (property->propType() == qMetaTypeId()) { return QQmlCompileError(binding->valueLocation, tr("Invalid property assignment: script expected")); } else { -- cgit v1.2.3 From 2b91e2bca332ecdfecc69b8942085afa162b2bc4 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Tue, 16 Apr 2019 10:19:25 +0200 Subject: Increment the private API version The removal of QQmlV4Handle causes incompatible changes in private API that other modules use. Change-Id: I4f983e83a570c195c190f6d8c968f59f6adce21b Reviewed-by: Erik Verbruggen --- src/qml/qtqmlglobal_p.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/qml') diff --git a/src/qml/qtqmlglobal_p.h b/src/qml/qtqmlglobal_p.h index 60988d12e6..46f0e3f409 100644 --- a/src/qml/qtqmlglobal_p.h +++ b/src/qml/qtqmlglobal_p.h @@ -57,7 +57,7 @@ # include #endif -#define Q_QML_PRIVATE_API_VERSION 3 +#define Q_QML_PRIVATE_API_VERSION 4 #define Q_QML_PRIVATE_EXPORT Q_QML_EXPORT -- cgit v1.2.3 From f8e90c61c5da4ca5e0aec24d42b2962877289ab2 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Thu, 11 Apr 2019 12:17:21 +0200 Subject: JIT: Avoid QString::sprintf() It's deprecated Change-Id: Id901056e3a4ca378fb03486cd941e7e7222ffbc4 Reviewed-by: Simon Hausmann --- src/qml/jit/qv4ir.cpp | 4 ++-- src/qml/jit/qv4operation.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/qml') diff --git a/src/qml/jit/qv4ir.cpp b/src/qml/jit/qv4ir.cpp index 0b82330394..cb3eeeec60 100644 --- a/src/qml/jit/qv4ir.cpp +++ b/src/qml/jit/qv4ir.cpp @@ -78,7 +78,7 @@ QString Function::name() const if (auto n = v4Function()->name()) name = n->toQString(); if (name.isEmpty()) - name.sprintf("%p", static_cast(v4Function())); + name = QString::asprintf("%p", v4Function()); auto loc = v4Function()->sourceLocation(); return name + QStringLiteral(" (%1:%2:%3)").arg(loc.sourceFile, QString::number(loc.line), QString::number(loc.column)); @@ -159,7 +159,7 @@ QByteArray Dumper::dump(const Function *f) name = n->toQString(); fo[QLatin1String("_searchKey")] = QStringLiteral("function %1").arg(name); if (name.isEmpty()) - name.sprintf("%p", static_cast(f->v4Function())); + name = QString::asprintf("%p", f->v4Function()); fo[QLatin1String("name")] = name; } diff --git a/src/qml/jit/qv4operation.cpp b/src/qml/jit/qv4operation.cpp index d10f8f19ac..acd5328fd0 100644 --- a/src/qml/jit/qv4operation.cpp +++ b/src/qml/jit/qv4operation.cpp @@ -417,7 +417,7 @@ QString ConstantPayload::debugString() const QString ConstantPayload::debugString(QV4::Value v) { if (v.isManaged()) - return QString().sprintf("Ptr: %p", v.heapObject()); + return QString::asprintf("Ptr: %p", v.heapObject()); if (v.isEmpty()) return QStringLiteral("empty"); return v.toQStringNoThrow(); -- cgit v1.2.3 From fe8962aa455c7d2a094b0d8bf29e9662a3448719 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 17 Apr 2019 15:29:18 +0200 Subject: Drop some dead QT_CONFIG conditions The file is not compiled at all if !qml-xml-http-request. Change-Id: If3d3d00f195e6bd8bd2af9cff247f31c918ab0c8 Reviewed-by: Erik Verbruggen --- src/qml/qml/qqmlxmlhttprequest.cpp | 4 ---- 1 file changed, 4 deletions(-) (limited to 'src/qml') diff --git a/src/qml/qml/qqmlxmlhttprequest.cpp b/src/qml/qml/qqmlxmlhttprequest.cpp index 1758158759..c6b7f2ab3f 100644 --- a/src/qml/qml/qqmlxmlhttprequest.cpp +++ b/src/qml/qml/qqmlxmlhttprequest.cpp @@ -68,8 +68,6 @@ using namespace QV4; -#if QT_CONFIG(xmlstreamreader) && QT_CONFIG(qml_network) - #define V4THROW_REFERENCE(string) \ do { \ ScopedObject error(scope, scope.engine->newReferenceErrorObject(QStringLiteral(string))); \ @@ -2065,6 +2063,4 @@ void *qt_add_qmlxmlhttprequest(ExecutionEngine *v4) QT_END_NAMESPACE -#endif // xmlstreamreader && qml_network - #include -- cgit v1.2.3 From 43573c8df170c5662b350703a06537c457c2d2fd Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 3 Apr 2019 15:54:27 +0200 Subject: Clean up type registration The QML-specific types need to be registered only for "QtQml". Make sure we don't accidentally add new QML-specific types to QtQuick. We also don't want the base types to be registered under any random URI and version. Formally qmlRegisterBaseTypes() is a public function, but it _really_ should not be called by anyone. Finally, split the types into ones that should belong to QtQml.Models and ones that belong to QtQml proper. Add a plugin that handles QtQml itself rather than using the QQmlEngine ctor for this. [ChangeLog] The accidentally exported function qmlRegisterBaseTypes() was removed. Change-Id: I8bf9f8515e18b016750c721fe694d4cda076780b Reviewed-by: Erik Verbruggen --- src/qml/qml/qqml.h | 2 - src/qml/qml/qqmlengine.cpp | 103 +++++++++++++------------------------ src/qml/qml/qqmlengine_p.h | 7 +-- src/qml/types/qqmlmodelsmodule.cpp | 37 +++++++++++++ src/qml/types/qqmlmodelsmodule_p.h | 5 ++ 5 files changed, 81 insertions(+), 73 deletions(-) (limited to 'src/qml') diff --git a/src/qml/qml/qqml.h b/src/qml/qml/qqml.h index bf9330856d..3000f56601 100644 --- a/src/qml/qml/qqml.h +++ b/src/qml/qml/qqml.h @@ -606,8 +606,6 @@ QObject *qmlAttachedPropertiesObject(const QObject *obj, bool create = true) return qmlAttachedPropertiesObject(&idx, obj, &T::staticMetaObject, create); } -Q_QML_EXPORT void qmlRegisterBaseTypes(const char *uri, int versionMajor, int versionMinor); - inline int qmlRegisterSingletonType(const char *uri, int versionMajor, int versionMinor, const char *typeName, QJSValue (*callback)(QQmlEngine *, QJSEngine *)) { diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp index 20440d7f49..f070f16afd 100644 --- a/src/qml/qml/qqmlengine.cpp +++ b/src/qml/qml/qqmlengine.cpp @@ -86,19 +86,10 @@ #if QT_CONFIG(qml_animation) #include #endif -#if QT_CONFIG(qml_list_model) -#include -#endif #include -#include -#if QT_CONFIG(qml_delegate_model) -#include -#endif -#include #if QT_CONFIG(qml_worker_script) #include #endif -#include #include #ifdef Q_OS_WIN // for %APPDATA% @@ -116,13 +107,6 @@ Q_DECLARE_METATYPE(QQmlProperty) QT_BEGIN_NAMESPACE -void qmlRegisterBaseTypes(const char *uri, int versionMajor, int versionMinor) -{ - QQmlEnginePrivate::registerBaseTypes(uri, versionMajor, versionMinor); - QQmlEnginePrivate::registerQtQuick2Types(uri, versionMajor, versionMinor); - QQmlValueTypeFactory::registerValueTypes(uri, versionMajor, versionMinor); -} - // Declared in qqml.h int qmlRegisterUncreatableMetaObject(const QMetaObject &staticMetaObject, const char *uri, int versionMajor, @@ -215,63 +199,54 @@ int qmlRegisterUncreatableMetaObject(const QMetaObject &staticMetaObject, bool QQmlEnginePrivate::qml_debugging_enabled = false; bool QQmlEnginePrivate::s_designerMode = false; -// these types are part of the QML language -void QQmlEnginePrivate::registerBaseTypes(const char *uri, int versionMajor, int versionMinor) +void QQmlEnginePrivate::defineModule() { - qmlRegisterType(uri,versionMajor,versionMinor,"Component"); - qmlRegisterType(uri,versionMajor,versionMinor,"QtObject"); - qmlRegisterType(uri, versionMajor, versionMinor,"Binding"); - qmlRegisterType(uri, versionMajor, (versionMinor < 8 ? 8 : versionMinor), "Binding"); //Only available in >=2.8 - qmlRegisterCustomType(uri, versionMajor, 0, "Connections", new QQmlConnectionsParser); - if (!strcmp(uri, "QtQuick")) - qmlRegisterCustomType(uri, versionMajor, 7, "Connections", new QQmlConnectionsParser); //Only available in QtQuick >=2.7 - else - qmlRegisterCustomType(uri, versionMajor, 3, "Connections", new QQmlConnectionsParser); //Only available in QtQml >=2.3 + const char uri[] = "QtQml"; + + qmlRegisterType(uri, 2, 0, "Component"); + qmlRegisterType(uri, 2, 0, "QtObject"); + qmlRegisterType(uri, 2, 0, "Binding"); + qmlRegisterType(uri, 2, 8, "Binding"); // Only available in >= 2.8 + qmlRegisterCustomType(uri, 2, 0, "Connections", new QQmlConnectionsParser); + qmlRegisterCustomType(uri, 2, 3, "Connections", new QQmlConnectionsParser); // Only available in QtQml >= 2.3 #if QT_CONFIG(qml_animation) - qmlRegisterType(uri, versionMajor, versionMinor,"Timer"); + qmlRegisterType(uri, 2, 0, "Timer"); #endif - qmlRegisterType(uri, versionMajor, (versionMinor < 1 ? 1 : versionMinor), "Instantiator"); //Only available in >=2.1 - qmlRegisterType(); - - qmlRegisterType(uri, versionMajor, 8, "LoggingCategory"); //Only available in >=2.8 - qmlRegisterType(uri, versionMajor, 12, "LoggingCategory"); //Only available in >=2.12 -} + qmlRegisterType(uri, 2, 8, "LoggingCategory"); // Only available in >= 2.8 + qmlRegisterType(uri, 2, 12, "LoggingCategory"); // Only available in >= 2.12 -// These QtQuick types' implementation resides in the QtQml module -void QQmlEnginePrivate::registerQtQuick2Types(const char *uri, int versionMajor, int versionMinor) -{ -#if QT_CONFIG(qml_list_model) - qmlRegisterType(uri, versionMajor, versionMinor, "ListElement"); // Now in QtQml.Models, here for compatibility - qmlRegisterCustomType(uri, versionMajor, versionMinor, "ListModel", new QQmlListModelParser); // Now in QtQml.Models, here for compatibility -#endif -#if QT_CONFIG(qml_worker_script) - qmlRegisterType(uri, versionMajor, versionMinor, "WorkerScript"); -#endif - qmlRegisterType(uri, versionMajor, versionMinor, "Package"); -#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) -#if QT_CONFIG(qml_delegate_model) - qmlRegisterType(uri, versionMajor, versionMinor, "VisualDataModel"); - qmlRegisterType(uri, versionMajor, versionMinor, "VisualDataGroup"); +#if QT_CONFIG(qml_locale) + qmlRegisterUncreatableType(uri, 2, 2, "Locale", QQmlEngine::tr("Locale cannot be instantiated. Use Qt.locale()")); #endif - qmlRegisterType(uri, versionMajor, versionMinor, "VisualItemModel"); -#endif // < Qt 6 } -void QQmlEnginePrivate::defineQtQuick2Module() +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +void QQmlEnginePrivate::registerQuickTypes() { - // register the base types into the QtQuick namespace - registerBaseTypes("QtQuick",2,0); + // Don't add anything here. These are only for backwards compatibility. + + const char uri[] = "QtQuick"; - // register the QtQuick2 types which are implemented in the QtQml module. - registerQtQuick2Types("QtQuick",2,0); + qmlRegisterType(uri, 2, 0, "Component"); + qmlRegisterType(uri, 2, 0, "QtObject"); + qmlRegisterType(uri, 2, 0, "Binding"); + qmlRegisterType(uri, 2, 8, "Binding"); + qmlRegisterCustomType(uri, 2, 0, "Connections", new QQmlConnectionsParser); + qmlRegisterCustomType(uri, 2, 7, "Connections", new QQmlConnectionsParser); +#if QT_CONFIG(qml_animation) + qmlRegisterType(uri, 2, 0,"Timer"); +#endif + qmlRegisterType(uri, 2, 8, "LoggingCategory"); + qmlRegisterType(uri, 2, 12, "LoggingCategory"); +#if QT_CONFIG(qml_worker_script) + qmlRegisterType(uri, 2, 0, "WorkerScript"); +#endif #if QT_CONFIG(qml_locale) - qmlRegisterUncreatableType("QtQuick", 2, 0, "Locale", QQmlEngine::tr("Locale cannot be instantiated. Use Qt.locale()")); + qmlRegisterUncreatableType(uri, 2, 0, "Locale", QQmlEngine::tr("Locale cannot be instantiated. Use Qt.locale()")); #endif - - // Auto-increment the import to stay in sync with ALL future QtQuick minor versions from 5.11 onward - qmlRegisterModule("QtQuick", 2, QT_VERSION_MINOR); } +#endif // QT_VERSION < QT_VERSION_CHECK(6, 0, 0) bool QQmlEnginePrivate::designerMode() { @@ -976,14 +951,6 @@ void QQmlEnginePrivate::init() if (baseModulesUninitialized) { qmlRegisterType("QML", 1, 0, "Component"); // required for the Compiler. - registerBaseTypes("QtQml", 2, 0); // import which provides language building blocks. -#if QT_CONFIG(qml_locale) - qmlRegisterUncreatableType("QtQml", 2, 2, "Locale", QQmlEngine::tr("Locale cannot be instantiated. Use Qt.locale()")); -#endif - - // Auto-increment the import to stay in sync with ALL future QtQml minor versions from 5.11 onward - qmlRegisterModule("QtQml", 2, QT_VERSION_MINOR); - QQmlData::init(); baseModulesUninitialized = false; } diff --git a/src/qml/qml/qqmlengine_p.h b/src/qml/qml/qqmlengine_p.h index 20d451d607..dab4e54cd6 100644 --- a/src/qml/qml/qqmlengine_p.h +++ b/src/qml/qml/qqmlengine_p.h @@ -249,9 +249,10 @@ public: static QList qmlErrorFromDiagnostics(const QString &fileName, const QList &diagnosticMessages); - static void registerBaseTypes(const char *uri, int versionMajor, int versionMinor); - static void registerQtQuick2Types(const char *uri, int versionMajor, int versionMinor); - static void defineQtQuick2Module(); + static void defineModule(); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + static void registerQuickTypes(); +#endif static bool designerMode(); static void activateDesignerMode(); diff --git a/src/qml/types/qqmlmodelsmodule.cpp b/src/qml/types/qqmlmodelsmodule.cpp index 5423054934..840b435d18 100644 --- a/src/qml/types/qqmlmodelsmodule.cpp +++ b/src/qml/types/qqmlmodelsmodule.cpp @@ -49,9 +49,42 @@ #include #include #include +#include +#include QT_BEGIN_NAMESPACE +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + +void QQmlModelsModule::registerQmlTypes() +{ + // Don't add anything here. These are only for backwards compatibility. + qmlRegisterType("QtQml", 2, 1, "Instantiator"); // Only available in >= 2.1 + qmlRegisterType(); +} + +void QQmlModelsModule::registerQuickTypes() +{ + // Don't add anything here. These are only for backwards compatibility. + + const char uri[] = "QtQuick"; + + qmlRegisterType(uri, 2, 1, "Instantiator"); + qmlRegisterType(); +#if QT_CONFIG(qml_list_model) + qmlRegisterType(uri, 2, 0, "ListElement"); + qmlRegisterCustomType(uri, 2, 0, "ListModel", new QQmlListModelParser); +#endif + qmlRegisterType(uri, 2, 0, "Package"); +#if QT_CONFIG(qml_delegate_model) + qmlRegisterType(uri, 2, 0, "VisualDataModel"); + qmlRegisterType(uri, 2, 0, "VisualDataGroup"); +#endif + qmlRegisterType(uri, 2, 0, "VisualItemModel"); +} + +#endif // QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + void QQmlModelsModule::defineModule() { const char uri[] = "QtQml.Models"; @@ -68,6 +101,10 @@ void QQmlModelsModule::defineModule() qmlRegisterType(uri, 2, 3, "ObjectModel"); qmlRegisterType(uri, 2, 2, "ItemSelectionModel"); + + qmlRegisterType(uri, 2, 14, "Package"); + qmlRegisterType(uri, 2, 14, "Instantiator"); + qmlRegisterType(); } void QQmlModelsModule::defineLabsModule() diff --git a/src/qml/types/qqmlmodelsmodule_p.h b/src/qml/types/qqmlmodelsmodule_p.h index 939ecc1500..2bb04f1e11 100644 --- a/src/qml/types/qqmlmodelsmodule_p.h +++ b/src/qml/types/qqmlmodelsmodule_p.h @@ -58,6 +58,11 @@ QT_BEGIN_NAMESPACE class Q_QML_PRIVATE_EXPORT QQmlModelsModule { public: +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + static void registerQmlTypes(); + static void registerQuickTypes(); +#endif + static void defineModule(); static void defineLabsModule(); }; -- cgit v1.2.3 From 8aea79d9087805e14d1d1f13bd60a561abbe9f23 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Thu, 7 Feb 2019 11:16:35 +0100 Subject: Move propertyCache-by-minor-version into QQmlMetaTypeData Ths data structure should only be accessed when protected by the metatype data lock. In fact we don't access it from anywhere else. To make that more obvious, move it to the right place. This allows us to eliminate some const_cast and poking around in QQmlTypePrivate from the outside. Change-Id: I16ffd240b9504b9c00010bdb2d17b05c8196fe8a Reviewed-by: Erik Verbruggen --- src/qml/qml/qqmlmetatype.cpp | 3 +++ src/qml/qml/qqmlmetatypedata.cpp | 31 ++++++++++++++++++++++++++----- src/qml/qml/qqmlmetatypedata_p.h | 6 ++++++ src/qml/qml/qqmlpropertycache_p.h | 2 -- src/qml/qml/qqmltype.cpp | 19 ------------------- src/qml/qml/qqmltype_p.h | 1 - src/qml/qml/qqmltype_p_p.h | 11 ----------- 7 files changed, 35 insertions(+), 38 deletions(-) (limited to 'src/qml') diff --git a/src/qml/qml/qqmlmetatype.cpp b/src/qml/qml/qqmlmetatype.cpp index 0da96f61e4..32f281b4f2 100644 --- a/src/qml/qml/qqmlmetatype.cpp +++ b/src/qml/qml/qqmlmetatype.cpp @@ -278,6 +278,7 @@ void QQmlMetaType::clearTypeRegistrations() data->idToType.clear(); data->nameToType.clear(); data->urlToType.clear(); + data->typePropertyCaches.clear(); data->urlToNonFileImportType.clear(); data->metaObjectToType.clear(); data->uriToModule.clear(); @@ -1206,6 +1207,7 @@ void QQmlMetaType::unregisterType(int typeIndex) removeQQmlTypePrivate(data->metaObjectToType, d); for (auto & module : data->uriToModule) module->remove(d); + data->clearPropertyCachesForMinorVersion(typeIndex); data->types[typeIndex] = QQmlType(); } } @@ -1232,6 +1234,7 @@ void QQmlMetaType::freeUnusedTypesAndCaches() for (auto &module : data->uriToModule) module->remove(d); + data->clearPropertyCachesForMinorVersion(d->index); *it = QQmlType(); } else { ++it; diff --git a/src/qml/qml/qqmlmetatypedata.cpp b/src/qml/qml/qqmlmetatypedata.cpp index 13c46e4911..5dc0083f54 100644 --- a/src/qml/qml/qqmlmetatypedata.cpp +++ b/src/qml/qml/qqmlmetatypedata.cpp @@ -77,6 +77,27 @@ void QQmlMetaTypeData::registerType(QQmlTypePrivate *priv) priv->release(); } +QQmlPropertyCache *QQmlMetaTypeData::propertyCacheForMinorVersion(int index, int minorVersion) const +{ + return (index < typePropertyCaches.length()) + ? typePropertyCaches.at(index).value(minorVersion).data() + : nullptr; +} + +void QQmlMetaTypeData::setPropertyCacheForMinorVersion(int index, int minorVersion, + QQmlPropertyCache *cache) +{ + if (index >= typePropertyCaches.length()) + typePropertyCaches.resize(index + 1); + typePropertyCaches[index][minorVersion] = cache; +} + +void QQmlMetaTypeData::clearPropertyCachesForMinorVersion(int index) +{ + if (index < typePropertyCaches.length()) + typePropertyCaches[index].clear(); +} + QQmlPropertyCache *QQmlMetaTypeData::propertyCache(const QMetaObject *metaObject, int minorVersion) { if (QQmlPropertyCache *rv = propertyCaches.value(metaObject)) @@ -97,7 +118,7 @@ QQmlPropertyCache *QQmlMetaTypeData::propertyCache(const QQmlType &type, int min { Q_ASSERT(type.isValid()); - if (QQmlPropertyCache *pc = type.key()->propertyCacheForMinorVersion(minorVersion)) + if (QQmlPropertyCache *pc = propertyCacheForMinorVersion(type.index(), minorVersion)) return pc; QVector types; @@ -118,8 +139,8 @@ QQmlPropertyCache *QQmlMetaTypeData::propertyCache(const QQmlType &type, int min metaObject = metaObject->superClass(); } - if (QQmlPropertyCache *pc = type.key()->propertyCacheForMinorVersion(maxMinorVersion)) { - const_cast(type.key())->setPropertyCacheForMinorVersion(minorVersion, pc); + if (QQmlPropertyCache *pc = propertyCacheForMinorVersion(type.index(), maxMinorVersion)) { + setPropertyCacheForMinorVersion(type.index(), minorVersion, pc); return pc; } @@ -190,13 +211,13 @@ QQmlPropertyCache *QQmlMetaTypeData::propertyCache(const QQmlType &type, int min } #endif - const_cast(type.key())->setPropertyCacheForMinorVersion(minorVersion, raw); + setPropertyCacheForMinorVersion(type.index(), minorVersion, raw); if (hasCopied) raw->release(); if (minorVersion != maxMinorVersion) - const_cast(type.key())->setPropertyCacheForMinorVersion(maxMinorVersion, raw); + setPropertyCacheForMinorVersion(type.index(), maxMinorVersion, raw); return raw; } diff --git a/src/qml/qml/qqmlmetatypedata_p.h b/src/qml/qml/qqmlmetatypedata_p.h index 2c5a32be1b..c45bc16280 100644 --- a/src/qml/qml/qqmlmetatypedata_p.h +++ b/src/qml/qml/qqmlmetatypedata_p.h @@ -83,6 +83,7 @@ struct QQmlMetaTypeData MetaObjects metaObjectToType; typedef QHash StringConverters; StringConverters stringConverters; + QVector>> typePropertyCaches; struct VersionedUri { VersionedUri() @@ -112,6 +113,11 @@ struct QQmlMetaTypeData QHash qmlLists; QHash propertyCaches; + + QQmlPropertyCache *propertyCacheForMinorVersion(int index, int minorVersion) const; + void setPropertyCacheForMinorVersion(int index, int minorVersion, QQmlPropertyCache *cache); + void clearPropertyCachesForMinorVersion(int index); + QQmlPropertyCache *propertyCache(const QMetaObject *metaObject, int minorVersion); QQmlPropertyCache *propertyCache(const QQmlType &type, int minorVersion); diff --git a/src/qml/qml/qqmlpropertycache_p.h b/src/qml/qml/qqmlpropertycache_p.h index 4f47e5d351..d74414bf67 100644 --- a/src/qml/qml/qqmlpropertycache_p.h +++ b/src/qml/qml/qqmlpropertycache_p.h @@ -243,8 +243,6 @@ private: QByteArray _checksum; }; -typedef QQmlRefPointer QQmlPropertyCachePtr; - inline QQmlPropertyData *QQmlPropertyCache::ensureResolved(QQmlPropertyData *p) const { if (p && Q_UNLIKELY(p->notFullyResolved())) diff --git a/src/qml/qml/qqmltype.cpp b/src/qml/qml/qqmltype.cpp index 5caeb8c1b0..88eedec061 100644 --- a/src/qml/qml/qqmltype.cpp +++ b/src/qml/qml/qqmltype.cpp @@ -509,25 +509,6 @@ void QQmlTypePrivate::setName(const QString &uri, const QString &element) name = uri.isEmpty() ? element : (uri + QLatin1Char('/') + element); } -QQmlPropertyCache *QQmlTypePrivate::propertyCacheForMinorVersion(int minorVersion) const -{ - for (int i = 0; i < propertyCaches.count(); ++i) - if (propertyCaches.at(i).minorVersion == minorVersion) - return propertyCaches.at(i).cache.data(); - return nullptr; -} - -void QQmlTypePrivate::setPropertyCacheForMinorVersion(int minorVersion, QQmlPropertyCache *cache) -{ - for (int i = 0; i < propertyCaches.count(); ++i) { - if (propertyCaches.at(i).minorVersion == minorVersion) { - propertyCaches[i].cache = cache; - return; - } - } - propertyCaches.append(PropertyCacheByMinorVersion(cache, minorVersion)); -} - QByteArray QQmlType::typeName() const { if (d) { diff --git a/src/qml/qml/qqmltype_p.h b/src/qml/qml/qqmltype_p.h index 1d1345a0cb..0e59b1be06 100644 --- a/src/qml/qml/qqmltype_p.h +++ b/src/qml/qml/qqmltype_p.h @@ -89,7 +89,6 @@ public: } bool isValid() const { return !d.isNull(); } - const QQmlTypePrivate *key() const { return d.data(); } QByteArray typeName() const; QString qmlTypeName() const; diff --git a/src/qml/qml/qqmltype_p_p.h b/src/qml/qml/qqmltype_p_p.h index dd6e046129..b317aff740 100644 --- a/src/qml/qml/qqmltype_p_p.h +++ b/src/qml/qml/qqmltype_p_p.h @@ -128,17 +128,6 @@ public: mutable QStringHash scopedEnumIndex; // maps from enum name to index in scopedEnums mutable QList*> scopedEnums; - struct PropertyCacheByMinorVersion - { - PropertyCacheByMinorVersion() : cache(nullptr), minorVersion(-1) {} - explicit PropertyCacheByMinorVersion(QQmlPropertyCache *pc, int ver) : cache(pc), minorVersion(ver) {} - QQmlPropertyCachePtr cache; - int minorVersion; - }; - QVector propertyCaches; - QQmlPropertyCache *propertyCacheForMinorVersion(int minorVersion) const; - void setPropertyCacheForMinorVersion(int minorVersion, QQmlPropertyCache *cache); - void setName(const QString &uri, const QString &element); private: -- cgit v1.2.3 From b3249c1dd519fc352b9fc10ec5c874c4afc8e0cd Mon Sep 17 00:00:00 2001 From: Lars Knoll Date: Tue, 2 Apr 2019 14:29:39 +0200 Subject: Dissolve QQmlPropertyRawData There is no reason anymore to split the class in two parts. Change-Id: Iabef7acec1db7afc0ed4e89e1fd5b78699dc0847 Reviewed-by: Erik Verbruggen --- src/qml/compiler/qqmlpropertycachecreator_p.h | 2 +- src/qml/qml/qml.pri | 1 - src/qml/qml/qqmlpropertycache.cpp | 52 ++-- src/qml/qml/qqmlpropertycache_p.h | 22 +- src/qml/qml/qqmlpropertydata_p.h | 293 ++++++++++++++++++++-- src/qml/qml/qqmlpropertyrawdata_p.h | 339 -------------------------- 6 files changed, 308 insertions(+), 401 deletions(-) delete mode 100644 src/qml/qml/qqmlpropertyrawdata_p.h (limited to 'src/qml') diff --git a/src/qml/compiler/qqmlpropertycachecreator_p.h b/src/qml/compiler/qqmlpropertycachecreator_p.h index a1e93702d1..21d653af55 100644 --- a/src/qml/compiler/qqmlpropertycachecreator_p.h +++ b/src/qml/compiler/qqmlpropertycachecreator_p.h @@ -578,7 +578,7 @@ public: private: void appendAliasPropertiesInMetaObjectsWithinComponent(const CompiledObject &component, int firstObjectIndex); - QQmlCompileError propertyDataForAlias(const CompiledObject &component, const QV4::CompiledData::Alias &alias, int *type, int *rev, QQmlPropertyRawData::Flags *propertyFlags); + QQmlCompileError propertyDataForAlias(const CompiledObject &component, const QV4::CompiledData::Alias &alias, int *type, int *rev, QQmlPropertyData::Flags *propertyFlags); void collectObjectsWithAliasesRecursively(int objectIndex, QVector *objectsWithAliases) const; diff --git a/src/qml/qml/qml.pri b/src/qml/qml/qml.pri index 0895e5ae68..9f79bfacdf 100644 --- a/src/qml/qml/qml.pri +++ b/src/qml/qml/qml.pri @@ -102,7 +102,6 @@ HEADERS += \ $$PWD/qqmlpropertycachevector_p.h \ $$PWD/qqmlpropertydata_p.h \ $$PWD/qqmlpropertyindex_p.h \ - $$PWD/qqmlpropertyrawdata_p.h \ $$PWD/qqmlmetaobject_p.h \ $$PWD/qqmlnotifier_p.h \ $$PWD/qqmlobjectorgadget_p.h \ diff --git a/src/qml/qml/qqmlpropertycache.cpp b/src/qml/qml/qqmlpropertycache.cpp index 678b78ed17..fb8136abb4 100644 --- a/src/qml/qml/qqmlpropertycache.cpp +++ b/src/qml/qml/qqmlpropertycache.cpp @@ -136,12 +136,12 @@ void QQmlPropertyData::lazyLoad(const QMetaProperty &p) int type = static_cast(p.type()); if (type == QMetaType::QObjectStar) { setPropType(type); - _flags.type = Flags::QObjectDerivedType; + m_flags.type = Flags::QObjectDerivedType; } else if (type == QMetaType::QVariant) { setPropType(type); - _flags.type = Flags::QVariantType; + m_flags.type = Flags::QVariantType; } else if (type == QVariant::UserType || type == -1) { - _flags.notFullyResolved = true; + m_flags.notFullyResolved = true; } else { setPropType(type); } @@ -153,7 +153,7 @@ void QQmlPropertyData::load(const QMetaProperty &p) setCoreIndex(p.propertyIndex()); setNotifyIndex(QMetaObjectPrivate::signalIndex(p.notifySignal())); setFlags(fastFlagsForProperty(p)); - flagsForPropertyType(propType(), _flags); + flagsForPropertyType(propType(), m_flags); Q_ASSERT(p.revision() <= Q_INT16_MAX); setRevision(p.revision()); } @@ -165,23 +165,23 @@ void QQmlPropertyData::load(const QMetaMethod &m) setPropType(m.returnType()); - _flags.type = Flags::FunctionType; + m_flags.type = Flags::FunctionType; if (m.methodType() == QMetaMethod::Signal) - _flags.isSignal = true; + m_flags.isSignal = true; else if (m.methodType() == QMetaMethod::Constructor) { - _flags.isConstructor = true; + m_flags.isConstructor = true; setPropType(QMetaType::QObjectStar); } if (m.parameterCount()) { - _flags.hasArguments = true; + m_flags.hasArguments = true; if ((m.parameterCount() == 1) && (m.parameterTypes().constFirst() == "QQmlV4Function*")) { - _flags.isV4Function = true; + m_flags.isV4Function = true; } } if (m.attributes() & QMetaMethod::Cloned) - _flags.isCloned = true; + m_flags.isCloned = true; Q_ASSERT(m.revision() <= Q_INT16_MAX); setRevision(m.revision()); @@ -192,11 +192,11 @@ void QQmlPropertyData::lazyLoad(const QMetaMethod &m) setCoreIndex(m.methodIndex()); setPropType(QMetaType::Void); setArguments(nullptr); - _flags.type = Flags::FunctionType; + m_flags.type = Flags::FunctionType; if (m.methodType() == QMetaMethod::Signal) - _flags.isSignal = true; + m_flags.isSignal = true; else if (m.methodType() == QMetaMethod::Constructor) { - _flags.isConstructor = true; + m_flags.isConstructor = true; setPropType(QMetaType::QObjectStar); } @@ -204,19 +204,19 @@ void QQmlPropertyData::lazyLoad(const QMetaMethod &m) if (!returnType) returnType = "\0"; if ((*returnType != 'v') || (qstrcmp(returnType+1, "oid") != 0)) { - _flags.notFullyResolved = true; + m_flags.notFullyResolved = true; } const int paramCount = m.parameterCount(); if (paramCount) { - _flags.hasArguments = true; + m_flags.hasArguments = true; if ((paramCount == 1) && (m.parameterTypes().constFirst() == "QQmlV4Function*")) { - _flags.isV4Function = true; + m_flags.isV4Function = true; } } if (m.attributes() & QMetaMethod::Cloned) - _flags.isCloned = true; + m_flags.isCloned = true; Q_ASSERT(m.revision() <= Q_INT16_MAX); setRevision(m.revision()); @@ -344,7 +344,7 @@ void QQmlPropertyCache::appendSignal(const QString &name, QQmlPropertyData::Flag data.setArguments(nullptr); QQmlPropertyData handler = data; - handler._flags.isSignalHandler = true; + handler.m_flags.isSignalHandler = true; if (types) { int argumentCount = *types; @@ -542,7 +542,7 @@ void QQmlPropertyCache::append(const QMetaObject *metaObject, data->setFlags(methodFlags); data->lazyLoad(m); - data->_flags.isDirect = !dynamicMetaObject; + data->m_flags.isDirect = !dynamicMetaObject; Q_ASSERT((allowedRevisionCache.count() - 1) < Q_INT16_MAX); data->setMetaObjectOffset(allowedRevisionCache.count() - 1); @@ -550,7 +550,7 @@ void QQmlPropertyCache::append(const QMetaObject *metaObject, if (data->isSignal()) { sigdata = &signalHandlerIndexCache[signalHandlerIndex - signalHandlerIndexCacheStart]; *sigdata = *data; - sigdata->_flags.isSignalHandler = true; + sigdata->m_flags.isSignalHandler = true; } QQmlPropertyData *old = nullptr; @@ -592,7 +592,7 @@ void QQmlPropertyCache::append(const QMetaObject *metaObject, if (old) { // We only overload methods in the same class, exactly like C++ if (old->isFunction() && old->coreIndex() >= methodOffset) - data->_flags.isOverload = true; + data->m_flags.isOverload = true; data->markAsOverrideOf(old); } @@ -623,7 +623,7 @@ void QQmlPropertyCache::append(const QMetaObject *metaObject, data->lazyLoad(p); data->setTypeMinorVersion(typeMinorVersion); - data->_flags.isDirect = !dynamicMetaObject; + data->m_flags.isDirect = !dynamicMetaObject; Q_ASSERT((allowedRevisionCache.count() - 1) < Q_INT16_MAX); data->setMetaObjectOffset(allowedRevisionCache.count() - 1); @@ -649,7 +649,7 @@ void QQmlPropertyCache::append(const QMetaObject *metaObject, } if (isGadget) // always dispatch over a 'normal' meta-call so the QQmlValueType can intercept - data->_flags.isDirect = false; + data->m_flags.isDirect = false; else data->trySetStaticMetaCallFunction(metaObject->d.static_metacall, ii - propOffset); if (old) @@ -660,7 +660,7 @@ void QQmlPropertyCache::append(const QMetaObject *metaObject, void QQmlPropertyCache::resolve(QQmlPropertyData *data) const { Q_ASSERT(data->notFullyResolved()); - data->_flags.notFullyResolved = false; + data->m_flags.notFullyResolved = false; const QMetaObject *mo = firstCppMetaObject(); if (data->isFunction()) { @@ -695,7 +695,7 @@ void QQmlPropertyCache::resolve(QQmlPropertyData *data) const data->setPropType(registerResult == -1 ? QMetaType::UnknownType : registerResult); } } - flagsForPropertyType(data->propType(), data->_flags); + flagsForPropertyType(data->propType(), data->m_flags); } } @@ -872,7 +872,7 @@ void QQmlPropertyData::markAsOverrideOf(QQmlPropertyData *predecessor) setOverrideIndexIsProperty(!predecessor->isFunction()); setOverrideIndex(predecessor->coreIndex()); - predecessor->_flags.isOverridden = true; + predecessor->m_flags.isOverridden = true; } QQmlPropertyCacheMethodArguments *QQmlPropertyCache::createArgumentsObject(int argc, const QList &names) diff --git a/src/qml/qml/qqmlpropertycache_p.h b/src/qml/qml/qqmlpropertycache_p.h index d74414bf67..72692ee522 100644 --- a/src/qml/qml/qqmlpropertycache_p.h +++ b/src/qml/qml/qqmlpropertycache_p.h @@ -89,19 +89,19 @@ public: QQmlPropertyCache *copy(); QQmlPropertyCache *copyAndAppend(const QMetaObject *, - QQmlPropertyRawData::Flags propertyFlags = QQmlPropertyData::Flags(), - QQmlPropertyRawData::Flags methodFlags = QQmlPropertyData::Flags(), - QQmlPropertyRawData::Flags signalFlags = QQmlPropertyData::Flags()); + QQmlPropertyData::Flags propertyFlags = QQmlPropertyData::Flags(), + QQmlPropertyData::Flags methodFlags = QQmlPropertyData::Flags(), + QQmlPropertyData::Flags signalFlags = QQmlPropertyData::Flags()); QQmlPropertyCache *copyAndAppend(const QMetaObject *, int typeMinorVersion, - QQmlPropertyRawData::Flags propertyFlags = QQmlPropertyData::Flags(), - QQmlPropertyRawData::Flags methodFlags = QQmlPropertyData::Flags(), - QQmlPropertyRawData::Flags signalFlags = QQmlPropertyData::Flags()); + QQmlPropertyData::Flags propertyFlags = QQmlPropertyData::Flags(), + QQmlPropertyData::Flags methodFlags = QQmlPropertyData::Flags(), + QQmlPropertyData::Flags signalFlags = QQmlPropertyData::Flags()); QQmlPropertyCache *copyAndReserve(int propertyCount, int methodCount, int signalCount, int enumCount); - void appendProperty(const QString &, QQmlPropertyRawData::Flags flags, int coreIndex, + void appendProperty(const QString &, QQmlPropertyData::Flags flags, int coreIndex, int propType, int revision, int notifyIndex); - void appendSignal(const QString &, QQmlPropertyRawData::Flags, int coreIndex, + void appendSignal(const QString &, QQmlPropertyData::Flags, int coreIndex, const int *types = nullptr, const QList &names = QList()); void appendMethod(const QString &, QQmlPropertyData::Flags flags, int coreIndex, const QList &names = QList()); @@ -187,9 +187,9 @@ private: inline QQmlPropertyCache *copy(int reserve); void append(const QMetaObject *, int typeMinorVersion, - QQmlPropertyRawData::Flags propertyFlags = QQmlPropertyRawData::Flags(), - QQmlPropertyRawData::Flags methodFlags = QQmlPropertyData::Flags(), - QQmlPropertyRawData::Flags signalFlags = QQmlPropertyData::Flags()); + QQmlPropertyData::Flags propertyFlags = QQmlPropertyData::Flags(), + QQmlPropertyData::Flags methodFlags = QQmlPropertyData::Flags(), + QQmlPropertyData::Flags signalFlags = QQmlPropertyData::Flags()); QQmlPropertyCacheMethodArguments *createArgumentsObject(int count, const QList &names); diff --git a/src/qml/qml/qqmlpropertydata_p.h b/src/qml/qml/qqmlpropertydata_p.h index a292bcc769..dec696226e 100644 --- a/src/qml/qml/qqmlpropertydata_p.h +++ b/src/qml/qml/qqmlpropertydata_p.h @@ -51,11 +51,12 @@ // We mean it. // -#include +#include QT_BEGIN_NAMESPACE -class QQmlPropertyData : public QQmlPropertyRawData +class QQmlPropertyCacheMethodArguments; +class QQmlPropertyData { public: enum WriteFlag { @@ -65,10 +66,193 @@ public: }; Q_DECLARE_FLAGS(WriteFlags, WriteFlag) - inline QQmlPropertyData(); - inline QQmlPropertyData(const QQmlPropertyRawData &); + typedef QObjectPrivate::StaticMetaCallFunction StaticMetaCallFunction; - inline bool operator==(const QQmlPropertyRawData &); + struct Flags { + enum Types { + OtherType = 0, + FunctionType = 1, // Is an invokable + QObjectDerivedType = 2, // Property type is a QObject* derived type + EnumType = 3, // Property type is an enum + QListType = 4, // Property type is a QML list + QmlBindingType = 5, // Property type is a QQmlBinding* + QJSValueType = 6, // Property type is a QScriptValue + // Gap, used to be V4HandleType + VarPropertyType = 8, // Property type is a "var" property of VMEMO + QVariantType = 9 // Property is a QVariant + }; + + // The _otherBits (which "pad" the Flags struct to align it nicely) are used + // to store the relative property index. It will only get used when said index fits. See + // trySetStaticMetaCallFunction for details. + // (Note: this padding is done here, because certain compilers have surprising behavior + // when an enum is declared in-between two bit fields.) + enum { BitsLeftInFlags = 10 }; + unsigned otherBits : BitsLeftInFlags; // align to 32 bits + + // Can apply to all properties, except IsFunction + unsigned isConstant : 1; // Has CONST flag + unsigned isWritable : 1; // Has WRITE function + unsigned isResettable : 1; // Has RESET function + unsigned isAlias : 1; // Is a QML alias to another property + unsigned isFinal : 1; // Has FINAL flag + unsigned isOverridden : 1; // Is overridden by a extension property + unsigned isDirect : 1; // Exists on a C++ QMetaObject + + unsigned type : 4; // stores an entry of Types + + // Apply only to IsFunctions + unsigned isVMEFunction : 1; // Function was added by QML + unsigned hasArguments : 1; // Function takes arguments + unsigned isSignal : 1; // Function is a signal + unsigned isVMESignal : 1; // Signal was added by QML + unsigned isV4Function : 1; // Function takes QQmlV4Function* args + unsigned isSignalHandler : 1; // Function is a signal handler + unsigned isOverload : 1; // Function is an overload of another function + unsigned isCloned : 1; // The function was marked as cloned + unsigned isConstructor : 1; // The function was marked is a constructor + + // Internal QQmlPropertyCache flags + unsigned notFullyResolved : 1; // True if the type data is to be lazily resolved + unsigned overrideIndexIsProperty: 1; + + inline Flags(); + inline bool operator==(const Flags &other) const; + inline void copyPropertyTypeFlags(Flags from); + }; + + inline bool operator==(const QQmlPropertyData &) const; + + Flags flags() const { return m_flags; } + void setFlags(Flags f) + { + unsigned otherBits = m_flags.otherBits; + m_flags = f; + m_flags.otherBits = otherBits; + } + + bool isValid() const { return coreIndex() != -1; } + + bool isConstant() const { return m_flags.isConstant; } + bool isWritable() const { return m_flags.isWritable; } + void setWritable(bool onoff) { m_flags.isWritable = onoff; } + bool isResettable() const { return m_flags.isResettable; } + bool isAlias() const { return m_flags.isAlias; } + bool isFinal() const { return m_flags.isFinal; } + bool isOverridden() const { return m_flags.isOverridden; } + bool isDirect() const { return m_flags.isDirect; } + bool hasStaticMetaCallFunction() const { return staticMetaCallFunction() != nullptr; } + bool isFunction() const { return m_flags.type == Flags::FunctionType; } + bool isQObject() const { return m_flags.type == Flags::QObjectDerivedType; } + bool isEnum() const { return m_flags.type == Flags::EnumType; } + bool isQList() const { return m_flags.type == Flags::QListType; } + bool isQmlBinding() const { return m_flags.type == Flags::QmlBindingType; } + bool isQJSValue() const { return m_flags.type == Flags::QJSValueType; } + bool isVarProperty() const { return m_flags.type == Flags::VarPropertyType; } + bool isQVariant() const { return m_flags.type == Flags::QVariantType; } + bool isVMEFunction() const { return m_flags.isVMEFunction; } + bool hasArguments() const { return m_flags.hasArguments; } + bool isSignal() const { return m_flags.isSignal; } + bool isVMESignal() const { return m_flags.isVMESignal; } + bool isV4Function() const { return m_flags.isV4Function; } + bool isSignalHandler() const { return m_flags.isSignalHandler; } + bool isOverload() const { return m_flags.isOverload; } + void setOverload(bool onoff) { m_flags.isOverload = onoff; } + bool isCloned() const { return m_flags.isCloned; } + bool isConstructor() const { return m_flags.isConstructor; } + + bool hasOverride() const { return overrideIndex() >= 0; } + bool hasRevision() const { return revision() != 0; } + + bool isFullyResolved() const { return !m_flags.notFullyResolved; } + + int propType() const { Q_ASSERT(isFullyResolved()); return m_propType; } + void setPropType(int pt) + { + Q_ASSERT(pt >= 0); + Q_ASSERT(pt <= std::numeric_limits::max()); + m_propType = quint16(pt); + } + + int notifyIndex() const { return m_notifyIndex; } + void setNotifyIndex(int idx) + { + Q_ASSERT(idx >= std::numeric_limits::min()); + Q_ASSERT(idx <= std::numeric_limits::max()); + m_notifyIndex = qint16(idx); + } + + bool overrideIndexIsProperty() const { return m_flags.overrideIndexIsProperty; } + void setOverrideIndexIsProperty(bool onoff) { m_flags.overrideIndexIsProperty = onoff; } + + int overrideIndex() const { return m_overrideIndex; } + void setOverrideIndex(int idx) + { + Q_ASSERT(idx >= std::numeric_limits::min()); + Q_ASSERT(idx <= std::numeric_limits::max()); + m_overrideIndex = qint16(idx); + } + + int coreIndex() const { return m_coreIndex; } + void setCoreIndex(int idx) + { + Q_ASSERT(idx >= std::numeric_limits::min()); + Q_ASSERT(idx <= std::numeric_limits::max()); + m_coreIndex = qint16(idx); + } + + quint8 revision() const { return m_revision; } + void setRevision(quint8 rev) + { + Q_ASSERT(rev <= std::numeric_limits::max()); + m_revision = quint8(rev); + } + + /* If a property is a C++ type, then we store the minor + * version of this type. + * This is required to resolve property or signal revisions + * if this property is used as a grouped property. + * + * Test.qml + * property TextEdit someTextEdit: TextEdit {} + * + * Test { + * someTextEdit.preeditText: "test" //revision 7 + * someTextEdit.onEditingFinished: console.log("test") //revision 6 + * } + * + * To determine if these properties with revisions are available we need + * the minor version of TextEdit as imported in Test.qml. + * + */ + + quint8 typeMinorVersion() const { return m_typeMinorVersion; } + void setTypeMinorVersion(quint8 rev) + { + Q_ASSERT(rev <= std::numeric_limits::max()); + m_typeMinorVersion = quint8(rev); + } + + QQmlPropertyCacheMethodArguments *arguments() const { return m_arguments; } + void setArguments(QQmlPropertyCacheMethodArguments *args) { m_arguments = args; } + + int metaObjectOffset() const { return m_metaObjectOffset; } + void setMetaObjectOffset(int off) + { + Q_ASSERT(off >= std::numeric_limits::min()); + Q_ASSERT(off <= std::numeric_limits::max()); + m_metaObjectOffset = qint16(off); + } + + StaticMetaCallFunction staticMetaCallFunction() const { return m_staticMetaCallFunction; } + void trySetStaticMetaCallFunction(StaticMetaCallFunction f, unsigned relativePropertyIndex) + { + if (relativePropertyIndex < (1 << Flags::BitsLeftInFlags) - 1) { + m_flags.otherBits = relativePropertyIndex; + m_staticMetaCallFunction = f; + } + } + quint16 relativePropertyIndex() const { Q_ASSERT(hasStaticMetaCallFunction()); return m_flags.otherBits; } static Flags flagsForProperty(const QMetaProperty &); void load(const QMetaProperty &); @@ -128,27 +312,32 @@ private: friend class QQmlPropertyCache; void lazyLoad(const QMetaProperty &); void lazyLoad(const QMetaMethod &); - bool notFullyResolved() const { return _flags.notFullyResolved; } -}; + bool notFullyResolved() const { return m_flags.notFullyResolved; } -QQmlPropertyData::QQmlPropertyData() -{ - setCoreIndex(-1); - setPropType(0); - setNotifyIndex(-1); - setOverrideIndex(-1); - setRevision(0); - setMetaObjectOffset(-1); - setArguments(nullptr); - trySetStaticMetaCallFunction(nullptr, 0); -} + Flags m_flags; + qint16 m_coreIndex = -1; + quint16 m_propType = 0; -QQmlPropertyData::QQmlPropertyData(const QQmlPropertyRawData &d) -{ - *(static_cast(this)) = d; -} + // The notify index is in the range returned by QObjectPrivate::signalIndex(). + // This is different from QMetaMethod::methodIndex(). + qint16 m_notifyIndex = -1; + qint16 m_overrideIndex = -1; -bool QQmlPropertyData::operator==(const QQmlPropertyRawData &other) + quint8 m_revision = 0; + quint8 m_typeMinorVersion = 0; + qint16 m_metaObjectOffset = -1; + + QQmlPropertyCacheMethodArguments *m_arguments = nullptr; + StaticMetaCallFunction m_staticMetaCallFunction = nullptr; +}; + +#if QT_POINTER_SIZE == 4 + Q_STATIC_ASSERT(sizeof(QQmlPropertyData) == 24); +#else // QT_POINTER_SIZE == 8 + Q_STATIC_ASSERT(sizeof(QQmlPropertyData) == 32); +#endif + +bool QQmlPropertyData::operator==(const QQmlPropertyData &other) const { return flags() == other.flags() && propType() == other.propType() && @@ -157,6 +346,64 @@ bool QQmlPropertyData::operator==(const QQmlPropertyRawData &other) revision() == other.revision(); } +QQmlPropertyData::Flags::Flags() + : otherBits(0) + , isConstant(false) + , isWritable(false) + , isResettable(false) + , isAlias(false) + , isFinal(false) + , isOverridden(false) + , isDirect(false) + , type(OtherType) + , isVMEFunction(false) + , hasArguments(false) + , isSignal(false) + , isVMESignal(false) + , isV4Function(false) + , isSignalHandler(false) + , isOverload(false) + , isCloned(false) + , isConstructor(false) + , notFullyResolved(false) + , overrideIndexIsProperty(false) +{} + +bool QQmlPropertyData::Flags::operator==(const QQmlPropertyData::Flags &other) const +{ + return isConstant == other.isConstant && + isWritable == other.isWritable && + isResettable == other.isResettable && + isAlias == other.isAlias && + isFinal == other.isFinal && + isOverridden == other.isOverridden && + type == other.type && + isVMEFunction == other.isVMEFunction && + hasArguments == other.hasArguments && + isSignal == other.isSignal && + isVMESignal == other.isVMESignal && + isV4Function == other.isV4Function && + isSignalHandler == other.isSignalHandler && + isOverload == other.isOverload && + isCloned == other.isCloned && + isConstructor == other.isConstructor && + notFullyResolved == other.notFullyResolved && + overrideIndexIsProperty == other.overrideIndexIsProperty; +} + +void QQmlPropertyData::Flags::copyPropertyTypeFlags(QQmlPropertyData::Flags from) +{ + switch (from.type) { + case QObjectDerivedType: + case EnumType: + case QListType: + case QmlBindingType: + case QJSValueType: + case QVariantType: + type = from.type; + } +} + Q_DECLARE_OPERATORS_FOR_FLAGS(QQmlPropertyData::WriteFlags) QT_END_NAMESPACE diff --git a/src/qml/qml/qqmlpropertyrawdata_p.h b/src/qml/qml/qqmlpropertyrawdata_p.h deleted file mode 100644 index 31d67b0953..0000000000 --- a/src/qml/qml/qqmlpropertyrawdata_p.h +++ /dev/null @@ -1,339 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QQMLPROPERTYRAWDATA_P_H -#define QQMLPROPERTYRAWDATA_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include - -QT_BEGIN_NAMESPACE - -// We have this somewhat awful split between RawData and Data so that RawData can be -// used in unions. In normal code, you should always use Data which initializes RawData -// to an invalid state on construction. -// ### We should be able to remove this split nowadays -class QQmlPropertyCacheMethodArguments; -class QQmlPropertyRawData -{ -public: - typedef QObjectPrivate::StaticMetaCallFunction StaticMetaCallFunction; - - struct Flags { - enum Types { - OtherType = 0, - FunctionType = 1, // Is an invokable - QObjectDerivedType = 2, // Property type is a QObject* derived type - EnumType = 3, // Property type is an enum - QListType = 4, // Property type is a QML list - QmlBindingType = 5, // Property type is a QQmlBinding* - QJSValueType = 6, // Property type is a QScriptValue - // Gap, used to be V4HandleType - VarPropertyType = 8, // Property type is a "var" property of VMEMO - QVariantType = 9 // Property is a QVariant - }; - - // The _otherBits (which "pad" the Flags struct to align it nicely) are used - // to store the relative property index. It will only get used when said index fits. See - // trySetStaticMetaCallFunction for details. - // (Note: this padding is done here, because certain compilers have surprising behavior - // when an enum is declared in-between two bit fields.) - enum { BitsLeftInFlags = 10 }; - unsigned _otherBits : BitsLeftInFlags; // align to 32 bits - - // Can apply to all properties, except IsFunction - unsigned isConstant : 1; // Has CONST flag - unsigned isWritable : 1; // Has WRITE function - unsigned isResettable : 1; // Has RESET function - unsigned isAlias : 1; // Is a QML alias to another property - unsigned isFinal : 1; // Has FINAL flag - unsigned isOverridden : 1; // Is overridden by a extension property - unsigned isDirect : 1; // Exists on a C++ QMetaObject - - unsigned type : 4; // stores an entry of Types - - // Apply only to IsFunctions - unsigned isVMEFunction : 1; // Function was added by QML - unsigned hasArguments : 1; // Function takes arguments - unsigned isSignal : 1; // Function is a signal - unsigned isVMESignal : 1; // Signal was added by QML - unsigned isV4Function : 1; // Function takes QQmlV4Function* args - unsigned isSignalHandler : 1; // Function is a signal handler - unsigned isOverload : 1; // Function is an overload of another function - unsigned isCloned : 1; // The function was marked as cloned - unsigned isConstructor : 1; // The function was marked is a constructor - - // Internal QQmlPropertyCache flags - unsigned notFullyResolved : 1; // True if the type data is to be lazily resolved - unsigned overrideIndexIsProperty: 1; - - inline Flags(); - inline bool operator==(const Flags &other) const; - inline void copyPropertyTypeFlags(Flags from); - }; - - Flags flags() const { return _flags; } - void setFlags(Flags f) - { - unsigned otherBits = _flags._otherBits; - _flags = f; - _flags._otherBits = otherBits; - } - - bool isValid() const { return coreIndex() != -1; } - - bool isConstant() const { return _flags.isConstant; } - bool isWritable() const { return _flags.isWritable; } - void setWritable(bool onoff) { _flags.isWritable = onoff; } - bool isResettable() const { return _flags.isResettable; } - bool isAlias() const { return _flags.isAlias; } - bool isFinal() const { return _flags.isFinal; } - bool isOverridden() const { return _flags.isOverridden; } - bool isDirect() const { return _flags.isDirect; } - bool hasStaticMetaCallFunction() const { return staticMetaCallFunction() != nullptr; } - bool isFunction() const { return _flags.type == Flags::FunctionType; } - bool isQObject() const { return _flags.type == Flags::QObjectDerivedType; } - bool isEnum() const { return _flags.type == Flags::EnumType; } - bool isQList() const { return _flags.type == Flags::QListType; } - bool isQmlBinding() const { return _flags.type == Flags::QmlBindingType; } - bool isQJSValue() const { return _flags.type == Flags::QJSValueType; } - bool isVarProperty() const { return _flags.type == Flags::VarPropertyType; } - bool isQVariant() const { return _flags.type == Flags::QVariantType; } - bool isVMEFunction() const { return _flags.isVMEFunction; } - bool hasArguments() const { return _flags.hasArguments; } - bool isSignal() const { return _flags.isSignal; } - bool isVMESignal() const { return _flags.isVMESignal; } - bool isV4Function() const { return _flags.isV4Function; } - bool isSignalHandler() const { return _flags.isSignalHandler; } - bool isOverload() const { return _flags.isOverload; } - void setOverload(bool onoff) { _flags.isOverload = onoff; } - bool isCloned() const { return _flags.isCloned; } - bool isConstructor() const { return _flags.isConstructor; } - - bool hasOverride() const { return overrideIndex() >= 0; } - bool hasRevision() const { return revision() != 0; } - - bool isFullyResolved() const { return !_flags.notFullyResolved; } - - int propType() const { Q_ASSERT(isFullyResolved()); return _propType; } - void setPropType(int pt) - { - Q_ASSERT(pt >= 0); - Q_ASSERT(pt <= std::numeric_limits::max()); - _propType = quint16(pt); - } - - int notifyIndex() const { return _notifyIndex; } - void setNotifyIndex(int idx) - { - Q_ASSERT(idx >= std::numeric_limits::min()); - Q_ASSERT(idx <= std::numeric_limits::max()); - _notifyIndex = qint16(idx); - } - - bool overrideIndexIsProperty() const { return _flags.overrideIndexIsProperty; } - void setOverrideIndexIsProperty(bool onoff) { _flags.overrideIndexIsProperty = onoff; } - - int overrideIndex() const { return _overrideIndex; } - void setOverrideIndex(int idx) - { - Q_ASSERT(idx >= std::numeric_limits::min()); - Q_ASSERT(idx <= std::numeric_limits::max()); - _overrideIndex = qint16(idx); - } - - int coreIndex() const { return _coreIndex; } - void setCoreIndex(int idx) - { - Q_ASSERT(idx >= std::numeric_limits::min()); - Q_ASSERT(idx <= std::numeric_limits::max()); - _coreIndex = qint16(idx); - } - - quint8 revision() const { return _revision; } - void setRevision(quint8 rev) - { - Q_ASSERT(rev <= std::numeric_limits::max()); - _revision = quint8(rev); - } - - /* If a property is a C++ type, then we store the minor - * version of this type. - * This is required to resolve property or signal revisions - * if this property is used as a grouped property. - * - * Test.qml - * property TextEdit someTextEdit: TextEdit {} - * - * Test { - * someTextEdit.preeditText: "test" //revision 7 - * someTextEdit.onEditingFinished: console.log("test") //revision 6 - * } - * - * To determine if these properties with revisions are available we need - * the minor version of TextEdit as imported in Test.qml. - * - */ - - quint8 typeMinorVersion() const { return _typeMinorVersion; } - void setTypeMinorVersion(quint8 rev) - { - Q_ASSERT(rev <= std::numeric_limits::max()); - _typeMinorVersion = quint8(rev); - } - - QQmlPropertyCacheMethodArguments *arguments() const { return _arguments; } - void setArguments(QQmlPropertyCacheMethodArguments *args) { _arguments = args; } - - int metaObjectOffset() const { return _metaObjectOffset; } - void setMetaObjectOffset(int off) - { - Q_ASSERT(off >= std::numeric_limits::min()); - Q_ASSERT(off <= std::numeric_limits::max()); - _metaObjectOffset = qint16(off); - } - - StaticMetaCallFunction staticMetaCallFunction() const { return _staticMetaCallFunction; } - void trySetStaticMetaCallFunction(StaticMetaCallFunction f, unsigned relativePropertyIndex) - { - if (relativePropertyIndex < (1 << Flags::BitsLeftInFlags) - 1) { - _flags._otherBits = relativePropertyIndex; - _staticMetaCallFunction = f; - } - } - quint16 relativePropertyIndex() const { Q_ASSERT(hasStaticMetaCallFunction()); return _flags._otherBits; } - -private: - Flags _flags; - qint16 _coreIndex = 0; - quint16 _propType = 0; - - // The notify index is in the range returned by QObjectPrivate::signalIndex(). - // This is different from QMetaMethod::methodIndex(). - qint16 _notifyIndex = 0; - qint16 _overrideIndex = 0; - - quint8 _revision = 0; - quint8 _typeMinorVersion = 0; - qint16 _metaObjectOffset = 0; - - QQmlPropertyCacheMethodArguments *_arguments = nullptr; - StaticMetaCallFunction _staticMetaCallFunction = nullptr; - - friend class QQmlPropertyData; - friend class QQmlPropertyCache; -}; - -#if QT_POINTER_SIZE == 4 - Q_STATIC_ASSERT(sizeof(QQmlPropertyRawData) == 24); -#else // QT_POINTER_SIZE == 8 - Q_STATIC_ASSERT(sizeof(QQmlPropertyRawData) == 32); -#endif - -QQmlPropertyRawData::Flags::Flags() - : _otherBits(0) - , isConstant(false) - , isWritable(false) - , isResettable(false) - , isAlias(false) - , isFinal(false) - , isOverridden(false) - , isDirect(false) - , type(OtherType) - , isVMEFunction(false) - , hasArguments(false) - , isSignal(false) - , isVMESignal(false) - , isV4Function(false) - , isSignalHandler(false) - , isOverload(false) - , isCloned(false) - , isConstructor(false) - , notFullyResolved(false) - , overrideIndexIsProperty(false) -{} - -bool QQmlPropertyRawData::Flags::operator==(const QQmlPropertyRawData::Flags &other) const -{ - return isConstant == other.isConstant && - isWritable == other.isWritable && - isResettable == other.isResettable && - isAlias == other.isAlias && - isFinal == other.isFinal && - isOverridden == other.isOverridden && - type == other.type && - isVMEFunction == other.isVMEFunction && - hasArguments == other.hasArguments && - isSignal == other.isSignal && - isVMESignal == other.isVMESignal && - isV4Function == other.isV4Function && - isSignalHandler == other.isSignalHandler && - isOverload == other.isOverload && - isCloned == other.isCloned && - isConstructor == other.isConstructor && - notFullyResolved == other.notFullyResolved && - overrideIndexIsProperty == other.overrideIndexIsProperty; -} - -void QQmlPropertyRawData::Flags::copyPropertyTypeFlags(QQmlPropertyRawData::Flags from) -{ - switch (from.type) { - case QObjectDerivedType: - case EnumType: - case QListType: - case QmlBindingType: - case QJSValueType: - case QVariantType: - type = from.type; - } -} - -QT_END_NAMESPACE - -#endif // QQMLPROPERTYRAWDATA_P_H -- cgit v1.2.3 From 8a2b0432373f398776c9da277b62ccd912292a73 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 27 Feb 2019 11:36:51 +0100 Subject: Don't needlessly restrict Qt.include() in case !qml-network Change-Id: I1cc23941121a5b2b16e94fc46e48bdce1f452afb Reviewed-by: Erik Verbruggen --- src/qml/jsruntime/qv4include.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'src/qml') diff --git a/src/qml/jsruntime/qv4include.cpp b/src/qml/jsruntime/qv4include.cpp index 36569b0a60..3c732bc555 100644 --- a/src/qml/jsruntime/qv4include.cpp +++ b/src/qml/jsruntime/qv4include.cpp @@ -214,7 +214,6 @@ QV4::ReturnedValue QV4Include::method_include(const QV4::FunctionObject *b, cons if (argc >= 2 && argv[1].as()) callbackFunction = argv[1]; -#if QT_CONFIG(qml_network) QUrl url(scope.engine->resolvedUrl(argv[0].toQStringNoThrow())); if (scope.engine->qmlEngine() && scope.engine->qmlEngine()->urlInterceptor()) url = scope.engine->qmlEngine()->urlInterceptor()->intercept(url, QQmlAbstractUrlInterceptor::JavaScriptFile); @@ -225,9 +224,13 @@ QV4::ReturnedValue QV4Include::method_include(const QV4::FunctionObject *b, cons QV4::Scoped qmlcontext(scope, scope.engine->qmlContext()); if (localFile.isEmpty()) { +#if QT_CONFIG(qml_network) QV4Include *i = new QV4Include(url, scope.engine, qmlcontext, callbackFunction); result = i->result(); - +#else + result = resultValue(scope.engine, NetworkError); + callback(callbackFunction, result); +#endif } else { QScopedPointer script; QString error; @@ -252,12 +255,6 @@ QV4::ReturnedValue QV4Include::method_include(const QV4::FunctionObject *b, cons callback(callbackFunction, result); } -#else - QV4::ScopedValue result(scope); - result = resultValue(scope.engine, NetworkError); - callback(callbackFunction, result); -#endif - return result->asReturnedValue(); } -- cgit v1.2.3 From c00283bb3bb966bf60c307ec8283bd98c12318bf Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Tue, 2 Apr 2019 15:46:56 +0200 Subject: Deduplicate QQmlPropertyCache::l{azyL}oad Change-Id: I0ca6d168abc076f93405791059db73667afa6009 Reviewed-by: Erik Verbruggen --- src/qml/qml/qqmlpropertycache.cpp | 56 +++++++++++---------------------------- 1 file changed, 16 insertions(+), 40 deletions(-) (limited to 'src/qml') diff --git a/src/qml/qml/qqmlpropertycache.cpp b/src/qml/qml/qqmlpropertycache.cpp index fb8136abb4..a6546ae330 100644 --- a/src/qml/qml/qqmlpropertycache.cpp +++ b/src/qml/qml/qqmlpropertycache.cpp @@ -124,15 +124,18 @@ QQmlPropertyData::flagsForProperty(const QMetaProperty &p) return flags; } -void QQmlPropertyData::lazyLoad(const QMetaProperty &p) +static void populate(QQmlPropertyData *data, const QMetaProperty &p) { - setCoreIndex(p.propertyIndex()); - setNotifyIndex(QMetaObjectPrivate::signalIndex(p.notifySignal())); Q_ASSERT(p.revision() <= Q_INT16_MAX); - setRevision(p.revision()); - - setFlags(fastFlagsForProperty(p)); + data->setCoreIndex(p.propertyIndex()); + data->setNotifyIndex(QMetaObjectPrivate::signalIndex(p.notifySignal())); + data->setFlags(fastFlagsForProperty(p)); + data->setRevision(p.revision()); +} +void QQmlPropertyData::lazyLoad(const QMetaProperty &p) +{ + populate(this, p); int type = static_cast(p.type()); if (type == QMetaType::QObjectStar) { setPropType(type); @@ -149,13 +152,9 @@ void QQmlPropertyData::lazyLoad(const QMetaProperty &p) void QQmlPropertyData::load(const QMetaProperty &p) { + populate(this, p); setPropType(p.userType()); - setCoreIndex(p.propertyIndex()); - setNotifyIndex(QMetaObjectPrivate::signalIndex(p.notifySignal())); - setFlags(fastFlagsForProperty(p)); flagsForPropertyType(propType(), m_flags); - Q_ASSERT(p.revision() <= Q_INT16_MAX); - setRevision(p.revision()); } void QQmlPropertyData::load(const QMetaMethod &m) @@ -166,18 +165,18 @@ void QQmlPropertyData::load(const QMetaMethod &m) setPropType(m.returnType()); m_flags.type = Flags::FunctionType; - if (m.methodType() == QMetaMethod::Signal) + if (m.methodType() == QMetaMethod::Signal) { m_flags.isSignal = true; - else if (m.methodType() == QMetaMethod::Constructor) { + } else if (m.methodType() == QMetaMethod::Constructor) { m_flags.isConstructor = true; setPropType(QMetaType::QObjectStar); } - if (m.parameterCount()) { + const int paramCount = m.parameterCount(); + if (paramCount) { m_flags.hasArguments = true; - if ((m.parameterCount() == 1) && (m.parameterTypes().constFirst() == "QQmlV4Function*")) { + if ((paramCount == 1) && (m.parameterTypes().constFirst() == "QQmlV4Function*")) m_flags.isV4Function = true; - } } if (m.attributes() & QMetaMethod::Cloned) @@ -189,16 +188,7 @@ void QQmlPropertyData::load(const QMetaMethod &m) void QQmlPropertyData::lazyLoad(const QMetaMethod &m) { - setCoreIndex(m.methodIndex()); - setPropType(QMetaType::Void); - setArguments(nullptr); - m_flags.type = Flags::FunctionType; - if (m.methodType() == QMetaMethod::Signal) - m_flags.isSignal = true; - else if (m.methodType() == QMetaMethod::Constructor) { - m_flags.isConstructor = true; - setPropType(QMetaType::QObjectStar); - } + load(m); const char *returnType = m.typeName(); if (!returnType) @@ -206,20 +196,6 @@ void QQmlPropertyData::lazyLoad(const QMetaMethod &m) if ((*returnType != 'v') || (qstrcmp(returnType+1, "oid") != 0)) { m_flags.notFullyResolved = true; } - - const int paramCount = m.parameterCount(); - if (paramCount) { - m_flags.hasArguments = true; - if ((paramCount == 1) && (m.parameterTypes().constFirst() == "QQmlV4Function*")) { - m_flags.isV4Function = true; - } - } - - if (m.attributes() & QMetaMethod::Cloned) - m_flags.isCloned = true; - - Q_ASSERT(m.revision() <= Q_INT16_MAX); - setRevision(m.revision()); } /*! -- cgit v1.2.3